blob: ad8915b54d6ec00f541db37c6fe52f821596188c [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
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +0200160class GNException(Exception):
161 pass
162
163
164def ToGNString(value, allow_dicts = True):
165 """Returns a stringified GN equivalent of the Python value.
166
167 allow_dicts indicates if this function will allow converting dictionaries
168 to GN scopes. This is only possible at the top level, you can't nest a
169 GN scope in a list, so this should be set to False for recursive calls."""
170 if isinstance(value, basestring):
171 if value.find('\n') >= 0:
172 raise GNException("Trying to print a string with a newline in it.")
173 return '"' + \
174 value.replace('\\', '\\\\').replace('"', '\\"').replace('$', '\\$') + \
175 '"'
176
177 if isinstance(value, unicode):
178 return ToGNString(value.encode('utf-8'))
179
180 if isinstance(value, bool):
181 if value:
182 return "true"
183 return "false"
184
185 # NOTE: some type handling removed compared to chromium/src copy.
186
187 raise GNException("Unsupported type when printing to GN.")
188
189
maruel@chromium.org116704f2010-06-11 17:34:38 +0000190class GClientKeywords(object):
maruel@chromium.org116704f2010-06-11 17:34:38 +0000191 class VarImpl(object):
192 def __init__(self, custom_vars, local_scope):
193 self._custom_vars = custom_vars
194 self._local_scope = local_scope
195
196 def Lookup(self, var_name):
197 """Implements the Var syntax."""
198 if var_name in self._custom_vars:
199 return self._custom_vars[var_name]
200 elif var_name in self._local_scope.get("vars", {}):
201 return self._local_scope["vars"][var_name]
202 raise gclient_utils.Error("Var is not defined: %s" % var_name)
203
204
maruel@chromium.org064186c2011-09-27 23:53:33 +0000205class DependencySettings(GClientKeywords):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000206 """Immutable configuration settings."""
207 def __init__(
agablea98a6cd2016-11-15 14:30:10 -0800208 self, parent, url, managed, custom_deps, custom_vars,
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +0200209 custom_hooks, deps_file, should_process, relative,
210 condition, condition_value):
maruel@chromium.org064186c2011-09-27 23:53:33 +0000211 GClientKeywords.__init__(self)
212
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000213 # These are not mutable:
214 self._parent = parent
mmoss@chromium.org8f93f792014-08-26 23:24:09 +0000215 self._deps_file = deps_file
maruel@chromium.org064186c2011-09-27 23:53:33 +0000216 self._url = url
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +0200217 # The condition as string (or None). Useful to keep e.g. for flatten.
218 self._condition = condition
219 # Boolean value of the condition. If there's no condition, just True.
220 self._condition_value = condition_value
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000221 # 'managed' determines whether or not this dependency is synced/updated by
222 # gclient after gclient checks it out initially. The difference between
223 # 'managed' and 'should_process' is that the user specifies 'managed' via
smutae7ea312016-07-18 11:59:41 -0700224 # the --unmanaged command-line flag or a .gclient config, where
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000225 # 'should_process' is dynamically set by gclient if it goes over its
226 # recursion limit and controls gclient's behavior so it does not misbehave.
227 self._managed = managed
228 self._should_process = should_process
agabledce6ddc2016-09-08 10:02:16 -0700229 # If this is a recursed-upon sub-dependency, and the parent has
230 # use_relative_paths set, then this dependency should check out its own
231 # dependencies relative to that parent's path for this, rather than
232 # relative to the .gclient file.
233 self._relative = relative
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +0000234 # This is a mutable value which has the list of 'target_os' OSes listed in
235 # the current deps file.
236 self.local_target_os = None
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000237
238 # These are only set in .gclient and not in DEPS files.
239 self._custom_vars = custom_vars or {}
240 self._custom_deps = custom_deps or {}
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000241 self._custom_hooks = custom_hooks or []
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000242
maruel@chromium.org064186c2011-09-27 23:53:33 +0000243 # Post process the url to remove trailing slashes.
244 if isinstance(self._url, basestring):
245 # urls are sometime incorrectly written as proto://host/path/@rev. Replace
246 # it to proto://host/path@rev.
maruel@chromium.org064186c2011-09-27 23:53:33 +0000247 self._url = self._url.replace('/@', '@')
Paweł Hajdan, Jr7e9303b2017-05-23 14:38:27 +0200248 elif not isinstance(self._url, (None.__class__)):
maruel@chromium.org064186c2011-09-27 23:53:33 +0000249 raise gclient_utils.Error(
Paweł Hajdan, Jr7e9303b2017-05-23 14:38:27 +0200250 ('dependency url must be either string or None, '
251 'instead of %s') % self._url.__class__.__name__)
mmoss@chromium.orgd0b272b2013-01-30 23:55:33 +0000252 # Make any deps_file path platform-appropriate.
253 for sep in ['/', '\\']:
254 self._deps_file = self._deps_file.replace(sep, os.sep)
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000255
256 @property
257 def deps_file(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000258 return self._deps_file
259
260 @property
261 def managed(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000262 return self._managed
263
264 @property
265 def parent(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000266 return self._parent
267
268 @property
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000269 def root(self):
270 """Returns the root node, a GClient object."""
271 if not self.parent:
272 # This line is to signal pylint that it could be a GClient instance.
273 return self or GClient(None, None)
274 return self.parent.root
275
276 @property
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000277 def should_process(self):
278 """True if this dependency should be processed, i.e. checked out."""
279 return self._should_process
280
281 @property
282 def custom_vars(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000283 return self._custom_vars.copy()
284
285 @property
286 def custom_deps(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000287 return self._custom_deps.copy()
288
maruel@chromium.org064186c2011-09-27 23:53:33 +0000289 @property
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000290 def custom_hooks(self):
291 return self._custom_hooks[:]
292
293 @property
maruel@chromium.org064186c2011-09-27 23:53:33 +0000294 def url(self):
295 return self._url
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000296
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000297 @property
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +0200298 def condition(self):
299 return self._condition
300
301 @property
302 def condition_value(self):
303 return self._condition_value
304
305 @property
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +0000306 def target_os(self):
307 if self.local_target_os is not None:
308 return tuple(set(self.local_target_os).union(self.parent.target_os))
309 else:
310 return self.parent.target_os
311
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000312 def get_custom_deps(self, name, url):
313 """Returns a custom deps if applicable."""
314 if self.parent:
315 url = self.parent.get_custom_deps(name, url)
316 # None is a valid return value to disable a dependency.
317 return self.custom_deps.get(name, url)
318
maruel@chromium.org064186c2011-09-27 23:53:33 +0000319
320class Dependency(gclient_utils.WorkItem, DependencySettings):
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000321 """Object that represents a dependency checkout."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000322
agablea98a6cd2016-11-15 14:30:10 -0800323 def __init__(self, parent, name, url, managed, custom_deps,
agabledce6ddc2016-09-08 10:02:16 -0700324 custom_vars, custom_hooks, deps_file, should_process,
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +0200325 relative, condition, condition_value):
maruel@chromium.org6ca8bf82011-09-19 23:04:30 +0000326 gclient_utils.WorkItem.__init__(self, name)
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000327 DependencySettings.__init__(
agablea98a6cd2016-11-15 14:30:10 -0800328 self, parent, url, managed, custom_deps, custom_vars,
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +0200329 custom_hooks, deps_file, should_process, relative,
330 condition, condition_value)
maruel@chromium.org68988972011-09-20 14:11:42 +0000331
332 # This is in both .gclient and DEPS files:
maruel@chromium.org064186c2011-09-27 23:53:33 +0000333 self._deps_hooks = []
maruel@chromium.org68988972011-09-20 14:11:42 +0000334
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000335 self._pre_deps_hooks = []
336
maruel@chromium.org68988972011-09-20 14:11:42 +0000337 # Calculates properties:
maruel@chromium.org064186c2011-09-27 23:53:33 +0000338 self._parsed_url = None
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +0000339 self._dependencies = []
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +0200340 self._vars = {}
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000341 # A cache of the files affected by the current operation, necessary for
342 # hooks.
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000343 self._file_list = []
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000344 # List of host names from which dependencies are allowed.
345 # Default is an empty set, meaning unspecified in DEPS file, and hence all
346 # hosts will be allowed. Non-empty set means whitelist of hosts.
347 # allowed_hosts var is scoped to its DEPS file, and so it isn't recursive.
348 self._allowed_hosts = frozenset()
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +0200349 # Spec for .gni output to write (if any).
350 self._gn_args_file = None
351 self._gn_args = []
maruel@chromium.org85c2a192010-07-22 21:14:43 +0000352 # If it is not set to True, the dependency wasn't processed for its child
353 # dependency, i.e. its DEPS wasn't read.
maruel@chromium.org064186c2011-09-27 23:53:33 +0000354 self._deps_parsed = False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000355 # This dependency has been processed, i.e. checked out
maruel@chromium.org064186c2011-09-27 23:53:33 +0000356 self._processed = False
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000357 # This dependency had its pre-DEPS hooks run
358 self._pre_deps_hooks_ran = False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000359 # This dependency had its hook run
maruel@chromium.org064186c2011-09-27 23:53:33 +0000360 self._hooks_ran = False
kustermann@google.coma692e8f2013-04-18 08:32:04 +0000361 # This is the scm used to checkout self.url. It may be used by dependencies
362 # to get the datetime of the revision we checked out.
363 self._used_scm = None
szager@chromium.org4ad264b2014-05-20 04:43:47 +0000364 self._used_revision = None
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +0000365 # The actual revision we ended up getting, or None if that information is
366 # unavailable
367 self._got_revision = None
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000368
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000369 # This is a mutable value that overrides the normal recursion limit for this
370 # dependency. It is read from the actual DEPS file so cannot be set on
371 # class instantiation.
372 self.recursion_override = None
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000373 # recursedeps is a mutable value that selectively overrides the default
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000374 # 'no recursion' setting on a dep-by-dep basis. It will replace
375 # recursion_override.
iannucci@chromium.orgafa11ac2016-05-04 22:17:34 +0000376 #
377 # It will be a dictionary of {deps_name: {"deps_file": depfile_name}} or
378 # None.
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000379 self.recursedeps = None
hinoka885e5b12016-06-08 14:40:09 -0700380 # This is inherited from WorkItem. We want the URL to be a resource.
381 if url and isinstance(url, basestring):
382 # The url is usually given to gclient either as https://blah@123
qyearsley12fa6ff2016-08-24 09:18:40 -0700383 # or just https://blah. The @123 portion is irrelevant.
hinoka885e5b12016-06-08 14:40:09 -0700384 self.resources.append(url.split('@')[0])
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000385
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000386 if not self.name and self.parent:
387 raise gclient_utils.Error('Dependency without name')
388
maruel@chromium.org470b5432011-10-11 18:18:19 +0000389 @property
390 def requirements(self):
391 """Calculate the list of requirements."""
392 requirements = set()
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000393 # self.parent is implicitly a requirement. This will be recursive by
394 # definition.
395 if self.parent and self.parent.name:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000396 requirements.add(self.parent.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000397
398 # For a tree with at least 2 levels*, the leaf node needs to depend
399 # on the level higher up in an orderly way.
400 # This becomes messy for >2 depth as the DEPS file format is a dictionary,
401 # thus unsorted, while the .gclient format is a list thus sorted.
402 #
403 # * _recursion_limit is hard coded 2 and there is no hope to change this
404 # value.
405 #
406 # Interestingly enough, the following condition only works in the case we
407 # want: self is a 2nd level node. 3nd level node wouldn't need this since
408 # they already have their parent as a requirement.
maruel@chromium.org470b5432011-10-11 18:18:19 +0000409 if self.parent and self.parent.parent and not self.parent.parent.parent:
410 requirements |= set(i.name for i in self.root.dependencies if i.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000411
maruel@chromium.org470b5432011-10-11 18:18:19 +0000412 if self.name:
413 requirements |= set(
414 obj.name for obj in self.root.subtree(False)
415 if (obj is not self
416 and obj.name and
417 self.name.startswith(posixpath.join(obj.name, ''))))
418 requirements = tuple(sorted(requirements))
419 logging.info('Dependency(%s).requirements = %s' % (self.name, requirements))
420 return requirements
421
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000422 @property
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000423 def try_recursedeps(self):
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000424 """Returns False if recursion_override is ever specified."""
425 if self.recursion_override is not None:
426 return False
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000427 return self.parent.try_recursedeps
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000428
429 @property
430 def recursion_limit(self):
431 """Returns > 0 if this dependency is not too recursed to be processed."""
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000432 # We continue to support the absence of recursedeps until tools and DEPS
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000433 # using recursion_override are updated.
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000434 if self.try_recursedeps and self.parent.recursedeps != None:
435 if self.name in self.parent.recursedeps:
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000436 return 1
437
438 if self.recursion_override is not None:
439 return self.recursion_override
440 return max(self.parent.recursion_limit - 1, 0)
441
maruel@chromium.org470b5432011-10-11 18:18:19 +0000442 def verify_validity(self):
443 """Verifies that this Dependency is fine to add as a child of another one.
444
445 Returns True if this entry should be added, False if it is a duplicate of
446 another entry.
447 """
448 logging.info('Dependency(%s).verify_validity()' % self.name)
449 if self.name in [s.name for s in self.parent.dependencies]:
450 raise gclient_utils.Error(
451 'The same name "%s" appears multiple times in the deps section' %
452 self.name)
453 if not self.should_process:
454 # Return early, no need to set requirements.
455 return True
456
457 # This require a full tree traversal with locks.
458 siblings = [d for d in self.root.subtree(False) if d.name == self.name]
459 for sibling in siblings:
maruel@chromium.orgb848d5b2012-10-10 23:25:50 +0000460 self_url = self.LateOverride(self.url)
461 sibling_url = sibling.LateOverride(sibling.url)
462 # Allow to have only one to be None or ''.
463 if self_url != sibling_url and bool(self_url) == bool(sibling_url):
maruel@chromium.org470b5432011-10-11 18:18:19 +0000464 raise gclient_utils.Error(
maruel@chromium.orgb848d5b2012-10-10 23:25:50 +0000465 ('Dependency %s specified more than once:\n'
466 ' %s [%s]\n'
467 'vs\n'
468 ' %s [%s]') % (
469 self.name,
470 sibling.hierarchy(),
471 sibling_url,
472 self.hierarchy(),
473 self_url))
maruel@chromium.org470b5432011-10-11 18:18:19 +0000474 # In theory we could keep it as a shadow of the other one. In
475 # practice, simply ignore it.
476 logging.warn('Won\'t process duplicate dependency %s' % sibling)
477 return False
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000478 return True
maruel@chromium.org064186c2011-09-27 23:53:33 +0000479
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000480 def LateOverride(self, url):
Paweł Hajdan, Jr7e9303b2017-05-23 14:38:27 +0200481 """Resolves the parsed url from url."""
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000482 assert self.parsed_url == None or not self.should_process, self.parsed_url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000483 parsed_url = self.get_custom_deps(self.name, url)
484 if parsed_url != url:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000485 logging.info(
486 'Dependency(%s).LateOverride(%s) -> %s' %
487 (self.name, url, parsed_url))
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000488 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 = {
iannucci@chromium.org0c3f3052014-05-22 00:29:22 +0000601 'Var': var.Lookup,
602 'deps_os': {},
603 }
maruel@chromium.org46304292010-10-28 11:42:00 +0000604 # Eval the content.
605 try:
Paweł Hajdan, Jrc485d5a2017-06-02 12:08:09 +0200606 if self._get_option('validate_syntax', False):
607 gclient_eval.Exec(deps_content, global_scope, local_scope, filepath)
608 else:
609 exec(deps_content, global_scope, local_scope)
vapier@chromium.orga81a56e2015-11-11 07:56:13 +0000610 except SyntaxError as e:
maruel@chromium.org46304292010-10-28 11:42:00 +0000611 gclient_utils.SyntaxErrorToError(filepath, e)
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
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +0200619 self._vars = local_scope.get('vars', {})
620
maruel@chromium.org271375b2010-06-23 19:17:38 +0000621 deps = local_scope.get('deps', {})
ilevy@chromium.org27ca3a92012-10-17 18:11:02 +0000622 if 'recursion' in local_scope:
623 self.recursion_override = local_scope.get('recursion')
624 logging.warning(
625 'Setting %s recursion to %d.', self.name, self.recursion_limit)
iannucci@chromium.orgafa11ac2016-05-04 22:17:34 +0000626 self.recursedeps = None
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000627 if 'recursedeps' in local_scope:
iannucci@chromium.orgafa11ac2016-05-04 22:17:34 +0000628 self.recursedeps = {}
629 for ent in local_scope['recursedeps']:
630 if isinstance(ent, basestring):
631 self.recursedeps[ent] = {"deps_file": self.deps_file}
632 else: # (depname, depsfilename)
633 self.recursedeps[ent[0]] = {"deps_file": ent[1]}
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000634 logging.warning('Found recursedeps %r.', repr(self.recursedeps))
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +0000635 # If present, save 'target_os' in the local_target_os property.
636 if 'target_os' in local_scope:
637 self.local_target_os = local_scope['target_os']
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000638 # load os specific dependencies if defined. these dependencies may
639 # override or extend the values defined by the 'deps' member.
bratell@opera.comed2b4fe2013-12-16 14:34:12 +0000640 target_os_list = self.target_os
641 if 'deps_os' in local_scope and target_os_list:
642 deps = self.MergeWithOsDeps(deps, local_scope['deps_os'], target_os_list)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000643
maruel@chromium.org271375b2010-06-23 19:17:38 +0000644 # If a line is in custom_deps, but not in the solution, we want to append
645 # this line to the solution.
646 for d in self.custom_deps:
647 if d not in deps:
648 deps[d] = self.custom_deps[d]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000649
650 # If use_relative_paths is set in the DEPS file, regenerate
651 # the dictionary using paths relative to the directory containing
cmp@chromium.orgf2def0a2014-07-16 19:48:54 +0000652 # the DEPS file. Also update recursedeps if use_relative_paths is
653 # enabled.
agabledce6ddc2016-09-08 10:02:16 -0700654 # If the deps file doesn't set use_relative_paths, but the parent did
655 # (and therefore set self.relative on this Dependency object), then we
656 # want to modify the deps and recursedeps by prepending the parent
657 # directory of this dependency.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000658 use_relative_paths = local_scope.get('use_relative_paths', False)
agabledce6ddc2016-09-08 10:02:16 -0700659 rel_prefix = None
maruel@chromium.org271375b2010-06-23 19:17:38 +0000660 if use_relative_paths:
agabledce6ddc2016-09-08 10:02:16 -0700661 rel_prefix = self.name
662 elif self._relative:
663 rel_prefix = os.path.dirname(self.name)
664 if rel_prefix:
cmp@chromium.orgf2def0a2014-07-16 19:48:54 +0000665 logging.warning('use_relative_paths enabled.')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000666 rel_deps = {}
667 for d, url in deps.items():
668 # normpath is required to allow DEPS to use .. in their
669 # dependency local path.
agabledce6ddc2016-09-08 10:02:16 -0700670 rel_deps[os.path.normpath(os.path.join(rel_prefix, d))] = url
671 logging.warning('Updating deps by prepending %s.', rel_prefix)
maruel@chromium.org271375b2010-06-23 19:17:38 +0000672 deps = rel_deps
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000673
cmp@chromium.orgf2def0a2014-07-16 19:48:54 +0000674 # Update recursedeps if it's set.
675 if self.recursedeps is not None:
agabledce6ddc2016-09-08 10:02:16 -0700676 logging.warning('Updating recursedeps by prepending %s.', rel_prefix)
iannucci@chromium.orgafa11ac2016-05-04 22:17:34 +0000677 rel_deps = {}
678 for depname, options in self.recursedeps.iteritems():
agabledce6ddc2016-09-08 10:02:16 -0700679 rel_deps[
680 os.path.normpath(os.path.join(rel_prefix, depname))] = options
cmp@chromium.orgf2def0a2014-07-16 19:48:54 +0000681 self.recursedeps = rel_deps
682
agabledce6ddc2016-09-08 10:02:16 -0700683
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000684 if 'allowed_hosts' in local_scope:
685 try:
686 self._allowed_hosts = frozenset(local_scope.get('allowed_hosts'))
687 except TypeError: # raised if non-iterable
688 pass
689 if not self._allowed_hosts:
690 logging.warning("allowed_hosts is specified but empty %s",
691 self._allowed_hosts)
692 raise gclient_utils.Error(
693 'ParseDepsFile(%s): allowed_hosts must be absent '
694 'or a non-empty iterable' % self.name)
695
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +0200696 self._gn_args_file = local_scope.get('gclient_gn_args_file')
697 self._gn_args = local_scope.get('gclient_gn_args', [])
698
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000699 # Convert the deps into real Dependency.
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000700 deps_to_add = []
Paweł Hajdan, Jrc7ba0332017-05-29 16:38:45 +0200701 for name, dep_value in deps.iteritems():
maruel@chromium.org68988972011-09-20 14:11:42 +0000702 should_process = self.recursion_limit and self.should_process
iannucci@chromium.orgafa11ac2016-05-04 22:17:34 +0000703 deps_file = self.deps_file
704 if self.recursedeps is not None:
705 ent = self.recursedeps.get(name)
706 if ent is not None:
707 deps_file = ent['deps_file']
Paweł Hajdan, Jr11016452017-05-29 18:02:15 +0200708 if dep_value is None:
709 continue
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +0200710 condition = None
711 condition_value = True
Paweł Hajdan, Jrc7ba0332017-05-29 16:38:45 +0200712 if isinstance(dep_value, basestring):
713 url = dep_value
714 else:
715 # This should be guaranteed by schema checking in gclient_eval.
716 assert isinstance(dep_value, dict)
717 url = dep_value['url']
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +0200718 condition = dep_value.get('condition')
719 if condition:
720 # TODO(phajdan.jr): should we also take custom vars into account?
721 condition_value = gclient_eval.EvaluateCondition(
722 condition, local_scope.get('vars', {}))
723 should_process = should_process and condition_value
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000724 deps_to_add.append(Dependency(
agablea98a6cd2016-11-15 14:30:10 -0800725 self, name, url, None, None, self.custom_vars, None,
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +0200726 deps_file, should_process, use_relative_paths, condition,
727 condition_value))
maruel@chromium.orgb9be0652011-10-14 18:05:40 +0000728 deps_to_add.sort(key=lambda x: x.name)
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000729
730 # override named sets of hooks by the custom hooks
731 hooks_to_run = []
732 hook_names_to_suppress = [c.get('name', '') for c in self.custom_hooks]
733 for hook in local_scope.get('hooks', []):
734 if hook.get('name', '') not in hook_names_to_suppress:
735 hooks_to_run.append(hook)
Scott Grahamc4826742017-05-11 16:59:23 -0700736 if 'hooks_os' in local_scope and target_os_list:
737 hooks_os = local_scope['hooks_os']
738 # Specifically append these to ensure that hooks_os run after hooks.
739 for the_target_os in target_os_list:
740 the_target_os_hooks = hooks_os.get(the_target_os, [])
741 hooks_to_run.extend(the_target_os_hooks)
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000742
743 # add the replacements and any additions
744 for hook in self.custom_hooks:
745 if 'action' in hook:
746 hooks_to_run.append(hook)
747
Dirk Prankeda3a29e2017-02-27 15:29:36 -0800748 if self.recursion_limit:
Paweł Hajdan, Jr35b298f2017-05-23 14:37:05 +0200749 self._pre_deps_hooks = [self.GetHookAction(hook) for hook in
Dirk Prankeda3a29e2017-02-27 15:29:36 -0800750 local_scope.get('pre_deps_hooks', [])]
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000751
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000752 self.add_dependencies_and_close(deps_to_add, hooks_to_run)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000753 logging.info('ParseDepsFile(%s) done' % self.name)
754
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +0200755 def _get_option(self, attr, default):
756 obj = self
757 while not hasattr(obj, '_options'):
758 obj = obj.parent
759 return getattr(obj._options, attr, default)
760
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000761 def add_dependencies_and_close(self, deps_to_add, hooks):
762 """Adds the dependencies, hooks and mark the parsing as done."""
maruel@chromium.orgb9be0652011-10-14 18:05:40 +0000763 for dep in deps_to_add:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000764 if dep.verify_validity():
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000765 self.add_dependency(dep)
766 self._mark_as_parsed(hooks)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000767
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000768 def findDepsFromNotAllowedHosts(self):
769 """Returns a list of depenecies from not allowed hosts.
770
771 If allowed_hosts is not set, allows all hosts and returns empty list.
772 """
773 if not self._allowed_hosts:
774 return []
775 bad_deps = []
776 for dep in self._dependencies:
szager@chromium.orgbd772dd2014-11-05 18:43:08 +0000777 # Don't enforce this for custom_deps.
778 if dep.name in self._custom_deps:
779 continue
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000780 if isinstance(dep.url, basestring):
781 parsed_url = urlparse.urlparse(dep.url)
782 if parsed_url.netloc and parsed_url.netloc not in self._allowed_hosts:
783 bad_deps.append(dep)
784 return bad_deps
785
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +0000786 # Arguments number differs from overridden method
Quinten Yearsleyb2cc4a92016-12-15 13:53:26 -0800787 # pylint: disable=arguments-differ
maruel@chromium.org3742c842010-09-09 19:27:14 +0000788 def run(self, revision_overrides, command, args, work_queue, options):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000789 """Runs |command| then parse the DEPS file."""
maruel@chromium.org470b5432011-10-11 18:18:19 +0000790 logging.info('Dependency(%s).run()' % self.name)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000791 assert self._file_list == []
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000792 if not self.should_process:
793 return
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000794 # When running runhooks, there's no need to consult the SCM.
795 # All known hooks are expected to run unconditionally regardless of working
796 # copy state, so skip the SCM status check.
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +0200797 run_scm = command not in (
798 'flatten', 'runhooks', 'recurse', 'validate', None)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000799 parsed_url = self.LateOverride(self.url)
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000800 file_list = [] if not options.nohooks else None
szager@chromium.org3a3608d2014-10-22 21:13:52 +0000801 revision_override = revision_overrides.pop(self.name, None)
Dave Tubbda9712017-06-01 15:10:53 -0700802 if not revision_override and parsed_url:
803 revision_override = revision_overrides.get(parsed_url.split('@')[0], None)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000804 if run_scm and parsed_url:
agabled437d762016-10-17 09:35:11 -0700805 # Create a shallow copy to mutate revision.
806 options = copy.copy(options)
807 options.revision = revision_override
808 self._used_revision = options.revision
809 self._used_scm = gclient_scm.CreateSCM(
810 parsed_url, self.root.root_dir, self.name, self.outbuf,
811 out_cb=work_queue.out_cb)
812 self._got_revision = self._used_scm.RunCommand(command, options, args,
813 file_list)
814 if file_list:
815 file_list = [os.path.join(self.name, f.strip()) for f in file_list]
maruel@chromium.org68988972011-09-20 14:11:42 +0000816
817 # TODO(phajdan.jr): We should know exactly when the paths are absolute.
818 # Convert all absolute paths to relative.
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000819 for i in range(len(file_list or [])):
maruel@chromium.org68988972011-09-20 14:11:42 +0000820 # It depends on the command being executed (like runhooks vs sync).
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000821 if not os.path.isabs(file_list[i]):
maruel@chromium.org68988972011-09-20 14:11:42 +0000822 continue
823 prefix = os.path.commonprefix(
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000824 [self.root.root_dir.lower(), file_list[i].lower()])
825 file_list[i] = file_list[i][len(prefix):]
maruel@chromium.org68988972011-09-20 14:11:42 +0000826 # Strip any leading path separators.
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000827 while file_list[i].startswith(('\\', '/')):
828 file_list[i] = file_list[i][1:]
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000829
830 # Always parse the DEPS file.
831 self.ParseDepsFile()
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +0200832 if self._gn_args_file and command == 'update':
833 self.WriteGNArgsFile()
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000834 self._run_is_done(file_list or [], parsed_url)
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000835 if command in ('update', 'revert') and not options.noprehooks:
836 self.RunPreDepsHooks()
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000837
838 if self.recursion_limit:
839 # Parse the dependencies of this dependency.
840 for s in self.dependencies:
841 work_queue.enqueue(s)
842
843 if command == 'recurse':
agabled437d762016-10-17 09:35:11 -0700844 # Skip file only checkout.
845 scm = gclient_scm.GetScmName(parsed_url)
846 if not options.scm or scm in options.scm:
847 cwd = os.path.normpath(os.path.join(self.root.root_dir, self.name))
848 # Pass in the SCM type as an env variable. Make sure we don't put
849 # unicode strings in the environment.
850 env = os.environ.copy()
851 if scm:
852 env['GCLIENT_SCM'] = str(scm)
853 if parsed_url:
854 env['GCLIENT_URL'] = str(parsed_url)
855 env['GCLIENT_DEP_PATH'] = str(self.name)
856 if options.prepend_dir and scm == 'git':
857 print_stdout = False
858 def filter_fn(line):
859 """Git-specific path marshaling. It is optimized for git-grep."""
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000860
agabled437d762016-10-17 09:35:11 -0700861 def mod_path(git_pathspec):
862 match = re.match('^(\\S+?:)?([^\0]+)$', git_pathspec)
863 modified_path = os.path.join(self.name, match.group(2))
864 branch = match.group(1) or ''
865 return '%s%s' % (branch, modified_path)
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000866
agabled437d762016-10-17 09:35:11 -0700867 match = re.match('^Binary file ([^\0]+) matches$', line)
868 if match:
869 print('Binary file %s matches\n' % mod_path(match.group(1)))
870 return
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000871
agabled437d762016-10-17 09:35:11 -0700872 items = line.split('\0')
873 if len(items) == 2 and items[1]:
874 print('%s : %s' % (mod_path(items[0]), items[1]))
875 elif len(items) >= 2:
876 # Multiple null bytes or a single trailing null byte indicate
877 # git is likely displaying filenames only (such as with -l)
878 print('\n'.join(mod_path(path) for path in items if path))
879 else:
880 print(line)
881 else:
882 print_stdout = True
883 filter_fn = None
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000884
agabled437d762016-10-17 09:35:11 -0700885 if parsed_url is None:
886 print('Skipped omitted dependency %s' % cwd, file=sys.stderr)
887 elif os.path.isdir(cwd):
888 try:
889 gclient_utils.CheckCallAndFilter(
890 args, cwd=cwd, env=env, print_stdout=print_stdout,
891 filter_fn=filter_fn,
892 )
893 except subprocess2.CalledProcessError:
894 if not options.ignore:
895 raise
896 else:
897 print('Skipped missing %s' % cwd, file=sys.stderr)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000898
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +0200899 def WriteGNArgsFile(self):
900 lines = ['# Generated from %r' % self.deps_file]
901 for arg in self._gn_args:
902 lines.append('%s = %s' % (arg, ToGNString(self._vars[arg])))
903 with open(os.path.join(self.root.root_dir, self._gn_args_file), 'w') as f:
904 f.write('\n'.join(lines))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000905
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000906 @gclient_utils.lockedmethod
907 def _run_is_done(self, file_list, parsed_url):
908 # Both these are kept for hooks that are run as a separate tree traversal.
909 self._file_list = file_list
910 self._parsed_url = parsed_url
911 self._processed = True
912
szager@google.comb9a78d32012-03-13 18:46:21 +0000913 @staticmethod
Paweł Hajdan, Jr35b298f2017-05-23 14:37:05 +0200914 def GetHookAction(hook_dict):
szager@google.comb9a78d32012-03-13 18:46:21 +0000915 """Turns a parsed 'hook' dict into an executable command."""
916 logging.debug(hook_dict)
szager@google.comb9a78d32012-03-13 18:46:21 +0000917 command = hook_dict['action'][:]
918 if command[0] == 'python':
919 # If the hook specified "python" as the first item, the action is a
920 # Python script. Run it by starting a new copy of the same
921 # interpreter.
922 command[0] = sys.executable
szager@google.comb9a78d32012-03-13 18:46:21 +0000923 return command
924
925 def GetHooks(self, options):
926 """Evaluates all hooks, and return them in a flat list.
927
928 RunOnDeps() must have been called before to load the DEPS.
929 """
930 result = []
maruel@chromium.org68988972011-09-20 14:11:42 +0000931 if not self.should_process or not self.recursion_limit:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000932 # Don't run the hook when it is above recursion_limit.
szager@google.comb9a78d32012-03-13 18:46:21 +0000933 return result
maruel@chromium.orgdc7445d2010-07-09 21:05:29 +0000934 # If "--force" was specified, run all hooks regardless of what files have
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000935 # changed.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000936 if self.deps_hooks:
agabled437d762016-10-17 09:35:11 -0700937 # TODO(maruel): If the user is using git, then we don't know
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000938 # what files have changed so we always run all hooks. It'd be nice to fix
939 # that.
940 if (options.force or
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000941 gclient_scm.GetScmName(self.parsed_url) in ('git', None) or
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000942 os.path.isdir(os.path.join(self.root.root_dir, self.name, '.git'))):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000943 for hook_dict in self.deps_hooks:
Paweł Hajdan, Jr35b298f2017-05-23 14:37:05 +0200944 result.append(self.GetHookAction(hook_dict))
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000945 else:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000946 # Run hooks on the basis of whether the files from the gclient operation
947 # match each hook's pattern.
948 for hook_dict in self.deps_hooks:
949 pattern = re.compile(hook_dict['pattern'])
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000950 matching_file_list = [
951 f for f in self.file_list_and_children if pattern.search(f)
952 ]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000953 if matching_file_list:
Paweł Hajdan, Jr35b298f2017-05-23 14:37:05 +0200954 result.append(self.GetHookAction(hook_dict))
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000955 for s in self.dependencies:
szager@google.comb9a78d32012-03-13 18:46:21 +0000956 result.extend(s.GetHooks(options))
957 return result
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000958
szager@google.comb9a78d32012-03-13 18:46:21 +0000959 def RunHooksRecursively(self, options):
960 assert self.hooks_ran == False
maruel@chromium.org064186c2011-09-27 23:53:33 +0000961 self._hooks_ran = True
szager@google.comb9a78d32012-03-13 18:46:21 +0000962 for hook in self.GetHooks(options):
963 try:
ilevy@chromium.orgc28d3772013-07-12 19:42:37 +0000964 start_time = time.time()
szager@google.comb9a78d32012-03-13 18:46:21 +0000965 gclient_utils.CheckCallAndFilterAndHeader(
966 hook, cwd=self.root.root_dir, always=True)
vapier@chromium.orga81a56e2015-11-11 07:56:13 +0000967 except (gclient_utils.Error, subprocess2.CalledProcessError) as e:
szager@google.comb9a78d32012-03-13 18:46:21 +0000968 # Use a discrete exit status code of 2 to indicate that a hook action
969 # failed. Users of this script may wish to treat hook action failures
970 # differently from VC failures.
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +0000971 print('Error: %s' % str(e), file=sys.stderr)
szager@google.comb9a78d32012-03-13 18:46:21 +0000972 sys.exit(2)
ilevy@chromium.orgc28d3772013-07-12 19:42:37 +0000973 finally:
974 elapsed_time = time.time() - start_time
975 if elapsed_time > 10:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +0000976 print("Hook '%s' took %.2f secs" % (
977 gclient_utils.CommandToStr(hook), elapsed_time))
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000978
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000979 def RunPreDepsHooks(self):
980 assert self.processed
981 assert self.deps_parsed
982 assert not self.pre_deps_hooks_ran
983 assert not self.hooks_ran
984 for s in self.dependencies:
985 assert not s.processed
986 self._pre_deps_hooks_ran = True
987 for hook in self.pre_deps_hooks:
988 try:
989 start_time = time.time()
990 gclient_utils.CheckCallAndFilterAndHeader(
991 hook, cwd=self.root.root_dir, always=True)
vapier@chromium.orga81a56e2015-11-11 07:56:13 +0000992 except (gclient_utils.Error, subprocess2.CalledProcessError) as e:
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000993 # Use a discrete exit status code of 2 to indicate that a hook action
994 # failed. Users of this script may wish to treat hook action failures
995 # differently from VC failures.
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +0000996 print('Error: %s' % str(e), file=sys.stderr)
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000997 sys.exit(2)
998 finally:
999 elapsed_time = time.time() - start_time
1000 if elapsed_time > 10:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001001 print("Hook '%s' took %.2f secs" % (
1002 gclient_utils.CommandToStr(hook), elapsed_time))
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001003
1004
maruel@chromium.org0d812442010-08-10 12:41:08 +00001005 def subtree(self, include_all):
maruel@chromium.orgad3287e2011-10-03 19:15:10 +00001006 """Breadth first recursion excluding root node."""
maruel@chromium.orgf13a4182011-09-22 00:26:15 +00001007 dependencies = self.dependencies
1008 for d in dependencies:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001009 if d.should_process or include_all:
maruel@chromium.orgad3287e2011-10-03 19:15:10 +00001010 yield d
maruel@chromium.orgf13a4182011-09-22 00:26:15 +00001011 for d in dependencies:
maruel@chromium.orgad3287e2011-10-03 19:15:10 +00001012 for i in d.subtree(include_all):
1013 yield i
1014
1015 def depth_first_tree(self):
1016 """Depth-first recursion including the root node."""
1017 yield self
1018 for i in self.dependencies:
1019 for j in i.depth_first_tree():
1020 if j.should_process:
1021 yield j
maruel@chromium.orgc57e4f22010-07-22 21:37:46 +00001022
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001023 @gclient_utils.lockedmethod
1024 def add_dependency(self, new_dep):
1025 self._dependencies.append(new_dep)
1026
1027 @gclient_utils.lockedmethod
1028 def _mark_as_parsed(self, new_hooks):
1029 self._deps_hooks.extend(new_hooks)
1030 self._deps_parsed = True
1031
maruel@chromium.org68988972011-09-20 14:11:42 +00001032 @property
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001033 @gclient_utils.lockedmethod
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +00001034 def dependencies(self):
1035 return tuple(self._dependencies)
1036
1037 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001038 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001039 def deps_hooks(self):
1040 return tuple(self._deps_hooks)
1041
1042 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001043 @gclient_utils.lockedmethod
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001044 def pre_deps_hooks(self):
1045 return tuple(self._pre_deps_hooks)
1046
1047 @property
1048 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001049 def parsed_url(self):
1050 return self._parsed_url
1051
1052 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001053 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001054 def deps_parsed(self):
maruel@chromium.org3223edd2011-10-10 23:17:39 +00001055 """This is purely for debugging purposes. It's not used anywhere."""
maruel@chromium.org064186c2011-09-27 23:53:33 +00001056 return self._deps_parsed
1057
1058 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001059 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001060 def processed(self):
1061 return self._processed
1062
1063 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001064 @gclient_utils.lockedmethod
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001065 def pre_deps_hooks_ran(self):
1066 return self._pre_deps_hooks_ran
1067
1068 @property
1069 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001070 def hooks_ran(self):
1071 return self._hooks_ran
1072
1073 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001074 @gclient_utils.lockedmethod
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00001075 def allowed_hosts(self):
1076 return self._allowed_hosts
1077
1078 @property
1079 @gclient_utils.lockedmethod
maruel@chromium.org861fd0f2010-07-23 03:05:05 +00001080 def file_list(self):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001081 return tuple(self._file_list)
1082
1083 @property
kustermann@google.coma692e8f2013-04-18 08:32:04 +00001084 def used_scm(self):
1085 """SCMWrapper instance for this dependency or None if not processed yet."""
1086 return self._used_scm
1087
1088 @property
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00001089 @gclient_utils.lockedmethod
1090 def got_revision(self):
1091 return self._got_revision
1092
1093 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001094 def file_list_and_children(self):
1095 result = list(self.file_list)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +00001096 for d in self.dependencies:
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001097 result.extend(d.file_list_and_children)
maruel@chromium.org68988972011-09-20 14:11:42 +00001098 return tuple(result)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +00001099
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001100 def __str__(self):
1101 out = []
agablea98a6cd2016-11-15 14:30:10 -08001102 for i in ('name', 'url', 'parsed_url', 'custom_deps',
maruel@chromium.org3c74bc92011-09-15 19:17:21 +00001103 'custom_vars', 'deps_hooks', 'file_list', 'should_process',
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00001104 'processed', 'hooks_ran', 'deps_parsed', 'requirements',
1105 'allowed_hosts'):
maruel@chromium.org3c74bc92011-09-15 19:17:21 +00001106 # First try the native property if it exists.
1107 if hasattr(self, '_' + i):
1108 value = getattr(self, '_' + i, False)
1109 else:
1110 value = getattr(self, i, False)
1111 if value:
1112 out.append('%s: %s' % (i, value))
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001113
1114 for d in self.dependencies:
1115 out.extend([' ' + x for x in str(d).splitlines()])
1116 out.append('')
1117 return '\n'.join(out)
1118
1119 def __repr__(self):
1120 return '%s: %s' % (self.name, self.url)
1121
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001122 def hierarchy(self, include_url=True):
maruel@chromium.orgbc2d2f92010-07-22 21:26:48 +00001123 """Returns a human-readable hierarchical reference to a Dependency."""
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001124 def format_name(d):
1125 if include_url:
1126 return '%s(%s)' % (d.name, d.url)
1127 return d.name
1128 out = format_name(self)
maruel@chromium.orgbffb9042010-07-22 20:59:36 +00001129 i = self.parent
1130 while i and i.name:
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001131 out = '%s -> %s' % (format_name(i), out)
maruel@chromium.orgbffb9042010-07-22 20:59:36 +00001132 i = i.parent
1133 return out
1134
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001135
1136class GClient(Dependency):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001137 """Object that represent a gclient checkout. A tree of Dependency(), one per
1138 solution or DEPS entry."""
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001139
1140 DEPS_OS_CHOICES = {
1141 "win32": "win",
1142 "win": "win",
1143 "cygwin": "win",
1144 "darwin": "mac",
1145 "mac": "mac",
1146 "unix": "unix",
1147 "linux": "unix",
1148 "linux2": "unix",
maruel@chromium.org244e3442011-06-12 15:20:55 +00001149 "linux3": "unix",
szager@chromium.orgf8c95cd2012-06-01 22:26:52 +00001150 "android": "android",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001151 }
1152
1153 DEFAULT_CLIENT_FILE_TEXT = ("""\
1154solutions = [
smutae7ea312016-07-18 11:59:41 -07001155 { "name" : "%(solution_name)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001156 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +00001157 "deps_file" : "%(deps_file)s",
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001158 "managed" : %(managed)s,
smutae7ea312016-07-18 11:59:41 -07001159 "custom_deps" : {
1160 },
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001161 },
1162]
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001163cache_dir = %(cache_dir)r
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001164""")
1165
1166 DEFAULT_SNAPSHOT_SOLUTION_TEXT = ("""\
smutae7ea312016-07-18 11:59:41 -07001167 { "name" : "%(solution_name)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001168 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +00001169 "deps_file" : "%(deps_file)s",
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001170 "managed" : %(managed)s,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001171 "custom_deps" : {
smutae7ea312016-07-18 11:59:41 -07001172%(solution_deps)s },
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001173 },
1174""")
1175
1176 DEFAULT_SNAPSHOT_FILE_TEXT = ("""\
1177# Snapshot generated with gclient revinfo --snapshot
1178solutions = [
maruel@chromium.org73e21142010-07-05 13:32:01 +00001179%(solution_list)s]
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001180""")
1181
1182 def __init__(self, root_dir, options):
maruel@chromium.org0d812442010-08-10 12:41:08 +00001183 # Do not change previous behavior. Only solution level and immediate DEPS
1184 # are processed.
1185 self._recursion_limit = 2
agablea98a6cd2016-11-15 14:30:10 -08001186 Dependency.__init__(self, None, None, None, True, None, None, None,
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +02001187 'unused', True, None, None, True)
maruel@chromium.org0d425922010-06-21 19:22:24 +00001188 self._options = options
maruel@chromium.org271375b2010-06-23 19:17:38 +00001189 if options.deps_os:
1190 enforced_os = options.deps_os.split(',')
1191 else:
1192 enforced_os = [self.DEPS_OS_CHOICES.get(sys.platform, 'unix')]
1193 if 'all' in enforced_os:
1194 enforced_os = self.DEPS_OS_CHOICES.itervalues()
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001195 self._enforced_os = tuple(set(enforced_os))
maruel@chromium.org271375b2010-06-23 19:17:38 +00001196 self._root_dir = root_dir
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001197 self.config_content = None
1198
borenet@google.com88d10082014-03-21 17:24:48 +00001199 def _CheckConfig(self):
1200 """Verify that the config matches the state of the existing checked-out
1201 solutions."""
1202 for dep in self.dependencies:
1203 if dep.managed and dep.url:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001204 scm = gclient_scm.CreateSCM(
1205 dep.url, self.root_dir, dep.name, self.outbuf)
smut@google.comd33eab32014-07-07 19:35:18 +00001206 actual_url = scm.GetActualRemoteURL(self._options)
borenet@google.com4e9be262014-04-08 19:40:30 +00001207 if actual_url and not scm.DoesRemoteURLMatch(self._options):
levarum@chromium.org27a6f9a2016-05-28 00:21:49 +00001208 mirror = scm.GetCacheMirror()
1209 if mirror:
1210 mirror_string = '%s (exists=%s)' % (mirror.mirror_path,
1211 mirror.exists())
1212 else:
1213 mirror_string = 'not used'
borenet@google.com0a427372014-04-02 19:12:13 +00001214 raise gclient_utils.Error('''
borenet@google.com88d10082014-03-21 17:24:48 +00001215Your .gclient file seems to be broken. The requested URL is different from what
borenet@google.com0a427372014-04-02 19:12:13 +00001216is actually checked out in %(checkout_path)s.
borenet@google.com88d10082014-03-21 17:24:48 +00001217
borenet@google.com97882362014-04-07 20:06:02 +00001218The .gclient file contains:
levarum@chromium.org27a6f9a2016-05-28 00:21:49 +00001219URL: %(expected_url)s (%(expected_scm)s)
1220Cache mirror: %(mirror_string)s
borenet@google.com97882362014-04-07 20:06:02 +00001221
1222The local checkout in %(checkout_path)s reports:
1223%(actual_url)s (%(actual_scm)s)
borenet@google.com88d10082014-03-21 17:24:48 +00001224
1225You should ensure that the URL listed in .gclient is correct and either change
agabled437d762016-10-17 09:35:11 -07001226it or fix the checkout.
borenet@google.com88d10082014-03-21 17:24:48 +00001227''' % {'checkout_path': os.path.join(self.root_dir, dep.name),
1228 'expected_url': dep.url,
1229 'expected_scm': gclient_scm.GetScmName(dep.url),
levarum@chromium.org27a6f9a2016-05-28 00:21:49 +00001230 'mirror_string' : mirror_string,
borenet@google.com88d10082014-03-21 17:24:48 +00001231 'actual_url': actual_url,
1232 'actual_scm': gclient_scm.GetScmName(actual_url)})
1233
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001234 def SetConfig(self, content):
maruel@chromium.orgf13a4182011-09-22 00:26:15 +00001235 assert not self.dependencies
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001236 config_dict = {}
1237 self.config_content = content
1238 try:
1239 exec(content, config_dict)
vapier@chromium.orga81a56e2015-11-11 07:56:13 +00001240 except SyntaxError as e:
maruel@chromium.org5990f9d2010-07-07 18:02:58 +00001241 gclient_utils.SyntaxErrorToError('.gclient', e)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001242
peter@chromium.org1efccc82012-04-27 16:34:38 +00001243 # Append any target OS that is not already being enforced to the tuple.
1244 target_os = config_dict.get('target_os', [])
stuartmorgan@chromium.org18a4f6a2012-11-02 13:37:24 +00001245 if config_dict.get('target_os_only', False):
1246 self._enforced_os = tuple(set(target_os))
1247 else:
1248 self._enforced_os = tuple(set(self._enforced_os).union(target_os))
1249
dyen@chromium.orgd915cca2014-08-07 21:41:37 +00001250 cache_dir = config_dict.get('cache_dir')
1251 if cache_dir:
1252 cache_dir = os.path.join(self.root_dir, cache_dir)
1253 cache_dir = os.path.abspath(cache_dir)
szager@chromium.orgcaf5bef2014-08-24 18:56:32 +00001254 # If running on a bot, force break any stale git cache locks.
dnj@chromium.orgb682b3e2014-08-25 19:17:12 +00001255 if os.path.exists(cache_dir) and os.environ.get('CHROME_HEADLESS'):
szager@chromium.org4848fb62014-08-24 19:16:31 +00001256 subprocess2.check_call(['git', 'cache', 'unlock', '--cache-dir',
1257 cache_dir, '--force', '--all'])
dyen@chromium.orgd915cca2014-08-07 21:41:37 +00001258 gclient_scm.GitWrapper.cache_dir = cache_dir
1259 git_cache.Mirror.SetCachePath(cache_dir)
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001260
stuartmorgan@chromium.org18a4f6a2012-11-02 13:37:24 +00001261 if not target_os and config_dict.get('target_os_only', False):
1262 raise gclient_utils.Error('Can\'t use target_os_only if target_os is '
1263 'not specified')
peter@chromium.org1efccc82012-04-27 16:34:38 +00001264
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001265 deps_to_add = []
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001266 for s in config_dict.get('solutions', []):
maruel@chromium.org81843b82010-06-28 16:49:26 +00001267 try:
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001268 deps_to_add.append(Dependency(
maruel@chromium.org81843b82010-06-28 16:49:26 +00001269 self, s['name'], s['url'],
smutae7ea312016-07-18 11:59:41 -07001270 s.get('managed', True),
maruel@chromium.org81843b82010-06-28 16:49:26 +00001271 s.get('custom_deps', {}),
maruel@chromium.org0d812442010-08-10 12:41:08 +00001272 s.get('custom_vars', {}),
petermayo@chromium.orge79161a2013-07-09 14:40:37 +00001273 s.get('custom_hooks', []),
nsylvain@google.comefc80932011-05-31 21:27:56 +00001274 s.get('deps_file', 'DEPS'),
agabledce6ddc2016-09-08 10:02:16 -07001275 True,
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +02001276 None,
1277 None,
1278 True))
maruel@chromium.org81843b82010-06-28 16:49:26 +00001279 except KeyError:
1280 raise gclient_utils.Error('Invalid .gclient file. Solution is '
1281 'incomplete: %s' % s)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001282 self.add_dependencies_and_close(deps_to_add, config_dict.get('hooks', []))
1283 logging.info('SetConfig() done')
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001284
1285 def SaveConfig(self):
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001286 gclient_utils.FileWrite(os.path.join(self.root_dir,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001287 self._options.config_filename),
1288 self.config_content)
1289
1290 @staticmethod
1291 def LoadCurrentConfig(options):
1292 """Searches for and loads a .gclient file relative to the current working
1293 dir. Returns a GClient object."""
szager@chromium.orge2e03202012-07-31 18:05:16 +00001294 if options.spec:
1295 client = GClient('.', options)
1296 client.SetConfig(options.spec)
1297 else:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00001298 if options.verbose:
1299 print('Looking for %s starting from %s\n' % (
1300 options.config_filename, os.getcwd()))
szager@chromium.orge2e03202012-07-31 18:05:16 +00001301 path = gclient_utils.FindGclientRoot(os.getcwd(), options.config_filename)
1302 if not path:
1303 return None
1304 client = GClient(path, options)
1305 client.SetConfig(gclient_utils.FileRead(
1306 os.path.join(path, options.config_filename)))
maruel@chromium.org69392e72011-10-13 22:09:00 +00001307
1308 if (options.revisions and
1309 len(client.dependencies) > 1 and
1310 any('@' not in r for r in options.revisions)):
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001311 print(
1312 ('You must specify the full solution name like --revision %s@%s\n'
1313 'when you have multiple solutions setup in your .gclient file.\n'
1314 'Other solutions present are: %s.') % (
maruel@chromium.org69392e72011-10-13 22:09:00 +00001315 client.dependencies[0].name,
1316 options.revisions[0],
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001317 ', '.join(s.name for s in client.dependencies[1:])),
1318 file=sys.stderr)
maruel@chromium.org15804092010-09-02 17:07:37 +00001319 return client
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001320
nsylvain@google.comefc80932011-05-31 21:27:56 +00001321 def SetDefaultConfig(self, solution_name, deps_file, solution_url,
agablea98a6cd2016-11-15 14:30:10 -08001322 managed=True, cache_dir=None):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001323 self.SetConfig(self.DEFAULT_CLIENT_FILE_TEXT % {
1324 'solution_name': solution_name,
1325 'solution_url': solution_url,
nsylvain@google.comefc80932011-05-31 21:27:56 +00001326 'deps_file': deps_file,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001327 'managed': managed,
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001328 'cache_dir': cache_dir,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001329 })
1330
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001331 def _SaveEntries(self):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001332 """Creates a .gclient_entries file to record the list of unique checkouts.
1333
1334 The .gclient_entries file lives in the same directory as .gclient.
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001335 """
1336 # Sometimes pprint.pformat will use {', sometimes it'll use { ' ... It
1337 # makes testing a bit too fun.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001338 result = 'entries = {\n'
maruel@chromium.org68988972011-09-20 14:11:42 +00001339 for entry in self.root.subtree(False):
agabled437d762016-10-17 09:35:11 -07001340 result += ' %s: %s,\n' % (pprint.pformat(entry.name),
1341 pprint.pformat(entry.parsed_url))
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001342 result += '}\n'
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001343 file_path = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org1333cb32011-10-04 23:40:16 +00001344 logging.debug(result)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001345 gclient_utils.FileWrite(file_path, result)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001346
1347 def _ReadEntries(self):
1348 """Read the .gclient_entries file for the given client.
1349
1350 Returns:
1351 A sequence of solution names, which will be empty if there is the
1352 entries file hasn't been created yet.
1353 """
1354 scope = {}
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001355 filename = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001356 if not os.path.exists(filename):
maruel@chromium.org73e21142010-07-05 13:32:01 +00001357 return {}
maruel@chromium.org5990f9d2010-07-07 18:02:58 +00001358 try:
1359 exec(gclient_utils.FileRead(filename), scope)
vapier@chromium.orga81a56e2015-11-11 07:56:13 +00001360 except SyntaxError as e:
maruel@chromium.org5990f9d2010-07-07 18:02:58 +00001361 gclient_utils.SyntaxErrorToError(filename, e)
Aaron Gable3721ee92017-04-03 14:53:14 -07001362 return scope.get('entries', {})
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001363
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001364 def _EnforceRevisions(self):
maruel@chromium.org918a9ae2010-05-28 15:50:30 +00001365 """Checks for revision overrides."""
1366 revision_overrides = {}
smutae7ea312016-07-18 11:59:41 -07001367 if self._options.head:
1368 return revision_overrides
joi@chromium.org792ea882010-11-10 02:37:27 +00001369 if not self._options.revisions:
1370 for s in self.dependencies:
smutae7ea312016-07-18 11:59:41 -07001371 if not s.managed:
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001372 self._options.revisions.append('%s@unmanaged' % s.name)
maruel@chromium.org307d1792010-05-31 20:03:13 +00001373 if not self._options.revisions:
1374 return revision_overrides
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001375 solutions_names = [s.name for s in self.dependencies]
smutae7ea312016-07-18 11:59:41 -07001376 index = 0
1377 for revision in self._options.revisions:
1378 if not '@' in revision:
maruel@chromium.org307d1792010-05-31 20:03:13 +00001379 # Support for --revision 123
smutae7ea312016-07-18 11:59:41 -07001380 revision = '%s@%s' % (solutions_names[index], revision)
1381 name, rev = revision.split('@', 1)
szager@chromium.org4ad264b2014-05-20 04:43:47 +00001382 revision_overrides[name] = rev
smutae7ea312016-07-18 11:59:41 -07001383 index += 1
maruel@chromium.org918a9ae2010-05-28 15:50:30 +00001384 return revision_overrides
1385
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001386 def RunOnDeps(self, command, args, ignore_requirements=False, progress=True):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001387 """Runs a command on each dependency in a client and its dependencies.
1388
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001389 Args:
1390 command: The command to use (e.g., 'status' or 'diff')
1391 args: list of str - extra arguments to add to the command line.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001392 """
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001393 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +00001394 raise gclient_utils.Error('No solution specified')
borenet@google.com0a427372014-04-02 19:12:13 +00001395
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001396 revision_overrides = {}
1397 # It's unnecessary to check for revision overrides for 'recurse'.
1398 # Save a few seconds by not calling _EnforceRevisions() in that case.
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +02001399 if command not in ('diff', 'recurse', 'runhooks', 'status', 'revert',
1400 'validate'):
szager@chromium.org5273b8a2014-08-21 15:10:10 +00001401 self._CheckConfig()
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001402 revision_overrides = self._EnforceRevisions()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001403 pm = None
maruel@chromium.org5b3f8852010-09-10 16:49:54 +00001404 # Disable progress for non-tty stdout.
iannucci@chromium.org596cd5c2016-04-04 21:34:39 +00001405 if (setup_color.IS_TTY and not self._options.verbose and progress):
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001406 if command in ('update', 'revert'):
1407 pm = Progress('Syncing projects', 1)
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +02001408 elif command in ('recurse', 'validate'):
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001409 pm = Progress(' '.join(args), 1)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001410 work_queue = gclient_utils.ExecutionQueue(
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001411 self._options.jobs, pm, ignore_requirements=ignore_requirements,
1412 verbose=self._options.verbose)
maruel@chromium.org049bced2010-08-12 13:37:20 +00001413 for s in self.dependencies:
1414 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +00001415 work_queue.flush(revision_overrides, command, args, options=self._options)
szager@chromium.org4ad264b2014-05-20 04:43:47 +00001416 if revision_overrides:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001417 print('Please fix your script, having invalid --revision flags will soon '
1418 'considered an error.', file=sys.stderr)
piman@chromium.org6f363722010-04-27 00:41:09 +00001419
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001420 # Once all the dependencies have been processed, it's now safe to run the
1421 # hooks.
1422 if not self._options.nohooks:
1423 self.RunHooksRecursively(self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001424
1425 if command == 'update':
ajwong@chromium.orgcdcee802009-06-23 15:30:42 +00001426 # Notify the user if there is an orphaned entry in their working copy.
1427 # Only delete the directory if there are no changes in it, and
1428 # delete_unversioned_trees is set to true.
maruel@chromium.org68988972011-09-20 14:11:42 +00001429 entries = [i.name for i in self.root.subtree(False) if i.url]
xusydoc@chromium.org885a9602013-05-31 09:54:40 +00001430 full_entries = [os.path.join(self.root_dir, e.replace('/', os.path.sep))
1431 for e in entries]
1432
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001433 for entry, prev_url in self._ReadEntries().iteritems():
maruel@chromium.org04dd7de2010-10-14 13:25:49 +00001434 if not prev_url:
1435 # entry must have been overridden via .gclient custom_deps
1436 continue
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001437 # Fix path separator on Windows.
1438 entry_fixed = entry.replace('/', os.path.sep)
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001439 e_dir = os.path.join(self.root_dir, entry_fixed)
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001440 # Use entry and not entry_fixed there.
jochen@chromium.orga78e5532013-03-11 13:33:03 +00001441 if (entry not in entries and
1442 (not any(path.startswith(entry + '/') for path in entries)) and
jochen@chromium.orgcc475722013-03-11 13:07:40 +00001443 os.path.exists(e_dir)):
primiano@chromium.org1c127382015-02-17 11:15:40 +00001444 # The entry has been removed from DEPS.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001445 scm = gclient_scm.CreateSCM(
1446 prev_url, self.root_dir, entry_fixed, self.outbuf)
xusydoc@chromium.org885a9602013-05-31 09:54:40 +00001447
1448 # Check to see if this directory is now part of a higher-up checkout.
borenet@google.com359bb642014-05-13 17:28:19 +00001449 scm_root = None
agabled437d762016-10-17 09:35:11 -07001450 try:
1451 scm_root = gclient_scm.scm.GIT.GetCheckoutRoot(scm.checkout_path)
1452 except subprocess2.CalledProcessError:
1453 pass
1454 if not scm_root:
borenet@google.com359bb642014-05-13 17:28:19 +00001455 logging.warning('Could not find checkout root for %s. Unable to '
1456 'determine whether it is part of a higher-level '
1457 'checkout, so not removing.' % entry)
1458 continue
primiano@chromium.org1c127382015-02-17 11:15:40 +00001459
1460 # This is to handle the case of third_party/WebKit migrating from
1461 # being a DEPS entry to being part of the main project.
1462 # If the subproject is a Git project, we need to remove its .git
1463 # folder. Otherwise git operations on that folder will have different
1464 # effects depending on the current working directory.
agabled437d762016-10-17 09:35:11 -07001465 if os.path.abspath(scm_root) == os.path.abspath(e_dir):
primiano@chromium.org1c127382015-02-17 11:15:40 +00001466 e_par_dir = os.path.join(e_dir, os.pardir)
agabled437d762016-10-17 09:35:11 -07001467 if gclient_scm.scm.GIT.IsInsideWorkTree(e_par_dir):
1468 par_scm_root = gclient_scm.scm.GIT.GetCheckoutRoot(e_par_dir)
primiano@chromium.org1c127382015-02-17 11:15:40 +00001469 # rel_e_dir : relative path of entry w.r.t. its parent repo.
1470 rel_e_dir = os.path.relpath(e_dir, par_scm_root)
agabled437d762016-10-17 09:35:11 -07001471 if gclient_scm.scm.GIT.IsDirectoryVersioned(
1472 par_scm_root, rel_e_dir):
primiano@chromium.org1c127382015-02-17 11:15:40 +00001473 save_dir = scm.GetGitBackupDirPath()
1474 # Remove any eventual stale backup dir for the same project.
1475 if os.path.exists(save_dir):
1476 gclient_utils.rmtree(save_dir)
1477 os.rename(os.path.join(e_dir, '.git'), save_dir)
1478 # When switching between the two states (entry/ is a subproject
1479 # -> entry/ is part of the outer project), it is very likely
1480 # that some files are changed in the checkout, unless we are
1481 # jumping *exactly* across the commit which changed just DEPS.
1482 # In such case we want to cleanup any eventual stale files
1483 # (coming from the old subproject) in order to end up with a
1484 # clean checkout.
agabled437d762016-10-17 09:35:11 -07001485 gclient_scm.scm.GIT.CleanupDir(par_scm_root, rel_e_dir)
primiano@chromium.org1c127382015-02-17 11:15:40 +00001486 assert not os.path.exists(os.path.join(e_dir, '.git'))
1487 print(('\nWARNING: \'%s\' has been moved from DEPS to a higher '
1488 'level checkout. The git folder containing all the local'
1489 ' branches has been saved to %s.\n'
1490 'If you don\'t care about its state you can safely '
1491 'remove that folder to free up space.') %
1492 (entry, save_dir))
1493 continue
1494
borenet@google.com359bb642014-05-13 17:28:19 +00001495 if scm_root in full_entries:
primiano@chromium.org1c127382015-02-17 11:15:40 +00001496 logging.info('%s is part of a higher level checkout, not removing',
1497 scm.GetCheckoutRoot())
xusydoc@chromium.org885a9602013-05-31 09:54:40 +00001498 continue
1499
1500 file_list = []
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001501 scm.status(self._options, [], file_list)
1502 modified_files = file_list != []
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001503 if (not self._options.delete_unversioned_trees or
1504 (modified_files and not self._options.force)):
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001505 # There are modified files in this entry. Keep warning until
1506 # removed.
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001507 print(('\nWARNING: \'%s\' is no longer part of this client. '
1508 'It is recommended that you manually remove it.\n') %
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001509 entry_fixed)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001510 else:
1511 # Delete the entry
maruel@chromium.org73e21142010-07-05 13:32:01 +00001512 print('\n________ deleting \'%s\' in \'%s\'' % (
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001513 entry_fixed, self.root_dir))
digit@chromium.orgdc112ac2013-04-24 13:00:19 +00001514 gclient_utils.rmtree(e_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001515 # record the current list of entries for next time
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001516 self._SaveEntries()
maruel@chromium.org17cdf762010-05-28 17:30:52 +00001517 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001518
1519 def PrintRevInfo(self):
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001520 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +00001521 raise gclient_utils.Error('No solution specified')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001522 # Load all the settings.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001523 work_queue = gclient_utils.ExecutionQueue(
1524 self._options.jobs, None, False, verbose=self._options.verbose)
maruel@chromium.org049bced2010-08-12 13:37:20 +00001525 for s in self.dependencies:
1526 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +00001527 work_queue.flush({}, None, [], options=self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001528
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001529 def GetURLAndRev(dep):
1530 """Returns the revision-qualified SCM url for a Dependency."""
1531 if dep.parsed_url is None:
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001532 return None
agabled437d762016-10-17 09:35:11 -07001533 url, _ = gclient_utils.SplitUrlRevision(dep.parsed_url)
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001534 scm = gclient_scm.CreateSCM(
agabled437d762016-10-17 09:35:11 -07001535 dep.parsed_url, self.root_dir, dep.name, self.outbuf)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001536 if not os.path.isdir(scm.checkout_path):
1537 return None
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001538 return '%s@%s' % (url, scm.revinfo(self._options, [], None))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001539
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001540 if self._options.snapshot:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001541 new_gclient = ''
1542 # First level at .gclient
1543 for d in self.dependencies:
1544 entries = {}
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001545 def GrabDeps(dep):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001546 """Recursively grab dependencies."""
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001547 for d in dep.dependencies:
1548 entries[d.name] = GetURLAndRev(d)
1549 GrabDeps(d)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001550 GrabDeps(d)
1551 custom_deps = []
1552 for k in sorted(entries.keys()):
1553 if entries[k]:
1554 # Quotes aren't escaped...
1555 custom_deps.append(' \"%s\": \'%s\',\n' % (k, entries[k]))
1556 else:
1557 custom_deps.append(' \"%s\": None,\n' % k)
1558 new_gclient += self.DEFAULT_SNAPSHOT_SOLUTION_TEXT % {
1559 'solution_name': d.name,
1560 'solution_url': d.url,
nsylvain@google.comefc80932011-05-31 21:27:56 +00001561 'deps_file': d.deps_file,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001562 'managed': d.managed,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001563 'solution_deps': ''.join(custom_deps),
1564 }
1565 # Print the snapshot configuration file
1566 print(self.DEFAULT_SNAPSHOT_FILE_TEXT % {'solution_list': new_gclient})
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +00001567 else:
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001568 entries = {}
maruel@chromium.org68988972011-09-20 14:11:42 +00001569 for d in self.root.subtree(False):
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001570 if self._options.actual:
1571 entries[d.name] = GetURLAndRev(d)
1572 else:
1573 entries[d.name] = d.parsed_url
1574 keys = sorted(entries.keys())
1575 for x in keys:
maruel@chromium.orgce464892010-08-12 17:12:18 +00001576 print('%s: %s' % (x, entries[x]))
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +00001577 logging.info(str(self))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001578
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001579 def ParseDepsFile(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001580 """No DEPS to parse for a .gclient file."""
maruel@chromium.org049bced2010-08-12 13:37:20 +00001581 raise gclient_utils.Error('Internal error')
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001582
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00001583 def PrintLocationAndContents(self):
1584 # Print out the .gclient file. This is longer than if we just printed the
1585 # client dict, but more legible, and it might contain helpful comments.
1586 print('Loaded .gclient config in %s:\n%s' % (
1587 self.root_dir, self.config_content))
1588
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001589 @property
maruel@chromium.org75a59272010-06-11 22:34:03 +00001590 def root_dir(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001591 """Root directory of gclient checkout."""
maruel@chromium.org75a59272010-06-11 22:34:03 +00001592 return self._root_dir
1593
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001594 @property
maruel@chromium.org271375b2010-06-23 19:17:38 +00001595 def enforced_os(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001596 """What deps_os entries that are to be parsed."""
maruel@chromium.org271375b2010-06-23 19:17:38 +00001597 return self._enforced_os
1598
maruel@chromium.org68988972011-09-20 14:11:42 +00001599 @property
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001600 def recursion_limit(self):
1601 """How recursive can each dependencies in DEPS file can load DEPS file."""
1602 return self._recursion_limit
1603
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +00001604 @property
cmp@chromium.orgc401ad12014-07-02 23:20:08 +00001605 def try_recursedeps(self):
1606 """Whether to attempt using recursedeps-style recursion processing."""
cmp@chromium.orge84ac912014-06-30 23:14:35 +00001607 return True
1608
1609 @property
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +00001610 def target_os(self):
1611 return self._enforced_os
1612
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001613
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001614#### gclient commands.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001615
1616
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001617@subcommand.usage('[command] [args ...]')
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001618def CMDrecurse(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001619 """Operates [command args ...] on all the dependencies.
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001620
1621 Runs a shell command on all entries.
qyearsley12fa6ff2016-08-24 09:18:40 -07001622 Sets GCLIENT_DEP_PATH environment variable as the dep's relative location to
ilevy@chromium.org37116242012-11-28 01:32:48 +00001623 root directory of the checkout.
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001624 """
1625 # Stop parsing at the first non-arg so that these go through to the command
1626 parser.disable_interspersed_args()
1627 parser.add_option('-s', '--scm', action='append', default=[],
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001628 help='Choose scm types to operate upon.')
maruel@chromium.org288054d2012-03-05 00:43:07 +00001629 parser.add_option('-i', '--ignore', action='store_true',
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001630 help='Ignore non-zero return codes from subcommands.')
1631 parser.add_option('--prepend-dir', action='store_true',
1632 help='Prepend relative dir for use with git <cmd> --null.')
1633 parser.add_option('--no-progress', action='store_true',
1634 help='Disable progress bar that shows sub-command updates')
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001635 options, args = parser.parse_args(args)
maruel@chromium.org45e9f2d2010-10-18 13:33:46 +00001636 if not args:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001637 print('Need to supply a command!', file=sys.stderr)
maruel@chromium.org45e9f2d2010-10-18 13:33:46 +00001638 return 1
maruel@chromium.org78cba522010-10-18 13:32:05 +00001639 root_and_entries = gclient_utils.GetGClientRootAndEntries()
1640 if not root_and_entries:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001641 print(
maruel@chromium.org78cba522010-10-18 13:32:05 +00001642 'You need to run gclient sync at least once to use \'recurse\'.\n'
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001643 'This is because .gclient_entries needs to exist and be up to date.',
1644 file=sys.stderr)
maruel@chromium.org78cba522010-10-18 13:32:05 +00001645 return 1
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001646
1647 # Normalize options.scm to a set()
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001648 scm_set = set()
1649 for scm in options.scm:
1650 scm_set.update(scm.split(','))
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001651 options.scm = scm_set
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001652
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001653 options.nohooks = True
1654 client = GClient.LoadCurrentConfig(options)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001655 return client.RunOnDeps('recurse', args, ignore_requirements=True,
1656 progress=not options.no_progress)
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001657
1658
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001659@subcommand.usage('[args ...]')
davidbarr@chromium.org12f944e2012-03-01 02:18:31 +00001660def CMDfetch(parser, args):
1661 """Fetches upstream commits for all modules.
1662
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001663 Completely git-specific. Simply runs 'git fetch [args ...]' for each module.
1664 """
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001665 (options, args) = parser.parse_args(args)
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00001666 return CMDrecurse(OptionParser(), [
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001667 '--jobs=%d' % options.jobs, '--scm=git', 'git', 'fetch'] + args)
1668
1669
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001670def CMDflatten(parser, args):
1671 """Flattens the solutions into a single DEPS file."""
1672 parser.add_option('--output-deps', help='Path to the output DEPS file')
1673 parser.add_option(
1674 '--require-pinned-revisions', action='store_true',
1675 help='Fail if any of the dependencies uses unpinned revision.')
1676 options, args = parser.parse_args(args)
1677
1678 options.nohooks = True
1679 client = GClient.LoadCurrentConfig(options)
1680
1681 # Only print progress if we're writing to a file. Otherwise, progress updates
1682 # could obscure intended output.
1683 code = client.RunOnDeps('flatten', args, progress=options.output_deps)
1684 if code != 0:
1685 return code
1686
1687 deps = {}
1688 hooks = []
1689 pre_deps_hooks = []
1690 unpinned_deps = {}
1691
1692 for solution in client.dependencies:
1693 _FlattenSolution(solution, deps, hooks, pre_deps_hooks, unpinned_deps)
1694
1695 if options.require_pinned_revisions and unpinned_deps:
1696 sys.stderr.write('The following dependencies are not pinned:\n')
1697 sys.stderr.write('\n'.join(sorted(unpinned_deps)))
1698 return 1
1699
1700 flattened_deps = '\n'.join(
1701 _DepsToLines(deps) +
1702 _HooksToLines('hooks', hooks) +
1703 _HooksToLines('pre_deps_hooks', pre_deps_hooks) +
1704 [''] # Ensure newline at end of file.
1705 )
1706
1707 if options.output_deps:
1708 with open(options.output_deps, 'w') as f:
1709 f.write(flattened_deps)
1710 else:
1711 print(flattened_deps)
1712
1713 return 0
1714
1715
1716def _FlattenSolution(solution, deps, hooks, pre_deps_hooks, unpinned_deps):
1717 """Visits a solution in order to flatten it (see CMDflatten).
1718
1719 Arguments:
1720 solution (Dependency): one of top-level solutions in .gclient
1721
1722 Out-parameters:
1723 deps (dict of name -> Dependency): will be filled with all Dependency
1724 objects indexed by their name
1725 hooks (list of (Dependency, hook)): will be filled with flattened hooks
1726 pre_deps_hooks (list of (Dependency, hook)): will be filled with flattened
1727 pre_deps_hooks
1728 unpinned_deps (dict of name -> Dependency): will be filled with unpinned
1729 deps
1730 """
1731 logging.debug('_FlattenSolution(%r)', solution)
1732
1733 _FlattenDep(solution, deps, hooks, pre_deps_hooks, unpinned_deps)
1734 _FlattenRecurse(solution, deps, hooks, pre_deps_hooks, unpinned_deps)
1735
1736
1737def _FlattenDep(dep, deps, hooks, pre_deps_hooks, unpinned_deps):
1738 """Visits a dependency in order to flatten it (see CMDflatten).
1739
1740 Arguments:
1741 dep (Dependency): dependency to process
1742
1743 Out-parameters:
1744 deps (dict): will be filled with flattened deps
1745 hooks (list): will be filled with flattened hooks
1746 pre_deps_hooks (list): will be filled with flattened pre_deps_hooks
1747 unpinned_deps (dict): will be filled with unpinned deps
1748 """
1749 logging.debug('_FlattenDep(%r)', dep)
1750
1751 _AddDep(dep, deps, unpinned_deps)
1752
1753 deps_by_name = dict((d.name, d) for d in dep.dependencies)
1754 for recurse_dep_name in (dep.recursedeps or []):
1755 _FlattenRecurse(
1756 deps_by_name[recurse_dep_name], deps, hooks, pre_deps_hooks,
1757 unpinned_deps)
1758
1759 # TODO(phajdan.jr): also handle hooks_os.
1760 hooks.extend([(dep, hook) for hook in dep.deps_hooks])
1761 pre_deps_hooks.extend(
1762 [(dep, {'action': hook}) for hook in dep.pre_deps_hooks])
1763
1764
1765def _FlattenRecurse(dep, deps, hooks, pre_deps_hooks, unpinned_deps):
1766 """Helper for flatten that recurses into |dep|'s dependencies.
1767
1768 Arguments:
1769 dep (Dependency): dependency to process
1770
1771 Out-parameters:
1772 deps (dict): will be filled with flattened deps
1773 hooks (list): will be filled with flattened hooks
1774 pre_deps_hooks (list): will be filled with flattened pre_deps_hooks
1775 unpinned_deps (dict): will be filled with unpinned deps
1776 """
1777 logging.debug('_FlattenRecurse(%r)', dep)
1778
1779 # TODO(phajdan.jr): also handle deps_os.
1780 for dep in dep.dependencies:
1781 _FlattenDep(dep, deps, hooks, pre_deps_hooks, unpinned_deps)
1782
1783
1784def _AddDep(dep, deps, unpinned_deps):
1785 """Helper to add a dependency to flattened lists.
1786
1787 Arguments:
1788 dep (Dependency): dependency to process
1789
1790 Out-parameters:
1791 deps (dict): will be filled with flattened deps
1792 unpinned_deps (dict): will be filled with unpinned deps
1793 """
1794 logging.debug('_AddDep(%r)', dep)
1795
1796 assert dep.name not in deps
1797 deps[dep.name] = dep
1798
1799 # Detect unpinned deps.
1800 _, revision = gclient_utils.SplitUrlRevision(dep.url)
1801 if not revision or not gclient_utils.IsGitSha(revision):
1802 unpinned_deps[dep.name] = dep
1803
1804
1805def _DepsToLines(deps):
1806 """Converts |deps| dict to list of lines for output."""
1807 s = ['deps = {']
1808 for name, dep in sorted(deps.iteritems()):
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +02001809 condition_part = ([' "condition": "%s",' % dep.condition]
1810 if dep.condition else [])
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001811 s.extend([
1812 ' # %s' % dep.hierarchy(include_url=False),
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +02001813 ' "%s": {' % (name,),
1814 ' "url": "%s",' % (dep.url,),
1815 ] + condition_part + [
1816 ' },',
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001817 '',
1818 ])
1819 s.extend(['}', ''])
1820 return s
1821
1822
1823def _HooksToLines(name, hooks):
1824 """Converts |hooks| list to list of lines for output."""
1825 s = ['%s = [' % name]
1826 for dep, hook in hooks:
1827 s.extend([
1828 ' # %s' % dep.hierarchy(include_url=False),
1829 ' {',
1830 ])
1831 if 'name' in hook:
1832 s.append(' "name": "%s",' % hook['name'])
1833 if 'pattern' in hook:
1834 s.append(' "pattern": "%s",' % hook['pattern'])
1835 # TODO(phajdan.jr): actions may contain paths that need to be adjusted,
1836 # i.e. they may be relative to the dependency path, not solution root.
1837 s.extend(
1838 [' "action": ['] +
1839 [' "%s",' % arg for arg in hook['action']] +
1840 [' ]', ' },', '']
1841 )
1842 s.extend([']', ''])
1843 return s
1844
1845
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001846def CMDgrep(parser, args):
1847 """Greps through git repos managed by gclient.
1848
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001849 Runs 'git grep [args...]' for each module.
1850 """
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001851 # We can't use optparse because it will try to parse arguments sent
1852 # to git grep and throw an error. :-(
1853 if not args or re.match('(-h|--help)$', args[0]):
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001854 print(
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001855 'Usage: gclient grep [-j <N>] git-grep-args...\n\n'
1856 'Example: "gclient grep -j10 -A2 RefCountedBase" runs\n"git grep '
1857 '-A2 RefCountedBase" on each of gclient\'s git\nrepos with up to '
1858 '10 jobs.\n\nBonus: page output by appending "|& less -FRSX" to the'
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001859 ' end of your query.',
1860 file=sys.stderr)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001861 return 1
1862
1863 jobs_arg = ['--jobs=1']
1864 if re.match(r'(-j|--jobs=)\d+$', args[0]):
1865 jobs_arg, args = args[:1], args[1:]
1866 elif re.match(r'(-j|--jobs)$', args[0]):
1867 jobs_arg, args = args[:2], args[2:]
1868
1869 return CMDrecurse(
1870 parser,
1871 jobs_arg + ['--ignore', '--prepend-dir', '--no-progress', '--scm=git',
1872 'git', 'grep', '--null', '--color=Always'] + args)
davidbarr@chromium.org12f944e2012-03-01 02:18:31 +00001873
1874
stip@chromium.orga735da22015-04-29 23:18:20 +00001875def CMDroot(parser, args):
1876 """Outputs the solution root (or current dir if there isn't one)."""
1877 (options, args) = parser.parse_args(args)
1878 client = GClient.LoadCurrentConfig(options)
1879 if client:
1880 print(os.path.abspath(client.root_dir))
1881 else:
1882 print(os.path.abspath('.'))
1883
1884
agablea98a6cd2016-11-15 14:30:10 -08001885@subcommand.usage('[url]')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001886def CMDconfig(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001887 """Creates a .gclient file in the current directory.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001888
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001889 This specifies the configuration for further commands. After update/sync,
1890 top-level DEPS files in each module are read to determine dependent
1891 modules to operate on as well. If optional [url] parameter is
1892 provided, then configuration is read from a specified Subversion server
1893 URL.
1894 """
szager@chromium.orge2e03202012-07-31 18:05:16 +00001895 # We do a little dance with the --gclientfile option. 'gclient config' is the
1896 # only command where it's acceptable to have both '--gclientfile' and '--spec'
1897 # arguments. So, we temporarily stash any --gclientfile parameter into
1898 # options.output_config_file until after the (gclientfile xor spec) error
1899 # check.
1900 parser.remove_option('--gclientfile')
1901 parser.add_option('--gclientfile', dest='output_config_file',
1902 help='Specify an alternate .gclient file')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001903 parser.add_option('--name',
1904 help='overrides the default name for the solution')
nsylvain@google.comefc80932011-05-31 21:27:56 +00001905 parser.add_option('--deps-file', default='DEPS',
1906 help='overrides the default name for the DEPS file for the'
1907 'main solutions and all sub-dependencies')
smutae7ea312016-07-18 11:59:41 -07001908 parser.add_option('--unmanaged', action='store_true', default=False,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001909 help='overrides the default behavior to make it possible '
smutae7ea312016-07-18 11:59:41 -07001910 'to have the main solution untouched by gclient '
1911 '(gclient will check out unmanaged dependencies but '
1912 'will never sync them)')
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001913 parser.add_option('--cache-dir',
1914 help='(git only) Cache all git repos into this dir and do '
1915 'shared clones from the cache, instead of cloning '
1916 'directly from the remote. (experimental)')
szager@chromium.orge2e03202012-07-31 18:05:16 +00001917 parser.set_defaults(config_filename=None)
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001918 (options, args) = parser.parse_args(args)
szager@chromium.orge2e03202012-07-31 18:05:16 +00001919 if options.output_config_file:
1920 setattr(options, 'config_filename', getattr(options, 'output_config_file'))
maruel@chromium.org5fc2a332010-05-26 19:37:15 +00001921 if ((options.spec and args) or len(args) > 2 or
1922 (not options.spec and not args)):
1923 parser.error('Inconsistent arguments. Use either --spec or one or 2 args')
1924
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001925 client = GClient('.', options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001926 if options.spec:
1927 client.SetConfig(options.spec)
1928 else:
maruel@chromium.org1ab7ffc2009-06-03 17:21:37 +00001929 base_url = args[0].rstrip('/')
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001930 if not options.name:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001931 name = base_url.split('/')[-1]
nsylvain@google.com12649ef2011-06-01 17:11:20 +00001932 if name.endswith('.git'):
1933 name = name[:-4]
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001934 else:
1935 # specify an alternate relpath for the given URL.
1936 name = options.name
agable@chromium.orgf2214672015-10-27 21:02:48 +00001937 if not os.path.abspath(os.path.join(os.getcwd(), name)).startswith(
1938 os.getcwd()):
1939 parser.error('Do not pass a relative path for --name.')
1940 if any(x in ('..', '.', '/', '\\') for x in name.split(os.sep)):
1941 parser.error('Do not include relative path components in --name.')
1942
nsylvain@google.comefc80932011-05-31 21:27:56 +00001943 deps_file = options.deps_file
agablea98a6cd2016-11-15 14:30:10 -08001944 client.SetDefaultConfig(name, deps_file, base_url,
smutae7ea312016-07-18 11:59:41 -07001945 managed=not options.unmanaged,
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001946 cache_dir=options.cache_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001947 client.SaveConfig()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001948 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001949
1950
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001951@subcommand.epilog("""Example:
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001952 gclient pack > patch.txt
1953 generate simple patch for configured client and dependences
1954""")
1955def CMDpack(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001956 """Generates a patch which can be applied at the root of the tree.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001957
agabled437d762016-10-17 09:35:11 -07001958 Internally, runs 'git diff' on each checked out module and
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001959 dependencies, and performs minimal postprocessing of the output. The
1960 resulting patch is printed to stdout and can be applied to a freshly
1961 checked out tree via 'patch -p0 < patchfile'.
1962 """
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001963 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1964 help='override deps for the specified (comma-separated) '
1965 'platform(s); \'all\' will process all deps_os '
1966 'references')
haitao.feng@intel.com306080c2012-05-04 13:11:29 +00001967 parser.remove_option('--jobs')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001968 (options, args) = parser.parse_args(args)
iannucci@chromium.org50395ea2013-04-04 04:47:42 +00001969 # Force jobs to 1 so the stdout is not annotated with the thread ids
haitao.feng@intel.com306080c2012-05-04 13:11:29 +00001970 options.jobs = 1
kbr@google.comab318592009-09-04 00:54:55 +00001971 client = GClient.LoadCurrentConfig(options)
1972 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001973 raise gclient_utils.Error('client not configured; see \'gclient config\'')
kbr@google.comab318592009-09-04 00:54:55 +00001974 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00001975 client.PrintLocationAndContents()
kbr@google.comab318592009-09-04 00:54:55 +00001976 return client.RunOnDeps('pack', args)
1977
1978
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001979def CMDstatus(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001980 """Shows modification status for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001981 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1982 help='override deps for the specified (comma-separated) '
1983 'platform(s); \'all\' will process all deps_os '
1984 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001985 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001986 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001987 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001988 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001989 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00001990 client.PrintLocationAndContents()
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001991 return client.RunOnDeps('status', args)
1992
1993
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001994@subcommand.epilog("""Examples:
maruel@chromium.org79692d62010-05-14 18:57:13 +00001995 gclient sync
1996 update files from SCM according to current configuration,
1997 *for modules which have changed since last update or sync*
1998 gclient sync --force
1999 update files from SCM according to current configuration, for
2000 all modules (useful for recovering files deleted from local copy)
2001 gclient sync --revision src@31000
2002 update src directory to r31000
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002003
2004JSON output format:
2005If the --output-json option is specified, the following document structure will
2006be emitted to the provided file. 'null' entries may occur for subprojects which
2007are present in the gclient solution, but were not processed (due to custom_deps,
2008os_deps, etc.)
2009
2010{
2011 "solutions" : {
2012 "<name>": { # <name> is the posix-normalized path to the solution.
agabled437d762016-10-17 09:35:11 -07002013 "revision": [<git id hex string>|null],
2014 "scm": ["git"|null],
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002015 }
2016 }
2017}
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002018""")
2019def CMDsync(parser, args):
2020 """Checkout/update all modules."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002021 parser.add_option('-f', '--force', action='store_true',
2022 help='force update even for unchanged modules')
2023 parser.add_option('-n', '--nohooks', action='store_true',
2024 help='don\'t run hooks after the update is complete')
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00002025 parser.add_option('-p', '--noprehooks', action='store_true',
2026 help='don\'t run pre-DEPS hooks', default=False)
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002027 parser.add_option('-r', '--revision', action='append',
2028 dest='revisions', metavar='REV', default=[],
2029 help='Enforces revision/hash for the solutions with the '
2030 'format src@rev. The src@ part is optional and can be '
2031 'skipped. -r can be used multiple times when .gclient '
2032 'has multiple solutions configured and will work even '
agablea98a6cd2016-11-15 14:30:10 -08002033 'if the src@ part is skipped.')
maruel@chromium.org794207e2013-03-08 15:29:43 +00002034 parser.add_option('--with_branch_heads', action='store_true',
2035 help='Clone git "branch_heads" refspecs in addition to '
2036 'the default refspecs. This adds about 1/2GB to a '
2037 'full checkout. (git only)')
szager@chromium.org8d3348f2014-08-19 22:49:16 +00002038 parser.add_option('--with_tags', action='store_true',
2039 help='Clone git tags in addition to the default refspecs.')
agable2697cd12016-06-28 10:23:53 -07002040 parser.add_option('-H', '--head', action='store_true',
agablea98a6cd2016-11-15 14:30:10 -08002041 help='DEPRECATED: only made sense with safesync urls.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002042 parser.add_option('-D', '--delete_unversioned_trees', action='store_true',
steveblock@chromium.org98e69452012-02-16 16:36:43 +00002043 help='Deletes from the working copy any dependencies that '
2044 'have been removed since the last sync, as long as '
2045 'there are no local modifications. When used with '
2046 '--force, such dependencies are removed even if they '
2047 'have local modifications. When used with --reset, '
2048 'all untracked directories are removed from the '
tnagel@chromium.orga2aaa632014-02-28 21:47:27 +00002049 'working copy, excluding those which are explicitly '
steveblock@chromium.org98e69452012-02-16 16:36:43 +00002050 'ignored in the repository.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002051 parser.add_option('-R', '--reset', action='store_true',
2052 help='resets any local changes before updating (git only)')
bauerb@chromium.org2aad1b22011-07-22 12:00:41 +00002053 parser.add_option('-M', '--merge', action='store_true',
2054 help='merge upstream changes instead of trying to '
2055 'fast-forward or rebase')
dnj@chromium.org5b23e872015-02-20 21:25:57 +00002056 parser.add_option('-A', '--auto_rebase', action='store_true',
2057 help='Automatically rebase repositories against local '
2058 'checkout during update (git only).')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002059 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2060 help='override deps for the specified (comma-separated) '
2061 'platform(s); \'all\' will process all deps_os '
2062 'references')
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +00002063 parser.add_option('--upstream', action='store_true',
2064 help='Make repo state match upstream branch.')
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002065 parser.add_option('--output-json',
2066 help='Output a json document to this path containing '
2067 'summary information about the sync.')
primiano@chromium.org5439ea52014-08-06 17:18:18 +00002068 parser.add_option('--no-history', action='store_true',
2069 help='GIT ONLY - Reduces the size/time of the checkout at '
2070 'the cost of no history. Requires Git 1.9+')
hinoka@chromium.org46b87412014-05-15 00:42:05 +00002071 parser.add_option('--shallow', action='store_true',
2072 help='GIT ONLY - Do a shallow clone into the cache dir. '
2073 'Requires Git 1.9+')
e.hakkinen@samsung.come8bc1aa2015-04-08 08:00:37 +00002074 parser.add_option('--no_bootstrap', '--no-bootstrap',
2075 action='store_true',
2076 help='Don\'t bootstrap from Google Storage.')
hinoka@chromium.org8a10f6d2014-06-23 18:38:57 +00002077 parser.add_option('--ignore_locks', action='store_true',
2078 help='GIT ONLY - Ignore cache locks.')
iannucci@chromium.org30a07982016-04-07 21:35:19 +00002079 parser.add_option('--break_repo_locks', action='store_true',
2080 help='GIT ONLY - Forcibly remove repo locks (e.g. '
2081 'index.lock). This should only be used if you know for '
2082 'certain that this invocation of gclient is the only '
2083 'thing operating on the git repos (e.g. on a bot).')
nodir@chromium.org5b48e482016-03-18 20:27:54 +00002084 parser.add_option('--lock_timeout', type='int', default=5000,
szager@chromium.orgdbb6f822016-02-02 22:59:30 +00002085 help='GIT ONLY - Deadline (in seconds) to wait for git '
nodir@chromium.org5b48e482016-03-18 20:27:54 +00002086 'cache lock to become available. Default is %default.')
agabled437d762016-10-17 09:35:11 -07002087 # TODO(agable): Remove these when the oldest CrOS release milestone is M56.
2088 parser.add_option('-t', '--transitive', action='store_true',
2089 help='DEPRECATED: This is a no-op.')
sdefresne69b1be12016-10-18 05:48:02 -07002090 parser.add_option('-m', '--manually_grab_svn_rev', action='store_true',
agabled437d762016-10-17 09:35:11 -07002091 help='DEPRECATED: This is a no-op.')
Paweł Hajdan, Jr7c7b5592017-05-23 15:06:05 +02002092 # TODO(phajdan.jr): Remove validation options once default (crbug/570091).
Paweł Hajdan, Jr694773d2017-05-29 16:06:23 +02002093 parser.add_option('--validate-syntax', action='store_true', default=True,
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +02002094 help='Validate the .gclient and DEPS syntax')
Paweł Hajdan, Jr7c7b5592017-05-23 15:06:05 +02002095 parser.add_option('--disable-syntax-validation', action='store_false',
2096 dest='validate_syntax',
2097 help='Disable validation of .gclient and DEPS syntax.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002098 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002099 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002100
2101 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002102 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002103
smutae7ea312016-07-18 11:59:41 -07002104 if options.revisions and options.head:
2105 # TODO(maruel): Make it a parser.error if it doesn't break any builder.
2106 print('Warning: you cannot use both --head and --revision')
2107
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002108 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002109 client.PrintLocationAndContents()
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002110 ret = client.RunOnDeps('update', args)
2111 if options.output_json:
2112 slns = {}
2113 for d in client.subtree(True):
2114 normed = d.name.replace('\\', '/').rstrip('/') + '/'
2115 slns[normed] = {
2116 'revision': d.got_revision,
2117 'scm': d.used_scm.name if d.used_scm else None,
hinoka@chromium.org17db9052014-05-10 01:11:29 +00002118 'url': str(d.url) if d.url else None,
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002119 }
2120 with open(options.output_json, 'wb') as f:
2121 json.dump({'solutions': slns}, f)
2122 return ret
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002123
2124
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002125CMDupdate = CMDsync
2126
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002127
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +02002128def CMDvalidate(parser, args):
2129 """Validates the .gclient and DEPS syntax."""
2130 options, args = parser.parse_args(args)
2131 options.validate_syntax = True
2132 client = GClient.LoadCurrentConfig(options)
2133 rv = client.RunOnDeps('validate', args)
2134 if rv == 0:
2135 print('validate: SUCCESS')
2136 else:
2137 print('validate: FAILURE')
2138 return rv
2139
2140
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002141def CMDdiff(parser, args):
2142 """Displays local diff for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002143 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2144 help='override deps for the specified (comma-separated) '
2145 'platform(s); \'all\' will process all deps_os '
2146 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002147 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002148 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002149 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002150 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002151 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002152 client.PrintLocationAndContents()
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002153 return client.RunOnDeps('diff', args)
2154
2155
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002156def CMDrevert(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002157 """Reverts all modifications in every dependencies.
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00002158
2159 That's the nuclear option to get back to a 'clean' state. It removes anything
agabled437d762016-10-17 09:35:11 -07002160 that shows up in git status."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002161 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2162 help='override deps for the specified (comma-separated) '
2163 'platform(s); \'all\' will process all deps_os '
2164 'references')
2165 parser.add_option('-n', '--nohooks', action='store_true',
2166 help='don\'t run hooks after the revert is complete')
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00002167 parser.add_option('-p', '--noprehooks', action='store_true',
2168 help='don\'t run pre-DEPS hooks', default=False)
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +00002169 parser.add_option('--upstream', action='store_true',
2170 help='Make repo state match upstream branch.')
iannucci@chromium.orgbf525dc2016-04-07 22:00:28 +00002171 parser.add_option('--break_repo_locks', action='store_true',
2172 help='GIT ONLY - Forcibly remove repo locks (e.g. '
2173 'index.lock). This should only be used if you know for '
2174 'certain that this invocation of gclient is the only '
2175 'thing operating on the git repos (e.g. on a bot).')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002176 (options, args) = parser.parse_args(args)
2177 # --force is implied.
2178 options.force = True
steveblock@chromium.org98e69452012-02-16 16:36:43 +00002179 options.reset = False
2180 options.delete_unversioned_trees = False
agablec903d732016-07-26 09:07:24 -07002181 options.merge = False
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002182 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002183 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002184 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002185 return client.RunOnDeps('revert', args)
2186
2187
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002188def CMDrunhooks(parser, args):
2189 """Runs hooks for files that have been modified in the local working copy."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002190 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2191 help='override deps for the specified (comma-separated) '
2192 'platform(s); \'all\' will process all deps_os '
2193 'references')
2194 parser.add_option('-f', '--force', action='store_true', default=True,
2195 help='Deprecated. No effect.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002196 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002197 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002198 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002199 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002200 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002201 client.PrintLocationAndContents()
maruel@chromium.org5df6a462009-08-28 18:52:26 +00002202 options.force = True
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002203 options.nohooks = False
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002204 return client.RunOnDeps('runhooks', args)
2205
2206
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002207def CMDrevinfo(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002208 """Outputs revision info mapping for the client and its dependencies.
maruel@chromium.org9eda4112010-06-11 18:56:10 +00002209
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002210 This allows the capture of an overall 'revision' for the source tree that
maruel@chromium.org9eda4112010-06-11 18:56:10 +00002211 can be used to reproduce the same tree in the future. It is only useful for
agabled437d762016-10-17 09:35:11 -07002212 'unpinned dependencies', i.e. DEPS/deps references without a git hash.
2213 A git branch name isn't 'pinned' since the actual commit can change.
maruel@chromium.org9eda4112010-06-11 18:56:10 +00002214 """
2215 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2216 help='override deps for the specified (comma-separated) '
2217 'platform(s); \'all\' will process all deps_os '
2218 'references')
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00002219 parser.add_option('-a', '--actual', action='store_true',
2220 help='gets the actual checked out revisions instead of the '
2221 'ones specified in the DEPS and .gclient files')
maruel@chromium.org9eda4112010-06-11 18:56:10 +00002222 parser.add_option('-s', '--snapshot', action='store_true',
2223 help='creates a snapshot .gclient file of the current '
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00002224 'version of all repositories to reproduce the tree, '
2225 'implies -a')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002226 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002227 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002228 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002229 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002230 client.PrintRevInfo()
maruel@chromium.org79692d62010-05-14 18:57:13 +00002231 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002232
2233
szager@google.comb9a78d32012-03-13 18:46:21 +00002234def CMDhookinfo(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002235 """Outputs the hooks that would be run by `gclient runhooks`."""
szager@google.comb9a78d32012-03-13 18:46:21 +00002236 (options, args) = parser.parse_args(args)
2237 options.force = True
2238 client = GClient.LoadCurrentConfig(options)
2239 if not client:
2240 raise gclient_utils.Error('client not configured; see \'gclient config\'')
2241 client.RunOnDeps(None, [])
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002242 print('; '.join(' '.join(hook) for hook in client.GetHooks(options)))
szager@google.comb9a78d32012-03-13 18:46:21 +00002243 return 0
2244
2245
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00002246def CMDverify(parser, args):
2247 """Verifies the DEPS file deps are only from allowed_hosts."""
2248 (options, args) = parser.parse_args(args)
2249 client = GClient.LoadCurrentConfig(options)
2250 if not client:
2251 raise gclient_utils.Error('client not configured; see \'gclient config\'')
2252 client.RunOnDeps(None, [])
2253 # Look at each first-level dependency of this gclient only.
2254 for dep in client.dependencies:
2255 bad_deps = dep.findDepsFromNotAllowedHosts()
2256 if not bad_deps:
2257 continue
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002258 print("There are deps from not allowed hosts in file %s" % dep.deps_file)
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00002259 for bad_dep in bad_deps:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002260 print("\t%s at %s" % (bad_dep.name, bad_dep.url))
2261 print("allowed_hosts:", ', '.join(dep.allowed_hosts))
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00002262 sys.stdout.flush()
2263 raise gclient_utils.Error(
2264 'dependencies from disallowed hosts; check your DEPS file.')
2265 return 0
2266
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002267class OptionParser(optparse.OptionParser):
szager@chromium.orge2e03202012-07-31 18:05:16 +00002268 gclientfile_default = os.environ.get('GCLIENT_FILE', '.gclient')
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002269
2270 def __init__(self, **kwargs):
2271 optparse.OptionParser.__init__(
2272 self, version='%prog ' + __version__, **kwargs)
2273
2274 # Some arm boards have issues with parallel sync.
2275 if platform.machine().startswith('arm'):
2276 jobs = 1
2277 else:
2278 jobs = max(8, gclient_utils.NumLocalCpus())
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002279
2280 self.add_option(
2281 '-j', '--jobs', default=jobs, type='int',
2282 help='Specify how many SCM commands can run in parallel; defaults to '
tnagel@chromium.orga2aaa632014-02-28 21:47:27 +00002283 '%default on this machine')
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002284 self.add_option(
2285 '-v', '--verbose', action='count', default=0,
2286 help='Produces additional output for diagnostics. Can be used up to '
2287 'three times for more logging info.')
2288 self.add_option(
2289 '--gclientfile', dest='config_filename',
2290 help='Specify an alternate %s file' % self.gclientfile_default)
2291 self.add_option(
2292 '--spec',
2293 help='create a gclient file containing the provided string. Due to '
2294 'Cygwin/Python brokenness, it can\'t contain any newlines.')
2295 self.add_option(
2296 '--no-nag-max', default=False, action='store_true',
scottmg@chromium.orgf547c802013-09-27 17:55:26 +00002297 help='Ignored for backwards compatibility.')
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002298
2299 def parse_args(self, args=None, values=None):
2300 """Integrates standard options processing."""
2301 options, args = optparse.OptionParser.parse_args(self, args, values)
2302 levels = [logging.ERROR, logging.WARNING, logging.INFO, logging.DEBUG]
2303 logging.basicConfig(
2304 level=levels[min(options.verbose, len(levels) - 1)],
maruel@chromium.org0895b752011-08-26 20:40:33 +00002305 format='%(module)s(%(lineno)d) %(funcName)s:%(message)s')
szager@chromium.orge2e03202012-07-31 18:05:16 +00002306 if options.config_filename and options.spec:
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002307 self.error('Cannot specifiy both --gclientfile and --spec')
rdsmith@chromium.orgd9591f02014-02-05 19:28:20 +00002308 if (options.config_filename and
2309 options.config_filename != os.path.basename(options.config_filename)):
2310 self.error('--gclientfile target must be a filename, not a path')
szager@chromium.orge2e03202012-07-31 18:05:16 +00002311 if not options.config_filename:
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002312 options.config_filename = self.gclientfile_default
maruel@chromium.org0895b752011-08-26 20:40:33 +00002313 options.entries_filename = options.config_filename + '_entries'
2314 if options.jobs < 1:
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002315 self.error('--jobs must be 1 or higher')
maruel@chromium.org0895b752011-08-26 20:40:33 +00002316
2317 # These hacks need to die.
2318 if not hasattr(options, 'revisions'):
2319 # GClient.RunOnDeps expects it even if not applicable.
2320 options.revisions = []
smutae7ea312016-07-18 11:59:41 -07002321 if not hasattr(options, 'head'):
2322 options.head = None
maruel@chromium.org0895b752011-08-26 20:40:33 +00002323 if not hasattr(options, 'nohooks'):
2324 options.nohooks = True
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00002325 if not hasattr(options, 'noprehooks'):
2326 options.noprehooks = True
maruel@chromium.org0895b752011-08-26 20:40:33 +00002327 if not hasattr(options, 'deps_os'):
2328 options.deps_os = None
maruel@chromium.org0895b752011-08-26 20:40:33 +00002329 if not hasattr(options, 'force'):
2330 options.force = None
2331 return (options, args)
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002332
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002333
2334def disable_buffering():
2335 # Make stdout auto-flush so buildbot doesn't kill us during lengthy
2336 # operations. Python as a strong tendency to buffer sys.stdout.
2337 sys.stdout = gclient_utils.MakeFileAutoFlush(sys.stdout)
2338 # Make stdout annotated with the thread ids.
2339 sys.stdout = gclient_utils.MakeFileAnnotated(sys.stdout)
maruel@chromium.org0895b752011-08-26 20:40:33 +00002340
2341
sbc@chromium.org013731e2015-02-26 18:28:43 +00002342def main(argv):
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002343 """Doesn't parse the arguments here, just find the right subcommand to
2344 execute."""
maruel@chromium.org82798cb2012-02-23 18:16:12 +00002345 if sys.hexversion < 0x02060000:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002346 print(
maruel@chromium.org82798cb2012-02-23 18:16:12 +00002347 '\nYour python version %s is unsupported, please upgrade.\n' %
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002348 sys.version.split(' ', 1)[0],
2349 file=sys.stderr)
maruel@chromium.org82798cb2012-02-23 18:16:12 +00002350 return 2
bcwhite@chromium.org6683ab42013-02-11 16:13:47 +00002351 if not sys.executable:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002352 print(
2353 '\nPython cannot find the location of it\'s own executable.\n',
2354 file=sys.stderr)
bcwhite@chromium.org6683ab42013-02-11 16:13:47 +00002355 return 2
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002356 fix_encoding.fix_encoding()
2357 disable_buffering()
iannucci@chromium.org596cd5c2016-04-04 21:34:39 +00002358 setup_color.init()
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002359 dispatcher = subcommand.CommandDispatcher(__name__)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00002360 try:
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002361 return dispatcher.execute(OptionParser(), argv)
xusydoc@chromium.org2fd6c3f2013-05-03 21:57:55 +00002362 except KeyboardInterrupt:
2363 gclient_utils.GClientChildren.KillAllRemainingChildren()
2364 raise
vapier@chromium.orga81a56e2015-11-11 07:56:13 +00002365 except (gclient_utils.Error, subprocess2.CalledProcessError) as e:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002366 print('Error: %s' % str(e), file=sys.stderr)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00002367 return 1
borenet@google.com6a9b1682014-03-24 18:35:23 +00002368 finally:
2369 gclient_utils.PrintWarnings()
sbc@chromium.org013731e2015-02-26 18:28:43 +00002370 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002371
2372
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00002373if '__main__' == __name__:
sbc@chromium.org013731e2015-02-26 18:28:43 +00002374 try:
2375 sys.exit(main(sys.argv[1:]))
2376 except KeyboardInterrupt:
2377 sys.stderr.write('interrupted\n')
2378 sys.exit(1)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002379
2380# vim: ts=2:sw=2:tw=80:et: