blob: 97fa624f64f5b575c37f80900f1ee6952fc36852 [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
194 def __init__(self, action, pattern=None, name=None):
195 """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
201 """
202 self._action = gclient_utils.freeze(action)
203 self._pattern = pattern
204 self._name = name
205
206 @staticmethod
207 def from_dict(d):
208 """Creates a Hook instance from a dict like in the DEPS file."""
209 return Hook(d['action'], d.get('pattern'), d.get('name'))
210
211 @property
212 def action(self):
213 return self._action
214
215 @property
216 def pattern(self):
217 return self._pattern
218
219 @property
220 def name(self):
221 return self._name
222
223 def matches(self, file_list):
224 """Returns true if the pattern matches any of files in the list."""
225 if not self._pattern:
226 return True
227 pattern = re.compile(self._pattern)
228 return bool([f for f in file_list if pattern.search(f)])
229
230 def run(self, root):
231 """Executes the hook's command."""
232 cmd = list(self._action)
233 if cmd[0] == 'python':
234 # If the hook specified "python" as the first item, the action is a
235 # Python script. Run it by starting a new copy of the same
236 # interpreter.
237 cmd[0] = sys.executable
238 try:
239 start_time = time.time()
240 gclient_utils.CheckCallAndFilterAndHeader(
241 cmd, cwd=root, always=True)
242 except (gclient_utils.Error, subprocess2.CalledProcessError) as e:
243 # Use a discrete exit status code of 2 to indicate that a hook action
244 # failed. Users of this script may wish to treat hook action failures
245 # differently from VC failures.
246 print('Error: %s' % str(e), file=sys.stderr)
247 sys.exit(2)
248 finally:
249 elapsed_time = time.time() - start_time
250 if elapsed_time > 10:
251 print("Hook '%s' took %.2f secs" % (
252 gclient_utils.CommandToStr(cmd), elapsed_time))
253
254
maruel@chromium.org116704f2010-06-11 17:34:38 +0000255class GClientKeywords(object):
maruel@chromium.org116704f2010-06-11 17:34:38 +0000256 class VarImpl(object):
257 def __init__(self, custom_vars, local_scope):
258 self._custom_vars = custom_vars
259 self._local_scope = local_scope
260
261 def Lookup(self, var_name):
262 """Implements the Var syntax."""
263 if var_name in self._custom_vars:
264 return self._custom_vars[var_name]
265 elif var_name in self._local_scope.get("vars", {}):
266 return self._local_scope["vars"][var_name]
267 raise gclient_utils.Error("Var is not defined: %s" % var_name)
268
269
maruel@chromium.org064186c2011-09-27 23:53:33 +0000270class DependencySettings(GClientKeywords):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000271 """Immutable configuration settings."""
272 def __init__(
agablea98a6cd2016-11-15 14:30:10 -0800273 self, parent, url, managed, custom_deps, custom_vars,
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +0200274 custom_hooks, deps_file, should_process, relative,
275 condition, condition_value):
maruel@chromium.org064186c2011-09-27 23:53:33 +0000276 GClientKeywords.__init__(self)
277
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000278 # These are not mutable:
279 self._parent = parent
mmoss@chromium.org8f93f792014-08-26 23:24:09 +0000280 self._deps_file = deps_file
maruel@chromium.org064186c2011-09-27 23:53:33 +0000281 self._url = url
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +0200282 # The condition as string (or None). Useful to keep e.g. for flatten.
283 self._condition = condition
284 # Boolean value of the condition. If there's no condition, just True.
285 self._condition_value = condition_value
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000286 # 'managed' determines whether or not this dependency is synced/updated by
287 # gclient after gclient checks it out initially. The difference between
288 # 'managed' and 'should_process' is that the user specifies 'managed' via
smutae7ea312016-07-18 11:59:41 -0700289 # the --unmanaged command-line flag or a .gclient config, where
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000290 # 'should_process' is dynamically set by gclient if it goes over its
291 # recursion limit and controls gclient's behavior so it does not misbehave.
292 self._managed = managed
293 self._should_process = should_process
agabledce6ddc2016-09-08 10:02:16 -0700294 # If this is a recursed-upon sub-dependency, and the parent has
295 # use_relative_paths set, then this dependency should check out its own
296 # dependencies relative to that parent's path for this, rather than
297 # relative to the .gclient file.
298 self._relative = relative
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +0000299 # This is a mutable value which has the list of 'target_os' OSes listed in
300 # the current deps file.
301 self.local_target_os = None
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000302
303 # These are only set in .gclient and not in DEPS files.
304 self._custom_vars = custom_vars or {}
305 self._custom_deps = custom_deps or {}
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000306 self._custom_hooks = custom_hooks or []
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000307
maruel@chromium.org064186c2011-09-27 23:53:33 +0000308 # Post process the url to remove trailing slashes.
309 if isinstance(self._url, basestring):
310 # urls are sometime incorrectly written as proto://host/path/@rev. Replace
311 # it to proto://host/path@rev.
maruel@chromium.org064186c2011-09-27 23:53:33 +0000312 self._url = self._url.replace('/@', '@')
Paweł Hajdan, Jr7e9303b2017-05-23 14:38:27 +0200313 elif not isinstance(self._url, (None.__class__)):
maruel@chromium.org064186c2011-09-27 23:53:33 +0000314 raise gclient_utils.Error(
Paweł Hajdan, Jr7e9303b2017-05-23 14:38:27 +0200315 ('dependency url must be either string or None, '
316 'instead of %s') % self._url.__class__.__name__)
mmoss@chromium.orgd0b272b2013-01-30 23:55:33 +0000317 # Make any deps_file path platform-appropriate.
318 for sep in ['/', '\\']:
319 self._deps_file = self._deps_file.replace(sep, os.sep)
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000320
321 @property
322 def deps_file(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000323 return self._deps_file
324
325 @property
326 def managed(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000327 return self._managed
328
329 @property
330 def parent(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000331 return self._parent
332
333 @property
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000334 def root(self):
335 """Returns the root node, a GClient object."""
336 if not self.parent:
337 # This line is to signal pylint that it could be a GClient instance.
338 return self or GClient(None, None)
339 return self.parent.root
340
341 @property
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000342 def should_process(self):
343 """True if this dependency should be processed, i.e. checked out."""
344 return self._should_process
345
346 @property
347 def custom_vars(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000348 return self._custom_vars.copy()
349
350 @property
351 def custom_deps(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000352 return self._custom_deps.copy()
353
maruel@chromium.org064186c2011-09-27 23:53:33 +0000354 @property
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000355 def custom_hooks(self):
356 return self._custom_hooks[:]
357
358 @property
maruel@chromium.org064186c2011-09-27 23:53:33 +0000359 def url(self):
360 return self._url
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000361
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000362 @property
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +0200363 def condition(self):
364 return self._condition
365
366 @property
367 def condition_value(self):
368 return self._condition_value
369
370 @property
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +0000371 def target_os(self):
372 if self.local_target_os is not None:
373 return tuple(set(self.local_target_os).union(self.parent.target_os))
374 else:
375 return self.parent.target_os
376
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000377 def get_custom_deps(self, name, url):
378 """Returns a custom deps if applicable."""
379 if self.parent:
380 url = self.parent.get_custom_deps(name, url)
381 # None is a valid return value to disable a dependency.
382 return self.custom_deps.get(name, url)
383
maruel@chromium.org064186c2011-09-27 23:53:33 +0000384
385class Dependency(gclient_utils.WorkItem, DependencySettings):
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000386 """Object that represents a dependency checkout."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000387
agablea98a6cd2016-11-15 14:30:10 -0800388 def __init__(self, parent, name, url, managed, custom_deps,
agabledce6ddc2016-09-08 10:02:16 -0700389 custom_vars, custom_hooks, deps_file, should_process,
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +0200390 relative, condition, condition_value):
maruel@chromium.org6ca8bf82011-09-19 23:04:30 +0000391 gclient_utils.WorkItem.__init__(self, name)
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000392 DependencySettings.__init__(
agablea98a6cd2016-11-15 14:30:10 -0800393 self, parent, url, managed, custom_deps, custom_vars,
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +0200394 custom_hooks, deps_file, should_process, relative,
395 condition, condition_value)
maruel@chromium.org68988972011-09-20 14:11:42 +0000396
397 # This is in both .gclient and DEPS files:
maruel@chromium.org064186c2011-09-27 23:53:33 +0000398 self._deps_hooks = []
maruel@chromium.org68988972011-09-20 14:11:42 +0000399
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000400 self._pre_deps_hooks = []
401
maruel@chromium.org68988972011-09-20 14:11:42 +0000402 # Calculates properties:
maruel@chromium.org064186c2011-09-27 23:53:33 +0000403 self._parsed_url = None
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +0000404 self._dependencies = []
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200405 # Keep track of original values, before post-processing (e.g. deps_os).
406 self._orig_dependencies = []
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +0200407 self._vars = {}
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +0200408 self._os_dependencies = {}
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200409
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000410 # A cache of the files affected by the current operation, necessary for
411 # hooks.
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000412 self._file_list = []
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000413 # List of host names from which dependencies are allowed.
414 # Default is an empty set, meaning unspecified in DEPS file, and hence all
415 # hosts will be allowed. Non-empty set means whitelist of hosts.
416 # allowed_hosts var is scoped to its DEPS file, and so it isn't recursive.
417 self._allowed_hosts = frozenset()
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +0200418 # Spec for .gni output to write (if any).
419 self._gn_args_file = None
420 self._gn_args = []
maruel@chromium.org85c2a192010-07-22 21:14:43 +0000421 # If it is not set to True, the dependency wasn't processed for its child
422 # dependency, i.e. its DEPS wasn't read.
maruel@chromium.org064186c2011-09-27 23:53:33 +0000423 self._deps_parsed = False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000424 # This dependency has been processed, i.e. checked out
maruel@chromium.org064186c2011-09-27 23:53:33 +0000425 self._processed = False
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000426 # This dependency had its pre-DEPS hooks run
427 self._pre_deps_hooks_ran = False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000428 # This dependency had its hook run
maruel@chromium.org064186c2011-09-27 23:53:33 +0000429 self._hooks_ran = False
kustermann@google.coma692e8f2013-04-18 08:32:04 +0000430 # This is the scm used to checkout self.url. It may be used by dependencies
431 # to get the datetime of the revision we checked out.
432 self._used_scm = None
szager@chromium.org4ad264b2014-05-20 04:43:47 +0000433 self._used_revision = None
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +0000434 # The actual revision we ended up getting, or None if that information is
435 # unavailable
436 self._got_revision = None
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000437
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000438 # This is a mutable value that overrides the normal recursion limit for this
439 # dependency. It is read from the actual DEPS file so cannot be set on
440 # class instantiation.
441 self.recursion_override = None
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000442 # recursedeps is a mutable value that selectively overrides the default
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000443 # 'no recursion' setting on a dep-by-dep basis. It will replace
444 # recursion_override.
iannucci@chromium.orgafa11ac2016-05-04 22:17:34 +0000445 #
446 # It will be a dictionary of {deps_name: {"deps_file": depfile_name}} or
447 # None.
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000448 self.recursedeps = None
hinoka885e5b12016-06-08 14:40:09 -0700449 # This is inherited from WorkItem. We want the URL to be a resource.
450 if url and isinstance(url, basestring):
451 # The url is usually given to gclient either as https://blah@123
qyearsley12fa6ff2016-08-24 09:18:40 -0700452 # or just https://blah. The @123 portion is irrelevant.
hinoka885e5b12016-06-08 14:40:09 -0700453 self.resources.append(url.split('@')[0])
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000454
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000455 if not self.name and self.parent:
456 raise gclient_utils.Error('Dependency without name')
457
maruel@chromium.org470b5432011-10-11 18:18:19 +0000458 @property
459 def requirements(self):
460 """Calculate the list of requirements."""
461 requirements = set()
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000462 # self.parent is implicitly a requirement. This will be recursive by
463 # definition.
464 if self.parent and self.parent.name:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000465 requirements.add(self.parent.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000466
467 # For a tree with at least 2 levels*, the leaf node needs to depend
468 # on the level higher up in an orderly way.
469 # This becomes messy for >2 depth as the DEPS file format is a dictionary,
470 # thus unsorted, while the .gclient format is a list thus sorted.
471 #
472 # * _recursion_limit is hard coded 2 and there is no hope to change this
473 # value.
474 #
475 # Interestingly enough, the following condition only works in the case we
476 # want: self is a 2nd level node. 3nd level node wouldn't need this since
477 # they already have their parent as a requirement.
maruel@chromium.org470b5432011-10-11 18:18:19 +0000478 if self.parent and self.parent.parent and not self.parent.parent.parent:
479 requirements |= set(i.name for i in self.root.dependencies if i.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000480
maruel@chromium.org470b5432011-10-11 18:18:19 +0000481 if self.name:
482 requirements |= set(
483 obj.name for obj in self.root.subtree(False)
484 if (obj is not self
485 and obj.name and
486 self.name.startswith(posixpath.join(obj.name, ''))))
487 requirements = tuple(sorted(requirements))
488 logging.info('Dependency(%s).requirements = %s' % (self.name, requirements))
489 return requirements
490
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000491 @property
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000492 def try_recursedeps(self):
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000493 """Returns False if recursion_override is ever specified."""
494 if self.recursion_override is not None:
495 return False
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000496 return self.parent.try_recursedeps
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000497
498 @property
499 def recursion_limit(self):
500 """Returns > 0 if this dependency is not too recursed to be processed."""
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000501 # We continue to support the absence of recursedeps until tools and DEPS
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000502 # using recursion_override are updated.
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000503 if self.try_recursedeps and self.parent.recursedeps != None:
504 if self.name in self.parent.recursedeps:
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000505 return 1
506
507 if self.recursion_override is not None:
508 return self.recursion_override
509 return max(self.parent.recursion_limit - 1, 0)
510
maruel@chromium.org470b5432011-10-11 18:18:19 +0000511 def verify_validity(self):
512 """Verifies that this Dependency is fine to add as a child of another one.
513
514 Returns True if this entry should be added, False if it is a duplicate of
515 another entry.
516 """
517 logging.info('Dependency(%s).verify_validity()' % self.name)
518 if self.name in [s.name for s in self.parent.dependencies]:
519 raise gclient_utils.Error(
520 'The same name "%s" appears multiple times in the deps section' %
521 self.name)
522 if not self.should_process:
523 # Return early, no need to set requirements.
524 return True
525
526 # This require a full tree traversal with locks.
527 siblings = [d for d in self.root.subtree(False) if d.name == self.name]
528 for sibling in siblings:
maruel@chromium.orgb848d5b2012-10-10 23:25:50 +0000529 self_url = self.LateOverride(self.url)
530 sibling_url = sibling.LateOverride(sibling.url)
531 # Allow to have only one to be None or ''.
532 if self_url != sibling_url and bool(self_url) == bool(sibling_url):
maruel@chromium.org470b5432011-10-11 18:18:19 +0000533 raise gclient_utils.Error(
maruel@chromium.orgb848d5b2012-10-10 23:25:50 +0000534 ('Dependency %s specified more than once:\n'
535 ' %s [%s]\n'
536 'vs\n'
537 ' %s [%s]') % (
538 self.name,
539 sibling.hierarchy(),
540 sibling_url,
541 self.hierarchy(),
542 self_url))
maruel@chromium.org470b5432011-10-11 18:18:19 +0000543 # In theory we could keep it as a shadow of the other one. In
544 # practice, simply ignore it.
545 logging.warn('Won\'t process duplicate dependency %s' % sibling)
546 return False
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000547 return True
maruel@chromium.org064186c2011-09-27 23:53:33 +0000548
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000549 def LateOverride(self, url):
Paweł Hajdan, Jr7e9303b2017-05-23 14:38:27 +0200550 """Resolves the parsed url from url."""
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000551 assert self.parsed_url == None or not self.should_process, self.parsed_url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000552 parsed_url = self.get_custom_deps(self.name, url)
553 if parsed_url != url:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000554 logging.info(
555 'Dependency(%s).LateOverride(%s) -> %s' %
556 (self.name, url, parsed_url))
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000557 return parsed_url
558
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000559 if isinstance(url, basestring):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000560 parsed_url = urlparse.urlparse(url)
scr@chromium.orgf1eccaf2014-04-11 15:51:33 +0000561 if (not parsed_url[0] and
562 not re.match(r'^\w+\@[\w\.-]+\:[\w\/]+', parsed_url[2])):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000563 # A relative url. Fetch the real base.
564 path = parsed_url[2]
565 if not path.startswith('/'):
566 raise gclient_utils.Error(
567 'relative DEPS entry \'%s\' must begin with a slash' % url)
568 # Create a scm just to query the full url.
569 parent_url = self.parent.parsed_url
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000570 scm = gclient_scm.CreateSCM(
571 parent_url, self.root.root_dir, None, self.outbuf)
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000572 parsed_url = scm.FullUrlForRelativeUrl(url)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000573 else:
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000574 parsed_url = url
maruel@chromium.org470b5432011-10-11 18:18:19 +0000575 logging.info(
576 'Dependency(%s).LateOverride(%s) -> %s' %
577 (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000578 return parsed_url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000579
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000580 if url is None:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000581 logging.info(
582 'Dependency(%s).LateOverride(%s) -> %s' % (self.name, url, url))
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000583 return url
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000584
585 raise gclient_utils.Error('Unknown url type')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000586
bratell@opera.comed2b4fe2013-12-16 14:34:12 +0000587 @staticmethod
588 def MergeWithOsDeps(deps, deps_os, target_os_list):
589 """Returns a new "deps" structure that is the deps sent in updated
590 with information from deps_os (the deps_os section of the DEPS
591 file) that matches the list of target os."""
592 os_overrides = {}
593 for the_target_os in target_os_list:
594 the_target_os_deps = deps_os.get(the_target_os, {})
595 for os_dep_key, os_dep_value in the_target_os_deps.iteritems():
596 overrides = os_overrides.setdefault(os_dep_key, [])
597 overrides.append((the_target_os, os_dep_value))
598
599 # If any os didn't specify a value (we have fewer value entries
600 # than in the os list), then it wants to use the default value.
601 for os_dep_key, os_dep_value in os_overrides.iteritems():
602 if len(os_dep_value) != len(target_os_list):
qyearsley12fa6ff2016-08-24 09:18:40 -0700603 # Record the default value too so that we don't accidentally
bratell@opera.comed2b4fe2013-12-16 14:34:12 +0000604 # set it to None or miss a conflicting DEPS.
605 if os_dep_key in deps:
606 os_dep_value.append(('default', deps[os_dep_key]))
607
608 target_os_deps = {}
609 for os_dep_key, os_dep_value in os_overrides.iteritems():
610 # os_dep_value is a list of (os, value) pairs.
611 possible_values = set(x[1] for x in os_dep_value if x[1] is not None)
612 if not possible_values:
613 target_os_deps[os_dep_key] = None
614 else:
615 if len(possible_values) > 1:
616 # It would be possible to abort here but it would be
617 # unfortunate if we end up preventing any kind of checkout.
618 logging.error('Conflicting dependencies for %s: %s. (target_os=%s)',
619 os_dep_key, os_dep_value, target_os_list)
620 # Sorting to get the same result every time in case of conflicts.
621 target_os_deps[os_dep_key] = sorted(possible_values)[0]
622
623 new_deps = deps.copy()
624 new_deps.update(target_os_deps)
625 return new_deps
626
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200627 def _postprocess_deps(self, deps, rel_prefix):
628 """Performs post-processing of deps compared to what's in the DEPS file."""
Paweł Hajdan, Jr4426eaf2017-06-13 12:25:47 +0200629 # Make sure the dict is mutable, e.g. in case it's frozen.
630 deps = dict(deps)
631
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200632 # If a line is in custom_deps, but not in the solution, we want to append
633 # this line to the solution.
634 for d in self.custom_deps:
635 if d not in deps:
636 deps[d] = self.custom_deps[d]
637
638 if rel_prefix:
639 logging.warning('use_relative_paths enabled.')
640 rel_deps = {}
641 for d, url in deps.items():
642 # normpath is required to allow DEPS to use .. in their
643 # dependency local path.
644 rel_deps[os.path.normpath(os.path.join(rel_prefix, d))] = url
645 logging.warning('Updating deps by prepending %s.', rel_prefix)
646 deps = rel_deps
647
648 return deps
649
650 def _deps_to_objects(self, deps, use_relative_paths):
651 """Convert a deps dict to a dict of Dependency objects."""
652 deps_to_add = []
653 for name, dep_value in deps.iteritems():
654 should_process = self.recursion_limit and self.should_process
655 deps_file = self.deps_file
656 if self.recursedeps is not None:
657 ent = self.recursedeps.get(name)
658 if ent is not None:
659 deps_file = ent['deps_file']
660 if dep_value is None:
661 continue
662 condition = None
663 condition_value = True
664 if isinstance(dep_value, basestring):
665 url = dep_value
666 else:
667 # This should be guaranteed by schema checking in gclient_eval.
668 assert isinstance(dep_value, collections.Mapping)
669 url = dep_value['url']
670 condition = dep_value.get('condition')
671 if condition:
672 # TODO(phajdan.jr): should we also take custom vars into account?
673 condition_value = gclient_eval.EvaluateCondition(condition, self._vars)
674 should_process = should_process and condition_value
675 deps_to_add.append(Dependency(
676 self, name, url, None, None, self.custom_vars, None,
677 deps_file, should_process, use_relative_paths, condition,
678 condition_value))
679 deps_to_add.sort(key=lambda x: x.name)
680 return deps_to_add
681
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000682 def ParseDepsFile(self):
maruel@chromium.org271375b2010-06-23 19:17:38 +0000683 """Parses the DEPS file for this dependency."""
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000684 assert not self.deps_parsed
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000685 assert not self.dependencies
iannucci@chromium.org0c3f3052014-05-22 00:29:22 +0000686
687 deps_content = None
688 use_strict = False
cmp@chromium.org76ce73c2014-07-02 00:13:18 +0000689
690 # First try to locate the configured deps file. If it's missing, fallback
691 # to DEPS.
692 deps_files = [self.deps_file]
693 if 'DEPS' not in deps_files:
694 deps_files.append('DEPS')
695 for deps_file in deps_files:
696 filepath = os.path.join(self.root.root_dir, self.name, deps_file)
697 if os.path.isfile(filepath):
698 logging.info(
699 'ParseDepsFile(%s): %s file found at %s', self.name, deps_file,
700 filepath)
701 break
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000702 logging.info(
cmp@chromium.org76ce73c2014-07-02 00:13:18 +0000703 'ParseDepsFile(%s): No %s file found at %s', self.name, deps_file,
704 filepath)
705
706 if os.path.isfile(filepath):
maruel@chromium.org46304292010-10-28 11:42:00 +0000707 deps_content = gclient_utils.FileRead(filepath)
cmp@chromium.org76ce73c2014-07-02 00:13:18 +0000708 logging.debug('ParseDepsFile(%s) read:\n%s', self.name, deps_content)
iannucci@chromium.org0c3f3052014-05-22 00:29:22 +0000709 use_strict = 'use strict' in deps_content.splitlines()[0]
710
711 local_scope = {}
712 if deps_content:
713 # One thing is unintuitive, vars = {} must happen before Var() use.
714 var = self.VarImpl(self.custom_vars, local_scope)
715 if use_strict:
716 logging.info(
717 'ParseDepsFile(%s): Strict Mode Enabled', self.name)
718 global_scope = {
719 '__builtins__': {'None': None},
720 'Var': var.Lookup,
721 'deps_os': {},
722 }
723 else:
724 global_scope = {
iannucci@chromium.org0c3f3052014-05-22 00:29:22 +0000725 'Var': var.Lookup,
726 'deps_os': {},
727 }
maruel@chromium.org46304292010-10-28 11:42:00 +0000728 # Eval the content.
729 try:
Paweł Hajdan, Jrc485d5a2017-06-02 12:08:09 +0200730 if self._get_option('validate_syntax', False):
731 gclient_eval.Exec(deps_content, global_scope, local_scope, filepath)
732 else:
733 exec(deps_content, global_scope, local_scope)
vapier@chromium.orga81a56e2015-11-11 07:56:13 +0000734 except SyntaxError as e:
maruel@chromium.org46304292010-10-28 11:42:00 +0000735 gclient_utils.SyntaxErrorToError(filepath, e)
iannucci@chromium.org0c3f3052014-05-22 00:29:22 +0000736 if use_strict:
737 for key, val in local_scope.iteritems():
738 if not isinstance(val, (dict, list, tuple, str)):
739 raise gclient_utils.Error(
740 'ParseDepsFile(%s): Strict mode disallows %r -> %r' %
741 (self.name, key, val))
742
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000743 if 'allowed_hosts' in local_scope:
744 try:
745 self._allowed_hosts = frozenset(local_scope.get('allowed_hosts'))
746 except TypeError: # raised if non-iterable
747 pass
748 if not self._allowed_hosts:
749 logging.warning("allowed_hosts is specified but empty %s",
750 self._allowed_hosts)
751 raise gclient_utils.Error(
752 'ParseDepsFile(%s): allowed_hosts must be absent '
753 'or a non-empty iterable' % self.name)
754
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +0200755 self._gn_args_file = local_scope.get('gclient_gn_args_file')
756 self._gn_args = local_scope.get('gclient_gn_args', [])
757
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200758 # Since we heavily post-process things, freeze ones which should
759 # reflect original state of DEPS.
760 self._vars = gclient_utils.freeze(local_scope.get('vars', {}))
761
762 # If use_relative_paths is set in the DEPS file, regenerate
763 # the dictionary using paths relative to the directory containing
764 # the DEPS file. Also update recursedeps if use_relative_paths is
765 # enabled.
766 # If the deps file doesn't set use_relative_paths, but the parent did
767 # (and therefore set self.relative on this Dependency object), then we
768 # want to modify the deps and recursedeps by prepending the parent
769 # directory of this dependency.
770 use_relative_paths = local_scope.get('use_relative_paths', False)
771 rel_prefix = None
772 if use_relative_paths:
773 rel_prefix = self.name
774 elif self._relative:
775 rel_prefix = os.path.dirname(self.name)
776
777 deps = local_scope.get('deps', {})
778 orig_deps = gclient_utils.freeze(deps)
779 if 'recursion' in local_scope:
780 self.recursion_override = local_scope.get('recursion')
781 logging.warning(
782 'Setting %s recursion to %d.', self.name, self.recursion_limit)
783 self.recursedeps = None
784 if 'recursedeps' in local_scope:
785 self.recursedeps = {}
786 for ent in local_scope['recursedeps']:
787 if isinstance(ent, basestring):
788 self.recursedeps[ent] = {"deps_file": self.deps_file}
789 else: # (depname, depsfilename)
790 self.recursedeps[ent[0]] = {"deps_file": ent[1]}
791 logging.warning('Found recursedeps %r.', repr(self.recursedeps))
792
793 if rel_prefix:
794 logging.warning('Updating recursedeps by prepending %s.', rel_prefix)
795 rel_deps = {}
796 for depname, options in self.recursedeps.iteritems():
797 rel_deps[
798 os.path.normpath(os.path.join(rel_prefix, depname))] = options
799 self.recursedeps = rel_deps
800
801 # If present, save 'target_os' in the local_target_os property.
802 if 'target_os' in local_scope:
803 self.local_target_os = local_scope['target_os']
804 # load os specific dependencies if defined. these dependencies may
805 # override or extend the values defined by the 'deps' member.
806 target_os_list = self.target_os
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +0200807 if 'deps_os' in local_scope:
808 for dep_os, os_deps in local_scope['deps_os'].iteritems():
809 self._os_dependencies[dep_os] = self._deps_to_objects(
810 self._postprocess_deps(os_deps, rel_prefix), use_relative_paths)
811 if target_os_list:
812 deps = self.MergeWithOsDeps(
813 deps, local_scope['deps_os'], target_os_list)
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200814
815 deps_to_add = self._deps_to_objects(
816 self._postprocess_deps(deps, rel_prefix), use_relative_paths)
817 orig_deps_to_add = self._deps_to_objects(
818 self._postprocess_deps(orig_deps, rel_prefix), use_relative_paths)
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000819
820 # override named sets of hooks by the custom hooks
821 hooks_to_run = []
822 hook_names_to_suppress = [c.get('name', '') for c in self.custom_hooks]
823 for hook in local_scope.get('hooks', []):
824 if hook.get('name', '') not in hook_names_to_suppress:
825 hooks_to_run.append(hook)
Scott Grahamc4826742017-05-11 16:59:23 -0700826 if 'hooks_os' in local_scope and target_os_list:
827 hooks_os = local_scope['hooks_os']
828 # Specifically append these to ensure that hooks_os run after hooks.
829 for the_target_os in target_os_list:
830 the_target_os_hooks = hooks_os.get(the_target_os, [])
831 hooks_to_run.extend(the_target_os_hooks)
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000832
833 # add the replacements and any additions
834 for hook in self.custom_hooks:
835 if 'action' in hook:
836 hooks_to_run.append(hook)
837
Dirk Prankeda3a29e2017-02-27 15:29:36 -0800838 if self.recursion_limit:
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200839 self._pre_deps_hooks = [Hook.from_dict(hook) for hook in
Dirk Prankeda3a29e2017-02-27 15:29:36 -0800840 local_scope.get('pre_deps_hooks', [])]
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000841
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200842 self.add_dependencies_and_close(
843 deps_to_add, hooks_to_run, orig_deps_to_add=orig_deps_to_add)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000844 logging.info('ParseDepsFile(%s) done' % self.name)
845
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +0200846 def _get_option(self, attr, default):
847 obj = self
848 while not hasattr(obj, '_options'):
849 obj = obj.parent
850 return getattr(obj._options, attr, default)
851
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200852 def add_dependencies_and_close(
853 self, deps_to_add, hooks, orig_deps_to_add=None):
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000854 """Adds the dependencies, hooks and mark the parsing as done."""
maruel@chromium.orgb9be0652011-10-14 18:05:40 +0000855 for dep in deps_to_add:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000856 if dep.verify_validity():
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000857 self.add_dependency(dep)
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200858 for dep in (orig_deps_to_add or deps_to_add):
859 self.add_orig_dependency(dep)
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200860 self._mark_as_parsed([Hook.from_dict(h) for h in hooks])
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000861
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000862 def findDepsFromNotAllowedHosts(self):
863 """Returns a list of depenecies from not allowed hosts.
864
865 If allowed_hosts is not set, allows all hosts and returns empty list.
866 """
867 if not self._allowed_hosts:
868 return []
869 bad_deps = []
870 for dep in self._dependencies:
szager@chromium.orgbd772dd2014-11-05 18:43:08 +0000871 # Don't enforce this for custom_deps.
872 if dep.name in self._custom_deps:
873 continue
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000874 if isinstance(dep.url, basestring):
875 parsed_url = urlparse.urlparse(dep.url)
876 if parsed_url.netloc and parsed_url.netloc not in self._allowed_hosts:
877 bad_deps.append(dep)
878 return bad_deps
879
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +0000880 # Arguments number differs from overridden method
Quinten Yearsleyb2cc4a92016-12-15 13:53:26 -0800881 # pylint: disable=arguments-differ
maruel@chromium.org3742c842010-09-09 19:27:14 +0000882 def run(self, revision_overrides, command, args, work_queue, options):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000883 """Runs |command| then parse the DEPS file."""
maruel@chromium.org470b5432011-10-11 18:18:19 +0000884 logging.info('Dependency(%s).run()' % self.name)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000885 assert self._file_list == []
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000886 if not self.should_process:
887 return
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000888 # When running runhooks, there's no need to consult the SCM.
889 # All known hooks are expected to run unconditionally regardless of working
890 # copy state, so skip the SCM status check.
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +0200891 run_scm = command not in (
892 'flatten', 'runhooks', 'recurse', 'validate', None)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000893 parsed_url = self.LateOverride(self.url)
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000894 file_list = [] if not options.nohooks else None
szager@chromium.org3a3608d2014-10-22 21:13:52 +0000895 revision_override = revision_overrides.pop(self.name, None)
Dave Tubbda9712017-06-01 15:10:53 -0700896 if not revision_override and parsed_url:
897 revision_override = revision_overrides.get(parsed_url.split('@')[0], None)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000898 if run_scm and parsed_url:
agabled437d762016-10-17 09:35:11 -0700899 # Create a shallow copy to mutate revision.
900 options = copy.copy(options)
901 options.revision = revision_override
902 self._used_revision = options.revision
903 self._used_scm = gclient_scm.CreateSCM(
904 parsed_url, self.root.root_dir, self.name, self.outbuf,
905 out_cb=work_queue.out_cb)
906 self._got_revision = self._used_scm.RunCommand(command, options, args,
907 file_list)
908 if file_list:
909 file_list = [os.path.join(self.name, f.strip()) for f in file_list]
maruel@chromium.org68988972011-09-20 14:11:42 +0000910
911 # TODO(phajdan.jr): We should know exactly when the paths are absolute.
912 # Convert all absolute paths to relative.
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000913 for i in range(len(file_list or [])):
maruel@chromium.org68988972011-09-20 14:11:42 +0000914 # It depends on the command being executed (like runhooks vs sync).
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000915 if not os.path.isabs(file_list[i]):
maruel@chromium.org68988972011-09-20 14:11:42 +0000916 continue
917 prefix = os.path.commonprefix(
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000918 [self.root.root_dir.lower(), file_list[i].lower()])
919 file_list[i] = file_list[i][len(prefix):]
maruel@chromium.org68988972011-09-20 14:11:42 +0000920 # Strip any leading path separators.
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000921 while file_list[i].startswith(('\\', '/')):
922 file_list[i] = file_list[i][1:]
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000923
924 # Always parse the DEPS file.
925 self.ParseDepsFile()
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +0200926 if self._gn_args_file and command == 'update':
927 self.WriteGNArgsFile()
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000928 self._run_is_done(file_list or [], parsed_url)
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000929 if command in ('update', 'revert') and not options.noprehooks:
930 self.RunPreDepsHooks()
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000931
932 if self.recursion_limit:
933 # Parse the dependencies of this dependency.
934 for s in self.dependencies:
935 work_queue.enqueue(s)
936
937 if command == 'recurse':
agabled437d762016-10-17 09:35:11 -0700938 # Skip file only checkout.
939 scm = gclient_scm.GetScmName(parsed_url)
940 if not options.scm or scm in options.scm:
941 cwd = os.path.normpath(os.path.join(self.root.root_dir, self.name))
942 # Pass in the SCM type as an env variable. Make sure we don't put
943 # unicode strings in the environment.
944 env = os.environ.copy()
945 if scm:
946 env['GCLIENT_SCM'] = str(scm)
947 if parsed_url:
948 env['GCLIENT_URL'] = str(parsed_url)
949 env['GCLIENT_DEP_PATH'] = str(self.name)
950 if options.prepend_dir and scm == 'git':
951 print_stdout = False
952 def filter_fn(line):
953 """Git-specific path marshaling. It is optimized for git-grep."""
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000954
agabled437d762016-10-17 09:35:11 -0700955 def mod_path(git_pathspec):
956 match = re.match('^(\\S+?:)?([^\0]+)$', git_pathspec)
957 modified_path = os.path.join(self.name, match.group(2))
958 branch = match.group(1) or ''
959 return '%s%s' % (branch, modified_path)
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000960
agabled437d762016-10-17 09:35:11 -0700961 match = re.match('^Binary file ([^\0]+) matches$', line)
962 if match:
963 print('Binary file %s matches\n' % mod_path(match.group(1)))
964 return
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000965
agabled437d762016-10-17 09:35:11 -0700966 items = line.split('\0')
967 if len(items) == 2 and items[1]:
968 print('%s : %s' % (mod_path(items[0]), items[1]))
969 elif len(items) >= 2:
970 # Multiple null bytes or a single trailing null byte indicate
971 # git is likely displaying filenames only (such as with -l)
972 print('\n'.join(mod_path(path) for path in items if path))
973 else:
974 print(line)
975 else:
976 print_stdout = True
977 filter_fn = None
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000978
agabled437d762016-10-17 09:35:11 -0700979 if parsed_url is None:
980 print('Skipped omitted dependency %s' % cwd, file=sys.stderr)
981 elif os.path.isdir(cwd):
982 try:
983 gclient_utils.CheckCallAndFilter(
984 args, cwd=cwd, env=env, print_stdout=print_stdout,
985 filter_fn=filter_fn,
986 )
987 except subprocess2.CalledProcessError:
988 if not options.ignore:
989 raise
990 else:
991 print('Skipped missing %s' % cwd, file=sys.stderr)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000992
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +0200993 def WriteGNArgsFile(self):
994 lines = ['# Generated from %r' % self.deps_file]
995 for arg in self._gn_args:
996 lines.append('%s = %s' % (arg, ToGNString(self._vars[arg])))
997 with open(os.path.join(self.root.root_dir, self._gn_args_file), 'w') as f:
998 f.write('\n'.join(lines))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000999
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001000 @gclient_utils.lockedmethod
1001 def _run_is_done(self, file_list, parsed_url):
1002 # Both these are kept for hooks that are run as a separate tree traversal.
1003 self._file_list = file_list
1004 self._parsed_url = parsed_url
1005 self._processed = True
1006
szager@google.comb9a78d32012-03-13 18:46:21 +00001007 def GetHooks(self, options):
1008 """Evaluates all hooks, and return them in a flat list.
1009
1010 RunOnDeps() must have been called before to load the DEPS.
1011 """
1012 result = []
maruel@chromium.org68988972011-09-20 14:11:42 +00001013 if not self.should_process or not self.recursion_limit:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001014 # Don't run the hook when it is above recursion_limit.
szager@google.comb9a78d32012-03-13 18:46:21 +00001015 return result
maruel@chromium.orgdc7445d2010-07-09 21:05:29 +00001016 # If "--force" was specified, run all hooks regardless of what files have
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001017 # changed.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001018 if self.deps_hooks:
agabled437d762016-10-17 09:35:11 -07001019 # TODO(maruel): If the user is using git, then we don't know
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001020 # what files have changed so we always run all hooks. It'd be nice to fix
1021 # that.
1022 if (options.force or
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001023 gclient_scm.GetScmName(self.parsed_url) in ('git', None) or
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001024 os.path.isdir(os.path.join(self.root.root_dir, self.name, '.git'))):
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +02001025 result.extend(self.deps_hooks)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001026 else:
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +02001027 for hook in self.deps_hooks:
1028 if hook.matches(self.file_list_and_children):
1029 result.append(hook)
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001030 for s in self.dependencies:
szager@google.comb9a78d32012-03-13 18:46:21 +00001031 result.extend(s.GetHooks(options))
1032 return result
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001033
szager@google.comb9a78d32012-03-13 18:46:21 +00001034 def RunHooksRecursively(self, options):
1035 assert self.hooks_ran == False
maruel@chromium.org064186c2011-09-27 23:53:33 +00001036 self._hooks_ran = True
szager@google.comb9a78d32012-03-13 18:46:21 +00001037 for hook in self.GetHooks(options):
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +02001038 hook.run(self.root.root_dir)
maruel@chromium.orgeaf61062010-07-07 18:42:39 +00001039
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001040 def RunPreDepsHooks(self):
1041 assert self.processed
1042 assert self.deps_parsed
1043 assert not self.pre_deps_hooks_ran
1044 assert not self.hooks_ran
1045 for s in self.dependencies:
1046 assert not s.processed
1047 self._pre_deps_hooks_ran = True
1048 for hook in self.pre_deps_hooks:
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +02001049 hook.run(self.root.root_dir)
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001050
1051
maruel@chromium.org0d812442010-08-10 12:41:08 +00001052 def subtree(self, include_all):
maruel@chromium.orgad3287e2011-10-03 19:15:10 +00001053 """Breadth first recursion excluding root node."""
maruel@chromium.orgf13a4182011-09-22 00:26:15 +00001054 dependencies = self.dependencies
1055 for d in dependencies:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001056 if d.should_process or include_all:
maruel@chromium.orgad3287e2011-10-03 19:15:10 +00001057 yield d
maruel@chromium.orgf13a4182011-09-22 00:26:15 +00001058 for d in dependencies:
maruel@chromium.orgad3287e2011-10-03 19:15:10 +00001059 for i in d.subtree(include_all):
1060 yield i
1061
1062 def depth_first_tree(self):
1063 """Depth-first recursion including the root node."""
1064 yield self
1065 for i in self.dependencies:
1066 for j in i.depth_first_tree():
1067 if j.should_process:
1068 yield j
maruel@chromium.orgc57e4f22010-07-22 21:37:46 +00001069
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001070 @gclient_utils.lockedmethod
1071 def add_dependency(self, new_dep):
1072 self._dependencies.append(new_dep)
1073
1074 @gclient_utils.lockedmethod
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +02001075 def add_orig_dependency(self, new_dep):
1076 self._orig_dependencies.append(new_dep)
1077
1078 @gclient_utils.lockedmethod
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001079 def _mark_as_parsed(self, new_hooks):
1080 self._deps_hooks.extend(new_hooks)
1081 self._deps_parsed = True
1082
maruel@chromium.org68988972011-09-20 14:11:42 +00001083 @property
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001084 @gclient_utils.lockedmethod
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +00001085 def dependencies(self):
1086 return tuple(self._dependencies)
1087
1088 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001089 @gclient_utils.lockedmethod
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +02001090 def orig_dependencies(self):
1091 return tuple(self._orig_dependencies)
1092
1093 @property
1094 @gclient_utils.lockedmethod
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02001095 def os_dependencies(self):
1096 return dict(self._os_dependencies)
1097
1098 @property
1099 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001100 def deps_hooks(self):
1101 return tuple(self._deps_hooks)
1102
1103 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001104 @gclient_utils.lockedmethod
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001105 def pre_deps_hooks(self):
1106 return tuple(self._pre_deps_hooks)
1107
1108 @property
1109 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001110 def parsed_url(self):
1111 return self._parsed_url
1112
1113 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001114 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001115 def deps_parsed(self):
maruel@chromium.org3223edd2011-10-10 23:17:39 +00001116 """This is purely for debugging purposes. It's not used anywhere."""
maruel@chromium.org064186c2011-09-27 23:53:33 +00001117 return self._deps_parsed
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 processed(self):
1122 return self._processed
1123
1124 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001125 @gclient_utils.lockedmethod
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001126 def pre_deps_hooks_ran(self):
1127 return self._pre_deps_hooks_ran
1128
1129 @property
1130 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001131 def hooks_ran(self):
1132 return self._hooks_ran
1133
1134 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001135 @gclient_utils.lockedmethod
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00001136 def allowed_hosts(self):
1137 return self._allowed_hosts
1138
1139 @property
1140 @gclient_utils.lockedmethod
maruel@chromium.org861fd0f2010-07-23 03:05:05 +00001141 def file_list(self):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001142 return tuple(self._file_list)
1143
1144 @property
kustermann@google.coma692e8f2013-04-18 08:32:04 +00001145 def used_scm(self):
1146 """SCMWrapper instance for this dependency or None if not processed yet."""
1147 return self._used_scm
1148
1149 @property
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00001150 @gclient_utils.lockedmethod
1151 def got_revision(self):
1152 return self._got_revision
1153
1154 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001155 def file_list_and_children(self):
1156 result = list(self.file_list)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +00001157 for d in self.dependencies:
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001158 result.extend(d.file_list_and_children)
maruel@chromium.org68988972011-09-20 14:11:42 +00001159 return tuple(result)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +00001160
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001161 def __str__(self):
1162 out = []
agablea98a6cd2016-11-15 14:30:10 -08001163 for i in ('name', 'url', 'parsed_url', 'custom_deps',
maruel@chromium.org3c74bc92011-09-15 19:17:21 +00001164 'custom_vars', 'deps_hooks', 'file_list', 'should_process',
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00001165 'processed', 'hooks_ran', 'deps_parsed', 'requirements',
1166 'allowed_hosts'):
maruel@chromium.org3c74bc92011-09-15 19:17:21 +00001167 # First try the native property if it exists.
1168 if hasattr(self, '_' + i):
1169 value = getattr(self, '_' + i, False)
1170 else:
1171 value = getattr(self, i, False)
1172 if value:
1173 out.append('%s: %s' % (i, value))
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001174
1175 for d in self.dependencies:
1176 out.extend([' ' + x for x in str(d).splitlines()])
1177 out.append('')
1178 return '\n'.join(out)
1179
1180 def __repr__(self):
1181 return '%s: %s' % (self.name, self.url)
1182
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001183 def hierarchy(self, include_url=True):
maruel@chromium.orgbc2d2f92010-07-22 21:26:48 +00001184 """Returns a human-readable hierarchical reference to a Dependency."""
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001185 def format_name(d):
1186 if include_url:
1187 return '%s(%s)' % (d.name, d.url)
1188 return d.name
1189 out = format_name(self)
maruel@chromium.orgbffb9042010-07-22 20:59:36 +00001190 i = self.parent
1191 while i and i.name:
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001192 out = '%s -> %s' % (format_name(i), out)
maruel@chromium.orgbffb9042010-07-22 20:59:36 +00001193 i = i.parent
1194 return out
1195
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001196
1197class GClient(Dependency):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001198 """Object that represent a gclient checkout. A tree of Dependency(), one per
1199 solution or DEPS entry."""
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001200
1201 DEPS_OS_CHOICES = {
1202 "win32": "win",
1203 "win": "win",
1204 "cygwin": "win",
1205 "darwin": "mac",
1206 "mac": "mac",
1207 "unix": "unix",
1208 "linux": "unix",
1209 "linux2": "unix",
maruel@chromium.org244e3442011-06-12 15:20:55 +00001210 "linux3": "unix",
szager@chromium.orgf8c95cd2012-06-01 22:26:52 +00001211 "android": "android",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001212 }
1213
1214 DEFAULT_CLIENT_FILE_TEXT = ("""\
1215solutions = [
smutae7ea312016-07-18 11:59:41 -07001216 { "name" : "%(solution_name)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001217 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +00001218 "deps_file" : "%(deps_file)s",
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001219 "managed" : %(managed)s,
smutae7ea312016-07-18 11:59:41 -07001220 "custom_deps" : {
1221 },
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001222 },
1223]
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001224cache_dir = %(cache_dir)r
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001225""")
1226
1227 DEFAULT_SNAPSHOT_SOLUTION_TEXT = ("""\
smutae7ea312016-07-18 11:59:41 -07001228 { "name" : "%(solution_name)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001229 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +00001230 "deps_file" : "%(deps_file)s",
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001231 "managed" : %(managed)s,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001232 "custom_deps" : {
smutae7ea312016-07-18 11:59:41 -07001233%(solution_deps)s },
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001234 },
1235""")
1236
1237 DEFAULT_SNAPSHOT_FILE_TEXT = ("""\
1238# Snapshot generated with gclient revinfo --snapshot
1239solutions = [
maruel@chromium.org73e21142010-07-05 13:32:01 +00001240%(solution_list)s]
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001241""")
1242
1243 def __init__(self, root_dir, options):
maruel@chromium.org0d812442010-08-10 12:41:08 +00001244 # Do not change previous behavior. Only solution level and immediate DEPS
1245 # are processed.
1246 self._recursion_limit = 2
agablea98a6cd2016-11-15 14:30:10 -08001247 Dependency.__init__(self, None, None, None, True, None, None, None,
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +02001248 'unused', True, None, None, True)
maruel@chromium.org0d425922010-06-21 19:22:24 +00001249 self._options = options
maruel@chromium.org271375b2010-06-23 19:17:38 +00001250 if options.deps_os:
1251 enforced_os = options.deps_os.split(',')
1252 else:
1253 enforced_os = [self.DEPS_OS_CHOICES.get(sys.platform, 'unix')]
1254 if 'all' in enforced_os:
1255 enforced_os = self.DEPS_OS_CHOICES.itervalues()
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001256 self._enforced_os = tuple(set(enforced_os))
maruel@chromium.org271375b2010-06-23 19:17:38 +00001257 self._root_dir = root_dir
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001258 self.config_content = None
1259
borenet@google.com88d10082014-03-21 17:24:48 +00001260 def _CheckConfig(self):
1261 """Verify that the config matches the state of the existing checked-out
1262 solutions."""
1263 for dep in self.dependencies:
1264 if dep.managed and dep.url:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001265 scm = gclient_scm.CreateSCM(
1266 dep.url, self.root_dir, dep.name, self.outbuf)
smut@google.comd33eab32014-07-07 19:35:18 +00001267 actual_url = scm.GetActualRemoteURL(self._options)
borenet@google.com4e9be262014-04-08 19:40:30 +00001268 if actual_url and not scm.DoesRemoteURLMatch(self._options):
levarum@chromium.org27a6f9a2016-05-28 00:21:49 +00001269 mirror = scm.GetCacheMirror()
1270 if mirror:
1271 mirror_string = '%s (exists=%s)' % (mirror.mirror_path,
1272 mirror.exists())
1273 else:
1274 mirror_string = 'not used'
borenet@google.com0a427372014-04-02 19:12:13 +00001275 raise gclient_utils.Error('''
borenet@google.com88d10082014-03-21 17:24:48 +00001276Your .gclient file seems to be broken. The requested URL is different from what
borenet@google.com0a427372014-04-02 19:12:13 +00001277is actually checked out in %(checkout_path)s.
borenet@google.com88d10082014-03-21 17:24:48 +00001278
borenet@google.com97882362014-04-07 20:06:02 +00001279The .gclient file contains:
levarum@chromium.org27a6f9a2016-05-28 00:21:49 +00001280URL: %(expected_url)s (%(expected_scm)s)
1281Cache mirror: %(mirror_string)s
borenet@google.com97882362014-04-07 20:06:02 +00001282
1283The local checkout in %(checkout_path)s reports:
1284%(actual_url)s (%(actual_scm)s)
borenet@google.com88d10082014-03-21 17:24:48 +00001285
1286You should ensure that the URL listed in .gclient is correct and either change
agabled437d762016-10-17 09:35:11 -07001287it or fix the checkout.
borenet@google.com88d10082014-03-21 17:24:48 +00001288''' % {'checkout_path': os.path.join(self.root_dir, dep.name),
1289 'expected_url': dep.url,
1290 'expected_scm': gclient_scm.GetScmName(dep.url),
levarum@chromium.org27a6f9a2016-05-28 00:21:49 +00001291 'mirror_string' : mirror_string,
borenet@google.com88d10082014-03-21 17:24:48 +00001292 'actual_url': actual_url,
1293 'actual_scm': gclient_scm.GetScmName(actual_url)})
1294
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001295 def SetConfig(self, content):
maruel@chromium.orgf13a4182011-09-22 00:26:15 +00001296 assert not self.dependencies
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001297 config_dict = {}
1298 self.config_content = content
1299 try:
1300 exec(content, config_dict)
vapier@chromium.orga81a56e2015-11-11 07:56:13 +00001301 except SyntaxError as e:
maruel@chromium.org5990f9d2010-07-07 18:02:58 +00001302 gclient_utils.SyntaxErrorToError('.gclient', e)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001303
peter@chromium.org1efccc82012-04-27 16:34:38 +00001304 # Append any target OS that is not already being enforced to the tuple.
1305 target_os = config_dict.get('target_os', [])
stuartmorgan@chromium.org18a4f6a2012-11-02 13:37:24 +00001306 if config_dict.get('target_os_only', False):
1307 self._enforced_os = tuple(set(target_os))
1308 else:
1309 self._enforced_os = tuple(set(self._enforced_os).union(target_os))
1310
dyen@chromium.orgd915cca2014-08-07 21:41:37 +00001311 cache_dir = config_dict.get('cache_dir')
1312 if cache_dir:
1313 cache_dir = os.path.join(self.root_dir, cache_dir)
1314 cache_dir = os.path.abspath(cache_dir)
szager@chromium.orgcaf5bef2014-08-24 18:56:32 +00001315 # If running on a bot, force break any stale git cache locks.
dnj@chromium.orgb682b3e2014-08-25 19:17:12 +00001316 if os.path.exists(cache_dir) and os.environ.get('CHROME_HEADLESS'):
szager@chromium.org4848fb62014-08-24 19:16:31 +00001317 subprocess2.check_call(['git', 'cache', 'unlock', '--cache-dir',
1318 cache_dir, '--force', '--all'])
dyen@chromium.orgd915cca2014-08-07 21:41:37 +00001319 gclient_scm.GitWrapper.cache_dir = cache_dir
1320 git_cache.Mirror.SetCachePath(cache_dir)
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001321
stuartmorgan@chromium.org18a4f6a2012-11-02 13:37:24 +00001322 if not target_os and config_dict.get('target_os_only', False):
1323 raise gclient_utils.Error('Can\'t use target_os_only if target_os is '
1324 'not specified')
peter@chromium.org1efccc82012-04-27 16:34:38 +00001325
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001326 deps_to_add = []
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001327 for s in config_dict.get('solutions', []):
maruel@chromium.org81843b82010-06-28 16:49:26 +00001328 try:
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001329 deps_to_add.append(Dependency(
maruel@chromium.org81843b82010-06-28 16:49:26 +00001330 self, s['name'], s['url'],
smutae7ea312016-07-18 11:59:41 -07001331 s.get('managed', True),
maruel@chromium.org81843b82010-06-28 16:49:26 +00001332 s.get('custom_deps', {}),
maruel@chromium.org0d812442010-08-10 12:41:08 +00001333 s.get('custom_vars', {}),
petermayo@chromium.orge79161a2013-07-09 14:40:37 +00001334 s.get('custom_hooks', []),
nsylvain@google.comefc80932011-05-31 21:27:56 +00001335 s.get('deps_file', 'DEPS'),
agabledce6ddc2016-09-08 10:02:16 -07001336 True,
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +02001337 None,
1338 None,
1339 True))
maruel@chromium.org81843b82010-06-28 16:49:26 +00001340 except KeyError:
1341 raise gclient_utils.Error('Invalid .gclient file. Solution is '
1342 'incomplete: %s' % s)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001343 self.add_dependencies_and_close(deps_to_add, config_dict.get('hooks', []))
1344 logging.info('SetConfig() done')
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001345
1346 def SaveConfig(self):
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001347 gclient_utils.FileWrite(os.path.join(self.root_dir,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001348 self._options.config_filename),
1349 self.config_content)
1350
1351 @staticmethod
1352 def LoadCurrentConfig(options):
1353 """Searches for and loads a .gclient file relative to the current working
1354 dir. Returns a GClient object."""
szager@chromium.orge2e03202012-07-31 18:05:16 +00001355 if options.spec:
1356 client = GClient('.', options)
1357 client.SetConfig(options.spec)
1358 else:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00001359 if options.verbose:
1360 print('Looking for %s starting from %s\n' % (
1361 options.config_filename, os.getcwd()))
szager@chromium.orge2e03202012-07-31 18:05:16 +00001362 path = gclient_utils.FindGclientRoot(os.getcwd(), options.config_filename)
1363 if not path:
1364 return None
1365 client = GClient(path, options)
1366 client.SetConfig(gclient_utils.FileRead(
1367 os.path.join(path, options.config_filename)))
maruel@chromium.org69392e72011-10-13 22:09:00 +00001368
1369 if (options.revisions and
1370 len(client.dependencies) > 1 and
1371 any('@' not in r for r in options.revisions)):
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001372 print(
1373 ('You must specify the full solution name like --revision %s@%s\n'
1374 'when you have multiple solutions setup in your .gclient file.\n'
1375 'Other solutions present are: %s.') % (
maruel@chromium.org69392e72011-10-13 22:09:00 +00001376 client.dependencies[0].name,
1377 options.revisions[0],
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001378 ', '.join(s.name for s in client.dependencies[1:])),
1379 file=sys.stderr)
maruel@chromium.org15804092010-09-02 17:07:37 +00001380 return client
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001381
nsylvain@google.comefc80932011-05-31 21:27:56 +00001382 def SetDefaultConfig(self, solution_name, deps_file, solution_url,
agablea98a6cd2016-11-15 14:30:10 -08001383 managed=True, cache_dir=None):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001384 self.SetConfig(self.DEFAULT_CLIENT_FILE_TEXT % {
1385 'solution_name': solution_name,
1386 'solution_url': solution_url,
nsylvain@google.comefc80932011-05-31 21:27:56 +00001387 'deps_file': deps_file,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001388 'managed': managed,
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001389 'cache_dir': cache_dir,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001390 })
1391
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001392 def _SaveEntries(self):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001393 """Creates a .gclient_entries file to record the list of unique checkouts.
1394
1395 The .gclient_entries file lives in the same directory as .gclient.
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001396 """
1397 # Sometimes pprint.pformat will use {', sometimes it'll use { ' ... It
1398 # makes testing a bit too fun.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001399 result = 'entries = {\n'
maruel@chromium.org68988972011-09-20 14:11:42 +00001400 for entry in self.root.subtree(False):
agabled437d762016-10-17 09:35:11 -07001401 result += ' %s: %s,\n' % (pprint.pformat(entry.name),
1402 pprint.pformat(entry.parsed_url))
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001403 result += '}\n'
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001404 file_path = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org1333cb32011-10-04 23:40:16 +00001405 logging.debug(result)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001406 gclient_utils.FileWrite(file_path, result)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001407
1408 def _ReadEntries(self):
1409 """Read the .gclient_entries file for the given client.
1410
1411 Returns:
1412 A sequence of solution names, which will be empty if there is the
1413 entries file hasn't been created yet.
1414 """
1415 scope = {}
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001416 filename = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001417 if not os.path.exists(filename):
maruel@chromium.org73e21142010-07-05 13:32:01 +00001418 return {}
maruel@chromium.org5990f9d2010-07-07 18:02:58 +00001419 try:
1420 exec(gclient_utils.FileRead(filename), scope)
vapier@chromium.orga81a56e2015-11-11 07:56:13 +00001421 except SyntaxError as e:
maruel@chromium.org5990f9d2010-07-07 18:02:58 +00001422 gclient_utils.SyntaxErrorToError(filename, e)
Aaron Gable3721ee92017-04-03 14:53:14 -07001423 return scope.get('entries', {})
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001424
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001425 def _EnforceRevisions(self):
maruel@chromium.org918a9ae2010-05-28 15:50:30 +00001426 """Checks for revision overrides."""
1427 revision_overrides = {}
smutae7ea312016-07-18 11:59:41 -07001428 if self._options.head:
1429 return revision_overrides
joi@chromium.org792ea882010-11-10 02:37:27 +00001430 if not self._options.revisions:
1431 for s in self.dependencies:
smutae7ea312016-07-18 11:59:41 -07001432 if not s.managed:
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001433 self._options.revisions.append('%s@unmanaged' % s.name)
maruel@chromium.org307d1792010-05-31 20:03:13 +00001434 if not self._options.revisions:
1435 return revision_overrides
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001436 solutions_names = [s.name for s in self.dependencies]
smutae7ea312016-07-18 11:59:41 -07001437 index = 0
1438 for revision in self._options.revisions:
1439 if not '@' in revision:
maruel@chromium.org307d1792010-05-31 20:03:13 +00001440 # Support for --revision 123
smutae7ea312016-07-18 11:59:41 -07001441 revision = '%s@%s' % (solutions_names[index], revision)
1442 name, rev = revision.split('@', 1)
szager@chromium.org4ad264b2014-05-20 04:43:47 +00001443 revision_overrides[name] = rev
smutae7ea312016-07-18 11:59:41 -07001444 index += 1
maruel@chromium.org918a9ae2010-05-28 15:50:30 +00001445 return revision_overrides
1446
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001447 def RunOnDeps(self, command, args, ignore_requirements=False, progress=True):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001448 """Runs a command on each dependency in a client and its dependencies.
1449
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001450 Args:
1451 command: The command to use (e.g., 'status' or 'diff')
1452 args: list of str - extra arguments to add to the command line.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001453 """
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001454 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +00001455 raise gclient_utils.Error('No solution specified')
borenet@google.com0a427372014-04-02 19:12:13 +00001456
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001457 revision_overrides = {}
1458 # It's unnecessary to check for revision overrides for 'recurse'.
1459 # Save a few seconds by not calling _EnforceRevisions() in that case.
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +02001460 if command not in ('diff', 'recurse', 'runhooks', 'status', 'revert',
1461 'validate'):
szager@chromium.org5273b8a2014-08-21 15:10:10 +00001462 self._CheckConfig()
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001463 revision_overrides = self._EnforceRevisions()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001464 pm = None
maruel@chromium.org5b3f8852010-09-10 16:49:54 +00001465 # Disable progress for non-tty stdout.
iannucci@chromium.org596cd5c2016-04-04 21:34:39 +00001466 if (setup_color.IS_TTY and not self._options.verbose and progress):
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001467 if command in ('update', 'revert'):
1468 pm = Progress('Syncing projects', 1)
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +02001469 elif command in ('recurse', 'validate'):
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001470 pm = Progress(' '.join(args), 1)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001471 work_queue = gclient_utils.ExecutionQueue(
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001472 self._options.jobs, pm, ignore_requirements=ignore_requirements,
1473 verbose=self._options.verbose)
maruel@chromium.org049bced2010-08-12 13:37:20 +00001474 for s in self.dependencies:
1475 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +00001476 work_queue.flush(revision_overrides, command, args, options=self._options)
szager@chromium.org4ad264b2014-05-20 04:43:47 +00001477 if revision_overrides:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001478 print('Please fix your script, having invalid --revision flags will soon '
1479 'considered an error.', file=sys.stderr)
piman@chromium.org6f363722010-04-27 00:41:09 +00001480
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001481 # Once all the dependencies have been processed, it's now safe to run the
1482 # hooks.
1483 if not self._options.nohooks:
1484 self.RunHooksRecursively(self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001485
1486 if command == 'update':
ajwong@chromium.orgcdcee802009-06-23 15:30:42 +00001487 # Notify the user if there is an orphaned entry in their working copy.
1488 # Only delete the directory if there are no changes in it, and
1489 # delete_unversioned_trees is set to true.
maruel@chromium.org68988972011-09-20 14:11:42 +00001490 entries = [i.name for i in self.root.subtree(False) if i.url]
xusydoc@chromium.org885a9602013-05-31 09:54:40 +00001491 full_entries = [os.path.join(self.root_dir, e.replace('/', os.path.sep))
1492 for e in entries]
1493
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001494 for entry, prev_url in self._ReadEntries().iteritems():
maruel@chromium.org04dd7de2010-10-14 13:25:49 +00001495 if not prev_url:
1496 # entry must have been overridden via .gclient custom_deps
1497 continue
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001498 # Fix path separator on Windows.
1499 entry_fixed = entry.replace('/', os.path.sep)
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001500 e_dir = os.path.join(self.root_dir, entry_fixed)
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001501 # Use entry and not entry_fixed there.
jochen@chromium.orga78e5532013-03-11 13:33:03 +00001502 if (entry not in entries and
1503 (not any(path.startswith(entry + '/') for path in entries)) and
jochen@chromium.orgcc475722013-03-11 13:07:40 +00001504 os.path.exists(e_dir)):
primiano@chromium.org1c127382015-02-17 11:15:40 +00001505 # The entry has been removed from DEPS.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001506 scm = gclient_scm.CreateSCM(
1507 prev_url, self.root_dir, entry_fixed, self.outbuf)
xusydoc@chromium.org885a9602013-05-31 09:54:40 +00001508
1509 # Check to see if this directory is now part of a higher-up checkout.
borenet@google.com359bb642014-05-13 17:28:19 +00001510 scm_root = None
agabled437d762016-10-17 09:35:11 -07001511 try:
1512 scm_root = gclient_scm.scm.GIT.GetCheckoutRoot(scm.checkout_path)
1513 except subprocess2.CalledProcessError:
1514 pass
1515 if not scm_root:
borenet@google.com359bb642014-05-13 17:28:19 +00001516 logging.warning('Could not find checkout root for %s. Unable to '
1517 'determine whether it is part of a higher-level '
1518 'checkout, so not removing.' % entry)
1519 continue
primiano@chromium.org1c127382015-02-17 11:15:40 +00001520
1521 # This is to handle the case of third_party/WebKit migrating from
1522 # being a DEPS entry to being part of the main project.
1523 # If the subproject is a Git project, we need to remove its .git
1524 # folder. Otherwise git operations on that folder will have different
1525 # effects depending on the current working directory.
agabled437d762016-10-17 09:35:11 -07001526 if os.path.abspath(scm_root) == os.path.abspath(e_dir):
primiano@chromium.org1c127382015-02-17 11:15:40 +00001527 e_par_dir = os.path.join(e_dir, os.pardir)
agabled437d762016-10-17 09:35:11 -07001528 if gclient_scm.scm.GIT.IsInsideWorkTree(e_par_dir):
1529 par_scm_root = gclient_scm.scm.GIT.GetCheckoutRoot(e_par_dir)
primiano@chromium.org1c127382015-02-17 11:15:40 +00001530 # rel_e_dir : relative path of entry w.r.t. its parent repo.
1531 rel_e_dir = os.path.relpath(e_dir, par_scm_root)
agabled437d762016-10-17 09:35:11 -07001532 if gclient_scm.scm.GIT.IsDirectoryVersioned(
1533 par_scm_root, rel_e_dir):
primiano@chromium.org1c127382015-02-17 11:15:40 +00001534 save_dir = scm.GetGitBackupDirPath()
1535 # Remove any eventual stale backup dir for the same project.
1536 if os.path.exists(save_dir):
1537 gclient_utils.rmtree(save_dir)
1538 os.rename(os.path.join(e_dir, '.git'), save_dir)
1539 # When switching between the two states (entry/ is a subproject
1540 # -> entry/ is part of the outer project), it is very likely
1541 # that some files are changed in the checkout, unless we are
1542 # jumping *exactly* across the commit which changed just DEPS.
1543 # In such case we want to cleanup any eventual stale files
1544 # (coming from the old subproject) in order to end up with a
1545 # clean checkout.
agabled437d762016-10-17 09:35:11 -07001546 gclient_scm.scm.GIT.CleanupDir(par_scm_root, rel_e_dir)
primiano@chromium.org1c127382015-02-17 11:15:40 +00001547 assert not os.path.exists(os.path.join(e_dir, '.git'))
1548 print(('\nWARNING: \'%s\' has been moved from DEPS to a higher '
1549 'level checkout. The git folder containing all the local'
1550 ' branches has been saved to %s.\n'
1551 'If you don\'t care about its state you can safely '
1552 'remove that folder to free up space.') %
1553 (entry, save_dir))
1554 continue
1555
borenet@google.com359bb642014-05-13 17:28:19 +00001556 if scm_root in full_entries:
primiano@chromium.org1c127382015-02-17 11:15:40 +00001557 logging.info('%s is part of a higher level checkout, not removing',
1558 scm.GetCheckoutRoot())
xusydoc@chromium.org885a9602013-05-31 09:54:40 +00001559 continue
1560
1561 file_list = []
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001562 scm.status(self._options, [], file_list)
1563 modified_files = file_list != []
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001564 if (not self._options.delete_unversioned_trees or
1565 (modified_files and not self._options.force)):
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001566 # There are modified files in this entry. Keep warning until
1567 # removed.
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001568 print(('\nWARNING: \'%s\' is no longer part of this client. '
1569 'It is recommended that you manually remove it.\n') %
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001570 entry_fixed)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001571 else:
1572 # Delete the entry
maruel@chromium.org73e21142010-07-05 13:32:01 +00001573 print('\n________ deleting \'%s\' in \'%s\'' % (
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001574 entry_fixed, self.root_dir))
digit@chromium.orgdc112ac2013-04-24 13:00:19 +00001575 gclient_utils.rmtree(e_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001576 # record the current list of entries for next time
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001577 self._SaveEntries()
maruel@chromium.org17cdf762010-05-28 17:30:52 +00001578 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001579
1580 def PrintRevInfo(self):
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001581 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +00001582 raise gclient_utils.Error('No solution specified')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001583 # Load all the settings.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001584 work_queue = gclient_utils.ExecutionQueue(
1585 self._options.jobs, None, False, verbose=self._options.verbose)
maruel@chromium.org049bced2010-08-12 13:37:20 +00001586 for s in self.dependencies:
1587 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +00001588 work_queue.flush({}, None, [], options=self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001589
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001590 def GetURLAndRev(dep):
1591 """Returns the revision-qualified SCM url for a Dependency."""
1592 if dep.parsed_url is None:
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001593 return None
agabled437d762016-10-17 09:35:11 -07001594 url, _ = gclient_utils.SplitUrlRevision(dep.parsed_url)
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001595 scm = gclient_scm.CreateSCM(
agabled437d762016-10-17 09:35:11 -07001596 dep.parsed_url, self.root_dir, dep.name, self.outbuf)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001597 if not os.path.isdir(scm.checkout_path):
1598 return None
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001599 return '%s@%s' % (url, scm.revinfo(self._options, [], None))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001600
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001601 if self._options.snapshot:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001602 new_gclient = ''
1603 # First level at .gclient
1604 for d in self.dependencies:
1605 entries = {}
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001606 def GrabDeps(dep):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001607 """Recursively grab dependencies."""
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001608 for d in dep.dependencies:
1609 entries[d.name] = GetURLAndRev(d)
1610 GrabDeps(d)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001611 GrabDeps(d)
1612 custom_deps = []
1613 for k in sorted(entries.keys()):
1614 if entries[k]:
1615 # Quotes aren't escaped...
1616 custom_deps.append(' \"%s\": \'%s\',\n' % (k, entries[k]))
1617 else:
1618 custom_deps.append(' \"%s\": None,\n' % k)
1619 new_gclient += self.DEFAULT_SNAPSHOT_SOLUTION_TEXT % {
1620 'solution_name': d.name,
1621 'solution_url': d.url,
nsylvain@google.comefc80932011-05-31 21:27:56 +00001622 'deps_file': d.deps_file,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001623 'managed': d.managed,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001624 'solution_deps': ''.join(custom_deps),
1625 }
1626 # Print the snapshot configuration file
1627 print(self.DEFAULT_SNAPSHOT_FILE_TEXT % {'solution_list': new_gclient})
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +00001628 else:
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001629 entries = {}
maruel@chromium.org68988972011-09-20 14:11:42 +00001630 for d in self.root.subtree(False):
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001631 if self._options.actual:
1632 entries[d.name] = GetURLAndRev(d)
1633 else:
1634 entries[d.name] = d.parsed_url
1635 keys = sorted(entries.keys())
1636 for x in keys:
maruel@chromium.orgce464892010-08-12 17:12:18 +00001637 print('%s: %s' % (x, entries[x]))
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +00001638 logging.info(str(self))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001639
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001640 def ParseDepsFile(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001641 """No DEPS to parse for a .gclient file."""
maruel@chromium.org049bced2010-08-12 13:37:20 +00001642 raise gclient_utils.Error('Internal error')
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001643
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00001644 def PrintLocationAndContents(self):
1645 # Print out the .gclient file. This is longer than if we just printed the
1646 # client dict, but more legible, and it might contain helpful comments.
1647 print('Loaded .gclient config in %s:\n%s' % (
1648 self.root_dir, self.config_content))
1649
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001650 @property
maruel@chromium.org75a59272010-06-11 22:34:03 +00001651 def root_dir(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001652 """Root directory of gclient checkout."""
maruel@chromium.org75a59272010-06-11 22:34:03 +00001653 return self._root_dir
1654
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001655 @property
maruel@chromium.org271375b2010-06-23 19:17:38 +00001656 def enforced_os(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001657 """What deps_os entries that are to be parsed."""
maruel@chromium.org271375b2010-06-23 19:17:38 +00001658 return self._enforced_os
1659
maruel@chromium.org68988972011-09-20 14:11:42 +00001660 @property
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001661 def recursion_limit(self):
1662 """How recursive can each dependencies in DEPS file can load DEPS file."""
1663 return self._recursion_limit
1664
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +00001665 @property
cmp@chromium.orgc401ad12014-07-02 23:20:08 +00001666 def try_recursedeps(self):
1667 """Whether to attempt using recursedeps-style recursion processing."""
cmp@chromium.orge84ac912014-06-30 23:14:35 +00001668 return True
1669
1670 @property
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +00001671 def target_os(self):
1672 return self._enforced_os
1673
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001674
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001675#### gclient commands.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001676
1677
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001678@subcommand.usage('[command] [args ...]')
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001679def CMDrecurse(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001680 """Operates [command args ...] on all the dependencies.
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001681
1682 Runs a shell command on all entries.
qyearsley12fa6ff2016-08-24 09:18:40 -07001683 Sets GCLIENT_DEP_PATH environment variable as the dep's relative location to
ilevy@chromium.org37116242012-11-28 01:32:48 +00001684 root directory of the checkout.
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001685 """
1686 # Stop parsing at the first non-arg so that these go through to the command
1687 parser.disable_interspersed_args()
1688 parser.add_option('-s', '--scm', action='append', default=[],
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001689 help='Choose scm types to operate upon.')
maruel@chromium.org288054d2012-03-05 00:43:07 +00001690 parser.add_option('-i', '--ignore', action='store_true',
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001691 help='Ignore non-zero return codes from subcommands.')
1692 parser.add_option('--prepend-dir', action='store_true',
1693 help='Prepend relative dir for use with git <cmd> --null.')
1694 parser.add_option('--no-progress', action='store_true',
1695 help='Disable progress bar that shows sub-command updates')
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001696 options, args = parser.parse_args(args)
maruel@chromium.org45e9f2d2010-10-18 13:33:46 +00001697 if not args:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001698 print('Need to supply a command!', file=sys.stderr)
maruel@chromium.org45e9f2d2010-10-18 13:33:46 +00001699 return 1
maruel@chromium.org78cba522010-10-18 13:32:05 +00001700 root_and_entries = gclient_utils.GetGClientRootAndEntries()
1701 if not root_and_entries:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001702 print(
maruel@chromium.org78cba522010-10-18 13:32:05 +00001703 'You need to run gclient sync at least once to use \'recurse\'.\n'
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001704 'This is because .gclient_entries needs to exist and be up to date.',
1705 file=sys.stderr)
maruel@chromium.org78cba522010-10-18 13:32:05 +00001706 return 1
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001707
1708 # Normalize options.scm to a set()
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001709 scm_set = set()
1710 for scm in options.scm:
1711 scm_set.update(scm.split(','))
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001712 options.scm = scm_set
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001713
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001714 options.nohooks = True
1715 client = GClient.LoadCurrentConfig(options)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001716 return client.RunOnDeps('recurse', args, ignore_requirements=True,
1717 progress=not options.no_progress)
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001718
1719
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001720@subcommand.usage('[args ...]')
davidbarr@chromium.org12f944e2012-03-01 02:18:31 +00001721def CMDfetch(parser, args):
1722 """Fetches upstream commits for all modules.
1723
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001724 Completely git-specific. Simply runs 'git fetch [args ...]' for each module.
1725 """
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001726 (options, args) = parser.parse_args(args)
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00001727 return CMDrecurse(OptionParser(), [
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001728 '--jobs=%d' % options.jobs, '--scm=git', 'git', 'fetch'] + args)
1729
1730
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001731def CMDflatten(parser, args):
1732 """Flattens the solutions into a single DEPS file."""
1733 parser.add_option('--output-deps', help='Path to the output DEPS file')
1734 parser.add_option(
1735 '--require-pinned-revisions', action='store_true',
1736 help='Fail if any of the dependencies uses unpinned revision.')
1737 options, args = parser.parse_args(args)
1738
1739 options.nohooks = True
1740 client = GClient.LoadCurrentConfig(options)
1741
1742 # Only print progress if we're writing to a file. Otherwise, progress updates
1743 # could obscure intended output.
1744 code = client.RunOnDeps('flatten', args, progress=options.output_deps)
1745 if code != 0:
1746 return code
1747
1748 deps = {}
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02001749 deps_os = {}
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001750 hooks = []
1751 pre_deps_hooks = []
1752 unpinned_deps = {}
1753
1754 for solution in client.dependencies:
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02001755 _FlattenSolution(
1756 solution, deps, deps_os, hooks, pre_deps_hooks, unpinned_deps)
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001757
1758 if options.require_pinned_revisions and unpinned_deps:
1759 sys.stderr.write('The following dependencies are not pinned:\n')
1760 sys.stderr.write('\n'.join(sorted(unpinned_deps)))
1761 return 1
1762
1763 flattened_deps = '\n'.join(
Paweł Hajdan, Jr3c2aa832017-06-07 20:22:16 +02001764 _GNSettingsToLines(
1765 client.dependencies[0]._gn_args_file,
1766 client.dependencies[0]._gn_args) +
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001767 _DepsToLines(deps) +
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02001768 _DepsOsToLines(deps_os) +
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001769 _HooksToLines('hooks', hooks) +
1770 _HooksToLines('pre_deps_hooks', pre_deps_hooks) +
1771 [''] # Ensure newline at end of file.
1772 )
1773
1774 if options.output_deps:
1775 with open(options.output_deps, 'w') as f:
1776 f.write(flattened_deps)
1777 else:
1778 print(flattened_deps)
1779
1780 return 0
1781
1782
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02001783def _FlattenSolution(
1784 solution, deps, deps_os, hooks, pre_deps_hooks, unpinned_deps):
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001785 """Visits a solution in order to flatten it (see CMDflatten).
1786
1787 Arguments:
1788 solution (Dependency): one of top-level solutions in .gclient
1789
1790 Out-parameters:
1791 deps (dict of name -> Dependency): will be filled with all Dependency
1792 objects indexed by their name
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02001793 deps_os (dict of os name -> dep name -> Dependency): same as above,
1794 for OS-specific deps
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001795 hooks (list of (Dependency, hook)): will be filled with flattened hooks
1796 pre_deps_hooks (list of (Dependency, hook)): will be filled with flattened
1797 pre_deps_hooks
1798 unpinned_deps (dict of name -> Dependency): will be filled with unpinned
1799 deps
1800 """
1801 logging.debug('_FlattenSolution(%r)', solution)
1802
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02001803 _FlattenDep(solution, deps, deps_os, hooks, pre_deps_hooks, unpinned_deps)
1804 _FlattenRecurse(solution, deps, deps_os, hooks, pre_deps_hooks, unpinned_deps)
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001805
1806
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02001807def _FlattenDep(dep, deps, deps_os, hooks, pre_deps_hooks, unpinned_deps):
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001808 """Visits a dependency in order to flatten it (see CMDflatten).
1809
1810 Arguments:
1811 dep (Dependency): dependency to process
1812
1813 Out-parameters:
1814 deps (dict): will be filled with flattened deps
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02001815 deps_os (dict): will be filled with flattened deps_os
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001816 hooks (list): will be filled with flattened hooks
1817 pre_deps_hooks (list): will be filled with flattened pre_deps_hooks
1818 unpinned_deps (dict): will be filled with unpinned deps
1819 """
1820 logging.debug('_FlattenDep(%r)', dep)
1821
1822 _AddDep(dep, deps, unpinned_deps)
1823
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02001824 for dep_os, os_deps in dep.os_dependencies.iteritems():
1825 for os_dep in os_deps:
1826 deps_os.setdefault(dep_os, {})[os_dep.name] = os_dep
1827
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001828 deps_by_name = dict((d.name, d) for d in dep.dependencies)
1829 for recurse_dep_name in (dep.recursedeps or []):
1830 _FlattenRecurse(
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02001831 deps_by_name[recurse_dep_name], deps, deps_os, hooks, pre_deps_hooks,
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001832 unpinned_deps)
1833
1834 # TODO(phajdan.jr): also handle hooks_os.
1835 hooks.extend([(dep, hook) for hook in dep.deps_hooks])
1836 pre_deps_hooks.extend(
1837 [(dep, {'action': hook}) for hook in dep.pre_deps_hooks])
1838
1839
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02001840def _FlattenRecurse(dep, deps, deps_os, hooks, pre_deps_hooks, unpinned_deps):
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001841 """Helper for flatten that recurses into |dep|'s dependencies.
1842
1843 Arguments:
1844 dep (Dependency): dependency to process
1845
1846 Out-parameters:
1847 deps (dict): will be filled with flattened deps
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02001848 deps_os (dict): will be filled with flattened deps_os
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001849 hooks (list): will be filled with flattened hooks
1850 pre_deps_hooks (list): will be filled with flattened pre_deps_hooks
1851 unpinned_deps (dict): will be filled with unpinned deps
1852 """
1853 logging.debug('_FlattenRecurse(%r)', dep)
1854
1855 # TODO(phajdan.jr): also handle deps_os.
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +02001856 for sub_dep in dep.orig_dependencies:
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02001857 _FlattenDep(sub_dep, deps, deps_os, hooks, pre_deps_hooks, unpinned_deps)
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001858
1859
1860def _AddDep(dep, deps, unpinned_deps):
1861 """Helper to add a dependency to flattened lists.
1862
1863 Arguments:
1864 dep (Dependency): dependency to process
1865
1866 Out-parameters:
1867 deps (dict): will be filled with flattened deps
1868 unpinned_deps (dict): will be filled with unpinned deps
1869 """
1870 logging.debug('_AddDep(%r)', dep)
1871
1872 assert dep.name not in deps
1873 deps[dep.name] = dep
1874
1875 # Detect unpinned deps.
1876 _, revision = gclient_utils.SplitUrlRevision(dep.url)
1877 if not revision or not gclient_utils.IsGitSha(revision):
1878 unpinned_deps[dep.name] = dep
1879
1880
Paweł Hajdan, Jr3c2aa832017-06-07 20:22:16 +02001881def _GNSettingsToLines(gn_args_file, gn_args):
1882 s = []
1883 if gn_args_file:
1884 s.extend([
1885 'gclient_gn_args_file = "%s"' % gn_args_file,
1886 'gclient_gn_args = %r' % gn_args,
1887 ])
1888 return s
1889
1890
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001891def _DepsToLines(deps):
1892 """Converts |deps| dict to list of lines for output."""
1893 s = ['deps = {']
1894 for name, dep in sorted(deps.iteritems()):
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +02001895 condition_part = ([' "condition": "%s",' % dep.condition]
1896 if dep.condition else [])
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001897 s.extend([
1898 ' # %s' % dep.hierarchy(include_url=False),
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +02001899 ' "%s": {' % (name,),
1900 ' "url": "%s",' % (dep.url,),
1901 ] + condition_part + [
1902 ' },',
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001903 '',
1904 ])
1905 s.extend(['}', ''])
1906 return s
1907
1908
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02001909def _DepsOsToLines(deps_os):
1910 """Converts |deps_os| dict to list of lines for output."""
1911 s = ['deps_os = {']
1912 for dep_os, os_deps in sorted(deps_os.iteritems()):
1913 s.append(' "%s": {' % dep_os)
1914 s.extend([' %s' % l for l in _DepsToLines(os_deps)])
1915 s.extend([' },', ''])
1916 s.extend(['}', ''])
1917 return s
1918
1919
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001920def _HooksToLines(name, hooks):
1921 """Converts |hooks| list to list of lines for output."""
1922 s = ['%s = [' % name]
1923 for dep, hook in hooks:
1924 s.extend([
1925 ' # %s' % dep.hierarchy(include_url=False),
1926 ' {',
1927 ])
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +02001928 if hook.name is not None:
1929 s.append(' "name": "%s",' % hook.name)
1930 if hook.pattern is not None:
1931 s.append(' "pattern": "%s",' % hook.pattern)
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001932 # TODO(phajdan.jr): actions may contain paths that need to be adjusted,
1933 # i.e. they may be relative to the dependency path, not solution root.
1934 s.extend(
1935 [' "action": ['] +
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +02001936 [' "%s",' % arg for arg in hook.action] +
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001937 [' ]', ' },', '']
1938 )
1939 s.extend([']', ''])
1940 return s
1941
1942
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001943def CMDgrep(parser, args):
1944 """Greps through git repos managed by gclient.
1945
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001946 Runs 'git grep [args...]' for each module.
1947 """
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001948 # We can't use optparse because it will try to parse arguments sent
1949 # to git grep and throw an error. :-(
1950 if not args or re.match('(-h|--help)$', args[0]):
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001951 print(
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001952 'Usage: gclient grep [-j <N>] git-grep-args...\n\n'
1953 'Example: "gclient grep -j10 -A2 RefCountedBase" runs\n"git grep '
1954 '-A2 RefCountedBase" on each of gclient\'s git\nrepos with up to '
1955 '10 jobs.\n\nBonus: page output by appending "|& less -FRSX" to the'
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001956 ' end of your query.',
1957 file=sys.stderr)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001958 return 1
1959
1960 jobs_arg = ['--jobs=1']
1961 if re.match(r'(-j|--jobs=)\d+$', args[0]):
1962 jobs_arg, args = args[:1], args[1:]
1963 elif re.match(r'(-j|--jobs)$', args[0]):
1964 jobs_arg, args = args[:2], args[2:]
1965
1966 return CMDrecurse(
1967 parser,
1968 jobs_arg + ['--ignore', '--prepend-dir', '--no-progress', '--scm=git',
1969 'git', 'grep', '--null', '--color=Always'] + args)
davidbarr@chromium.org12f944e2012-03-01 02:18:31 +00001970
1971
stip@chromium.orga735da22015-04-29 23:18:20 +00001972def CMDroot(parser, args):
1973 """Outputs the solution root (or current dir if there isn't one)."""
1974 (options, args) = parser.parse_args(args)
1975 client = GClient.LoadCurrentConfig(options)
1976 if client:
1977 print(os.path.abspath(client.root_dir))
1978 else:
1979 print(os.path.abspath('.'))
1980
1981
agablea98a6cd2016-11-15 14:30:10 -08001982@subcommand.usage('[url]')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001983def CMDconfig(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001984 """Creates a .gclient file in the current directory.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001985
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001986 This specifies the configuration for further commands. After update/sync,
1987 top-level DEPS files in each module are read to determine dependent
1988 modules to operate on as well. If optional [url] parameter is
1989 provided, then configuration is read from a specified Subversion server
1990 URL.
1991 """
szager@chromium.orge2e03202012-07-31 18:05:16 +00001992 # We do a little dance with the --gclientfile option. 'gclient config' is the
1993 # only command where it's acceptable to have both '--gclientfile' and '--spec'
1994 # arguments. So, we temporarily stash any --gclientfile parameter into
1995 # options.output_config_file until after the (gclientfile xor spec) error
1996 # check.
1997 parser.remove_option('--gclientfile')
1998 parser.add_option('--gclientfile', dest='output_config_file',
1999 help='Specify an alternate .gclient file')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002000 parser.add_option('--name',
2001 help='overrides the default name for the solution')
nsylvain@google.comefc80932011-05-31 21:27:56 +00002002 parser.add_option('--deps-file', default='DEPS',
2003 help='overrides the default name for the DEPS file for the'
2004 'main solutions and all sub-dependencies')
smutae7ea312016-07-18 11:59:41 -07002005 parser.add_option('--unmanaged', action='store_true', default=False,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00002006 help='overrides the default behavior to make it possible '
smutae7ea312016-07-18 11:59:41 -07002007 'to have the main solution untouched by gclient '
2008 '(gclient will check out unmanaged dependencies but '
2009 'will never sync them)')
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00002010 parser.add_option('--cache-dir',
2011 help='(git only) Cache all git repos into this dir and do '
2012 'shared clones from the cache, instead of cloning '
2013 'directly from the remote. (experimental)')
szager@chromium.orge2e03202012-07-31 18:05:16 +00002014 parser.set_defaults(config_filename=None)
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002015 (options, args) = parser.parse_args(args)
szager@chromium.orge2e03202012-07-31 18:05:16 +00002016 if options.output_config_file:
2017 setattr(options, 'config_filename', getattr(options, 'output_config_file'))
maruel@chromium.org5fc2a332010-05-26 19:37:15 +00002018 if ((options.spec and args) or len(args) > 2 or
2019 (not options.spec and not args)):
2020 parser.error('Inconsistent arguments. Use either --spec or one or 2 args')
2021
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002022 client = GClient('.', options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002023 if options.spec:
2024 client.SetConfig(options.spec)
2025 else:
maruel@chromium.org1ab7ffc2009-06-03 17:21:37 +00002026 base_url = args[0].rstrip('/')
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00002027 if not options.name:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002028 name = base_url.split('/')[-1]
nsylvain@google.com12649ef2011-06-01 17:11:20 +00002029 if name.endswith('.git'):
2030 name = name[:-4]
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00002031 else:
2032 # specify an alternate relpath for the given URL.
2033 name = options.name
agable@chromium.orgf2214672015-10-27 21:02:48 +00002034 if not os.path.abspath(os.path.join(os.getcwd(), name)).startswith(
2035 os.getcwd()):
2036 parser.error('Do not pass a relative path for --name.')
2037 if any(x in ('..', '.', '/', '\\') for x in name.split(os.sep)):
2038 parser.error('Do not include relative path components in --name.')
2039
nsylvain@google.comefc80932011-05-31 21:27:56 +00002040 deps_file = options.deps_file
agablea98a6cd2016-11-15 14:30:10 -08002041 client.SetDefaultConfig(name, deps_file, base_url,
smutae7ea312016-07-18 11:59:41 -07002042 managed=not options.unmanaged,
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00002043 cache_dir=options.cache_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002044 client.SaveConfig()
maruel@chromium.org79692d62010-05-14 18:57:13 +00002045 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002046
2047
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002048@subcommand.epilog("""Example:
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002049 gclient pack > patch.txt
2050 generate simple patch for configured client and dependences
2051""")
2052def CMDpack(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002053 """Generates a patch which can be applied at the root of the tree.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00002054
agabled437d762016-10-17 09:35:11 -07002055 Internally, runs 'git diff' on each checked out module and
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002056 dependencies, and performs minimal postprocessing of the output. The
2057 resulting patch is printed to stdout and can be applied to a freshly
2058 checked out tree via 'patch -p0 < patchfile'.
2059 """
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002060 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2061 help='override deps for the specified (comma-separated) '
2062 'platform(s); \'all\' will process all deps_os '
2063 'references')
haitao.feng@intel.com306080c2012-05-04 13:11:29 +00002064 parser.remove_option('--jobs')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002065 (options, args) = parser.parse_args(args)
iannucci@chromium.org50395ea2013-04-04 04:47:42 +00002066 # Force jobs to 1 so the stdout is not annotated with the thread ids
haitao.feng@intel.com306080c2012-05-04 13:11:29 +00002067 options.jobs = 1
kbr@google.comab318592009-09-04 00:54:55 +00002068 client = GClient.LoadCurrentConfig(options)
2069 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002070 raise gclient_utils.Error('client not configured; see \'gclient config\'')
kbr@google.comab318592009-09-04 00:54:55 +00002071 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002072 client.PrintLocationAndContents()
kbr@google.comab318592009-09-04 00:54:55 +00002073 return client.RunOnDeps('pack', args)
2074
2075
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002076def CMDstatus(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002077 """Shows modification status for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002078 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2079 help='override deps for the specified (comma-separated) '
2080 'platform(s); \'all\' will process all deps_os '
2081 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002082 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002083 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002084 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002085 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002086 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002087 client.PrintLocationAndContents()
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002088 return client.RunOnDeps('status', args)
2089
2090
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002091@subcommand.epilog("""Examples:
maruel@chromium.org79692d62010-05-14 18:57:13 +00002092 gclient sync
2093 update files from SCM according to current configuration,
2094 *for modules which have changed since last update or sync*
2095 gclient sync --force
2096 update files from SCM according to current configuration, for
2097 all modules (useful for recovering files deleted from local copy)
2098 gclient sync --revision src@31000
2099 update src directory to r31000
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002100
2101JSON output format:
2102If the --output-json option is specified, the following document structure will
2103be emitted to the provided file. 'null' entries may occur for subprojects which
2104are present in the gclient solution, but were not processed (due to custom_deps,
2105os_deps, etc.)
2106
2107{
2108 "solutions" : {
2109 "<name>": { # <name> is the posix-normalized path to the solution.
agabled437d762016-10-17 09:35:11 -07002110 "revision": [<git id hex string>|null],
2111 "scm": ["git"|null],
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002112 }
2113 }
2114}
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002115""")
2116def CMDsync(parser, args):
2117 """Checkout/update all modules."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002118 parser.add_option('-f', '--force', action='store_true',
2119 help='force update even for unchanged modules')
2120 parser.add_option('-n', '--nohooks', action='store_true',
2121 help='don\'t run hooks after the update is complete')
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00002122 parser.add_option('-p', '--noprehooks', action='store_true',
2123 help='don\'t run pre-DEPS hooks', default=False)
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002124 parser.add_option('-r', '--revision', action='append',
2125 dest='revisions', metavar='REV', default=[],
2126 help='Enforces revision/hash for the solutions with the '
2127 'format src@rev. The src@ part is optional and can be '
2128 'skipped. -r can be used multiple times when .gclient '
2129 'has multiple solutions configured and will work even '
agablea98a6cd2016-11-15 14:30:10 -08002130 'if the src@ part is skipped.')
maruel@chromium.org794207e2013-03-08 15:29:43 +00002131 parser.add_option('--with_branch_heads', action='store_true',
2132 help='Clone git "branch_heads" refspecs in addition to '
2133 'the default refspecs. This adds about 1/2GB to a '
2134 'full checkout. (git only)')
szager@chromium.org8d3348f2014-08-19 22:49:16 +00002135 parser.add_option('--with_tags', action='store_true',
2136 help='Clone git tags in addition to the default refspecs.')
agable2697cd12016-06-28 10:23:53 -07002137 parser.add_option('-H', '--head', action='store_true',
agablea98a6cd2016-11-15 14:30:10 -08002138 help='DEPRECATED: only made sense with safesync urls.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002139 parser.add_option('-D', '--delete_unversioned_trees', action='store_true',
steveblock@chromium.org98e69452012-02-16 16:36:43 +00002140 help='Deletes from the working copy any dependencies that '
2141 'have been removed since the last sync, as long as '
2142 'there are no local modifications. When used with '
2143 '--force, such dependencies are removed even if they '
2144 'have local modifications. When used with --reset, '
2145 'all untracked directories are removed from the '
tnagel@chromium.orga2aaa632014-02-28 21:47:27 +00002146 'working copy, excluding those which are explicitly '
steveblock@chromium.org98e69452012-02-16 16:36:43 +00002147 'ignored in the repository.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002148 parser.add_option('-R', '--reset', action='store_true',
2149 help='resets any local changes before updating (git only)')
bauerb@chromium.org2aad1b22011-07-22 12:00:41 +00002150 parser.add_option('-M', '--merge', action='store_true',
2151 help='merge upstream changes instead of trying to '
2152 'fast-forward or rebase')
dnj@chromium.org5b23e872015-02-20 21:25:57 +00002153 parser.add_option('-A', '--auto_rebase', action='store_true',
2154 help='Automatically rebase repositories against local '
2155 'checkout during update (git only).')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002156 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2157 help='override deps for the specified (comma-separated) '
2158 'platform(s); \'all\' will process all deps_os '
2159 'references')
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +00002160 parser.add_option('--upstream', action='store_true',
2161 help='Make repo state match upstream branch.')
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002162 parser.add_option('--output-json',
2163 help='Output a json document to this path containing '
2164 'summary information about the sync.')
primiano@chromium.org5439ea52014-08-06 17:18:18 +00002165 parser.add_option('--no-history', action='store_true',
2166 help='GIT ONLY - Reduces the size/time of the checkout at '
2167 'the cost of no history. Requires Git 1.9+')
hinoka@chromium.org46b87412014-05-15 00:42:05 +00002168 parser.add_option('--shallow', action='store_true',
2169 help='GIT ONLY - Do a shallow clone into the cache dir. '
2170 'Requires Git 1.9+')
e.hakkinen@samsung.come8bc1aa2015-04-08 08:00:37 +00002171 parser.add_option('--no_bootstrap', '--no-bootstrap',
2172 action='store_true',
2173 help='Don\'t bootstrap from Google Storage.')
hinoka@chromium.org8a10f6d2014-06-23 18:38:57 +00002174 parser.add_option('--ignore_locks', action='store_true',
2175 help='GIT ONLY - Ignore cache locks.')
iannucci@chromium.org30a07982016-04-07 21:35:19 +00002176 parser.add_option('--break_repo_locks', action='store_true',
2177 help='GIT ONLY - Forcibly remove repo locks (e.g. '
2178 'index.lock). This should only be used if you know for '
2179 'certain that this invocation of gclient is the only '
2180 'thing operating on the git repos (e.g. on a bot).')
nodir@chromium.org5b48e482016-03-18 20:27:54 +00002181 parser.add_option('--lock_timeout', type='int', default=5000,
szager@chromium.orgdbb6f822016-02-02 22:59:30 +00002182 help='GIT ONLY - Deadline (in seconds) to wait for git '
nodir@chromium.org5b48e482016-03-18 20:27:54 +00002183 'cache lock to become available. Default is %default.')
agabled437d762016-10-17 09:35:11 -07002184 # TODO(agable): Remove these when the oldest CrOS release milestone is M56.
2185 parser.add_option('-t', '--transitive', action='store_true',
2186 help='DEPRECATED: This is a no-op.')
sdefresne69b1be12016-10-18 05:48:02 -07002187 parser.add_option('-m', '--manually_grab_svn_rev', action='store_true',
agabled437d762016-10-17 09:35:11 -07002188 help='DEPRECATED: This is a no-op.')
Paweł Hajdan, Jr7c7b5592017-05-23 15:06:05 +02002189 # TODO(phajdan.jr): Remove validation options once default (crbug/570091).
Paweł Hajdan, Jr694773d2017-05-29 16:06:23 +02002190 parser.add_option('--validate-syntax', action='store_true', default=True,
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +02002191 help='Validate the .gclient and DEPS syntax')
Paweł Hajdan, Jr7c7b5592017-05-23 15:06:05 +02002192 parser.add_option('--disable-syntax-validation', action='store_false',
2193 dest='validate_syntax',
2194 help='Disable validation of .gclient and DEPS syntax.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002195 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002196 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002197
2198 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002199 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002200
smutae7ea312016-07-18 11:59:41 -07002201 if options.revisions and options.head:
2202 # TODO(maruel): Make it a parser.error if it doesn't break any builder.
2203 print('Warning: you cannot use both --head and --revision')
2204
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002205 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002206 client.PrintLocationAndContents()
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002207 ret = client.RunOnDeps('update', args)
2208 if options.output_json:
2209 slns = {}
2210 for d in client.subtree(True):
2211 normed = d.name.replace('\\', '/').rstrip('/') + '/'
2212 slns[normed] = {
2213 'revision': d.got_revision,
2214 'scm': d.used_scm.name if d.used_scm else None,
hinoka@chromium.org17db9052014-05-10 01:11:29 +00002215 'url': str(d.url) if d.url else None,
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002216 }
2217 with open(options.output_json, 'wb') as f:
2218 json.dump({'solutions': slns}, f)
2219 return ret
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002220
2221
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002222CMDupdate = CMDsync
2223
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002224
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +02002225def CMDvalidate(parser, args):
2226 """Validates the .gclient and DEPS syntax."""
2227 options, args = parser.parse_args(args)
2228 options.validate_syntax = True
2229 client = GClient.LoadCurrentConfig(options)
2230 rv = client.RunOnDeps('validate', args)
2231 if rv == 0:
2232 print('validate: SUCCESS')
2233 else:
2234 print('validate: FAILURE')
2235 return rv
2236
2237
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002238def CMDdiff(parser, args):
2239 """Displays local diff for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002240 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2241 help='override deps for the specified (comma-separated) '
2242 'platform(s); \'all\' will process all deps_os '
2243 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002244 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002245 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002246 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002247 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002248 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002249 client.PrintLocationAndContents()
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002250 return client.RunOnDeps('diff', args)
2251
2252
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002253def CMDrevert(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002254 """Reverts all modifications in every dependencies.
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00002255
2256 That's the nuclear option to get back to a 'clean' state. It removes anything
agabled437d762016-10-17 09:35:11 -07002257 that shows up in git status."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002258 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2259 help='override deps for the specified (comma-separated) '
2260 'platform(s); \'all\' will process all deps_os '
2261 'references')
2262 parser.add_option('-n', '--nohooks', action='store_true',
2263 help='don\'t run hooks after the revert is complete')
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00002264 parser.add_option('-p', '--noprehooks', action='store_true',
2265 help='don\'t run pre-DEPS hooks', default=False)
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +00002266 parser.add_option('--upstream', action='store_true',
2267 help='Make repo state match upstream branch.')
iannucci@chromium.orgbf525dc2016-04-07 22:00:28 +00002268 parser.add_option('--break_repo_locks', action='store_true',
2269 help='GIT ONLY - Forcibly remove repo locks (e.g. '
2270 'index.lock). This should only be used if you know for '
2271 'certain that this invocation of gclient is the only '
2272 'thing operating on the git repos (e.g. on a bot).')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002273 (options, args) = parser.parse_args(args)
2274 # --force is implied.
2275 options.force = True
steveblock@chromium.org98e69452012-02-16 16:36:43 +00002276 options.reset = False
2277 options.delete_unversioned_trees = False
agablec903d732016-07-26 09:07:24 -07002278 options.merge = False
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002279 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002280 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002281 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002282 return client.RunOnDeps('revert', args)
2283
2284
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002285def CMDrunhooks(parser, args):
2286 """Runs hooks for files that have been modified in the local working copy."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002287 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2288 help='override deps for the specified (comma-separated) '
2289 'platform(s); \'all\' will process all deps_os '
2290 'references')
2291 parser.add_option('-f', '--force', action='store_true', default=True,
2292 help='Deprecated. No effect.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002293 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002294 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002295 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002296 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002297 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002298 client.PrintLocationAndContents()
maruel@chromium.org5df6a462009-08-28 18:52:26 +00002299 options.force = True
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002300 options.nohooks = False
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002301 return client.RunOnDeps('runhooks', args)
2302
2303
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002304def CMDrevinfo(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002305 """Outputs revision info mapping for the client and its dependencies.
maruel@chromium.org9eda4112010-06-11 18:56:10 +00002306
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002307 This allows the capture of an overall 'revision' for the source tree that
maruel@chromium.org9eda4112010-06-11 18:56:10 +00002308 can be used to reproduce the same tree in the future. It is only useful for
agabled437d762016-10-17 09:35:11 -07002309 'unpinned dependencies', i.e. DEPS/deps references without a git hash.
2310 A git branch name isn't 'pinned' since the actual commit can change.
maruel@chromium.org9eda4112010-06-11 18:56:10 +00002311 """
2312 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2313 help='override deps for the specified (comma-separated) '
2314 'platform(s); \'all\' will process all deps_os '
2315 'references')
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00002316 parser.add_option('-a', '--actual', action='store_true',
2317 help='gets the actual checked out revisions instead of the '
2318 'ones specified in the DEPS and .gclient files')
maruel@chromium.org9eda4112010-06-11 18:56:10 +00002319 parser.add_option('-s', '--snapshot', action='store_true',
2320 help='creates a snapshot .gclient file of the current '
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00002321 'version of all repositories to reproduce the tree, '
2322 'implies -a')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002323 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002324 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002325 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002326 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002327 client.PrintRevInfo()
maruel@chromium.org79692d62010-05-14 18:57:13 +00002328 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002329
2330
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00002331def CMDverify(parser, args):
2332 """Verifies the DEPS file deps are only from allowed_hosts."""
2333 (options, args) = parser.parse_args(args)
2334 client = GClient.LoadCurrentConfig(options)
2335 if not client:
2336 raise gclient_utils.Error('client not configured; see \'gclient config\'')
2337 client.RunOnDeps(None, [])
2338 # Look at each first-level dependency of this gclient only.
2339 for dep in client.dependencies:
2340 bad_deps = dep.findDepsFromNotAllowedHosts()
2341 if not bad_deps:
2342 continue
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002343 print("There are deps from not allowed hosts in file %s" % dep.deps_file)
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00002344 for bad_dep in bad_deps:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002345 print("\t%s at %s" % (bad_dep.name, bad_dep.url))
2346 print("allowed_hosts:", ', '.join(dep.allowed_hosts))
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00002347 sys.stdout.flush()
2348 raise gclient_utils.Error(
2349 'dependencies from disallowed hosts; check your DEPS file.')
2350 return 0
2351
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002352class OptionParser(optparse.OptionParser):
szager@chromium.orge2e03202012-07-31 18:05:16 +00002353 gclientfile_default = os.environ.get('GCLIENT_FILE', '.gclient')
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002354
2355 def __init__(self, **kwargs):
2356 optparse.OptionParser.__init__(
2357 self, version='%prog ' + __version__, **kwargs)
2358
2359 # Some arm boards have issues with parallel sync.
2360 if platform.machine().startswith('arm'):
2361 jobs = 1
2362 else:
2363 jobs = max(8, gclient_utils.NumLocalCpus())
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002364
2365 self.add_option(
2366 '-j', '--jobs', default=jobs, type='int',
2367 help='Specify how many SCM commands can run in parallel; defaults to '
tnagel@chromium.orga2aaa632014-02-28 21:47:27 +00002368 '%default on this machine')
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002369 self.add_option(
2370 '-v', '--verbose', action='count', default=0,
2371 help='Produces additional output for diagnostics. Can be used up to '
2372 'three times for more logging info.')
2373 self.add_option(
2374 '--gclientfile', dest='config_filename',
2375 help='Specify an alternate %s file' % self.gclientfile_default)
2376 self.add_option(
2377 '--spec',
2378 help='create a gclient file containing the provided string. Due to '
2379 'Cygwin/Python brokenness, it can\'t contain any newlines.')
2380 self.add_option(
2381 '--no-nag-max', default=False, action='store_true',
scottmg@chromium.orgf547c802013-09-27 17:55:26 +00002382 help='Ignored for backwards compatibility.')
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002383
2384 def parse_args(self, args=None, values=None):
2385 """Integrates standard options processing."""
2386 options, args = optparse.OptionParser.parse_args(self, args, values)
2387 levels = [logging.ERROR, logging.WARNING, logging.INFO, logging.DEBUG]
2388 logging.basicConfig(
2389 level=levels[min(options.verbose, len(levels) - 1)],
maruel@chromium.org0895b752011-08-26 20:40:33 +00002390 format='%(module)s(%(lineno)d) %(funcName)s:%(message)s')
szager@chromium.orge2e03202012-07-31 18:05:16 +00002391 if options.config_filename and options.spec:
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002392 self.error('Cannot specifiy both --gclientfile and --spec')
rdsmith@chromium.orgd9591f02014-02-05 19:28:20 +00002393 if (options.config_filename and
2394 options.config_filename != os.path.basename(options.config_filename)):
2395 self.error('--gclientfile target must be a filename, not a path')
szager@chromium.orge2e03202012-07-31 18:05:16 +00002396 if not options.config_filename:
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002397 options.config_filename = self.gclientfile_default
maruel@chromium.org0895b752011-08-26 20:40:33 +00002398 options.entries_filename = options.config_filename + '_entries'
2399 if options.jobs < 1:
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002400 self.error('--jobs must be 1 or higher')
maruel@chromium.org0895b752011-08-26 20:40:33 +00002401
2402 # These hacks need to die.
2403 if not hasattr(options, 'revisions'):
2404 # GClient.RunOnDeps expects it even if not applicable.
2405 options.revisions = []
smutae7ea312016-07-18 11:59:41 -07002406 if not hasattr(options, 'head'):
2407 options.head = None
maruel@chromium.org0895b752011-08-26 20:40:33 +00002408 if not hasattr(options, 'nohooks'):
2409 options.nohooks = True
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00002410 if not hasattr(options, 'noprehooks'):
2411 options.noprehooks = True
maruel@chromium.org0895b752011-08-26 20:40:33 +00002412 if not hasattr(options, 'deps_os'):
2413 options.deps_os = None
maruel@chromium.org0895b752011-08-26 20:40:33 +00002414 if not hasattr(options, 'force'):
2415 options.force = None
2416 return (options, args)
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002417
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002418
2419def disable_buffering():
2420 # Make stdout auto-flush so buildbot doesn't kill us during lengthy
2421 # operations. Python as a strong tendency to buffer sys.stdout.
2422 sys.stdout = gclient_utils.MakeFileAutoFlush(sys.stdout)
2423 # Make stdout annotated with the thread ids.
2424 sys.stdout = gclient_utils.MakeFileAnnotated(sys.stdout)
maruel@chromium.org0895b752011-08-26 20:40:33 +00002425
2426
sbc@chromium.org013731e2015-02-26 18:28:43 +00002427def main(argv):
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002428 """Doesn't parse the arguments here, just find the right subcommand to
2429 execute."""
maruel@chromium.org82798cb2012-02-23 18:16:12 +00002430 if sys.hexversion < 0x02060000:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002431 print(
maruel@chromium.org82798cb2012-02-23 18:16:12 +00002432 '\nYour python version %s is unsupported, please upgrade.\n' %
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002433 sys.version.split(' ', 1)[0],
2434 file=sys.stderr)
maruel@chromium.org82798cb2012-02-23 18:16:12 +00002435 return 2
bcwhite@chromium.org6683ab42013-02-11 16:13:47 +00002436 if not sys.executable:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002437 print(
2438 '\nPython cannot find the location of it\'s own executable.\n',
2439 file=sys.stderr)
bcwhite@chromium.org6683ab42013-02-11 16:13:47 +00002440 return 2
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002441 fix_encoding.fix_encoding()
2442 disable_buffering()
iannucci@chromium.org596cd5c2016-04-04 21:34:39 +00002443 setup_color.init()
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002444 dispatcher = subcommand.CommandDispatcher(__name__)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00002445 try:
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002446 return dispatcher.execute(OptionParser(), argv)
xusydoc@chromium.org2fd6c3f2013-05-03 21:57:55 +00002447 except KeyboardInterrupt:
2448 gclient_utils.GClientChildren.KillAllRemainingChildren()
2449 raise
vapier@chromium.orga81a56e2015-11-11 07:56:13 +00002450 except (gclient_utils.Error, subprocess2.CalledProcessError) as e:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002451 print('Error: %s' % str(e), file=sys.stderr)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00002452 return 1
borenet@google.com6a9b1682014-03-24 18:35:23 +00002453 finally:
2454 gclient_utils.PrintWarnings()
sbc@chromium.org013731e2015-02-26 18:28:43 +00002455 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002456
2457
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00002458if '__main__' == __name__:
sbc@chromium.org013731e2015-02-26 18:28:43 +00002459 try:
2460 sys.exit(main(sys.argv[1:]))
2461 except KeyboardInterrupt:
2462 sys.stderr.write('interrupted\n')
2463 sys.exit(1)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002464
2465# vim: ts=2:sw=2:tw=80:et: