blob: b95979f20ebf7e09fa1bcd91f05ed6939e7b75b6 [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
Tom Andersonc31ae0b2018-02-06 14:48:56 -080078#
79# Specifying a target CPU
80# To specify a target CPU, the variables target_cpu and target_cpu_only
81# are available and are analagous to target_os and target_os_only.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000082
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +000083from __future__ import print_function
84
maruel@chromium.org39c0b222013-08-17 16:57:01 +000085__version__ = '0.7'
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000086
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +020087import collections
maruel@chromium.org9e5317a2010-08-13 20:35:11 +000088import copy
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +000089import json
maruel@chromium.org754960e2009-09-21 12:31:05 +000090import logging
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000091import optparse
92import os
bradnelson@google.com4949dab2012-04-19 16:41:07 +000093import platform
maruel@chromium.org621939b2010-08-10 20:12:00 +000094import posixpath
msb@chromium.org2e38de72009-09-28 17:04:47 +000095import pprint
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000096import re
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000097import sys
ilevy@chromium.orgc28d3772013-07-12 19:42:37 +000098import time
bradnelson@google.com4949dab2012-04-19 16:41:07 +000099import urlparse
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000100
Tom Andersonc31ae0b2018-02-06 14:48:56 -0800101import detect_host_arch
maruel@chromium.org35625c72011-03-23 17:34:02 +0000102import fix_encoding
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +0200103import gclient_eval
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000104import gclient_scm
105import gclient_utils
szager@chromium.org848fd492014-04-09 19:06:44 +0000106import git_cache
nasser@codeaurora.org1f7a3d12010-02-04 15:11:50 +0000107from third_party.repo.progress import Progress
maruel@chromium.org39c0b222013-08-17 16:57:01 +0000108import subcommand
maruel@chromium.org31cb48a2011-04-04 18:01:36 +0000109import subprocess2
iannucci@chromium.org596cd5c2016-04-04 21:34:39 +0000110import setup_color
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000111
112
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +0200113class GNException(Exception):
114 pass
115
116
117def ToGNString(value, allow_dicts = True):
118 """Returns a stringified GN equivalent of the Python value.
119
120 allow_dicts indicates if this function will allow converting dictionaries
121 to GN scopes. This is only possible at the top level, you can't nest a
122 GN scope in a list, so this should be set to False for recursive calls."""
123 if isinstance(value, basestring):
124 if value.find('\n') >= 0:
125 raise GNException("Trying to print a string with a newline in it.")
126 return '"' + \
127 value.replace('\\', '\\\\').replace('"', '\\"').replace('$', '\\$') + \
128 '"'
129
130 if isinstance(value, unicode):
131 return ToGNString(value.encode('utf-8'))
132
133 if isinstance(value, bool):
134 if value:
135 return "true"
136 return "false"
137
138 # NOTE: some type handling removed compared to chromium/src copy.
139
140 raise GNException("Unsupported type when printing to GN.")
141
142
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200143class Hook(object):
144 """Descriptor of command ran before/after sync or on demand."""
145
Paweł Hajdan, Jr032d5452017-06-22 20:43:53 +0200146 def __init__(self, action, pattern=None, name=None, cwd=None, condition=None,
Daniel Chenga0c5f082017-10-19 13:35:19 -0700147 variables=None, verbose=False):
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200148 """Constructor.
149
150 Arguments:
151 action (list of basestring): argv of the command to run
152 pattern (basestring regex): noop with git; deprecated
153 name (basestring): optional name; no effect on operation
Paweł Hajdan, Jrc9364392017-06-14 17:11:56 +0200154 cwd (basestring): working directory to use
Paweł Hajdan, Jr032d5452017-06-22 20:43:53 +0200155 condition (basestring): condition when to run the hook
156 variables (dict): variables for evaluating the condition
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200157 """
158 self._action = gclient_utils.freeze(action)
159 self._pattern = pattern
160 self._name = name
Paweł Hajdan, Jrc9364392017-06-14 17:11:56 +0200161 self._cwd = cwd
Paweł Hajdan, Jr032d5452017-06-22 20:43:53 +0200162 self._condition = condition
163 self._variables = variables
Daniel Chenga0c5f082017-10-19 13:35:19 -0700164 self._verbose = verbose
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200165
166 @staticmethod
Michael Moss42d02c22018-02-05 10:32:24 -0800167 def from_dict(d, variables=None, verbose=False, conditions=None):
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200168 """Creates a Hook instance from a dict like in the DEPS file."""
Michael Moss42d02c22018-02-05 10:32:24 -0800169 # Merge any local and inherited conditions.
170 if conditions and d.get('condition'):
171 condition = '(%s) and (%s)' % (conditions, d['condition'])
172 else:
173 condition = conditions or d.get('condition')
Paweł Hajdan, Jr032d5452017-06-22 20:43:53 +0200174 return Hook(
175 d['action'],
176 d.get('pattern'),
177 d.get('name'),
178 d.get('cwd'),
Michael Moss42d02c22018-02-05 10:32:24 -0800179 condition,
Daniel Chenga0c5f082017-10-19 13:35:19 -0700180 variables=variables,
181 # Always print the header if not printing to a TTY.
182 verbose=verbose or not setup_color.IS_TTY)
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200183
184 @property
185 def action(self):
186 return self._action
187
188 @property
189 def pattern(self):
190 return self._pattern
191
192 @property
193 def name(self):
194 return self._name
195
Paweł Hajdan, Jrecf53fe2017-09-29 18:28:49 +0200196 @property
197 def condition(self):
198 return self._condition
199
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200200 def matches(self, file_list):
201 """Returns true if the pattern matches any of files in the list."""
202 if not self._pattern:
203 return True
204 pattern = re.compile(self._pattern)
205 return bool([f for f in file_list if pattern.search(f)])
206
207 def run(self, root):
Paweł Hajdan, Jr032d5452017-06-22 20:43:53 +0200208 """Executes the hook's command (provided the condition is met)."""
209 if (self._condition and
210 not gclient_eval.EvaluateCondition(self._condition, self._variables)):
211 return
212
Paweł Hajdan, Jrfc6196b2017-07-27 13:15:25 +0200213 cmd = [arg.format(**self._variables) for arg in self._action]
214
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200215 if cmd[0] == 'python':
216 # If the hook specified "python" as the first item, the action is a
217 # Python script. Run it by starting a new copy of the same
218 # interpreter.
219 cmd[0] = sys.executable
Nodir Turakulov0ffcc872017-11-09 16:44:58 -0800220 elif cmd[0] == 'vpython' and _detect_host_os() == 'win':
221 cmd[0] += '.bat'
Paweł Hajdan, Jrc9364392017-06-14 17:11:56 +0200222
223 cwd = root
224 if self._cwd:
225 cwd = os.path.join(cwd, self._cwd)
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200226 try:
227 start_time = time.time()
228 gclient_utils.CheckCallAndFilterAndHeader(
Daniel Chenga0c5f082017-10-19 13:35:19 -0700229 cmd, cwd=cwd, always=self._verbose)
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200230 except (gclient_utils.Error, subprocess2.CalledProcessError) as e:
231 # Use a discrete exit status code of 2 to indicate that a hook action
232 # failed. Users of this script may wish to treat hook action failures
233 # differently from VC failures.
234 print('Error: %s' % str(e), file=sys.stderr)
235 sys.exit(2)
236 finally:
237 elapsed_time = time.time() - start_time
238 if elapsed_time > 10:
239 print("Hook '%s' took %.2f secs" % (
240 gclient_utils.CommandToStr(cmd), elapsed_time))
241
242
Paweł Hajdan, Jrfc6196b2017-07-27 13:15:25 +0200243class DependencySettings(object):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000244 """Immutable configuration settings."""
245 def __init__(
Paweł Hajdan, Jrfc6196b2017-07-27 13:15:25 +0200246 self, parent, raw_url, url, managed, custom_deps, custom_vars,
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +0200247 custom_hooks, deps_file, should_process, relative,
248 condition, condition_value):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000249 # These are not mutable:
250 self._parent = parent
mmoss@chromium.org8f93f792014-08-26 23:24:09 +0000251 self._deps_file = deps_file
Paweł Hajdan, Jrfc6196b2017-07-27 13:15:25 +0200252 self._raw_url = raw_url
maruel@chromium.org064186c2011-09-27 23:53:33 +0000253 self._url = url
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +0200254 # The condition as string (or None). Useful to keep e.g. for flatten.
255 self._condition = condition
256 # Boolean value of the condition. If there's no condition, just True.
257 self._condition_value = condition_value
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000258 # 'managed' determines whether or not this dependency is synced/updated by
259 # gclient after gclient checks it out initially. The difference between
260 # 'managed' and 'should_process' is that the user specifies 'managed' via
smutae7ea312016-07-18 11:59:41 -0700261 # the --unmanaged command-line flag or a .gclient config, where
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000262 # 'should_process' is dynamically set by gclient if it goes over its
263 # recursion limit and controls gclient's behavior so it does not misbehave.
264 self._managed = managed
265 self._should_process = should_process
agabledce6ddc2016-09-08 10:02:16 -0700266 # If this is a recursed-upon sub-dependency, and the parent has
267 # use_relative_paths set, then this dependency should check out its own
268 # dependencies relative to that parent's path for this, rather than
269 # relative to the .gclient file.
270 self._relative = relative
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +0000271 # This is a mutable value which has the list of 'target_os' OSes listed in
272 # the current deps file.
273 self.local_target_os = None
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000274
275 # These are only set in .gclient and not in DEPS files.
276 self._custom_vars = custom_vars or {}
277 self._custom_deps = custom_deps or {}
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000278 self._custom_hooks = custom_hooks or []
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000279
maruel@chromium.org064186c2011-09-27 23:53:33 +0000280 # Post process the url to remove trailing slashes.
Edward Lemure7273d22018-05-10 19:13:51 -0400281 if isinstance(self.url, basestring):
maruel@chromium.org064186c2011-09-27 23:53:33 +0000282 # urls are sometime incorrectly written as proto://host/path/@rev. Replace
283 # it to proto://host/path@rev.
Edward Lemure7273d22018-05-10 19:13:51 -0400284 self.set_url(self.url.replace('/@', '@'))
285 elif not isinstance(self.url, (None.__class__)):
maruel@chromium.org064186c2011-09-27 23:53:33 +0000286 raise gclient_utils.Error(
Paweł Hajdan, Jr7e9303b2017-05-23 14:38:27 +0200287 ('dependency url must be either string or None, '
Edward Lemure7273d22018-05-10 19:13:51 -0400288 'instead of %s') % self.url.__class__.__name__)
289
mmoss@chromium.orgd0b272b2013-01-30 23:55:33 +0000290 # Make any deps_file path platform-appropriate.
John Budorick0f7b2002018-01-19 15:46:17 -0800291 if self._deps_file:
292 for sep in ['/', '\\']:
293 self._deps_file = self._deps_file.replace(sep, os.sep)
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000294
295 @property
296 def deps_file(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000297 return self._deps_file
298
299 @property
300 def managed(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000301 return self._managed
302
303 @property
304 def parent(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000305 return self._parent
306
307 @property
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000308 def root(self):
309 """Returns the root node, a GClient object."""
310 if not self.parent:
311 # This line is to signal pylint that it could be a GClient instance.
312 return self or GClient(None, None)
313 return self.parent.root
314
315 @property
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000316 def should_process(self):
317 """True if this dependency should be processed, i.e. checked out."""
318 return self._should_process
319
320 @property
321 def custom_vars(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000322 return self._custom_vars.copy()
323
324 @property
325 def custom_deps(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000326 return self._custom_deps.copy()
327
maruel@chromium.org064186c2011-09-27 23:53:33 +0000328 @property
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000329 def custom_hooks(self):
330 return self._custom_hooks[:]
331
332 @property
Paweł Hajdan, Jrfc6196b2017-07-27 13:15:25 +0200333 def raw_url(self):
334 """URL before variable expansion."""
335 return self._raw_url
336
337 @property
maruel@chromium.org064186c2011-09-27 23:53:33 +0000338 def url(self):
Paweł Hajdan, Jrfc6196b2017-07-27 13:15:25 +0200339 """URL after variable expansion."""
maruel@chromium.org064186c2011-09-27 23:53:33 +0000340 return self._url
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000341
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000342 @property
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +0200343 def condition(self):
344 return self._condition
345
346 @property
347 def condition_value(self):
348 return self._condition_value
349
350 @property
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +0000351 def target_os(self):
352 if self.local_target_os is not None:
353 return tuple(set(self.local_target_os).union(self.parent.target_os))
354 else:
355 return self.parent.target_os
356
Tom Andersonc31ae0b2018-02-06 14:48:56 -0800357 @property
358 def target_cpu(self):
359 return self.parent.target_cpu
360
Edward Lemure7273d22018-05-10 19:13:51 -0400361 def set_url(self, url):
362 self._url = url
363
364 def set_raw_url(self, url):
365 self._raw_url = url
366
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000367 def get_custom_deps(self, name, url):
368 """Returns a custom deps if applicable."""
369 if self.parent:
370 url = self.parent.get_custom_deps(name, url)
371 # None is a valid return value to disable a dependency.
372 return self.custom_deps.get(name, url)
373
maruel@chromium.org064186c2011-09-27 23:53:33 +0000374
375class Dependency(gclient_utils.WorkItem, DependencySettings):
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000376 """Object that represents a dependency checkout."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000377
Paweł Hajdan, Jrfc6196b2017-07-27 13:15:25 +0200378 def __init__(self, parent, name, raw_url, url, managed, custom_deps,
agabledce6ddc2016-09-08 10:02:16 -0700379 custom_vars, custom_hooks, deps_file, should_process,
Edward Lemur231f5ea2018-01-31 19:02:36 +0100380 relative, condition, condition_value, print_outbuf=False):
maruel@chromium.org6ca8bf82011-09-19 23:04:30 +0000381 gclient_utils.WorkItem.__init__(self, name)
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000382 DependencySettings.__init__(
Paweł Hajdan, Jrfc6196b2017-07-27 13:15:25 +0200383 self, parent, raw_url, url, managed, custom_deps, custom_vars,
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +0200384 custom_hooks, deps_file, should_process, relative,
385 condition, condition_value)
maruel@chromium.org68988972011-09-20 14:11:42 +0000386
387 # This is in both .gclient and DEPS files:
maruel@chromium.org064186c2011-09-27 23:53:33 +0000388 self._deps_hooks = []
maruel@chromium.org68988972011-09-20 14:11:42 +0000389
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000390 self._pre_deps_hooks = []
391
maruel@chromium.org68988972011-09-20 14:11:42 +0000392 # Calculates properties:
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +0000393 self._dependencies = []
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +0200394 self._vars = {}
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +0200395 self._os_dependencies = {}
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +0200396 self._os_deps_hooks = {}
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200397
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000398 # A cache of the files affected by the current operation, necessary for
399 # hooks.
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000400 self._file_list = []
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000401 # List of host names from which dependencies are allowed.
402 # Default is an empty set, meaning unspecified in DEPS file, and hence all
403 # hosts will be allowed. Non-empty set means whitelist of hosts.
404 # allowed_hosts var is scoped to its DEPS file, and so it isn't recursive.
405 self._allowed_hosts = frozenset()
Michael Moss848c86e2018-05-03 16:05:50 -0700406 self._gn_args_from = None
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +0200407 # Spec for .gni output to write (if any).
408 self._gn_args_file = None
409 self._gn_args = []
maruel@chromium.org85c2a192010-07-22 21:14:43 +0000410 # If it is not set to True, the dependency wasn't processed for its child
411 # dependency, i.e. its DEPS wasn't read.
maruel@chromium.org064186c2011-09-27 23:53:33 +0000412 self._deps_parsed = False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000413 # This dependency has been processed, i.e. checked out
maruel@chromium.org064186c2011-09-27 23:53:33 +0000414 self._processed = False
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000415 # This dependency had its pre-DEPS hooks run
416 self._pre_deps_hooks_ran = False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000417 # This dependency had its hook run
maruel@chromium.org064186c2011-09-27 23:53:33 +0000418 self._hooks_ran = False
kustermann@google.coma692e8f2013-04-18 08:32:04 +0000419 # This is the scm used to checkout self.url. It may be used by dependencies
420 # to get the datetime of the revision we checked out.
421 self._used_scm = None
szager@chromium.org4ad264b2014-05-20 04:43:47 +0000422 self._used_revision = None
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +0000423 # The actual revision we ended up getting, or None if that information is
424 # unavailable
425 self._got_revision = None
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000426
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000427 # This is a mutable value that overrides the normal recursion limit for this
428 # dependency. It is read from the actual DEPS file so cannot be set on
429 # class instantiation.
430 self.recursion_override = None
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000431 # recursedeps is a mutable value that selectively overrides the default
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000432 # 'no recursion' setting on a dep-by-dep basis. It will replace
433 # recursion_override.
iannucci@chromium.orgafa11ac2016-05-04 22:17:34 +0000434 #
435 # It will be a dictionary of {deps_name: {"deps_file": depfile_name}} or
436 # None.
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000437 self.recursedeps = None
Edward Lemure7273d22018-05-10 19:13:51 -0400438
439 self._OverrideUrl()
hinoka885e5b12016-06-08 14:40:09 -0700440 # This is inherited from WorkItem. We want the URL to be a resource.
Edward Lemure7273d22018-05-10 19:13:51 -0400441 if self.url and isinstance(self.url, basestring):
hinoka885e5b12016-06-08 14:40:09 -0700442 # The url is usually given to gclient either as https://blah@123
qyearsley12fa6ff2016-08-24 09:18:40 -0700443 # or just https://blah. The @123 portion is irrelevant.
Edward Lemure7273d22018-05-10 19:13:51 -0400444 self.resources.append(self.url.split('@')[0])
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000445
Edward Lemur231f5ea2018-01-31 19:02:36 +0100446 # Controls whether we want to print git's output when we first clone the
447 # dependency
448 self.print_outbuf = print_outbuf
449
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000450 if not self.name and self.parent:
451 raise gclient_utils.Error('Dependency without name')
452
Edward Lemure7273d22018-05-10 19:13:51 -0400453 def _OverrideUrl(self):
454 """Resolves the parsed url from the parent hierarchy."""
455 parsed_url = self.get_custom_deps(self._name, self.url)
456 if parsed_url != self.url:
457 logging.info('Dependency(%s)._OverrideUrl(%s) -> %s', self._name,
458 self.url, parsed_url)
459 self.set_url(parsed_url)
460
461 elif isinstance(self.url, basestring):
462 parsed_url = urlparse.urlparse(self.url)
463 if (not parsed_url[0] and
464 not re.match(r'^\w+\@[\w\.-]+\:[\w\/]+', parsed_url[2])):
465 path = parsed_url[2]
466 if not path.startswith('/'):
467 raise gclient_utils.Error(
468 'relative DEPS entry \'%s\' must begin with a slash' % self.url)
469 # A relative url. Get the parent url, strip from the last '/'
470 # (equivalent to unix basename), and append the relative url.
471 parent_url = self.parent.url
472 parsed_url = parent_url[:parent_url.rfind('/')] + self.url
473 logging.info('Dependency(%s)._OverrideUrl(%s) -> %s', self.name,
474 self.url, parsed_url)
475 self.set_url(parsed_url)
476
477 elif self.url is None:
478 logging.info('Dependency(%s)._OverrideUrl(None) -> None', self._name)
479
480 else:
481 raise gclient_utils.Error('Unknown url type')
482
483 def PinToActualRevision(self):
484 """Updates self.url and self.raw_url to the revision checked out on disk."""
485 if self.url is None:
486 return
487 url = raw_url = None
Edward Lemurbabd0982018-05-11 13:32:37 -0400488 scm = self.CreateSCM()
Edward Lemure7273d22018-05-10 19:13:51 -0400489 if os.path.isdir(scm.checkout_path):
490 revision = scm.revinfo(None, None, None)
491 url = '%s@%s' % (gclient_utils.SplitUrlRevision(self.url)[0], revision)
492 raw_url = '%s@%s' % (
493 gclient_utils.SplitUrlRevision(self.raw_url)[0], revision)
494 self.set_url(url)
495 self.set_raw_url(raw_url)
496
John Budorick0f7b2002018-01-19 15:46:17 -0800497 def ToLines(self):
498 s = []
499 condition_part = ([' "condition": %r,' % self.condition]
500 if self.condition else [])
501 s.extend([
502 ' # %s' % self.hierarchy(include_url=False),
503 ' "%s": {' % (self.name,),
504 ' "url": "%s",' % (self.raw_url,),
505 ] + condition_part + [
506 ' },',
507 '',
508 ])
509 return s
510
maruel@chromium.org470b5432011-10-11 18:18:19 +0000511 @property
512 def requirements(self):
513 """Calculate the list of requirements."""
514 requirements = set()
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000515 # self.parent is implicitly a requirement. This will be recursive by
516 # definition.
517 if self.parent and self.parent.name:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000518 requirements.add(self.parent.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000519
520 # For a tree with at least 2 levels*, the leaf node needs to depend
521 # on the level higher up in an orderly way.
522 # This becomes messy for >2 depth as the DEPS file format is a dictionary,
523 # thus unsorted, while the .gclient format is a list thus sorted.
524 #
525 # * _recursion_limit is hard coded 2 and there is no hope to change this
526 # value.
527 #
528 # Interestingly enough, the following condition only works in the case we
529 # want: self is a 2nd level node. 3nd level node wouldn't need this since
530 # they already have their parent as a requirement.
maruel@chromium.org470b5432011-10-11 18:18:19 +0000531 if self.parent and self.parent.parent and not self.parent.parent.parent:
532 requirements |= set(i.name for i in self.root.dependencies if i.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000533
maruel@chromium.org470b5432011-10-11 18:18:19 +0000534 if self.name:
535 requirements |= set(
536 obj.name for obj in self.root.subtree(False)
537 if (obj is not self
538 and obj.name and
539 self.name.startswith(posixpath.join(obj.name, ''))))
540 requirements = tuple(sorted(requirements))
541 logging.info('Dependency(%s).requirements = %s' % (self.name, requirements))
542 return requirements
543
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000544 @property
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000545 def try_recursedeps(self):
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000546 """Returns False if recursion_override is ever specified."""
547 if self.recursion_override is not None:
548 return False
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000549 return self.parent.try_recursedeps
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000550
551 @property
552 def recursion_limit(self):
553 """Returns > 0 if this dependency is not too recursed to be processed."""
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000554 # We continue to support the absence of recursedeps until tools and DEPS
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000555 # using recursion_override are updated.
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000556 if self.try_recursedeps and self.parent.recursedeps != None:
557 if self.name in self.parent.recursedeps:
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000558 return 1
559
560 if self.recursion_override is not None:
561 return self.recursion_override
562 return max(self.parent.recursion_limit - 1, 0)
563
maruel@chromium.org470b5432011-10-11 18:18:19 +0000564 def verify_validity(self):
565 """Verifies that this Dependency is fine to add as a child of another one.
566
567 Returns True if this entry should be added, False if it is a duplicate of
568 another entry.
569 """
570 logging.info('Dependency(%s).verify_validity()' % self.name)
571 if self.name in [s.name for s in self.parent.dependencies]:
572 raise gclient_utils.Error(
573 'The same name "%s" appears multiple times in the deps section' %
574 self.name)
575 if not self.should_process:
576 # Return early, no need to set requirements.
577 return True
578
579 # This require a full tree traversal with locks.
580 siblings = [d for d in self.root.subtree(False) if d.name == self.name]
581 for sibling in siblings:
maruel@chromium.orgb848d5b2012-10-10 23:25:50 +0000582 # Allow to have only one to be None or ''.
Edward Lemure7273d22018-05-10 19:13:51 -0400583 if self.url != sibling.url and bool(self.url) == bool(sibling.url):
maruel@chromium.org470b5432011-10-11 18:18:19 +0000584 raise gclient_utils.Error(
maruel@chromium.orgb848d5b2012-10-10 23:25:50 +0000585 ('Dependency %s specified more than once:\n'
586 ' %s [%s]\n'
587 'vs\n'
588 ' %s [%s]') % (
589 self.name,
590 sibling.hierarchy(),
Edward Lemure7273d22018-05-10 19:13:51 -0400591 sibling.url,
maruel@chromium.orgb848d5b2012-10-10 23:25:50 +0000592 self.hierarchy(),
Edward Lemure7273d22018-05-10 19:13:51 -0400593 self.url))
maruel@chromium.org470b5432011-10-11 18:18:19 +0000594 # In theory we could keep it as a shadow of the other one. In
595 # practice, simply ignore it.
596 logging.warn('Won\'t process duplicate dependency %s' % sibling)
597 return False
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000598 return True
maruel@chromium.org064186c2011-09-27 23:53:33 +0000599
bratell@opera.comed2b4fe2013-12-16 14:34:12 +0000600 @staticmethod
Paweł Hajdan, Jr5ec77132017-08-16 19:21:06 +0200601 def MergeWithOsDeps(deps, deps_os, target_os_list, process_all_deps):
bratell@opera.comed2b4fe2013-12-16 14:34:12 +0000602 """Returns a new "deps" structure that is the deps sent in updated
603 with information from deps_os (the deps_os section of the DEPS
604 file) that matches the list of target os."""
bratell@opera.comed2b4fe2013-12-16 14:34:12 +0000605 new_deps = deps.copy()
Paweł Hajdan, Jrfd0057e2017-06-21 14:20:21 +0200606 for dep_os, os_deps in deps_os.iteritems():
607 for key, value in os_deps.iteritems():
608 if value is None:
609 # Make this condition very visible, so it's not a silent failure.
610 # It's unclear how to support None override in deps_os.
611 logging.error('Ignoring %r:%r in %r deps_os', key, value, dep_os)
612 continue
613
614 # Normalize value to be a dict which contains |should_process| metadata.
615 if isinstance(value, basestring):
616 value = {'url': value}
617 assert isinstance(value, collections.Mapping), (key, value)
Paweł Hajdan, Jr5ec77132017-08-16 19:21:06 +0200618 value['should_process'] = dep_os in target_os_list or process_all_deps
Paweł Hajdan, Jrfd0057e2017-06-21 14:20:21 +0200619
620 # Handle collisions/overrides.
621 if key in new_deps and new_deps[key] != value:
622 # Normalize the existing new_deps entry.
623 if isinstance(new_deps[key], basestring):
624 new_deps[key] = {'url': new_deps[key]}
625 assert isinstance(new_deps[key],
626 collections.Mapping), (key, new_deps[key])
627
628 # It's OK if the "override" sets the key to the same value.
629 # This is mostly for legacy reasons to keep existing DEPS files
630 # working. Often mac/ios and unix/android will do this.
631 if value['url'] != new_deps[key]['url']:
632 raise gclient_utils.Error(
633 ('Value from deps_os (%r; %r: %r) conflicts with existing deps '
634 'entry (%r).') % (dep_os, key, value, new_deps[key]))
635
636 # We'd otherwise overwrite |should_process| metadata, but a dep should
637 # be processed if _any_ of its references call for that.
638 value['should_process'] = (
639 value['should_process'] or
640 new_deps[key].get('should_process', True))
641
642 new_deps[key] = value
643
bratell@opera.comed2b4fe2013-12-16 14:34:12 +0000644 return new_deps
645
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200646 def _postprocess_deps(self, deps, rel_prefix):
647 """Performs post-processing of deps compared to what's in the DEPS file."""
Paweł Hajdan, Jr4426eaf2017-06-13 12:25:47 +0200648 # Make sure the dict is mutable, e.g. in case it's frozen.
649 deps = dict(deps)
650
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200651 # If a line is in custom_deps, but not in the solution, we want to append
652 # this line to the solution.
653 for d in self.custom_deps:
654 if d not in deps:
655 deps[d] = self.custom_deps[d]
Michael Moss42d02c22018-02-05 10:32:24 -0800656 # Make child deps conditional on any parent conditions. This ensures that,
657 # when flattened, recursed entries have the correct restrictions, even if
658 # not explicitly set in the recursed DEPS file. For instance, if
659 # "src/ios_foo" is conditional on "checkout_ios=True", then anything
660 # recursively included by "src/ios_foo/DEPS" should also require
661 # "checkout_ios=True".
662 if self.condition:
663 for dname, dval in deps.iteritems():
664 if isinstance(dval, basestring):
665 dval = {'url': dval}
666 deps[dname] = dval
667 else:
668 assert isinstance(dval, collections.Mapping)
669 if dval.get('condition'):
670 dval['condition'] = '(%s) and (%s)' % (
671 dval['condition'], self.condition)
672 else:
673 dval['condition'] = self.condition
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200674
675 if rel_prefix:
676 logging.warning('use_relative_paths enabled.')
677 rel_deps = {}
678 for d, url in deps.items():
679 # normpath is required to allow DEPS to use .. in their
680 # dependency local path.
681 rel_deps[os.path.normpath(os.path.join(rel_prefix, d))] = url
682 logging.warning('Updating deps by prepending %s.', rel_prefix)
683 deps = rel_deps
684
685 return deps
686
687 def _deps_to_objects(self, deps, use_relative_paths):
688 """Convert a deps dict to a dict of Dependency objects."""
689 deps_to_add = []
690 for name, dep_value in deps.iteritems():
691 should_process = self.recursion_limit and self.should_process
692 deps_file = self.deps_file
693 if self.recursedeps is not None:
694 ent = self.recursedeps.get(name)
695 if ent is not None:
696 deps_file = ent['deps_file']
697 if dep_value is None:
698 continue
John Budorick0f7b2002018-01-19 15:46:17 -0800699
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200700 condition = None
701 condition_value = True
702 if isinstance(dep_value, basestring):
Paweł Hajdan, Jrfc6196b2017-07-27 13:15:25 +0200703 raw_url = dep_value
John Budorick0f7b2002018-01-19 15:46:17 -0800704 dep_type = None
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200705 else:
706 # This should be guaranteed by schema checking in gclient_eval.
707 assert isinstance(dep_value, collections.Mapping)
John Budorick0f7b2002018-01-19 15:46:17 -0800708 raw_url = dep_value.get('url')
Paweł Hajdan, Jrfd0057e2017-06-21 14:20:21 +0200709 # Take into account should_process metadata set by MergeWithOsDeps.
710 should_process = (should_process and
711 dep_value.get('should_process', True))
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200712 condition = dep_value.get('condition')
John Budorick0f7b2002018-01-19 15:46:17 -0800713 dep_type = dep_value.get('dep_type')
Paweł Hajdan, Jrfc6196b2017-07-27 13:15:25 +0200714
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200715 if condition:
Paweł Hajdan, Jrd3790252017-07-03 21:06:24 +0200716 condition_value = gclient_eval.EvaluateCondition(
717 condition, self.get_vars())
Paweł Hajdan, Jr5ec77132017-08-16 19:21:06 +0200718 if not self._get_option('process_all_deps', False):
719 should_process = should_process and condition_value
John Budorick0f7b2002018-01-19 15:46:17 -0800720
721 if dep_type == 'cipd':
John Budorickd3ba72b2018-03-20 12:27:42 -0700722 cipd_root = self.GetCipdRoot()
John Budorick0f7b2002018-01-19 15:46:17 -0800723 for package in dep_value.get('packages', []):
Shenghua Zhangf438ff72018-02-23 14:20:19 -0800724 if 'version' in package:
725 # Matches version to vars value.
726 raw_version = package['version']
727 version = raw_version.format(**self.get_vars())
728 package['version'] = version
John Budorick0f7b2002018-01-19 15:46:17 -0800729 deps_to_add.append(
730 CipdDependency(
731 self, name, package, cipd_root,
732 self.custom_vars, should_process, use_relative_paths,
733 condition, condition_value))
John Budorick0f7b2002018-01-19 15:46:17 -0800734 else:
Michael Moss012013e2018-03-30 17:03:19 -0700735 url = raw_url.format(**self.get_vars()) if raw_url else None
John Budorick0f7b2002018-01-19 15:46:17 -0800736 deps_to_add.append(
Edward Lemurb61d3872018-05-09 18:42:47 -0400737 GitDependency(
John Budorick0f7b2002018-01-19 15:46:17 -0800738 self, name, raw_url, url, None, None, self.custom_vars, None,
739 deps_file, should_process, use_relative_paths, condition,
740 condition_value))
741
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200742 deps_to_add.sort(key=lambda x: x.name)
743 return deps_to_add
744
Edward Lesmes6c24d372018-03-28 12:52:29 -0400745 def ParseDepsFile(self, expand_vars=True):
maruel@chromium.org271375b2010-06-23 19:17:38 +0000746 """Parses the DEPS file for this dependency."""
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000747 assert not self.deps_parsed
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000748 assert not self.dependencies
iannucci@chromium.org0c3f3052014-05-22 00:29:22 +0000749
750 deps_content = None
cmp@chromium.org76ce73c2014-07-02 00:13:18 +0000751
752 # First try to locate the configured deps file. If it's missing, fallback
753 # to DEPS.
754 deps_files = [self.deps_file]
755 if 'DEPS' not in deps_files:
756 deps_files.append('DEPS')
757 for deps_file in deps_files:
758 filepath = os.path.join(self.root.root_dir, self.name, deps_file)
759 if os.path.isfile(filepath):
760 logging.info(
761 'ParseDepsFile(%s): %s file found at %s', self.name, deps_file,
762 filepath)
763 break
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000764 logging.info(
cmp@chromium.org76ce73c2014-07-02 00:13:18 +0000765 'ParseDepsFile(%s): No %s file found at %s', self.name, deps_file,
766 filepath)
767
768 if os.path.isfile(filepath):
maruel@chromium.org46304292010-10-28 11:42:00 +0000769 deps_content = gclient_utils.FileRead(filepath)
cmp@chromium.org76ce73c2014-07-02 00:13:18 +0000770 logging.debug('ParseDepsFile(%s) read:\n%s', self.name, deps_content)
iannucci@chromium.org0c3f3052014-05-22 00:29:22 +0000771
772 local_scope = {}
773 if deps_content:
maruel@chromium.org46304292010-10-28 11:42:00 +0000774 try:
Edward Lesmes6c24d372018-03-28 12:52:29 -0400775 local_scope = gclient_eval.Parse(
776 deps_content, expand_vars,
777 self._get_option('validate_syntax', False),
Michael Mossda55cdc2018-04-06 18:37:19 -0700778 filepath, self.get_vars())
vapier@chromium.orga81a56e2015-11-11 07:56:13 +0000779 except SyntaxError as e:
maruel@chromium.org46304292010-10-28 11:42:00 +0000780 gclient_utils.SyntaxErrorToError(filepath, e)
iannucci@chromium.org0c3f3052014-05-22 00:29:22 +0000781
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000782 if 'allowed_hosts' in local_scope:
783 try:
784 self._allowed_hosts = frozenset(local_scope.get('allowed_hosts'))
785 except TypeError: # raised if non-iterable
786 pass
787 if not self._allowed_hosts:
788 logging.warning("allowed_hosts is specified but empty %s",
789 self._allowed_hosts)
790 raise gclient_utils.Error(
791 'ParseDepsFile(%s): allowed_hosts must be absent '
792 'or a non-empty iterable' % self.name)
793
Michael Moss848c86e2018-05-03 16:05:50 -0700794 self._gn_args_from = local_scope.get('gclient_gn_args_from')
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +0200795 self._gn_args_file = local_scope.get('gclient_gn_args_file')
796 self._gn_args = local_scope.get('gclient_gn_args', [])
Michael Moss848c86e2018-05-03 16:05:50 -0700797 # It doesn't make sense to set all of these, since setting gn_args_from to
798 # another DEPS will make gclient ignore any other local gn_args* settings.
799 assert not (self._gn_args_from and self._gn_args_file), \
800 'Only specify one of "gclient_gn_args_from" or ' \
801 '"gclient_gn_args_file + gclient_gn_args".'
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +0200802
Edward Lesmes0b899352018-03-19 21:59:55 +0000803 self._vars = local_scope.get('vars', {})
Paweł Hajdan, Jr1407d002017-08-01 20:01:01 +0200804 if self.parent:
805 for key, value in self.parent.get_vars().iteritems():
806 if key in self._vars:
807 self._vars[key] = value
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200808 # Since we heavily post-process things, freeze ones which should
809 # reflect original state of DEPS.
Paweł Hajdan, Jr1407d002017-08-01 20:01:01 +0200810 self._vars = gclient_utils.freeze(self._vars)
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200811
812 # If use_relative_paths is set in the DEPS file, regenerate
813 # the dictionary using paths relative to the directory containing
814 # the DEPS file. Also update recursedeps if use_relative_paths is
815 # enabled.
816 # If the deps file doesn't set use_relative_paths, but the parent did
817 # (and therefore set self.relative on this Dependency object), then we
818 # want to modify the deps and recursedeps by prepending the parent
819 # directory of this dependency.
820 use_relative_paths = local_scope.get('use_relative_paths', False)
821 rel_prefix = None
822 if use_relative_paths:
823 rel_prefix = self.name
824 elif self._relative:
825 rel_prefix = os.path.dirname(self.name)
826
Paweł Hajdan, Jrfc6196b2017-07-27 13:15:25 +0200827 deps = {}
828 for key, value in local_scope.get('deps', {}).iteritems():
829 deps[key.format(**self.get_vars())] = value
830
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200831 if 'recursion' in local_scope:
832 self.recursion_override = local_scope.get('recursion')
833 logging.warning(
834 'Setting %s recursion to %d.', self.name, self.recursion_limit)
835 self.recursedeps = None
836 if 'recursedeps' in local_scope:
837 self.recursedeps = {}
838 for ent in local_scope['recursedeps']:
839 if isinstance(ent, basestring):
840 self.recursedeps[ent] = {"deps_file": self.deps_file}
841 else: # (depname, depsfilename)
842 self.recursedeps[ent[0]] = {"deps_file": ent[1]}
843 logging.warning('Found recursedeps %r.', repr(self.recursedeps))
844
845 if rel_prefix:
846 logging.warning('Updating recursedeps by prepending %s.', rel_prefix)
847 rel_deps = {}
848 for depname, options in self.recursedeps.iteritems():
849 rel_deps[
850 os.path.normpath(os.path.join(rel_prefix, depname))] = options
851 self.recursedeps = rel_deps
Michael Moss848c86e2018-05-03 16:05:50 -0700852 # To get gn_args from another DEPS, that DEPS must be recursed into.
853 if self._gn_args_from:
854 assert self.recursedeps and self._gn_args_from in self.recursedeps, \
855 'The "gclient_gn_args_from" value must be in recursedeps.'
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200856
857 # If present, save 'target_os' in the local_target_os property.
858 if 'target_os' in local_scope:
859 self.local_target_os = local_scope['target_os']
860 # load os specific dependencies if defined. these dependencies may
861 # override or extend the values defined by the 'deps' member.
862 target_os_list = self.target_os
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +0200863 if 'deps_os' in local_scope:
864 for dep_os, os_deps in local_scope['deps_os'].iteritems():
865 self._os_dependencies[dep_os] = self._deps_to_objects(
866 self._postprocess_deps(os_deps, rel_prefix), use_relative_paths)
Paweł Hajdan, Jr357415c2017-07-24 14:35:28 +0200867 if target_os_list and not self._get_option(
868 'do_not_merge_os_specific_entries', False):
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +0200869 deps = self.MergeWithOsDeps(
Paweł Hajdan, Jr5ec77132017-08-16 19:21:06 +0200870 deps, local_scope['deps_os'], target_os_list,
871 self._get_option('process_all_deps', False))
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200872
873 deps_to_add = self._deps_to_objects(
874 self._postprocess_deps(deps, rel_prefix), use_relative_paths)
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000875
876 # override named sets of hooks by the custom hooks
877 hooks_to_run = []
878 hook_names_to_suppress = [c.get('name', '') for c in self.custom_hooks]
879 for hook in local_scope.get('hooks', []):
880 if hook.get('name', '') not in hook_names_to_suppress:
881 hooks_to_run.append(hook)
Scott Grahamc4826742017-05-11 16:59:23 -0700882 if 'hooks_os' in local_scope and target_os_list:
883 hooks_os = local_scope['hooks_os']
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +0200884
885 # Keep original contents of hooks_os for flatten.
886 for hook_os, os_hooks in hooks_os.iteritems():
887 self._os_deps_hooks[hook_os] = [
Michael Moss42d02c22018-02-05 10:32:24 -0800888 Hook.from_dict(hook, variables=self.get_vars(), verbose=True,
889 conditions=self.condition)
Paweł Hajdan, Jrd3790252017-07-03 21:06:24 +0200890 for hook in os_hooks]
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +0200891
Scott Grahamc4826742017-05-11 16:59:23 -0700892 # Specifically append these to ensure that hooks_os run after hooks.
Paweł Hajdan, Jr357415c2017-07-24 14:35:28 +0200893 if not self._get_option('do_not_merge_os_specific_entries', False):
894 for the_target_os in target_os_list:
895 the_target_os_hooks = hooks_os.get(the_target_os, [])
896 hooks_to_run.extend(the_target_os_hooks)
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000897
898 # add the replacements and any additions
899 for hook in self.custom_hooks:
900 if 'action' in hook:
901 hooks_to_run.append(hook)
902
Dirk Prankeda3a29e2017-02-27 15:29:36 -0800903 if self.recursion_limit:
Paweł Hajdan, Jr032d5452017-06-22 20:43:53 +0200904 self._pre_deps_hooks = [
Michael Moss42d02c22018-02-05 10:32:24 -0800905 Hook.from_dict(hook, variables=self.get_vars(), verbose=True,
906 conditions=self.condition)
Daniel Chenga0c5f082017-10-19 13:35:19 -0700907 for hook in local_scope.get('pre_deps_hooks', [])
908 ]
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000909
Paweł Hajdan, Jr357415c2017-07-24 14:35:28 +0200910 self.add_dependencies_and_close(deps_to_add, hooks_to_run)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000911 logging.info('ParseDepsFile(%s) done' % self.name)
912
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +0200913 def _get_option(self, attr, default):
914 obj = self
915 while not hasattr(obj, '_options'):
916 obj = obj.parent
917 return getattr(obj._options, attr, default)
918
Paweł Hajdan, Jr357415c2017-07-24 14:35:28 +0200919 def add_dependencies_and_close(self, deps_to_add, hooks):
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000920 """Adds the dependencies, hooks and mark the parsing as done."""
maruel@chromium.orgb9be0652011-10-14 18:05:40 +0000921 for dep in deps_to_add:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000922 if dep.verify_validity():
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000923 self.add_dependency(dep)
Daniel Chenga0c5f082017-10-19 13:35:19 -0700924 self._mark_as_parsed([
925 Hook.from_dict(
Michael Moss42d02c22018-02-05 10:32:24 -0800926 h, variables=self.get_vars(), verbose=self.root._options.verbose,
927 conditions=self.condition)
Daniel Chenga0c5f082017-10-19 13:35:19 -0700928 for h in hooks
929 ])
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000930
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000931 def findDepsFromNotAllowedHosts(self):
932 """Returns a list of depenecies from not allowed hosts.
933
934 If allowed_hosts is not set, allows all hosts and returns empty list.
935 """
936 if not self._allowed_hosts:
937 return []
938 bad_deps = []
939 for dep in self._dependencies:
szager@chromium.orgbd772dd2014-11-05 18:43:08 +0000940 # Don't enforce this for custom_deps.
941 if dep.name in self._custom_deps:
942 continue
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000943 if isinstance(dep.url, basestring):
944 parsed_url = urlparse.urlparse(dep.url)
945 if parsed_url.netloc and parsed_url.netloc not in self._allowed_hosts:
946 bad_deps.append(dep)
947 return bad_deps
948
Edward Lemure7273d22018-05-10 19:13:51 -0400949 def FuzzyMatchUrl(self, candidates):
Edward Lesmesbb16e332018-03-30 17:54:51 -0400950 """Attempts to find this dependency in the list of candidates.
951
Edward Lemure7273d22018-05-10 19:13:51 -0400952 It looks first for the URL of this dependency in the list of
Edward Lesmesbb16e332018-03-30 17:54:51 -0400953 candidates. If it doesn't succeed, and the URL ends in '.git', it will try
954 looking for the URL minus '.git'. Finally it will try to look for the name
955 of the dependency.
956
957 Args:
Edward Lesmesbb16e332018-03-30 17:54:51 -0400958 candidates: list, dict. The list of candidates in which to look for this
959 dependency. It can contain URLs as above, or dependency names like
960 "src/some/dep".
961
962 Returns:
963 If this dependency is not found in the list of candidates, returns None.
964 Otherwise, it returns under which name did we find this dependency:
965 - Its parsed url: "https://example.com/src.git'
966 - Its parsed url minus '.git': "https://example.com/src"
967 - Its name: "src"
968 """
Edward Lemure7273d22018-05-10 19:13:51 -0400969 if self.url:
970 origin, _ = gclient_utils.SplitUrlRevision(self.url)
Edward Lesmesbb16e332018-03-30 17:54:51 -0400971 if origin in candidates:
972 return origin
973 if origin.endswith('.git') and origin[:-len('.git')] in candidates:
974 return origin[:-len('.git')]
Edward Lesmes990148e2018-04-26 14:56:55 -0400975 if origin + '.git' in candidates:
976 return origin + '.git'
Edward Lesmesbb16e332018-03-30 17:54:51 -0400977 if self.name in candidates:
978 return self.name
979 return None
980
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +0000981 # Arguments number differs from overridden method
Quinten Yearsleyb2cc4a92016-12-15 13:53:26 -0800982 # pylint: disable=arguments-differ
Edward Lesmesc621b212018-03-21 20:26:56 -0400983 def run(self, revision_overrides, command, args, work_queue, options,
984 patch_refs):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000985 """Runs |command| then parse the DEPS file."""
maruel@chromium.org470b5432011-10-11 18:18:19 +0000986 logging.info('Dependency(%s).run()' % self.name)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000987 assert self._file_list == []
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000988 if not self.should_process:
989 return
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000990 # When running runhooks, there's no need to consult the SCM.
991 # All known hooks are expected to run unconditionally regardless of working
992 # copy state, so skip the SCM status check.
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +0200993 run_scm = command not in (
994 'flatten', 'runhooks', 'recurse', 'validate', None)
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000995 file_list = [] if not options.nohooks else None
Edward Lesmesbb16e332018-03-30 17:54:51 -0400996 revision_override = revision_overrides.pop(
Edward Lemure7273d22018-05-10 19:13:51 -0400997 self.FuzzyMatchUrl(revision_overrides), None)
998 if run_scm and self.url:
agabled437d762016-10-17 09:35:11 -0700999 # Create a shallow copy to mutate revision.
1000 options = copy.copy(options)
1001 options.revision = revision_override
1002 self._used_revision = options.revision
Edward Lemurbabd0982018-05-11 13:32:37 -04001003 self._used_scm = self.CreateSCM(out_cb=work_queue.out_cb)
agabled437d762016-10-17 09:35:11 -07001004 self._got_revision = self._used_scm.RunCommand(command, options, args,
1005 file_list)
Edward Lesmesc621b212018-03-21 20:26:56 -04001006
Edward Lemure7273d22018-05-10 19:13:51 -04001007 patch_repo = self.url.split('@')[0]
1008 patch_ref = patch_refs.pop(self.FuzzyMatchUrl(patch_refs), None)
Edward Lesmesc621b212018-03-21 20:26:56 -04001009 if command == 'update' and patch_ref is not None:
1010 self._used_scm.apply_patch_ref(patch_repo, patch_ref, options,
Edward Lesmesbb16e332018-03-30 17:54:51 -04001011 file_list)
Edward Lesmesc621b212018-03-21 20:26:56 -04001012
agabled437d762016-10-17 09:35:11 -07001013 if file_list:
1014 file_list = [os.path.join(self.name, f.strip()) for f in file_list]
maruel@chromium.org68988972011-09-20 14:11:42 +00001015
1016 # TODO(phajdan.jr): We should know exactly when the paths are absolute.
1017 # Convert all absolute paths to relative.
iannucci@chromium.org396e1a62013-07-03 19:41:04 +00001018 for i in range(len(file_list or [])):
maruel@chromium.org68988972011-09-20 14:11:42 +00001019 # It depends on the command being executed (like runhooks vs sync).
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001020 if not os.path.isabs(file_list[i]):
maruel@chromium.org68988972011-09-20 14:11:42 +00001021 continue
1022 prefix = os.path.commonprefix(
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001023 [self.root.root_dir.lower(), file_list[i].lower()])
1024 file_list[i] = file_list[i][len(prefix):]
maruel@chromium.org68988972011-09-20 14:11:42 +00001025 # Strip any leading path separators.
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001026 while file_list[i].startswith(('\\', '/')):
1027 file_list[i] = file_list[i][1:]
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001028
Edward Lesmes5d6cde32018-04-12 18:32:46 -04001029 if self.recursion_limit:
1030 self.ParseDepsFile(expand_vars=(command != 'flatten'))
1031
Edward Lemure7273d22018-05-10 19:13:51 -04001032 self._run_is_done(file_list or [])
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001033
1034 if self.recursion_limit:
Edward Lesmes5d6cde32018-04-12 18:32:46 -04001035 if command in ('update', 'revert') and not options.noprehooks:
1036 self.RunPreDepsHooks()
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001037 # Parse the dependencies of this dependency.
1038 for s in self.dependencies:
Paweł Hajdan, Jr4baaa112017-07-04 19:09:32 +02001039 if s.should_process:
1040 work_queue.enqueue(s)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001041
1042 if command == 'recurse':
agabled437d762016-10-17 09:35:11 -07001043 # Skip file only checkout.
Edward Lemurbabd0982018-05-11 13:32:37 -04001044 scm = self.GetScmName()
agabled437d762016-10-17 09:35:11 -07001045 if not options.scm or scm in options.scm:
1046 cwd = os.path.normpath(os.path.join(self.root.root_dir, self.name))
1047 # Pass in the SCM type as an env variable. Make sure we don't put
1048 # unicode strings in the environment.
1049 env = os.environ.copy()
1050 if scm:
1051 env['GCLIENT_SCM'] = str(scm)
Edward Lemure7273d22018-05-10 19:13:51 -04001052 if self.url:
1053 env['GCLIENT_URL'] = str(self.url)
agabled437d762016-10-17 09:35:11 -07001054 env['GCLIENT_DEP_PATH'] = str(self.name)
1055 if options.prepend_dir and scm == 'git':
1056 print_stdout = False
1057 def filter_fn(line):
1058 """Git-specific path marshaling. It is optimized for git-grep."""
ilevy@chromium.org0233ac22012-11-28 20:27:02 +00001059
agabled437d762016-10-17 09:35:11 -07001060 def mod_path(git_pathspec):
1061 match = re.match('^(\\S+?:)?([^\0]+)$', git_pathspec)
1062 modified_path = os.path.join(self.name, match.group(2))
1063 branch = match.group(1) or ''
1064 return '%s%s' % (branch, modified_path)
ilevy@chromium.org0233ac22012-11-28 20:27:02 +00001065
agabled437d762016-10-17 09:35:11 -07001066 match = re.match('^Binary file ([^\0]+) matches$', line)
1067 if match:
1068 print('Binary file %s matches\n' % mod_path(match.group(1)))
1069 return
ilevy@chromium.org0233ac22012-11-28 20:27:02 +00001070
agabled437d762016-10-17 09:35:11 -07001071 items = line.split('\0')
1072 if len(items) == 2 and items[1]:
1073 print('%s : %s' % (mod_path(items[0]), items[1]))
1074 elif len(items) >= 2:
1075 # Multiple null bytes or a single trailing null byte indicate
1076 # git is likely displaying filenames only (such as with -l)
1077 print('\n'.join(mod_path(path) for path in items if path))
1078 else:
1079 print(line)
1080 else:
1081 print_stdout = True
1082 filter_fn = None
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001083
Edward Lemure7273d22018-05-10 19:13:51 -04001084 if self.url is None:
agabled437d762016-10-17 09:35:11 -07001085 print('Skipped omitted dependency %s' % cwd, file=sys.stderr)
1086 elif os.path.isdir(cwd):
1087 try:
1088 gclient_utils.CheckCallAndFilter(
1089 args, cwd=cwd, env=env, print_stdout=print_stdout,
1090 filter_fn=filter_fn,
1091 )
1092 except subprocess2.CalledProcessError:
1093 if not options.ignore:
1094 raise
1095 else:
1096 print('Skipped missing %s' % cwd, file=sys.stderr)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001097
Edward Lemurbabd0982018-05-11 13:32:37 -04001098 def GetScmName(self):
Edward Lemurb61d3872018-05-09 18:42:47 -04001099 raise NotImplementedError()
John Budorick0f7b2002018-01-19 15:46:17 -08001100
Edward Lemurbabd0982018-05-11 13:32:37 -04001101 def CreateSCM(self, out_cb=None):
Edward Lemurb61d3872018-05-09 18:42:47 -04001102 raise NotImplementedError()
John Budorick0f7b2002018-01-19 15:46:17 -08001103
Dirk Pranke9f20d022017-10-11 18:36:54 -07001104 def HasGNArgsFile(self):
1105 return self._gn_args_file is not None
1106
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +02001107 def WriteGNArgsFile(self):
1108 lines = ['# Generated from %r' % self.deps_file]
Paweł Hajdan, Jrb495bf52017-09-25 19:33:50 +02001109 variables = self.get_vars()
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +02001110 for arg in self._gn_args:
Paweł Hajdan, Jre0214742017-09-28 12:21:01 +02001111 value = variables[arg]
1112 if isinstance(value, basestring):
1113 value = gclient_eval.EvaluateCondition(value, variables)
Paweł Hajdan, Jrb495bf52017-09-25 19:33:50 +02001114 lines.append('%s = %s' % (arg, ToGNString(value)))
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +02001115 with open(os.path.join(self.root.root_dir, self._gn_args_file), 'w') as f:
1116 f.write('\n'.join(lines))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001117
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001118 @gclient_utils.lockedmethod
Edward Lemure7273d22018-05-10 19:13:51 -04001119 def _run_is_done(self, file_list):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001120 # Both these are kept for hooks that are run as a separate tree traversal.
1121 self._file_list = file_list
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001122 self._processed = True
1123
szager@google.comb9a78d32012-03-13 18:46:21 +00001124 def GetHooks(self, options):
1125 """Evaluates all hooks, and return them in a flat list.
1126
1127 RunOnDeps() must have been called before to load the DEPS.
1128 """
1129 result = []
maruel@chromium.org68988972011-09-20 14:11:42 +00001130 if not self.should_process or not self.recursion_limit:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001131 # Don't run the hook when it is above recursion_limit.
szager@google.comb9a78d32012-03-13 18:46:21 +00001132 return result
maruel@chromium.orgdc7445d2010-07-09 21:05:29 +00001133 # If "--force" was specified, run all hooks regardless of what files have
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001134 # changed.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001135 if self.deps_hooks:
agabled437d762016-10-17 09:35:11 -07001136 # TODO(maruel): If the user is using git, then we don't know
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001137 # what files have changed so we always run all hooks. It'd be nice to fix
1138 # that.
Edward Lemurbabd0982018-05-11 13:32:37 -04001139 result.extend(self.deps_hooks)
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001140 for s in self.dependencies:
szager@google.comb9a78d32012-03-13 18:46:21 +00001141 result.extend(s.GetHooks(options))
1142 return result
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001143
Daniel Chenga0c5f082017-10-19 13:35:19 -07001144 def RunHooksRecursively(self, options, progress):
szager@google.comb9a78d32012-03-13 18:46:21 +00001145 assert self.hooks_ran == False
maruel@chromium.org064186c2011-09-27 23:53:33 +00001146 self._hooks_ran = True
Daniel Chenga0c5f082017-10-19 13:35:19 -07001147 hooks = self.GetHooks(options)
1148 if progress:
1149 progress._total = len(hooks)
1150 for hook in hooks:
Daniel Chenga0c5f082017-10-19 13:35:19 -07001151 if progress:
1152 progress.update(extra=hook.name or '')
Daniel Cheng93c5d602017-10-20 11:40:17 -07001153 hook.run(self.root.root_dir)
Daniel Chenga0c5f082017-10-19 13:35:19 -07001154 if progress:
1155 progress.end()
maruel@chromium.orgeaf61062010-07-07 18:42:39 +00001156
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001157 def RunPreDepsHooks(self):
1158 assert self.processed
1159 assert self.deps_parsed
1160 assert not self.pre_deps_hooks_ran
1161 assert not self.hooks_ran
1162 for s in self.dependencies:
1163 assert not s.processed
1164 self._pre_deps_hooks_ran = True
1165 for hook in self.pre_deps_hooks:
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +02001166 hook.run(self.root.root_dir)
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001167
John Budorickd3ba72b2018-03-20 12:27:42 -07001168 def GetCipdRoot(self):
1169 if self.root is self:
1170 # Let's not infinitely recurse. If this is root and isn't an
1171 # instance of GClient, do nothing.
1172 return None
1173 return self.root.GetCipdRoot()
1174
maruel@chromium.org0d812442010-08-10 12:41:08 +00001175 def subtree(self, include_all):
maruel@chromium.orgad3287e2011-10-03 19:15:10 +00001176 """Breadth first recursion excluding root node."""
maruel@chromium.orgf13a4182011-09-22 00:26:15 +00001177 dependencies = self.dependencies
1178 for d in dependencies:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001179 if d.should_process or include_all:
maruel@chromium.orgad3287e2011-10-03 19:15:10 +00001180 yield d
maruel@chromium.orgf13a4182011-09-22 00:26:15 +00001181 for d in dependencies:
maruel@chromium.orgad3287e2011-10-03 19:15:10 +00001182 for i in d.subtree(include_all):
1183 yield i
1184
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001185 @gclient_utils.lockedmethod
1186 def add_dependency(self, new_dep):
1187 self._dependencies.append(new_dep)
1188
1189 @gclient_utils.lockedmethod
Paweł Hajdan, Jr357415c2017-07-24 14:35:28 +02001190 def _mark_as_parsed(self, new_hooks):
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001191 self._deps_hooks.extend(new_hooks)
1192 self._deps_parsed = True
1193
maruel@chromium.org68988972011-09-20 14:11:42 +00001194 @property
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001195 @gclient_utils.lockedmethod
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +00001196 def dependencies(self):
1197 return tuple(self._dependencies)
1198
1199 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001200 @gclient_utils.lockedmethod
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02001201 def os_dependencies(self):
1202 return dict(self._os_dependencies)
1203
1204 @property
1205 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001206 def deps_hooks(self):
1207 return tuple(self._deps_hooks)
1208
1209 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001210 @gclient_utils.lockedmethod
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +02001211 def os_deps_hooks(self):
1212 return dict(self._os_deps_hooks)
1213
1214 @property
1215 @gclient_utils.lockedmethod
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001216 def pre_deps_hooks(self):
1217 return tuple(self._pre_deps_hooks)
1218
1219 @property
1220 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001221 def deps_parsed(self):
maruel@chromium.org3223edd2011-10-10 23:17:39 +00001222 """This is purely for debugging purposes. It's not used anywhere."""
maruel@chromium.org064186c2011-09-27 23:53:33 +00001223 return self._deps_parsed
1224
1225 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001226 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001227 def processed(self):
1228 return self._processed
1229
1230 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001231 @gclient_utils.lockedmethod
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001232 def pre_deps_hooks_ran(self):
1233 return self._pre_deps_hooks_ran
1234
1235 @property
1236 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001237 def hooks_ran(self):
1238 return self._hooks_ran
1239
1240 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001241 @gclient_utils.lockedmethod
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00001242 def allowed_hosts(self):
1243 return self._allowed_hosts
1244
1245 @property
1246 @gclient_utils.lockedmethod
maruel@chromium.org861fd0f2010-07-23 03:05:05 +00001247 def file_list(self):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001248 return tuple(self._file_list)
1249
1250 @property
kustermann@google.coma692e8f2013-04-18 08:32:04 +00001251 def used_scm(self):
1252 """SCMWrapper instance for this dependency or None if not processed yet."""
1253 return self._used_scm
1254
1255 @property
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00001256 @gclient_utils.lockedmethod
1257 def got_revision(self):
1258 return self._got_revision
1259
1260 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001261 def file_list_and_children(self):
1262 result = list(self.file_list)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +00001263 for d in self.dependencies:
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001264 result.extend(d.file_list_and_children)
maruel@chromium.org68988972011-09-20 14:11:42 +00001265 return tuple(result)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +00001266
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001267 def __str__(self):
1268 out = []
Edward Lemure7273d22018-05-10 19:13:51 -04001269 for i in ('name', 'url', 'custom_deps',
maruel@chromium.org3c74bc92011-09-15 19:17:21 +00001270 'custom_vars', 'deps_hooks', 'file_list', 'should_process',
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00001271 'processed', 'hooks_ran', 'deps_parsed', 'requirements',
1272 'allowed_hosts'):
maruel@chromium.org3c74bc92011-09-15 19:17:21 +00001273 # First try the native property if it exists.
1274 if hasattr(self, '_' + i):
1275 value = getattr(self, '_' + i, False)
1276 else:
1277 value = getattr(self, i, False)
1278 if value:
1279 out.append('%s: %s' % (i, value))
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001280
1281 for d in self.dependencies:
1282 out.extend([' ' + x for x in str(d).splitlines()])
1283 out.append('')
1284 return '\n'.join(out)
1285
1286 def __repr__(self):
1287 return '%s: %s' % (self.name, self.url)
1288
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001289 def hierarchy(self, include_url=True):
maruel@chromium.orgbc2d2f92010-07-22 21:26:48 +00001290 """Returns a human-readable hierarchical reference to a Dependency."""
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001291 def format_name(d):
1292 if include_url:
1293 return '%s(%s)' % (d.name, d.url)
1294 return d.name
1295 out = format_name(self)
maruel@chromium.orgbffb9042010-07-22 20:59:36 +00001296 i = self.parent
1297 while i and i.name:
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001298 out = '%s -> %s' % (format_name(i), out)
maruel@chromium.orgbffb9042010-07-22 20:59:36 +00001299 i = i.parent
1300 return out
1301
Michael Mossfe68c912018-03-22 19:19:35 -07001302 def hierarchy_data(self):
1303 """Returns a machine-readable hierarchical reference to a Dependency."""
1304 d = self
1305 out = []
1306 while d and d.name:
1307 out.insert(0, (d.name, d.url))
1308 d = d.parent
1309 return tuple(out)
1310
Paweł Hajdan, Jrd3790252017-07-03 21:06:24 +02001311 def get_vars(self):
1312 """Returns a dictionary of effective variable values
1313 (DEPS file contents with applied custom_vars overrides)."""
Paweł Hajdan, Jra3b67ae2017-08-30 15:18:21 +02001314 # Provide some built-in variables.
1315 result = {
Paweł Hajdan, Jrd325eb32017-10-03 17:43:37 +02001316 'checkout_android': 'android' in self.target_os,
Benjamin Pastene6fe29412018-01-23 15:35:58 -08001317 'checkout_chromeos': 'chromeos' in self.target_os,
Paweł Hajdan, Jrd325eb32017-10-03 17:43:37 +02001318 'checkout_fuchsia': 'fuchsia' in self.target_os,
1319 'checkout_ios': 'ios' in self.target_os,
1320 'checkout_linux': 'unix' in self.target_os,
1321 'checkout_mac': 'mac' in self.target_os,
1322 'checkout_win': 'win' in self.target_os,
1323 'host_os': _detect_host_os(),
Tom Andersonc31ae0b2018-02-06 14:48:56 -08001324
1325 'checkout_arm': 'arm' in self.target_cpu,
1326 'checkout_arm64': 'arm64' in self.target_cpu,
1327 'checkout_x86': 'x86' in self.target_cpu,
1328 'checkout_mips': 'mips' in self.target_cpu,
1329 'checkout_ppc': 'ppc' in self.target_cpu,
1330 'checkout_s390': 's390' in self.target_cpu,
1331 'checkout_x64': 'x64' in self.target_cpu,
1332 'host_cpu': detect_host_arch.HostArch(),
Paweł Hajdan, Jra3b67ae2017-08-30 15:18:21 +02001333 }
Michael Mossda55cdc2018-04-06 18:37:19 -07001334 # Variable precedence:
1335 # - built-in
1336 # - DEPS vars
1337 # - parents, from first to last
1338 # - custom_vars overrides
Paweł Hajdan, Jra3b67ae2017-08-30 15:18:21 +02001339 result.update(self._vars)
Michael Mossda55cdc2018-04-06 18:37:19 -07001340 if self.parent:
1341 parent_vars = self.parent.get_vars()
1342 result.update(parent_vars)
Paweł Hajdan, Jrd3790252017-07-03 21:06:24 +02001343 result.update(self.custom_vars or {})
1344 return result
1345
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001346
Paweł Hajdan, Jra3b67ae2017-08-30 15:18:21 +02001347_PLATFORM_MAPPING = {
1348 'cygwin': 'win',
1349 'darwin': 'mac',
1350 'linux2': 'linux',
1351 'win32': 'win',
Jaideep Bajwad05f3582017-09-11 12:31:48 -04001352 'aix6': 'aix',
Paweł Hajdan, Jra3b67ae2017-08-30 15:18:21 +02001353}
1354
1355
1356def _detect_host_os():
1357 return _PLATFORM_MAPPING[sys.platform]
1358
1359
Edward Lemurb61d3872018-05-09 18:42:47 -04001360class GitDependency(Dependency):
1361 """A Dependency object that represents a single git checkout."""
1362
1363 #override
Edward Lemurbabd0982018-05-11 13:32:37 -04001364 def GetScmName(self):
Edward Lemurb61d3872018-05-09 18:42:47 -04001365 """Always 'git'."""
Edward Lemurb61d3872018-05-09 18:42:47 -04001366 return 'git'
1367
1368 #override
Edward Lemurbabd0982018-05-11 13:32:37 -04001369 def CreateSCM(self, out_cb=None):
Edward Lemurb61d3872018-05-09 18:42:47 -04001370 """Create a Wrapper instance suitable for handling this git dependency."""
Edward Lemurbabd0982018-05-11 13:32:37 -04001371 return gclient_scm.GitWrapper(
1372 self.url, self.root.root_dir, self.name, self.outbuf, out_cb,
1373 print_outbuf=self.print_outbuf)
Edward Lemurb61d3872018-05-09 18:42:47 -04001374
1375
1376class GClient(GitDependency):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001377 """Object that represent a gclient checkout. A tree of Dependency(), one per
1378 solution or DEPS entry."""
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001379
1380 DEPS_OS_CHOICES = {
Jaideep Bajwad05f3582017-09-11 12:31:48 -04001381 "aix6": "unix",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001382 "win32": "win",
1383 "win": "win",
1384 "cygwin": "win",
1385 "darwin": "mac",
1386 "mac": "mac",
1387 "unix": "unix",
1388 "linux": "unix",
1389 "linux2": "unix",
maruel@chromium.org244e3442011-06-12 15:20:55 +00001390 "linux3": "unix",
szager@chromium.orgf8c95cd2012-06-01 22:26:52 +00001391 "android": "android",
Michael Mossc54fa812017-08-17 11:27:58 -07001392 "ios": "ios",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001393 }
1394
1395 DEFAULT_CLIENT_FILE_TEXT = ("""\
1396solutions = [
smutae7ea312016-07-18 11:59:41 -07001397 { "name" : "%(solution_name)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001398 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +00001399 "deps_file" : "%(deps_file)s",
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001400 "managed" : %(managed)s,
smutae7ea312016-07-18 11:59:41 -07001401 "custom_deps" : {
1402 },
Paweł Hajdan, Jr3ba2a7c2017-10-04 19:24:46 +02001403 "custom_vars": %(custom_vars)r,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001404 },
1405]
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001406cache_dir = %(cache_dir)r
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001407""")
1408
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001409 DEFAULT_SNAPSHOT_FILE_TEXT = ("""\
1410# Snapshot generated with gclient revinfo --snapshot
Edward Lesmesc2960242018-03-06 20:50:15 -05001411solutions = %(solution_list)s
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001412""")
1413
1414 def __init__(self, root_dir, options):
maruel@chromium.org0d812442010-08-10 12:41:08 +00001415 # Do not change previous behavior. Only solution level and immediate DEPS
1416 # are processed.
1417 self._recursion_limit = 2
Edward Lemurb61d3872018-05-09 18:42:47 -04001418 GitDependency.__init__(self, None, None, None, None, True, None, None, None,
1419 'unused', True, None, None, True)
maruel@chromium.org0d425922010-06-21 19:22:24 +00001420 self._options = options
maruel@chromium.org271375b2010-06-23 19:17:38 +00001421 if options.deps_os:
1422 enforced_os = options.deps_os.split(',')
1423 else:
1424 enforced_os = [self.DEPS_OS_CHOICES.get(sys.platform, 'unix')]
1425 if 'all' in enforced_os:
1426 enforced_os = self.DEPS_OS_CHOICES.itervalues()
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001427 self._enforced_os = tuple(set(enforced_os))
Tom Andersonc31ae0b2018-02-06 14:48:56 -08001428 self._enforced_cpu = detect_host_arch.HostArch(),
maruel@chromium.org271375b2010-06-23 19:17:38 +00001429 self._root_dir = root_dir
John Budorickd3ba72b2018-03-20 12:27:42 -07001430 self._cipd_root = None
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001431 self.config_content = None
1432
borenet@google.com88d10082014-03-21 17:24:48 +00001433 def _CheckConfig(self):
1434 """Verify that the config matches the state of the existing checked-out
1435 solutions."""
1436 for dep in self.dependencies:
1437 if dep.managed and dep.url:
Edward Lemurbabd0982018-05-11 13:32:37 -04001438 scm = dep.CreateSCM()
smut@google.comd33eab32014-07-07 19:35:18 +00001439 actual_url = scm.GetActualRemoteURL(self._options)
borenet@google.com4e9be262014-04-08 19:40:30 +00001440 if actual_url and not scm.DoesRemoteURLMatch(self._options):
levarum@chromium.org27a6f9a2016-05-28 00:21:49 +00001441 mirror = scm.GetCacheMirror()
1442 if mirror:
1443 mirror_string = '%s (exists=%s)' % (mirror.mirror_path,
1444 mirror.exists())
1445 else:
1446 mirror_string = 'not used'
borenet@google.com0a427372014-04-02 19:12:13 +00001447 raise gclient_utils.Error('''
borenet@google.com88d10082014-03-21 17:24:48 +00001448Your .gclient file seems to be broken. The requested URL is different from what
borenet@google.com0a427372014-04-02 19:12:13 +00001449is actually checked out in %(checkout_path)s.
borenet@google.com88d10082014-03-21 17:24:48 +00001450
borenet@google.com97882362014-04-07 20:06:02 +00001451The .gclient file contains:
levarum@chromium.org27a6f9a2016-05-28 00:21:49 +00001452URL: %(expected_url)s (%(expected_scm)s)
1453Cache mirror: %(mirror_string)s
borenet@google.com97882362014-04-07 20:06:02 +00001454
1455The local checkout in %(checkout_path)s reports:
1456%(actual_url)s (%(actual_scm)s)
borenet@google.com88d10082014-03-21 17:24:48 +00001457
1458You should ensure that the URL listed in .gclient is correct and either change
agabled437d762016-10-17 09:35:11 -07001459it or fix the checkout.
borenet@google.com88d10082014-03-21 17:24:48 +00001460''' % {'checkout_path': os.path.join(self.root_dir, dep.name),
1461 'expected_url': dep.url,
Edward Lemurbabd0982018-05-11 13:32:37 -04001462 'expected_scm': dep.GetScmName(),
levarum@chromium.org27a6f9a2016-05-28 00:21:49 +00001463 'mirror_string' : mirror_string,
borenet@google.com88d10082014-03-21 17:24:48 +00001464 'actual_url': actual_url,
Edward Lemurbabd0982018-05-11 13:32:37 -04001465 'actual_scm': dep.GetScmName()})
borenet@google.com88d10082014-03-21 17:24:48 +00001466
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001467 def SetConfig(self, content):
maruel@chromium.orgf13a4182011-09-22 00:26:15 +00001468 assert not self.dependencies
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001469 config_dict = {}
1470 self.config_content = content
1471 try:
1472 exec(content, config_dict)
vapier@chromium.orga81a56e2015-11-11 07:56:13 +00001473 except SyntaxError as e:
maruel@chromium.org5990f9d2010-07-07 18:02:58 +00001474 gclient_utils.SyntaxErrorToError('.gclient', e)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001475
peter@chromium.org1efccc82012-04-27 16:34:38 +00001476 # Append any target OS that is not already being enforced to the tuple.
1477 target_os = config_dict.get('target_os', [])
stuartmorgan@chromium.org18a4f6a2012-11-02 13:37:24 +00001478 if config_dict.get('target_os_only', False):
1479 self._enforced_os = tuple(set(target_os))
1480 else:
1481 self._enforced_os = tuple(set(self._enforced_os).union(target_os))
1482
Tom Andersonc31ae0b2018-02-06 14:48:56 -08001483 # Append any target CPU that is not already being enforced to the tuple.
1484 target_cpu = config_dict.get('target_cpu', [])
1485 if config_dict.get('target_cpu_only', False):
1486 self._enforced_cpu = tuple(set(target_cpu))
1487 else:
1488 self._enforced_cpu = tuple(set(self._enforced_cpu).union(target_cpu))
1489
Aleksandr Derbenev9e8fb0e2017-08-01 20:18:31 +03001490 cache_dir = config_dict.get('cache_dir', self._options.cache_dir)
dyen@chromium.orgd915cca2014-08-07 21:41:37 +00001491 if cache_dir:
1492 cache_dir = os.path.join(self.root_dir, cache_dir)
1493 cache_dir = os.path.abspath(cache_dir)
Andrii Shyshkalov77ce4bd2017-11-27 12:38:18 -08001494
dyen@chromium.orgd915cca2014-08-07 21:41:37 +00001495 gclient_scm.GitWrapper.cache_dir = cache_dir
1496 git_cache.Mirror.SetCachePath(cache_dir)
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001497
stuartmorgan@chromium.org18a4f6a2012-11-02 13:37:24 +00001498 if not target_os and config_dict.get('target_os_only', False):
1499 raise gclient_utils.Error('Can\'t use target_os_only if target_os is '
1500 'not specified')
peter@chromium.org1efccc82012-04-27 16:34:38 +00001501
Tom Andersonc31ae0b2018-02-06 14:48:56 -08001502 if not target_cpu and config_dict.get('target_cpu_only', False):
1503 raise gclient_utils.Error('Can\'t use target_cpu_only if target_cpu is '
1504 'not specified')
1505
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001506 deps_to_add = []
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001507 for s in config_dict.get('solutions', []):
maruel@chromium.org81843b82010-06-28 16:49:26 +00001508 try:
Edward Lemurb61d3872018-05-09 18:42:47 -04001509 deps_to_add.append(GitDependency(
Paweł Hajdan, Jrfc6196b2017-07-27 13:15:25 +02001510 self, s['name'], s['url'], s['url'],
smutae7ea312016-07-18 11:59:41 -07001511 s.get('managed', True),
maruel@chromium.org81843b82010-06-28 16:49:26 +00001512 s.get('custom_deps', {}),
maruel@chromium.org0d812442010-08-10 12:41:08 +00001513 s.get('custom_vars', {}),
petermayo@chromium.orge79161a2013-07-09 14:40:37 +00001514 s.get('custom_hooks', []),
nsylvain@google.comefc80932011-05-31 21:27:56 +00001515 s.get('deps_file', 'DEPS'),
agabledce6ddc2016-09-08 10:02:16 -07001516 True,
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +02001517 None,
1518 None,
Edward Lemur231f5ea2018-01-31 19:02:36 +01001519 True,
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +02001520 True))
maruel@chromium.org81843b82010-06-28 16:49:26 +00001521 except KeyError:
1522 raise gclient_utils.Error('Invalid .gclient file. Solution is '
1523 'incomplete: %s' % s)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001524 self.add_dependencies_and_close(deps_to_add, config_dict.get('hooks', []))
1525 logging.info('SetConfig() done')
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001526
1527 def SaveConfig(self):
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001528 gclient_utils.FileWrite(os.path.join(self.root_dir,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001529 self._options.config_filename),
1530 self.config_content)
1531
1532 @staticmethod
1533 def LoadCurrentConfig(options):
1534 """Searches for and loads a .gclient file relative to the current working
1535 dir. Returns a GClient object."""
szager@chromium.orge2e03202012-07-31 18:05:16 +00001536 if options.spec:
1537 client = GClient('.', options)
1538 client.SetConfig(options.spec)
1539 else:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00001540 if options.verbose:
1541 print('Looking for %s starting from %s\n' % (
1542 options.config_filename, os.getcwd()))
szager@chromium.orge2e03202012-07-31 18:05:16 +00001543 path = gclient_utils.FindGclientRoot(os.getcwd(), options.config_filename)
1544 if not path:
Michael Achenbachb3ce73d2017-10-11 16:41:27 +02001545 if options.verbose:
1546 print('Couldn\'t find configuration file.')
szager@chromium.orge2e03202012-07-31 18:05:16 +00001547 return None
1548 client = GClient(path, options)
1549 client.SetConfig(gclient_utils.FileRead(
1550 os.path.join(path, options.config_filename)))
maruel@chromium.org69392e72011-10-13 22:09:00 +00001551
1552 if (options.revisions and
1553 len(client.dependencies) > 1 and
1554 any('@' not in r for r in options.revisions)):
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001555 print(
1556 ('You must specify the full solution name like --revision %s@%s\n'
1557 'when you have multiple solutions setup in your .gclient file.\n'
1558 'Other solutions present are: %s.') % (
maruel@chromium.org69392e72011-10-13 22:09:00 +00001559 client.dependencies[0].name,
1560 options.revisions[0],
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001561 ', '.join(s.name for s in client.dependencies[1:])),
1562 file=sys.stderr)
maruel@chromium.org15804092010-09-02 17:07:37 +00001563 return client
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001564
nsylvain@google.comefc80932011-05-31 21:27:56 +00001565 def SetDefaultConfig(self, solution_name, deps_file, solution_url,
Paweł Hajdan, Jr3ba2a7c2017-10-04 19:24:46 +02001566 managed=True, cache_dir=None, custom_vars=None):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001567 self.SetConfig(self.DEFAULT_CLIENT_FILE_TEXT % {
1568 'solution_name': solution_name,
1569 'solution_url': solution_url,
nsylvain@google.comefc80932011-05-31 21:27:56 +00001570 'deps_file': deps_file,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001571 'managed': managed,
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001572 'cache_dir': cache_dir,
Paweł Hajdan, Jr3ba2a7c2017-10-04 19:24:46 +02001573 'custom_vars': custom_vars or {},
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001574 })
1575
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001576 def _SaveEntries(self):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001577 """Creates a .gclient_entries file to record the list of unique checkouts.
1578
1579 The .gclient_entries file lives in the same directory as .gclient.
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001580 """
1581 # Sometimes pprint.pformat will use {', sometimes it'll use { ' ... It
1582 # makes testing a bit too fun.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001583 result = 'entries = {\n'
maruel@chromium.org68988972011-09-20 14:11:42 +00001584 for entry in self.root.subtree(False):
agabled437d762016-10-17 09:35:11 -07001585 result += ' %s: %s,\n' % (pprint.pformat(entry.name),
Edward Lemure7273d22018-05-10 19:13:51 -04001586 pprint.pformat(entry.url))
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001587 result += '}\n'
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001588 file_path = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org1333cb32011-10-04 23:40:16 +00001589 logging.debug(result)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001590 gclient_utils.FileWrite(file_path, result)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001591
1592 def _ReadEntries(self):
1593 """Read the .gclient_entries file for the given client.
1594
1595 Returns:
1596 A sequence of solution names, which will be empty if there is the
1597 entries file hasn't been created yet.
1598 """
1599 scope = {}
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001600 filename = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001601 if not os.path.exists(filename):
maruel@chromium.org73e21142010-07-05 13:32:01 +00001602 return {}
maruel@chromium.org5990f9d2010-07-07 18:02:58 +00001603 try:
1604 exec(gclient_utils.FileRead(filename), scope)
vapier@chromium.orga81a56e2015-11-11 07:56:13 +00001605 except SyntaxError as e:
maruel@chromium.org5990f9d2010-07-07 18:02:58 +00001606 gclient_utils.SyntaxErrorToError(filename, e)
Aaron Gable3721ee92017-04-03 14:53:14 -07001607 return scope.get('entries', {})
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001608
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001609 def _EnforceRevisions(self):
maruel@chromium.org918a9ae2010-05-28 15:50:30 +00001610 """Checks for revision overrides."""
1611 revision_overrides = {}
smutae7ea312016-07-18 11:59:41 -07001612 if self._options.head:
1613 return revision_overrides
joi@chromium.org792ea882010-11-10 02:37:27 +00001614 if not self._options.revisions:
1615 for s in self.dependencies:
smutae7ea312016-07-18 11:59:41 -07001616 if not s.managed:
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001617 self._options.revisions.append('%s@unmanaged' % s.name)
maruel@chromium.org307d1792010-05-31 20:03:13 +00001618 if not self._options.revisions:
1619 return revision_overrides
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001620 solutions_names = [s.name for s in self.dependencies]
smutae7ea312016-07-18 11:59:41 -07001621 index = 0
1622 for revision in self._options.revisions:
1623 if not '@' in revision:
maruel@chromium.org307d1792010-05-31 20:03:13 +00001624 # Support for --revision 123
smutae7ea312016-07-18 11:59:41 -07001625 revision = '%s@%s' % (solutions_names[index], revision)
1626 name, rev = revision.split('@', 1)
szager@chromium.org4ad264b2014-05-20 04:43:47 +00001627 revision_overrides[name] = rev
smutae7ea312016-07-18 11:59:41 -07001628 index += 1
maruel@chromium.org918a9ae2010-05-28 15:50:30 +00001629 return revision_overrides
1630
Edward Lesmesc621b212018-03-21 20:26:56 -04001631 def _EnforcePatchRefs(self):
1632 """Checks for patch refs."""
1633 patch_refs = {}
1634 if not self._options.patch_refs:
1635 return patch_refs
1636 for given_patch_ref in self._options.patch_refs:
1637 patch_repo, _, patch_ref = given_patch_ref.partition('@')
1638 if not patch_repo or not patch_ref:
1639 raise gclient_utils.Error(
1640 'Wrong revision format: %s should be of the form '
1641 'patch_repo@patch_ref.' % given_patch_ref)
1642 patch_refs[patch_repo] = patch_ref
1643 return patch_refs
1644
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001645 def RunOnDeps(self, command, args, ignore_requirements=False, progress=True):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001646 """Runs a command on each dependency in a client and its dependencies.
1647
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001648 Args:
1649 command: The command to use (e.g., 'status' or 'diff')
1650 args: list of str - extra arguments to add to the command line.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001651 """
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001652 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +00001653 raise gclient_utils.Error('No solution specified')
borenet@google.com0a427372014-04-02 19:12:13 +00001654
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001655 revision_overrides = {}
Edward Lesmesc621b212018-03-21 20:26:56 -04001656 patch_refs = {}
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001657 # It's unnecessary to check for revision overrides for 'recurse'.
1658 # Save a few seconds by not calling _EnforceRevisions() in that case.
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +02001659 if command not in ('diff', 'recurse', 'runhooks', 'status', 'revert',
1660 'validate'):
szager@chromium.org5273b8a2014-08-21 15:10:10 +00001661 self._CheckConfig()
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001662 revision_overrides = self._EnforceRevisions()
Edward Lesmesc621b212018-03-21 20:26:56 -04001663
1664 if command == 'update':
1665 patch_refs = self._EnforcePatchRefs()
Daniel Chenga21b5b32017-10-19 20:07:48 +00001666 # Disable progress for non-tty stdout.
Daniel Chenga0c5f082017-10-19 13:35:19 -07001667 should_show_progress = (
1668 setup_color.IS_TTY and not self._options.verbose and progress)
1669 pm = None
1670 if should_show_progress:
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001671 if command in ('update', 'revert'):
1672 pm = Progress('Syncing projects', 1)
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +02001673 elif command in ('recurse', 'validate'):
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001674 pm = Progress(' '.join(args), 1)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001675 work_queue = gclient_utils.ExecutionQueue(
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001676 self._options.jobs, pm, ignore_requirements=ignore_requirements,
1677 verbose=self._options.verbose)
maruel@chromium.org049bced2010-08-12 13:37:20 +00001678 for s in self.dependencies:
Paweł Hajdan, Jr4baaa112017-07-04 19:09:32 +02001679 if s.should_process:
1680 work_queue.enqueue(s)
Edward Lesmesc621b212018-03-21 20:26:56 -04001681 work_queue.flush(revision_overrides, command, args, options=self._options,
1682 patch_refs=patch_refs)
1683
szager@chromium.org4ad264b2014-05-20 04:43:47 +00001684 if revision_overrides:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001685 print('Please fix your script, having invalid --revision flags will soon '
Edward Lesmesc621b212018-03-21 20:26:56 -04001686 'be considered an error.', file=sys.stderr)
1687
1688 if patch_refs:
1689 raise gclient_utils.Error(
1690 'The following --patch-ref flags were not used. Please fix it:\n%s' %
1691 ('\n'.join(
1692 patch_repo + '@' + patch_ref
1693 for patch_repo, patch_ref in patch_refs.iteritems())))
piman@chromium.org6f363722010-04-27 00:41:09 +00001694
John Budorickd3ba72b2018-03-20 12:27:42 -07001695 if self._cipd_root:
1696 self._cipd_root.run(command)
1697
Dirk Pranke9f20d022017-10-11 18:36:54 -07001698 # Once all the dependencies have been processed, it's now safe to write
Michael Moss848c86e2018-05-03 16:05:50 -07001699 # out the gn_args_file and run the hooks.
Dirk Pranke9f20d022017-10-11 18:36:54 -07001700 if command == 'update':
Michael Moss848c86e2018-05-03 16:05:50 -07001701 gn_args_dep = self.dependencies[0]
1702 if gn_args_dep._gn_args_from:
1703 deps_map = dict([(dep.name, dep) for dep in gn_args_dep.dependencies])
1704 gn_args_dep = deps_map.get(gn_args_dep._gn_args_from)
1705 if gn_args_dep and gn_args_dep.HasGNArgsFile():
1706 gn_args_dep.WriteGNArgsFile()
Dirk Pranke9f20d022017-10-11 18:36:54 -07001707
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001708 if not self._options.nohooks:
Daniel Chenga0c5f082017-10-19 13:35:19 -07001709 if should_show_progress:
1710 pm = Progress('Running hooks', 1)
1711 self.RunHooksRecursively(self._options, pm)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001712
1713 if command == 'update':
ajwong@chromium.orgcdcee802009-06-23 15:30:42 +00001714 # Notify the user if there is an orphaned entry in their working copy.
1715 # Only delete the directory if there are no changes in it, and
1716 # delete_unversioned_trees is set to true.
maruel@chromium.org68988972011-09-20 14:11:42 +00001717 entries = [i.name for i in self.root.subtree(False) if i.url]
xusydoc@chromium.org885a9602013-05-31 09:54:40 +00001718 full_entries = [os.path.join(self.root_dir, e.replace('/', os.path.sep))
1719 for e in entries]
1720
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001721 for entry, prev_url in self._ReadEntries().iteritems():
maruel@chromium.org04dd7de2010-10-14 13:25:49 +00001722 if not prev_url:
1723 # entry must have been overridden via .gclient custom_deps
1724 continue
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001725 # Fix path separator on Windows.
1726 entry_fixed = entry.replace('/', os.path.sep)
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001727 e_dir = os.path.join(self.root_dir, entry_fixed)
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001728 # Use entry and not entry_fixed there.
jochen@chromium.orga78e5532013-03-11 13:33:03 +00001729 if (entry not in entries and
1730 (not any(path.startswith(entry + '/') for path in entries)) and
jochen@chromium.orgcc475722013-03-11 13:07:40 +00001731 os.path.exists(e_dir)):
primiano@chromium.org1c127382015-02-17 11:15:40 +00001732 # The entry has been removed from DEPS.
Edward Lemurbabd0982018-05-11 13:32:37 -04001733 scm = gclient_scm.GitWrapper(
1734 prev_url, self.root_dir, entry_fixed, self.outbuf)
xusydoc@chromium.org885a9602013-05-31 09:54:40 +00001735
1736 # Check to see if this directory is now part of a higher-up checkout.
borenet@google.com359bb642014-05-13 17:28:19 +00001737 scm_root = None
agabled437d762016-10-17 09:35:11 -07001738 try:
1739 scm_root = gclient_scm.scm.GIT.GetCheckoutRoot(scm.checkout_path)
1740 except subprocess2.CalledProcessError:
1741 pass
1742 if not scm_root:
borenet@google.com359bb642014-05-13 17:28:19 +00001743 logging.warning('Could not find checkout root for %s. Unable to '
1744 'determine whether it is part of a higher-level '
1745 'checkout, so not removing.' % entry)
1746 continue
primiano@chromium.org1c127382015-02-17 11:15:40 +00001747
1748 # This is to handle the case of third_party/WebKit migrating from
1749 # being a DEPS entry to being part of the main project.
1750 # If the subproject is a Git project, we need to remove its .git
1751 # folder. Otherwise git operations on that folder will have different
1752 # effects depending on the current working directory.
agabled437d762016-10-17 09:35:11 -07001753 if os.path.abspath(scm_root) == os.path.abspath(e_dir):
primiano@chromium.org1c127382015-02-17 11:15:40 +00001754 e_par_dir = os.path.join(e_dir, os.pardir)
agabled437d762016-10-17 09:35:11 -07001755 if gclient_scm.scm.GIT.IsInsideWorkTree(e_par_dir):
1756 par_scm_root = gclient_scm.scm.GIT.GetCheckoutRoot(e_par_dir)
primiano@chromium.org1c127382015-02-17 11:15:40 +00001757 # rel_e_dir : relative path of entry w.r.t. its parent repo.
1758 rel_e_dir = os.path.relpath(e_dir, par_scm_root)
agabled437d762016-10-17 09:35:11 -07001759 if gclient_scm.scm.GIT.IsDirectoryVersioned(
1760 par_scm_root, rel_e_dir):
primiano@chromium.org1c127382015-02-17 11:15:40 +00001761 save_dir = scm.GetGitBackupDirPath()
1762 # Remove any eventual stale backup dir for the same project.
1763 if os.path.exists(save_dir):
1764 gclient_utils.rmtree(save_dir)
1765 os.rename(os.path.join(e_dir, '.git'), save_dir)
1766 # When switching between the two states (entry/ is a subproject
1767 # -> entry/ is part of the outer project), it is very likely
1768 # that some files are changed in the checkout, unless we are
1769 # jumping *exactly* across the commit which changed just DEPS.
1770 # In such case we want to cleanup any eventual stale files
1771 # (coming from the old subproject) in order to end up with a
1772 # clean checkout.
agabled437d762016-10-17 09:35:11 -07001773 gclient_scm.scm.GIT.CleanupDir(par_scm_root, rel_e_dir)
primiano@chromium.org1c127382015-02-17 11:15:40 +00001774 assert not os.path.exists(os.path.join(e_dir, '.git'))
1775 print(('\nWARNING: \'%s\' has been moved from DEPS to a higher '
1776 'level checkout. The git folder containing all the local'
1777 ' branches has been saved to %s.\n'
1778 'If you don\'t care about its state you can safely '
1779 'remove that folder to free up space.') %
1780 (entry, save_dir))
1781 continue
1782
borenet@google.com359bb642014-05-13 17:28:19 +00001783 if scm_root in full_entries:
primiano@chromium.org1c127382015-02-17 11:15:40 +00001784 logging.info('%s is part of a higher level checkout, not removing',
1785 scm.GetCheckoutRoot())
xusydoc@chromium.org885a9602013-05-31 09:54:40 +00001786 continue
1787
1788 file_list = []
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001789 scm.status(self._options, [], file_list)
1790 modified_files = file_list != []
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001791 if (not self._options.delete_unversioned_trees or
1792 (modified_files and not self._options.force)):
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001793 # There are modified files in this entry. Keep warning until
1794 # removed.
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001795 print(('\nWARNING: \'%s\' is no longer part of this client. '
1796 'It is recommended that you manually remove it.\n') %
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001797 entry_fixed)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001798 else:
1799 # Delete the entry
maruel@chromium.org73e21142010-07-05 13:32:01 +00001800 print('\n________ deleting \'%s\' in \'%s\'' % (
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001801 entry_fixed, self.root_dir))
digit@chromium.orgdc112ac2013-04-24 13:00:19 +00001802 gclient_utils.rmtree(e_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001803 # record the current list of entries for next time
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001804 self._SaveEntries()
maruel@chromium.org17cdf762010-05-28 17:30:52 +00001805 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001806
1807 def PrintRevInfo(self):
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001808 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +00001809 raise gclient_utils.Error('No solution specified')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001810 # Load all the settings.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001811 work_queue = gclient_utils.ExecutionQueue(
1812 self._options.jobs, None, False, verbose=self._options.verbose)
maruel@chromium.org049bced2010-08-12 13:37:20 +00001813 for s in self.dependencies:
Paweł Hajdan, Jr4baaa112017-07-04 19:09:32 +02001814 if s.should_process:
1815 work_queue.enqueue(s)
Edward Lesmesc621b212018-03-21 20:26:56 -04001816 work_queue.flush({}, None, [], options=self._options, patch_refs=None)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001817
Edward Lemure7273d22018-05-10 19:13:51 -04001818 def ShouldPrintRevision(dep):
Edward Lesmesbb16e332018-03-30 17:54:51 -04001819 return (not self._options.filter
Edward Lemure7273d22018-05-10 19:13:51 -04001820 or dep.FuzzyMatchUrl(self._options.filter))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001821
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001822 if self._options.snapshot:
Edward Lesmesc2960242018-03-06 20:50:15 -05001823 json_output = []
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001824 # First level at .gclient
1825 for d in self.dependencies:
1826 entries = {}
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001827 def GrabDeps(dep):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001828 """Recursively grab dependencies."""
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001829 for d in dep.dependencies:
Edward Lemure7273d22018-05-10 19:13:51 -04001830 d.PinToActualRevision()
1831 if ShouldPrintRevision(d):
1832 entries[d.name] = d.url
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001833 GrabDeps(d)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001834 GrabDeps(d)
Edward Lesmesc2960242018-03-06 20:50:15 -05001835 json_output.append({
1836 'name': d.name,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001837 'solution_url': d.url,
nsylvain@google.comefc80932011-05-31 21:27:56 +00001838 'deps_file': d.deps_file,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001839 'managed': d.managed,
Edward Lesmesc2960242018-03-06 20:50:15 -05001840 'custom_deps': entries,
1841 })
1842 if self._options.output_json == '-':
1843 print(json.dumps(json_output, indent=2, separators=(',', ': ')))
1844 elif self._options.output_json:
1845 with open(self._options.output_json, 'w') as f:
1846 json.dump(json_output, f)
1847 else:
1848 # Print the snapshot configuration file
1849 print(self.DEFAULT_SNAPSHOT_FILE_TEXT % {
1850 'solution_list': pprint.pformat(json_output, indent=2),
1851 })
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +00001852 else:
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001853 entries = {}
maruel@chromium.org68988972011-09-20 14:11:42 +00001854 for d in self.root.subtree(False):
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001855 if self._options.actual:
Edward Lemure7273d22018-05-10 19:13:51 -04001856 d.PinToActualRevision()
1857 if ShouldPrintRevision(d):
1858 entries[d.name] = d.url
Edward Lesmesc2960242018-03-06 20:50:15 -05001859 if self._options.output_json:
1860 json_output = {
1861 name: {
Michael Moss012013e2018-03-30 17:03:19 -07001862 'url': rev.split('@')[0] if rev else None,
1863 'rev': rev.split('@')[1] if rev and '@' in rev else None,
Edward Lesmesc2960242018-03-06 20:50:15 -05001864 }
1865 for name, rev in entries.iteritems()
1866 }
1867 if self._options.output_json == '-':
1868 print(json.dumps(json_output, indent=2, separators=(',', ': ')))
1869 else:
1870 with open(self._options.output_json, 'w') as f:
1871 json.dump(json_output, f)
1872 else:
1873 keys = sorted(entries.keys())
1874 for x in keys:
1875 print('%s: %s' % (x, entries[x]))
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +00001876 logging.info(str(self))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001877
Edward Lesmes6c24d372018-03-28 12:52:29 -04001878 def ParseDepsFile(self, expand_vars=None):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001879 """No DEPS to parse for a .gclient file."""
maruel@chromium.org049bced2010-08-12 13:37:20 +00001880 raise gclient_utils.Error('Internal error')
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001881
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00001882 def PrintLocationAndContents(self):
1883 # Print out the .gclient file. This is longer than if we just printed the
1884 # client dict, but more legible, and it might contain helpful comments.
1885 print('Loaded .gclient config in %s:\n%s' % (
1886 self.root_dir, self.config_content))
1887
John Budorickd3ba72b2018-03-20 12:27:42 -07001888 def GetCipdRoot(self):
1889 if not self._cipd_root:
1890 self._cipd_root = gclient_scm.CipdRoot(
1891 self.root_dir,
1892 # TODO(jbudorick): Support other service URLs as necessary.
1893 # Service URLs should be constant over the scope of a cipd
1894 # root, so a var per DEPS file specifying the service URL
1895 # should suffice.
1896 'https://chrome-infra-packages.appspot.com')
1897 return self._cipd_root
1898
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001899 @property
maruel@chromium.org75a59272010-06-11 22:34:03 +00001900 def root_dir(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001901 """Root directory of gclient checkout."""
maruel@chromium.org75a59272010-06-11 22:34:03 +00001902 return self._root_dir
1903
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001904 @property
maruel@chromium.org271375b2010-06-23 19:17:38 +00001905 def enforced_os(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001906 """What deps_os entries that are to be parsed."""
maruel@chromium.org271375b2010-06-23 19:17:38 +00001907 return self._enforced_os
1908
maruel@chromium.org68988972011-09-20 14:11:42 +00001909 @property
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001910 def recursion_limit(self):
1911 """How recursive can each dependencies in DEPS file can load DEPS file."""
1912 return self._recursion_limit
1913
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +00001914 @property
cmp@chromium.orgc401ad12014-07-02 23:20:08 +00001915 def try_recursedeps(self):
1916 """Whether to attempt using recursedeps-style recursion processing."""
cmp@chromium.orge84ac912014-06-30 23:14:35 +00001917 return True
1918
1919 @property
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +00001920 def target_os(self):
1921 return self._enforced_os
1922
Tom Andersonc31ae0b2018-02-06 14:48:56 -08001923 @property
1924 def target_cpu(self):
1925 return self._enforced_cpu
1926
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001927
John Budorick0f7b2002018-01-19 15:46:17 -08001928class CipdDependency(Dependency):
1929 """A Dependency object that represents a single CIPD package."""
1930
1931 def __init__(
1932 self, parent, name, dep_value, cipd_root,
1933 custom_vars, should_process, relative, condition, condition_value):
1934 package = dep_value['package']
1935 version = dep_value['version']
1936 url = urlparse.urljoin(
1937 cipd_root.service_url, '%s@%s' % (package, version))
1938 super(CipdDependency, self).__init__(
Shenghua Zhang6f830312018-02-26 11:45:07 -08001939 parent, name + ':' + package, url, url, None, None, custom_vars,
John Budorick0f7b2002018-01-19 15:46:17 -08001940 None, None, should_process, relative, condition, condition_value)
1941 if relative:
1942 # TODO(jbudorick): Implement relative if necessary.
1943 raise gclient_utils.Error(
1944 'Relative CIPD dependencies are not currently supported.')
John Budorickd3ba72b2018-03-20 12:27:42 -07001945 self._cipd_package = None
John Budorick0f7b2002018-01-19 15:46:17 -08001946 self._cipd_root = cipd_root
John Budorick0f7b2002018-01-19 15:46:17 -08001947 self._cipd_subdir = os.path.relpath(
Shenghua Zhang6f830312018-02-26 11:45:07 -08001948 os.path.join(self.root.root_dir, name), cipd_root.root_dir)
John Budorickd3ba72b2018-03-20 12:27:42 -07001949 self._package_name = package
1950 self._package_version = version
1951
1952 #override
Edward Lesmesc621b212018-03-21 20:26:56 -04001953 def run(self, revision_overrides, command, args, work_queue, options,
1954 patch_refs):
John Budorickd3ba72b2018-03-20 12:27:42 -07001955 """Runs |command| then parse the DEPS file."""
1956 logging.info('CipdDependency(%s).run()' % self.name)
1957 if not self.should_process:
1958 return
1959 self._CreatePackageIfNecessary()
1960 super(CipdDependency, self).run(revision_overrides, command, args,
Edward Lesmesc621b212018-03-21 20:26:56 -04001961 work_queue, options, patch_refs)
John Budorickd3ba72b2018-03-20 12:27:42 -07001962
1963 def _CreatePackageIfNecessary(self):
1964 # We lazily create the CIPD package to make sure that only packages
1965 # that we want (as opposed to all packages defined in all DEPS files
1966 # we parse) get added to the root and subsequently ensured.
1967 if not self._cipd_package:
1968 self._cipd_package = self._cipd_root.add_package(
1969 self._cipd_subdir, self._package_name, self._package_version)
John Budorick0f7b2002018-01-19 15:46:17 -08001970
Edward Lesmes6c24d372018-03-28 12:52:29 -04001971 def ParseDepsFile(self, expand_vars=None):
John Budorick0f7b2002018-01-19 15:46:17 -08001972 """CIPD dependencies are not currently allowed to have nested deps."""
1973 self.add_dependencies_and_close([], [])
1974
1975 #override
Shenghua Zhang6f830312018-02-26 11:45:07 -08001976 def verify_validity(self):
1977 """CIPD dependencies allow duplicate name for packages in same directory."""
1978 logging.info('Dependency(%s).verify_validity()' % self.name)
1979 return True
1980
1981 #override
Edward Lemurbabd0982018-05-11 13:32:37 -04001982 def GetScmName(self):
John Budorick0f7b2002018-01-19 15:46:17 -08001983 """Always 'cipd'."""
John Budorick0f7b2002018-01-19 15:46:17 -08001984 return 'cipd'
1985
1986 #override
Edward Lemurbabd0982018-05-11 13:32:37 -04001987 def CreateSCM(self, out_cb=None):
John Budorick0f7b2002018-01-19 15:46:17 -08001988 """Create a Wrapper instance suitable for handling this CIPD dependency."""
John Budorickd3ba72b2018-03-20 12:27:42 -07001989 self._CreatePackageIfNecessary()
John Budorick0f7b2002018-01-19 15:46:17 -08001990 return gclient_scm.CipdWrapper(
Edward Lemurbabd0982018-05-11 13:32:37 -04001991 self.url, self.root.root_dir, self.name, self.outbuf, out_cb,
1992 root=self._cipd_root, package=self._cipd_package)
John Budorick0f7b2002018-01-19 15:46:17 -08001993
1994 def ToLines(self):
1995 """Return a list of lines representing this in a DEPS file."""
1996 s = []
John Budorickd3ba72b2018-03-20 12:27:42 -07001997 self._CreatePackageIfNecessary()
John Budorick0f7b2002018-01-19 15:46:17 -08001998 if self._cipd_package.authority_for_subdir:
1999 condition_part = ([' "condition": %r,' % self.condition]
2000 if self.condition else [])
2001 s.extend([
2002 ' # %s' % self.hierarchy(include_url=False),
John Budorickd3ba72b2018-03-20 12:27:42 -07002003 ' "%s": {' % (self.name.split(':')[0],),
John Budorick0f7b2002018-01-19 15:46:17 -08002004 ' "packages": [',
2005 ])
2006 for p in self._cipd_root.packages(self._cipd_subdir):
2007 s.extend([
John Budorick64e33cb2018-02-20 09:40:30 -08002008 ' {',
2009 ' "package": "%s",' % p.name,
2010 ' "version": "%s",' % p.version,
2011 ' },',
John Budorick0f7b2002018-01-19 15:46:17 -08002012 ])
John Budorickd3ba72b2018-03-20 12:27:42 -07002013
John Budorick0f7b2002018-01-19 15:46:17 -08002014 s.extend([
2015 ' ],',
2016 ' "dep_type": "cipd",',
2017 ] + condition_part + [
2018 ' },',
2019 '',
2020 ])
2021 return s
2022
2023
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002024#### gclient commands.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002025
2026
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002027@subcommand.usage('[command] [args ...]')
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00002028def CMDrecurse(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002029 """Operates [command args ...] on all the dependencies.
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00002030
2031 Runs a shell command on all entries.
qyearsley12fa6ff2016-08-24 09:18:40 -07002032 Sets GCLIENT_DEP_PATH environment variable as the dep's relative location to
ilevy@chromium.org37116242012-11-28 01:32:48 +00002033 root directory of the checkout.
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00002034 """
2035 # Stop parsing at the first non-arg so that these go through to the command
2036 parser.disable_interspersed_args()
2037 parser.add_option('-s', '--scm', action='append', default=[],
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00002038 help='Choose scm types to operate upon.')
maruel@chromium.org288054d2012-03-05 00:43:07 +00002039 parser.add_option('-i', '--ignore', action='store_true',
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00002040 help='Ignore non-zero return codes from subcommands.')
2041 parser.add_option('--prepend-dir', action='store_true',
2042 help='Prepend relative dir for use with git <cmd> --null.')
2043 parser.add_option('--no-progress', action='store_true',
2044 help='Disable progress bar that shows sub-command updates')
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00002045 options, args = parser.parse_args(args)
maruel@chromium.org45e9f2d2010-10-18 13:33:46 +00002046 if not args:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002047 print('Need to supply a command!', file=sys.stderr)
maruel@chromium.org45e9f2d2010-10-18 13:33:46 +00002048 return 1
maruel@chromium.org78cba522010-10-18 13:32:05 +00002049 root_and_entries = gclient_utils.GetGClientRootAndEntries()
2050 if not root_and_entries:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002051 print(
maruel@chromium.org78cba522010-10-18 13:32:05 +00002052 'You need to run gclient sync at least once to use \'recurse\'.\n'
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002053 'This is because .gclient_entries needs to exist and be up to date.',
2054 file=sys.stderr)
maruel@chromium.org78cba522010-10-18 13:32:05 +00002055 return 1
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00002056
2057 # Normalize options.scm to a set()
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00002058 scm_set = set()
2059 for scm in options.scm:
2060 scm_set.update(scm.split(','))
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00002061 options.scm = scm_set
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00002062
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00002063 options.nohooks = True
2064 client = GClient.LoadCurrentConfig(options)
Marc-Antoine Ruele6e06412017-10-18 13:47:02 -04002065 if not client:
2066 raise gclient_utils.Error('client not configured; see \'gclient config\'')
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00002067 return client.RunOnDeps('recurse', args, ignore_requirements=True,
2068 progress=not options.no_progress)
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00002069
2070
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002071@subcommand.usage('[args ...]')
davidbarr@chromium.org12f944e2012-03-01 02:18:31 +00002072def CMDfetch(parser, args):
2073 """Fetches upstream commits for all modules.
2074
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002075 Completely git-specific. Simply runs 'git fetch [args ...]' for each module.
2076 """
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00002077 (options, args) = parser.parse_args(args)
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002078 return CMDrecurse(OptionParser(), [
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00002079 '--jobs=%d' % options.jobs, '--scm=git', 'git', 'fetch'] + args)
2080
2081
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002082class Flattener(object):
2083 """Flattens a gclient solution."""
2084
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02002085 def __init__(self, client, pin_all_deps=False):
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002086 """Constructor.
2087
2088 Arguments:
2089 client (GClient): client to flatten
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02002090 pin_all_deps (bool): whether to pin all deps, even if they're not pinned
2091 in DEPS
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002092 """
2093 self._client = client
2094
2095 self._deps_string = None
Paweł Hajdan, Jrfeb01642017-09-12 15:50:46 +02002096 self._deps_files = set()
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002097
2098 self._allowed_hosts = set()
2099 self._deps = {}
2100 self._deps_os = {}
2101 self._hooks = []
2102 self._hooks_os = {}
2103 self._pre_deps_hooks = []
Paweł Hajdan, Jrfb022012017-07-06 18:00:08 +02002104 self._vars = {}
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002105
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02002106 self._flatten(pin_all_deps=pin_all_deps)
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002107
2108 @property
2109 def deps_string(self):
2110 assert self._deps_string is not None
2111 return self._deps_string
2112
Paweł Hajdan, Jrfeb01642017-09-12 15:50:46 +02002113 @property
2114 def deps_files(self):
2115 return self._deps_files
2116
Paweł Hajdan, Jr5ec77132017-08-16 19:21:06 +02002117 def _pin_dep(self, dep):
2118 """Pins a dependency to specific full revision sha.
2119
2120 Arguments:
2121 dep (Dependency): dependency to process
2122 """
Edward Lemure7273d22018-05-10 19:13:51 -04002123 if dep.url is None:
Paweł Hajdan, Jr5ec77132017-08-16 19:21:06 +02002124 return
2125
2126 # Make sure the revision is always fully specified (a hash),
2127 # as opposed to refs or tags which might change. Similarly,
2128 # shortened shas might become ambiguous; make sure to always
2129 # use full one for pinning.
Edward Lemure7273d22018-05-10 19:13:51 -04002130 revision = gclient_utils.SplitUrlRevision(dep.url)[1]
2131 if not revision or not gclient_utils.IsFullGitSha(revision):
2132 dep.PinToActualRevision()
Paweł Hajdan, Jr5ec77132017-08-16 19:21:06 +02002133
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02002134 def _flatten(self, pin_all_deps=False):
2135 """Runs the flattener. Saves resulting DEPS string.
2136
2137 Arguments:
2138 pin_all_deps (bool): whether to pin all deps, even if they're not pinned
2139 in DEPS
2140 """
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002141 for solution in self._client.dependencies:
Paweł Hajdan, Jr11eb7152017-08-10 12:50:11 +02002142 self._add_dep(solution)
Paweł Hajdan, Jrb0ad16e2017-08-03 15:33:21 +02002143 self._flatten_dep(solution)
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002144
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02002145 if pin_all_deps:
2146 for dep in self._deps.itervalues():
Paweł Hajdan, Jr5ec77132017-08-16 19:21:06 +02002147 self._pin_dep(dep)
Paweł Hajdan, Jr39300ba2017-08-11 16:52:38 +02002148
Paweł Hajdan, Jr5ec77132017-08-16 19:21:06 +02002149 for os_deps in self._deps_os.itervalues():
2150 for dep in os_deps.itervalues():
Paweł Hajdan, Jr5ec77132017-08-16 19:21:06 +02002151 self._pin_dep(dep)
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02002152
Paweł Hajdan, Jr76a9d042017-08-18 20:05:41 +02002153 def add_deps_file(dep):
Paweł Hajdan, Jr0870df22017-08-23 17:59:29 +02002154 # Only include DEPS files referenced by recursedeps.
2155 if not (dep.parent is None or
2156 (dep.name in (dep.parent.recursedeps or {}))):
2157 return
Paweł Hajdan, Jrfeb01642017-09-12 15:50:46 +02002158 deps_file = dep.deps_file
2159 deps_path = os.path.join(self._client.root_dir, dep.name, deps_file)
Paweł Hajdan, Jr76a9d042017-08-18 20:05:41 +02002160 if not os.path.exists(deps_path):
Paweł Hajdan, Jrfeb01642017-09-12 15:50:46 +02002161 # gclient has a fallback that if deps_file doesn't exist, it'll try
2162 # DEPS. Do the same here.
2163 deps_file = 'DEPS'
2164 deps_path = os.path.join(self._client.root_dir, dep.name, deps_file)
2165 if not os.path.exists(deps_path):
2166 return
Edward Lemure7273d22018-05-10 19:13:51 -04002167 assert dep.url
2168 self._deps_files.add((dep.url, deps_file, dep.hierarchy_data()))
Paweł Hajdan, Jr76a9d042017-08-18 20:05:41 +02002169 for dep in self._deps.itervalues():
2170 add_deps_file(dep)
2171 for os_deps in self._deps_os.itervalues():
2172 for dep in os_deps.itervalues():
2173 add_deps_file(dep)
2174
Michael Moss848c86e2018-05-03 16:05:50 -07002175 gn_args_dep = self._deps.get(self._client.dependencies[0]._gn_args_from,
2176 self._client.dependencies[0])
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002177 self._deps_string = '\n'.join(
Michael Moss848c86e2018-05-03 16:05:50 -07002178 _GNSettingsToLines(gn_args_dep._gn_args_file, gn_args_dep._gn_args) +
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002179 _AllowedHostsToLines(self._allowed_hosts) +
2180 _DepsToLines(self._deps) +
2181 _DepsOsToLines(self._deps_os) +
2182 _HooksToLines('hooks', self._hooks) +
2183 _HooksToLines('pre_deps_hooks', self._pre_deps_hooks) +
2184 _HooksOsToLines(self._hooks_os) +
Paweł Hajdan, Jrfb022012017-07-06 18:00:08 +02002185 _VarsToLines(self._vars) +
Paweł Hajdan, Jr76a9d042017-08-18 20:05:41 +02002186 ['# %s, %s' % (url, deps_file)
Michael Mossfe68c912018-03-22 19:19:35 -07002187 for url, deps_file, _ in sorted(self._deps_files)] +
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002188 ['']) # Ensure newline at end of file.
2189
Paweł Hajdan, Jr11eb7152017-08-10 12:50:11 +02002190 def _add_dep(self, dep):
2191 """Helper to add a dependency to flattened DEPS.
2192
2193 Arguments:
2194 dep (Dependency): dependency to add
2195 """
2196 assert dep.name not in self._deps or self._deps.get(dep.name) == dep, (
2197 dep.name, self._deps.get(dep.name))
Paweł Hajdan, Jr9a289022017-08-10 16:04:24 +02002198 if dep.url:
2199 self._deps[dep.name] = dep
Paweł Hajdan, Jr11eb7152017-08-10 12:50:11 +02002200
Paweł Hajdan, Jradae2a62017-08-18 16:49:57 +02002201 def _add_os_dep(self, os_dep, dep_os):
2202 """Helper to add an OS-specific dependency to flattened DEPS.
2203
2204 Arguments:
2205 os_dep (Dependency): dependency to add
2206 dep_os (str): name of the OS
2207 """
2208 assert (
2209 os_dep.name not in self._deps_os.get(dep_os, {}) or
2210 self._deps_os.get(dep_os, {}).get(os_dep.name) == os_dep), (
2211 os_dep.name, self._deps_os.get(dep_os, {}).get(os_dep.name))
2212 if os_dep.url:
2213 self._deps_os.setdefault(dep_os, {})[os_dep.name] = os_dep
2214
2215 def _flatten_dep(self, dep, dep_os=None):
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002216 """Visits a dependency in order to flatten it (see CMDflatten).
2217
2218 Arguments:
2219 dep (Dependency): dependency to process
Paweł Hajdan, Jradae2a62017-08-18 16:49:57 +02002220 dep_os (str or None): name of the OS |dep| is specific to
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002221 """
Paweł Hajdan, Jradae2a62017-08-18 16:49:57 +02002222 logging.debug('_flatten_dep(%s, %s)', dep.name, dep_os)
2223
Paweł Hajdan, Jrc69b32e2017-08-17 18:47:48 +02002224 if not dep.deps_parsed:
2225 dep.ParseDepsFile()
2226
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002227 self._allowed_hosts.update(dep.allowed_hosts)
2228
Michael Mossce9f17f2018-01-31 13:16:35 -08002229 # Only include vars explicitly listed in the DEPS files or gclient solution,
2230 # not automatic, local overrides (i.e. not all of dep.get_vars()).
2231 hierarchy = dep.hierarchy(include_url=False)
Paweł Hajdan, Jra3b67ae2017-08-30 15:18:21 +02002232 for key, value in dep._vars.iteritems():
Paweł Hajdan, Jrc9353602017-08-02 17:52:08 +02002233 # Make sure there are no conflicting variables. It is fine however
2234 # to use same variable name, as long as the value is consistent.
2235 assert key not in self._vars or self._vars[key][1] == value
Michael Mossce9f17f2018-01-31 13:16:35 -08002236 self._vars[key] = (hierarchy, value)
2237 # Override explicit custom variables.
2238 for key, value in dep.custom_vars.iteritems():
2239 # Do custom_vars that don't correspond to DEPS vars ever make sense? DEPS
2240 # conditionals shouldn't be using vars that aren't also defined in the
2241 # DEPS (presubmit actually disallows this), so any new custom_var must be
2242 # unused in the DEPS, so no need to add it to the flattened output either.
2243 if key not in self._vars:
2244 continue
2245 # Don't "override" existing vars if it's actually the same value.
2246 elif self._vars[key][1] == value:
2247 continue
2248 # Anything else is overriding a default value from the DEPS.
2249 self._vars[key] = (hierarchy + ' [custom_var override]', value)
Paweł Hajdan, Jrfb022012017-07-06 18:00:08 +02002250
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002251 self._pre_deps_hooks.extend([(dep, hook) for hook in dep.pre_deps_hooks])
2252
Paweł Hajdan, Jradae2a62017-08-18 16:49:57 +02002253 if dep_os:
2254 if dep.deps_hooks:
2255 self._hooks_os.setdefault(dep_os, []).extend(
2256 [(dep, hook) for hook in dep.deps_hooks])
2257 else:
2258 self._hooks.extend([(dep, hook) for hook in dep.deps_hooks])
2259
Paweł Hajdan, Jrb0ad16e2017-08-03 15:33:21 +02002260 for sub_dep in dep.dependencies:
Paweł Hajdan, Jradae2a62017-08-18 16:49:57 +02002261 if dep_os:
2262 self._add_os_dep(sub_dep, dep_os)
2263 else:
2264 self._add_dep(sub_dep)
Paweł Hajdan, Jrb0ad16e2017-08-03 15:33:21 +02002265
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002266 for hook_os, os_hooks in dep.os_deps_hooks.iteritems():
2267 self._hooks_os.setdefault(hook_os, []).extend(
2268 [(dep, hook) for hook in os_hooks])
2269
Paweł Hajdan, Jradae2a62017-08-18 16:49:57 +02002270 for sub_dep_os, os_deps in dep.os_dependencies.iteritems():
Paweł Hajdan, Jre2deb1e2017-08-09 17:29:21 +02002271 for os_dep in os_deps:
Paweł Hajdan, Jradae2a62017-08-18 16:49:57 +02002272 self._add_os_dep(os_dep, sub_dep_os)
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002273
Paweł Hajdan, Jradae2a62017-08-18 16:49:57 +02002274 # Process recursedeps. |deps_by_name| is a map where keys are dependency
2275 # names, and values are maps of OS names to |Dependency| instances.
2276 # |None| in place of OS name means the dependency is not OS-specific.
2277 deps_by_name = dict((d.name, {None: d}) for d in dep.dependencies)
2278 for sub_dep_os, os_deps in dep.os_dependencies.iteritems():
Paweł Hajdan, Jrc9353602017-08-02 17:52:08 +02002279 for os_dep in os_deps:
Paweł Hajdan, Jradae2a62017-08-18 16:49:57 +02002280 assert sub_dep_os not in deps_by_name.get(os_dep.name, {}), (
2281 os_dep.name, sub_dep_os)
2282 deps_by_name.setdefault(os_dep.name, {})[sub_dep_os] = os_dep
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002283 for recurse_dep_name in (dep.recursedeps or []):
Paweł Hajdan, Jradae2a62017-08-18 16:49:57 +02002284 dep_info = deps_by_name[recurse_dep_name]
2285 for sub_dep_os, os_dep in dep_info.iteritems():
2286 self._flatten_dep(os_dep, dep_os=(sub_dep_os or dep_os))
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002287
2288
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002289def CMDflatten(parser, args):
2290 """Flattens the solutions into a single DEPS file."""
2291 parser.add_option('--output-deps', help='Path to the output DEPS file')
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02002292 parser.add_option(
Paweł Hajdan, Jrfeb01642017-09-12 15:50:46 +02002293 '--output-deps-files',
2294 help=('Path to the output metadata about DEPS files referenced by '
2295 'recursedeps.'))
2296 parser.add_option(
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02002297 '--pin-all-deps', action='store_true',
2298 help=('Pin all deps, even if not pinned in DEPS. CAVEAT: only does so '
2299 'for checked out deps, NOT deps_os.'))
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002300 options, args = parser.parse_args(args)
2301
Paweł Hajdan, Jr357415c2017-07-24 14:35:28 +02002302 options.do_not_merge_os_specific_entries = True
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002303 options.nohooks = True
Paweł Hajdan, Jr5ec77132017-08-16 19:21:06 +02002304 options.process_all_deps = True
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002305 client = GClient.LoadCurrentConfig(options)
2306
2307 # Only print progress if we're writing to a file. Otherwise, progress updates
2308 # could obscure intended output.
2309 code = client.RunOnDeps('flatten', args, progress=options.output_deps)
2310 if code != 0:
2311 return code
2312
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02002313 flattener = Flattener(client, pin_all_deps=options.pin_all_deps)
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002314
2315 if options.output_deps:
2316 with open(options.output_deps, 'w') as f:
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002317 f.write(flattener.deps_string)
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002318 else:
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002319 print(flattener.deps_string)
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002320
Michael Mossfe68c912018-03-22 19:19:35 -07002321 deps_files = [{'url': d[0], 'deps_file': d[1], 'hierarchy': d[2]}
Paweł Hajdan, Jrfeb01642017-09-12 15:50:46 +02002322 for d in sorted(flattener.deps_files)]
2323 if options.output_deps_files:
2324 with open(options.output_deps_files, 'w') as f:
2325 json.dump(deps_files, f)
2326
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002327 return 0
2328
2329
Paweł Hajdan, Jr3c2aa832017-06-07 20:22:16 +02002330def _GNSettingsToLines(gn_args_file, gn_args):
2331 s = []
2332 if gn_args_file:
2333 s.extend([
2334 'gclient_gn_args_file = "%s"' % gn_args_file,
2335 'gclient_gn_args = %r' % gn_args,
2336 ])
2337 return s
2338
2339
Paweł Hajdan, Jr6014b562017-06-30 17:43:42 +02002340def _AllowedHostsToLines(allowed_hosts):
2341 """Converts |allowed_hosts| set to list of lines for output."""
2342 if not allowed_hosts:
2343 return []
2344 s = ['allowed_hosts = [']
2345 for h in sorted(allowed_hosts):
2346 s.append(' "%s",' % h)
2347 s.extend([']', ''])
2348 return s
2349
2350
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002351def _DepsToLines(deps):
2352 """Converts |deps| dict to list of lines for output."""
Paweł Hajdan, Jr5b593352017-06-29 18:37:45 +02002353 if not deps:
2354 return []
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002355 s = ['deps = {']
John Budorick0f7b2002018-01-19 15:46:17 -08002356 for _, dep in sorted(deps.iteritems()):
2357 s.extend(dep.ToLines())
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002358 s.extend(['}', ''])
2359 return s
2360
2361
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02002362def _DepsOsToLines(deps_os):
2363 """Converts |deps_os| dict to list of lines for output."""
Paweł Hajdan, Jr5b593352017-06-29 18:37:45 +02002364 if not deps_os:
2365 return []
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02002366 s = ['deps_os = {']
2367 for dep_os, os_deps in sorted(deps_os.iteritems()):
2368 s.append(' "%s": {' % dep_os)
Paweł Hajdan, Jrad30de62017-06-26 18:51:58 +02002369 for name, dep in sorted(os_deps.iteritems()):
Paweł Hajdan, Jr78ce24e2017-10-03 17:09:13 +02002370 condition_part = ([' "condition": %r,' % dep.condition]
Paweł Hajdan, Jrad30de62017-06-26 18:51:58 +02002371 if dep.condition else [])
2372 s.extend([
2373 ' # %s' % dep.hierarchy(include_url=False),
2374 ' "%s": {' % (name,),
Paweł Hajdan, Jrde86ab32017-08-10 13:55:16 +02002375 ' "url": "%s",' % (dep.raw_url,),
Paweł Hajdan, Jrad30de62017-06-26 18:51:58 +02002376 ] + condition_part + [
2377 ' },',
2378 '',
2379 ])
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02002380 s.extend([' },', ''])
2381 s.extend(['}', ''])
2382 return s
2383
2384
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002385def _HooksToLines(name, hooks):
2386 """Converts |hooks| list to list of lines for output."""
Paweł Hajdan, Jr5b593352017-06-29 18:37:45 +02002387 if not hooks:
2388 return []
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002389 s = ['%s = [' % name]
2390 for dep, hook in hooks:
2391 s.extend([
2392 ' # %s' % dep.hierarchy(include_url=False),
2393 ' {',
2394 ])
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +02002395 if hook.name is not None:
2396 s.append(' "name": "%s",' % hook.name)
2397 if hook.pattern is not None:
2398 s.append(' "pattern": "%s",' % hook.pattern)
Paweł Hajdan, Jrecf53fe2017-09-29 18:28:49 +02002399 if hook.condition is not None:
Paweł Hajdan, Jr78ce24e2017-10-03 17:09:13 +02002400 s.append(' "condition": %r,' % hook.condition)
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002401 s.extend(
Paweł Hajdan, Jrc9364392017-06-14 17:11:56 +02002402 # Hooks run in the parent directory of their dep.
Paweł Hajdan, Jrad30de62017-06-26 18:51:58 +02002403 [' "cwd": "%s",' % os.path.normpath(os.path.dirname(dep.name))] +
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002404 [' "action": ['] +
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +02002405 [' "%s",' % arg for arg in hook.action] +
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002406 [' ]', ' },', '']
2407 )
2408 s.extend([']', ''])
2409 return s
2410
2411
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +02002412def _HooksOsToLines(hooks_os):
2413 """Converts |hooks| list to list of lines for output."""
Paweł Hajdan, Jr5b593352017-06-29 18:37:45 +02002414 if not hooks_os:
2415 return []
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +02002416 s = ['hooks_os = {']
2417 for hook_os, os_hooks in hooks_os.iteritems():
Michael Moss017bcf62017-06-28 15:26:38 -07002418 s.append(' "%s": [' % hook_os)
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +02002419 for dep, hook in os_hooks:
2420 s.extend([
2421 ' # %s' % dep.hierarchy(include_url=False),
2422 ' {',
2423 ])
2424 if hook.name is not None:
2425 s.append(' "name": "%s",' % hook.name)
2426 if hook.pattern is not None:
2427 s.append(' "pattern": "%s",' % hook.pattern)
Paweł Hajdan, Jrecf53fe2017-09-29 18:28:49 +02002428 if hook.condition is not None:
Paweł Hajdan, Jr78ce24e2017-10-03 17:09:13 +02002429 s.append(' "condition": %r,' % hook.condition)
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +02002430 s.extend(
2431 # Hooks run in the parent directory of their dep.
2432 [' "cwd": "%s",' % os.path.normpath(os.path.dirname(dep.name))] +
2433 [' "action": ['] +
2434 [' "%s",' % arg for arg in hook.action] +
2435 [' ]', ' },', '']
2436 )
Michael Moss017bcf62017-06-28 15:26:38 -07002437 s.extend([' ],', ''])
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +02002438 s.extend(['}', ''])
2439 return s
2440
2441
Paweł Hajdan, Jrfb022012017-07-06 18:00:08 +02002442def _VarsToLines(variables):
2443 """Converts |variables| dict to list of lines for output."""
2444 if not variables:
2445 return []
2446 s = ['vars = {']
2447 for key, tup in sorted(variables.iteritems()):
Michael Mossce9f17f2018-01-31 13:16:35 -08002448 hierarchy, value = tup
Paweł Hajdan, Jrfb022012017-07-06 18:00:08 +02002449 s.extend([
Michael Mossce9f17f2018-01-31 13:16:35 -08002450 ' # %s' % hierarchy,
Paweł Hajdan, Jrfb022012017-07-06 18:00:08 +02002451 ' "%s": %r,' % (key, value),
2452 '',
2453 ])
2454 s.extend(['}', ''])
2455 return s
2456
2457
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00002458def CMDgrep(parser, args):
2459 """Greps through git repos managed by gclient.
2460
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002461 Runs 'git grep [args...]' for each module.
2462 """
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00002463 # We can't use optparse because it will try to parse arguments sent
2464 # to git grep and throw an error. :-(
2465 if not args or re.match('(-h|--help)$', args[0]):
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002466 print(
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00002467 'Usage: gclient grep [-j <N>] git-grep-args...\n\n'
2468 'Example: "gclient grep -j10 -A2 RefCountedBase" runs\n"git grep '
2469 '-A2 RefCountedBase" on each of gclient\'s git\nrepos with up to '
2470 '10 jobs.\n\nBonus: page output by appending "|& less -FRSX" to the'
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002471 ' end of your query.',
2472 file=sys.stderr)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00002473 return 1
2474
2475 jobs_arg = ['--jobs=1']
2476 if re.match(r'(-j|--jobs=)\d+$', args[0]):
2477 jobs_arg, args = args[:1], args[1:]
2478 elif re.match(r'(-j|--jobs)$', args[0]):
2479 jobs_arg, args = args[:2], args[2:]
2480
2481 return CMDrecurse(
2482 parser,
2483 jobs_arg + ['--ignore', '--prepend-dir', '--no-progress', '--scm=git',
2484 'git', 'grep', '--null', '--color=Always'] + args)
davidbarr@chromium.org12f944e2012-03-01 02:18:31 +00002485
2486
stip@chromium.orga735da22015-04-29 23:18:20 +00002487def CMDroot(parser, args):
2488 """Outputs the solution root (or current dir if there isn't one)."""
2489 (options, args) = parser.parse_args(args)
2490 client = GClient.LoadCurrentConfig(options)
2491 if client:
2492 print(os.path.abspath(client.root_dir))
2493 else:
2494 print(os.path.abspath('.'))
2495
2496
agablea98a6cd2016-11-15 14:30:10 -08002497@subcommand.usage('[url]')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002498def CMDconfig(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002499 """Creates a .gclient file in the current directory.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00002500
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002501 This specifies the configuration for further commands. After update/sync,
2502 top-level DEPS files in each module are read to determine dependent
2503 modules to operate on as well. If optional [url] parameter is
2504 provided, then configuration is read from a specified Subversion server
2505 URL.
2506 """
szager@chromium.orge2e03202012-07-31 18:05:16 +00002507 # We do a little dance with the --gclientfile option. 'gclient config' is the
2508 # only command where it's acceptable to have both '--gclientfile' and '--spec'
2509 # arguments. So, we temporarily stash any --gclientfile parameter into
2510 # options.output_config_file until after the (gclientfile xor spec) error
2511 # check.
2512 parser.remove_option('--gclientfile')
2513 parser.add_option('--gclientfile', dest='output_config_file',
2514 help='Specify an alternate .gclient file')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002515 parser.add_option('--name',
2516 help='overrides the default name for the solution')
nsylvain@google.comefc80932011-05-31 21:27:56 +00002517 parser.add_option('--deps-file', default='DEPS',
David Benjamin105e11e2017-10-16 10:39:35 -04002518 help='overrides the default name for the DEPS file for the '
nsylvain@google.comefc80932011-05-31 21:27:56 +00002519 'main solutions and all sub-dependencies')
smutae7ea312016-07-18 11:59:41 -07002520 parser.add_option('--unmanaged', action='store_true', default=False,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00002521 help='overrides the default behavior to make it possible '
smutae7ea312016-07-18 11:59:41 -07002522 'to have the main solution untouched by gclient '
2523 '(gclient will check out unmanaged dependencies but '
2524 'will never sync them)')
Paweł Hajdan, Jr3ba2a7c2017-10-04 19:24:46 +02002525 parser.add_option('--custom-var', action='append', dest='custom_vars',
2526 default=[],
2527 help='overrides variables; key=value syntax')
szager@chromium.orge2e03202012-07-31 18:05:16 +00002528 parser.set_defaults(config_filename=None)
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002529 (options, args) = parser.parse_args(args)
szager@chromium.orge2e03202012-07-31 18:05:16 +00002530 if options.output_config_file:
2531 setattr(options, 'config_filename', getattr(options, 'output_config_file'))
maruel@chromium.org5fc2a332010-05-26 19:37:15 +00002532 if ((options.spec and args) or len(args) > 2 or
2533 (not options.spec and not args)):
2534 parser.error('Inconsistent arguments. Use either --spec or one or 2 args')
2535
Paweł Hajdan, Jr3ba2a7c2017-10-04 19:24:46 +02002536 custom_vars = {}
2537 for arg in options.custom_vars:
2538 kv = arg.split('=', 1)
2539 if len(kv) != 2:
2540 parser.error('Invalid --custom-var argument: %r' % arg)
2541 custom_vars[kv[0]] = gclient_eval.EvaluateCondition(kv[1], {})
2542
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002543 client = GClient('.', options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002544 if options.spec:
2545 client.SetConfig(options.spec)
2546 else:
maruel@chromium.org1ab7ffc2009-06-03 17:21:37 +00002547 base_url = args[0].rstrip('/')
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00002548 if not options.name:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002549 name = base_url.split('/')[-1]
nsylvain@google.com12649ef2011-06-01 17:11:20 +00002550 if name.endswith('.git'):
2551 name = name[:-4]
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00002552 else:
2553 # specify an alternate relpath for the given URL.
2554 name = options.name
agable@chromium.orgf2214672015-10-27 21:02:48 +00002555 if not os.path.abspath(os.path.join(os.getcwd(), name)).startswith(
2556 os.getcwd()):
2557 parser.error('Do not pass a relative path for --name.')
2558 if any(x in ('..', '.', '/', '\\') for x in name.split(os.sep)):
2559 parser.error('Do not include relative path components in --name.')
2560
nsylvain@google.comefc80932011-05-31 21:27:56 +00002561 deps_file = options.deps_file
agablea98a6cd2016-11-15 14:30:10 -08002562 client.SetDefaultConfig(name, deps_file, base_url,
smutae7ea312016-07-18 11:59:41 -07002563 managed=not options.unmanaged,
Paweł Hajdan, Jr3ba2a7c2017-10-04 19:24:46 +02002564 cache_dir=options.cache_dir,
2565 custom_vars=custom_vars)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002566 client.SaveConfig()
maruel@chromium.org79692d62010-05-14 18:57:13 +00002567 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002568
2569
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002570@subcommand.epilog("""Example:
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002571 gclient pack > patch.txt
2572 generate simple patch for configured client and dependences
2573""")
2574def CMDpack(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002575 """Generates a patch which can be applied at the root of the tree.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00002576
agabled437d762016-10-17 09:35:11 -07002577 Internally, runs 'git diff' on each checked out module and
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002578 dependencies, and performs minimal postprocessing of the output. The
2579 resulting patch is printed to stdout and can be applied to a freshly
2580 checked out tree via 'patch -p0 < patchfile'.
2581 """
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002582 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2583 help='override deps for the specified (comma-separated) '
2584 'platform(s); \'all\' will process all deps_os '
2585 'references')
haitao.feng@intel.com306080c2012-05-04 13:11:29 +00002586 parser.remove_option('--jobs')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002587 (options, args) = parser.parse_args(args)
iannucci@chromium.org50395ea2013-04-04 04:47:42 +00002588 # Force jobs to 1 so the stdout is not annotated with the thread ids
haitao.feng@intel.com306080c2012-05-04 13:11:29 +00002589 options.jobs = 1
kbr@google.comab318592009-09-04 00:54:55 +00002590 client = GClient.LoadCurrentConfig(options)
2591 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002592 raise gclient_utils.Error('client not configured; see \'gclient config\'')
kbr@google.comab318592009-09-04 00:54:55 +00002593 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002594 client.PrintLocationAndContents()
kbr@google.comab318592009-09-04 00:54:55 +00002595 return client.RunOnDeps('pack', args)
2596
2597
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002598def CMDstatus(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002599 """Shows modification status for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002600 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2601 help='override deps for the specified (comma-separated) '
2602 'platform(s); \'all\' will process all deps_os '
2603 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002604 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002605 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002606 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002607 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002608 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002609 client.PrintLocationAndContents()
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002610 return client.RunOnDeps('status', args)
2611
2612
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002613@subcommand.epilog("""Examples:
maruel@chromium.org79692d62010-05-14 18:57:13 +00002614 gclient sync
2615 update files from SCM according to current configuration,
2616 *for modules which have changed since last update or sync*
2617 gclient sync --force
2618 update files from SCM according to current configuration, for
2619 all modules (useful for recovering files deleted from local copy)
2620 gclient sync --revision src@31000
2621 update src directory to r31000
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002622
2623JSON output format:
2624If the --output-json option is specified, the following document structure will
2625be emitted to the provided file. 'null' entries may occur for subprojects which
2626are present in the gclient solution, but were not processed (due to custom_deps,
2627os_deps, etc.)
2628
2629{
2630 "solutions" : {
2631 "<name>": { # <name> is the posix-normalized path to the solution.
agabled437d762016-10-17 09:35:11 -07002632 "revision": [<git id hex string>|null],
2633 "scm": ["git"|null],
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002634 }
2635 }
2636}
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002637""")
2638def CMDsync(parser, args):
2639 """Checkout/update all modules."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002640 parser.add_option('-f', '--force', action='store_true',
2641 help='force update even for unchanged modules')
2642 parser.add_option('-n', '--nohooks', action='store_true',
2643 help='don\'t run hooks after the update is complete')
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00002644 parser.add_option('-p', '--noprehooks', action='store_true',
2645 help='don\'t run pre-DEPS hooks', default=False)
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002646 parser.add_option('-r', '--revision', action='append',
2647 dest='revisions', metavar='REV', default=[],
2648 help='Enforces revision/hash for the solutions with the '
2649 'format src@rev. The src@ part is optional and can be '
Edward Lesmes53014652018-03-07 18:01:40 -05002650 'skipped. You can also specify URLs instead of paths '
2651 'and gclient will find the solution corresponding to '
2652 'the given URL. If a path is also specified, the URL '
2653 'takes precedence. -r can be used multiple times when '
2654 '.gclient has multiple solutions configured, and will '
2655 'work even if the src@ part is skipped.')
Edward Lesmesc621b212018-03-21 20:26:56 -04002656 parser.add_option('--patch-ref', action='append',
2657 dest='patch_refs', metavar='GERRIT_REF', default=[],
2658 help='Patches the given reference with the format dep@ref. '
2659 'For dep, you can specify URLs as well as paths, with '
2660 'URLs taking preference. The reference will be '
2661 'applied to the necessary path, will be rebased on '
2662 'top what the dep was synced to, and then will do a '
2663 'soft reset. Use --no-rebase-patch-ref and '
2664 '--reset-patch-ref to disable this behavior.')
maruel@chromium.org794207e2013-03-08 15:29:43 +00002665 parser.add_option('--with_branch_heads', action='store_true',
2666 help='Clone git "branch_heads" refspecs in addition to '
2667 'the default refspecs. This adds about 1/2GB to a '
2668 'full checkout. (git only)')
szager@chromium.org8d3348f2014-08-19 22:49:16 +00002669 parser.add_option('--with_tags', action='store_true',
2670 help='Clone git tags in addition to the default refspecs.')
agable2697cd12016-06-28 10:23:53 -07002671 parser.add_option('-H', '--head', action='store_true',
agablea98a6cd2016-11-15 14:30:10 -08002672 help='DEPRECATED: only made sense with safesync urls.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002673 parser.add_option('-D', '--delete_unversioned_trees', action='store_true',
steveblock@chromium.org98e69452012-02-16 16:36:43 +00002674 help='Deletes from the working copy any dependencies that '
2675 'have been removed since the last sync, as long as '
2676 'there are no local modifications. When used with '
2677 '--force, such dependencies are removed even if they '
2678 'have local modifications. When used with --reset, '
2679 'all untracked directories are removed from the '
tnagel@chromium.orga2aaa632014-02-28 21:47:27 +00002680 'working copy, excluding those which are explicitly '
steveblock@chromium.org98e69452012-02-16 16:36:43 +00002681 'ignored in the repository.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002682 parser.add_option('-R', '--reset', action='store_true',
2683 help='resets any local changes before updating (git only)')
bauerb@chromium.org2aad1b22011-07-22 12:00:41 +00002684 parser.add_option('-M', '--merge', action='store_true',
2685 help='merge upstream changes instead of trying to '
2686 'fast-forward or rebase')
dnj@chromium.org5b23e872015-02-20 21:25:57 +00002687 parser.add_option('-A', '--auto_rebase', action='store_true',
2688 help='Automatically rebase repositories against local '
2689 'checkout during update (git only).')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002690 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2691 help='override deps for the specified (comma-separated) '
2692 'platform(s); \'all\' will process all deps_os '
2693 'references')
Paweł Hajdan, Jr357415c2017-07-24 14:35:28 +02002694 # TODO(phajdan.jr): use argparse.SUPPRESS to hide internal flags.
2695 parser.add_option('--do-not-merge-os-specific-entries', action='store_true',
2696 help='INTERNAL ONLY - disables merging of deps_os and '
2697 'hooks_os to dependencies and hooks')
Paweł Hajdan, Jr5ec77132017-08-16 19:21:06 +02002698 parser.add_option('--process-all-deps', action='store_true',
2699 help='Check out all deps, even for different OS-es, '
2700 'or with conditions evaluating to false')
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +00002701 parser.add_option('--upstream', action='store_true',
2702 help='Make repo state match upstream branch.')
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002703 parser.add_option('--output-json',
2704 help='Output a json document to this path containing '
2705 'summary information about the sync.')
primiano@chromium.org5439ea52014-08-06 17:18:18 +00002706 parser.add_option('--no-history', action='store_true',
2707 help='GIT ONLY - Reduces the size/time of the checkout at '
2708 'the cost of no history. Requires Git 1.9+')
hinoka@chromium.org46b87412014-05-15 00:42:05 +00002709 parser.add_option('--shallow', action='store_true',
2710 help='GIT ONLY - Do a shallow clone into the cache dir. '
2711 'Requires Git 1.9+')
e.hakkinen@samsung.come8bc1aa2015-04-08 08:00:37 +00002712 parser.add_option('--no_bootstrap', '--no-bootstrap',
2713 action='store_true',
2714 help='Don\'t bootstrap from Google Storage.')
Vadim Shtayura08049e22017-10-11 00:14:52 +00002715 parser.add_option('--ignore_locks', action='store_true',
2716 help='GIT ONLY - Ignore cache locks.')
iannucci@chromium.org30a07982016-04-07 21:35:19 +00002717 parser.add_option('--break_repo_locks', action='store_true',
2718 help='GIT ONLY - Forcibly remove repo locks (e.g. '
2719 'index.lock). This should only be used if you know for '
2720 'certain that this invocation of gclient is the only '
2721 'thing operating on the git repos (e.g. on a bot).')
Vadim Shtayura08049e22017-10-11 00:14:52 +00002722 parser.add_option('--lock_timeout', type='int', default=5000,
2723 help='GIT ONLY - Deadline (in seconds) to wait for git '
2724 'cache lock to become available. Default is %default.')
agabled437d762016-10-17 09:35:11 -07002725 # TODO(agable): Remove these when the oldest CrOS release milestone is M56.
2726 parser.add_option('-t', '--transitive', action='store_true',
2727 help='DEPRECATED: This is a no-op.')
sdefresne69b1be12016-10-18 05:48:02 -07002728 parser.add_option('-m', '--manually_grab_svn_rev', action='store_true',
agabled437d762016-10-17 09:35:11 -07002729 help='DEPRECATED: This is a no-op.')
Paweł Hajdan, Jr7c7b5592017-05-23 15:06:05 +02002730 # TODO(phajdan.jr): Remove validation options once default (crbug/570091).
Paweł Hajdan, Jr694773d2017-05-29 16:06:23 +02002731 parser.add_option('--validate-syntax', action='store_true', default=True,
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +02002732 help='Validate the .gclient and DEPS syntax')
Paweł Hajdan, Jr7c7b5592017-05-23 15:06:05 +02002733 parser.add_option('--disable-syntax-validation', action='store_false',
2734 dest='validate_syntax',
2735 help='Disable validation of .gclient and DEPS syntax.')
Edward Lesmesc621b212018-03-21 20:26:56 -04002736 parser.add_option('--no-rebase-patch-ref', action='store_false',
2737 dest='rebase_patch_ref', default=True,
2738 help='Bypass rebase of the patch ref after checkout.')
2739 parser.add_option('--no-reset-patch-ref', action='store_false',
2740 dest='reset_patch_ref', default=True,
2741 help='Bypass calling reset after patching the ref.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002742 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002743 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002744
2745 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002746 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002747
smutae7ea312016-07-18 11:59:41 -07002748 if options.revisions and options.head:
2749 # TODO(maruel): Make it a parser.error if it doesn't break any builder.
2750 print('Warning: you cannot use both --head and --revision')
2751
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002752 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002753 client.PrintLocationAndContents()
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002754 ret = client.RunOnDeps('update', args)
2755 if options.output_json:
2756 slns = {}
2757 for d in client.subtree(True):
2758 normed = d.name.replace('\\', '/').rstrip('/') + '/'
John Budorickd1de7252018-05-11 12:31:40 -07002759 if normed in slns and not d.should_process:
2760 # If an unprocessed dependency would override an existing dependency,
2761 # ignore it.
2762 continue
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002763 slns[normed] = {
2764 'revision': d.got_revision,
2765 'scm': d.used_scm.name if d.used_scm else None,
hinoka@chromium.org17db9052014-05-10 01:11:29 +00002766 'url': str(d.url) if d.url else None,
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002767 }
2768 with open(options.output_json, 'wb') as f:
2769 json.dump({'solutions': slns}, f)
2770 return ret
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002771
2772
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002773CMDupdate = CMDsync
2774
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002775
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +02002776def CMDvalidate(parser, args):
2777 """Validates the .gclient and DEPS syntax."""
2778 options, args = parser.parse_args(args)
2779 options.validate_syntax = True
2780 client = GClient.LoadCurrentConfig(options)
2781 rv = client.RunOnDeps('validate', args)
2782 if rv == 0:
2783 print('validate: SUCCESS')
2784 else:
2785 print('validate: FAILURE')
2786 return rv
2787
2788
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002789def CMDdiff(parser, args):
2790 """Displays local diff for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002791 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2792 help='override deps for the specified (comma-separated) '
2793 'platform(s); \'all\' will process all deps_os '
2794 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002795 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002796 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002797 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002798 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002799 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002800 client.PrintLocationAndContents()
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002801 return client.RunOnDeps('diff', args)
2802
2803
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002804def CMDrevert(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002805 """Reverts all modifications in every dependencies.
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00002806
2807 That's the nuclear option to get back to a 'clean' state. It removes anything
agabled437d762016-10-17 09:35:11 -07002808 that shows up in git status."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002809 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2810 help='override deps for the specified (comma-separated) '
2811 'platform(s); \'all\' will process all deps_os '
2812 'references')
2813 parser.add_option('-n', '--nohooks', action='store_true',
2814 help='don\'t run hooks after the revert is complete')
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00002815 parser.add_option('-p', '--noprehooks', action='store_true',
2816 help='don\'t run pre-DEPS hooks', default=False)
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +00002817 parser.add_option('--upstream', action='store_true',
2818 help='Make repo state match upstream branch.')
iannucci@chromium.orgbf525dc2016-04-07 22:00:28 +00002819 parser.add_option('--break_repo_locks', action='store_true',
2820 help='GIT ONLY - Forcibly remove repo locks (e.g. '
2821 'index.lock). This should only be used if you know for '
2822 'certain that this invocation of gclient is the only '
2823 'thing operating on the git repos (e.g. on a bot).')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002824 (options, args) = parser.parse_args(args)
2825 # --force is implied.
2826 options.force = True
steveblock@chromium.org98e69452012-02-16 16:36:43 +00002827 options.reset = False
2828 options.delete_unversioned_trees = False
agablec903d732016-07-26 09:07:24 -07002829 options.merge = False
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002830 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002831 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002832 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002833 return client.RunOnDeps('revert', args)
2834
2835
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002836def CMDrunhooks(parser, args):
2837 """Runs hooks for files that have been modified in the local working copy."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002838 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2839 help='override deps for the specified (comma-separated) '
2840 'platform(s); \'all\' will process all deps_os '
2841 'references')
2842 parser.add_option('-f', '--force', action='store_true', default=True,
2843 help='Deprecated. No effect.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002844 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002845 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002846 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002847 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002848 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002849 client.PrintLocationAndContents()
maruel@chromium.org5df6a462009-08-28 18:52:26 +00002850 options.force = True
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002851 options.nohooks = False
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002852 return client.RunOnDeps('runhooks', args)
2853
2854
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002855def CMDrevinfo(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002856 """Outputs revision info mapping for the client and its dependencies.
maruel@chromium.org9eda4112010-06-11 18:56:10 +00002857
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002858 This allows the capture of an overall 'revision' for the source tree that
maruel@chromium.org9eda4112010-06-11 18:56:10 +00002859 can be used to reproduce the same tree in the future. It is only useful for
agabled437d762016-10-17 09:35:11 -07002860 'unpinned dependencies', i.e. DEPS/deps references without a git hash.
2861 A git branch name isn't 'pinned' since the actual commit can change.
maruel@chromium.org9eda4112010-06-11 18:56:10 +00002862 """
2863 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2864 help='override deps for the specified (comma-separated) '
2865 'platform(s); \'all\' will process all deps_os '
2866 'references')
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00002867 parser.add_option('-a', '--actual', action='store_true',
2868 help='gets the actual checked out revisions instead of the '
2869 'ones specified in the DEPS and .gclient files')
maruel@chromium.org9eda4112010-06-11 18:56:10 +00002870 parser.add_option('-s', '--snapshot', action='store_true',
2871 help='creates a snapshot .gclient file of the current '
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00002872 'version of all repositories to reproduce the tree, '
2873 'implies -a')
Edward Lesmesbb16e332018-03-30 17:54:51 -04002874 parser.add_option('--filter', action='append', dest='filter',
Edward Lesmesdaa76d22018-03-06 14:56:57 -05002875 help='Display revision information only for the specified '
Edward Lesmesbb16e332018-03-30 17:54:51 -04002876 'dependencies (filtered by URL or path).')
Edward Lesmesc2960242018-03-06 20:50:15 -05002877 parser.add_option('--output-json',
2878 help='Output a json document to this path containing '
2879 'information about the revisions.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002880 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002881 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002882 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002883 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002884 client.PrintRevInfo()
maruel@chromium.org79692d62010-05-14 18:57:13 +00002885 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002886
2887
Edward Lesmes411041f2018-04-05 20:12:55 -04002888def CMDgetdep(parser, args):
2889 """Gets revision information and variable values from a DEPS file."""
2890 parser.add_option('--var', action='append',
2891 dest='vars', metavar='VAR', default=[],
2892 help='Gets the value of a given variable.')
2893 parser.add_option('-r', '--revision', action='append',
2894 dest='revisions', metavar='DEP', default=[],
2895 help='Gets the revision/version for the given dependency. '
2896 'If it is a git dependency, dep must be a path. If it '
2897 'is a CIPD dependency, dep must be of the form '
2898 'path:package.')
2899 parser.add_option('--deps-file', default='DEPS',
2900 # TODO(ehmaldonado): Try to find the DEPS file pointed by
2901 # .gclient first.
2902 help='The DEPS file to be edited. Defaults to the DEPS '
2903 'file in the current directory.')
2904 (options, args) = parser.parse_args(args)
2905
2906 if not os.path.isfile(options.deps_file):
2907 raise gclient_utils.Error(
2908 'DEPS file %s does not exist.' % options.deps_file)
2909 with open(options.deps_file) as f:
2910 contents = f.read()
2911 local_scope = gclient_eval.Parse(
2912 contents, expand_vars=True, validate_syntax=True,
2913 filename=options.deps_file)
2914
2915 for var in options.vars:
2916 print(gclient_eval.GetVar(local_scope, var))
2917
2918 for name in options.revisions:
2919 if ':' in name:
2920 name, _, package = name.partition(':')
2921 if not name or not package:
2922 parser.error(
2923 'Wrong CIPD format: %s:%s should be of the form path:pkg.'
2924 % (name, package))
2925 print(gclient_eval.GetCIPD(local_scope, name, package))
2926 else:
2927 print(gclient_eval.GetRevision(local_scope, name))
2928
2929
Edward Lesmes6f64a052018-03-20 17:35:49 -04002930def CMDsetdep(parser, args):
Edward Lesmes0ecf6d62018-04-05 18:28:55 -04002931 """Modifies dependency revisions and variable values in a DEPS file"""
Edward Lesmes6f64a052018-03-20 17:35:49 -04002932 parser.add_option('--var', action='append',
2933 dest='vars', metavar='VAR=VAL', default=[],
2934 help='Sets a variable to the given value with the format '
2935 'name=value.')
2936 parser.add_option('-r', '--revision', action='append',
2937 dest='revisions', metavar='DEP@REV', default=[],
2938 help='Sets the revision/version for the dependency with '
2939 'the format dep@rev. If it is a git dependency, dep '
2940 'must be a path and rev must be a git hash or '
2941 'reference (e.g. src/dep@deadbeef). If it is a CIPD '
2942 'dependency, dep must be of the form path:package and '
2943 'rev must be the package version '
2944 '(e.g. src/pkg:chromium/pkg@2.1-cr0).')
2945 parser.add_option('--deps-file', default='DEPS',
2946 # TODO(ehmaldonado): Try to find the DEPS file pointed by
2947 # .gclient first.
2948 help='The DEPS file to be edited. Defaults to the DEPS '
2949 'file in the current directory.')
2950 (options, args) = parser.parse_args(args)
Edward Lesmes0ecf6d62018-04-05 18:28:55 -04002951 if args:
2952 parser.error('Unused arguments: "%s"' % '" "'.join(args))
2953 if not options.revisions and not options.vars:
2954 parser.error(
2955 'You must specify at least one variable or revision to modify.')
Edward Lesmes6f64a052018-03-20 17:35:49 -04002956
Edward Lesmes6f64a052018-03-20 17:35:49 -04002957 if not os.path.isfile(options.deps_file):
2958 raise gclient_utils.Error(
2959 'DEPS file %s does not exist.' % options.deps_file)
2960 with open(options.deps_file) as f:
2961 contents = f.read()
Edward Lesmes6c24d372018-03-28 12:52:29 -04002962 local_scope = gclient_eval.Parse(
2963 contents, expand_vars=True, validate_syntax=True,
2964 filename=options.deps_file)
Edward Lesmes6f64a052018-03-20 17:35:49 -04002965
2966 for var in options.vars:
2967 name, _, value = var.partition('=')
2968 if not name or not value:
Edward Lesmes0ecf6d62018-04-05 18:28:55 -04002969 parser.error(
Edward Lesmes6f64a052018-03-20 17:35:49 -04002970 'Wrong var format: %s should be of the form name=value.' % var)
Edward Lesmes3d993812018-04-02 12:52:49 -04002971 if name in local_scope['vars']:
2972 gclient_eval.SetVar(local_scope, name, value)
2973 else:
2974 gclient_eval.AddVar(local_scope, name, value)
Edward Lesmes6f64a052018-03-20 17:35:49 -04002975
2976 for revision in options.revisions:
2977 name, _, value = revision.partition('@')
2978 if not name or not value:
Edward Lesmes0ecf6d62018-04-05 18:28:55 -04002979 parser.error(
Edward Lesmes6f64a052018-03-20 17:35:49 -04002980 'Wrong dep format: %s should be of the form dep@rev.' % revision)
2981 if ':' in name:
2982 name, _, package = name.partition(':')
2983 if not name or not package:
Edward Lesmes0ecf6d62018-04-05 18:28:55 -04002984 parser.error(
Edward Lesmes6f64a052018-03-20 17:35:49 -04002985 'Wrong CIPD format: %s:%s should be of the form path:pkg@version.'
2986 % (name, package))
2987 gclient_eval.SetCIPD(local_scope, name, package, value)
2988 else:
Edward Lesmes9f531292018-03-20 21:27:15 -04002989 gclient_eval.SetRevision(local_scope, name, value)
Edward Lesmes6f64a052018-03-20 17:35:49 -04002990
2991 with open(options.deps_file, 'w') as f:
2992 f.write(gclient_eval.RenderDEPSFile(local_scope))
2993
2994
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00002995def CMDverify(parser, args):
2996 """Verifies the DEPS file deps are only from allowed_hosts."""
2997 (options, args) = parser.parse_args(args)
2998 client = GClient.LoadCurrentConfig(options)
2999 if not client:
3000 raise gclient_utils.Error('client not configured; see \'gclient config\'')
3001 client.RunOnDeps(None, [])
3002 # Look at each first-level dependency of this gclient only.
3003 for dep in client.dependencies:
3004 bad_deps = dep.findDepsFromNotAllowedHosts()
3005 if not bad_deps:
3006 continue
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00003007 print("There are deps from not allowed hosts in file %s" % dep.deps_file)
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00003008 for bad_dep in bad_deps:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00003009 print("\t%s at %s" % (bad_dep.name, bad_dep.url))
3010 print("allowed_hosts:", ', '.join(dep.allowed_hosts))
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00003011 sys.stdout.flush()
3012 raise gclient_utils.Error(
3013 'dependencies from disallowed hosts; check your DEPS file.')
3014 return 0
3015
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00003016class OptionParser(optparse.OptionParser):
szager@chromium.orge2e03202012-07-31 18:05:16 +00003017 gclientfile_default = os.environ.get('GCLIENT_FILE', '.gclient')
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00003018
3019 def __init__(self, **kwargs):
3020 optparse.OptionParser.__init__(
3021 self, version='%prog ' + __version__, **kwargs)
3022
3023 # Some arm boards have issues with parallel sync.
3024 if platform.machine().startswith('arm'):
3025 jobs = 1
3026 else:
3027 jobs = max(8, gclient_utils.NumLocalCpus())
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00003028
3029 self.add_option(
3030 '-j', '--jobs', default=jobs, type='int',
3031 help='Specify how many SCM commands can run in parallel; defaults to '
tnagel@chromium.orga2aaa632014-02-28 21:47:27 +00003032 '%default on this machine')
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00003033 self.add_option(
3034 '-v', '--verbose', action='count', default=0,
3035 help='Produces additional output for diagnostics. Can be used up to '
3036 'three times for more logging info.')
3037 self.add_option(
3038 '--gclientfile', dest='config_filename',
3039 help='Specify an alternate %s file' % self.gclientfile_default)
3040 self.add_option(
3041 '--spec',
3042 help='create a gclient file containing the provided string. Due to '
3043 'Cygwin/Python brokenness, it can\'t contain any newlines.')
3044 self.add_option(
Aleksandr Derbenev9e8fb0e2017-08-01 20:18:31 +03003045 '--cache-dir',
3046 help='(git only) Cache all git repos into this dir and do '
3047 'shared clones from the cache, instead of cloning '
3048 'directly from the remote. (experimental)',
3049 default=os.environ.get('GCLIENT_CACHE_DIR'))
3050 self.add_option(
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00003051 '--no-nag-max', default=False, action='store_true',
scottmg@chromium.orgf547c802013-09-27 17:55:26 +00003052 help='Ignored for backwards compatibility.')
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00003053
3054 def parse_args(self, args=None, values=None):
3055 """Integrates standard options processing."""
3056 options, args = optparse.OptionParser.parse_args(self, args, values)
3057 levels = [logging.ERROR, logging.WARNING, logging.INFO, logging.DEBUG]
3058 logging.basicConfig(
3059 level=levels[min(options.verbose, len(levels) - 1)],
maruel@chromium.org0895b752011-08-26 20:40:33 +00003060 format='%(module)s(%(lineno)d) %(funcName)s:%(message)s')
szager@chromium.orge2e03202012-07-31 18:05:16 +00003061 if options.config_filename and options.spec:
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00003062 self.error('Cannot specifiy both --gclientfile and --spec')
rdsmith@chromium.orgd9591f02014-02-05 19:28:20 +00003063 if (options.config_filename and
3064 options.config_filename != os.path.basename(options.config_filename)):
3065 self.error('--gclientfile target must be a filename, not a path')
szager@chromium.orge2e03202012-07-31 18:05:16 +00003066 if not options.config_filename:
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00003067 options.config_filename = self.gclientfile_default
maruel@chromium.org0895b752011-08-26 20:40:33 +00003068 options.entries_filename = options.config_filename + '_entries'
3069 if options.jobs < 1:
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00003070 self.error('--jobs must be 1 or higher')
maruel@chromium.org0895b752011-08-26 20:40:33 +00003071
3072 # These hacks need to die.
3073 if not hasattr(options, 'revisions'):
3074 # GClient.RunOnDeps expects it even if not applicable.
3075 options.revisions = []
smutae7ea312016-07-18 11:59:41 -07003076 if not hasattr(options, 'head'):
3077 options.head = None
maruel@chromium.org0895b752011-08-26 20:40:33 +00003078 if not hasattr(options, 'nohooks'):
3079 options.nohooks = True
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00003080 if not hasattr(options, 'noprehooks'):
3081 options.noprehooks = True
maruel@chromium.org0895b752011-08-26 20:40:33 +00003082 if not hasattr(options, 'deps_os'):
3083 options.deps_os = None
maruel@chromium.org0895b752011-08-26 20:40:33 +00003084 if not hasattr(options, 'force'):
3085 options.force = None
3086 return (options, args)
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00003087
maruel@chromium.org39c0b222013-08-17 16:57:01 +00003088
3089def disable_buffering():
3090 # Make stdout auto-flush so buildbot doesn't kill us during lengthy
3091 # operations. Python as a strong tendency to buffer sys.stdout.
3092 sys.stdout = gclient_utils.MakeFileAutoFlush(sys.stdout)
3093 # Make stdout annotated with the thread ids.
3094 sys.stdout = gclient_utils.MakeFileAnnotated(sys.stdout)
maruel@chromium.org0895b752011-08-26 20:40:33 +00003095
3096
sbc@chromium.org013731e2015-02-26 18:28:43 +00003097def main(argv):
maruel@chromium.org5ca27692010-05-26 19:32:41 +00003098 """Doesn't parse the arguments here, just find the right subcommand to
3099 execute."""
maruel@chromium.org82798cb2012-02-23 18:16:12 +00003100 if sys.hexversion < 0x02060000:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00003101 print(
maruel@chromium.org82798cb2012-02-23 18:16:12 +00003102 '\nYour python version %s is unsupported, please upgrade.\n' %
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00003103 sys.version.split(' ', 1)[0],
3104 file=sys.stderr)
maruel@chromium.org82798cb2012-02-23 18:16:12 +00003105 return 2
bcwhite@chromium.org6683ab42013-02-11 16:13:47 +00003106 if not sys.executable:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00003107 print(
3108 '\nPython cannot find the location of it\'s own executable.\n',
3109 file=sys.stderr)
bcwhite@chromium.org6683ab42013-02-11 16:13:47 +00003110 return 2
maruel@chromium.org39c0b222013-08-17 16:57:01 +00003111 fix_encoding.fix_encoding()
3112 disable_buffering()
iannucci@chromium.org596cd5c2016-04-04 21:34:39 +00003113 setup_color.init()
maruel@chromium.org39c0b222013-08-17 16:57:01 +00003114 dispatcher = subcommand.CommandDispatcher(__name__)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00003115 try:
maruel@chromium.org39c0b222013-08-17 16:57:01 +00003116 return dispatcher.execute(OptionParser(), argv)
xusydoc@chromium.org2fd6c3f2013-05-03 21:57:55 +00003117 except KeyboardInterrupt:
3118 gclient_utils.GClientChildren.KillAllRemainingChildren()
3119 raise
vapier@chromium.orga81a56e2015-11-11 07:56:13 +00003120 except (gclient_utils.Error, subprocess2.CalledProcessError) as e:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00003121 print('Error: %s' % str(e), file=sys.stderr)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00003122 return 1
borenet@google.com6a9b1682014-03-24 18:35:23 +00003123 finally:
3124 gclient_utils.PrintWarnings()
sbc@chromium.org013731e2015-02-26 18:28:43 +00003125 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00003126
3127
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00003128if '__main__' == __name__:
sbc@chromium.org013731e2015-02-26 18:28:43 +00003129 try:
3130 sys.exit(main(sys.argv[1:]))
3131 except KeyboardInterrupt:
3132 sys.stderr.write('interrupted\n')
3133 sys.exit(1)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00003134
3135# vim: ts=2:sw=2:tw=80:et: