blob: 4badcfbc0d6bb5401a2efac899dab149b2760794 [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
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +020084import collections
maruel@chromium.org9e5317a2010-08-13 20:35:11 +000085import copy
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +000086import json
maruel@chromium.org754960e2009-09-21 12:31:05 +000087import logging
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000088import optparse
89import os
bradnelson@google.com4949dab2012-04-19 16:41:07 +000090import platform
maruel@chromium.org621939b2010-08-10 20:12:00 +000091import posixpath
msb@chromium.org2e38de72009-09-28 17:04:47 +000092import pprint
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000093import re
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000094import sys
ilevy@chromium.orgc28d3772013-07-12 19:42:37 +000095import time
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000096import urllib
bradnelson@google.com4949dab2012-04-19 16:41:07 +000097import urlparse
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000098
maruel@chromium.org35625c72011-03-23 17:34:02 +000099import fix_encoding
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +0200100import gclient_eval
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000101import gclient_scm
102import gclient_utils
szager@chromium.org848fd492014-04-09 19:06:44 +0000103import git_cache
nasser@codeaurora.org1f7a3d12010-02-04 15:11:50 +0000104from third_party.repo.progress import Progress
maruel@chromium.org39c0b222013-08-17 16:57:01 +0000105import subcommand
maruel@chromium.org31cb48a2011-04-04 18:01:36 +0000106import subprocess2
iannucci@chromium.org596cd5c2016-04-04 21:34:39 +0000107import setup_color
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000108
szager@chromium.org7b8b6de2014-08-23 00:57:31 +0000109CHROMIUM_SRC_URL = 'https://chromium.googlesource.com/chromium/src.git'
110
111
112def ast_dict_index(dnode, key):
113 """Search an ast.Dict for the argument key, and return its index."""
114 idx = [i for i in range(len(dnode.keys)) if (
115 type(dnode.keys[i]) is ast.Str and dnode.keys[i].s == key)]
116 if not idx:
117 return -1
118 elif len(idx) > 1:
119 raise gclient_utils.Error('Multiple dict entries with same key in AST')
120 return idx[-1]
121
122def ast2str(node, indent=0):
123 """Return a pretty-printed rendition of an ast.Node."""
124 t = type(node)
125 if t is ast.Module:
126 return '\n'.join([ast2str(x, indent) for x in node.body])
127 elif t is ast.Assign:
128 return ((' ' * indent) +
129 ' = '.join([ast2str(x) for x in node.targets] +
130 [ast2str(node.value, indent)]) + '\n')
131 elif t is ast.Name:
132 return node.id
133 elif t is ast.List:
134 if not node.elts:
135 return '[]'
136 elif len(node.elts) == 1:
137 return '[' + ast2str(node.elts[0], indent) + ']'
138 return ('[\n' + (' ' * (indent + 1)) +
139 (',\n' + (' ' * (indent + 1))).join(
140 [ast2str(x, indent + 1) for x in node.elts]) +
141 '\n' + (' ' * indent) + ']')
142 elif t is ast.Dict:
143 if not node.keys:
144 return '{}'
145 elif len(node.keys) == 1:
146 return '{%s: %s}' % (ast2str(node.keys[0]),
147 ast2str(node.values[0], indent + 1))
148 return ('{\n' + (' ' * (indent + 1)) +
149 (',\n' + (' ' * (indent + 1))).join(
150 ['%s: %s' % (ast2str(node.keys[i]),
151 ast2str(node.values[i], indent + 1))
152 for i in range(len(node.keys))]) +
153 '\n' + (' ' * indent) + '}')
154 elif t is ast.Str:
155 return "'%s'" % node.s
156 else:
157 raise gclient_utils.Error("Unexpected AST node at line %d, column %d: %s"
158 % (node.lineno, node.col_offset, t))
159
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000160
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +0200161class GNException(Exception):
162 pass
163
164
165def ToGNString(value, allow_dicts = True):
166 """Returns a stringified GN equivalent of the Python value.
167
168 allow_dicts indicates if this function will allow converting dictionaries
169 to GN scopes. This is only possible at the top level, you can't nest a
170 GN scope in a list, so this should be set to False for recursive calls."""
171 if isinstance(value, basestring):
172 if value.find('\n') >= 0:
173 raise GNException("Trying to print a string with a newline in it.")
174 return '"' + \
175 value.replace('\\', '\\\\').replace('"', '\\"').replace('$', '\\$') + \
176 '"'
177
178 if isinstance(value, unicode):
179 return ToGNString(value.encode('utf-8'))
180
181 if isinstance(value, bool):
182 if value:
183 return "true"
184 return "false"
185
186 # NOTE: some type handling removed compared to chromium/src copy.
187
188 raise GNException("Unsupported type when printing to GN.")
189
190
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200191class Hook(object):
192 """Descriptor of command ran before/after sync or on demand."""
193
Paweł Hajdan, Jrc9364392017-06-14 17:11:56 +0200194 def __init__(self, action, pattern=None, name=None, cwd=None):
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200195 """Constructor.
196
197 Arguments:
198 action (list of basestring): argv of the command to run
199 pattern (basestring regex): noop with git; deprecated
200 name (basestring): optional name; no effect on operation
Paweł Hajdan, Jrc9364392017-06-14 17:11:56 +0200201 cwd (basestring): working directory to use
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200202 """
203 self._action = gclient_utils.freeze(action)
204 self._pattern = pattern
205 self._name = name
Paweł Hajdan, Jrc9364392017-06-14 17:11:56 +0200206 self._cwd = cwd
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200207
208 @staticmethod
209 def from_dict(d):
210 """Creates a Hook instance from a dict like in the DEPS file."""
Paweł Hajdan, Jrc9364392017-06-14 17:11:56 +0200211 return Hook(d['action'], d.get('pattern'), d.get('name'), d.get('cwd'))
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200212
213 @property
214 def action(self):
215 return self._action
216
217 @property
218 def pattern(self):
219 return self._pattern
220
221 @property
222 def name(self):
223 return self._name
224
225 def matches(self, file_list):
226 """Returns true if the pattern matches any of files in the list."""
227 if not self._pattern:
228 return True
229 pattern = re.compile(self._pattern)
230 return bool([f for f in file_list if pattern.search(f)])
231
232 def run(self, root):
233 """Executes the hook's command."""
234 cmd = list(self._action)
235 if cmd[0] == 'python':
236 # If the hook specified "python" as the first item, the action is a
237 # Python script. Run it by starting a new copy of the same
238 # interpreter.
239 cmd[0] = sys.executable
Paweł Hajdan, Jrc9364392017-06-14 17:11:56 +0200240
241 cwd = root
242 if self._cwd:
243 cwd = os.path.join(cwd, self._cwd)
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200244 try:
245 start_time = time.time()
246 gclient_utils.CheckCallAndFilterAndHeader(
Paweł Hajdan, Jrc9364392017-06-14 17:11:56 +0200247 cmd, cwd=cwd, always=True)
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200248 except (gclient_utils.Error, subprocess2.CalledProcessError) as e:
249 # Use a discrete exit status code of 2 to indicate that a hook action
250 # failed. Users of this script may wish to treat hook action failures
251 # differently from VC failures.
252 print('Error: %s' % str(e), file=sys.stderr)
253 sys.exit(2)
254 finally:
255 elapsed_time = time.time() - start_time
256 if elapsed_time > 10:
257 print("Hook '%s' took %.2f secs" % (
258 gclient_utils.CommandToStr(cmd), elapsed_time))
259
260
maruel@chromium.org116704f2010-06-11 17:34:38 +0000261class GClientKeywords(object):
maruel@chromium.org116704f2010-06-11 17:34:38 +0000262 class VarImpl(object):
263 def __init__(self, custom_vars, local_scope):
264 self._custom_vars = custom_vars
265 self._local_scope = local_scope
266
267 def Lookup(self, var_name):
268 """Implements the Var syntax."""
269 if var_name in self._custom_vars:
270 return self._custom_vars[var_name]
271 elif var_name in self._local_scope.get("vars", {}):
272 return self._local_scope["vars"][var_name]
273 raise gclient_utils.Error("Var is not defined: %s" % var_name)
274
275
maruel@chromium.org064186c2011-09-27 23:53:33 +0000276class DependencySettings(GClientKeywords):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000277 """Immutable configuration settings."""
278 def __init__(
agablea98a6cd2016-11-15 14:30:10 -0800279 self, parent, url, managed, custom_deps, custom_vars,
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +0200280 custom_hooks, deps_file, should_process, relative,
281 condition, condition_value):
maruel@chromium.org064186c2011-09-27 23:53:33 +0000282 GClientKeywords.__init__(self)
283
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000284 # These are not mutable:
285 self._parent = parent
mmoss@chromium.org8f93f792014-08-26 23:24:09 +0000286 self._deps_file = deps_file
maruel@chromium.org064186c2011-09-27 23:53:33 +0000287 self._url = url
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +0200288 # The condition as string (or None). Useful to keep e.g. for flatten.
289 self._condition = condition
290 # Boolean value of the condition. If there's no condition, just True.
291 self._condition_value = condition_value
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000292 # 'managed' determines whether or not this dependency is synced/updated by
293 # gclient after gclient checks it out initially. The difference between
294 # 'managed' and 'should_process' is that the user specifies 'managed' via
smutae7ea312016-07-18 11:59:41 -0700295 # the --unmanaged command-line flag or a .gclient config, where
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000296 # 'should_process' is dynamically set by gclient if it goes over its
297 # recursion limit and controls gclient's behavior so it does not misbehave.
298 self._managed = managed
299 self._should_process = should_process
agabledce6ddc2016-09-08 10:02:16 -0700300 # If this is a recursed-upon sub-dependency, and the parent has
301 # use_relative_paths set, then this dependency should check out its own
302 # dependencies relative to that parent's path for this, rather than
303 # relative to the .gclient file.
304 self._relative = relative
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +0000305 # This is a mutable value which has the list of 'target_os' OSes listed in
306 # the current deps file.
307 self.local_target_os = None
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000308
309 # These are only set in .gclient and not in DEPS files.
310 self._custom_vars = custom_vars or {}
311 self._custom_deps = custom_deps or {}
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000312 self._custom_hooks = custom_hooks or []
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000313
maruel@chromium.org064186c2011-09-27 23:53:33 +0000314 # Post process the url to remove trailing slashes.
315 if isinstance(self._url, basestring):
316 # urls are sometime incorrectly written as proto://host/path/@rev. Replace
317 # it to proto://host/path@rev.
maruel@chromium.org064186c2011-09-27 23:53:33 +0000318 self._url = self._url.replace('/@', '@')
Paweł Hajdan, Jr7e9303b2017-05-23 14:38:27 +0200319 elif not isinstance(self._url, (None.__class__)):
maruel@chromium.org064186c2011-09-27 23:53:33 +0000320 raise gclient_utils.Error(
Paweł Hajdan, Jr7e9303b2017-05-23 14:38:27 +0200321 ('dependency url must be either string or None, '
322 'instead of %s') % self._url.__class__.__name__)
mmoss@chromium.orgd0b272b2013-01-30 23:55:33 +0000323 # Make any deps_file path platform-appropriate.
324 for sep in ['/', '\\']:
325 self._deps_file = self._deps_file.replace(sep, os.sep)
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000326
327 @property
328 def deps_file(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000329 return self._deps_file
330
331 @property
332 def managed(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000333 return self._managed
334
335 @property
336 def parent(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000337 return self._parent
338
339 @property
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000340 def root(self):
341 """Returns the root node, a GClient object."""
342 if not self.parent:
343 # This line is to signal pylint that it could be a GClient instance.
344 return self or GClient(None, None)
345 return self.parent.root
346
347 @property
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000348 def should_process(self):
349 """True if this dependency should be processed, i.e. checked out."""
350 return self._should_process
351
352 @property
353 def custom_vars(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000354 return self._custom_vars.copy()
355
356 @property
357 def custom_deps(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000358 return self._custom_deps.copy()
359
maruel@chromium.org064186c2011-09-27 23:53:33 +0000360 @property
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000361 def custom_hooks(self):
362 return self._custom_hooks[:]
363
364 @property
maruel@chromium.org064186c2011-09-27 23:53:33 +0000365 def url(self):
366 return self._url
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000367
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000368 @property
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +0200369 def condition(self):
370 return self._condition
371
372 @property
373 def condition_value(self):
374 return self._condition_value
375
376 @property
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +0000377 def target_os(self):
378 if self.local_target_os is not None:
379 return tuple(set(self.local_target_os).union(self.parent.target_os))
380 else:
381 return self.parent.target_os
382
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000383 def get_custom_deps(self, name, url):
384 """Returns a custom deps if applicable."""
385 if self.parent:
386 url = self.parent.get_custom_deps(name, url)
387 # None is a valid return value to disable a dependency.
388 return self.custom_deps.get(name, url)
389
maruel@chromium.org064186c2011-09-27 23:53:33 +0000390
391class Dependency(gclient_utils.WorkItem, DependencySettings):
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000392 """Object that represents a dependency checkout."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000393
agablea98a6cd2016-11-15 14:30:10 -0800394 def __init__(self, parent, name, url, managed, custom_deps,
agabledce6ddc2016-09-08 10:02:16 -0700395 custom_vars, custom_hooks, deps_file, should_process,
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +0200396 relative, condition, condition_value):
maruel@chromium.org6ca8bf82011-09-19 23:04:30 +0000397 gclient_utils.WorkItem.__init__(self, name)
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000398 DependencySettings.__init__(
agablea98a6cd2016-11-15 14:30:10 -0800399 self, parent, url, managed, custom_deps, custom_vars,
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +0200400 custom_hooks, deps_file, should_process, relative,
401 condition, condition_value)
maruel@chromium.org68988972011-09-20 14:11:42 +0000402
403 # This is in both .gclient and DEPS files:
maruel@chromium.org064186c2011-09-27 23:53:33 +0000404 self._deps_hooks = []
maruel@chromium.org68988972011-09-20 14:11:42 +0000405
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000406 self._pre_deps_hooks = []
407
maruel@chromium.org68988972011-09-20 14:11:42 +0000408 # Calculates properties:
maruel@chromium.org064186c2011-09-27 23:53:33 +0000409 self._parsed_url = None
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +0000410 self._dependencies = []
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200411 # Keep track of original values, before post-processing (e.g. deps_os).
412 self._orig_dependencies = []
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +0200413 self._vars = {}
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +0200414 self._os_dependencies = {}
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200415
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000416 # A cache of the files affected by the current operation, necessary for
417 # hooks.
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000418 self._file_list = []
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000419 # List of host names from which dependencies are allowed.
420 # Default is an empty set, meaning unspecified in DEPS file, and hence all
421 # hosts will be allowed. Non-empty set means whitelist of hosts.
422 # allowed_hosts var is scoped to its DEPS file, and so it isn't recursive.
423 self._allowed_hosts = frozenset()
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +0200424 # Spec for .gni output to write (if any).
425 self._gn_args_file = None
426 self._gn_args = []
maruel@chromium.org85c2a192010-07-22 21:14:43 +0000427 # If it is not set to True, the dependency wasn't processed for its child
428 # dependency, i.e. its DEPS wasn't read.
maruel@chromium.org064186c2011-09-27 23:53:33 +0000429 self._deps_parsed = False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000430 # This dependency has been processed, i.e. checked out
maruel@chromium.org064186c2011-09-27 23:53:33 +0000431 self._processed = False
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000432 # This dependency had its pre-DEPS hooks run
433 self._pre_deps_hooks_ran = False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000434 # This dependency had its hook run
maruel@chromium.org064186c2011-09-27 23:53:33 +0000435 self._hooks_ran = False
kustermann@google.coma692e8f2013-04-18 08:32:04 +0000436 # This is the scm used to checkout self.url. It may be used by dependencies
437 # to get the datetime of the revision we checked out.
438 self._used_scm = None
szager@chromium.org4ad264b2014-05-20 04:43:47 +0000439 self._used_revision = None
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +0000440 # The actual revision we ended up getting, or None if that information is
441 # unavailable
442 self._got_revision = None
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000443
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000444 # This is a mutable value that overrides the normal recursion limit for this
445 # dependency. It is read from the actual DEPS file so cannot be set on
446 # class instantiation.
447 self.recursion_override = None
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000448 # recursedeps is a mutable value that selectively overrides the default
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000449 # 'no recursion' setting on a dep-by-dep basis. It will replace
450 # recursion_override.
iannucci@chromium.orgafa11ac2016-05-04 22:17:34 +0000451 #
452 # It will be a dictionary of {deps_name: {"deps_file": depfile_name}} or
453 # None.
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000454 self.recursedeps = None
hinoka885e5b12016-06-08 14:40:09 -0700455 # This is inherited from WorkItem. We want the URL to be a resource.
456 if url and isinstance(url, basestring):
457 # The url is usually given to gclient either as https://blah@123
qyearsley12fa6ff2016-08-24 09:18:40 -0700458 # or just https://blah. The @123 portion is irrelevant.
hinoka885e5b12016-06-08 14:40:09 -0700459 self.resources.append(url.split('@')[0])
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000460
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000461 if not self.name and self.parent:
462 raise gclient_utils.Error('Dependency without name')
463
maruel@chromium.org470b5432011-10-11 18:18:19 +0000464 @property
465 def requirements(self):
466 """Calculate the list of requirements."""
467 requirements = set()
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000468 # self.parent is implicitly a requirement. This will be recursive by
469 # definition.
470 if self.parent and self.parent.name:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000471 requirements.add(self.parent.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000472
473 # For a tree with at least 2 levels*, the leaf node needs to depend
474 # on the level higher up in an orderly way.
475 # This becomes messy for >2 depth as the DEPS file format is a dictionary,
476 # thus unsorted, while the .gclient format is a list thus sorted.
477 #
478 # * _recursion_limit is hard coded 2 and there is no hope to change this
479 # value.
480 #
481 # Interestingly enough, the following condition only works in the case we
482 # want: self is a 2nd level node. 3nd level node wouldn't need this since
483 # they already have their parent as a requirement.
maruel@chromium.org470b5432011-10-11 18:18:19 +0000484 if self.parent and self.parent.parent and not self.parent.parent.parent:
485 requirements |= set(i.name for i in self.root.dependencies if i.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000486
maruel@chromium.org470b5432011-10-11 18:18:19 +0000487 if self.name:
488 requirements |= set(
489 obj.name for obj in self.root.subtree(False)
490 if (obj is not self
491 and obj.name and
492 self.name.startswith(posixpath.join(obj.name, ''))))
493 requirements = tuple(sorted(requirements))
494 logging.info('Dependency(%s).requirements = %s' % (self.name, requirements))
495 return requirements
496
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000497 @property
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000498 def try_recursedeps(self):
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000499 """Returns False if recursion_override is ever specified."""
500 if self.recursion_override is not None:
501 return False
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000502 return self.parent.try_recursedeps
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000503
504 @property
505 def recursion_limit(self):
506 """Returns > 0 if this dependency is not too recursed to be processed."""
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000507 # We continue to support the absence of recursedeps until tools and DEPS
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000508 # using recursion_override are updated.
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000509 if self.try_recursedeps and self.parent.recursedeps != None:
510 if self.name in self.parent.recursedeps:
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000511 return 1
512
513 if self.recursion_override is not None:
514 return self.recursion_override
515 return max(self.parent.recursion_limit - 1, 0)
516
maruel@chromium.org470b5432011-10-11 18:18:19 +0000517 def verify_validity(self):
518 """Verifies that this Dependency is fine to add as a child of another one.
519
520 Returns True if this entry should be added, False if it is a duplicate of
521 another entry.
522 """
523 logging.info('Dependency(%s).verify_validity()' % self.name)
524 if self.name in [s.name for s in self.parent.dependencies]:
525 raise gclient_utils.Error(
526 'The same name "%s" appears multiple times in the deps section' %
527 self.name)
528 if not self.should_process:
529 # Return early, no need to set requirements.
530 return True
531
532 # This require a full tree traversal with locks.
533 siblings = [d for d in self.root.subtree(False) if d.name == self.name]
534 for sibling in siblings:
maruel@chromium.orgb848d5b2012-10-10 23:25:50 +0000535 self_url = self.LateOverride(self.url)
536 sibling_url = sibling.LateOverride(sibling.url)
537 # Allow to have only one to be None or ''.
538 if self_url != sibling_url and bool(self_url) == bool(sibling_url):
maruel@chromium.org470b5432011-10-11 18:18:19 +0000539 raise gclient_utils.Error(
maruel@chromium.orgb848d5b2012-10-10 23:25:50 +0000540 ('Dependency %s specified more than once:\n'
541 ' %s [%s]\n'
542 'vs\n'
543 ' %s [%s]') % (
544 self.name,
545 sibling.hierarchy(),
546 sibling_url,
547 self.hierarchy(),
548 self_url))
maruel@chromium.org470b5432011-10-11 18:18:19 +0000549 # In theory we could keep it as a shadow of the other one. In
550 # practice, simply ignore it.
551 logging.warn('Won\'t process duplicate dependency %s' % sibling)
552 return False
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000553 return True
maruel@chromium.org064186c2011-09-27 23:53:33 +0000554
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000555 def LateOverride(self, url):
Paweł Hajdan, Jr7e9303b2017-05-23 14:38:27 +0200556 """Resolves the parsed url from url."""
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000557 assert self.parsed_url == None or not self.should_process, self.parsed_url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000558 parsed_url = self.get_custom_deps(self.name, url)
559 if parsed_url != url:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000560 logging.info(
561 'Dependency(%s).LateOverride(%s) -> %s' %
562 (self.name, url, parsed_url))
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000563 return parsed_url
564
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000565 if isinstance(url, basestring):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000566 parsed_url = urlparse.urlparse(url)
scr@chromium.orgf1eccaf2014-04-11 15:51:33 +0000567 if (not parsed_url[0] and
568 not re.match(r'^\w+\@[\w\.-]+\:[\w\/]+', parsed_url[2])):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000569 # A relative url. Fetch the real base.
570 path = parsed_url[2]
571 if not path.startswith('/'):
572 raise gclient_utils.Error(
573 'relative DEPS entry \'%s\' must begin with a slash' % url)
574 # Create a scm just to query the full url.
575 parent_url = self.parent.parsed_url
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000576 scm = gclient_scm.CreateSCM(
577 parent_url, self.root.root_dir, None, self.outbuf)
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000578 parsed_url = scm.FullUrlForRelativeUrl(url)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000579 else:
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000580 parsed_url = url
maruel@chromium.org470b5432011-10-11 18:18:19 +0000581 logging.info(
582 'Dependency(%s).LateOverride(%s) -> %s' %
583 (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000584 return parsed_url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000585
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000586 if url is None:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000587 logging.info(
588 'Dependency(%s).LateOverride(%s) -> %s' % (self.name, url, url))
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000589 return url
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000590
591 raise gclient_utils.Error('Unknown url type')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000592
bratell@opera.comed2b4fe2013-12-16 14:34:12 +0000593 @staticmethod
594 def MergeWithOsDeps(deps, deps_os, target_os_list):
595 """Returns a new "deps" structure that is the deps sent in updated
596 with information from deps_os (the deps_os section of the DEPS
597 file) that matches the list of target os."""
598 os_overrides = {}
599 for the_target_os in target_os_list:
600 the_target_os_deps = deps_os.get(the_target_os, {})
601 for os_dep_key, os_dep_value in the_target_os_deps.iteritems():
602 overrides = os_overrides.setdefault(os_dep_key, [])
603 overrides.append((the_target_os, os_dep_value))
604
605 # If any os didn't specify a value (we have fewer value entries
606 # than in the os list), then it wants to use the default value.
607 for os_dep_key, os_dep_value in os_overrides.iteritems():
608 if len(os_dep_value) != len(target_os_list):
qyearsley12fa6ff2016-08-24 09:18:40 -0700609 # Record the default value too so that we don't accidentally
bratell@opera.comed2b4fe2013-12-16 14:34:12 +0000610 # set it to None or miss a conflicting DEPS.
611 if os_dep_key in deps:
612 os_dep_value.append(('default', deps[os_dep_key]))
613
614 target_os_deps = {}
615 for os_dep_key, os_dep_value in os_overrides.iteritems():
616 # os_dep_value is a list of (os, value) pairs.
617 possible_values = set(x[1] for x in os_dep_value if x[1] is not None)
618 if not possible_values:
619 target_os_deps[os_dep_key] = None
620 else:
621 if len(possible_values) > 1:
622 # It would be possible to abort here but it would be
623 # unfortunate if we end up preventing any kind of checkout.
624 logging.error('Conflicting dependencies for %s: %s. (target_os=%s)',
625 os_dep_key, os_dep_value, target_os_list)
626 # Sorting to get the same result every time in case of conflicts.
627 target_os_deps[os_dep_key] = sorted(possible_values)[0]
628
629 new_deps = deps.copy()
630 new_deps.update(target_os_deps)
631 return new_deps
632
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200633 def _postprocess_deps(self, deps, rel_prefix):
634 """Performs post-processing of deps compared to what's in the DEPS file."""
Paweł Hajdan, Jr4426eaf2017-06-13 12:25:47 +0200635 # Make sure the dict is mutable, e.g. in case it's frozen.
636 deps = dict(deps)
637
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200638 # If a line is in custom_deps, but not in the solution, we want to append
639 # this line to the solution.
640 for d in self.custom_deps:
641 if d not in deps:
642 deps[d] = self.custom_deps[d]
643
644 if rel_prefix:
645 logging.warning('use_relative_paths enabled.')
646 rel_deps = {}
647 for d, url in deps.items():
648 # normpath is required to allow DEPS to use .. in their
649 # dependency local path.
650 rel_deps[os.path.normpath(os.path.join(rel_prefix, d))] = url
651 logging.warning('Updating deps by prepending %s.', rel_prefix)
652 deps = rel_deps
653
654 return deps
655
656 def _deps_to_objects(self, deps, use_relative_paths):
657 """Convert a deps dict to a dict of Dependency objects."""
658 deps_to_add = []
659 for name, dep_value in deps.iteritems():
660 should_process = self.recursion_limit and self.should_process
661 deps_file = self.deps_file
662 if self.recursedeps is not None:
663 ent = self.recursedeps.get(name)
664 if ent is not None:
665 deps_file = ent['deps_file']
666 if dep_value is None:
667 continue
668 condition = None
669 condition_value = True
670 if isinstance(dep_value, basestring):
671 url = dep_value
672 else:
673 # This should be guaranteed by schema checking in gclient_eval.
674 assert isinstance(dep_value, collections.Mapping)
675 url = dep_value['url']
676 condition = dep_value.get('condition')
677 if condition:
678 # TODO(phajdan.jr): should we also take custom vars into account?
679 condition_value = gclient_eval.EvaluateCondition(condition, self._vars)
680 should_process = should_process and condition_value
681 deps_to_add.append(Dependency(
682 self, name, url, None, None, self.custom_vars, None,
683 deps_file, should_process, use_relative_paths, condition,
684 condition_value))
685 deps_to_add.sort(key=lambda x: x.name)
686 return deps_to_add
687
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000688 def ParseDepsFile(self):
maruel@chromium.org271375b2010-06-23 19:17:38 +0000689 """Parses the DEPS file for this dependency."""
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000690 assert not self.deps_parsed
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000691 assert not self.dependencies
iannucci@chromium.org0c3f3052014-05-22 00:29:22 +0000692
693 deps_content = None
694 use_strict = False
cmp@chromium.org76ce73c2014-07-02 00:13:18 +0000695
696 # First try to locate the configured deps file. If it's missing, fallback
697 # to DEPS.
698 deps_files = [self.deps_file]
699 if 'DEPS' not in deps_files:
700 deps_files.append('DEPS')
701 for deps_file in deps_files:
702 filepath = os.path.join(self.root.root_dir, self.name, deps_file)
703 if os.path.isfile(filepath):
704 logging.info(
705 'ParseDepsFile(%s): %s file found at %s', self.name, deps_file,
706 filepath)
707 break
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000708 logging.info(
cmp@chromium.org76ce73c2014-07-02 00:13:18 +0000709 'ParseDepsFile(%s): No %s file found at %s', self.name, deps_file,
710 filepath)
711
712 if os.path.isfile(filepath):
maruel@chromium.org46304292010-10-28 11:42:00 +0000713 deps_content = gclient_utils.FileRead(filepath)
cmp@chromium.org76ce73c2014-07-02 00:13:18 +0000714 logging.debug('ParseDepsFile(%s) read:\n%s', self.name, deps_content)
iannucci@chromium.org0c3f3052014-05-22 00:29:22 +0000715 use_strict = 'use strict' in deps_content.splitlines()[0]
716
717 local_scope = {}
718 if deps_content:
719 # One thing is unintuitive, vars = {} must happen before Var() use.
720 var = self.VarImpl(self.custom_vars, local_scope)
721 if use_strict:
722 logging.info(
723 'ParseDepsFile(%s): Strict Mode Enabled', self.name)
724 global_scope = {
725 '__builtins__': {'None': None},
726 'Var': var.Lookup,
727 'deps_os': {},
728 }
729 else:
730 global_scope = {
iannucci@chromium.org0c3f3052014-05-22 00:29:22 +0000731 'Var': var.Lookup,
732 'deps_os': {},
733 }
maruel@chromium.org46304292010-10-28 11:42:00 +0000734 # Eval the content.
735 try:
Paweł Hajdan, Jrc485d5a2017-06-02 12:08:09 +0200736 if self._get_option('validate_syntax', False):
737 gclient_eval.Exec(deps_content, global_scope, local_scope, filepath)
738 else:
739 exec(deps_content, global_scope, local_scope)
vapier@chromium.orga81a56e2015-11-11 07:56:13 +0000740 except SyntaxError as e:
maruel@chromium.org46304292010-10-28 11:42:00 +0000741 gclient_utils.SyntaxErrorToError(filepath, e)
iannucci@chromium.org0c3f3052014-05-22 00:29:22 +0000742 if use_strict:
743 for key, val in local_scope.iteritems():
744 if not isinstance(val, (dict, list, tuple, str)):
745 raise gclient_utils.Error(
746 'ParseDepsFile(%s): Strict mode disallows %r -> %r' %
747 (self.name, key, val))
748
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000749 if 'allowed_hosts' in local_scope:
750 try:
751 self._allowed_hosts = frozenset(local_scope.get('allowed_hosts'))
752 except TypeError: # raised if non-iterable
753 pass
754 if not self._allowed_hosts:
755 logging.warning("allowed_hosts is specified but empty %s",
756 self._allowed_hosts)
757 raise gclient_utils.Error(
758 'ParseDepsFile(%s): allowed_hosts must be absent '
759 'or a non-empty iterable' % self.name)
760
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +0200761 self._gn_args_file = local_scope.get('gclient_gn_args_file')
762 self._gn_args = local_scope.get('gclient_gn_args', [])
763
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200764 # Since we heavily post-process things, freeze ones which should
765 # reflect original state of DEPS.
766 self._vars = gclient_utils.freeze(local_scope.get('vars', {}))
767
768 # If use_relative_paths is set in the DEPS file, regenerate
769 # the dictionary using paths relative to the directory containing
770 # the DEPS file. Also update recursedeps if use_relative_paths is
771 # enabled.
772 # If the deps file doesn't set use_relative_paths, but the parent did
773 # (and therefore set self.relative on this Dependency object), then we
774 # want to modify the deps and recursedeps by prepending the parent
775 # directory of this dependency.
776 use_relative_paths = local_scope.get('use_relative_paths', False)
777 rel_prefix = None
778 if use_relative_paths:
779 rel_prefix = self.name
780 elif self._relative:
781 rel_prefix = os.path.dirname(self.name)
782
783 deps = local_scope.get('deps', {})
784 orig_deps = gclient_utils.freeze(deps)
785 if 'recursion' in local_scope:
786 self.recursion_override = local_scope.get('recursion')
787 logging.warning(
788 'Setting %s recursion to %d.', self.name, self.recursion_limit)
789 self.recursedeps = None
790 if 'recursedeps' in local_scope:
791 self.recursedeps = {}
792 for ent in local_scope['recursedeps']:
793 if isinstance(ent, basestring):
794 self.recursedeps[ent] = {"deps_file": self.deps_file}
795 else: # (depname, depsfilename)
796 self.recursedeps[ent[0]] = {"deps_file": ent[1]}
797 logging.warning('Found recursedeps %r.', repr(self.recursedeps))
798
799 if rel_prefix:
800 logging.warning('Updating recursedeps by prepending %s.', rel_prefix)
801 rel_deps = {}
802 for depname, options in self.recursedeps.iteritems():
803 rel_deps[
804 os.path.normpath(os.path.join(rel_prefix, depname))] = options
805 self.recursedeps = rel_deps
806
807 # If present, save 'target_os' in the local_target_os property.
808 if 'target_os' in local_scope:
809 self.local_target_os = local_scope['target_os']
810 # load os specific dependencies if defined. these dependencies may
811 # override or extend the values defined by the 'deps' member.
812 target_os_list = self.target_os
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +0200813 if 'deps_os' in local_scope:
814 for dep_os, os_deps in local_scope['deps_os'].iteritems():
815 self._os_dependencies[dep_os] = self._deps_to_objects(
816 self._postprocess_deps(os_deps, rel_prefix), use_relative_paths)
817 if target_os_list:
818 deps = self.MergeWithOsDeps(
819 deps, local_scope['deps_os'], target_os_list)
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200820
821 deps_to_add = self._deps_to_objects(
822 self._postprocess_deps(deps, rel_prefix), use_relative_paths)
823 orig_deps_to_add = self._deps_to_objects(
824 self._postprocess_deps(orig_deps, rel_prefix), use_relative_paths)
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000825
826 # override named sets of hooks by the custom hooks
827 hooks_to_run = []
828 hook_names_to_suppress = [c.get('name', '') for c in self.custom_hooks]
829 for hook in local_scope.get('hooks', []):
830 if hook.get('name', '') not in hook_names_to_suppress:
831 hooks_to_run.append(hook)
Scott Grahamc4826742017-05-11 16:59:23 -0700832 if 'hooks_os' in local_scope and target_os_list:
833 hooks_os = local_scope['hooks_os']
834 # Specifically append these to ensure that hooks_os run after hooks.
835 for the_target_os in target_os_list:
836 the_target_os_hooks = hooks_os.get(the_target_os, [])
837 hooks_to_run.extend(the_target_os_hooks)
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000838
839 # add the replacements and any additions
840 for hook in self.custom_hooks:
841 if 'action' in hook:
842 hooks_to_run.append(hook)
843
Dirk Prankeda3a29e2017-02-27 15:29:36 -0800844 if self.recursion_limit:
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200845 self._pre_deps_hooks = [Hook.from_dict(hook) for hook in
Dirk Prankeda3a29e2017-02-27 15:29:36 -0800846 local_scope.get('pre_deps_hooks', [])]
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000847
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200848 self.add_dependencies_and_close(
849 deps_to_add, hooks_to_run, orig_deps_to_add=orig_deps_to_add)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000850 logging.info('ParseDepsFile(%s) done' % self.name)
851
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +0200852 def _get_option(self, attr, default):
853 obj = self
854 while not hasattr(obj, '_options'):
855 obj = obj.parent
856 return getattr(obj._options, attr, default)
857
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200858 def add_dependencies_and_close(
859 self, deps_to_add, hooks, orig_deps_to_add=None):
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000860 """Adds the dependencies, hooks and mark the parsing as done."""
maruel@chromium.orgb9be0652011-10-14 18:05:40 +0000861 for dep in deps_to_add:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000862 if dep.verify_validity():
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000863 self.add_dependency(dep)
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200864 for dep in (orig_deps_to_add or deps_to_add):
865 self.add_orig_dependency(dep)
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200866 self._mark_as_parsed([Hook.from_dict(h) for h in hooks])
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000867
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000868 def findDepsFromNotAllowedHosts(self):
869 """Returns a list of depenecies from not allowed hosts.
870
871 If allowed_hosts is not set, allows all hosts and returns empty list.
872 """
873 if not self._allowed_hosts:
874 return []
875 bad_deps = []
876 for dep in self._dependencies:
szager@chromium.orgbd772dd2014-11-05 18:43:08 +0000877 # Don't enforce this for custom_deps.
878 if dep.name in self._custom_deps:
879 continue
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000880 if isinstance(dep.url, basestring):
881 parsed_url = urlparse.urlparse(dep.url)
882 if parsed_url.netloc and parsed_url.netloc not in self._allowed_hosts:
883 bad_deps.append(dep)
884 return bad_deps
885
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +0000886 # Arguments number differs from overridden method
Quinten Yearsleyb2cc4a92016-12-15 13:53:26 -0800887 # pylint: disable=arguments-differ
maruel@chromium.org3742c842010-09-09 19:27:14 +0000888 def run(self, revision_overrides, command, args, work_queue, options):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000889 """Runs |command| then parse the DEPS file."""
maruel@chromium.org470b5432011-10-11 18:18:19 +0000890 logging.info('Dependency(%s).run()' % self.name)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000891 assert self._file_list == []
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000892 if not self.should_process:
893 return
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000894 # When running runhooks, there's no need to consult the SCM.
895 # All known hooks are expected to run unconditionally regardless of working
896 # copy state, so skip the SCM status check.
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +0200897 run_scm = command not in (
898 'flatten', 'runhooks', 'recurse', 'validate', None)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000899 parsed_url = self.LateOverride(self.url)
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000900 file_list = [] if not options.nohooks else None
szager@chromium.org3a3608d2014-10-22 21:13:52 +0000901 revision_override = revision_overrides.pop(self.name, None)
Dave Tubbda9712017-06-01 15:10:53 -0700902 if not revision_override and parsed_url:
903 revision_override = revision_overrides.get(parsed_url.split('@')[0], None)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000904 if run_scm and parsed_url:
agabled437d762016-10-17 09:35:11 -0700905 # Create a shallow copy to mutate revision.
906 options = copy.copy(options)
907 options.revision = revision_override
908 self._used_revision = options.revision
909 self._used_scm = gclient_scm.CreateSCM(
910 parsed_url, self.root.root_dir, self.name, self.outbuf,
911 out_cb=work_queue.out_cb)
912 self._got_revision = self._used_scm.RunCommand(command, options, args,
913 file_list)
914 if file_list:
915 file_list = [os.path.join(self.name, f.strip()) for f in file_list]
maruel@chromium.org68988972011-09-20 14:11:42 +0000916
917 # TODO(phajdan.jr): We should know exactly when the paths are absolute.
918 # Convert all absolute paths to relative.
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000919 for i in range(len(file_list or [])):
maruel@chromium.org68988972011-09-20 14:11:42 +0000920 # It depends on the command being executed (like runhooks vs sync).
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000921 if not os.path.isabs(file_list[i]):
maruel@chromium.org68988972011-09-20 14:11:42 +0000922 continue
923 prefix = os.path.commonprefix(
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000924 [self.root.root_dir.lower(), file_list[i].lower()])
925 file_list[i] = file_list[i][len(prefix):]
maruel@chromium.org68988972011-09-20 14:11:42 +0000926 # Strip any leading path separators.
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000927 while file_list[i].startswith(('\\', '/')):
928 file_list[i] = file_list[i][1:]
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000929
930 # Always parse the DEPS file.
931 self.ParseDepsFile()
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +0200932 if self._gn_args_file and command == 'update':
933 self.WriteGNArgsFile()
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000934 self._run_is_done(file_list or [], parsed_url)
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000935 if command in ('update', 'revert') and not options.noprehooks:
936 self.RunPreDepsHooks()
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000937
938 if self.recursion_limit:
939 # Parse the dependencies of this dependency.
940 for s in self.dependencies:
941 work_queue.enqueue(s)
942
943 if command == 'recurse':
agabled437d762016-10-17 09:35:11 -0700944 # Skip file only checkout.
945 scm = gclient_scm.GetScmName(parsed_url)
946 if not options.scm or scm in options.scm:
947 cwd = os.path.normpath(os.path.join(self.root.root_dir, self.name))
948 # Pass in the SCM type as an env variable. Make sure we don't put
949 # unicode strings in the environment.
950 env = os.environ.copy()
951 if scm:
952 env['GCLIENT_SCM'] = str(scm)
953 if parsed_url:
954 env['GCLIENT_URL'] = str(parsed_url)
955 env['GCLIENT_DEP_PATH'] = str(self.name)
956 if options.prepend_dir and scm == 'git':
957 print_stdout = False
958 def filter_fn(line):
959 """Git-specific path marshaling. It is optimized for git-grep."""
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000960
agabled437d762016-10-17 09:35:11 -0700961 def mod_path(git_pathspec):
962 match = re.match('^(\\S+?:)?([^\0]+)$', git_pathspec)
963 modified_path = os.path.join(self.name, match.group(2))
964 branch = match.group(1) or ''
965 return '%s%s' % (branch, modified_path)
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000966
agabled437d762016-10-17 09:35:11 -0700967 match = re.match('^Binary file ([^\0]+) matches$', line)
968 if match:
969 print('Binary file %s matches\n' % mod_path(match.group(1)))
970 return
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000971
agabled437d762016-10-17 09:35:11 -0700972 items = line.split('\0')
973 if len(items) == 2 and items[1]:
974 print('%s : %s' % (mod_path(items[0]), items[1]))
975 elif len(items) >= 2:
976 # Multiple null bytes or a single trailing null byte indicate
977 # git is likely displaying filenames only (such as with -l)
978 print('\n'.join(mod_path(path) for path in items if path))
979 else:
980 print(line)
981 else:
982 print_stdout = True
983 filter_fn = None
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000984
agabled437d762016-10-17 09:35:11 -0700985 if parsed_url is None:
986 print('Skipped omitted dependency %s' % cwd, file=sys.stderr)
987 elif os.path.isdir(cwd):
988 try:
989 gclient_utils.CheckCallAndFilter(
990 args, cwd=cwd, env=env, print_stdout=print_stdout,
991 filter_fn=filter_fn,
992 )
993 except subprocess2.CalledProcessError:
994 if not options.ignore:
995 raise
996 else:
997 print('Skipped missing %s' % cwd, file=sys.stderr)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000998
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +0200999 def WriteGNArgsFile(self):
1000 lines = ['# Generated from %r' % self.deps_file]
1001 for arg in self._gn_args:
1002 lines.append('%s = %s' % (arg, ToGNString(self._vars[arg])))
1003 with open(os.path.join(self.root.root_dir, self._gn_args_file), 'w') as f:
1004 f.write('\n'.join(lines))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001005
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001006 @gclient_utils.lockedmethod
1007 def _run_is_done(self, file_list, parsed_url):
1008 # Both these are kept for hooks that are run as a separate tree traversal.
1009 self._file_list = file_list
1010 self._parsed_url = parsed_url
1011 self._processed = True
1012
szager@google.comb9a78d32012-03-13 18:46:21 +00001013 def GetHooks(self, options):
1014 """Evaluates all hooks, and return them in a flat list.
1015
1016 RunOnDeps() must have been called before to load the DEPS.
1017 """
1018 result = []
maruel@chromium.org68988972011-09-20 14:11:42 +00001019 if not self.should_process or not self.recursion_limit:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001020 # Don't run the hook when it is above recursion_limit.
szager@google.comb9a78d32012-03-13 18:46:21 +00001021 return result
maruel@chromium.orgdc7445d2010-07-09 21:05:29 +00001022 # If "--force" was specified, run all hooks regardless of what files have
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001023 # changed.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001024 if self.deps_hooks:
agabled437d762016-10-17 09:35:11 -07001025 # TODO(maruel): If the user is using git, then we don't know
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001026 # what files have changed so we always run all hooks. It'd be nice to fix
1027 # that.
1028 if (options.force or
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001029 gclient_scm.GetScmName(self.parsed_url) in ('git', None) or
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001030 os.path.isdir(os.path.join(self.root.root_dir, self.name, '.git'))):
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +02001031 result.extend(self.deps_hooks)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001032 else:
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +02001033 for hook in self.deps_hooks:
1034 if hook.matches(self.file_list_and_children):
1035 result.append(hook)
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001036 for s in self.dependencies:
szager@google.comb9a78d32012-03-13 18:46:21 +00001037 result.extend(s.GetHooks(options))
1038 return result
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001039
szager@google.comb9a78d32012-03-13 18:46:21 +00001040 def RunHooksRecursively(self, options):
1041 assert self.hooks_ran == False
maruel@chromium.org064186c2011-09-27 23:53:33 +00001042 self._hooks_ran = True
szager@google.comb9a78d32012-03-13 18:46:21 +00001043 for hook in self.GetHooks(options):
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +02001044 hook.run(self.root.root_dir)
maruel@chromium.orgeaf61062010-07-07 18:42:39 +00001045
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001046 def RunPreDepsHooks(self):
1047 assert self.processed
1048 assert self.deps_parsed
1049 assert not self.pre_deps_hooks_ran
1050 assert not self.hooks_ran
1051 for s in self.dependencies:
1052 assert not s.processed
1053 self._pre_deps_hooks_ran = True
1054 for hook in self.pre_deps_hooks:
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +02001055 hook.run(self.root.root_dir)
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001056
1057
maruel@chromium.org0d812442010-08-10 12:41:08 +00001058 def subtree(self, include_all):
maruel@chromium.orgad3287e2011-10-03 19:15:10 +00001059 """Breadth first recursion excluding root node."""
maruel@chromium.orgf13a4182011-09-22 00:26:15 +00001060 dependencies = self.dependencies
1061 for d in dependencies:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001062 if d.should_process or include_all:
maruel@chromium.orgad3287e2011-10-03 19:15:10 +00001063 yield d
maruel@chromium.orgf13a4182011-09-22 00:26:15 +00001064 for d in dependencies:
maruel@chromium.orgad3287e2011-10-03 19:15:10 +00001065 for i in d.subtree(include_all):
1066 yield i
1067
1068 def depth_first_tree(self):
1069 """Depth-first recursion including the root node."""
1070 yield self
1071 for i in self.dependencies:
1072 for j in i.depth_first_tree():
1073 if j.should_process:
1074 yield j
maruel@chromium.orgc57e4f22010-07-22 21:37:46 +00001075
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001076 @gclient_utils.lockedmethod
1077 def add_dependency(self, new_dep):
1078 self._dependencies.append(new_dep)
1079
1080 @gclient_utils.lockedmethod
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +02001081 def add_orig_dependency(self, new_dep):
1082 self._orig_dependencies.append(new_dep)
1083
1084 @gclient_utils.lockedmethod
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001085 def _mark_as_parsed(self, new_hooks):
1086 self._deps_hooks.extend(new_hooks)
1087 self._deps_parsed = True
1088
maruel@chromium.org68988972011-09-20 14:11:42 +00001089 @property
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001090 @gclient_utils.lockedmethod
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +00001091 def dependencies(self):
1092 return tuple(self._dependencies)
1093
1094 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001095 @gclient_utils.lockedmethod
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +02001096 def orig_dependencies(self):
1097 return tuple(self._orig_dependencies)
1098
1099 @property
1100 @gclient_utils.lockedmethod
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02001101 def os_dependencies(self):
1102 return dict(self._os_dependencies)
1103
1104 @property
1105 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001106 def deps_hooks(self):
1107 return tuple(self._deps_hooks)
1108
1109 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001110 @gclient_utils.lockedmethod
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001111 def pre_deps_hooks(self):
1112 return tuple(self._pre_deps_hooks)
1113
1114 @property
1115 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001116 def parsed_url(self):
1117 return self._parsed_url
1118
1119 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001120 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001121 def deps_parsed(self):
maruel@chromium.org3223edd2011-10-10 23:17:39 +00001122 """This is purely for debugging purposes. It's not used anywhere."""
maruel@chromium.org064186c2011-09-27 23:53:33 +00001123 return self._deps_parsed
1124
1125 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001126 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001127 def processed(self):
1128 return self._processed
1129
1130 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001131 @gclient_utils.lockedmethod
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001132 def pre_deps_hooks_ran(self):
1133 return self._pre_deps_hooks_ran
1134
1135 @property
1136 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001137 def hooks_ran(self):
1138 return self._hooks_ran
1139
1140 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001141 @gclient_utils.lockedmethod
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00001142 def allowed_hosts(self):
1143 return self._allowed_hosts
1144
1145 @property
1146 @gclient_utils.lockedmethod
maruel@chromium.org861fd0f2010-07-23 03:05:05 +00001147 def file_list(self):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001148 return tuple(self._file_list)
1149
1150 @property
kustermann@google.coma692e8f2013-04-18 08:32:04 +00001151 def used_scm(self):
1152 """SCMWrapper instance for this dependency or None if not processed yet."""
1153 return self._used_scm
1154
1155 @property
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00001156 @gclient_utils.lockedmethod
1157 def got_revision(self):
1158 return self._got_revision
1159
1160 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001161 def file_list_and_children(self):
1162 result = list(self.file_list)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +00001163 for d in self.dependencies:
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001164 result.extend(d.file_list_and_children)
maruel@chromium.org68988972011-09-20 14:11:42 +00001165 return tuple(result)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +00001166
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001167 def __str__(self):
1168 out = []
agablea98a6cd2016-11-15 14:30:10 -08001169 for i in ('name', 'url', 'parsed_url', 'custom_deps',
maruel@chromium.org3c74bc92011-09-15 19:17:21 +00001170 'custom_vars', 'deps_hooks', 'file_list', 'should_process',
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00001171 'processed', 'hooks_ran', 'deps_parsed', 'requirements',
1172 'allowed_hosts'):
maruel@chromium.org3c74bc92011-09-15 19:17:21 +00001173 # First try the native property if it exists.
1174 if hasattr(self, '_' + i):
1175 value = getattr(self, '_' + i, False)
1176 else:
1177 value = getattr(self, i, False)
1178 if value:
1179 out.append('%s: %s' % (i, value))
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001180
1181 for d in self.dependencies:
1182 out.extend([' ' + x for x in str(d).splitlines()])
1183 out.append('')
1184 return '\n'.join(out)
1185
1186 def __repr__(self):
1187 return '%s: %s' % (self.name, self.url)
1188
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001189 def hierarchy(self, include_url=True):
maruel@chromium.orgbc2d2f92010-07-22 21:26:48 +00001190 """Returns a human-readable hierarchical reference to a Dependency."""
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001191 def format_name(d):
1192 if include_url:
1193 return '%s(%s)' % (d.name, d.url)
1194 return d.name
1195 out = format_name(self)
maruel@chromium.orgbffb9042010-07-22 20:59:36 +00001196 i = self.parent
1197 while i and i.name:
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001198 out = '%s -> %s' % (format_name(i), out)
maruel@chromium.orgbffb9042010-07-22 20:59:36 +00001199 i = i.parent
1200 return out
1201
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001202
1203class GClient(Dependency):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001204 """Object that represent a gclient checkout. A tree of Dependency(), one per
1205 solution or DEPS entry."""
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001206
1207 DEPS_OS_CHOICES = {
1208 "win32": "win",
1209 "win": "win",
1210 "cygwin": "win",
1211 "darwin": "mac",
1212 "mac": "mac",
1213 "unix": "unix",
1214 "linux": "unix",
1215 "linux2": "unix",
maruel@chromium.org244e3442011-06-12 15:20:55 +00001216 "linux3": "unix",
szager@chromium.orgf8c95cd2012-06-01 22:26:52 +00001217 "android": "android",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001218 }
1219
1220 DEFAULT_CLIENT_FILE_TEXT = ("""\
1221solutions = [
smutae7ea312016-07-18 11:59:41 -07001222 { "name" : "%(solution_name)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001223 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +00001224 "deps_file" : "%(deps_file)s",
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001225 "managed" : %(managed)s,
smutae7ea312016-07-18 11:59:41 -07001226 "custom_deps" : {
1227 },
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001228 },
1229]
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001230cache_dir = %(cache_dir)r
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001231""")
1232
1233 DEFAULT_SNAPSHOT_SOLUTION_TEXT = ("""\
smutae7ea312016-07-18 11:59:41 -07001234 { "name" : "%(solution_name)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001235 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +00001236 "deps_file" : "%(deps_file)s",
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001237 "managed" : %(managed)s,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001238 "custom_deps" : {
smutae7ea312016-07-18 11:59:41 -07001239%(solution_deps)s },
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001240 },
1241""")
1242
1243 DEFAULT_SNAPSHOT_FILE_TEXT = ("""\
1244# Snapshot generated with gclient revinfo --snapshot
1245solutions = [
maruel@chromium.org73e21142010-07-05 13:32:01 +00001246%(solution_list)s]
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001247""")
1248
1249 def __init__(self, root_dir, options):
maruel@chromium.org0d812442010-08-10 12:41:08 +00001250 # Do not change previous behavior. Only solution level and immediate DEPS
1251 # are processed.
1252 self._recursion_limit = 2
agablea98a6cd2016-11-15 14:30:10 -08001253 Dependency.__init__(self, None, None, None, True, None, None, None,
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +02001254 'unused', True, None, None, True)
maruel@chromium.org0d425922010-06-21 19:22:24 +00001255 self._options = options
maruel@chromium.org271375b2010-06-23 19:17:38 +00001256 if options.deps_os:
1257 enforced_os = options.deps_os.split(',')
1258 else:
1259 enforced_os = [self.DEPS_OS_CHOICES.get(sys.platform, 'unix')]
1260 if 'all' in enforced_os:
1261 enforced_os = self.DEPS_OS_CHOICES.itervalues()
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001262 self._enforced_os = tuple(set(enforced_os))
maruel@chromium.org271375b2010-06-23 19:17:38 +00001263 self._root_dir = root_dir
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001264 self.config_content = None
1265
borenet@google.com88d10082014-03-21 17:24:48 +00001266 def _CheckConfig(self):
1267 """Verify that the config matches the state of the existing checked-out
1268 solutions."""
1269 for dep in self.dependencies:
1270 if dep.managed and dep.url:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001271 scm = gclient_scm.CreateSCM(
1272 dep.url, self.root_dir, dep.name, self.outbuf)
smut@google.comd33eab32014-07-07 19:35:18 +00001273 actual_url = scm.GetActualRemoteURL(self._options)
borenet@google.com4e9be262014-04-08 19:40:30 +00001274 if actual_url and not scm.DoesRemoteURLMatch(self._options):
levarum@chromium.org27a6f9a2016-05-28 00:21:49 +00001275 mirror = scm.GetCacheMirror()
1276 if mirror:
1277 mirror_string = '%s (exists=%s)' % (mirror.mirror_path,
1278 mirror.exists())
1279 else:
1280 mirror_string = 'not used'
borenet@google.com0a427372014-04-02 19:12:13 +00001281 raise gclient_utils.Error('''
borenet@google.com88d10082014-03-21 17:24:48 +00001282Your .gclient file seems to be broken. The requested URL is different from what
borenet@google.com0a427372014-04-02 19:12:13 +00001283is actually checked out in %(checkout_path)s.
borenet@google.com88d10082014-03-21 17:24:48 +00001284
borenet@google.com97882362014-04-07 20:06:02 +00001285The .gclient file contains:
levarum@chromium.org27a6f9a2016-05-28 00:21:49 +00001286URL: %(expected_url)s (%(expected_scm)s)
1287Cache mirror: %(mirror_string)s
borenet@google.com97882362014-04-07 20:06:02 +00001288
1289The local checkout in %(checkout_path)s reports:
1290%(actual_url)s (%(actual_scm)s)
borenet@google.com88d10082014-03-21 17:24:48 +00001291
1292You should ensure that the URL listed in .gclient is correct and either change
agabled437d762016-10-17 09:35:11 -07001293it or fix the checkout.
borenet@google.com88d10082014-03-21 17:24:48 +00001294''' % {'checkout_path': os.path.join(self.root_dir, dep.name),
1295 'expected_url': dep.url,
1296 'expected_scm': gclient_scm.GetScmName(dep.url),
levarum@chromium.org27a6f9a2016-05-28 00:21:49 +00001297 'mirror_string' : mirror_string,
borenet@google.com88d10082014-03-21 17:24:48 +00001298 'actual_url': actual_url,
1299 'actual_scm': gclient_scm.GetScmName(actual_url)})
1300
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001301 def SetConfig(self, content):
maruel@chromium.orgf13a4182011-09-22 00:26:15 +00001302 assert not self.dependencies
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001303 config_dict = {}
1304 self.config_content = content
1305 try:
1306 exec(content, config_dict)
vapier@chromium.orga81a56e2015-11-11 07:56:13 +00001307 except SyntaxError as e:
maruel@chromium.org5990f9d2010-07-07 18:02:58 +00001308 gclient_utils.SyntaxErrorToError('.gclient', e)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001309
peter@chromium.org1efccc82012-04-27 16:34:38 +00001310 # Append any target OS that is not already being enforced to the tuple.
1311 target_os = config_dict.get('target_os', [])
stuartmorgan@chromium.org18a4f6a2012-11-02 13:37:24 +00001312 if config_dict.get('target_os_only', False):
1313 self._enforced_os = tuple(set(target_os))
1314 else:
1315 self._enforced_os = tuple(set(self._enforced_os).union(target_os))
1316
dyen@chromium.orgd915cca2014-08-07 21:41:37 +00001317 cache_dir = config_dict.get('cache_dir')
1318 if cache_dir:
1319 cache_dir = os.path.join(self.root_dir, cache_dir)
1320 cache_dir = os.path.abspath(cache_dir)
szager@chromium.orgcaf5bef2014-08-24 18:56:32 +00001321 # If running on a bot, force break any stale git cache locks.
dnj@chromium.orgb682b3e2014-08-25 19:17:12 +00001322 if os.path.exists(cache_dir) and os.environ.get('CHROME_HEADLESS'):
szager@chromium.org4848fb62014-08-24 19:16:31 +00001323 subprocess2.check_call(['git', 'cache', 'unlock', '--cache-dir',
1324 cache_dir, '--force', '--all'])
dyen@chromium.orgd915cca2014-08-07 21:41:37 +00001325 gclient_scm.GitWrapper.cache_dir = cache_dir
1326 git_cache.Mirror.SetCachePath(cache_dir)
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001327
stuartmorgan@chromium.org18a4f6a2012-11-02 13:37:24 +00001328 if not target_os and config_dict.get('target_os_only', False):
1329 raise gclient_utils.Error('Can\'t use target_os_only if target_os is '
1330 'not specified')
peter@chromium.org1efccc82012-04-27 16:34:38 +00001331
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001332 deps_to_add = []
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001333 for s in config_dict.get('solutions', []):
maruel@chromium.org81843b82010-06-28 16:49:26 +00001334 try:
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001335 deps_to_add.append(Dependency(
maruel@chromium.org81843b82010-06-28 16:49:26 +00001336 self, s['name'], s['url'],
smutae7ea312016-07-18 11:59:41 -07001337 s.get('managed', True),
maruel@chromium.org81843b82010-06-28 16:49:26 +00001338 s.get('custom_deps', {}),
maruel@chromium.org0d812442010-08-10 12:41:08 +00001339 s.get('custom_vars', {}),
petermayo@chromium.orge79161a2013-07-09 14:40:37 +00001340 s.get('custom_hooks', []),
nsylvain@google.comefc80932011-05-31 21:27:56 +00001341 s.get('deps_file', 'DEPS'),
agabledce6ddc2016-09-08 10:02:16 -07001342 True,
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +02001343 None,
1344 None,
1345 True))
maruel@chromium.org81843b82010-06-28 16:49:26 +00001346 except KeyError:
1347 raise gclient_utils.Error('Invalid .gclient file. Solution is '
1348 'incomplete: %s' % s)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001349 self.add_dependencies_and_close(deps_to_add, config_dict.get('hooks', []))
1350 logging.info('SetConfig() done')
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001351
1352 def SaveConfig(self):
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001353 gclient_utils.FileWrite(os.path.join(self.root_dir,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001354 self._options.config_filename),
1355 self.config_content)
1356
1357 @staticmethod
1358 def LoadCurrentConfig(options):
1359 """Searches for and loads a .gclient file relative to the current working
1360 dir. Returns a GClient object."""
szager@chromium.orge2e03202012-07-31 18:05:16 +00001361 if options.spec:
1362 client = GClient('.', options)
1363 client.SetConfig(options.spec)
1364 else:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00001365 if options.verbose:
1366 print('Looking for %s starting from %s\n' % (
1367 options.config_filename, os.getcwd()))
szager@chromium.orge2e03202012-07-31 18:05:16 +00001368 path = gclient_utils.FindGclientRoot(os.getcwd(), options.config_filename)
1369 if not path:
1370 return None
1371 client = GClient(path, options)
1372 client.SetConfig(gclient_utils.FileRead(
1373 os.path.join(path, options.config_filename)))
maruel@chromium.org69392e72011-10-13 22:09:00 +00001374
1375 if (options.revisions and
1376 len(client.dependencies) > 1 and
1377 any('@' not in r for r in options.revisions)):
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001378 print(
1379 ('You must specify the full solution name like --revision %s@%s\n'
1380 'when you have multiple solutions setup in your .gclient file.\n'
1381 'Other solutions present are: %s.') % (
maruel@chromium.org69392e72011-10-13 22:09:00 +00001382 client.dependencies[0].name,
1383 options.revisions[0],
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001384 ', '.join(s.name for s in client.dependencies[1:])),
1385 file=sys.stderr)
maruel@chromium.org15804092010-09-02 17:07:37 +00001386 return client
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001387
nsylvain@google.comefc80932011-05-31 21:27:56 +00001388 def SetDefaultConfig(self, solution_name, deps_file, solution_url,
agablea98a6cd2016-11-15 14:30:10 -08001389 managed=True, cache_dir=None):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001390 self.SetConfig(self.DEFAULT_CLIENT_FILE_TEXT % {
1391 'solution_name': solution_name,
1392 'solution_url': solution_url,
nsylvain@google.comefc80932011-05-31 21:27:56 +00001393 'deps_file': deps_file,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001394 'managed': managed,
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001395 'cache_dir': cache_dir,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001396 })
1397
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001398 def _SaveEntries(self):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001399 """Creates a .gclient_entries file to record the list of unique checkouts.
1400
1401 The .gclient_entries file lives in the same directory as .gclient.
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001402 """
1403 # Sometimes pprint.pformat will use {', sometimes it'll use { ' ... It
1404 # makes testing a bit too fun.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001405 result = 'entries = {\n'
maruel@chromium.org68988972011-09-20 14:11:42 +00001406 for entry in self.root.subtree(False):
agabled437d762016-10-17 09:35:11 -07001407 result += ' %s: %s,\n' % (pprint.pformat(entry.name),
1408 pprint.pformat(entry.parsed_url))
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001409 result += '}\n'
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001410 file_path = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org1333cb32011-10-04 23:40:16 +00001411 logging.debug(result)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001412 gclient_utils.FileWrite(file_path, result)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001413
1414 def _ReadEntries(self):
1415 """Read the .gclient_entries file for the given client.
1416
1417 Returns:
1418 A sequence of solution names, which will be empty if there is the
1419 entries file hasn't been created yet.
1420 """
1421 scope = {}
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001422 filename = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001423 if not os.path.exists(filename):
maruel@chromium.org73e21142010-07-05 13:32:01 +00001424 return {}
maruel@chromium.org5990f9d2010-07-07 18:02:58 +00001425 try:
1426 exec(gclient_utils.FileRead(filename), scope)
vapier@chromium.orga81a56e2015-11-11 07:56:13 +00001427 except SyntaxError as e:
maruel@chromium.org5990f9d2010-07-07 18:02:58 +00001428 gclient_utils.SyntaxErrorToError(filename, e)
Aaron Gable3721ee92017-04-03 14:53:14 -07001429 return scope.get('entries', {})
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001430
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001431 def _EnforceRevisions(self):
maruel@chromium.org918a9ae2010-05-28 15:50:30 +00001432 """Checks for revision overrides."""
1433 revision_overrides = {}
smutae7ea312016-07-18 11:59:41 -07001434 if self._options.head:
1435 return revision_overrides
joi@chromium.org792ea882010-11-10 02:37:27 +00001436 if not self._options.revisions:
1437 for s in self.dependencies:
smutae7ea312016-07-18 11:59:41 -07001438 if not s.managed:
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001439 self._options.revisions.append('%s@unmanaged' % s.name)
maruel@chromium.org307d1792010-05-31 20:03:13 +00001440 if not self._options.revisions:
1441 return revision_overrides
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001442 solutions_names = [s.name for s in self.dependencies]
smutae7ea312016-07-18 11:59:41 -07001443 index = 0
1444 for revision in self._options.revisions:
1445 if not '@' in revision:
maruel@chromium.org307d1792010-05-31 20:03:13 +00001446 # Support for --revision 123
smutae7ea312016-07-18 11:59:41 -07001447 revision = '%s@%s' % (solutions_names[index], revision)
1448 name, rev = revision.split('@', 1)
szager@chromium.org4ad264b2014-05-20 04:43:47 +00001449 revision_overrides[name] = rev
smutae7ea312016-07-18 11:59:41 -07001450 index += 1
maruel@chromium.org918a9ae2010-05-28 15:50:30 +00001451 return revision_overrides
1452
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001453 def RunOnDeps(self, command, args, ignore_requirements=False, progress=True):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001454 """Runs a command on each dependency in a client and its dependencies.
1455
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001456 Args:
1457 command: The command to use (e.g., 'status' or 'diff')
1458 args: list of str - extra arguments to add to the command line.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001459 """
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001460 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +00001461 raise gclient_utils.Error('No solution specified')
borenet@google.com0a427372014-04-02 19:12:13 +00001462
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001463 revision_overrides = {}
1464 # It's unnecessary to check for revision overrides for 'recurse'.
1465 # Save a few seconds by not calling _EnforceRevisions() in that case.
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +02001466 if command not in ('diff', 'recurse', 'runhooks', 'status', 'revert',
1467 'validate'):
szager@chromium.org5273b8a2014-08-21 15:10:10 +00001468 self._CheckConfig()
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001469 revision_overrides = self._EnforceRevisions()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001470 pm = None
maruel@chromium.org5b3f8852010-09-10 16:49:54 +00001471 # Disable progress for non-tty stdout.
iannucci@chromium.org596cd5c2016-04-04 21:34:39 +00001472 if (setup_color.IS_TTY and not self._options.verbose and progress):
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001473 if command in ('update', 'revert'):
1474 pm = Progress('Syncing projects', 1)
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +02001475 elif command in ('recurse', 'validate'):
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001476 pm = Progress(' '.join(args), 1)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001477 work_queue = gclient_utils.ExecutionQueue(
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001478 self._options.jobs, pm, ignore_requirements=ignore_requirements,
1479 verbose=self._options.verbose)
maruel@chromium.org049bced2010-08-12 13:37:20 +00001480 for s in self.dependencies:
1481 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +00001482 work_queue.flush(revision_overrides, command, args, options=self._options)
szager@chromium.org4ad264b2014-05-20 04:43:47 +00001483 if revision_overrides:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001484 print('Please fix your script, having invalid --revision flags will soon '
1485 'considered an error.', file=sys.stderr)
piman@chromium.org6f363722010-04-27 00:41:09 +00001486
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001487 # Once all the dependencies have been processed, it's now safe to run the
1488 # hooks.
1489 if not self._options.nohooks:
1490 self.RunHooksRecursively(self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001491
1492 if command == 'update':
ajwong@chromium.orgcdcee802009-06-23 15:30:42 +00001493 # Notify the user if there is an orphaned entry in their working copy.
1494 # Only delete the directory if there are no changes in it, and
1495 # delete_unversioned_trees is set to true.
maruel@chromium.org68988972011-09-20 14:11:42 +00001496 entries = [i.name for i in self.root.subtree(False) if i.url]
xusydoc@chromium.org885a9602013-05-31 09:54:40 +00001497 full_entries = [os.path.join(self.root_dir, e.replace('/', os.path.sep))
1498 for e in entries]
1499
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001500 for entry, prev_url in self._ReadEntries().iteritems():
maruel@chromium.org04dd7de2010-10-14 13:25:49 +00001501 if not prev_url:
1502 # entry must have been overridden via .gclient custom_deps
1503 continue
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001504 # Fix path separator on Windows.
1505 entry_fixed = entry.replace('/', os.path.sep)
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001506 e_dir = os.path.join(self.root_dir, entry_fixed)
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001507 # Use entry and not entry_fixed there.
jochen@chromium.orga78e5532013-03-11 13:33:03 +00001508 if (entry not in entries and
1509 (not any(path.startswith(entry + '/') for path in entries)) and
jochen@chromium.orgcc475722013-03-11 13:07:40 +00001510 os.path.exists(e_dir)):
primiano@chromium.org1c127382015-02-17 11:15:40 +00001511 # The entry has been removed from DEPS.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001512 scm = gclient_scm.CreateSCM(
1513 prev_url, self.root_dir, entry_fixed, self.outbuf)
xusydoc@chromium.org885a9602013-05-31 09:54:40 +00001514
1515 # Check to see if this directory is now part of a higher-up checkout.
borenet@google.com359bb642014-05-13 17:28:19 +00001516 scm_root = None
agabled437d762016-10-17 09:35:11 -07001517 try:
1518 scm_root = gclient_scm.scm.GIT.GetCheckoutRoot(scm.checkout_path)
1519 except subprocess2.CalledProcessError:
1520 pass
1521 if not scm_root:
borenet@google.com359bb642014-05-13 17:28:19 +00001522 logging.warning('Could not find checkout root for %s. Unable to '
1523 'determine whether it is part of a higher-level '
1524 'checkout, so not removing.' % entry)
1525 continue
primiano@chromium.org1c127382015-02-17 11:15:40 +00001526
1527 # This is to handle the case of third_party/WebKit migrating from
1528 # being a DEPS entry to being part of the main project.
1529 # If the subproject is a Git project, we need to remove its .git
1530 # folder. Otherwise git operations on that folder will have different
1531 # effects depending on the current working directory.
agabled437d762016-10-17 09:35:11 -07001532 if os.path.abspath(scm_root) == os.path.abspath(e_dir):
primiano@chromium.org1c127382015-02-17 11:15:40 +00001533 e_par_dir = os.path.join(e_dir, os.pardir)
agabled437d762016-10-17 09:35:11 -07001534 if gclient_scm.scm.GIT.IsInsideWorkTree(e_par_dir):
1535 par_scm_root = gclient_scm.scm.GIT.GetCheckoutRoot(e_par_dir)
primiano@chromium.org1c127382015-02-17 11:15:40 +00001536 # rel_e_dir : relative path of entry w.r.t. its parent repo.
1537 rel_e_dir = os.path.relpath(e_dir, par_scm_root)
agabled437d762016-10-17 09:35:11 -07001538 if gclient_scm.scm.GIT.IsDirectoryVersioned(
1539 par_scm_root, rel_e_dir):
primiano@chromium.org1c127382015-02-17 11:15:40 +00001540 save_dir = scm.GetGitBackupDirPath()
1541 # Remove any eventual stale backup dir for the same project.
1542 if os.path.exists(save_dir):
1543 gclient_utils.rmtree(save_dir)
1544 os.rename(os.path.join(e_dir, '.git'), save_dir)
1545 # When switching between the two states (entry/ is a subproject
1546 # -> entry/ is part of the outer project), it is very likely
1547 # that some files are changed in the checkout, unless we are
1548 # jumping *exactly* across the commit which changed just DEPS.
1549 # In such case we want to cleanup any eventual stale files
1550 # (coming from the old subproject) in order to end up with a
1551 # clean checkout.
agabled437d762016-10-17 09:35:11 -07001552 gclient_scm.scm.GIT.CleanupDir(par_scm_root, rel_e_dir)
primiano@chromium.org1c127382015-02-17 11:15:40 +00001553 assert not os.path.exists(os.path.join(e_dir, '.git'))
1554 print(('\nWARNING: \'%s\' has been moved from DEPS to a higher '
1555 'level checkout. The git folder containing all the local'
1556 ' branches has been saved to %s.\n'
1557 'If you don\'t care about its state you can safely '
1558 'remove that folder to free up space.') %
1559 (entry, save_dir))
1560 continue
1561
borenet@google.com359bb642014-05-13 17:28:19 +00001562 if scm_root in full_entries:
primiano@chromium.org1c127382015-02-17 11:15:40 +00001563 logging.info('%s is part of a higher level checkout, not removing',
1564 scm.GetCheckoutRoot())
xusydoc@chromium.org885a9602013-05-31 09:54:40 +00001565 continue
1566
1567 file_list = []
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001568 scm.status(self._options, [], file_list)
1569 modified_files = file_list != []
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001570 if (not self._options.delete_unversioned_trees or
1571 (modified_files and not self._options.force)):
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001572 # There are modified files in this entry. Keep warning until
1573 # removed.
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001574 print(('\nWARNING: \'%s\' is no longer part of this client. '
1575 'It is recommended that you manually remove it.\n') %
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001576 entry_fixed)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001577 else:
1578 # Delete the entry
maruel@chromium.org73e21142010-07-05 13:32:01 +00001579 print('\n________ deleting \'%s\' in \'%s\'' % (
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001580 entry_fixed, self.root_dir))
digit@chromium.orgdc112ac2013-04-24 13:00:19 +00001581 gclient_utils.rmtree(e_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001582 # record the current list of entries for next time
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001583 self._SaveEntries()
maruel@chromium.org17cdf762010-05-28 17:30:52 +00001584 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001585
1586 def PrintRevInfo(self):
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001587 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +00001588 raise gclient_utils.Error('No solution specified')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001589 # Load all the settings.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001590 work_queue = gclient_utils.ExecutionQueue(
1591 self._options.jobs, None, False, verbose=self._options.verbose)
maruel@chromium.org049bced2010-08-12 13:37:20 +00001592 for s in self.dependencies:
1593 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +00001594 work_queue.flush({}, None, [], options=self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001595
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001596 def GetURLAndRev(dep):
1597 """Returns the revision-qualified SCM url for a Dependency."""
1598 if dep.parsed_url is None:
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001599 return None
agabled437d762016-10-17 09:35:11 -07001600 url, _ = gclient_utils.SplitUrlRevision(dep.parsed_url)
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001601 scm = gclient_scm.CreateSCM(
agabled437d762016-10-17 09:35:11 -07001602 dep.parsed_url, self.root_dir, dep.name, self.outbuf)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001603 if not os.path.isdir(scm.checkout_path):
1604 return None
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001605 return '%s@%s' % (url, scm.revinfo(self._options, [], None))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001606
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001607 if self._options.snapshot:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001608 new_gclient = ''
1609 # First level at .gclient
1610 for d in self.dependencies:
1611 entries = {}
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001612 def GrabDeps(dep):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001613 """Recursively grab dependencies."""
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001614 for d in dep.dependencies:
1615 entries[d.name] = GetURLAndRev(d)
1616 GrabDeps(d)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001617 GrabDeps(d)
1618 custom_deps = []
1619 for k in sorted(entries.keys()):
1620 if entries[k]:
1621 # Quotes aren't escaped...
1622 custom_deps.append(' \"%s\": \'%s\',\n' % (k, entries[k]))
1623 else:
1624 custom_deps.append(' \"%s\": None,\n' % k)
1625 new_gclient += self.DEFAULT_SNAPSHOT_SOLUTION_TEXT % {
1626 'solution_name': d.name,
1627 'solution_url': d.url,
nsylvain@google.comefc80932011-05-31 21:27:56 +00001628 'deps_file': d.deps_file,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001629 'managed': d.managed,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001630 'solution_deps': ''.join(custom_deps),
1631 }
1632 # Print the snapshot configuration file
1633 print(self.DEFAULT_SNAPSHOT_FILE_TEXT % {'solution_list': new_gclient})
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +00001634 else:
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001635 entries = {}
maruel@chromium.org68988972011-09-20 14:11:42 +00001636 for d in self.root.subtree(False):
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001637 if self._options.actual:
1638 entries[d.name] = GetURLAndRev(d)
1639 else:
1640 entries[d.name] = d.parsed_url
1641 keys = sorted(entries.keys())
1642 for x in keys:
maruel@chromium.orgce464892010-08-12 17:12:18 +00001643 print('%s: %s' % (x, entries[x]))
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +00001644 logging.info(str(self))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001645
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001646 def ParseDepsFile(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001647 """No DEPS to parse for a .gclient file."""
maruel@chromium.org049bced2010-08-12 13:37:20 +00001648 raise gclient_utils.Error('Internal error')
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001649
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00001650 def PrintLocationAndContents(self):
1651 # Print out the .gclient file. This is longer than if we just printed the
1652 # client dict, but more legible, and it might contain helpful comments.
1653 print('Loaded .gclient config in %s:\n%s' % (
1654 self.root_dir, self.config_content))
1655
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001656 @property
maruel@chromium.org75a59272010-06-11 22:34:03 +00001657 def root_dir(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001658 """Root directory of gclient checkout."""
maruel@chromium.org75a59272010-06-11 22:34:03 +00001659 return self._root_dir
1660
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001661 @property
maruel@chromium.org271375b2010-06-23 19:17:38 +00001662 def enforced_os(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001663 """What deps_os entries that are to be parsed."""
maruel@chromium.org271375b2010-06-23 19:17:38 +00001664 return self._enforced_os
1665
maruel@chromium.org68988972011-09-20 14:11:42 +00001666 @property
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001667 def recursion_limit(self):
1668 """How recursive can each dependencies in DEPS file can load DEPS file."""
1669 return self._recursion_limit
1670
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +00001671 @property
cmp@chromium.orgc401ad12014-07-02 23:20:08 +00001672 def try_recursedeps(self):
1673 """Whether to attempt using recursedeps-style recursion processing."""
cmp@chromium.orge84ac912014-06-30 23:14:35 +00001674 return True
1675
1676 @property
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +00001677 def target_os(self):
1678 return self._enforced_os
1679
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001680
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001681#### gclient commands.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001682
1683
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001684@subcommand.usage('[command] [args ...]')
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001685def CMDrecurse(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001686 """Operates [command args ...] on all the dependencies.
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001687
1688 Runs a shell command on all entries.
qyearsley12fa6ff2016-08-24 09:18:40 -07001689 Sets GCLIENT_DEP_PATH environment variable as the dep's relative location to
ilevy@chromium.org37116242012-11-28 01:32:48 +00001690 root directory of the checkout.
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001691 """
1692 # Stop parsing at the first non-arg so that these go through to the command
1693 parser.disable_interspersed_args()
1694 parser.add_option('-s', '--scm', action='append', default=[],
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001695 help='Choose scm types to operate upon.')
maruel@chromium.org288054d2012-03-05 00:43:07 +00001696 parser.add_option('-i', '--ignore', action='store_true',
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001697 help='Ignore non-zero return codes from subcommands.')
1698 parser.add_option('--prepend-dir', action='store_true',
1699 help='Prepend relative dir for use with git <cmd> --null.')
1700 parser.add_option('--no-progress', action='store_true',
1701 help='Disable progress bar that shows sub-command updates')
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001702 options, args = parser.parse_args(args)
maruel@chromium.org45e9f2d2010-10-18 13:33:46 +00001703 if not args:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001704 print('Need to supply a command!', file=sys.stderr)
maruel@chromium.org45e9f2d2010-10-18 13:33:46 +00001705 return 1
maruel@chromium.org78cba522010-10-18 13:32:05 +00001706 root_and_entries = gclient_utils.GetGClientRootAndEntries()
1707 if not root_and_entries:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001708 print(
maruel@chromium.org78cba522010-10-18 13:32:05 +00001709 'You need to run gclient sync at least once to use \'recurse\'.\n'
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001710 'This is because .gclient_entries needs to exist and be up to date.',
1711 file=sys.stderr)
maruel@chromium.org78cba522010-10-18 13:32:05 +00001712 return 1
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001713
1714 # Normalize options.scm to a set()
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001715 scm_set = set()
1716 for scm in options.scm:
1717 scm_set.update(scm.split(','))
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001718 options.scm = scm_set
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001719
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001720 options.nohooks = True
1721 client = GClient.LoadCurrentConfig(options)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001722 return client.RunOnDeps('recurse', args, ignore_requirements=True,
1723 progress=not options.no_progress)
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001724
1725
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001726@subcommand.usage('[args ...]')
davidbarr@chromium.org12f944e2012-03-01 02:18:31 +00001727def CMDfetch(parser, args):
1728 """Fetches upstream commits for all modules.
1729
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001730 Completely git-specific. Simply runs 'git fetch [args ...]' for each module.
1731 """
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001732 (options, args) = parser.parse_args(args)
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00001733 return CMDrecurse(OptionParser(), [
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001734 '--jobs=%d' % options.jobs, '--scm=git', 'git', 'fetch'] + args)
1735
1736
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001737def CMDflatten(parser, args):
1738 """Flattens the solutions into a single DEPS file."""
1739 parser.add_option('--output-deps', help='Path to the output DEPS file')
1740 parser.add_option(
1741 '--require-pinned-revisions', action='store_true',
1742 help='Fail if any of the dependencies uses unpinned revision.')
1743 options, args = parser.parse_args(args)
1744
1745 options.nohooks = True
1746 client = GClient.LoadCurrentConfig(options)
1747
1748 # Only print progress if we're writing to a file. Otherwise, progress updates
1749 # could obscure intended output.
1750 code = client.RunOnDeps('flatten', args, progress=options.output_deps)
1751 if code != 0:
1752 return code
1753
1754 deps = {}
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02001755 deps_os = {}
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001756 hooks = []
1757 pre_deps_hooks = []
1758 unpinned_deps = {}
1759
1760 for solution in client.dependencies:
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02001761 _FlattenSolution(
1762 solution, deps, deps_os, hooks, pre_deps_hooks, unpinned_deps)
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001763
1764 if options.require_pinned_revisions and unpinned_deps:
1765 sys.stderr.write('The following dependencies are not pinned:\n')
1766 sys.stderr.write('\n'.join(sorted(unpinned_deps)))
1767 return 1
1768
1769 flattened_deps = '\n'.join(
Paweł Hajdan, Jr3c2aa832017-06-07 20:22:16 +02001770 _GNSettingsToLines(
1771 client.dependencies[0]._gn_args_file,
1772 client.dependencies[0]._gn_args) +
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001773 _DepsToLines(deps) +
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02001774 _DepsOsToLines(deps_os) +
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001775 _HooksToLines('hooks', hooks) +
1776 _HooksToLines('pre_deps_hooks', pre_deps_hooks) +
1777 [''] # Ensure newline at end of file.
1778 )
1779
1780 if options.output_deps:
1781 with open(options.output_deps, 'w') as f:
1782 f.write(flattened_deps)
1783 else:
1784 print(flattened_deps)
1785
1786 return 0
1787
1788
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02001789def _FlattenSolution(
1790 solution, deps, deps_os, hooks, pre_deps_hooks, unpinned_deps):
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001791 """Visits a solution in order to flatten it (see CMDflatten).
1792
1793 Arguments:
1794 solution (Dependency): one of top-level solutions in .gclient
1795
1796 Out-parameters:
1797 deps (dict of name -> Dependency): will be filled with all Dependency
1798 objects indexed by their name
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02001799 deps_os (dict of os name -> dep name -> Dependency): same as above,
1800 for OS-specific deps
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001801 hooks (list of (Dependency, hook)): will be filled with flattened hooks
1802 pre_deps_hooks (list of (Dependency, hook)): will be filled with flattened
1803 pre_deps_hooks
1804 unpinned_deps (dict of name -> Dependency): will be filled with unpinned
1805 deps
1806 """
1807 logging.debug('_FlattenSolution(%r)', solution)
1808
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02001809 _FlattenDep(solution, deps, deps_os, hooks, pre_deps_hooks, unpinned_deps)
1810 _FlattenRecurse(solution, deps, deps_os, hooks, pre_deps_hooks, unpinned_deps)
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001811
1812
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02001813def _FlattenDep(dep, deps, deps_os, hooks, pre_deps_hooks, unpinned_deps):
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001814 """Visits a dependency in order to flatten it (see CMDflatten).
1815
1816 Arguments:
1817 dep (Dependency): dependency to process
1818
1819 Out-parameters:
1820 deps (dict): will be filled with flattened deps
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02001821 deps_os (dict): will be filled with flattened deps_os
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001822 hooks (list): will be filled with flattened hooks
1823 pre_deps_hooks (list): will be filled with flattened pre_deps_hooks
1824 unpinned_deps (dict): will be filled with unpinned deps
1825 """
1826 logging.debug('_FlattenDep(%r)', dep)
1827
1828 _AddDep(dep, deps, unpinned_deps)
1829
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02001830 for dep_os, os_deps in dep.os_dependencies.iteritems():
1831 for os_dep in os_deps:
1832 deps_os.setdefault(dep_os, {})[os_dep.name] = os_dep
1833
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001834 deps_by_name = dict((d.name, d) for d in dep.dependencies)
1835 for recurse_dep_name in (dep.recursedeps or []):
1836 _FlattenRecurse(
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02001837 deps_by_name[recurse_dep_name], deps, deps_os, hooks, pre_deps_hooks,
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001838 unpinned_deps)
1839
1840 # TODO(phajdan.jr): also handle hooks_os.
1841 hooks.extend([(dep, hook) for hook in dep.deps_hooks])
Paweł Hajdan, Jrc9364392017-06-14 17:11:56 +02001842 pre_deps_hooks.extend([(dep, hook) for hook in dep.pre_deps_hooks])
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001843
1844
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02001845def _FlattenRecurse(dep, deps, deps_os, hooks, pre_deps_hooks, unpinned_deps):
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001846 """Helper for flatten that recurses into |dep|'s dependencies.
1847
1848 Arguments:
1849 dep (Dependency): dependency to process
1850
1851 Out-parameters:
1852 deps (dict): will be filled with flattened deps
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02001853 deps_os (dict): will be filled with flattened deps_os
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001854 hooks (list): will be filled with flattened hooks
1855 pre_deps_hooks (list): will be filled with flattened pre_deps_hooks
1856 unpinned_deps (dict): will be filled with unpinned deps
1857 """
1858 logging.debug('_FlattenRecurse(%r)', dep)
1859
1860 # TODO(phajdan.jr): also handle deps_os.
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +02001861 for sub_dep in dep.orig_dependencies:
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02001862 _FlattenDep(sub_dep, deps, deps_os, hooks, pre_deps_hooks, unpinned_deps)
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001863
1864
1865def _AddDep(dep, deps, unpinned_deps):
1866 """Helper to add a dependency to flattened lists.
1867
1868 Arguments:
1869 dep (Dependency): dependency to process
1870
1871 Out-parameters:
1872 deps (dict): will be filled with flattened deps
1873 unpinned_deps (dict): will be filled with unpinned deps
1874 """
1875 logging.debug('_AddDep(%r)', dep)
1876
1877 assert dep.name not in deps
1878 deps[dep.name] = dep
1879
1880 # Detect unpinned deps.
1881 _, revision = gclient_utils.SplitUrlRevision(dep.url)
1882 if not revision or not gclient_utils.IsGitSha(revision):
1883 unpinned_deps[dep.name] = dep
1884
1885
Paweł Hajdan, Jr3c2aa832017-06-07 20:22:16 +02001886def _GNSettingsToLines(gn_args_file, gn_args):
1887 s = []
1888 if gn_args_file:
1889 s.extend([
1890 'gclient_gn_args_file = "%s"' % gn_args_file,
1891 'gclient_gn_args = %r' % gn_args,
1892 ])
1893 return s
1894
1895
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001896def _DepsToLines(deps):
1897 """Converts |deps| dict to list of lines for output."""
1898 s = ['deps = {']
1899 for name, dep in sorted(deps.iteritems()):
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +02001900 condition_part = ([' "condition": "%s",' % dep.condition]
1901 if dep.condition else [])
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001902 s.extend([
1903 ' # %s' % dep.hierarchy(include_url=False),
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +02001904 ' "%s": {' % (name,),
1905 ' "url": "%s",' % (dep.url,),
1906 ] + condition_part + [
1907 ' },',
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001908 '',
1909 ])
1910 s.extend(['}', ''])
1911 return s
1912
1913
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02001914def _DepsOsToLines(deps_os):
1915 """Converts |deps_os| dict to list of lines for output."""
1916 s = ['deps_os = {']
1917 for dep_os, os_deps in sorted(deps_os.iteritems()):
1918 s.append(' "%s": {' % dep_os)
1919 s.extend([' %s' % l for l in _DepsToLines(os_deps)])
1920 s.extend([' },', ''])
1921 s.extend(['}', ''])
1922 return s
1923
1924
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001925def _HooksToLines(name, hooks):
1926 """Converts |hooks| list to list of lines for output."""
1927 s = ['%s = [' % name]
1928 for dep, hook in hooks:
1929 s.extend([
1930 ' # %s' % dep.hierarchy(include_url=False),
1931 ' {',
1932 ])
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +02001933 if hook.name is not None:
1934 s.append(' "name": "%s",' % hook.name)
1935 if hook.pattern is not None:
1936 s.append(' "pattern": "%s",' % hook.pattern)
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001937 s.extend(
Paweł Hajdan, Jrc9364392017-06-14 17:11:56 +02001938 # Hooks run in the parent directory of their dep.
1939 [' "cwd": "%s"' % os.path.normpath(os.path.dirname(dep.name))] +
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001940 [' "action": ['] +
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +02001941 [' "%s",' % arg for arg in hook.action] +
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001942 [' ]', ' },', '']
1943 )
1944 s.extend([']', ''])
1945 return s
1946
1947
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001948def CMDgrep(parser, args):
1949 """Greps through git repos managed by gclient.
1950
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001951 Runs 'git grep [args...]' for each module.
1952 """
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001953 # We can't use optparse because it will try to parse arguments sent
1954 # to git grep and throw an error. :-(
1955 if not args or re.match('(-h|--help)$', args[0]):
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001956 print(
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001957 'Usage: gclient grep [-j <N>] git-grep-args...\n\n'
1958 'Example: "gclient grep -j10 -A2 RefCountedBase" runs\n"git grep '
1959 '-A2 RefCountedBase" on each of gclient\'s git\nrepos with up to '
1960 '10 jobs.\n\nBonus: page output by appending "|& less -FRSX" to the'
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001961 ' end of your query.',
1962 file=sys.stderr)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001963 return 1
1964
1965 jobs_arg = ['--jobs=1']
1966 if re.match(r'(-j|--jobs=)\d+$', args[0]):
1967 jobs_arg, args = args[:1], args[1:]
1968 elif re.match(r'(-j|--jobs)$', args[0]):
1969 jobs_arg, args = args[:2], args[2:]
1970
1971 return CMDrecurse(
1972 parser,
1973 jobs_arg + ['--ignore', '--prepend-dir', '--no-progress', '--scm=git',
1974 'git', 'grep', '--null', '--color=Always'] + args)
davidbarr@chromium.org12f944e2012-03-01 02:18:31 +00001975
1976
stip@chromium.orga735da22015-04-29 23:18:20 +00001977def CMDroot(parser, args):
1978 """Outputs the solution root (or current dir if there isn't one)."""
1979 (options, args) = parser.parse_args(args)
1980 client = GClient.LoadCurrentConfig(options)
1981 if client:
1982 print(os.path.abspath(client.root_dir))
1983 else:
1984 print(os.path.abspath('.'))
1985
1986
agablea98a6cd2016-11-15 14:30:10 -08001987@subcommand.usage('[url]')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001988def CMDconfig(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001989 """Creates a .gclient file in the current directory.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001990
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001991 This specifies the configuration for further commands. After update/sync,
1992 top-level DEPS files in each module are read to determine dependent
1993 modules to operate on as well. If optional [url] parameter is
1994 provided, then configuration is read from a specified Subversion server
1995 URL.
1996 """
szager@chromium.orge2e03202012-07-31 18:05:16 +00001997 # We do a little dance with the --gclientfile option. 'gclient config' is the
1998 # only command where it's acceptable to have both '--gclientfile' and '--spec'
1999 # arguments. So, we temporarily stash any --gclientfile parameter into
2000 # options.output_config_file until after the (gclientfile xor spec) error
2001 # check.
2002 parser.remove_option('--gclientfile')
2003 parser.add_option('--gclientfile', dest='output_config_file',
2004 help='Specify an alternate .gclient file')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002005 parser.add_option('--name',
2006 help='overrides the default name for the solution')
nsylvain@google.comefc80932011-05-31 21:27:56 +00002007 parser.add_option('--deps-file', default='DEPS',
2008 help='overrides the default name for the DEPS file for the'
2009 'main solutions and all sub-dependencies')
smutae7ea312016-07-18 11:59:41 -07002010 parser.add_option('--unmanaged', action='store_true', default=False,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00002011 help='overrides the default behavior to make it possible '
smutae7ea312016-07-18 11:59:41 -07002012 'to have the main solution untouched by gclient '
2013 '(gclient will check out unmanaged dependencies but '
2014 'will never sync them)')
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00002015 parser.add_option('--cache-dir',
2016 help='(git only) Cache all git repos into this dir and do '
2017 'shared clones from the cache, instead of cloning '
2018 'directly from the remote. (experimental)')
szager@chromium.orge2e03202012-07-31 18:05:16 +00002019 parser.set_defaults(config_filename=None)
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002020 (options, args) = parser.parse_args(args)
szager@chromium.orge2e03202012-07-31 18:05:16 +00002021 if options.output_config_file:
2022 setattr(options, 'config_filename', getattr(options, 'output_config_file'))
maruel@chromium.org5fc2a332010-05-26 19:37:15 +00002023 if ((options.spec and args) or len(args) > 2 or
2024 (not options.spec and not args)):
2025 parser.error('Inconsistent arguments. Use either --spec or one or 2 args')
2026
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002027 client = GClient('.', options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002028 if options.spec:
2029 client.SetConfig(options.spec)
2030 else:
maruel@chromium.org1ab7ffc2009-06-03 17:21:37 +00002031 base_url = args[0].rstrip('/')
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00002032 if not options.name:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002033 name = base_url.split('/')[-1]
nsylvain@google.com12649ef2011-06-01 17:11:20 +00002034 if name.endswith('.git'):
2035 name = name[:-4]
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00002036 else:
2037 # specify an alternate relpath for the given URL.
2038 name = options.name
agable@chromium.orgf2214672015-10-27 21:02:48 +00002039 if not os.path.abspath(os.path.join(os.getcwd(), name)).startswith(
2040 os.getcwd()):
2041 parser.error('Do not pass a relative path for --name.')
2042 if any(x in ('..', '.', '/', '\\') for x in name.split(os.sep)):
2043 parser.error('Do not include relative path components in --name.')
2044
nsylvain@google.comefc80932011-05-31 21:27:56 +00002045 deps_file = options.deps_file
agablea98a6cd2016-11-15 14:30:10 -08002046 client.SetDefaultConfig(name, deps_file, base_url,
smutae7ea312016-07-18 11:59:41 -07002047 managed=not options.unmanaged,
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00002048 cache_dir=options.cache_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002049 client.SaveConfig()
maruel@chromium.org79692d62010-05-14 18:57:13 +00002050 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002051
2052
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002053@subcommand.epilog("""Example:
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002054 gclient pack > patch.txt
2055 generate simple patch for configured client and dependences
2056""")
2057def CMDpack(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002058 """Generates a patch which can be applied at the root of the tree.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00002059
agabled437d762016-10-17 09:35:11 -07002060 Internally, runs 'git diff' on each checked out module and
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002061 dependencies, and performs minimal postprocessing of the output. The
2062 resulting patch is printed to stdout and can be applied to a freshly
2063 checked out tree via 'patch -p0 < patchfile'.
2064 """
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002065 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2066 help='override deps for the specified (comma-separated) '
2067 'platform(s); \'all\' will process all deps_os '
2068 'references')
haitao.feng@intel.com306080c2012-05-04 13:11:29 +00002069 parser.remove_option('--jobs')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002070 (options, args) = parser.parse_args(args)
iannucci@chromium.org50395ea2013-04-04 04:47:42 +00002071 # Force jobs to 1 so the stdout is not annotated with the thread ids
haitao.feng@intel.com306080c2012-05-04 13:11:29 +00002072 options.jobs = 1
kbr@google.comab318592009-09-04 00:54:55 +00002073 client = GClient.LoadCurrentConfig(options)
2074 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002075 raise gclient_utils.Error('client not configured; see \'gclient config\'')
kbr@google.comab318592009-09-04 00:54:55 +00002076 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002077 client.PrintLocationAndContents()
kbr@google.comab318592009-09-04 00:54:55 +00002078 return client.RunOnDeps('pack', args)
2079
2080
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002081def CMDstatus(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002082 """Shows modification status for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002083 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2084 help='override deps for the specified (comma-separated) '
2085 'platform(s); \'all\' will process all deps_os '
2086 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002087 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002088 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002089 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002090 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002091 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002092 client.PrintLocationAndContents()
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002093 return client.RunOnDeps('status', args)
2094
2095
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002096@subcommand.epilog("""Examples:
maruel@chromium.org79692d62010-05-14 18:57:13 +00002097 gclient sync
2098 update files from SCM according to current configuration,
2099 *for modules which have changed since last update or sync*
2100 gclient sync --force
2101 update files from SCM according to current configuration, for
2102 all modules (useful for recovering files deleted from local copy)
2103 gclient sync --revision src@31000
2104 update src directory to r31000
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002105
2106JSON output format:
2107If the --output-json option is specified, the following document structure will
2108be emitted to the provided file. 'null' entries may occur for subprojects which
2109are present in the gclient solution, but were not processed (due to custom_deps,
2110os_deps, etc.)
2111
2112{
2113 "solutions" : {
2114 "<name>": { # <name> is the posix-normalized path to the solution.
agabled437d762016-10-17 09:35:11 -07002115 "revision": [<git id hex string>|null],
2116 "scm": ["git"|null],
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002117 }
2118 }
2119}
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002120""")
2121def CMDsync(parser, args):
2122 """Checkout/update all modules."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002123 parser.add_option('-f', '--force', action='store_true',
2124 help='force update even for unchanged modules')
2125 parser.add_option('-n', '--nohooks', action='store_true',
2126 help='don\'t run hooks after the update is complete')
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00002127 parser.add_option('-p', '--noprehooks', action='store_true',
2128 help='don\'t run pre-DEPS hooks', default=False)
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002129 parser.add_option('-r', '--revision', action='append',
2130 dest='revisions', metavar='REV', default=[],
2131 help='Enforces revision/hash for the solutions with the '
2132 'format src@rev. The src@ part is optional and can be '
2133 'skipped. -r can be used multiple times when .gclient '
2134 'has multiple solutions configured and will work even '
agablea98a6cd2016-11-15 14:30:10 -08002135 'if the src@ part is skipped.')
maruel@chromium.org794207e2013-03-08 15:29:43 +00002136 parser.add_option('--with_branch_heads', action='store_true',
2137 help='Clone git "branch_heads" refspecs in addition to '
2138 'the default refspecs. This adds about 1/2GB to a '
2139 'full checkout. (git only)')
szager@chromium.org8d3348f2014-08-19 22:49:16 +00002140 parser.add_option('--with_tags', action='store_true',
2141 help='Clone git tags in addition to the default refspecs.')
agable2697cd12016-06-28 10:23:53 -07002142 parser.add_option('-H', '--head', action='store_true',
agablea98a6cd2016-11-15 14:30:10 -08002143 help='DEPRECATED: only made sense with safesync urls.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002144 parser.add_option('-D', '--delete_unversioned_trees', action='store_true',
steveblock@chromium.org98e69452012-02-16 16:36:43 +00002145 help='Deletes from the working copy any dependencies that '
2146 'have been removed since the last sync, as long as '
2147 'there are no local modifications. When used with '
2148 '--force, such dependencies are removed even if they '
2149 'have local modifications. When used with --reset, '
2150 'all untracked directories are removed from the '
tnagel@chromium.orga2aaa632014-02-28 21:47:27 +00002151 'working copy, excluding those which are explicitly '
steveblock@chromium.org98e69452012-02-16 16:36:43 +00002152 'ignored in the repository.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002153 parser.add_option('-R', '--reset', action='store_true',
2154 help='resets any local changes before updating (git only)')
bauerb@chromium.org2aad1b22011-07-22 12:00:41 +00002155 parser.add_option('-M', '--merge', action='store_true',
2156 help='merge upstream changes instead of trying to '
2157 'fast-forward or rebase')
dnj@chromium.org5b23e872015-02-20 21:25:57 +00002158 parser.add_option('-A', '--auto_rebase', action='store_true',
2159 help='Automatically rebase repositories against local '
2160 'checkout during update (git only).')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002161 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2162 help='override deps for the specified (comma-separated) '
2163 'platform(s); \'all\' will process all deps_os '
2164 'references')
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +00002165 parser.add_option('--upstream', action='store_true',
2166 help='Make repo state match upstream branch.')
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002167 parser.add_option('--output-json',
2168 help='Output a json document to this path containing '
2169 'summary information about the sync.')
primiano@chromium.org5439ea52014-08-06 17:18:18 +00002170 parser.add_option('--no-history', action='store_true',
2171 help='GIT ONLY - Reduces the size/time of the checkout at '
2172 'the cost of no history. Requires Git 1.9+')
hinoka@chromium.org46b87412014-05-15 00:42:05 +00002173 parser.add_option('--shallow', action='store_true',
2174 help='GIT ONLY - Do a shallow clone into the cache dir. '
2175 'Requires Git 1.9+')
e.hakkinen@samsung.come8bc1aa2015-04-08 08:00:37 +00002176 parser.add_option('--no_bootstrap', '--no-bootstrap',
2177 action='store_true',
2178 help='Don\'t bootstrap from Google Storage.')
hinoka@chromium.org8a10f6d2014-06-23 18:38:57 +00002179 parser.add_option('--ignore_locks', action='store_true',
2180 help='GIT ONLY - Ignore cache locks.')
iannucci@chromium.org30a07982016-04-07 21:35:19 +00002181 parser.add_option('--break_repo_locks', action='store_true',
2182 help='GIT ONLY - Forcibly remove repo locks (e.g. '
2183 'index.lock). This should only be used if you know for '
2184 'certain that this invocation of gclient is the only '
2185 'thing operating on the git repos (e.g. on a bot).')
nodir@chromium.org5b48e482016-03-18 20:27:54 +00002186 parser.add_option('--lock_timeout', type='int', default=5000,
szager@chromium.orgdbb6f822016-02-02 22:59:30 +00002187 help='GIT ONLY - Deadline (in seconds) to wait for git '
nodir@chromium.org5b48e482016-03-18 20:27:54 +00002188 'cache lock to become available. Default is %default.')
agabled437d762016-10-17 09:35:11 -07002189 # TODO(agable): Remove these when the oldest CrOS release milestone is M56.
2190 parser.add_option('-t', '--transitive', action='store_true',
2191 help='DEPRECATED: This is a no-op.')
sdefresne69b1be12016-10-18 05:48:02 -07002192 parser.add_option('-m', '--manually_grab_svn_rev', action='store_true',
agabled437d762016-10-17 09:35:11 -07002193 help='DEPRECATED: This is a no-op.')
Paweł Hajdan, Jr7c7b5592017-05-23 15:06:05 +02002194 # TODO(phajdan.jr): Remove validation options once default (crbug/570091).
Paweł Hajdan, Jr694773d2017-05-29 16:06:23 +02002195 parser.add_option('--validate-syntax', action='store_true', default=True,
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +02002196 help='Validate the .gclient and DEPS syntax')
Paweł Hajdan, Jr7c7b5592017-05-23 15:06:05 +02002197 parser.add_option('--disable-syntax-validation', action='store_false',
2198 dest='validate_syntax',
2199 help='Disable validation of .gclient and DEPS syntax.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002200 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002201 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002202
2203 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002204 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002205
smutae7ea312016-07-18 11:59:41 -07002206 if options.revisions and options.head:
2207 # TODO(maruel): Make it a parser.error if it doesn't break any builder.
2208 print('Warning: you cannot use both --head and --revision')
2209
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002210 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002211 client.PrintLocationAndContents()
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002212 ret = client.RunOnDeps('update', args)
2213 if options.output_json:
2214 slns = {}
2215 for d in client.subtree(True):
2216 normed = d.name.replace('\\', '/').rstrip('/') + '/'
2217 slns[normed] = {
2218 'revision': d.got_revision,
2219 'scm': d.used_scm.name if d.used_scm else None,
hinoka@chromium.org17db9052014-05-10 01:11:29 +00002220 'url': str(d.url) if d.url else None,
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002221 }
2222 with open(options.output_json, 'wb') as f:
2223 json.dump({'solutions': slns}, f)
2224 return ret
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002225
2226
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002227CMDupdate = CMDsync
2228
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002229
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +02002230def CMDvalidate(parser, args):
2231 """Validates the .gclient and DEPS syntax."""
2232 options, args = parser.parse_args(args)
2233 options.validate_syntax = True
2234 client = GClient.LoadCurrentConfig(options)
2235 rv = client.RunOnDeps('validate', args)
2236 if rv == 0:
2237 print('validate: SUCCESS')
2238 else:
2239 print('validate: FAILURE')
2240 return rv
2241
2242
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002243def CMDdiff(parser, args):
2244 """Displays local diff for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002245 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2246 help='override deps for the specified (comma-separated) '
2247 'platform(s); \'all\' will process all deps_os '
2248 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002249 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002250 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002251 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002252 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002253 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002254 client.PrintLocationAndContents()
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002255 return client.RunOnDeps('diff', args)
2256
2257
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002258def CMDrevert(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002259 """Reverts all modifications in every dependencies.
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00002260
2261 That's the nuclear option to get back to a 'clean' state. It removes anything
agabled437d762016-10-17 09:35:11 -07002262 that shows up in git status."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002263 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2264 help='override deps for the specified (comma-separated) '
2265 'platform(s); \'all\' will process all deps_os '
2266 'references')
2267 parser.add_option('-n', '--nohooks', action='store_true',
2268 help='don\'t run hooks after the revert is complete')
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00002269 parser.add_option('-p', '--noprehooks', action='store_true',
2270 help='don\'t run pre-DEPS hooks', default=False)
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +00002271 parser.add_option('--upstream', action='store_true',
2272 help='Make repo state match upstream branch.')
iannucci@chromium.orgbf525dc2016-04-07 22:00:28 +00002273 parser.add_option('--break_repo_locks', action='store_true',
2274 help='GIT ONLY - Forcibly remove repo locks (e.g. '
2275 'index.lock). This should only be used if you know for '
2276 'certain that this invocation of gclient is the only '
2277 'thing operating on the git repos (e.g. on a bot).')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002278 (options, args) = parser.parse_args(args)
2279 # --force is implied.
2280 options.force = True
steveblock@chromium.org98e69452012-02-16 16:36:43 +00002281 options.reset = False
2282 options.delete_unversioned_trees = False
agablec903d732016-07-26 09:07:24 -07002283 options.merge = False
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002284 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002285 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002286 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002287 return client.RunOnDeps('revert', args)
2288
2289
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002290def CMDrunhooks(parser, args):
2291 """Runs hooks for files that have been modified in the local working copy."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002292 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2293 help='override deps for the specified (comma-separated) '
2294 'platform(s); \'all\' will process all deps_os '
2295 'references')
2296 parser.add_option('-f', '--force', action='store_true', default=True,
2297 help='Deprecated. No effect.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002298 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002299 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002300 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002301 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002302 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002303 client.PrintLocationAndContents()
maruel@chromium.org5df6a462009-08-28 18:52:26 +00002304 options.force = True
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002305 options.nohooks = False
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002306 return client.RunOnDeps('runhooks', args)
2307
2308
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002309def CMDrevinfo(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002310 """Outputs revision info mapping for the client and its dependencies.
maruel@chromium.org9eda4112010-06-11 18:56:10 +00002311
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002312 This allows the capture of an overall 'revision' for the source tree that
maruel@chromium.org9eda4112010-06-11 18:56:10 +00002313 can be used to reproduce the same tree in the future. It is only useful for
agabled437d762016-10-17 09:35:11 -07002314 'unpinned dependencies', i.e. DEPS/deps references without a git hash.
2315 A git branch name isn't 'pinned' since the actual commit can change.
maruel@chromium.org9eda4112010-06-11 18:56:10 +00002316 """
2317 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2318 help='override deps for the specified (comma-separated) '
2319 'platform(s); \'all\' will process all deps_os '
2320 'references')
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00002321 parser.add_option('-a', '--actual', action='store_true',
2322 help='gets the actual checked out revisions instead of the '
2323 'ones specified in the DEPS and .gclient files')
maruel@chromium.org9eda4112010-06-11 18:56:10 +00002324 parser.add_option('-s', '--snapshot', action='store_true',
2325 help='creates a snapshot .gclient file of the current '
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00002326 'version of all repositories to reproduce the tree, '
2327 'implies -a')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002328 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002329 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002330 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002331 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002332 client.PrintRevInfo()
maruel@chromium.org79692d62010-05-14 18:57:13 +00002333 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002334
2335
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00002336def CMDverify(parser, args):
2337 """Verifies the DEPS file deps are only from allowed_hosts."""
2338 (options, args) = parser.parse_args(args)
2339 client = GClient.LoadCurrentConfig(options)
2340 if not client:
2341 raise gclient_utils.Error('client not configured; see \'gclient config\'')
2342 client.RunOnDeps(None, [])
2343 # Look at each first-level dependency of this gclient only.
2344 for dep in client.dependencies:
2345 bad_deps = dep.findDepsFromNotAllowedHosts()
2346 if not bad_deps:
2347 continue
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002348 print("There are deps from not allowed hosts in file %s" % dep.deps_file)
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00002349 for bad_dep in bad_deps:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002350 print("\t%s at %s" % (bad_dep.name, bad_dep.url))
2351 print("allowed_hosts:", ', '.join(dep.allowed_hosts))
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00002352 sys.stdout.flush()
2353 raise gclient_utils.Error(
2354 'dependencies from disallowed hosts; check your DEPS file.')
2355 return 0
2356
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002357class OptionParser(optparse.OptionParser):
szager@chromium.orge2e03202012-07-31 18:05:16 +00002358 gclientfile_default = os.environ.get('GCLIENT_FILE', '.gclient')
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002359
2360 def __init__(self, **kwargs):
2361 optparse.OptionParser.__init__(
2362 self, version='%prog ' + __version__, **kwargs)
2363
2364 # Some arm boards have issues with parallel sync.
2365 if platform.machine().startswith('arm'):
2366 jobs = 1
2367 else:
2368 jobs = max(8, gclient_utils.NumLocalCpus())
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002369
2370 self.add_option(
2371 '-j', '--jobs', default=jobs, type='int',
2372 help='Specify how many SCM commands can run in parallel; defaults to '
tnagel@chromium.orga2aaa632014-02-28 21:47:27 +00002373 '%default on this machine')
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002374 self.add_option(
2375 '-v', '--verbose', action='count', default=0,
2376 help='Produces additional output for diagnostics. Can be used up to '
2377 'three times for more logging info.')
2378 self.add_option(
2379 '--gclientfile', dest='config_filename',
2380 help='Specify an alternate %s file' % self.gclientfile_default)
2381 self.add_option(
2382 '--spec',
2383 help='create a gclient file containing the provided string. Due to '
2384 'Cygwin/Python brokenness, it can\'t contain any newlines.')
2385 self.add_option(
2386 '--no-nag-max', default=False, action='store_true',
scottmg@chromium.orgf547c802013-09-27 17:55:26 +00002387 help='Ignored for backwards compatibility.')
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002388
2389 def parse_args(self, args=None, values=None):
2390 """Integrates standard options processing."""
2391 options, args = optparse.OptionParser.parse_args(self, args, values)
2392 levels = [logging.ERROR, logging.WARNING, logging.INFO, logging.DEBUG]
2393 logging.basicConfig(
2394 level=levels[min(options.verbose, len(levels) - 1)],
maruel@chromium.org0895b752011-08-26 20:40:33 +00002395 format='%(module)s(%(lineno)d) %(funcName)s:%(message)s')
szager@chromium.orge2e03202012-07-31 18:05:16 +00002396 if options.config_filename and options.spec:
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002397 self.error('Cannot specifiy both --gclientfile and --spec')
rdsmith@chromium.orgd9591f02014-02-05 19:28:20 +00002398 if (options.config_filename and
2399 options.config_filename != os.path.basename(options.config_filename)):
2400 self.error('--gclientfile target must be a filename, not a path')
szager@chromium.orge2e03202012-07-31 18:05:16 +00002401 if not options.config_filename:
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002402 options.config_filename = self.gclientfile_default
maruel@chromium.org0895b752011-08-26 20:40:33 +00002403 options.entries_filename = options.config_filename + '_entries'
2404 if options.jobs < 1:
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002405 self.error('--jobs must be 1 or higher')
maruel@chromium.org0895b752011-08-26 20:40:33 +00002406
2407 # These hacks need to die.
2408 if not hasattr(options, 'revisions'):
2409 # GClient.RunOnDeps expects it even if not applicable.
2410 options.revisions = []
smutae7ea312016-07-18 11:59:41 -07002411 if not hasattr(options, 'head'):
2412 options.head = None
maruel@chromium.org0895b752011-08-26 20:40:33 +00002413 if not hasattr(options, 'nohooks'):
2414 options.nohooks = True
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00002415 if not hasattr(options, 'noprehooks'):
2416 options.noprehooks = True
maruel@chromium.org0895b752011-08-26 20:40:33 +00002417 if not hasattr(options, 'deps_os'):
2418 options.deps_os = None
maruel@chromium.org0895b752011-08-26 20:40:33 +00002419 if not hasattr(options, 'force'):
2420 options.force = None
2421 return (options, args)
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002422
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002423
2424def disable_buffering():
2425 # Make stdout auto-flush so buildbot doesn't kill us during lengthy
2426 # operations. Python as a strong tendency to buffer sys.stdout.
2427 sys.stdout = gclient_utils.MakeFileAutoFlush(sys.stdout)
2428 # Make stdout annotated with the thread ids.
2429 sys.stdout = gclient_utils.MakeFileAnnotated(sys.stdout)
maruel@chromium.org0895b752011-08-26 20:40:33 +00002430
2431
sbc@chromium.org013731e2015-02-26 18:28:43 +00002432def main(argv):
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002433 """Doesn't parse the arguments here, just find the right subcommand to
2434 execute."""
maruel@chromium.org82798cb2012-02-23 18:16:12 +00002435 if sys.hexversion < 0x02060000:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002436 print(
maruel@chromium.org82798cb2012-02-23 18:16:12 +00002437 '\nYour python version %s is unsupported, please upgrade.\n' %
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002438 sys.version.split(' ', 1)[0],
2439 file=sys.stderr)
maruel@chromium.org82798cb2012-02-23 18:16:12 +00002440 return 2
bcwhite@chromium.org6683ab42013-02-11 16:13:47 +00002441 if not sys.executable:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002442 print(
2443 '\nPython cannot find the location of it\'s own executable.\n',
2444 file=sys.stderr)
bcwhite@chromium.org6683ab42013-02-11 16:13:47 +00002445 return 2
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002446 fix_encoding.fix_encoding()
2447 disable_buffering()
iannucci@chromium.org596cd5c2016-04-04 21:34:39 +00002448 setup_color.init()
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002449 dispatcher = subcommand.CommandDispatcher(__name__)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00002450 try:
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002451 return dispatcher.execute(OptionParser(), argv)
xusydoc@chromium.org2fd6c3f2013-05-03 21:57:55 +00002452 except KeyboardInterrupt:
2453 gclient_utils.GClientChildren.KillAllRemainingChildren()
2454 raise
vapier@chromium.orga81a56e2015-11-11 07:56:13 +00002455 except (gclient_utils.Error, subprocess2.CalledProcessError) as e:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002456 print('Error: %s' % str(e), file=sys.stderr)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00002457 return 1
borenet@google.com6a9b1682014-03-24 18:35:23 +00002458 finally:
2459 gclient_utils.PrintWarnings()
sbc@chromium.org013731e2015-02-26 18:28:43 +00002460 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002461
2462
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00002463if '__main__' == __name__:
sbc@chromium.org013731e2015-02-26 18:28:43 +00002464 try:
2465 sys.exit(main(sys.argv[1:]))
2466 except KeyboardInterrupt:
2467 sys.stderr.write('interrupted\n')
2468 sys.exit(1)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002469
2470# vim: ts=2:sw=2:tw=80:et: