blob: 0fb51d9879ced6be7a6cba53b79755389094e6e7 [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, Jr7e502612017-06-12 16:58:38 +0200619 # Since we heavily post-process things, freeze ones which should
620 # reflect original state of DEPS.
621 self._vars = gclient_utils.freeze(local_scope.get('vars', {}))
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +0200622
maruel@chromium.org271375b2010-06-23 19:17:38 +0000623 deps = local_scope.get('deps', {})
ilevy@chromium.org27ca3a92012-10-17 18:11:02 +0000624 if 'recursion' in local_scope:
625 self.recursion_override = local_scope.get('recursion')
626 logging.warning(
627 'Setting %s recursion to %d.', self.name, self.recursion_limit)
iannucci@chromium.orgafa11ac2016-05-04 22:17:34 +0000628 self.recursedeps = None
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000629 if 'recursedeps' in local_scope:
iannucci@chromium.orgafa11ac2016-05-04 22:17:34 +0000630 self.recursedeps = {}
631 for ent in local_scope['recursedeps']:
632 if isinstance(ent, basestring):
633 self.recursedeps[ent] = {"deps_file": self.deps_file}
634 else: # (depname, depsfilename)
635 self.recursedeps[ent[0]] = {"deps_file": ent[1]}
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000636 logging.warning('Found recursedeps %r.', repr(self.recursedeps))
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +0000637 # If present, save 'target_os' in the local_target_os property.
638 if 'target_os' in local_scope:
639 self.local_target_os = local_scope['target_os']
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000640 # load os specific dependencies if defined. these dependencies may
641 # override or extend the values defined by the 'deps' member.
bratell@opera.comed2b4fe2013-12-16 14:34:12 +0000642 target_os_list = self.target_os
643 if 'deps_os' in local_scope and target_os_list:
644 deps = self.MergeWithOsDeps(deps, local_scope['deps_os'], target_os_list)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000645
maruel@chromium.org271375b2010-06-23 19:17:38 +0000646 # If a line is in custom_deps, but not in the solution, we want to append
647 # this line to the solution.
648 for d in self.custom_deps:
649 if d not in deps:
650 deps[d] = self.custom_deps[d]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000651
652 # If use_relative_paths is set in the DEPS file, regenerate
653 # the dictionary using paths relative to the directory containing
cmp@chromium.orgf2def0a2014-07-16 19:48:54 +0000654 # the DEPS file. Also update recursedeps if use_relative_paths is
655 # enabled.
agabledce6ddc2016-09-08 10:02:16 -0700656 # If the deps file doesn't set use_relative_paths, but the parent did
657 # (and therefore set self.relative on this Dependency object), then we
658 # want to modify the deps and recursedeps by prepending the parent
659 # directory of this dependency.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000660 use_relative_paths = local_scope.get('use_relative_paths', False)
agabledce6ddc2016-09-08 10:02:16 -0700661 rel_prefix = None
maruel@chromium.org271375b2010-06-23 19:17:38 +0000662 if use_relative_paths:
agabledce6ddc2016-09-08 10:02:16 -0700663 rel_prefix = self.name
664 elif self._relative:
665 rel_prefix = os.path.dirname(self.name)
666 if rel_prefix:
cmp@chromium.orgf2def0a2014-07-16 19:48:54 +0000667 logging.warning('use_relative_paths enabled.')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000668 rel_deps = {}
669 for d, url in deps.items():
670 # normpath is required to allow DEPS to use .. in their
671 # dependency local path.
agabledce6ddc2016-09-08 10:02:16 -0700672 rel_deps[os.path.normpath(os.path.join(rel_prefix, d))] = url
673 logging.warning('Updating deps by prepending %s.', rel_prefix)
maruel@chromium.org271375b2010-06-23 19:17:38 +0000674 deps = rel_deps
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000675
cmp@chromium.orgf2def0a2014-07-16 19:48:54 +0000676 # Update recursedeps if it's set.
677 if self.recursedeps is not None:
agabledce6ddc2016-09-08 10:02:16 -0700678 logging.warning('Updating recursedeps by prepending %s.', rel_prefix)
iannucci@chromium.orgafa11ac2016-05-04 22:17:34 +0000679 rel_deps = {}
680 for depname, options in self.recursedeps.iteritems():
agabledce6ddc2016-09-08 10:02:16 -0700681 rel_deps[
682 os.path.normpath(os.path.join(rel_prefix, depname))] = options
cmp@chromium.orgf2def0a2014-07-16 19:48:54 +0000683 self.recursedeps = rel_deps
684
agabledce6ddc2016-09-08 10:02:16 -0700685
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000686 if 'allowed_hosts' in local_scope:
687 try:
688 self._allowed_hosts = frozenset(local_scope.get('allowed_hosts'))
689 except TypeError: # raised if non-iterable
690 pass
691 if not self._allowed_hosts:
692 logging.warning("allowed_hosts is specified but empty %s",
693 self._allowed_hosts)
694 raise gclient_utils.Error(
695 'ParseDepsFile(%s): allowed_hosts must be absent '
696 'or a non-empty iterable' % self.name)
697
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +0200698 self._gn_args_file = local_scope.get('gclient_gn_args_file')
699 self._gn_args = local_scope.get('gclient_gn_args', [])
700
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000701 # Convert the deps into real Dependency.
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000702 deps_to_add = []
Paweł Hajdan, Jrc7ba0332017-05-29 16:38:45 +0200703 for name, dep_value in deps.iteritems():
maruel@chromium.org68988972011-09-20 14:11:42 +0000704 should_process = self.recursion_limit and self.should_process
iannucci@chromium.orgafa11ac2016-05-04 22:17:34 +0000705 deps_file = self.deps_file
706 if self.recursedeps is not None:
707 ent = self.recursedeps.get(name)
708 if ent is not None:
709 deps_file = ent['deps_file']
Paweł Hajdan, Jr11016452017-05-29 18:02:15 +0200710 if dep_value is None:
711 continue
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +0200712 condition = None
713 condition_value = True
Paweł Hajdan, Jrc7ba0332017-05-29 16:38:45 +0200714 if isinstance(dep_value, basestring):
715 url = dep_value
716 else:
717 # This should be guaranteed by schema checking in gclient_eval.
718 assert isinstance(dep_value, dict)
719 url = dep_value['url']
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +0200720 condition = dep_value.get('condition')
721 if condition:
722 # TODO(phajdan.jr): should we also take custom vars into account?
Paweł Hajdan, Jr7e502612017-06-12 16:58:38 +0200723 condition_value = gclient_eval.EvaluateCondition(condition, self._vars)
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +0200724 should_process = should_process and condition_value
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000725 deps_to_add.append(Dependency(
agablea98a6cd2016-11-15 14:30:10 -0800726 self, name, url, None, None, self.custom_vars, None,
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +0200727 deps_file, should_process, use_relative_paths, condition,
728 condition_value))
maruel@chromium.orgb9be0652011-10-14 18:05:40 +0000729 deps_to_add.sort(key=lambda x: x.name)
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000730
731 # override named sets of hooks by the custom hooks
732 hooks_to_run = []
733 hook_names_to_suppress = [c.get('name', '') for c in self.custom_hooks]
734 for hook in local_scope.get('hooks', []):
735 if hook.get('name', '') not in hook_names_to_suppress:
736 hooks_to_run.append(hook)
Scott Grahamc4826742017-05-11 16:59:23 -0700737 if 'hooks_os' in local_scope and target_os_list:
738 hooks_os = local_scope['hooks_os']
739 # Specifically append these to ensure that hooks_os run after hooks.
740 for the_target_os in target_os_list:
741 the_target_os_hooks = hooks_os.get(the_target_os, [])
742 hooks_to_run.extend(the_target_os_hooks)
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000743
744 # add the replacements and any additions
745 for hook in self.custom_hooks:
746 if 'action' in hook:
747 hooks_to_run.append(hook)
748
Dirk Prankeda3a29e2017-02-27 15:29:36 -0800749 if self.recursion_limit:
Paweł Hajdan, Jr35b298f2017-05-23 14:37:05 +0200750 self._pre_deps_hooks = [self.GetHookAction(hook) for hook in
Dirk Prankeda3a29e2017-02-27 15:29:36 -0800751 local_scope.get('pre_deps_hooks', [])]
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000752
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000753 self.add_dependencies_and_close(deps_to_add, hooks_to_run)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000754 logging.info('ParseDepsFile(%s) done' % self.name)
755
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +0200756 def _get_option(self, attr, default):
757 obj = self
758 while not hasattr(obj, '_options'):
759 obj = obj.parent
760 return getattr(obj._options, attr, default)
761
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000762 def add_dependencies_and_close(self, deps_to_add, hooks):
763 """Adds the dependencies, hooks and mark the parsing as done."""
maruel@chromium.orgb9be0652011-10-14 18:05:40 +0000764 for dep in deps_to_add:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000765 if dep.verify_validity():
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000766 self.add_dependency(dep)
767 self._mark_as_parsed(hooks)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000768
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000769 def findDepsFromNotAllowedHosts(self):
770 """Returns a list of depenecies from not allowed hosts.
771
772 If allowed_hosts is not set, allows all hosts and returns empty list.
773 """
774 if not self._allowed_hosts:
775 return []
776 bad_deps = []
777 for dep in self._dependencies:
szager@chromium.orgbd772dd2014-11-05 18:43:08 +0000778 # Don't enforce this for custom_deps.
779 if dep.name in self._custom_deps:
780 continue
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000781 if isinstance(dep.url, basestring):
782 parsed_url = urlparse.urlparse(dep.url)
783 if parsed_url.netloc and parsed_url.netloc not in self._allowed_hosts:
784 bad_deps.append(dep)
785 return bad_deps
786
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +0000787 # Arguments number differs from overridden method
Quinten Yearsleyb2cc4a92016-12-15 13:53:26 -0800788 # pylint: disable=arguments-differ
maruel@chromium.org3742c842010-09-09 19:27:14 +0000789 def run(self, revision_overrides, command, args, work_queue, options):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000790 """Runs |command| then parse the DEPS file."""
maruel@chromium.org470b5432011-10-11 18:18:19 +0000791 logging.info('Dependency(%s).run()' % self.name)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000792 assert self._file_list == []
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000793 if not self.should_process:
794 return
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000795 # When running runhooks, there's no need to consult the SCM.
796 # All known hooks are expected to run unconditionally regardless of working
797 # copy state, so skip the SCM status check.
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +0200798 run_scm = command not in (
799 'flatten', 'runhooks', 'recurse', 'validate', None)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000800 parsed_url = self.LateOverride(self.url)
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000801 file_list = [] if not options.nohooks else None
szager@chromium.org3a3608d2014-10-22 21:13:52 +0000802 revision_override = revision_overrides.pop(self.name, None)
Dave Tubbda9712017-06-01 15:10:53 -0700803 if not revision_override and parsed_url:
804 revision_override = revision_overrides.get(parsed_url.split('@')[0], None)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000805 if run_scm and parsed_url:
agabled437d762016-10-17 09:35:11 -0700806 # Create a shallow copy to mutate revision.
807 options = copy.copy(options)
808 options.revision = revision_override
809 self._used_revision = options.revision
810 self._used_scm = gclient_scm.CreateSCM(
811 parsed_url, self.root.root_dir, self.name, self.outbuf,
812 out_cb=work_queue.out_cb)
813 self._got_revision = self._used_scm.RunCommand(command, options, args,
814 file_list)
815 if file_list:
816 file_list = [os.path.join(self.name, f.strip()) for f in file_list]
maruel@chromium.org68988972011-09-20 14:11:42 +0000817
818 # TODO(phajdan.jr): We should know exactly when the paths are absolute.
819 # Convert all absolute paths to relative.
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000820 for i in range(len(file_list or [])):
maruel@chromium.org68988972011-09-20 14:11:42 +0000821 # It depends on the command being executed (like runhooks vs sync).
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000822 if not os.path.isabs(file_list[i]):
maruel@chromium.org68988972011-09-20 14:11:42 +0000823 continue
824 prefix = os.path.commonprefix(
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000825 [self.root.root_dir.lower(), file_list[i].lower()])
826 file_list[i] = file_list[i][len(prefix):]
maruel@chromium.org68988972011-09-20 14:11:42 +0000827 # Strip any leading path separators.
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000828 while file_list[i].startswith(('\\', '/')):
829 file_list[i] = file_list[i][1:]
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000830
831 # Always parse the DEPS file.
832 self.ParseDepsFile()
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +0200833 if self._gn_args_file and command == 'update':
834 self.WriteGNArgsFile()
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000835 self._run_is_done(file_list or [], parsed_url)
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000836 if command in ('update', 'revert') and not options.noprehooks:
837 self.RunPreDepsHooks()
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000838
839 if self.recursion_limit:
840 # Parse the dependencies of this dependency.
841 for s in self.dependencies:
842 work_queue.enqueue(s)
843
844 if command == 'recurse':
agabled437d762016-10-17 09:35:11 -0700845 # Skip file only checkout.
846 scm = gclient_scm.GetScmName(parsed_url)
847 if not options.scm or scm in options.scm:
848 cwd = os.path.normpath(os.path.join(self.root.root_dir, self.name))
849 # Pass in the SCM type as an env variable. Make sure we don't put
850 # unicode strings in the environment.
851 env = os.environ.copy()
852 if scm:
853 env['GCLIENT_SCM'] = str(scm)
854 if parsed_url:
855 env['GCLIENT_URL'] = str(parsed_url)
856 env['GCLIENT_DEP_PATH'] = str(self.name)
857 if options.prepend_dir and scm == 'git':
858 print_stdout = False
859 def filter_fn(line):
860 """Git-specific path marshaling. It is optimized for git-grep."""
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000861
agabled437d762016-10-17 09:35:11 -0700862 def mod_path(git_pathspec):
863 match = re.match('^(\\S+?:)?([^\0]+)$', git_pathspec)
864 modified_path = os.path.join(self.name, match.group(2))
865 branch = match.group(1) or ''
866 return '%s%s' % (branch, modified_path)
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000867
agabled437d762016-10-17 09:35:11 -0700868 match = re.match('^Binary file ([^\0]+) matches$', line)
869 if match:
870 print('Binary file %s matches\n' % mod_path(match.group(1)))
871 return
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000872
agabled437d762016-10-17 09:35:11 -0700873 items = line.split('\0')
874 if len(items) == 2 and items[1]:
875 print('%s : %s' % (mod_path(items[0]), items[1]))
876 elif len(items) >= 2:
877 # Multiple null bytes or a single trailing null byte indicate
878 # git is likely displaying filenames only (such as with -l)
879 print('\n'.join(mod_path(path) for path in items if path))
880 else:
881 print(line)
882 else:
883 print_stdout = True
884 filter_fn = None
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000885
agabled437d762016-10-17 09:35:11 -0700886 if parsed_url is None:
887 print('Skipped omitted dependency %s' % cwd, file=sys.stderr)
888 elif os.path.isdir(cwd):
889 try:
890 gclient_utils.CheckCallAndFilter(
891 args, cwd=cwd, env=env, print_stdout=print_stdout,
892 filter_fn=filter_fn,
893 )
894 except subprocess2.CalledProcessError:
895 if not options.ignore:
896 raise
897 else:
898 print('Skipped missing %s' % cwd, file=sys.stderr)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000899
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +0200900 def WriteGNArgsFile(self):
901 lines = ['# Generated from %r' % self.deps_file]
902 for arg in self._gn_args:
903 lines.append('%s = %s' % (arg, ToGNString(self._vars[arg])))
904 with open(os.path.join(self.root.root_dir, self._gn_args_file), 'w') as f:
905 f.write('\n'.join(lines))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000906
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000907 @gclient_utils.lockedmethod
908 def _run_is_done(self, file_list, parsed_url):
909 # Both these are kept for hooks that are run as a separate tree traversal.
910 self._file_list = file_list
911 self._parsed_url = parsed_url
912 self._processed = True
913
szager@google.comb9a78d32012-03-13 18:46:21 +0000914 @staticmethod
Paweł Hajdan, Jr35b298f2017-05-23 14:37:05 +0200915 def GetHookAction(hook_dict):
szager@google.comb9a78d32012-03-13 18:46:21 +0000916 """Turns a parsed 'hook' dict into an executable command."""
917 logging.debug(hook_dict)
szager@google.comb9a78d32012-03-13 18:46:21 +0000918 command = hook_dict['action'][:]
919 if command[0] == 'python':
920 # If the hook specified "python" as the first item, the action is a
921 # Python script. Run it by starting a new copy of the same
922 # interpreter.
923 command[0] = sys.executable
szager@google.comb9a78d32012-03-13 18:46:21 +0000924 return command
925
926 def GetHooks(self, options):
927 """Evaluates all hooks, and return them in a flat list.
928
929 RunOnDeps() must have been called before to load the DEPS.
930 """
931 result = []
maruel@chromium.org68988972011-09-20 14:11:42 +0000932 if not self.should_process or not self.recursion_limit:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000933 # Don't run the hook when it is above recursion_limit.
szager@google.comb9a78d32012-03-13 18:46:21 +0000934 return result
maruel@chromium.orgdc7445d2010-07-09 21:05:29 +0000935 # If "--force" was specified, run all hooks regardless of what files have
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000936 # changed.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000937 if self.deps_hooks:
agabled437d762016-10-17 09:35:11 -0700938 # TODO(maruel): If the user is using git, then we don't know
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000939 # what files have changed so we always run all hooks. It'd be nice to fix
940 # that.
941 if (options.force or
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000942 gclient_scm.GetScmName(self.parsed_url) in ('git', None) or
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000943 os.path.isdir(os.path.join(self.root.root_dir, self.name, '.git'))):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000944 for hook_dict in self.deps_hooks:
Paweł Hajdan, Jr35b298f2017-05-23 14:37:05 +0200945 result.append(self.GetHookAction(hook_dict))
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000946 else:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000947 # Run hooks on the basis of whether the files from the gclient operation
948 # match each hook's pattern.
949 for hook_dict in self.deps_hooks:
950 pattern = re.compile(hook_dict['pattern'])
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000951 matching_file_list = [
952 f for f in self.file_list_and_children if pattern.search(f)
953 ]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000954 if matching_file_list:
Paweł Hajdan, Jr35b298f2017-05-23 14:37:05 +0200955 result.append(self.GetHookAction(hook_dict))
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000956 for s in self.dependencies:
szager@google.comb9a78d32012-03-13 18:46:21 +0000957 result.extend(s.GetHooks(options))
958 return result
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000959
szager@google.comb9a78d32012-03-13 18:46:21 +0000960 def RunHooksRecursively(self, options):
961 assert self.hooks_ran == False
maruel@chromium.org064186c2011-09-27 23:53:33 +0000962 self._hooks_ran = True
szager@google.comb9a78d32012-03-13 18:46:21 +0000963 for hook in self.GetHooks(options):
964 try:
ilevy@chromium.orgc28d3772013-07-12 19:42:37 +0000965 start_time = time.time()
szager@google.comb9a78d32012-03-13 18:46:21 +0000966 gclient_utils.CheckCallAndFilterAndHeader(
967 hook, cwd=self.root.root_dir, always=True)
vapier@chromium.orga81a56e2015-11-11 07:56:13 +0000968 except (gclient_utils.Error, subprocess2.CalledProcessError) as e:
szager@google.comb9a78d32012-03-13 18:46:21 +0000969 # Use a discrete exit status code of 2 to indicate that a hook action
970 # failed. Users of this script may wish to treat hook action failures
971 # differently from VC failures.
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +0000972 print('Error: %s' % str(e), file=sys.stderr)
szager@google.comb9a78d32012-03-13 18:46:21 +0000973 sys.exit(2)
ilevy@chromium.orgc28d3772013-07-12 19:42:37 +0000974 finally:
975 elapsed_time = time.time() - start_time
976 if elapsed_time > 10:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +0000977 print("Hook '%s' took %.2f secs" % (
978 gclient_utils.CommandToStr(hook), elapsed_time))
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000979
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000980 def RunPreDepsHooks(self):
981 assert self.processed
982 assert self.deps_parsed
983 assert not self.pre_deps_hooks_ran
984 assert not self.hooks_ran
985 for s in self.dependencies:
986 assert not s.processed
987 self._pre_deps_hooks_ran = True
988 for hook in self.pre_deps_hooks:
989 try:
990 start_time = time.time()
991 gclient_utils.CheckCallAndFilterAndHeader(
992 hook, cwd=self.root.root_dir, always=True)
vapier@chromium.orga81a56e2015-11-11 07:56:13 +0000993 except (gclient_utils.Error, subprocess2.CalledProcessError) as e:
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000994 # Use a discrete exit status code of 2 to indicate that a hook action
995 # failed. Users of this script may wish to treat hook action failures
996 # differently from VC failures.
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +0000997 print('Error: %s' % str(e), file=sys.stderr)
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000998 sys.exit(2)
999 finally:
1000 elapsed_time = time.time() - start_time
1001 if elapsed_time > 10:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001002 print("Hook '%s' took %.2f secs" % (
1003 gclient_utils.CommandToStr(hook), elapsed_time))
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001004
1005
maruel@chromium.org0d812442010-08-10 12:41:08 +00001006 def subtree(self, include_all):
maruel@chromium.orgad3287e2011-10-03 19:15:10 +00001007 """Breadth first recursion excluding root node."""
maruel@chromium.orgf13a4182011-09-22 00:26:15 +00001008 dependencies = self.dependencies
1009 for d in dependencies:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001010 if d.should_process or include_all:
maruel@chromium.orgad3287e2011-10-03 19:15:10 +00001011 yield d
maruel@chromium.orgf13a4182011-09-22 00:26:15 +00001012 for d in dependencies:
maruel@chromium.orgad3287e2011-10-03 19:15:10 +00001013 for i in d.subtree(include_all):
1014 yield i
1015
1016 def depth_first_tree(self):
1017 """Depth-first recursion including the root node."""
1018 yield self
1019 for i in self.dependencies:
1020 for j in i.depth_first_tree():
1021 if j.should_process:
1022 yield j
maruel@chromium.orgc57e4f22010-07-22 21:37:46 +00001023
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001024 @gclient_utils.lockedmethod
1025 def add_dependency(self, new_dep):
1026 self._dependencies.append(new_dep)
1027
1028 @gclient_utils.lockedmethod
1029 def _mark_as_parsed(self, new_hooks):
1030 self._deps_hooks.extend(new_hooks)
1031 self._deps_parsed = True
1032
maruel@chromium.org68988972011-09-20 14:11:42 +00001033 @property
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001034 @gclient_utils.lockedmethod
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +00001035 def dependencies(self):
1036 return tuple(self._dependencies)
1037
1038 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001039 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001040 def deps_hooks(self):
1041 return tuple(self._deps_hooks)
1042
1043 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001044 @gclient_utils.lockedmethod
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001045 def pre_deps_hooks(self):
1046 return tuple(self._pre_deps_hooks)
1047
1048 @property
1049 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001050 def parsed_url(self):
1051 return self._parsed_url
1052
1053 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001054 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001055 def deps_parsed(self):
maruel@chromium.org3223edd2011-10-10 23:17:39 +00001056 """This is purely for debugging purposes. It's not used anywhere."""
maruel@chromium.org064186c2011-09-27 23:53:33 +00001057 return self._deps_parsed
1058
1059 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001060 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001061 def processed(self):
1062 return self._processed
1063
1064 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001065 @gclient_utils.lockedmethod
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001066 def pre_deps_hooks_ran(self):
1067 return self._pre_deps_hooks_ran
1068
1069 @property
1070 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001071 def hooks_ran(self):
1072 return self._hooks_ran
1073
1074 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001075 @gclient_utils.lockedmethod
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00001076 def allowed_hosts(self):
1077 return self._allowed_hosts
1078
1079 @property
1080 @gclient_utils.lockedmethod
maruel@chromium.org861fd0f2010-07-23 03:05:05 +00001081 def file_list(self):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001082 return tuple(self._file_list)
1083
1084 @property
kustermann@google.coma692e8f2013-04-18 08:32:04 +00001085 def used_scm(self):
1086 """SCMWrapper instance for this dependency or None if not processed yet."""
1087 return self._used_scm
1088
1089 @property
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00001090 @gclient_utils.lockedmethod
1091 def got_revision(self):
1092 return self._got_revision
1093
1094 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001095 def file_list_and_children(self):
1096 result = list(self.file_list)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +00001097 for d in self.dependencies:
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001098 result.extend(d.file_list_and_children)
maruel@chromium.org68988972011-09-20 14:11:42 +00001099 return tuple(result)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +00001100
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001101 def __str__(self):
1102 out = []
agablea98a6cd2016-11-15 14:30:10 -08001103 for i in ('name', 'url', 'parsed_url', 'custom_deps',
maruel@chromium.org3c74bc92011-09-15 19:17:21 +00001104 'custom_vars', 'deps_hooks', 'file_list', 'should_process',
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00001105 'processed', 'hooks_ran', 'deps_parsed', 'requirements',
1106 'allowed_hosts'):
maruel@chromium.org3c74bc92011-09-15 19:17:21 +00001107 # First try the native property if it exists.
1108 if hasattr(self, '_' + i):
1109 value = getattr(self, '_' + i, False)
1110 else:
1111 value = getattr(self, i, False)
1112 if value:
1113 out.append('%s: %s' % (i, value))
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001114
1115 for d in self.dependencies:
1116 out.extend([' ' + x for x in str(d).splitlines()])
1117 out.append('')
1118 return '\n'.join(out)
1119
1120 def __repr__(self):
1121 return '%s: %s' % (self.name, self.url)
1122
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001123 def hierarchy(self, include_url=True):
maruel@chromium.orgbc2d2f92010-07-22 21:26:48 +00001124 """Returns a human-readable hierarchical reference to a Dependency."""
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001125 def format_name(d):
1126 if include_url:
1127 return '%s(%s)' % (d.name, d.url)
1128 return d.name
1129 out = format_name(self)
maruel@chromium.orgbffb9042010-07-22 20:59:36 +00001130 i = self.parent
1131 while i and i.name:
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001132 out = '%s -> %s' % (format_name(i), out)
maruel@chromium.orgbffb9042010-07-22 20:59:36 +00001133 i = i.parent
1134 return out
1135
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001136
1137class GClient(Dependency):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001138 """Object that represent a gclient checkout. A tree of Dependency(), one per
1139 solution or DEPS entry."""
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001140
1141 DEPS_OS_CHOICES = {
1142 "win32": "win",
1143 "win": "win",
1144 "cygwin": "win",
1145 "darwin": "mac",
1146 "mac": "mac",
1147 "unix": "unix",
1148 "linux": "unix",
1149 "linux2": "unix",
maruel@chromium.org244e3442011-06-12 15:20:55 +00001150 "linux3": "unix",
szager@chromium.orgf8c95cd2012-06-01 22:26:52 +00001151 "android": "android",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001152 }
1153
1154 DEFAULT_CLIENT_FILE_TEXT = ("""\
1155solutions = [
smutae7ea312016-07-18 11:59:41 -07001156 { "name" : "%(solution_name)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001157 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +00001158 "deps_file" : "%(deps_file)s",
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001159 "managed" : %(managed)s,
smutae7ea312016-07-18 11:59:41 -07001160 "custom_deps" : {
1161 },
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001162 },
1163]
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001164cache_dir = %(cache_dir)r
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001165""")
1166
1167 DEFAULT_SNAPSHOT_SOLUTION_TEXT = ("""\
smutae7ea312016-07-18 11:59:41 -07001168 { "name" : "%(solution_name)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001169 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +00001170 "deps_file" : "%(deps_file)s",
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001171 "managed" : %(managed)s,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001172 "custom_deps" : {
smutae7ea312016-07-18 11:59:41 -07001173%(solution_deps)s },
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001174 },
1175""")
1176
1177 DEFAULT_SNAPSHOT_FILE_TEXT = ("""\
1178# Snapshot generated with gclient revinfo --snapshot
1179solutions = [
maruel@chromium.org73e21142010-07-05 13:32:01 +00001180%(solution_list)s]
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001181""")
1182
1183 def __init__(self, root_dir, options):
maruel@chromium.org0d812442010-08-10 12:41:08 +00001184 # Do not change previous behavior. Only solution level and immediate DEPS
1185 # are processed.
1186 self._recursion_limit = 2
agablea98a6cd2016-11-15 14:30:10 -08001187 Dependency.__init__(self, None, None, None, True, None, None, None,
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +02001188 'unused', True, None, None, True)
maruel@chromium.org0d425922010-06-21 19:22:24 +00001189 self._options = options
maruel@chromium.org271375b2010-06-23 19:17:38 +00001190 if options.deps_os:
1191 enforced_os = options.deps_os.split(',')
1192 else:
1193 enforced_os = [self.DEPS_OS_CHOICES.get(sys.platform, 'unix')]
1194 if 'all' in enforced_os:
1195 enforced_os = self.DEPS_OS_CHOICES.itervalues()
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001196 self._enforced_os = tuple(set(enforced_os))
maruel@chromium.org271375b2010-06-23 19:17:38 +00001197 self._root_dir = root_dir
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001198 self.config_content = None
1199
borenet@google.com88d10082014-03-21 17:24:48 +00001200 def _CheckConfig(self):
1201 """Verify that the config matches the state of the existing checked-out
1202 solutions."""
1203 for dep in self.dependencies:
1204 if dep.managed and dep.url:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001205 scm = gclient_scm.CreateSCM(
1206 dep.url, self.root_dir, dep.name, self.outbuf)
smut@google.comd33eab32014-07-07 19:35:18 +00001207 actual_url = scm.GetActualRemoteURL(self._options)
borenet@google.com4e9be262014-04-08 19:40:30 +00001208 if actual_url and not scm.DoesRemoteURLMatch(self._options):
levarum@chromium.org27a6f9a2016-05-28 00:21:49 +00001209 mirror = scm.GetCacheMirror()
1210 if mirror:
1211 mirror_string = '%s (exists=%s)' % (mirror.mirror_path,
1212 mirror.exists())
1213 else:
1214 mirror_string = 'not used'
borenet@google.com0a427372014-04-02 19:12:13 +00001215 raise gclient_utils.Error('''
borenet@google.com88d10082014-03-21 17:24:48 +00001216Your .gclient file seems to be broken. The requested URL is different from what
borenet@google.com0a427372014-04-02 19:12:13 +00001217is actually checked out in %(checkout_path)s.
borenet@google.com88d10082014-03-21 17:24:48 +00001218
borenet@google.com97882362014-04-07 20:06:02 +00001219The .gclient file contains:
levarum@chromium.org27a6f9a2016-05-28 00:21:49 +00001220URL: %(expected_url)s (%(expected_scm)s)
1221Cache mirror: %(mirror_string)s
borenet@google.com97882362014-04-07 20:06:02 +00001222
1223The local checkout in %(checkout_path)s reports:
1224%(actual_url)s (%(actual_scm)s)
borenet@google.com88d10082014-03-21 17:24:48 +00001225
1226You should ensure that the URL listed in .gclient is correct and either change
agabled437d762016-10-17 09:35:11 -07001227it or fix the checkout.
borenet@google.com88d10082014-03-21 17:24:48 +00001228''' % {'checkout_path': os.path.join(self.root_dir, dep.name),
1229 'expected_url': dep.url,
1230 'expected_scm': gclient_scm.GetScmName(dep.url),
levarum@chromium.org27a6f9a2016-05-28 00:21:49 +00001231 'mirror_string' : mirror_string,
borenet@google.com88d10082014-03-21 17:24:48 +00001232 'actual_url': actual_url,
1233 'actual_scm': gclient_scm.GetScmName(actual_url)})
1234
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001235 def SetConfig(self, content):
maruel@chromium.orgf13a4182011-09-22 00:26:15 +00001236 assert not self.dependencies
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001237 config_dict = {}
1238 self.config_content = content
1239 try:
1240 exec(content, config_dict)
vapier@chromium.orga81a56e2015-11-11 07:56:13 +00001241 except SyntaxError as e:
maruel@chromium.org5990f9d2010-07-07 18:02:58 +00001242 gclient_utils.SyntaxErrorToError('.gclient', e)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001243
peter@chromium.org1efccc82012-04-27 16:34:38 +00001244 # Append any target OS that is not already being enforced to the tuple.
1245 target_os = config_dict.get('target_os', [])
stuartmorgan@chromium.org18a4f6a2012-11-02 13:37:24 +00001246 if config_dict.get('target_os_only', False):
1247 self._enforced_os = tuple(set(target_os))
1248 else:
1249 self._enforced_os = tuple(set(self._enforced_os).union(target_os))
1250
dyen@chromium.orgd915cca2014-08-07 21:41:37 +00001251 cache_dir = config_dict.get('cache_dir')
1252 if cache_dir:
1253 cache_dir = os.path.join(self.root_dir, cache_dir)
1254 cache_dir = os.path.abspath(cache_dir)
szager@chromium.orgcaf5bef2014-08-24 18:56:32 +00001255 # If running on a bot, force break any stale git cache locks.
dnj@chromium.orgb682b3e2014-08-25 19:17:12 +00001256 if os.path.exists(cache_dir) and os.environ.get('CHROME_HEADLESS'):
szager@chromium.org4848fb62014-08-24 19:16:31 +00001257 subprocess2.check_call(['git', 'cache', 'unlock', '--cache-dir',
1258 cache_dir, '--force', '--all'])
dyen@chromium.orgd915cca2014-08-07 21:41:37 +00001259 gclient_scm.GitWrapper.cache_dir = cache_dir
1260 git_cache.Mirror.SetCachePath(cache_dir)
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001261
stuartmorgan@chromium.org18a4f6a2012-11-02 13:37:24 +00001262 if not target_os and config_dict.get('target_os_only', False):
1263 raise gclient_utils.Error('Can\'t use target_os_only if target_os is '
1264 'not specified')
peter@chromium.org1efccc82012-04-27 16:34:38 +00001265
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001266 deps_to_add = []
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001267 for s in config_dict.get('solutions', []):
maruel@chromium.org81843b82010-06-28 16:49:26 +00001268 try:
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001269 deps_to_add.append(Dependency(
maruel@chromium.org81843b82010-06-28 16:49:26 +00001270 self, s['name'], s['url'],
smutae7ea312016-07-18 11:59:41 -07001271 s.get('managed', True),
maruel@chromium.org81843b82010-06-28 16:49:26 +00001272 s.get('custom_deps', {}),
maruel@chromium.org0d812442010-08-10 12:41:08 +00001273 s.get('custom_vars', {}),
petermayo@chromium.orge79161a2013-07-09 14:40:37 +00001274 s.get('custom_hooks', []),
nsylvain@google.comefc80932011-05-31 21:27:56 +00001275 s.get('deps_file', 'DEPS'),
agabledce6ddc2016-09-08 10:02:16 -07001276 True,
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +02001277 None,
1278 None,
1279 True))
maruel@chromium.org81843b82010-06-28 16:49:26 +00001280 except KeyError:
1281 raise gclient_utils.Error('Invalid .gclient file. Solution is '
1282 'incomplete: %s' % s)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001283 self.add_dependencies_and_close(deps_to_add, config_dict.get('hooks', []))
1284 logging.info('SetConfig() done')
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001285
1286 def SaveConfig(self):
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001287 gclient_utils.FileWrite(os.path.join(self.root_dir,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001288 self._options.config_filename),
1289 self.config_content)
1290
1291 @staticmethod
1292 def LoadCurrentConfig(options):
1293 """Searches for and loads a .gclient file relative to the current working
1294 dir. Returns a GClient object."""
szager@chromium.orge2e03202012-07-31 18:05:16 +00001295 if options.spec:
1296 client = GClient('.', options)
1297 client.SetConfig(options.spec)
1298 else:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00001299 if options.verbose:
1300 print('Looking for %s starting from %s\n' % (
1301 options.config_filename, os.getcwd()))
szager@chromium.orge2e03202012-07-31 18:05:16 +00001302 path = gclient_utils.FindGclientRoot(os.getcwd(), options.config_filename)
1303 if not path:
1304 return None
1305 client = GClient(path, options)
1306 client.SetConfig(gclient_utils.FileRead(
1307 os.path.join(path, options.config_filename)))
maruel@chromium.org69392e72011-10-13 22:09:00 +00001308
1309 if (options.revisions and
1310 len(client.dependencies) > 1 and
1311 any('@' not in r for r in options.revisions)):
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001312 print(
1313 ('You must specify the full solution name like --revision %s@%s\n'
1314 'when you have multiple solutions setup in your .gclient file.\n'
1315 'Other solutions present are: %s.') % (
maruel@chromium.org69392e72011-10-13 22:09:00 +00001316 client.dependencies[0].name,
1317 options.revisions[0],
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001318 ', '.join(s.name for s in client.dependencies[1:])),
1319 file=sys.stderr)
maruel@chromium.org15804092010-09-02 17:07:37 +00001320 return client
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001321
nsylvain@google.comefc80932011-05-31 21:27:56 +00001322 def SetDefaultConfig(self, solution_name, deps_file, solution_url,
agablea98a6cd2016-11-15 14:30:10 -08001323 managed=True, cache_dir=None):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001324 self.SetConfig(self.DEFAULT_CLIENT_FILE_TEXT % {
1325 'solution_name': solution_name,
1326 'solution_url': solution_url,
nsylvain@google.comefc80932011-05-31 21:27:56 +00001327 'deps_file': deps_file,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001328 'managed': managed,
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001329 'cache_dir': cache_dir,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001330 })
1331
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001332 def _SaveEntries(self):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001333 """Creates a .gclient_entries file to record the list of unique checkouts.
1334
1335 The .gclient_entries file lives in the same directory as .gclient.
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001336 """
1337 # Sometimes pprint.pformat will use {', sometimes it'll use { ' ... It
1338 # makes testing a bit too fun.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001339 result = 'entries = {\n'
maruel@chromium.org68988972011-09-20 14:11:42 +00001340 for entry in self.root.subtree(False):
agabled437d762016-10-17 09:35:11 -07001341 result += ' %s: %s,\n' % (pprint.pformat(entry.name),
1342 pprint.pformat(entry.parsed_url))
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001343 result += '}\n'
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001344 file_path = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org1333cb32011-10-04 23:40:16 +00001345 logging.debug(result)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001346 gclient_utils.FileWrite(file_path, result)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001347
1348 def _ReadEntries(self):
1349 """Read the .gclient_entries file for the given client.
1350
1351 Returns:
1352 A sequence of solution names, which will be empty if there is the
1353 entries file hasn't been created yet.
1354 """
1355 scope = {}
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001356 filename = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001357 if not os.path.exists(filename):
maruel@chromium.org73e21142010-07-05 13:32:01 +00001358 return {}
maruel@chromium.org5990f9d2010-07-07 18:02:58 +00001359 try:
1360 exec(gclient_utils.FileRead(filename), scope)
vapier@chromium.orga81a56e2015-11-11 07:56:13 +00001361 except SyntaxError as e:
maruel@chromium.org5990f9d2010-07-07 18:02:58 +00001362 gclient_utils.SyntaxErrorToError(filename, e)
Aaron Gable3721ee92017-04-03 14:53:14 -07001363 return scope.get('entries', {})
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001364
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001365 def _EnforceRevisions(self):
maruel@chromium.org918a9ae2010-05-28 15:50:30 +00001366 """Checks for revision overrides."""
1367 revision_overrides = {}
smutae7ea312016-07-18 11:59:41 -07001368 if self._options.head:
1369 return revision_overrides
joi@chromium.org792ea882010-11-10 02:37:27 +00001370 if not self._options.revisions:
1371 for s in self.dependencies:
smutae7ea312016-07-18 11:59:41 -07001372 if not s.managed:
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001373 self._options.revisions.append('%s@unmanaged' % s.name)
maruel@chromium.org307d1792010-05-31 20:03:13 +00001374 if not self._options.revisions:
1375 return revision_overrides
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001376 solutions_names = [s.name for s in self.dependencies]
smutae7ea312016-07-18 11:59:41 -07001377 index = 0
1378 for revision in self._options.revisions:
1379 if not '@' in revision:
maruel@chromium.org307d1792010-05-31 20:03:13 +00001380 # Support for --revision 123
smutae7ea312016-07-18 11:59:41 -07001381 revision = '%s@%s' % (solutions_names[index], revision)
1382 name, rev = revision.split('@', 1)
szager@chromium.org4ad264b2014-05-20 04:43:47 +00001383 revision_overrides[name] = rev
smutae7ea312016-07-18 11:59:41 -07001384 index += 1
maruel@chromium.org918a9ae2010-05-28 15:50:30 +00001385 return revision_overrides
1386
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001387 def RunOnDeps(self, command, args, ignore_requirements=False, progress=True):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001388 """Runs a command on each dependency in a client and its dependencies.
1389
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001390 Args:
1391 command: The command to use (e.g., 'status' or 'diff')
1392 args: list of str - extra arguments to add to the command line.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001393 """
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001394 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +00001395 raise gclient_utils.Error('No solution specified')
borenet@google.com0a427372014-04-02 19:12:13 +00001396
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001397 revision_overrides = {}
1398 # It's unnecessary to check for revision overrides for 'recurse'.
1399 # Save a few seconds by not calling _EnforceRevisions() in that case.
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +02001400 if command not in ('diff', 'recurse', 'runhooks', 'status', 'revert',
1401 'validate'):
szager@chromium.org5273b8a2014-08-21 15:10:10 +00001402 self._CheckConfig()
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001403 revision_overrides = self._EnforceRevisions()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001404 pm = None
maruel@chromium.org5b3f8852010-09-10 16:49:54 +00001405 # Disable progress for non-tty stdout.
iannucci@chromium.org596cd5c2016-04-04 21:34:39 +00001406 if (setup_color.IS_TTY and not self._options.verbose and progress):
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001407 if command in ('update', 'revert'):
1408 pm = Progress('Syncing projects', 1)
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +02001409 elif command in ('recurse', 'validate'):
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001410 pm = Progress(' '.join(args), 1)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001411 work_queue = gclient_utils.ExecutionQueue(
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001412 self._options.jobs, pm, ignore_requirements=ignore_requirements,
1413 verbose=self._options.verbose)
maruel@chromium.org049bced2010-08-12 13:37:20 +00001414 for s in self.dependencies:
1415 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +00001416 work_queue.flush(revision_overrides, command, args, options=self._options)
szager@chromium.org4ad264b2014-05-20 04:43:47 +00001417 if revision_overrides:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001418 print('Please fix your script, having invalid --revision flags will soon '
1419 'considered an error.', file=sys.stderr)
piman@chromium.org6f363722010-04-27 00:41:09 +00001420
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001421 # Once all the dependencies have been processed, it's now safe to run the
1422 # hooks.
1423 if not self._options.nohooks:
1424 self.RunHooksRecursively(self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001425
1426 if command == 'update':
ajwong@chromium.orgcdcee802009-06-23 15:30:42 +00001427 # Notify the user if there is an orphaned entry in their working copy.
1428 # Only delete the directory if there are no changes in it, and
1429 # delete_unversioned_trees is set to true.
maruel@chromium.org68988972011-09-20 14:11:42 +00001430 entries = [i.name for i in self.root.subtree(False) if i.url]
xusydoc@chromium.org885a9602013-05-31 09:54:40 +00001431 full_entries = [os.path.join(self.root_dir, e.replace('/', os.path.sep))
1432 for e in entries]
1433
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001434 for entry, prev_url in self._ReadEntries().iteritems():
maruel@chromium.org04dd7de2010-10-14 13:25:49 +00001435 if not prev_url:
1436 # entry must have been overridden via .gclient custom_deps
1437 continue
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001438 # Fix path separator on Windows.
1439 entry_fixed = entry.replace('/', os.path.sep)
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001440 e_dir = os.path.join(self.root_dir, entry_fixed)
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001441 # Use entry and not entry_fixed there.
jochen@chromium.orga78e5532013-03-11 13:33:03 +00001442 if (entry not in entries and
1443 (not any(path.startswith(entry + '/') for path in entries)) and
jochen@chromium.orgcc475722013-03-11 13:07:40 +00001444 os.path.exists(e_dir)):
primiano@chromium.org1c127382015-02-17 11:15:40 +00001445 # The entry has been removed from DEPS.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001446 scm = gclient_scm.CreateSCM(
1447 prev_url, self.root_dir, entry_fixed, self.outbuf)
xusydoc@chromium.org885a9602013-05-31 09:54:40 +00001448
1449 # Check to see if this directory is now part of a higher-up checkout.
borenet@google.com359bb642014-05-13 17:28:19 +00001450 scm_root = None
agabled437d762016-10-17 09:35:11 -07001451 try:
1452 scm_root = gclient_scm.scm.GIT.GetCheckoutRoot(scm.checkout_path)
1453 except subprocess2.CalledProcessError:
1454 pass
1455 if not scm_root:
borenet@google.com359bb642014-05-13 17:28:19 +00001456 logging.warning('Could not find checkout root for %s. Unable to '
1457 'determine whether it is part of a higher-level '
1458 'checkout, so not removing.' % entry)
1459 continue
primiano@chromium.org1c127382015-02-17 11:15:40 +00001460
1461 # This is to handle the case of third_party/WebKit migrating from
1462 # being a DEPS entry to being part of the main project.
1463 # If the subproject is a Git project, we need to remove its .git
1464 # folder. Otherwise git operations on that folder will have different
1465 # effects depending on the current working directory.
agabled437d762016-10-17 09:35:11 -07001466 if os.path.abspath(scm_root) == os.path.abspath(e_dir):
primiano@chromium.org1c127382015-02-17 11:15:40 +00001467 e_par_dir = os.path.join(e_dir, os.pardir)
agabled437d762016-10-17 09:35:11 -07001468 if gclient_scm.scm.GIT.IsInsideWorkTree(e_par_dir):
1469 par_scm_root = gclient_scm.scm.GIT.GetCheckoutRoot(e_par_dir)
primiano@chromium.org1c127382015-02-17 11:15:40 +00001470 # rel_e_dir : relative path of entry w.r.t. its parent repo.
1471 rel_e_dir = os.path.relpath(e_dir, par_scm_root)
agabled437d762016-10-17 09:35:11 -07001472 if gclient_scm.scm.GIT.IsDirectoryVersioned(
1473 par_scm_root, rel_e_dir):
primiano@chromium.org1c127382015-02-17 11:15:40 +00001474 save_dir = scm.GetGitBackupDirPath()
1475 # Remove any eventual stale backup dir for the same project.
1476 if os.path.exists(save_dir):
1477 gclient_utils.rmtree(save_dir)
1478 os.rename(os.path.join(e_dir, '.git'), save_dir)
1479 # When switching between the two states (entry/ is a subproject
1480 # -> entry/ is part of the outer project), it is very likely
1481 # that some files are changed in the checkout, unless we are
1482 # jumping *exactly* across the commit which changed just DEPS.
1483 # In such case we want to cleanup any eventual stale files
1484 # (coming from the old subproject) in order to end up with a
1485 # clean checkout.
agabled437d762016-10-17 09:35:11 -07001486 gclient_scm.scm.GIT.CleanupDir(par_scm_root, rel_e_dir)
primiano@chromium.org1c127382015-02-17 11:15:40 +00001487 assert not os.path.exists(os.path.join(e_dir, '.git'))
1488 print(('\nWARNING: \'%s\' has been moved from DEPS to a higher '
1489 'level checkout. The git folder containing all the local'
1490 ' branches has been saved to %s.\n'
1491 'If you don\'t care about its state you can safely '
1492 'remove that folder to free up space.') %
1493 (entry, save_dir))
1494 continue
1495
borenet@google.com359bb642014-05-13 17:28:19 +00001496 if scm_root in full_entries:
primiano@chromium.org1c127382015-02-17 11:15:40 +00001497 logging.info('%s is part of a higher level checkout, not removing',
1498 scm.GetCheckoutRoot())
xusydoc@chromium.org885a9602013-05-31 09:54:40 +00001499 continue
1500
1501 file_list = []
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001502 scm.status(self._options, [], file_list)
1503 modified_files = file_list != []
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001504 if (not self._options.delete_unversioned_trees or
1505 (modified_files and not self._options.force)):
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001506 # There are modified files in this entry. Keep warning until
1507 # removed.
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001508 print(('\nWARNING: \'%s\' is no longer part of this client. '
1509 'It is recommended that you manually remove it.\n') %
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001510 entry_fixed)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001511 else:
1512 # Delete the entry
maruel@chromium.org73e21142010-07-05 13:32:01 +00001513 print('\n________ deleting \'%s\' in \'%s\'' % (
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001514 entry_fixed, self.root_dir))
digit@chromium.orgdc112ac2013-04-24 13:00:19 +00001515 gclient_utils.rmtree(e_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001516 # record the current list of entries for next time
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001517 self._SaveEntries()
maruel@chromium.org17cdf762010-05-28 17:30:52 +00001518 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001519
1520 def PrintRevInfo(self):
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001521 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +00001522 raise gclient_utils.Error('No solution specified')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001523 # Load all the settings.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001524 work_queue = gclient_utils.ExecutionQueue(
1525 self._options.jobs, None, False, verbose=self._options.verbose)
maruel@chromium.org049bced2010-08-12 13:37:20 +00001526 for s in self.dependencies:
1527 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +00001528 work_queue.flush({}, None, [], options=self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001529
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001530 def GetURLAndRev(dep):
1531 """Returns the revision-qualified SCM url for a Dependency."""
1532 if dep.parsed_url is None:
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001533 return None
agabled437d762016-10-17 09:35:11 -07001534 url, _ = gclient_utils.SplitUrlRevision(dep.parsed_url)
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001535 scm = gclient_scm.CreateSCM(
agabled437d762016-10-17 09:35:11 -07001536 dep.parsed_url, self.root_dir, dep.name, self.outbuf)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001537 if not os.path.isdir(scm.checkout_path):
1538 return None
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001539 return '%s@%s' % (url, scm.revinfo(self._options, [], None))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001540
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001541 if self._options.snapshot:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001542 new_gclient = ''
1543 # First level at .gclient
1544 for d in self.dependencies:
1545 entries = {}
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001546 def GrabDeps(dep):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001547 """Recursively grab dependencies."""
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001548 for d in dep.dependencies:
1549 entries[d.name] = GetURLAndRev(d)
1550 GrabDeps(d)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001551 GrabDeps(d)
1552 custom_deps = []
1553 for k in sorted(entries.keys()):
1554 if entries[k]:
1555 # Quotes aren't escaped...
1556 custom_deps.append(' \"%s\": \'%s\',\n' % (k, entries[k]))
1557 else:
1558 custom_deps.append(' \"%s\": None,\n' % k)
1559 new_gclient += self.DEFAULT_SNAPSHOT_SOLUTION_TEXT % {
1560 'solution_name': d.name,
1561 'solution_url': d.url,
nsylvain@google.comefc80932011-05-31 21:27:56 +00001562 'deps_file': d.deps_file,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001563 'managed': d.managed,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001564 'solution_deps': ''.join(custom_deps),
1565 }
1566 # Print the snapshot configuration file
1567 print(self.DEFAULT_SNAPSHOT_FILE_TEXT % {'solution_list': new_gclient})
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +00001568 else:
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001569 entries = {}
maruel@chromium.org68988972011-09-20 14:11:42 +00001570 for d in self.root.subtree(False):
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001571 if self._options.actual:
1572 entries[d.name] = GetURLAndRev(d)
1573 else:
1574 entries[d.name] = d.parsed_url
1575 keys = sorted(entries.keys())
1576 for x in keys:
maruel@chromium.orgce464892010-08-12 17:12:18 +00001577 print('%s: %s' % (x, entries[x]))
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +00001578 logging.info(str(self))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001579
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001580 def ParseDepsFile(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001581 """No DEPS to parse for a .gclient file."""
maruel@chromium.org049bced2010-08-12 13:37:20 +00001582 raise gclient_utils.Error('Internal error')
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001583
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00001584 def PrintLocationAndContents(self):
1585 # Print out the .gclient file. This is longer than if we just printed the
1586 # client dict, but more legible, and it might contain helpful comments.
1587 print('Loaded .gclient config in %s:\n%s' % (
1588 self.root_dir, self.config_content))
1589
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001590 @property
maruel@chromium.org75a59272010-06-11 22:34:03 +00001591 def root_dir(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001592 """Root directory of gclient checkout."""
maruel@chromium.org75a59272010-06-11 22:34:03 +00001593 return self._root_dir
1594
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001595 @property
maruel@chromium.org271375b2010-06-23 19:17:38 +00001596 def enforced_os(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001597 """What deps_os entries that are to be parsed."""
maruel@chromium.org271375b2010-06-23 19:17:38 +00001598 return self._enforced_os
1599
maruel@chromium.org68988972011-09-20 14:11:42 +00001600 @property
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001601 def recursion_limit(self):
1602 """How recursive can each dependencies in DEPS file can load DEPS file."""
1603 return self._recursion_limit
1604
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +00001605 @property
cmp@chromium.orgc401ad12014-07-02 23:20:08 +00001606 def try_recursedeps(self):
1607 """Whether to attempt using recursedeps-style recursion processing."""
cmp@chromium.orge84ac912014-06-30 23:14:35 +00001608 return True
1609
1610 @property
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +00001611 def target_os(self):
1612 return self._enforced_os
1613
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001614
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001615#### gclient commands.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001616
1617
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001618@subcommand.usage('[command] [args ...]')
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001619def CMDrecurse(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001620 """Operates [command args ...] on all the dependencies.
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001621
1622 Runs a shell command on all entries.
qyearsley12fa6ff2016-08-24 09:18:40 -07001623 Sets GCLIENT_DEP_PATH environment variable as the dep's relative location to
ilevy@chromium.org37116242012-11-28 01:32:48 +00001624 root directory of the checkout.
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001625 """
1626 # Stop parsing at the first non-arg so that these go through to the command
1627 parser.disable_interspersed_args()
1628 parser.add_option('-s', '--scm', action='append', default=[],
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001629 help='Choose scm types to operate upon.')
maruel@chromium.org288054d2012-03-05 00:43:07 +00001630 parser.add_option('-i', '--ignore', action='store_true',
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001631 help='Ignore non-zero return codes from subcommands.')
1632 parser.add_option('--prepend-dir', action='store_true',
1633 help='Prepend relative dir for use with git <cmd> --null.')
1634 parser.add_option('--no-progress', action='store_true',
1635 help='Disable progress bar that shows sub-command updates')
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001636 options, args = parser.parse_args(args)
maruel@chromium.org45e9f2d2010-10-18 13:33:46 +00001637 if not args:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001638 print('Need to supply a command!', file=sys.stderr)
maruel@chromium.org45e9f2d2010-10-18 13:33:46 +00001639 return 1
maruel@chromium.org78cba522010-10-18 13:32:05 +00001640 root_and_entries = gclient_utils.GetGClientRootAndEntries()
1641 if not root_and_entries:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001642 print(
maruel@chromium.org78cba522010-10-18 13:32:05 +00001643 'You need to run gclient sync at least once to use \'recurse\'.\n'
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001644 'This is because .gclient_entries needs to exist and be up to date.',
1645 file=sys.stderr)
maruel@chromium.org78cba522010-10-18 13:32:05 +00001646 return 1
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001647
1648 # Normalize options.scm to a set()
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001649 scm_set = set()
1650 for scm in options.scm:
1651 scm_set.update(scm.split(','))
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001652 options.scm = scm_set
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001653
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001654 options.nohooks = True
1655 client = GClient.LoadCurrentConfig(options)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001656 return client.RunOnDeps('recurse', args, ignore_requirements=True,
1657 progress=not options.no_progress)
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001658
1659
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001660@subcommand.usage('[args ...]')
davidbarr@chromium.org12f944e2012-03-01 02:18:31 +00001661def CMDfetch(parser, args):
1662 """Fetches upstream commits for all modules.
1663
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001664 Completely git-specific. Simply runs 'git fetch [args ...]' for each module.
1665 """
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001666 (options, args) = parser.parse_args(args)
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00001667 return CMDrecurse(OptionParser(), [
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001668 '--jobs=%d' % options.jobs, '--scm=git', 'git', 'fetch'] + args)
1669
1670
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001671def CMDflatten(parser, args):
1672 """Flattens the solutions into a single DEPS file."""
1673 parser.add_option('--output-deps', help='Path to the output DEPS file')
1674 parser.add_option(
1675 '--require-pinned-revisions', action='store_true',
1676 help='Fail if any of the dependencies uses unpinned revision.')
1677 options, args = parser.parse_args(args)
1678
1679 options.nohooks = True
1680 client = GClient.LoadCurrentConfig(options)
1681
1682 # Only print progress if we're writing to a file. Otherwise, progress updates
1683 # could obscure intended output.
1684 code = client.RunOnDeps('flatten', args, progress=options.output_deps)
1685 if code != 0:
1686 return code
1687
1688 deps = {}
1689 hooks = []
1690 pre_deps_hooks = []
1691 unpinned_deps = {}
1692
1693 for solution in client.dependencies:
1694 _FlattenSolution(solution, deps, hooks, pre_deps_hooks, unpinned_deps)
1695
1696 if options.require_pinned_revisions and unpinned_deps:
1697 sys.stderr.write('The following dependencies are not pinned:\n')
1698 sys.stderr.write('\n'.join(sorted(unpinned_deps)))
1699 return 1
1700
1701 flattened_deps = '\n'.join(
Paweł Hajdan, Jr3c2aa832017-06-07 20:22:16 +02001702 _GNSettingsToLines(
1703 client.dependencies[0]._gn_args_file,
1704 client.dependencies[0]._gn_args) +
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001705 _DepsToLines(deps) +
1706 _HooksToLines('hooks', hooks) +
1707 _HooksToLines('pre_deps_hooks', pre_deps_hooks) +
1708 [''] # Ensure newline at end of file.
1709 )
1710
1711 if options.output_deps:
1712 with open(options.output_deps, 'w') as f:
1713 f.write(flattened_deps)
1714 else:
1715 print(flattened_deps)
1716
1717 return 0
1718
1719
1720def _FlattenSolution(solution, deps, hooks, pre_deps_hooks, unpinned_deps):
1721 """Visits a solution in order to flatten it (see CMDflatten).
1722
1723 Arguments:
1724 solution (Dependency): one of top-level solutions in .gclient
1725
1726 Out-parameters:
1727 deps (dict of name -> Dependency): will be filled with all Dependency
1728 objects indexed by their name
1729 hooks (list of (Dependency, hook)): will be filled with flattened hooks
1730 pre_deps_hooks (list of (Dependency, hook)): will be filled with flattened
1731 pre_deps_hooks
1732 unpinned_deps (dict of name -> Dependency): will be filled with unpinned
1733 deps
1734 """
1735 logging.debug('_FlattenSolution(%r)', solution)
1736
1737 _FlattenDep(solution, deps, hooks, pre_deps_hooks, unpinned_deps)
1738 _FlattenRecurse(solution, deps, hooks, pre_deps_hooks, unpinned_deps)
1739
1740
1741def _FlattenDep(dep, deps, hooks, pre_deps_hooks, unpinned_deps):
1742 """Visits a dependency in order to flatten it (see CMDflatten).
1743
1744 Arguments:
1745 dep (Dependency): dependency to process
1746
1747 Out-parameters:
1748 deps (dict): will be filled with flattened deps
1749 hooks (list): will be filled with flattened hooks
1750 pre_deps_hooks (list): will be filled with flattened pre_deps_hooks
1751 unpinned_deps (dict): will be filled with unpinned deps
1752 """
1753 logging.debug('_FlattenDep(%r)', dep)
1754
1755 _AddDep(dep, deps, unpinned_deps)
1756
1757 deps_by_name = dict((d.name, d) for d in dep.dependencies)
1758 for recurse_dep_name in (dep.recursedeps or []):
1759 _FlattenRecurse(
1760 deps_by_name[recurse_dep_name], deps, hooks, pre_deps_hooks,
1761 unpinned_deps)
1762
1763 # TODO(phajdan.jr): also handle hooks_os.
1764 hooks.extend([(dep, hook) for hook in dep.deps_hooks])
1765 pre_deps_hooks.extend(
1766 [(dep, {'action': hook}) for hook in dep.pre_deps_hooks])
1767
1768
1769def _FlattenRecurse(dep, deps, hooks, pre_deps_hooks, unpinned_deps):
1770 """Helper for flatten that recurses into |dep|'s dependencies.
1771
1772 Arguments:
1773 dep (Dependency): dependency to process
1774
1775 Out-parameters:
1776 deps (dict): will be filled with flattened deps
1777 hooks (list): will be filled with flattened hooks
1778 pre_deps_hooks (list): will be filled with flattened pre_deps_hooks
1779 unpinned_deps (dict): will be filled with unpinned deps
1780 """
1781 logging.debug('_FlattenRecurse(%r)', dep)
1782
1783 # TODO(phajdan.jr): also handle deps_os.
1784 for dep in dep.dependencies:
1785 _FlattenDep(dep, deps, hooks, pre_deps_hooks, unpinned_deps)
1786
1787
1788def _AddDep(dep, deps, unpinned_deps):
1789 """Helper to add a dependency to flattened lists.
1790
1791 Arguments:
1792 dep (Dependency): dependency to process
1793
1794 Out-parameters:
1795 deps (dict): will be filled with flattened deps
1796 unpinned_deps (dict): will be filled with unpinned deps
1797 """
1798 logging.debug('_AddDep(%r)', dep)
1799
1800 assert dep.name not in deps
1801 deps[dep.name] = dep
1802
1803 # Detect unpinned deps.
1804 _, revision = gclient_utils.SplitUrlRevision(dep.url)
1805 if not revision or not gclient_utils.IsGitSha(revision):
1806 unpinned_deps[dep.name] = dep
1807
1808
Paweł Hajdan, Jr3c2aa832017-06-07 20:22:16 +02001809def _GNSettingsToLines(gn_args_file, gn_args):
1810 s = []
1811 if gn_args_file:
1812 s.extend([
1813 'gclient_gn_args_file = "%s"' % gn_args_file,
1814 'gclient_gn_args = %r' % gn_args,
1815 ])
1816 return s
1817
1818
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001819def _DepsToLines(deps):
1820 """Converts |deps| dict to list of lines for output."""
1821 s = ['deps = {']
1822 for name, dep in sorted(deps.iteritems()):
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +02001823 condition_part = ([' "condition": "%s",' % dep.condition]
1824 if dep.condition else [])
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001825 s.extend([
1826 ' # %s' % dep.hierarchy(include_url=False),
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +02001827 ' "%s": {' % (name,),
1828 ' "url": "%s",' % (dep.url,),
1829 ] + condition_part + [
1830 ' },',
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001831 '',
1832 ])
1833 s.extend(['}', ''])
1834 return s
1835
1836
1837def _HooksToLines(name, hooks):
1838 """Converts |hooks| list to list of lines for output."""
1839 s = ['%s = [' % name]
1840 for dep, hook in hooks:
1841 s.extend([
1842 ' # %s' % dep.hierarchy(include_url=False),
1843 ' {',
1844 ])
1845 if 'name' in hook:
1846 s.append(' "name": "%s",' % hook['name'])
1847 if 'pattern' in hook:
1848 s.append(' "pattern": "%s",' % hook['pattern'])
1849 # TODO(phajdan.jr): actions may contain paths that need to be adjusted,
1850 # i.e. they may be relative to the dependency path, not solution root.
1851 s.extend(
1852 [' "action": ['] +
1853 [' "%s",' % arg for arg in hook['action']] +
1854 [' ]', ' },', '']
1855 )
1856 s.extend([']', ''])
1857 return s
1858
1859
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001860def CMDgrep(parser, args):
1861 """Greps through git repos managed by gclient.
1862
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001863 Runs 'git grep [args...]' for each module.
1864 """
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001865 # We can't use optparse because it will try to parse arguments sent
1866 # to git grep and throw an error. :-(
1867 if not args or re.match('(-h|--help)$', args[0]):
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001868 print(
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001869 'Usage: gclient grep [-j <N>] git-grep-args...\n\n'
1870 'Example: "gclient grep -j10 -A2 RefCountedBase" runs\n"git grep '
1871 '-A2 RefCountedBase" on each of gclient\'s git\nrepos with up to '
1872 '10 jobs.\n\nBonus: page output by appending "|& less -FRSX" to the'
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001873 ' end of your query.',
1874 file=sys.stderr)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001875 return 1
1876
1877 jobs_arg = ['--jobs=1']
1878 if re.match(r'(-j|--jobs=)\d+$', args[0]):
1879 jobs_arg, args = args[:1], args[1:]
1880 elif re.match(r'(-j|--jobs)$', args[0]):
1881 jobs_arg, args = args[:2], args[2:]
1882
1883 return CMDrecurse(
1884 parser,
1885 jobs_arg + ['--ignore', '--prepend-dir', '--no-progress', '--scm=git',
1886 'git', 'grep', '--null', '--color=Always'] + args)
davidbarr@chromium.org12f944e2012-03-01 02:18:31 +00001887
1888
stip@chromium.orga735da22015-04-29 23:18:20 +00001889def CMDroot(parser, args):
1890 """Outputs the solution root (or current dir if there isn't one)."""
1891 (options, args) = parser.parse_args(args)
1892 client = GClient.LoadCurrentConfig(options)
1893 if client:
1894 print(os.path.abspath(client.root_dir))
1895 else:
1896 print(os.path.abspath('.'))
1897
1898
agablea98a6cd2016-11-15 14:30:10 -08001899@subcommand.usage('[url]')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001900def CMDconfig(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001901 """Creates a .gclient file in the current directory.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001902
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001903 This specifies the configuration for further commands. After update/sync,
1904 top-level DEPS files in each module are read to determine dependent
1905 modules to operate on as well. If optional [url] parameter is
1906 provided, then configuration is read from a specified Subversion server
1907 URL.
1908 """
szager@chromium.orge2e03202012-07-31 18:05:16 +00001909 # We do a little dance with the --gclientfile option. 'gclient config' is the
1910 # only command where it's acceptable to have both '--gclientfile' and '--spec'
1911 # arguments. So, we temporarily stash any --gclientfile parameter into
1912 # options.output_config_file until after the (gclientfile xor spec) error
1913 # check.
1914 parser.remove_option('--gclientfile')
1915 parser.add_option('--gclientfile', dest='output_config_file',
1916 help='Specify an alternate .gclient file')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001917 parser.add_option('--name',
1918 help='overrides the default name for the solution')
nsylvain@google.comefc80932011-05-31 21:27:56 +00001919 parser.add_option('--deps-file', default='DEPS',
1920 help='overrides the default name for the DEPS file for the'
1921 'main solutions and all sub-dependencies')
smutae7ea312016-07-18 11:59:41 -07001922 parser.add_option('--unmanaged', action='store_true', default=False,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001923 help='overrides the default behavior to make it possible '
smutae7ea312016-07-18 11:59:41 -07001924 'to have the main solution untouched by gclient '
1925 '(gclient will check out unmanaged dependencies but '
1926 'will never sync them)')
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001927 parser.add_option('--cache-dir',
1928 help='(git only) Cache all git repos into this dir and do '
1929 'shared clones from the cache, instead of cloning '
1930 'directly from the remote. (experimental)')
szager@chromium.orge2e03202012-07-31 18:05:16 +00001931 parser.set_defaults(config_filename=None)
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001932 (options, args) = parser.parse_args(args)
szager@chromium.orge2e03202012-07-31 18:05:16 +00001933 if options.output_config_file:
1934 setattr(options, 'config_filename', getattr(options, 'output_config_file'))
maruel@chromium.org5fc2a332010-05-26 19:37:15 +00001935 if ((options.spec and args) or len(args) > 2 or
1936 (not options.spec and not args)):
1937 parser.error('Inconsistent arguments. Use either --spec or one or 2 args')
1938
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001939 client = GClient('.', options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001940 if options.spec:
1941 client.SetConfig(options.spec)
1942 else:
maruel@chromium.org1ab7ffc2009-06-03 17:21:37 +00001943 base_url = args[0].rstrip('/')
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001944 if not options.name:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001945 name = base_url.split('/')[-1]
nsylvain@google.com12649ef2011-06-01 17:11:20 +00001946 if name.endswith('.git'):
1947 name = name[:-4]
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001948 else:
1949 # specify an alternate relpath for the given URL.
1950 name = options.name
agable@chromium.orgf2214672015-10-27 21:02:48 +00001951 if not os.path.abspath(os.path.join(os.getcwd(), name)).startswith(
1952 os.getcwd()):
1953 parser.error('Do not pass a relative path for --name.')
1954 if any(x in ('..', '.', '/', '\\') for x in name.split(os.sep)):
1955 parser.error('Do not include relative path components in --name.')
1956
nsylvain@google.comefc80932011-05-31 21:27:56 +00001957 deps_file = options.deps_file
agablea98a6cd2016-11-15 14:30:10 -08001958 client.SetDefaultConfig(name, deps_file, base_url,
smutae7ea312016-07-18 11:59:41 -07001959 managed=not options.unmanaged,
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001960 cache_dir=options.cache_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001961 client.SaveConfig()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001962 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001963
1964
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001965@subcommand.epilog("""Example:
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001966 gclient pack > patch.txt
1967 generate simple patch for configured client and dependences
1968""")
1969def CMDpack(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001970 """Generates a patch which can be applied at the root of the tree.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001971
agabled437d762016-10-17 09:35:11 -07001972 Internally, runs 'git diff' on each checked out module and
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001973 dependencies, and performs minimal postprocessing of the output. The
1974 resulting patch is printed to stdout and can be applied to a freshly
1975 checked out tree via 'patch -p0 < patchfile'.
1976 """
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001977 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1978 help='override deps for the specified (comma-separated) '
1979 'platform(s); \'all\' will process all deps_os '
1980 'references')
haitao.feng@intel.com306080c2012-05-04 13:11:29 +00001981 parser.remove_option('--jobs')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001982 (options, args) = parser.parse_args(args)
iannucci@chromium.org50395ea2013-04-04 04:47:42 +00001983 # Force jobs to 1 so the stdout is not annotated with the thread ids
haitao.feng@intel.com306080c2012-05-04 13:11:29 +00001984 options.jobs = 1
kbr@google.comab318592009-09-04 00:54:55 +00001985 client = GClient.LoadCurrentConfig(options)
1986 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001987 raise gclient_utils.Error('client not configured; see \'gclient config\'')
kbr@google.comab318592009-09-04 00:54:55 +00001988 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00001989 client.PrintLocationAndContents()
kbr@google.comab318592009-09-04 00:54:55 +00001990 return client.RunOnDeps('pack', args)
1991
1992
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001993def CMDstatus(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001994 """Shows modification status for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001995 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1996 help='override deps for the specified (comma-separated) '
1997 'platform(s); \'all\' will process all deps_os '
1998 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001999 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002000 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002001 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002002 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002003 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002004 client.PrintLocationAndContents()
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002005 return client.RunOnDeps('status', args)
2006
2007
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002008@subcommand.epilog("""Examples:
maruel@chromium.org79692d62010-05-14 18:57:13 +00002009 gclient sync
2010 update files from SCM according to current configuration,
2011 *for modules which have changed since last update or sync*
2012 gclient sync --force
2013 update files from SCM according to current configuration, for
2014 all modules (useful for recovering files deleted from local copy)
2015 gclient sync --revision src@31000
2016 update src directory to r31000
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002017
2018JSON output format:
2019If the --output-json option is specified, the following document structure will
2020be emitted to the provided file. 'null' entries may occur for subprojects which
2021are present in the gclient solution, but were not processed (due to custom_deps,
2022os_deps, etc.)
2023
2024{
2025 "solutions" : {
2026 "<name>": { # <name> is the posix-normalized path to the solution.
agabled437d762016-10-17 09:35:11 -07002027 "revision": [<git id hex string>|null],
2028 "scm": ["git"|null],
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002029 }
2030 }
2031}
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002032""")
2033def CMDsync(parser, args):
2034 """Checkout/update all modules."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002035 parser.add_option('-f', '--force', action='store_true',
2036 help='force update even for unchanged modules')
2037 parser.add_option('-n', '--nohooks', action='store_true',
2038 help='don\'t run hooks after the update is complete')
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00002039 parser.add_option('-p', '--noprehooks', action='store_true',
2040 help='don\'t run pre-DEPS hooks', default=False)
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002041 parser.add_option('-r', '--revision', action='append',
2042 dest='revisions', metavar='REV', default=[],
2043 help='Enforces revision/hash for the solutions with the '
2044 'format src@rev. The src@ part is optional and can be '
2045 'skipped. -r can be used multiple times when .gclient '
2046 'has multiple solutions configured and will work even '
agablea98a6cd2016-11-15 14:30:10 -08002047 'if the src@ part is skipped.')
maruel@chromium.org794207e2013-03-08 15:29:43 +00002048 parser.add_option('--with_branch_heads', action='store_true',
2049 help='Clone git "branch_heads" refspecs in addition to '
2050 'the default refspecs. This adds about 1/2GB to a '
2051 'full checkout. (git only)')
szager@chromium.org8d3348f2014-08-19 22:49:16 +00002052 parser.add_option('--with_tags', action='store_true',
2053 help='Clone git tags in addition to the default refspecs.')
agable2697cd12016-06-28 10:23:53 -07002054 parser.add_option('-H', '--head', action='store_true',
agablea98a6cd2016-11-15 14:30:10 -08002055 help='DEPRECATED: only made sense with safesync urls.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002056 parser.add_option('-D', '--delete_unversioned_trees', action='store_true',
steveblock@chromium.org98e69452012-02-16 16:36:43 +00002057 help='Deletes from the working copy any dependencies that '
2058 'have been removed since the last sync, as long as '
2059 'there are no local modifications. When used with '
2060 '--force, such dependencies are removed even if they '
2061 'have local modifications. When used with --reset, '
2062 'all untracked directories are removed from the '
tnagel@chromium.orga2aaa632014-02-28 21:47:27 +00002063 'working copy, excluding those which are explicitly '
steveblock@chromium.org98e69452012-02-16 16:36:43 +00002064 'ignored in the repository.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002065 parser.add_option('-R', '--reset', action='store_true',
2066 help='resets any local changes before updating (git only)')
bauerb@chromium.org2aad1b22011-07-22 12:00:41 +00002067 parser.add_option('-M', '--merge', action='store_true',
2068 help='merge upstream changes instead of trying to '
2069 'fast-forward or rebase')
dnj@chromium.org5b23e872015-02-20 21:25:57 +00002070 parser.add_option('-A', '--auto_rebase', action='store_true',
2071 help='Automatically rebase repositories against local '
2072 'checkout during update (git only).')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002073 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2074 help='override deps for the specified (comma-separated) '
2075 'platform(s); \'all\' will process all deps_os '
2076 'references')
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +00002077 parser.add_option('--upstream', action='store_true',
2078 help='Make repo state match upstream branch.')
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002079 parser.add_option('--output-json',
2080 help='Output a json document to this path containing '
2081 'summary information about the sync.')
primiano@chromium.org5439ea52014-08-06 17:18:18 +00002082 parser.add_option('--no-history', action='store_true',
2083 help='GIT ONLY - Reduces the size/time of the checkout at '
2084 'the cost of no history. Requires Git 1.9+')
hinoka@chromium.org46b87412014-05-15 00:42:05 +00002085 parser.add_option('--shallow', action='store_true',
2086 help='GIT ONLY - Do a shallow clone into the cache dir. '
2087 'Requires Git 1.9+')
e.hakkinen@samsung.come8bc1aa2015-04-08 08:00:37 +00002088 parser.add_option('--no_bootstrap', '--no-bootstrap',
2089 action='store_true',
2090 help='Don\'t bootstrap from Google Storage.')
hinoka@chromium.org8a10f6d2014-06-23 18:38:57 +00002091 parser.add_option('--ignore_locks', action='store_true',
2092 help='GIT ONLY - Ignore cache locks.')
iannucci@chromium.org30a07982016-04-07 21:35:19 +00002093 parser.add_option('--break_repo_locks', action='store_true',
2094 help='GIT ONLY - Forcibly remove repo locks (e.g. '
2095 'index.lock). This should only be used if you know for '
2096 'certain that this invocation of gclient is the only '
2097 'thing operating on the git repos (e.g. on a bot).')
nodir@chromium.org5b48e482016-03-18 20:27:54 +00002098 parser.add_option('--lock_timeout', type='int', default=5000,
szager@chromium.orgdbb6f822016-02-02 22:59:30 +00002099 help='GIT ONLY - Deadline (in seconds) to wait for git '
nodir@chromium.org5b48e482016-03-18 20:27:54 +00002100 'cache lock to become available. Default is %default.')
agabled437d762016-10-17 09:35:11 -07002101 # TODO(agable): Remove these when the oldest CrOS release milestone is M56.
2102 parser.add_option('-t', '--transitive', action='store_true',
2103 help='DEPRECATED: This is a no-op.')
sdefresne69b1be12016-10-18 05:48:02 -07002104 parser.add_option('-m', '--manually_grab_svn_rev', action='store_true',
agabled437d762016-10-17 09:35:11 -07002105 help='DEPRECATED: This is a no-op.')
Paweł Hajdan, Jr7c7b5592017-05-23 15:06:05 +02002106 # TODO(phajdan.jr): Remove validation options once default (crbug/570091).
Paweł Hajdan, Jr694773d2017-05-29 16:06:23 +02002107 parser.add_option('--validate-syntax', action='store_true', default=True,
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +02002108 help='Validate the .gclient and DEPS syntax')
Paweł Hajdan, Jr7c7b5592017-05-23 15:06:05 +02002109 parser.add_option('--disable-syntax-validation', action='store_false',
2110 dest='validate_syntax',
2111 help='Disable validation of .gclient and DEPS syntax.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002112 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002113 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002114
2115 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002116 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002117
smutae7ea312016-07-18 11:59:41 -07002118 if options.revisions and options.head:
2119 # TODO(maruel): Make it a parser.error if it doesn't break any builder.
2120 print('Warning: you cannot use both --head and --revision')
2121
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002122 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002123 client.PrintLocationAndContents()
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002124 ret = client.RunOnDeps('update', args)
2125 if options.output_json:
2126 slns = {}
2127 for d in client.subtree(True):
2128 normed = d.name.replace('\\', '/').rstrip('/') + '/'
2129 slns[normed] = {
2130 'revision': d.got_revision,
2131 'scm': d.used_scm.name if d.used_scm else None,
hinoka@chromium.org17db9052014-05-10 01:11:29 +00002132 'url': str(d.url) if d.url else None,
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002133 }
2134 with open(options.output_json, 'wb') as f:
2135 json.dump({'solutions': slns}, f)
2136 return ret
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002137
2138
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002139CMDupdate = CMDsync
2140
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002141
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +02002142def CMDvalidate(parser, args):
2143 """Validates the .gclient and DEPS syntax."""
2144 options, args = parser.parse_args(args)
2145 options.validate_syntax = True
2146 client = GClient.LoadCurrentConfig(options)
2147 rv = client.RunOnDeps('validate', args)
2148 if rv == 0:
2149 print('validate: SUCCESS')
2150 else:
2151 print('validate: FAILURE')
2152 return rv
2153
2154
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002155def CMDdiff(parser, args):
2156 """Displays local diff for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002157 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2158 help='override deps for the specified (comma-separated) '
2159 'platform(s); \'all\' will process all deps_os '
2160 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002161 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002162 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002163 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002164 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002165 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002166 client.PrintLocationAndContents()
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002167 return client.RunOnDeps('diff', args)
2168
2169
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002170def CMDrevert(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002171 """Reverts all modifications in every dependencies.
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00002172
2173 That's the nuclear option to get back to a 'clean' state. It removes anything
agabled437d762016-10-17 09:35:11 -07002174 that shows up in git status."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002175 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2176 help='override deps for the specified (comma-separated) '
2177 'platform(s); \'all\' will process all deps_os '
2178 'references')
2179 parser.add_option('-n', '--nohooks', action='store_true',
2180 help='don\'t run hooks after the revert is complete')
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00002181 parser.add_option('-p', '--noprehooks', action='store_true',
2182 help='don\'t run pre-DEPS hooks', default=False)
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +00002183 parser.add_option('--upstream', action='store_true',
2184 help='Make repo state match upstream branch.')
iannucci@chromium.orgbf525dc2016-04-07 22:00:28 +00002185 parser.add_option('--break_repo_locks', action='store_true',
2186 help='GIT ONLY - Forcibly remove repo locks (e.g. '
2187 'index.lock). This should only be used if you know for '
2188 'certain that this invocation of gclient is the only '
2189 'thing operating on the git repos (e.g. on a bot).')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002190 (options, args) = parser.parse_args(args)
2191 # --force is implied.
2192 options.force = True
steveblock@chromium.org98e69452012-02-16 16:36:43 +00002193 options.reset = False
2194 options.delete_unversioned_trees = False
agablec903d732016-07-26 09:07:24 -07002195 options.merge = False
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002196 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002197 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002198 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002199 return client.RunOnDeps('revert', args)
2200
2201
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002202def CMDrunhooks(parser, args):
2203 """Runs hooks for files that have been modified in the local working copy."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002204 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2205 help='override deps for the specified (comma-separated) '
2206 'platform(s); \'all\' will process all deps_os '
2207 'references')
2208 parser.add_option('-f', '--force', action='store_true', default=True,
2209 help='Deprecated. No effect.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002210 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002211 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002212 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002213 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002214 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002215 client.PrintLocationAndContents()
maruel@chromium.org5df6a462009-08-28 18:52:26 +00002216 options.force = True
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002217 options.nohooks = False
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002218 return client.RunOnDeps('runhooks', args)
2219
2220
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002221def CMDrevinfo(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002222 """Outputs revision info mapping for the client and its dependencies.
maruel@chromium.org9eda4112010-06-11 18:56:10 +00002223
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002224 This allows the capture of an overall 'revision' for the source tree that
maruel@chromium.org9eda4112010-06-11 18:56:10 +00002225 can be used to reproduce the same tree in the future. It is only useful for
agabled437d762016-10-17 09:35:11 -07002226 'unpinned dependencies', i.e. DEPS/deps references without a git hash.
2227 A git branch name isn't 'pinned' since the actual commit can change.
maruel@chromium.org9eda4112010-06-11 18:56:10 +00002228 """
2229 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2230 help='override deps for the specified (comma-separated) '
2231 'platform(s); \'all\' will process all deps_os '
2232 'references')
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00002233 parser.add_option('-a', '--actual', action='store_true',
2234 help='gets the actual checked out revisions instead of the '
2235 'ones specified in the DEPS and .gclient files')
maruel@chromium.org9eda4112010-06-11 18:56:10 +00002236 parser.add_option('-s', '--snapshot', action='store_true',
2237 help='creates a snapshot .gclient file of the current '
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00002238 'version of all repositories to reproduce the tree, '
2239 'implies -a')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002240 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002241 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002242 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002243 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002244 client.PrintRevInfo()
maruel@chromium.org79692d62010-05-14 18:57:13 +00002245 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002246
2247
szager@google.comb9a78d32012-03-13 18:46:21 +00002248def CMDhookinfo(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002249 """Outputs the hooks that would be run by `gclient runhooks`."""
szager@google.comb9a78d32012-03-13 18:46:21 +00002250 (options, args) = parser.parse_args(args)
2251 options.force = True
2252 client = GClient.LoadCurrentConfig(options)
2253 if not client:
2254 raise gclient_utils.Error('client not configured; see \'gclient config\'')
2255 client.RunOnDeps(None, [])
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002256 print('; '.join(' '.join(hook) for hook in client.GetHooks(options)))
szager@google.comb9a78d32012-03-13 18:46:21 +00002257 return 0
2258
2259
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00002260def CMDverify(parser, args):
2261 """Verifies the DEPS file deps are only from allowed_hosts."""
2262 (options, args) = parser.parse_args(args)
2263 client = GClient.LoadCurrentConfig(options)
2264 if not client:
2265 raise gclient_utils.Error('client not configured; see \'gclient config\'')
2266 client.RunOnDeps(None, [])
2267 # Look at each first-level dependency of this gclient only.
2268 for dep in client.dependencies:
2269 bad_deps = dep.findDepsFromNotAllowedHosts()
2270 if not bad_deps:
2271 continue
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002272 print("There are deps from not allowed hosts in file %s" % dep.deps_file)
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00002273 for bad_dep in bad_deps:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002274 print("\t%s at %s" % (bad_dep.name, bad_dep.url))
2275 print("allowed_hosts:", ', '.join(dep.allowed_hosts))
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00002276 sys.stdout.flush()
2277 raise gclient_utils.Error(
2278 'dependencies from disallowed hosts; check your DEPS file.')
2279 return 0
2280
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002281class OptionParser(optparse.OptionParser):
szager@chromium.orge2e03202012-07-31 18:05:16 +00002282 gclientfile_default = os.environ.get('GCLIENT_FILE', '.gclient')
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002283
2284 def __init__(self, **kwargs):
2285 optparse.OptionParser.__init__(
2286 self, version='%prog ' + __version__, **kwargs)
2287
2288 # Some arm boards have issues with parallel sync.
2289 if platform.machine().startswith('arm'):
2290 jobs = 1
2291 else:
2292 jobs = max(8, gclient_utils.NumLocalCpus())
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002293
2294 self.add_option(
2295 '-j', '--jobs', default=jobs, type='int',
2296 help='Specify how many SCM commands can run in parallel; defaults to '
tnagel@chromium.orga2aaa632014-02-28 21:47:27 +00002297 '%default on this machine')
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002298 self.add_option(
2299 '-v', '--verbose', action='count', default=0,
2300 help='Produces additional output for diagnostics. Can be used up to '
2301 'three times for more logging info.')
2302 self.add_option(
2303 '--gclientfile', dest='config_filename',
2304 help='Specify an alternate %s file' % self.gclientfile_default)
2305 self.add_option(
2306 '--spec',
2307 help='create a gclient file containing the provided string. Due to '
2308 'Cygwin/Python brokenness, it can\'t contain any newlines.')
2309 self.add_option(
2310 '--no-nag-max', default=False, action='store_true',
scottmg@chromium.orgf547c802013-09-27 17:55:26 +00002311 help='Ignored for backwards compatibility.')
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002312
2313 def parse_args(self, args=None, values=None):
2314 """Integrates standard options processing."""
2315 options, args = optparse.OptionParser.parse_args(self, args, values)
2316 levels = [logging.ERROR, logging.WARNING, logging.INFO, logging.DEBUG]
2317 logging.basicConfig(
2318 level=levels[min(options.verbose, len(levels) - 1)],
maruel@chromium.org0895b752011-08-26 20:40:33 +00002319 format='%(module)s(%(lineno)d) %(funcName)s:%(message)s')
szager@chromium.orge2e03202012-07-31 18:05:16 +00002320 if options.config_filename and options.spec:
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002321 self.error('Cannot specifiy both --gclientfile and --spec')
rdsmith@chromium.orgd9591f02014-02-05 19:28:20 +00002322 if (options.config_filename and
2323 options.config_filename != os.path.basename(options.config_filename)):
2324 self.error('--gclientfile target must be a filename, not a path')
szager@chromium.orge2e03202012-07-31 18:05:16 +00002325 if not options.config_filename:
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002326 options.config_filename = self.gclientfile_default
maruel@chromium.org0895b752011-08-26 20:40:33 +00002327 options.entries_filename = options.config_filename + '_entries'
2328 if options.jobs < 1:
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002329 self.error('--jobs must be 1 or higher')
maruel@chromium.org0895b752011-08-26 20:40:33 +00002330
2331 # These hacks need to die.
2332 if not hasattr(options, 'revisions'):
2333 # GClient.RunOnDeps expects it even if not applicable.
2334 options.revisions = []
smutae7ea312016-07-18 11:59:41 -07002335 if not hasattr(options, 'head'):
2336 options.head = None
maruel@chromium.org0895b752011-08-26 20:40:33 +00002337 if not hasattr(options, 'nohooks'):
2338 options.nohooks = True
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00002339 if not hasattr(options, 'noprehooks'):
2340 options.noprehooks = True
maruel@chromium.org0895b752011-08-26 20:40:33 +00002341 if not hasattr(options, 'deps_os'):
2342 options.deps_os = None
maruel@chromium.org0895b752011-08-26 20:40:33 +00002343 if not hasattr(options, 'force'):
2344 options.force = None
2345 return (options, args)
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002346
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002347
2348def disable_buffering():
2349 # Make stdout auto-flush so buildbot doesn't kill us during lengthy
2350 # operations. Python as a strong tendency to buffer sys.stdout.
2351 sys.stdout = gclient_utils.MakeFileAutoFlush(sys.stdout)
2352 # Make stdout annotated with the thread ids.
2353 sys.stdout = gclient_utils.MakeFileAnnotated(sys.stdout)
maruel@chromium.org0895b752011-08-26 20:40:33 +00002354
2355
sbc@chromium.org013731e2015-02-26 18:28:43 +00002356def main(argv):
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002357 """Doesn't parse the arguments here, just find the right subcommand to
2358 execute."""
maruel@chromium.org82798cb2012-02-23 18:16:12 +00002359 if sys.hexversion < 0x02060000:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002360 print(
maruel@chromium.org82798cb2012-02-23 18:16:12 +00002361 '\nYour python version %s is unsupported, please upgrade.\n' %
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002362 sys.version.split(' ', 1)[0],
2363 file=sys.stderr)
maruel@chromium.org82798cb2012-02-23 18:16:12 +00002364 return 2
bcwhite@chromium.org6683ab42013-02-11 16:13:47 +00002365 if not sys.executable:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002366 print(
2367 '\nPython cannot find the location of it\'s own executable.\n',
2368 file=sys.stderr)
bcwhite@chromium.org6683ab42013-02-11 16:13:47 +00002369 return 2
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002370 fix_encoding.fix_encoding()
2371 disable_buffering()
iannucci@chromium.org596cd5c2016-04-04 21:34:39 +00002372 setup_color.init()
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002373 dispatcher = subcommand.CommandDispatcher(__name__)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00002374 try:
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002375 return dispatcher.execute(OptionParser(), argv)
xusydoc@chromium.org2fd6c3f2013-05-03 21:57:55 +00002376 except KeyboardInterrupt:
2377 gclient_utils.GClientChildren.KillAllRemainingChildren()
2378 raise
vapier@chromium.orga81a56e2015-11-11 07:56:13 +00002379 except (gclient_utils.Error, subprocess2.CalledProcessError) as e:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002380 print('Error: %s' % str(e), file=sys.stderr)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00002381 return 1
borenet@google.com6a9b1682014-03-24 18:35:23 +00002382 finally:
2383 gclient_utils.PrintWarnings()
sbc@chromium.org013731e2015-02-26 18:28:43 +00002384 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002385
2386
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00002387if '__main__' == __name__:
sbc@chromium.org013731e2015-02-26 18:28:43 +00002388 try:
2389 sys.exit(main(sys.argv[1:]))
2390 except KeyboardInterrupt:
2391 sys.stderr.write('interrupted\n')
2392 sys.exit(1)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002393
2394# vim: ts=2:sw=2:tw=80:et: