blob: ab2ccd5e97f4ebf7c1f26d79a25f494e95cf822e [file] [log] [blame]
Edward Lesmes7149d232019-08-12 21:04:04 +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
Raul Tambreb946b232019-03-26 14:48:46 +000099
100try:
101 import urlparse
102except ImportError: # For Py3 compatibility
103 import urllib.parse as urlparse
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000104
Tom Andersonc31ae0b2018-02-06 14:48:56 -0800105import detect_host_arch
maruel@chromium.org35625c72011-03-23 17:34:02 +0000106import fix_encoding
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +0200107import gclient_eval
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000108import gclient_scm
Nico Weber09e0b382019-03-11 16:54:07 +0000109import gclient_paths
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000110import gclient_utils
szager@chromium.org848fd492014-04-09 19:06:44 +0000111import git_cache
Edward Lemur32e3d1e2018-07-12 00:54:05 +0000112import metrics
Edward Lemur40764b02018-07-20 18:50:29 +0000113import metrics_utils
nasser@codeaurora.org1f7a3d12010-02-04 15:11:50 +0000114from third_party.repo.progress import Progress
maruel@chromium.org39c0b222013-08-17 16:57:01 +0000115import subcommand
maruel@chromium.org31cb48a2011-04-04 18:01:36 +0000116import subprocess2
iannucci@chromium.org596cd5c2016-04-04 21:34:39 +0000117import setup_color
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000118
James Darpinianf994d872019-08-06 18:57:40 +0000119from third_party import six
Edward Lemuree7b9dd2019-07-20 01:29:08 +0000120
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000121
Aaron Gableac9b0f32019-04-18 17:38:37 +0000122# TODO(crbug.com/953884): Remove this when python3 migration is done.
Edward Lemuree7b9dd2019-07-20 01:29:08 +0000123if six.PY3:
Aaron Gableac9b0f32019-04-18 17:38:37 +0000124 # pylint: disable=redefined-builtin
125 basestring = str
126
127
Henrique Ferreiro4ef32212019-04-29 23:32:31 +0000128DEPOT_TOOLS_DIR = os.path.dirname(os.path.abspath(os.path.realpath(__file__)))
129
Robert Iannuccia19649b2018-06-29 16:31:45 +0000130# Singleton object to represent an unset cache_dir (as opposed to a disabled
131# one, e.g. if a spec explicitly says `cache_dir = None`.)
132UNSET_CACHE_DIR = object()
133
134
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +0200135class GNException(Exception):
136 pass
137
138
139def ToGNString(value, allow_dicts = True):
140 """Returns a stringified GN equivalent of the Python value.
141
142 allow_dicts indicates if this function will allow converting dictionaries
143 to GN scopes. This is only possible at the top level, you can't nest a
144 GN scope in a list, so this should be set to False for recursive calls."""
Aaron Gableac9b0f32019-04-18 17:38:37 +0000145 if isinstance(value, basestring):
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +0200146 if value.find('\n') >= 0:
147 raise GNException("Trying to print a string with a newline in it.")
148 return '"' + \
149 value.replace('\\', '\\\\').replace('"', '\\"').replace('$', '\\$') + \
150 '"'
151
Raul Tambreb946b232019-03-26 14:48:46 +0000152 if sys.version_info.major == 2 and isinstance(value, unicode):
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +0200153 return ToGNString(value.encode('utf-8'))
154
155 if isinstance(value, bool):
156 if value:
157 return "true"
158 return "false"
159
160 # NOTE: some type handling removed compared to chromium/src copy.
161
162 raise GNException("Unsupported type when printing to GN.")
163
164
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200165class Hook(object):
166 """Descriptor of command ran before/after sync or on demand."""
167
Paweł Hajdan, Jr032d5452017-06-22 20:43:53 +0200168 def __init__(self, action, pattern=None, name=None, cwd=None, condition=None,
Corentin Walleza68660d2018-09-10 17:33:24 +0000169 variables=None, verbose=False, cwd_base=None):
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200170 """Constructor.
171
172 Arguments:
173 action (list of basestring): argv of the command to run
174 pattern (basestring regex): noop with git; deprecated
175 name (basestring): optional name; no effect on operation
Paweł Hajdan, Jrc9364392017-06-14 17:11:56 +0200176 cwd (basestring): working directory to use
Paweł Hajdan, Jr032d5452017-06-22 20:43:53 +0200177 condition (basestring): condition when to run the hook
178 variables (dict): variables for evaluating the condition
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200179 """
180 self._action = gclient_utils.freeze(action)
181 self._pattern = pattern
182 self._name = name
Paweł Hajdan, Jrc9364392017-06-14 17:11:56 +0200183 self._cwd = cwd
Paweł Hajdan, Jr032d5452017-06-22 20:43:53 +0200184 self._condition = condition
185 self._variables = variables
Daniel Chenga0c5f082017-10-19 13:35:19 -0700186 self._verbose = verbose
Corentin Walleza68660d2018-09-10 17:33:24 +0000187 self._cwd_base = cwd_base
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200188
189 @staticmethod
Corentin Walleza68660d2018-09-10 17:33:24 +0000190 def from_dict(d, variables=None, verbose=False, conditions=None,
191 cwd_base=None):
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200192 """Creates a Hook instance from a dict like in the DEPS file."""
Michael Moss42d02c22018-02-05 10:32:24 -0800193 # Merge any local and inherited conditions.
Edward Lemur16f4bad2018-05-16 16:53:49 -0400194 gclient_eval.UpdateCondition(d, 'and', conditions)
Paweł Hajdan, Jr032d5452017-06-22 20:43:53 +0200195 return Hook(
196 d['action'],
197 d.get('pattern'),
198 d.get('name'),
199 d.get('cwd'),
Edward Lemur16f4bad2018-05-16 16:53:49 -0400200 d.get('condition'),
Daniel Chenga0c5f082017-10-19 13:35:19 -0700201 variables=variables,
202 # Always print the header if not printing to a TTY.
Corentin Walleza68660d2018-09-10 17:33:24 +0000203 verbose=verbose or not setup_color.IS_TTY,
204 cwd_base=cwd_base)
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200205
206 @property
207 def action(self):
208 return self._action
209
210 @property
211 def pattern(self):
212 return self._pattern
213
214 @property
215 def name(self):
216 return self._name
217
Paweł Hajdan, Jrecf53fe2017-09-29 18:28:49 +0200218 @property
219 def condition(self):
220 return self._condition
221
Corentin Walleza68660d2018-09-10 17:33:24 +0000222 @property
223 def effective_cwd(self):
224 cwd = self._cwd_base
225 if self._cwd:
226 cwd = os.path.join(cwd, self._cwd)
227 return cwd
228
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200229 def matches(self, file_list):
230 """Returns true if the pattern matches any of files in the list."""
231 if not self._pattern:
232 return True
233 pattern = re.compile(self._pattern)
234 return bool([f for f in file_list if pattern.search(f)])
235
Corentin Walleza68660d2018-09-10 17:33:24 +0000236 def run(self):
Paweł Hajdan, Jr032d5452017-06-22 20:43:53 +0200237 """Executes the hook's command (provided the condition is met)."""
238 if (self._condition and
239 not gclient_eval.EvaluateCondition(self._condition, self._variables)):
240 return
241
Edward Lemure05f18d2018-06-08 17:36:53 +0000242 cmd = [arg for arg in self._action]
Paweł Hajdan, Jrfc6196b2017-07-27 13:15:25 +0200243
Edward Lemurca879322019-09-09 20:18:13 +0000244 if cmd[0] == 'python':
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200245 # If the hook specified "python" as the first item, the action is a
Edward Lemuree7b9dd2019-07-20 01:29:08 +0000246 # Python script. Run it by starting a new copy of the same interpreter if
247 # we're running on Python 2.
Edward Lemurca879322019-09-09 20:18:13 +0000248 # When using vpython3, "python" refers to the Python 3 executable used by
249 # vpython3, so use "vpython" instead.
250 cmd[0] = sys.executable if six.PY2 else 'vpython'
251 if cmd[0] == 'vpython' and _detect_host_os() == 'win':
Nodir Turakulov0ffcc872017-11-09 16:44:58 -0800252 cmd[0] += '.bat'
Paweł Hajdan, Jrc9364392017-06-14 17:11:56 +0200253
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200254 try:
255 start_time = time.time()
Edward Lemur24146be2019-08-01 21:44:52 +0000256 gclient_utils.CheckCallAndFilter(
257 cmd, cwd=self.effective_cwd, print_stdout=True, show_header=True,
258 always_show_header=self._verbose)
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200259 except (gclient_utils.Error, subprocess2.CalledProcessError) as e:
260 # Use a discrete exit status code of 2 to indicate that a hook action
261 # failed. Users of this script may wish to treat hook action failures
262 # differently from VC failures.
263 print('Error: %s' % str(e), file=sys.stderr)
264 sys.exit(2)
265 finally:
266 elapsed_time = time.time() - start_time
267 if elapsed_time > 10:
268 print("Hook '%s' took %.2f secs" % (
269 gclient_utils.CommandToStr(cmd), elapsed_time))
270
271
Paweł Hajdan, Jrfc6196b2017-07-27 13:15:25 +0200272class DependencySettings(object):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000273 """Immutable configuration settings."""
274 def __init__(
Edward Lemure05f18d2018-06-08 17:36:53 +0000275 self, parent, url, managed, custom_deps, custom_vars,
Michael Mossd683d7c2018-06-15 05:05:17 +0000276 custom_hooks, deps_file, should_process, relative, condition):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000277 # These are not mutable:
278 self._parent = parent
mmoss@chromium.org8f93f792014-08-26 23:24:09 +0000279 self._deps_file = deps_file
maruel@chromium.org064186c2011-09-27 23:53:33 +0000280 self._url = url
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +0200281 # The condition as string (or None). Useful to keep e.g. for flatten.
282 self._condition = condition
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000283 # 'managed' determines whether or not this dependency is synced/updated by
Michael Mossd683d7c2018-06-15 05:05:17 +0000284 # gclient after gclient checks it out initially. The difference between
285 # 'managed' and 'should_process' is that the user specifies 'managed' via
286 # the --unmanaged command-line flag or a .gclient config, where
287 # 'should_process' is dynamically set by gclient if it goes over its
288 # recursion limit and controls gclient's behavior so it does not misbehave.
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000289 self._managed = managed
Michael Mossd683d7c2018-06-15 05:05:17 +0000290 self._should_process = should_process
agabledce6ddc2016-09-08 10:02:16 -0700291 # If this is a recursed-upon sub-dependency, and the parent has
292 # use_relative_paths set, then this dependency should check out its own
293 # dependencies relative to that parent's path for this, rather than
294 # relative to the .gclient file.
295 self._relative = relative
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +0000296 # This is a mutable value which has the list of 'target_os' OSes listed in
297 # the current deps file.
298 self.local_target_os = None
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000299
300 # These are only set in .gclient and not in DEPS files.
301 self._custom_vars = custom_vars or {}
302 self._custom_deps = custom_deps or {}
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000303 self._custom_hooks = custom_hooks or []
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000304
Michael Mossd683d7c2018-06-15 05:05:17 +0000305 # Post process the url to remove trailing slashes.
Aaron Gableac9b0f32019-04-18 17:38:37 +0000306 if isinstance(self.url, basestring):
Michael Moss4e9b50a2018-05-23 22:35:06 -0700307 # urls are sometime incorrectly written as proto://host/path/@rev. Replace
308 # it to proto://host/path@rev.
309 self.set_url(self.url.replace('/@', '@'))
Michael Mossd683d7c2018-06-15 05:05:17 +0000310 elif not isinstance(self.url, (None.__class__)):
311 raise gclient_utils.Error(
312 ('dependency url must be either string or None, '
313 'instead of %s') % self.url.__class__.__name__)
Edward Lemure7273d22018-05-10 19:13:51 -0400314
mmoss@chromium.orgd0b272b2013-01-30 23:55:33 +0000315 # Make any deps_file path platform-appropriate.
John Budorick0f7b2002018-01-19 15:46:17 -0800316 if self._deps_file:
317 for sep in ['/', '\\']:
318 self._deps_file = self._deps_file.replace(sep, os.sep)
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000319
320 @property
321 def deps_file(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000322 return self._deps_file
323
324 @property
325 def managed(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000326 return self._managed
327
328 @property
329 def parent(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000330 return self._parent
331
332 @property
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000333 def root(self):
334 """Returns the root node, a GClient object."""
335 if not self.parent:
336 # This line is to signal pylint that it could be a GClient instance.
337 return self or GClient(None, None)
338 return self.parent.root
339
340 @property
Michael Mossd683d7c2018-06-15 05:05:17 +0000341 def should_process(self):
342 """True if this dependency should be processed, i.e. checked out."""
343 return self._should_process
344
345 @property
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000346 def custom_vars(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000347 return self._custom_vars.copy()
348
349 @property
350 def custom_deps(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000351 return self._custom_deps.copy()
352
maruel@chromium.org064186c2011-09-27 23:53:33 +0000353 @property
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000354 def custom_hooks(self):
355 return self._custom_hooks[:]
356
357 @property
maruel@chromium.org064186c2011-09-27 23:53:33 +0000358 def url(self):
Paweł Hajdan, Jrfc6196b2017-07-27 13:15:25 +0200359 """URL after variable expansion."""
maruel@chromium.org064186c2011-09-27 23:53:33 +0000360 return self._url
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000361
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000362 @property
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +0200363 def condition(self):
364 return self._condition
365
366 @property
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +0000367 def target_os(self):
368 if self.local_target_os is not None:
369 return tuple(set(self.local_target_os).union(self.parent.target_os))
370 else:
371 return self.parent.target_os
372
Tom Andersonc31ae0b2018-02-06 14:48:56 -0800373 @property
374 def target_cpu(self):
375 return self.parent.target_cpu
376
Edward Lemure7273d22018-05-10 19:13:51 -0400377 def set_url(self, url):
378 self._url = url
379
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000380 def get_custom_deps(self, name, url):
381 """Returns a custom deps if applicable."""
382 if self.parent:
383 url = self.parent.get_custom_deps(name, url)
384 # None is a valid return value to disable a dependency.
385 return self.custom_deps.get(name, url)
386
maruel@chromium.org064186c2011-09-27 23:53:33 +0000387
388class Dependency(gclient_utils.WorkItem, DependencySettings):
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000389 """Object that represents a dependency checkout."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000390
Edward Lemure05f18d2018-06-08 17:36:53 +0000391 def __init__(self, parent, name, url, managed, custom_deps,
Michael Mossd683d7c2018-06-15 05:05:17 +0000392 custom_vars, custom_hooks, deps_file, should_process,
393 should_recurse, relative, condition, print_outbuf=False):
maruel@chromium.org6ca8bf82011-09-19 23:04:30 +0000394 gclient_utils.WorkItem.__init__(self, name)
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000395 DependencySettings.__init__(
Michael Mossd683d7c2018-06-15 05:05:17 +0000396 self, parent, url, managed, custom_deps, custom_vars,
397 custom_hooks, deps_file, should_process, relative, condition)
maruel@chromium.org68988972011-09-20 14:11:42 +0000398
399 # This is in both .gclient and DEPS files:
maruel@chromium.org064186c2011-09-27 23:53:33 +0000400 self._deps_hooks = []
maruel@chromium.org68988972011-09-20 14:11:42 +0000401
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000402 self._pre_deps_hooks = []
403
maruel@chromium.org68988972011-09-20 14:11:42 +0000404 # Calculates properties:
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +0000405 self._dependencies = []
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +0200406 self._vars = {}
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200407
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000408 # A cache of the files affected by the current operation, necessary for
409 # hooks.
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000410 self._file_list = []
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000411 # List of host names from which dependencies are allowed.
412 # Default is an empty set, meaning unspecified in DEPS file, and hence all
413 # hosts will be allowed. Non-empty set means whitelist of hosts.
414 # allowed_hosts var is scoped to its DEPS file, and so it isn't recursive.
415 self._allowed_hosts = frozenset()
Michael Moss848c86e2018-05-03 16:05:50 -0700416 self._gn_args_from = None
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +0200417 # Spec for .gni output to write (if any).
418 self._gn_args_file = None
419 self._gn_args = []
maruel@chromium.org85c2a192010-07-22 21:14:43 +0000420 # If it is not set to True, the dependency wasn't processed for its child
421 # dependency, i.e. its DEPS wasn't read.
maruel@chromium.org064186c2011-09-27 23:53:33 +0000422 self._deps_parsed = False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000423 # This dependency has been processed, i.e. checked out
maruel@chromium.org064186c2011-09-27 23:53:33 +0000424 self._processed = False
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000425 # This dependency had its pre-DEPS hooks run
426 self._pre_deps_hooks_ran = False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000427 # This dependency had its hook run
maruel@chromium.org064186c2011-09-27 23:53:33 +0000428 self._hooks_ran = False
kustermann@google.coma692e8f2013-04-18 08:32:04 +0000429 # This is the scm used to checkout self.url. It may be used by dependencies
430 # to get the datetime of the revision we checked out.
431 self._used_scm = None
szager@chromium.org4ad264b2014-05-20 04:43:47 +0000432 self._used_revision = None
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +0000433 # The actual revision we ended up getting, or None if that information is
434 # unavailable
435 self._got_revision = None
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000436
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000437 # recursedeps is a mutable value that selectively overrides the default
Edward Lemurfbb06aa2018-06-11 20:43:06 +0000438 # 'no recursion' setting on a dep-by-dep basis.
iannucci@chromium.orgafa11ac2016-05-04 22:17:34 +0000439 #
Edward Lemurfbb06aa2018-06-11 20:43:06 +0000440 # It will be a dictionary of {deps_name: depfile_namee}
441 self.recursedeps = {}
442
443 # Whether we should process this dependency's DEPS file.
444 self._should_recurse = should_recurse
Edward Lemure7273d22018-05-10 19:13:51 -0400445
Michael Mossd683d7c2018-06-15 05:05:17 +0000446 self._OverrideUrl()
447 # This is inherited from WorkItem. We want the URL to be a resource.
Aaron Gableac9b0f32019-04-18 17:38:37 +0000448 if self.url and isinstance(self.url, basestring):
Michael Moss4e9b50a2018-05-23 22:35:06 -0700449 # The url is usually given to gclient either as https://blah@123
450 # or just https://blah. The @123 portion is irrelevant.
451 self.resources.append(self.url.split('@')[0])
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000452
Edward Lemur231f5ea2018-01-31 19:02:36 +0100453 # Controls whether we want to print git's output when we first clone the
454 # dependency
455 self.print_outbuf = print_outbuf
456
Michael Mossd683d7c2018-06-15 05:05:17 +0000457 if not self.name and self.parent:
458 raise gclient_utils.Error('Dependency without name')
459
460 def _OverrideUrl(self):
461 """Resolves the parsed url from the parent hierarchy."""
462 parsed_url = self.get_custom_deps(self._name, self.url)
463 if parsed_url != self.url:
464 logging.info('Dependency(%s)._OverrideUrl(%s) -> %s', self._name,
465 self.url, parsed_url)
466 self.set_url(parsed_url)
467
Aaron Gableac9b0f32019-04-18 17:38:37 +0000468 elif isinstance(self.url, basestring):
Michael Mossd683d7c2018-06-15 05:05:17 +0000469 parsed_url = urlparse.urlparse(self.url)
470 if (not parsed_url[0] and
471 not re.match(r'^\w+\@[\w\.-]+\:[\w\/]+', parsed_url[2])):
472 path = parsed_url[2]
473 if not path.startswith('/'):
474 raise gclient_utils.Error(
475 'relative DEPS entry \'%s\' must begin with a slash' % self.url)
476 # A relative url. Get the parent url, strip from the last '/'
477 # (equivalent to unix basename), and append the relative url.
478 parent_url = self.parent.url
479 parsed_url = parent_url[:parent_url.rfind('/')] + self.url
480 logging.info('Dependency(%s)._OverrideUrl(%s) -> %s', self.name,
481 self.url, parsed_url)
482 self.set_url(parsed_url)
483
484 elif self.url is None:
485 logging.info('Dependency(%s)._OverrideUrl(None) -> None', self._name)
486
487 else:
488 raise gclient_utils.Error('Unknown url type')
489
Edward Lemure7273d22018-05-10 19:13:51 -0400490 def PinToActualRevision(self):
Edward Lemure05f18d2018-06-08 17:36:53 +0000491 """Updates self.url to the revision checked out on disk."""
Michael Mossd683d7c2018-06-15 05:05:17 +0000492 if self.url is None:
493 return
Edward Lemure05f18d2018-06-08 17:36:53 +0000494 url = None
Edward Lemurbabd0982018-05-11 13:32:37 -0400495 scm = self.CreateSCM()
Edward Lemure7273d22018-05-10 19:13:51 -0400496 if os.path.isdir(scm.checkout_path):
497 revision = scm.revinfo(None, None, None)
498 url = '%s@%s' % (gclient_utils.SplitUrlRevision(self.url)[0], revision)
Edward Lemure7273d22018-05-10 19:13:51 -0400499 self.set_url(url)
Edward Lemure7273d22018-05-10 19:13:51 -0400500
John Budorick0f7b2002018-01-19 15:46:17 -0800501 def ToLines(self):
502 s = []
503 condition_part = ([' "condition": %r,' % self.condition]
504 if self.condition else [])
505 s.extend([
Michael Moss4e9b50a2018-05-23 22:35:06 -0700506 ' # %s' % self.hierarchy(include_url=False),
John Budorick0f7b2002018-01-19 15:46:17 -0800507 ' "%s": {' % (self.name,),
Edward Lemure05f18d2018-06-08 17:36:53 +0000508 ' "url": "%s",' % (self.url,),
John Budorick0f7b2002018-01-19 15:46:17 -0800509 ] + condition_part + [
510 ' },',
511 '',
512 ])
513 return s
514
maruel@chromium.org470b5432011-10-11 18:18:19 +0000515 @property
516 def requirements(self):
517 """Calculate the list of requirements."""
518 requirements = set()
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000519 # self.parent is implicitly a requirement. This will be recursive by
520 # definition.
521 if self.parent and self.parent.name:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000522 requirements.add(self.parent.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000523
524 # For a tree with at least 2 levels*, the leaf node needs to depend
525 # on the level higher up in an orderly way.
526 # This becomes messy for >2 depth as the DEPS file format is a dictionary,
527 # thus unsorted, while the .gclient format is a list thus sorted.
528 #
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000529 # Interestingly enough, the following condition only works in the case we
530 # want: self is a 2nd level node. 3nd level node wouldn't need this since
531 # they already have their parent as a requirement.
maruel@chromium.org470b5432011-10-11 18:18:19 +0000532 if self.parent and self.parent.parent and not self.parent.parent.parent:
533 requirements |= set(i.name for i in self.root.dependencies if i.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000534
maruel@chromium.org470b5432011-10-11 18:18:19 +0000535 if self.name:
536 requirements |= set(
Michael Mossd683d7c2018-06-15 05:05:17 +0000537 obj.name for obj in self.root.subtree(False)
maruel@chromium.org470b5432011-10-11 18:18:19 +0000538 if (obj is not self
539 and obj.name and
540 self.name.startswith(posixpath.join(obj.name, ''))))
541 requirements = tuple(sorted(requirements))
542 logging.info('Dependency(%s).requirements = %s' % (self.name, requirements))
543 return requirements
544
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000545 @property
Edward Lemurfbb06aa2018-06-11 20:43:06 +0000546 def should_recurse(self):
547 return self._should_recurse
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000548
maruel@chromium.org470b5432011-10-11 18:18:19 +0000549 def verify_validity(self):
550 """Verifies that this Dependency is fine to add as a child of another one.
551
552 Returns True if this entry should be added, False if it is a duplicate of
553 another entry.
554 """
555 logging.info('Dependency(%s).verify_validity()' % self.name)
556 if self.name in [s.name for s in self.parent.dependencies]:
557 raise gclient_utils.Error(
558 'The same name "%s" appears multiple times in the deps section' %
559 self.name)
Michael Mossd683d7c2018-06-15 05:05:17 +0000560 if not self.should_process:
561 # Return early, no need to set requirements.
Edward Lemur7ccf2f02018-06-26 20:41:56 +0000562 return not any(d.name == self.name for d in self.root.subtree(True))
maruel@chromium.org470b5432011-10-11 18:18:19 +0000563
564 # This require a full tree traversal with locks.
Michael Mossd683d7c2018-06-15 05:05:17 +0000565 siblings = [d for d in self.root.subtree(False) if d.name == self.name]
maruel@chromium.org470b5432011-10-11 18:18:19 +0000566 for sibling in siblings:
Michael Mossd683d7c2018-06-15 05:05:17 +0000567 # Allow to have only one to be None or ''.
568 if self.url != sibling.url and bool(self.url) == bool(sibling.url):
maruel@chromium.org470b5432011-10-11 18:18:19 +0000569 raise gclient_utils.Error(
maruel@chromium.orgb848d5b2012-10-10 23:25:50 +0000570 ('Dependency %s specified more than once:\n'
571 ' %s [%s]\n'
572 'vs\n'
573 ' %s [%s]') % (
574 self.name,
575 sibling.hierarchy(),
Edward Lemure7273d22018-05-10 19:13:51 -0400576 sibling.url,
maruel@chromium.orgb848d5b2012-10-10 23:25:50 +0000577 self.hierarchy(),
Edward Lemure7273d22018-05-10 19:13:51 -0400578 self.url))
maruel@chromium.org470b5432011-10-11 18:18:19 +0000579 # In theory we could keep it as a shadow of the other one. In
580 # practice, simply ignore it.
581 logging.warn('Won\'t process duplicate dependency %s' % sibling)
582 return False
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000583 return True
maruel@chromium.org064186c2011-09-27 23:53:33 +0000584
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200585 def _postprocess_deps(self, deps, rel_prefix):
586 """Performs post-processing of deps compared to what's in the DEPS file."""
Paweł Hajdan, Jr4426eaf2017-06-13 12:25:47 +0200587 # Make sure the dict is mutable, e.g. in case it's frozen.
588 deps = dict(deps)
589
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200590 # If a line is in custom_deps, but not in the solution, we want to append
591 # this line to the solution.
Edward Lemuree7b9dd2019-07-20 01:29:08 +0000592 for dep_name, dep_info in six.iteritems(self.custom_deps):
Edward Lemur23a35872018-05-17 01:57:06 -0400593 if dep_name not in deps:
594 deps[dep_name] = {'url': dep_info, 'dep_type': 'git'}
Edward Lemur16f4bad2018-05-16 16:53:49 -0400595
Michael Moss42d02c22018-02-05 10:32:24 -0800596 # Make child deps conditional on any parent conditions. This ensures that,
597 # when flattened, recursed entries have the correct restrictions, even if
598 # not explicitly set in the recursed DEPS file. For instance, if
599 # "src/ios_foo" is conditional on "checkout_ios=True", then anything
600 # recursively included by "src/ios_foo/DEPS" should also require
601 # "checkout_ios=True".
602 if self.condition:
Edward Lemuree7b9dd2019-07-20 01:29:08 +0000603 for value in six.itervalues(deps):
Edward Lemur16f4bad2018-05-16 16:53:49 -0400604 gclient_eval.UpdateCondition(value, 'and', self.condition)
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200605
606 if rel_prefix:
607 logging.warning('use_relative_paths enabled.')
608 rel_deps = {}
Edward Lemuree7b9dd2019-07-20 01:29:08 +0000609 for d, url in six.iteritems(deps):
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200610 # normpath is required to allow DEPS to use .. in their
611 # dependency local path.
612 rel_deps[os.path.normpath(os.path.join(rel_prefix, d))] = url
613 logging.warning('Updating deps by prepending %s.', rel_prefix)
614 deps = rel_deps
615
616 return deps
617
618 def _deps_to_objects(self, deps, use_relative_paths):
619 """Convert a deps dict to a dict of Dependency objects."""
620 deps_to_add = []
Edward Lemuree7b9dd2019-07-20 01:29:08 +0000621 for name, dep_value in six.iteritems(deps):
Michael Mossd683d7c2018-06-15 05:05:17 +0000622 should_process = self.should_process
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200623 if dep_value is None:
624 continue
John Budorick0f7b2002018-01-19 15:46:17 -0800625
Edward Lemur16f4bad2018-05-16 16:53:49 -0400626 condition = dep_value.get('condition')
Michael Mossd683d7c2018-06-15 05:05:17 +0000627 dep_type = dep_value.get('dep_type')
Paweł Hajdan, Jrfc6196b2017-07-27 13:15:25 +0200628
Michael Mossd683d7c2018-06-15 05:05:17 +0000629 if condition and not self._get_option('process_all_deps', False):
630 should_process = should_process and gclient_eval.EvaluateCondition(
Paweł Hajdan, Jrd3790252017-07-03 21:06:24 +0200631 condition, self.get_vars())
John Budorick0f7b2002018-01-19 15:46:17 -0800632
Joey Scarr8d3925b2018-07-15 23:36:25 +0000633 # The following option is only set by the 'revinfo' command.
634 if self._get_option('ignore_dep_type', None) == dep_type:
635 continue
636
John Budorick0f7b2002018-01-19 15:46:17 -0800637 if dep_type == 'cipd':
John Budorickd3ba72b2018-03-20 12:27:42 -0700638 cipd_root = self.GetCipdRoot()
John Budorick0f7b2002018-01-19 15:46:17 -0800639 for package in dep_value.get('packages', []):
640 deps_to_add.append(
641 CipdDependency(
Edward Lemure05f18d2018-06-08 17:36:53 +0000642 parent=self,
643 name=name,
644 dep_value=package,
645 cipd_root=cipd_root,
646 custom_vars=self.custom_vars,
Michael Mossd683d7c2018-06-15 05:05:17 +0000647 should_process=should_process,
Edward Lemure05f18d2018-06-08 17:36:53 +0000648 relative=use_relative_paths,
649 condition=condition))
John Budorick0f7b2002018-01-19 15:46:17 -0800650 else:
Michael Mossd683d7c2018-06-15 05:05:17 +0000651 url = dep_value.get('url')
652 deps_to_add.append(
653 GitDependency(
654 parent=self,
655 name=name,
656 url=url,
Edward Lemure4213702018-06-21 21:15:50 +0000657 managed=True,
Michael Mossd683d7c2018-06-15 05:05:17 +0000658 custom_deps=None,
659 custom_vars=self.custom_vars,
660 custom_hooks=None,
661 deps_file=self.recursedeps.get(name, self.deps_file),
662 should_process=should_process,
663 should_recurse=name in self.recursedeps,
664 relative=use_relative_paths,
665 condition=condition))
John Budorick0f7b2002018-01-19 15:46:17 -0800666
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200667 deps_to_add.sort(key=lambda x: x.name)
668 return deps_to_add
669
Edward Lemure05f18d2018-06-08 17:36:53 +0000670 def ParseDepsFile(self):
maruel@chromium.org271375b2010-06-23 19:17:38 +0000671 """Parses the DEPS file for this dependency."""
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000672 assert not self.deps_parsed
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000673 assert not self.dependencies
iannucci@chromium.org0c3f3052014-05-22 00:29:22 +0000674
675 deps_content = None
cmp@chromium.org76ce73c2014-07-02 00:13:18 +0000676
677 # First try to locate the configured deps file. If it's missing, fallback
678 # to DEPS.
679 deps_files = [self.deps_file]
680 if 'DEPS' not in deps_files:
681 deps_files.append('DEPS')
682 for deps_file in deps_files:
683 filepath = os.path.join(self.root.root_dir, self.name, deps_file)
684 if os.path.isfile(filepath):
685 logging.info(
686 'ParseDepsFile(%s): %s file found at %s', self.name, deps_file,
687 filepath)
688 break
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000689 logging.info(
cmp@chromium.org76ce73c2014-07-02 00:13:18 +0000690 'ParseDepsFile(%s): No %s file found at %s', self.name, deps_file,
691 filepath)
692
693 if os.path.isfile(filepath):
maruel@chromium.org46304292010-10-28 11:42:00 +0000694 deps_content = gclient_utils.FileRead(filepath)
cmp@chromium.org76ce73c2014-07-02 00:13:18 +0000695 logging.debug('ParseDepsFile(%s) read:\n%s', self.name, deps_content)
iannucci@chromium.org0c3f3052014-05-22 00:29:22 +0000696
697 local_scope = {}
698 if deps_content:
maruel@chromium.org46304292010-10-28 11:42:00 +0000699 try:
Edward Lesmes6c24d372018-03-28 12:52:29 -0400700 local_scope = gclient_eval.Parse(
Michael Mossd683d7c2018-06-15 05:05:17 +0000701 deps_content, self._get_option('validate_syntax', False),
Edward Lemur8f8a50d2018-11-01 22:03:02 +0000702 filepath, self.get_vars(), self.get_builtin_vars())
vapier@chromium.orga81a56e2015-11-11 07:56:13 +0000703 except SyntaxError as e:
maruel@chromium.org46304292010-10-28 11:42:00 +0000704 gclient_utils.SyntaxErrorToError(filepath, e)
iannucci@chromium.org0c3f3052014-05-22 00:29:22 +0000705
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000706 if 'allowed_hosts' in local_scope:
707 try:
708 self._allowed_hosts = frozenset(local_scope.get('allowed_hosts'))
709 except TypeError: # raised if non-iterable
710 pass
711 if not self._allowed_hosts:
712 logging.warning("allowed_hosts is specified but empty %s",
713 self._allowed_hosts)
714 raise gclient_utils.Error(
715 'ParseDepsFile(%s): allowed_hosts must be absent '
716 'or a non-empty iterable' % self.name)
717
Michael Moss848c86e2018-05-03 16:05:50 -0700718 self._gn_args_from = local_scope.get('gclient_gn_args_from')
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +0200719 self._gn_args_file = local_scope.get('gclient_gn_args_file')
720 self._gn_args = local_scope.get('gclient_gn_args', [])
Michael Moss848c86e2018-05-03 16:05:50 -0700721 # It doesn't make sense to set all of these, since setting gn_args_from to
722 # another DEPS will make gclient ignore any other local gn_args* settings.
723 assert not (self._gn_args_from and self._gn_args_file), \
724 'Only specify one of "gclient_gn_args_from" or ' \
725 '"gclient_gn_args_file + gclient_gn_args".'
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +0200726
Edward Lesmes0b899352018-03-19 21:59:55 +0000727 self._vars = local_scope.get('vars', {})
Paweł Hajdan, Jr1407d002017-08-01 20:01:01 +0200728 if self.parent:
Edward Lemuree7b9dd2019-07-20 01:29:08 +0000729 for key, value in six.iteritems(self.parent.get_vars()):
Paweł Hajdan, Jr1407d002017-08-01 20:01:01 +0200730 if key in self._vars:
731 self._vars[key] = value
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200732 # Since we heavily post-process things, freeze ones which should
733 # reflect original state of DEPS.
Paweł Hajdan, Jr1407d002017-08-01 20:01:01 +0200734 self._vars = gclient_utils.freeze(self._vars)
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200735
736 # If use_relative_paths is set in the DEPS file, regenerate
737 # the dictionary using paths relative to the directory containing
738 # the DEPS file. Also update recursedeps if use_relative_paths is
739 # enabled.
740 # If the deps file doesn't set use_relative_paths, but the parent did
741 # (and therefore set self.relative on this Dependency object), then we
742 # want to modify the deps and recursedeps by prepending the parent
743 # directory of this dependency.
744 use_relative_paths = local_scope.get('use_relative_paths', False)
745 rel_prefix = None
746 if use_relative_paths:
747 rel_prefix = self.name
748 elif self._relative:
749 rel_prefix = os.path.dirname(self.name)
750
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200751 if 'recursion' in local_scope:
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200752 logging.warning(
Edward Lemurfbb06aa2018-06-11 20:43:06 +0000753 '%s: Ignoring recursion = %d.', self.name, local_scope['recursion'])
754
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200755 if 'recursedeps' in local_scope:
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200756 for ent in local_scope['recursedeps']:
Aaron Gableac9b0f32019-04-18 17:38:37 +0000757 if isinstance(ent, basestring):
Edward Lemurfbb06aa2018-06-11 20:43:06 +0000758 self.recursedeps[ent] = self.deps_file
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200759 else: # (depname, depsfilename)
Edward Lemurfbb06aa2018-06-11 20:43:06 +0000760 self.recursedeps[ent[0]] = ent[1]
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200761 logging.warning('Found recursedeps %r.', repr(self.recursedeps))
762
763 if rel_prefix:
764 logging.warning('Updating recursedeps by prepending %s.', rel_prefix)
765 rel_deps = {}
Edward Lemuree7b9dd2019-07-20 01:29:08 +0000766 for depname, options in six.iteritems(self.recursedeps):
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200767 rel_deps[
768 os.path.normpath(os.path.join(rel_prefix, depname))] = options
769 self.recursedeps = rel_deps
Michael Moss848c86e2018-05-03 16:05:50 -0700770 # To get gn_args from another DEPS, that DEPS must be recursed into.
771 if self._gn_args_from:
772 assert self.recursedeps and self._gn_args_from in self.recursedeps, \
773 'The "gclient_gn_args_from" value must be in recursedeps.'
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200774
775 # If present, save 'target_os' in the local_target_os property.
776 if 'target_os' in local_scope:
777 self.local_target_os = local_scope['target_os']
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200778
Edward Lemur16f4bad2018-05-16 16:53:49 -0400779 deps = local_scope.get('deps', {})
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200780 deps_to_add = self._deps_to_objects(
781 self._postprocess_deps(deps, rel_prefix), use_relative_paths)
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000782
Corentin Walleza68660d2018-09-10 17:33:24 +0000783 # compute which working directory should be used for hooks
784 use_relative_hooks = local_scope.get('use_relative_hooks', False)
785 hooks_cwd = self.root.root_dir
786 if use_relative_hooks:
787 if not use_relative_paths:
788 raise gclient_utils.Error(
789 'ParseDepsFile(%s): use_relative_hooks must be used with '
790 'use_relative_paths' % self.name)
791 hooks_cwd = os.path.join(hooks_cwd, self.name)
792 logging.warning('Updating hook base working directory to %s.',
793 hooks_cwd)
794
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000795 # override named sets of hooks by the custom hooks
796 hooks_to_run = []
797 hook_names_to_suppress = [c.get('name', '') for c in self.custom_hooks]
798 for hook in local_scope.get('hooks', []):
799 if hook.get('name', '') not in hook_names_to_suppress:
800 hooks_to_run.append(hook)
801
802 # add the replacements and any additions
803 for hook in self.custom_hooks:
804 if 'action' in hook:
805 hooks_to_run.append(hook)
806
Edward Lemurfbb06aa2018-06-11 20:43:06 +0000807 if self.should_recurse:
Paweł Hajdan, Jr032d5452017-06-22 20:43:53 +0200808 self._pre_deps_hooks = [
Michael Moss42d02c22018-02-05 10:32:24 -0800809 Hook.from_dict(hook, variables=self.get_vars(), verbose=True,
Corentin Walleza68660d2018-09-10 17:33:24 +0000810 conditions=self.condition, cwd_base=hooks_cwd)
Daniel Chenga0c5f082017-10-19 13:35:19 -0700811 for hook in local_scope.get('pre_deps_hooks', [])
812 ]
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000813
Corentin Walleza68660d2018-09-10 17:33:24 +0000814 self.add_dependencies_and_close(deps_to_add, hooks_to_run,
815 hooks_cwd=hooks_cwd)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000816 logging.info('ParseDepsFile(%s) done' % self.name)
817
Michael Mossd683d7c2018-06-15 05:05:17 +0000818 def _get_option(self, attr, default):
819 obj = self
820 while not hasattr(obj, '_options'):
821 obj = obj.parent
822 return getattr(obj._options, attr, default)
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +0200823
Corentin Walleza68660d2018-09-10 17:33:24 +0000824 def add_dependencies_and_close(self, deps_to_add, hooks, hooks_cwd=None):
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000825 """Adds the dependencies, hooks and mark the parsing as done."""
Corentin Walleza68660d2018-09-10 17:33:24 +0000826 if hooks_cwd == None:
827 hooks_cwd = self.root.root_dir
828
maruel@chromium.orgb9be0652011-10-14 18:05:40 +0000829 for dep in deps_to_add:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000830 if dep.verify_validity():
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000831 self.add_dependency(dep)
Daniel Chenga0c5f082017-10-19 13:35:19 -0700832 self._mark_as_parsed([
833 Hook.from_dict(
Michael Moss42d02c22018-02-05 10:32:24 -0800834 h, variables=self.get_vars(), verbose=self.root._options.verbose,
Corentin Walleza68660d2018-09-10 17:33:24 +0000835 conditions=self.condition, cwd_base=hooks_cwd)
Daniel Chenga0c5f082017-10-19 13:35:19 -0700836 for h in hooks
837 ])
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000838
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000839 def findDepsFromNotAllowedHosts(self):
Corentin Wallezaca984c2018-09-07 21:52:14 +0000840 """Returns a list of dependencies from not allowed hosts.
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000841
842 If allowed_hosts is not set, allows all hosts and returns empty list.
843 """
844 if not self._allowed_hosts:
845 return []
846 bad_deps = []
847 for dep in self._dependencies:
szager@chromium.orgbd772dd2014-11-05 18:43:08 +0000848 # Don't enforce this for custom_deps.
849 if dep.name in self._custom_deps:
850 continue
Michael Mossd683d7c2018-06-15 05:05:17 +0000851 if isinstance(dep.url, basestring):
852 parsed_url = urlparse.urlparse(dep.url)
853 if parsed_url.netloc and parsed_url.netloc not in self._allowed_hosts:
854 bad_deps.append(dep)
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000855 return bad_deps
856
Edward Lemure7273d22018-05-10 19:13:51 -0400857 def FuzzyMatchUrl(self, candidates):
Edward Lesmesbb16e332018-03-30 17:54:51 -0400858 """Attempts to find this dependency in the list of candidates.
859
Edward Lemure7273d22018-05-10 19:13:51 -0400860 It looks first for the URL of this dependency in the list of
Edward Lesmesbb16e332018-03-30 17:54:51 -0400861 candidates. If it doesn't succeed, and the URL ends in '.git', it will try
862 looking for the URL minus '.git'. Finally it will try to look for the name
863 of the dependency.
864
865 Args:
Edward Lesmesbb16e332018-03-30 17:54:51 -0400866 candidates: list, dict. The list of candidates in which to look for this
867 dependency. It can contain URLs as above, or dependency names like
868 "src/some/dep".
869
870 Returns:
871 If this dependency is not found in the list of candidates, returns None.
872 Otherwise, it returns under which name did we find this dependency:
873 - Its parsed url: "https://example.com/src.git'
874 - Its parsed url minus '.git': "https://example.com/src"
875 - Its name: "src"
876 """
Edward Lemure7273d22018-05-10 19:13:51 -0400877 if self.url:
878 origin, _ = gclient_utils.SplitUrlRevision(self.url)
Edward Lesmesbb16e332018-03-30 17:54:51 -0400879 if origin in candidates:
880 return origin
881 if origin.endswith('.git') and origin[:-len('.git')] in candidates:
882 return origin[:-len('.git')]
Edward Lesmes990148e2018-04-26 14:56:55 -0400883 if origin + '.git' in candidates:
884 return origin + '.git'
Edward Lesmesbb16e332018-03-30 17:54:51 -0400885 if self.name in candidates:
886 return self.name
887 return None
888
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +0000889 # Arguments number differs from overridden method
Quinten Yearsleyb2cc4a92016-12-15 13:53:26 -0800890 # pylint: disable=arguments-differ
Edward Lesmesc621b212018-03-21 20:26:56 -0400891 def run(self, revision_overrides, command, args, work_queue, options,
Edward Lemur6a4e31b2018-08-10 19:59:02 +0000892 patch_refs, target_branches):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000893 """Runs |command| then parse the DEPS file."""
maruel@chromium.org470b5432011-10-11 18:18:19 +0000894 logging.info('Dependency(%s).run()' % self.name)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000895 assert self._file_list == []
Michael Mossd683d7c2018-06-15 05:05:17 +0000896 if not self.should_process:
897 return
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000898 # When running runhooks, there's no need to consult the SCM.
899 # All known hooks are expected to run unconditionally regardless of working
900 # copy state, so skip the SCM status check.
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +0200901 run_scm = command not in (
902 'flatten', 'runhooks', 'recurse', 'validate', None)
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000903 file_list = [] if not options.nohooks else None
Edward Lesmesbb16e332018-03-30 17:54:51 -0400904 revision_override = revision_overrides.pop(
Edward Lemure7273d22018-05-10 19:13:51 -0400905 self.FuzzyMatchUrl(revision_overrides), None)
Edward Lemure4213702018-06-21 21:15:50 +0000906 if not revision_override and not self.managed:
907 revision_override = 'unmanaged'
Michael Mossd683d7c2018-06-15 05:05:17 +0000908 if run_scm and self.url:
agabled437d762016-10-17 09:35:11 -0700909 # Create a shallow copy to mutate revision.
910 options = copy.copy(options)
911 options.revision = revision_override
912 self._used_revision = options.revision
Edward Lemurbabd0982018-05-11 13:32:37 -0400913 self._used_scm = self.CreateSCM(out_cb=work_queue.out_cb)
agabled437d762016-10-17 09:35:11 -0700914 self._got_revision = self._used_scm.RunCommand(command, options, args,
915 file_list)
Edward Lesmesc621b212018-03-21 20:26:56 -0400916
Edward Lemure7273d22018-05-10 19:13:51 -0400917 patch_repo = self.url.split('@')[0]
918 patch_ref = patch_refs.pop(self.FuzzyMatchUrl(patch_refs), None)
Edward Lemur6a4e31b2018-08-10 19:59:02 +0000919 target_branch = target_branches.pop(
920 self.FuzzyMatchUrl(target_branches), None)
Edward Lesmesc621b212018-03-21 20:26:56 -0400921 if command == 'update' and patch_ref is not None:
Edward Lemur6a4e31b2018-08-10 19:59:02 +0000922 self._used_scm.apply_patch_ref(patch_repo, patch_ref, target_branch,
923 options, file_list)
Edward Lesmesc621b212018-03-21 20:26:56 -0400924
agabled437d762016-10-17 09:35:11 -0700925 if file_list:
926 file_list = [os.path.join(self.name, f.strip()) for f in file_list]
maruel@chromium.org68988972011-09-20 14:11:42 +0000927
928 # TODO(phajdan.jr): We should know exactly when the paths are absolute.
929 # Convert all absolute paths to relative.
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000930 for i in range(len(file_list or [])):
maruel@chromium.org68988972011-09-20 14:11:42 +0000931 # It depends on the command being executed (like runhooks vs sync).
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000932 if not os.path.isabs(file_list[i]):
maruel@chromium.org68988972011-09-20 14:11:42 +0000933 continue
934 prefix = os.path.commonprefix(
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000935 [self.root.root_dir.lower(), file_list[i].lower()])
936 file_list[i] = file_list[i][len(prefix):]
maruel@chromium.org68988972011-09-20 14:11:42 +0000937 # Strip any leading path separators.
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000938 while file_list[i].startswith(('\\', '/')):
939 file_list[i] = file_list[i][1:]
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000940
Edward Lemurfbb06aa2018-06-11 20:43:06 +0000941 if self.should_recurse:
Edward Lemure05f18d2018-06-08 17:36:53 +0000942 self.ParseDepsFile()
Edward Lesmes5d6cde32018-04-12 18:32:46 -0400943
Edward Lemure7273d22018-05-10 19:13:51 -0400944 self._run_is_done(file_list or [])
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000945
Edward Lemurfbb06aa2018-06-11 20:43:06 +0000946 if self.should_recurse:
Edward Lesmes5d6cde32018-04-12 18:32:46 -0400947 if command in ('update', 'revert') and not options.noprehooks:
948 self.RunPreDepsHooks()
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000949 # Parse the dependencies of this dependency.
950 for s in self.dependencies:
Michael Mossd683d7c2018-06-15 05:05:17 +0000951 if s.should_process:
952 work_queue.enqueue(s)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000953
954 if command == 'recurse':
agabled437d762016-10-17 09:35:11 -0700955 # Skip file only checkout.
Edward Lemurbabd0982018-05-11 13:32:37 -0400956 scm = self.GetScmName()
agabled437d762016-10-17 09:35:11 -0700957 if not options.scm or scm in options.scm:
958 cwd = os.path.normpath(os.path.join(self.root.root_dir, self.name))
959 # Pass in the SCM type as an env variable. Make sure we don't put
960 # unicode strings in the environment.
961 env = os.environ.copy()
Michael Mossd683d7c2018-06-15 05:05:17 +0000962 if scm:
963 env['GCLIENT_SCM'] = str(scm)
964 if self.url:
965 env['GCLIENT_URL'] = str(self.url)
agabled437d762016-10-17 09:35:11 -0700966 env['GCLIENT_DEP_PATH'] = str(self.name)
967 if options.prepend_dir and scm == 'git':
968 print_stdout = False
969 def filter_fn(line):
970 """Git-specific path marshaling. It is optimized for git-grep."""
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000971
agabled437d762016-10-17 09:35:11 -0700972 def mod_path(git_pathspec):
973 match = re.match('^(\\S+?:)?([^\0]+)$', git_pathspec)
974 modified_path = os.path.join(self.name, match.group(2))
975 branch = match.group(1) or ''
976 return '%s%s' % (branch, modified_path)
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000977
agabled437d762016-10-17 09:35:11 -0700978 match = re.match('^Binary file ([^\0]+) matches$', line)
979 if match:
980 print('Binary file %s matches\n' % mod_path(match.group(1)))
981 return
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000982
agabled437d762016-10-17 09:35:11 -0700983 items = line.split('\0')
984 if len(items) == 2 and items[1]:
985 print('%s : %s' % (mod_path(items[0]), items[1]))
986 elif len(items) >= 2:
987 # Multiple null bytes or a single trailing null byte indicate
988 # git is likely displaying filenames only (such as with -l)
989 print('\n'.join(mod_path(path) for path in items if path))
990 else:
991 print(line)
992 else:
993 print_stdout = True
994 filter_fn = None
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000995
Michael Mossd683d7c2018-06-15 05:05:17 +0000996 if self.url is None:
997 print('Skipped omitted dependency %s' % cwd, file=sys.stderr)
998 elif os.path.isdir(cwd):
agabled437d762016-10-17 09:35:11 -0700999 try:
1000 gclient_utils.CheckCallAndFilter(
1001 args, cwd=cwd, env=env, print_stdout=print_stdout,
1002 filter_fn=filter_fn,
1003 )
1004 except subprocess2.CalledProcessError:
1005 if not options.ignore:
1006 raise
1007 else:
1008 print('Skipped missing %s' % cwd, file=sys.stderr)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001009
Edward Lemurbabd0982018-05-11 13:32:37 -04001010 def GetScmName(self):
Edward Lemurb61d3872018-05-09 18:42:47 -04001011 raise NotImplementedError()
John Budorick0f7b2002018-01-19 15:46:17 -08001012
Edward Lemurbabd0982018-05-11 13:32:37 -04001013 def CreateSCM(self, out_cb=None):
Edward Lemurb61d3872018-05-09 18:42:47 -04001014 raise NotImplementedError()
John Budorick0f7b2002018-01-19 15:46:17 -08001015
Dirk Pranke9f20d022017-10-11 18:36:54 -07001016 def HasGNArgsFile(self):
1017 return self._gn_args_file is not None
1018
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +02001019 def WriteGNArgsFile(self):
1020 lines = ['# Generated from %r' % self.deps_file]
Paweł Hajdan, Jrb495bf52017-09-25 19:33:50 +02001021 variables = self.get_vars()
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +02001022 for arg in self._gn_args:
Paweł Hajdan, Jre0214742017-09-28 12:21:01 +02001023 value = variables[arg]
Aaron Gableac9b0f32019-04-18 17:38:37 +00001024 if isinstance(value, basestring):
Paweł Hajdan, Jre0214742017-09-28 12:21:01 +02001025 value = gclient_eval.EvaluateCondition(value, variables)
Paweł Hajdan, Jrb495bf52017-09-25 19:33:50 +02001026 lines.append('%s = %s' % (arg, ToGNString(value)))
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +02001027 with open(os.path.join(self.root.root_dir, self._gn_args_file), 'w') as f:
1028 f.write('\n'.join(lines))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001029
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001030 @gclient_utils.lockedmethod
Edward Lemure7273d22018-05-10 19:13:51 -04001031 def _run_is_done(self, file_list):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001032 # Both these are kept for hooks that are run as a separate tree traversal.
1033 self._file_list = file_list
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001034 self._processed = True
1035
szager@google.comb9a78d32012-03-13 18:46:21 +00001036 def GetHooks(self, options):
1037 """Evaluates all hooks, and return them in a flat list.
1038
1039 RunOnDeps() must have been called before to load the DEPS.
1040 """
1041 result = []
Michael Mossd683d7c2018-06-15 05:05:17 +00001042 if not self.should_process or not self.should_recurse:
1043 # Don't run the hook when it is above recursion_limit.
1044 return result
maruel@chromium.orgdc7445d2010-07-09 21:05:29 +00001045 # If "--force" was specified, run all hooks regardless of what files have
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001046 # changed.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001047 if self.deps_hooks:
agabled437d762016-10-17 09:35:11 -07001048 # TODO(maruel): If the user is using git, then we don't know
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001049 # what files have changed so we always run all hooks. It'd be nice to fix
1050 # that.
Edward Lemurbabd0982018-05-11 13:32:37 -04001051 result.extend(self.deps_hooks)
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001052 for s in self.dependencies:
szager@google.comb9a78d32012-03-13 18:46:21 +00001053 result.extend(s.GetHooks(options))
1054 return result
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001055
Daniel Chenga0c5f082017-10-19 13:35:19 -07001056 def RunHooksRecursively(self, options, progress):
szager@google.comb9a78d32012-03-13 18:46:21 +00001057 assert self.hooks_ran == False
maruel@chromium.org064186c2011-09-27 23:53:33 +00001058 self._hooks_ran = True
Daniel Chenga0c5f082017-10-19 13:35:19 -07001059 hooks = self.GetHooks(options)
1060 if progress:
1061 progress._total = len(hooks)
1062 for hook in hooks:
Daniel Chenga0c5f082017-10-19 13:35:19 -07001063 if progress:
1064 progress.update(extra=hook.name or '')
Corentin Walleza68660d2018-09-10 17:33:24 +00001065 hook.run()
Daniel Chenga0c5f082017-10-19 13:35:19 -07001066 if progress:
1067 progress.end()
maruel@chromium.orgeaf61062010-07-07 18:42:39 +00001068
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001069 def RunPreDepsHooks(self):
1070 assert self.processed
1071 assert self.deps_parsed
1072 assert not self.pre_deps_hooks_ran
1073 assert not self.hooks_ran
1074 for s in self.dependencies:
1075 assert not s.processed
1076 self._pre_deps_hooks_ran = True
1077 for hook in self.pre_deps_hooks:
Corentin Walleza68660d2018-09-10 17:33:24 +00001078 hook.run()
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001079
John Budorickd3ba72b2018-03-20 12:27:42 -07001080 def GetCipdRoot(self):
1081 if self.root is self:
1082 # Let's not infinitely recurse. If this is root and isn't an
1083 # instance of GClient, do nothing.
1084 return None
1085 return self.root.GetCipdRoot()
1086
Michael Mossd683d7c2018-06-15 05:05:17 +00001087 def subtree(self, include_all):
maruel@chromium.orgad3287e2011-10-03 19:15:10 +00001088 """Breadth first recursion excluding root node."""
maruel@chromium.orgf13a4182011-09-22 00:26:15 +00001089 dependencies = self.dependencies
1090 for d in dependencies:
Michael Mossd683d7c2018-06-15 05:05:17 +00001091 if d.should_process or include_all:
1092 yield d
maruel@chromium.orgf13a4182011-09-22 00:26:15 +00001093 for d in dependencies:
Michael Mossd683d7c2018-06-15 05:05:17 +00001094 for i in d.subtree(include_all):
maruel@chromium.orgad3287e2011-10-03 19:15:10 +00001095 yield i
1096
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001097 @gclient_utils.lockedmethod
1098 def add_dependency(self, new_dep):
1099 self._dependencies.append(new_dep)
1100
1101 @gclient_utils.lockedmethod
Paweł Hajdan, Jr357415c2017-07-24 14:35:28 +02001102 def _mark_as_parsed(self, new_hooks):
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001103 self._deps_hooks.extend(new_hooks)
1104 self._deps_parsed = True
1105
maruel@chromium.org68988972011-09-20 14:11:42 +00001106 @property
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001107 @gclient_utils.lockedmethod
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +00001108 def dependencies(self):
1109 return tuple(self._dependencies)
1110
1111 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001112 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001113 def deps_hooks(self):
1114 return tuple(self._deps_hooks)
1115
1116 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001117 @gclient_utils.lockedmethod
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001118 def pre_deps_hooks(self):
1119 return tuple(self._pre_deps_hooks)
1120
1121 @property
1122 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001123 def deps_parsed(self):
maruel@chromium.org3223edd2011-10-10 23:17:39 +00001124 """This is purely for debugging purposes. It's not used anywhere."""
maruel@chromium.org064186c2011-09-27 23:53:33 +00001125 return self._deps_parsed
1126
1127 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001128 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001129 def processed(self):
1130 return self._processed
1131
1132 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001133 @gclient_utils.lockedmethod
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001134 def pre_deps_hooks_ran(self):
1135 return self._pre_deps_hooks_ran
1136
1137 @property
1138 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001139 def hooks_ran(self):
1140 return self._hooks_ran
1141
1142 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001143 @gclient_utils.lockedmethod
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00001144 def allowed_hosts(self):
1145 return self._allowed_hosts
1146
1147 @property
1148 @gclient_utils.lockedmethod
maruel@chromium.org861fd0f2010-07-23 03:05:05 +00001149 def file_list(self):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001150 return tuple(self._file_list)
1151
1152 @property
kustermann@google.coma692e8f2013-04-18 08:32:04 +00001153 def used_scm(self):
1154 """SCMWrapper instance for this dependency or None if not processed yet."""
1155 return self._used_scm
1156
1157 @property
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00001158 @gclient_utils.lockedmethod
1159 def got_revision(self):
1160 return self._got_revision
1161
1162 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001163 def file_list_and_children(self):
1164 result = list(self.file_list)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +00001165 for d in self.dependencies:
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001166 result.extend(d.file_list_and_children)
maruel@chromium.org68988972011-09-20 14:11:42 +00001167 return tuple(result)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +00001168
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001169 def __str__(self):
1170 out = []
Edward Lemure7273d22018-05-10 19:13:51 -04001171 for i in ('name', 'url', 'custom_deps',
Michael Mossd683d7c2018-06-15 05:05:17 +00001172 'custom_vars', 'deps_hooks', 'file_list', 'should_process',
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00001173 'processed', 'hooks_ran', 'deps_parsed', 'requirements',
1174 'allowed_hosts'):
maruel@chromium.org3c74bc92011-09-15 19:17:21 +00001175 # First try the native property if it exists.
1176 if hasattr(self, '_' + i):
1177 value = getattr(self, '_' + i, False)
1178 else:
1179 value = getattr(self, i, False)
1180 if value:
1181 out.append('%s: %s' % (i, value))
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001182
1183 for d in self.dependencies:
1184 out.extend([' ' + x for x in str(d).splitlines()])
1185 out.append('')
1186 return '\n'.join(out)
1187
1188 def __repr__(self):
1189 return '%s: %s' % (self.name, self.url)
1190
Michael Moss4e9b50a2018-05-23 22:35:06 -07001191 def hierarchy(self, include_url=True):
maruel@chromium.orgbc2d2f92010-07-22 21:26:48 +00001192 """Returns a human-readable hierarchical reference to a Dependency."""
Michael Moss4e9b50a2018-05-23 22:35:06 -07001193 def format_name(d):
1194 if include_url:
1195 return '%s(%s)' % (d.name, d.url)
1196 return d.name
1197 out = format_name(self)
maruel@chromium.orgbffb9042010-07-22 20:59:36 +00001198 i = self.parent
1199 while i and i.name:
Michael Moss4e9b50a2018-05-23 22:35:06 -07001200 out = '%s -> %s' % (format_name(i), out)
maruel@chromium.orgbffb9042010-07-22 20:59:36 +00001201 i = i.parent
1202 return out
1203
Michael Mossfe68c912018-03-22 19:19:35 -07001204 def hierarchy_data(self):
1205 """Returns a machine-readable hierarchical reference to a Dependency."""
1206 d = self
1207 out = []
1208 while d and d.name:
1209 out.insert(0, (d.name, d.url))
1210 d = d.parent
1211 return tuple(out)
1212
Edward Lemur8f8a50d2018-11-01 22:03:02 +00001213 def get_builtin_vars(self):
1214 return {
Paweł Hajdan, Jrd325eb32017-10-03 17:43:37 +02001215 'checkout_android': 'android' in self.target_os,
Benjamin Pastene6fe29412018-01-23 15:35:58 -08001216 'checkout_chromeos': 'chromeos' in self.target_os,
Paweł Hajdan, Jrd325eb32017-10-03 17:43:37 +02001217 'checkout_fuchsia': 'fuchsia' in self.target_os,
1218 'checkout_ios': 'ios' in self.target_os,
1219 'checkout_linux': 'unix' in self.target_os,
1220 'checkout_mac': 'mac' in self.target_os,
1221 'checkout_win': 'win' in self.target_os,
1222 'host_os': _detect_host_os(),
Tom Andersonc31ae0b2018-02-06 14:48:56 -08001223
1224 'checkout_arm': 'arm' in self.target_cpu,
1225 'checkout_arm64': 'arm64' in self.target_cpu,
1226 'checkout_x86': 'x86' in self.target_cpu,
1227 'checkout_mips': 'mips' in self.target_cpu,
Wang Qing254538b2018-07-26 02:23:53 +00001228 'checkout_mips64': 'mips64' in self.target_cpu,
Tom Andersonc31ae0b2018-02-06 14:48:56 -08001229 'checkout_ppc': 'ppc' in self.target_cpu,
1230 'checkout_s390': 's390' in self.target_cpu,
1231 'checkout_x64': 'x64' in self.target_cpu,
1232 'host_cpu': detect_host_arch.HostArch(),
Paweł Hajdan, Jra3b67ae2017-08-30 15:18:21 +02001233 }
Edward Lemur8f8a50d2018-11-01 22:03:02 +00001234
1235 def get_vars(self):
1236 """Returns a dictionary of effective variable values
1237 (DEPS file contents with applied custom_vars overrides)."""
1238 # Variable precedence (last has highest):
Michael Mossda55cdc2018-04-06 18:37:19 -07001239 # - DEPS vars
1240 # - parents, from first to last
Edward Lemur8f8a50d2018-11-01 22:03:02 +00001241 # - built-in
Michael Mossda55cdc2018-04-06 18:37:19 -07001242 # - custom_vars overrides
Edward Lemur8f8a50d2018-11-01 22:03:02 +00001243 result = {}
Paweł Hajdan, Jra3b67ae2017-08-30 15:18:21 +02001244 result.update(self._vars)
Michael Mossda55cdc2018-04-06 18:37:19 -07001245 if self.parent:
1246 parent_vars = self.parent.get_vars()
1247 result.update(parent_vars)
Edward Lemur8f8a50d2018-11-01 22:03:02 +00001248 # Provide some built-in variables.
1249 result.update(self.get_builtin_vars())
Paweł Hajdan, Jrd3790252017-07-03 21:06:24 +02001250 result.update(self.custom_vars or {})
1251 return result
1252
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001253
Paweł Hajdan, Jra3b67ae2017-08-30 15:18:21 +02001254_PLATFORM_MAPPING = {
1255 'cygwin': 'win',
1256 'darwin': 'mac',
1257 'linux2': 'linux',
Edward Lemuree7b9dd2019-07-20 01:29:08 +00001258 'linux': 'linux',
Paweł Hajdan, Jra3b67ae2017-08-30 15:18:21 +02001259 'win32': 'win',
Jaideep Bajwad05f3582017-09-11 12:31:48 -04001260 'aix6': 'aix',
Paweł Hajdan, Jra3b67ae2017-08-30 15:18:21 +02001261}
1262
1263
1264def _detect_host_os():
1265 return _PLATFORM_MAPPING[sys.platform]
1266
1267
Edward Lemurb61d3872018-05-09 18:42:47 -04001268class GitDependency(Dependency):
1269 """A Dependency object that represents a single git checkout."""
1270
1271 #override
Edward Lemurbabd0982018-05-11 13:32:37 -04001272 def GetScmName(self):
Edward Lemurb61d3872018-05-09 18:42:47 -04001273 """Always 'git'."""
Edward Lemurb61d3872018-05-09 18:42:47 -04001274 return 'git'
1275
1276 #override
Edward Lemurbabd0982018-05-11 13:32:37 -04001277 def CreateSCM(self, out_cb=None):
Edward Lemurb61d3872018-05-09 18:42:47 -04001278 """Create a Wrapper instance suitable for handling this git dependency."""
Edward Lemurbabd0982018-05-11 13:32:37 -04001279 return gclient_scm.GitWrapper(
1280 self.url, self.root.root_dir, self.name, self.outbuf, out_cb,
1281 print_outbuf=self.print_outbuf)
Edward Lemurb61d3872018-05-09 18:42:47 -04001282
1283
1284class GClient(GitDependency):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001285 """Object that represent a gclient checkout. A tree of Dependency(), one per
1286 solution or DEPS entry."""
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001287
1288 DEPS_OS_CHOICES = {
Jaideep Bajwad05f3582017-09-11 12:31:48 -04001289 "aix6": "unix",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001290 "win32": "win",
1291 "win": "win",
1292 "cygwin": "win",
1293 "darwin": "mac",
1294 "mac": "mac",
1295 "unix": "unix",
1296 "linux": "unix",
1297 "linux2": "unix",
maruel@chromium.org244e3442011-06-12 15:20:55 +00001298 "linux3": "unix",
szager@chromium.orgf8c95cd2012-06-01 22:26:52 +00001299 "android": "android",
Michael Mossc54fa812017-08-17 11:27:58 -07001300 "ios": "ios",
Sergiy Byelozyorov518bb682018-06-03 11:25:58 +02001301 "fuchsia": "fuchsia",
Michael Moss484d74f2019-02-06 01:55:43 +00001302 "chromeos": "chromeos",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001303 }
1304
1305 DEFAULT_CLIENT_FILE_TEXT = ("""\
1306solutions = [
smutae7ea312016-07-18 11:59:41 -07001307 { "name" : "%(solution_name)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001308 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +00001309 "deps_file" : "%(deps_file)s",
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001310 "managed" : %(managed)s,
smutae7ea312016-07-18 11:59:41 -07001311 "custom_deps" : {
1312 },
Paweł Hajdan, Jr3ba2a7c2017-10-04 19:24:46 +02001313 "custom_vars": %(custom_vars)r,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001314 },
1315]
Robert Iannuccia19649b2018-06-29 16:31:45 +00001316""")
1317
1318 DEFAULT_CLIENT_CACHE_DIR_TEXT = ("""\
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001319cache_dir = %(cache_dir)r
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001320""")
1321
Robert Iannuccia19649b2018-06-29 16:31:45 +00001322
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001323 DEFAULT_SNAPSHOT_FILE_TEXT = ("""\
1324# Snapshot generated with gclient revinfo --snapshot
Edward Lesmesc2960242018-03-06 20:50:15 -05001325solutions = %(solution_list)s
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001326""")
1327
1328 def __init__(self, root_dir, options):
maruel@chromium.org0d812442010-08-10 12:41:08 +00001329 # Do not change previous behavior. Only solution level and immediate DEPS
1330 # are processed.
1331 self._recursion_limit = 2
Edward Lemure05f18d2018-06-08 17:36:53 +00001332 super(GClient, self).__init__(
1333 parent=None,
1334 name=None,
Michael Mossd683d7c2018-06-15 05:05:17 +00001335 url=None,
Edward Lemure05f18d2018-06-08 17:36:53 +00001336 managed=True,
1337 custom_deps=None,
1338 custom_vars=None,
1339 custom_hooks=None,
1340 deps_file='unused',
Michael Mossd683d7c2018-06-15 05:05:17 +00001341 should_process=True,
Edward Lemurfbb06aa2018-06-11 20:43:06 +00001342 should_recurse=True,
Edward Lemure05f18d2018-06-08 17:36:53 +00001343 relative=None,
1344 condition=None,
1345 print_outbuf=True)
1346
maruel@chromium.org0d425922010-06-21 19:22:24 +00001347 self._options = options
maruel@chromium.org271375b2010-06-23 19:17:38 +00001348 if options.deps_os:
1349 enforced_os = options.deps_os.split(',')
1350 else:
1351 enforced_os = [self.DEPS_OS_CHOICES.get(sys.platform, 'unix')]
1352 if 'all' in enforced_os:
Edward Lemuree7b9dd2019-07-20 01:29:08 +00001353 enforced_os = self.DEPS_OS_CHOICES.values()
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001354 self._enforced_os = tuple(set(enforced_os))
Tom Andersonc31ae0b2018-02-06 14:48:56 -08001355 self._enforced_cpu = detect_host_arch.HostArch(),
maruel@chromium.org271375b2010-06-23 19:17:38 +00001356 self._root_dir = root_dir
John Budorickd3ba72b2018-03-20 12:27:42 -07001357 self._cipd_root = None
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001358 self.config_content = None
1359
borenet@google.com88d10082014-03-21 17:24:48 +00001360 def _CheckConfig(self):
1361 """Verify that the config matches the state of the existing checked-out
1362 solutions."""
1363 for dep in self.dependencies:
Michael Mossd683d7c2018-06-15 05:05:17 +00001364 if dep.managed and dep.url:
Edward Lemurbabd0982018-05-11 13:32:37 -04001365 scm = dep.CreateSCM()
smut@google.comd33eab32014-07-07 19:35:18 +00001366 actual_url = scm.GetActualRemoteURL(self._options)
borenet@google.com4e9be262014-04-08 19:40:30 +00001367 if actual_url and not scm.DoesRemoteURLMatch(self._options):
levarum@chromium.org27a6f9a2016-05-28 00:21:49 +00001368 mirror = scm.GetCacheMirror()
1369 if mirror:
1370 mirror_string = '%s (exists=%s)' % (mirror.mirror_path,
1371 mirror.exists())
1372 else:
1373 mirror_string = 'not used'
Raul Tambreb946b232019-03-26 14:48:46 +00001374 raise gclient_utils.Error(
1375 '''
borenet@google.com88d10082014-03-21 17:24:48 +00001376Your .gclient file seems to be broken. The requested URL is different from what
borenet@google.com0a427372014-04-02 19:12:13 +00001377is actually checked out in %(checkout_path)s.
borenet@google.com88d10082014-03-21 17:24:48 +00001378
borenet@google.com97882362014-04-07 20:06:02 +00001379The .gclient file contains:
levarum@chromium.org27a6f9a2016-05-28 00:21:49 +00001380URL: %(expected_url)s (%(expected_scm)s)
1381Cache mirror: %(mirror_string)s
borenet@google.com97882362014-04-07 20:06:02 +00001382
1383The local checkout in %(checkout_path)s reports:
1384%(actual_url)s (%(actual_scm)s)
borenet@google.com88d10082014-03-21 17:24:48 +00001385
1386You should ensure that the URL listed in .gclient is correct and either change
agabled437d762016-10-17 09:35:11 -07001387it or fix the checkout.
borenet@google.com88d10082014-03-21 17:24:48 +00001388''' % {'checkout_path': os.path.join(self.root_dir, dep.name),
1389 'expected_url': dep.url,
Edward Lemurbabd0982018-05-11 13:32:37 -04001390 'expected_scm': dep.GetScmName(),
Raul Tambred19589f2019-03-29 05:53:09 +00001391 'mirror_string': mirror_string,
borenet@google.com88d10082014-03-21 17:24:48 +00001392 'actual_url': actual_url,
Edward Lemurbabd0982018-05-11 13:32:37 -04001393 'actual_scm': dep.GetScmName()})
borenet@google.com88d10082014-03-21 17:24:48 +00001394
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001395 def SetConfig(self, content):
maruel@chromium.orgf13a4182011-09-22 00:26:15 +00001396 assert not self.dependencies
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001397 config_dict = {}
1398 self.config_content = content
1399 try:
1400 exec(content, config_dict)
vapier@chromium.orga81a56e2015-11-11 07:56:13 +00001401 except SyntaxError as e:
maruel@chromium.org5990f9d2010-07-07 18:02:58 +00001402 gclient_utils.SyntaxErrorToError('.gclient', e)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001403
peter@chromium.org1efccc82012-04-27 16:34:38 +00001404 # Append any target OS that is not already being enforced to the tuple.
1405 target_os = config_dict.get('target_os', [])
stuartmorgan@chromium.org18a4f6a2012-11-02 13:37:24 +00001406 if config_dict.get('target_os_only', False):
1407 self._enforced_os = tuple(set(target_os))
1408 else:
1409 self._enforced_os = tuple(set(self._enforced_os).union(target_os))
1410
Tom Andersonc31ae0b2018-02-06 14:48:56 -08001411 # Append any target CPU that is not already being enforced to the tuple.
1412 target_cpu = config_dict.get('target_cpu', [])
1413 if config_dict.get('target_cpu_only', False):
1414 self._enforced_cpu = tuple(set(target_cpu))
1415 else:
1416 self._enforced_cpu = tuple(set(self._enforced_cpu).union(target_cpu))
1417
Robert Iannuccia19649b2018-06-29 16:31:45 +00001418 cache_dir = config_dict.get('cache_dir', UNSET_CACHE_DIR)
1419 if cache_dir is not UNSET_CACHE_DIR:
1420 if cache_dir:
1421 cache_dir = os.path.join(self.root_dir, cache_dir)
1422 cache_dir = os.path.abspath(cache_dir)
Andrii Shyshkalov77ce4bd2017-11-27 12:38:18 -08001423
Robert Iannuccia19649b2018-06-29 16:31:45 +00001424 git_cache.Mirror.SetCachePath(cache_dir)
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001425
stuartmorgan@chromium.org18a4f6a2012-11-02 13:37:24 +00001426 if not target_os and config_dict.get('target_os_only', False):
1427 raise gclient_utils.Error('Can\'t use target_os_only if target_os is '
1428 'not specified')
peter@chromium.org1efccc82012-04-27 16:34:38 +00001429
Tom Andersonc31ae0b2018-02-06 14:48:56 -08001430 if not target_cpu and config_dict.get('target_cpu_only', False):
1431 raise gclient_utils.Error('Can\'t use target_cpu_only if target_cpu is '
1432 'not specified')
1433
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001434 deps_to_add = []
Michael Mossd683d7c2018-06-15 05:05:17 +00001435 for s in config_dict.get('solutions', []):
1436 try:
Michael Moss4e9b50a2018-05-23 22:35:06 -07001437 deps_to_add.append(GitDependency(
Edward Lemure05f18d2018-06-08 17:36:53 +00001438 parent=self,
1439 name=s['name'],
1440 url=s['url'],
1441 managed=s.get('managed', True),
1442 custom_deps=s.get('custom_deps', {}),
1443 custom_vars=s.get('custom_vars', {}),
1444 custom_hooks=s.get('custom_hooks', []),
1445 deps_file=s.get('deps_file', 'DEPS'),
Michael Mossd683d7c2018-06-15 05:05:17 +00001446 should_process=True,
Edward Lemurfbb06aa2018-06-11 20:43:06 +00001447 should_recurse=True,
Edward Lemure05f18d2018-06-08 17:36:53 +00001448 relative=None,
1449 condition=None,
1450 print_outbuf=True))
Michael Mossd683d7c2018-06-15 05:05:17 +00001451 except KeyError:
1452 raise gclient_utils.Error('Invalid .gclient file. Solution is '
1453 'incomplete: %s' % s)
Edward Lemur40764b02018-07-20 18:50:29 +00001454 metrics.collector.add(
1455 'project_urls',
1456 [
Edward Lemuraffd4102019-06-05 18:07:49 +00001457 dep.FuzzyMatchUrl(metrics_utils.KNOWN_PROJECT_URLS)
Edward Lemur40764b02018-07-20 18:50:29 +00001458 for dep in deps_to_add
1459 if dep.FuzzyMatchUrl(metrics_utils.KNOWN_PROJECT_URLS)
1460 ]
1461 )
1462
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001463 self.add_dependencies_and_close(deps_to_add, config_dict.get('hooks', []))
1464 logging.info('SetConfig() done')
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001465
1466 def SaveConfig(self):
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001467 gclient_utils.FileWrite(os.path.join(self.root_dir,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001468 self._options.config_filename),
1469 self.config_content)
1470
1471 @staticmethod
1472 def LoadCurrentConfig(options):
1473 """Searches for and loads a .gclient file relative to the current working
1474 dir. Returns a GClient object."""
szager@chromium.orge2e03202012-07-31 18:05:16 +00001475 if options.spec:
1476 client = GClient('.', options)
1477 client.SetConfig(options.spec)
1478 else:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00001479 if options.verbose:
1480 print('Looking for %s starting from %s\n' % (
1481 options.config_filename, os.getcwd()))
Nico Weber09e0b382019-03-11 16:54:07 +00001482 path = gclient_paths.FindGclientRoot(os.getcwd(), options.config_filename)
szager@chromium.orge2e03202012-07-31 18:05:16 +00001483 if not path:
Michael Achenbachb3ce73d2017-10-11 16:41:27 +02001484 if options.verbose:
1485 print('Couldn\'t find configuration file.')
szager@chromium.orge2e03202012-07-31 18:05:16 +00001486 return None
1487 client = GClient(path, options)
1488 client.SetConfig(gclient_utils.FileRead(
1489 os.path.join(path, options.config_filename)))
maruel@chromium.org69392e72011-10-13 22:09:00 +00001490
1491 if (options.revisions and
1492 len(client.dependencies) > 1 and
1493 any('@' not in r for r in options.revisions)):
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001494 print(
1495 ('You must specify the full solution name like --revision %s@%s\n'
1496 'when you have multiple solutions setup in your .gclient file.\n'
1497 'Other solutions present are: %s.') % (
maruel@chromium.org69392e72011-10-13 22:09:00 +00001498 client.dependencies[0].name,
1499 options.revisions[0],
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001500 ', '.join(s.name for s in client.dependencies[1:])),
1501 file=sys.stderr)
maruel@chromium.org15804092010-09-02 17:07:37 +00001502 return client
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001503
nsylvain@google.comefc80932011-05-31 21:27:56 +00001504 def SetDefaultConfig(self, solution_name, deps_file, solution_url,
Robert Iannuccia19649b2018-06-29 16:31:45 +00001505 managed=True, cache_dir=UNSET_CACHE_DIR,
1506 custom_vars=None):
1507 text = self.DEFAULT_CLIENT_FILE_TEXT
1508 format_dict = {
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001509 'solution_name': solution_name,
1510 'solution_url': solution_url,
nsylvain@google.comefc80932011-05-31 21:27:56 +00001511 'deps_file': deps_file,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001512 'managed': managed,
Paweł Hajdan, Jr3ba2a7c2017-10-04 19:24:46 +02001513 'custom_vars': custom_vars or {},
Robert Iannuccia19649b2018-06-29 16:31:45 +00001514 }
1515
1516 if cache_dir is not UNSET_CACHE_DIR:
1517 text += self.DEFAULT_CLIENT_CACHE_DIR_TEXT
1518 format_dict['cache_dir'] = cache_dir
1519
1520 self.SetConfig(text % format_dict)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001521
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001522 def _SaveEntries(self):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001523 """Creates a .gclient_entries file to record the list of unique checkouts.
1524
1525 The .gclient_entries file lives in the same directory as .gclient.
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001526 """
1527 # Sometimes pprint.pformat will use {', sometimes it'll use { ' ... It
1528 # makes testing a bit too fun.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001529 result = 'entries = {\n'
Michael Mossd683d7c2018-06-15 05:05:17 +00001530 for entry in self.root.subtree(False):
agabled437d762016-10-17 09:35:11 -07001531 result += ' %s: %s,\n' % (pprint.pformat(entry.name),
Edward Lemure7273d22018-05-10 19:13:51 -04001532 pprint.pformat(entry.url))
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001533 result += '}\n'
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001534 file_path = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org1333cb32011-10-04 23:40:16 +00001535 logging.debug(result)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001536 gclient_utils.FileWrite(file_path, result)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001537
1538 def _ReadEntries(self):
1539 """Read the .gclient_entries file for the given client.
1540
1541 Returns:
1542 A sequence of solution names, which will be empty if there is the
1543 entries file hasn't been created yet.
1544 """
1545 scope = {}
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001546 filename = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001547 if not os.path.exists(filename):
maruel@chromium.org73e21142010-07-05 13:32:01 +00001548 return {}
maruel@chromium.org5990f9d2010-07-07 18:02:58 +00001549 try:
1550 exec(gclient_utils.FileRead(filename), scope)
vapier@chromium.orga81a56e2015-11-11 07:56:13 +00001551 except SyntaxError as e:
maruel@chromium.org5990f9d2010-07-07 18:02:58 +00001552 gclient_utils.SyntaxErrorToError(filename, e)
Aaron Gable3721ee92017-04-03 14:53:14 -07001553 return scope.get('entries', {})
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001554
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001555 def _EnforceRevisions(self):
maruel@chromium.org918a9ae2010-05-28 15:50:30 +00001556 """Checks for revision overrides."""
1557 revision_overrides = {}
smutae7ea312016-07-18 11:59:41 -07001558 if self._options.head:
1559 return revision_overrides
joi@chromium.org792ea882010-11-10 02:37:27 +00001560 if not self._options.revisions:
maruel@chromium.org307d1792010-05-31 20:03:13 +00001561 return revision_overrides
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001562 solutions_names = [s.name for s in self.dependencies]
smutae7ea312016-07-18 11:59:41 -07001563 index = 0
1564 for revision in self._options.revisions:
1565 if not '@' in revision:
maruel@chromium.org307d1792010-05-31 20:03:13 +00001566 # Support for --revision 123
smutae7ea312016-07-18 11:59:41 -07001567 revision = '%s@%s' % (solutions_names[index], revision)
1568 name, rev = revision.split('@', 1)
szager@chromium.org4ad264b2014-05-20 04:43:47 +00001569 revision_overrides[name] = rev
smutae7ea312016-07-18 11:59:41 -07001570 index += 1
maruel@chromium.org918a9ae2010-05-28 15:50:30 +00001571 return revision_overrides
1572
Edward Lemur6a4e31b2018-08-10 19:59:02 +00001573 def _EnforcePatchRefsAndBranches(self):
Edward Lesmesc621b212018-03-21 20:26:56 -04001574 """Checks for patch refs."""
1575 patch_refs = {}
Edward Lemur6a4e31b2018-08-10 19:59:02 +00001576 target_branches = {}
Edward Lesmesc621b212018-03-21 20:26:56 -04001577 if not self._options.patch_refs:
Edward Lemur6a4e31b2018-08-10 19:59:02 +00001578 return patch_refs, target_branches
Edward Lesmesc621b212018-03-21 20:26:56 -04001579 for given_patch_ref in self._options.patch_refs:
1580 patch_repo, _, patch_ref = given_patch_ref.partition('@')
Edward Lemur4c5c8ab2019-06-07 15:58:13 +00001581 if not patch_repo or not patch_ref or ':' not in patch_ref:
Edward Lesmesc621b212018-03-21 20:26:56 -04001582 raise gclient_utils.Error(
1583 'Wrong revision format: %s should be of the form '
Edward Lemur4c5c8ab2019-06-07 15:58:13 +00001584 'patch_repo@target_branch:patch_ref.' % given_patch_ref)
1585 target_branch, _, patch_ref = patch_ref.partition(':')
1586 target_branches[patch_repo] = target_branch
Edward Lesmesc621b212018-03-21 20:26:56 -04001587 patch_refs[patch_repo] = patch_ref
Edward Lemur6a4e31b2018-08-10 19:59:02 +00001588 return patch_refs, target_branches
Edward Lesmesc621b212018-03-21 20:26:56 -04001589
Edward Lemur5b1fa942018-10-04 23:22:09 +00001590 def _RemoveUnversionedGitDirs(self):
1591 """Remove directories that are no longer part of the checkout.
1592
1593 Notify the user if there is an orphaned entry in their working copy.
1594 Only delete the directory if there are no changes in it, and
1595 delete_unversioned_trees is set to true.
1596 """
1597
1598 entries = [i.name for i in self.root.subtree(False) if i.url]
1599 full_entries = [os.path.join(self.root_dir, e.replace('/', os.path.sep))
1600 for e in entries]
1601
Edward Lemuree7b9dd2019-07-20 01:29:08 +00001602 for entry, prev_url in six.iteritems(self._ReadEntries()):
Edward Lemur5b1fa942018-10-04 23:22:09 +00001603 if not prev_url:
1604 # entry must have been overridden via .gclient custom_deps
1605 continue
1606 # Fix path separator on Windows.
1607 entry_fixed = entry.replace('/', os.path.sep)
1608 e_dir = os.path.join(self.root_dir, entry_fixed)
1609 # Use entry and not entry_fixed there.
1610 if (entry not in entries and
1611 (not any(path.startswith(entry + '/') for path in entries)) and
1612 os.path.exists(e_dir)):
1613 # The entry has been removed from DEPS.
1614 scm = gclient_scm.GitWrapper(
1615 prev_url, self.root_dir, entry_fixed, self.outbuf)
1616
1617 # Check to see if this directory is now part of a higher-up checkout.
1618 scm_root = None
1619 try:
1620 scm_root = gclient_scm.scm.GIT.GetCheckoutRoot(scm.checkout_path)
1621 except subprocess2.CalledProcessError:
1622 pass
1623 if not scm_root:
1624 logging.warning('Could not find checkout root for %s. Unable to '
1625 'determine whether it is part of a higher-level '
1626 'checkout, so not removing.' % entry)
1627 continue
1628
1629 # This is to handle the case of third_party/WebKit migrating from
1630 # being a DEPS entry to being part of the main project.
1631 # If the subproject is a Git project, we need to remove its .git
1632 # folder. Otherwise git operations on that folder will have different
1633 # effects depending on the current working directory.
1634 if os.path.abspath(scm_root) == os.path.abspath(e_dir):
1635 e_par_dir = os.path.join(e_dir, os.pardir)
1636 if gclient_scm.scm.GIT.IsInsideWorkTree(e_par_dir):
1637 par_scm_root = gclient_scm.scm.GIT.GetCheckoutRoot(e_par_dir)
1638 # rel_e_dir : relative path of entry w.r.t. its parent repo.
1639 rel_e_dir = os.path.relpath(e_dir, par_scm_root)
1640 if gclient_scm.scm.GIT.IsDirectoryVersioned(
1641 par_scm_root, rel_e_dir):
1642 save_dir = scm.GetGitBackupDirPath()
1643 # Remove any eventual stale backup dir for the same project.
1644 if os.path.exists(save_dir):
1645 gclient_utils.rmtree(save_dir)
1646 os.rename(os.path.join(e_dir, '.git'), save_dir)
1647 # When switching between the two states (entry/ is a subproject
1648 # -> entry/ is part of the outer project), it is very likely
1649 # that some files are changed in the checkout, unless we are
1650 # jumping *exactly* across the commit which changed just DEPS.
1651 # In such case we want to cleanup any eventual stale files
1652 # (coming from the old subproject) in order to end up with a
1653 # clean checkout.
1654 gclient_scm.scm.GIT.CleanupDir(par_scm_root, rel_e_dir)
1655 assert not os.path.exists(os.path.join(e_dir, '.git'))
Raul Tambre80ee78e2019-05-06 22:41:05 +00001656 print('\nWARNING: \'%s\' has been moved from DEPS to a higher '
1657 'level checkout. The git folder containing all the local'
1658 ' branches has been saved to %s.\n'
1659 'If you don\'t care about its state you can safely '
1660 'remove that folder to free up space.' % (entry, save_dir))
Edward Lemur5b1fa942018-10-04 23:22:09 +00001661 continue
1662
1663 if scm_root in full_entries:
1664 logging.info('%s is part of a higher level checkout, not removing',
1665 scm.GetCheckoutRoot())
1666 continue
1667
1668 file_list = []
1669 scm.status(self._options, [], file_list)
1670 modified_files = file_list != []
1671 if (not self._options.delete_unversioned_trees or
1672 (modified_files and not self._options.force)):
1673 # There are modified files in this entry. Keep warning until
1674 # removed.
Henrique Ferreiroe72279d2019-04-17 12:01:50 +00001675 self.add_dependency(
1676 GitDependency(
1677 parent=self,
1678 name=entry,
1679 url=prev_url,
1680 managed=False,
1681 custom_deps={},
1682 custom_vars={},
1683 custom_hooks=[],
1684 deps_file=None,
1685 should_process=True,
1686 should_recurse=False,
1687 relative=None,
1688 condition=None))
Raul Tambre80ee78e2019-05-06 22:41:05 +00001689 print('\nWARNING: \'%s\' is no longer part of this client.\n'
1690 'It is recommended that you manually remove it or use '
1691 '\'gclient sync -D\' next time.' % entry_fixed)
Edward Lemur5b1fa942018-10-04 23:22:09 +00001692 else:
1693 # Delete the entry
1694 print('\n________ deleting \'%s\' in \'%s\'' % (
1695 entry_fixed, self.root_dir))
1696 gclient_utils.rmtree(e_dir)
1697 # record the current list of entries for next time
1698 self._SaveEntries()
1699
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001700 def RunOnDeps(self, command, args, ignore_requirements=False, progress=True):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001701 """Runs a command on each dependency in a client and its dependencies.
1702
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001703 Args:
1704 command: The command to use (e.g., 'status' or 'diff')
1705 args: list of str - extra arguments to add to the command line.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001706 """
Michael Mossd683d7c2018-06-15 05:05:17 +00001707 if not self.dependencies:
1708 raise gclient_utils.Error('No solution specified')
1709
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001710 revision_overrides = {}
Edward Lesmesc621b212018-03-21 20:26:56 -04001711 patch_refs = {}
Edward Lemur6a4e31b2018-08-10 19:59:02 +00001712 target_branches = {}
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001713 # It's unnecessary to check for revision overrides for 'recurse'.
1714 # Save a few seconds by not calling _EnforceRevisions() in that case.
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +02001715 if command not in ('diff', 'recurse', 'runhooks', 'status', 'revert',
1716 'validate'):
szager@chromium.org5273b8a2014-08-21 15:10:10 +00001717 self._CheckConfig()
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001718 revision_overrides = self._EnforceRevisions()
Edward Lesmesc621b212018-03-21 20:26:56 -04001719
1720 if command == 'update':
Edward Lemur6a4e31b2018-08-10 19:59:02 +00001721 patch_refs, target_branches = self._EnforcePatchRefsAndBranches()
Daniel Chenga21b5b32017-10-19 20:07:48 +00001722 # Disable progress for non-tty stdout.
Daniel Chenga0c5f082017-10-19 13:35:19 -07001723 should_show_progress = (
1724 setup_color.IS_TTY and not self._options.verbose and progress)
1725 pm = None
1726 if should_show_progress:
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001727 if command in ('update', 'revert'):
1728 pm = Progress('Syncing projects', 1)
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +02001729 elif command in ('recurse', 'validate'):
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001730 pm = Progress(' '.join(args), 1)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001731 work_queue = gclient_utils.ExecutionQueue(
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001732 self._options.jobs, pm, ignore_requirements=ignore_requirements,
1733 verbose=self._options.verbose)
maruel@chromium.org049bced2010-08-12 13:37:20 +00001734 for s in self.dependencies:
Michael Mossd683d7c2018-06-15 05:05:17 +00001735 if s.should_process:
1736 work_queue.enqueue(s)
Edward Lesmesc621b212018-03-21 20:26:56 -04001737 work_queue.flush(revision_overrides, command, args, options=self._options,
Edward Lemur6a4e31b2018-08-10 19:59:02 +00001738 patch_refs=patch_refs, target_branches=target_branches)
Edward Lesmesc621b212018-03-21 20:26:56 -04001739
szager@chromium.org4ad264b2014-05-20 04:43:47 +00001740 if revision_overrides:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001741 print('Please fix your script, having invalid --revision flags will soon '
Edward Lesmesc621b212018-03-21 20:26:56 -04001742 'be considered an error.', file=sys.stderr)
1743
1744 if patch_refs:
1745 raise gclient_utils.Error(
1746 'The following --patch-ref flags were not used. Please fix it:\n%s' %
1747 ('\n'.join(
1748 patch_repo + '@' + patch_ref
Edward Lemuree7b9dd2019-07-20 01:29:08 +00001749 for patch_repo, patch_ref in six.iteritems(patch_refs))))
piman@chromium.org6f363722010-04-27 00:41:09 +00001750
Dirk Pranke9f20d022017-10-11 18:36:54 -07001751 # Once all the dependencies have been processed, it's now safe to write
Michael Moss848c86e2018-05-03 16:05:50 -07001752 # out the gn_args_file and run the hooks.
Dirk Pranke9f20d022017-10-11 18:36:54 -07001753 if command == 'update':
Michael Moss848c86e2018-05-03 16:05:50 -07001754 gn_args_dep = self.dependencies[0]
1755 if gn_args_dep._gn_args_from:
1756 deps_map = dict([(dep.name, dep) for dep in gn_args_dep.dependencies])
1757 gn_args_dep = deps_map.get(gn_args_dep._gn_args_from)
1758 if gn_args_dep and gn_args_dep.HasGNArgsFile():
1759 gn_args_dep.WriteGNArgsFile()
Dirk Pranke9f20d022017-10-11 18:36:54 -07001760
Edward Lemur5b1fa942018-10-04 23:22:09 +00001761 self._RemoveUnversionedGitDirs()
Edward Lemur647e1e72018-09-19 18:15:29 +00001762
1763 # Sync CIPD dependencies once removed deps are deleted. In case a git
1764 # dependency was moved to CIPD, we want to remove the old git directory
1765 # first and then sync the CIPD dep.
1766 if self._cipd_root:
1767 self._cipd_root.run(command)
1768
Edward Lemur5b1fa942018-10-04 23:22:09 +00001769 if not self._options.nohooks:
1770 if should_show_progress:
1771 pm = Progress('Running hooks', 1)
1772 self.RunHooksRecursively(self._options, pm)
1773
1774
maruel@chromium.org17cdf762010-05-28 17:30:52 +00001775 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001776
1777 def PrintRevInfo(self):
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001778 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +00001779 raise gclient_utils.Error('No solution specified')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001780 # Load all the settings.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001781 work_queue = gclient_utils.ExecutionQueue(
1782 self._options.jobs, None, False, verbose=self._options.verbose)
maruel@chromium.org049bced2010-08-12 13:37:20 +00001783 for s in self.dependencies:
Michael Mossd683d7c2018-06-15 05:05:17 +00001784 if s.should_process:
1785 work_queue.enqueue(s)
Edward Lemur6a4e31b2018-08-10 19:59:02 +00001786 work_queue.flush({}, None, [], options=self._options, patch_refs=None,
1787 target_branches=None)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001788
Michael Mossd683d7c2018-06-15 05:05:17 +00001789 def ShouldPrintRevision(dep):
Edward Lesmesbb16e332018-03-30 17:54:51 -04001790 return (not self._options.filter
Edward Lemure7273d22018-05-10 19:13:51 -04001791 or dep.FuzzyMatchUrl(self._options.filter))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001792
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001793 if self._options.snapshot:
Michael Mossd683d7c2018-06-15 05:05:17 +00001794 json_output = []
1795 # First level at .gclient
1796 for d in self.dependencies:
1797 entries = {}
1798 def GrabDeps(dep):
1799 """Recursively grab dependencies."""
1800 for d in dep.dependencies:
1801 d.PinToActualRevision()
1802 if ShouldPrintRevision(d):
1803 entries[d.name] = d.url
1804 GrabDeps(d)
1805 GrabDeps(d)
1806 json_output.append({
1807 'name': d.name,
1808 'solution_url': d.url,
1809 'deps_file': d.deps_file,
1810 'managed': d.managed,
1811 'custom_deps': entries,
1812 })
1813 if self._options.output_json == '-':
1814 print(json.dumps(json_output, indent=2, separators=(',', ': ')))
1815 elif self._options.output_json:
1816 with open(self._options.output_json, 'w') as f:
1817 json.dump(json_output, f)
1818 else:
1819 # Print the snapshot configuration file
1820 print(self.DEFAULT_SNAPSHOT_FILE_TEXT % {
1821 'solution_list': pprint.pformat(json_output, indent=2),
1822 })
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +00001823 else:
Michael Mossd683d7c2018-06-15 05:05:17 +00001824 entries = {}
1825 for d in self.root.subtree(False):
1826 if self._options.actual:
1827 d.PinToActualRevision()
1828 if ShouldPrintRevision(d):
1829 entries[d.name] = d.url
1830 if self._options.output_json:
1831 json_output = {
1832 name: {
1833 'url': rev.split('@')[0] if rev else None,
1834 'rev': rev.split('@')[1] if rev and '@' in rev else None,
1835 }
Edward Lemuree7b9dd2019-07-20 01:29:08 +00001836 for name, rev in six.iteritems(entries)
Michael Mossd683d7c2018-06-15 05:05:17 +00001837 }
1838 if self._options.output_json == '-':
1839 print(json.dumps(json_output, indent=2, separators=(',', ': ')))
1840 else:
1841 with open(self._options.output_json, 'w') as f:
1842 json.dump(json_output, f)
1843 else:
1844 keys = sorted(entries.keys())
1845 for x in keys:
1846 print('%s: %s' % (x, entries[x]))
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +00001847 logging.info(str(self))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001848
Edward Lemure05f18d2018-06-08 17:36:53 +00001849 def ParseDepsFile(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001850 """No DEPS to parse for a .gclient file."""
maruel@chromium.org049bced2010-08-12 13:37:20 +00001851 raise gclient_utils.Error('Internal error')
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001852
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00001853 def PrintLocationAndContents(self):
1854 # Print out the .gclient file. This is longer than if we just printed the
1855 # client dict, but more legible, and it might contain helpful comments.
1856 print('Loaded .gclient config in %s:\n%s' % (
1857 self.root_dir, self.config_content))
1858
John Budorickd3ba72b2018-03-20 12:27:42 -07001859 def GetCipdRoot(self):
1860 if not self._cipd_root:
1861 self._cipd_root = gclient_scm.CipdRoot(
1862 self.root_dir,
1863 # TODO(jbudorick): Support other service URLs as necessary.
1864 # Service URLs should be constant over the scope of a cipd
1865 # root, so a var per DEPS file specifying the service URL
1866 # should suffice.
1867 'https://chrome-infra-packages.appspot.com')
1868 return self._cipd_root
1869
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001870 @property
maruel@chromium.org75a59272010-06-11 22:34:03 +00001871 def root_dir(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001872 """Root directory of gclient checkout."""
maruel@chromium.org75a59272010-06-11 22:34:03 +00001873 return self._root_dir
1874
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001875 @property
maruel@chromium.org271375b2010-06-23 19:17:38 +00001876 def enforced_os(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001877 """What deps_os entries that are to be parsed."""
maruel@chromium.org271375b2010-06-23 19:17:38 +00001878 return self._enforced_os
1879
maruel@chromium.org68988972011-09-20 14:11:42 +00001880 @property
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +00001881 def target_os(self):
1882 return self._enforced_os
1883
Tom Andersonc31ae0b2018-02-06 14:48:56 -08001884 @property
1885 def target_cpu(self):
1886 return self._enforced_cpu
1887
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001888
John Budorick0f7b2002018-01-19 15:46:17 -08001889class CipdDependency(Dependency):
1890 """A Dependency object that represents a single CIPD package."""
1891
Michael Mossd683d7c2018-06-15 05:05:17 +00001892 def __init__(
1893 self, parent, name, dep_value, cipd_root,
1894 custom_vars, should_process, relative, condition):
John Budorick0f7b2002018-01-19 15:46:17 -08001895 package = dep_value['package']
1896 version = dep_value['version']
1897 url = urlparse.urljoin(
1898 cipd_root.service_url, '%s@%s' % (package, version))
1899 super(CipdDependency, self).__init__(
Edward Lemure05f18d2018-06-08 17:36:53 +00001900 parent=parent,
1901 name=name + ':' + package,
1902 url=url,
1903 managed=None,
1904 custom_deps=None,
1905 custom_vars=custom_vars,
1906 custom_hooks=None,
1907 deps_file=None,
Michael Mossd683d7c2018-06-15 05:05:17 +00001908 should_process=should_process,
Edward Lemurfbb06aa2018-06-11 20:43:06 +00001909 should_recurse=False,
Edward Lemure05f18d2018-06-08 17:36:53 +00001910 relative=relative,
1911 condition=condition)
John Budorickd3ba72b2018-03-20 12:27:42 -07001912 self._cipd_package = None
John Budorick0f7b2002018-01-19 15:46:17 -08001913 self._cipd_root = cipd_root
John Budorick4099daa2018-06-21 19:22:10 +00001914 # CIPD wants /-separated paths, even on Windows.
1915 native_subdir_path = os.path.relpath(
Shenghua Zhang6f830312018-02-26 11:45:07 -08001916 os.path.join(self.root.root_dir, name), cipd_root.root_dir)
John Budorick4099daa2018-06-21 19:22:10 +00001917 self._cipd_subdir = posixpath.join(*native_subdir_path.split(os.sep))
John Budorickd3ba72b2018-03-20 12:27:42 -07001918 self._package_name = package
1919 self._package_version = version
1920
1921 #override
Edward Lesmesc621b212018-03-21 20:26:56 -04001922 def run(self, revision_overrides, command, args, work_queue, options,
Edward Lemur6a4e31b2018-08-10 19:59:02 +00001923 patch_refs, target_branches):
John Budorickd3ba72b2018-03-20 12:27:42 -07001924 """Runs |command| then parse the DEPS file."""
1925 logging.info('CipdDependency(%s).run()' % self.name)
Michael Mossd683d7c2018-06-15 05:05:17 +00001926 if not self.should_process:
1927 return
John Budorickd3ba72b2018-03-20 12:27:42 -07001928 self._CreatePackageIfNecessary()
1929 super(CipdDependency, self).run(revision_overrides, command, args,
Edward Lemur6a4e31b2018-08-10 19:59:02 +00001930 work_queue, options, patch_refs,
1931 target_branches)
John Budorickd3ba72b2018-03-20 12:27:42 -07001932
1933 def _CreatePackageIfNecessary(self):
1934 # We lazily create the CIPD package to make sure that only packages
1935 # that we want (as opposed to all packages defined in all DEPS files
1936 # we parse) get added to the root and subsequently ensured.
1937 if not self._cipd_package:
1938 self._cipd_package = self._cipd_root.add_package(
1939 self._cipd_subdir, self._package_name, self._package_version)
John Budorick0f7b2002018-01-19 15:46:17 -08001940
Edward Lemure05f18d2018-06-08 17:36:53 +00001941 def ParseDepsFile(self):
John Budorick0f7b2002018-01-19 15:46:17 -08001942 """CIPD dependencies are not currently allowed to have nested deps."""
1943 self.add_dependencies_and_close([], [])
1944
1945 #override
Shenghua Zhang6f830312018-02-26 11:45:07 -08001946 def verify_validity(self):
1947 """CIPD dependencies allow duplicate name for packages in same directory."""
1948 logging.info('Dependency(%s).verify_validity()' % self.name)
1949 return True
1950
1951 #override
Edward Lemurbabd0982018-05-11 13:32:37 -04001952 def GetScmName(self):
John Budorick0f7b2002018-01-19 15:46:17 -08001953 """Always 'cipd'."""
John Budorick0f7b2002018-01-19 15:46:17 -08001954 return 'cipd'
1955
1956 #override
Edward Lemurbabd0982018-05-11 13:32:37 -04001957 def CreateSCM(self, out_cb=None):
John Budorick0f7b2002018-01-19 15:46:17 -08001958 """Create a Wrapper instance suitable for handling this CIPD dependency."""
John Budorickd3ba72b2018-03-20 12:27:42 -07001959 self._CreatePackageIfNecessary()
John Budorick0f7b2002018-01-19 15:46:17 -08001960 return gclient_scm.CipdWrapper(
Edward Lemurbabd0982018-05-11 13:32:37 -04001961 self.url, self.root.root_dir, self.name, self.outbuf, out_cb,
1962 root=self._cipd_root, package=self._cipd_package)
John Budorick0f7b2002018-01-19 15:46:17 -08001963
Edward Lemure4e15042018-06-28 18:07:00 +00001964 def hierarchy(self, include_url=False):
1965 return self.parent.hierarchy(include_url) + ' -> ' + self._cipd_subdir
1966
John Budorick0f7b2002018-01-19 15:46:17 -08001967 def ToLines(self):
1968 """Return a list of lines representing this in a DEPS file."""
John Budorickc35aba52018-06-28 20:57:03 +00001969 def escape_cipd_var(package):
1970 return package.replace('{', '{{').replace('}', '}}')
1971
John Budorick0f7b2002018-01-19 15:46:17 -08001972 s = []
John Budorickd3ba72b2018-03-20 12:27:42 -07001973 self._CreatePackageIfNecessary()
John Budorick0f7b2002018-01-19 15:46:17 -08001974 if self._cipd_package.authority_for_subdir:
1975 condition_part = ([' "condition": %r,' % self.condition]
1976 if self.condition else [])
1977 s.extend([
Michael Moss4e9b50a2018-05-23 22:35:06 -07001978 ' # %s' % self.hierarchy(include_url=False),
John Budorickd3ba72b2018-03-20 12:27:42 -07001979 ' "%s": {' % (self.name.split(':')[0],),
John Budorick0f7b2002018-01-19 15:46:17 -08001980 ' "packages": [',
1981 ])
John Budorick4099daa2018-06-21 19:22:10 +00001982 for p in sorted(
1983 self._cipd_root.packages(self._cipd_subdir),
Edward Lemur26a8b9f2019-08-15 20:46:44 +00001984 key=lambda x: x.name):
John Budorick0f7b2002018-01-19 15:46:17 -08001985 s.extend([
John Budorick64e33cb2018-02-20 09:40:30 -08001986 ' {',
John Budorickc35aba52018-06-28 20:57:03 +00001987 ' "package": "%s",' % escape_cipd_var(p.name),
John Budorick64e33cb2018-02-20 09:40:30 -08001988 ' "version": "%s",' % p.version,
1989 ' },',
John Budorick0f7b2002018-01-19 15:46:17 -08001990 ])
John Budorickd3ba72b2018-03-20 12:27:42 -07001991
John Budorick0f7b2002018-01-19 15:46:17 -08001992 s.extend([
1993 ' ],',
1994 ' "dep_type": "cipd",',
1995 ] + condition_part + [
1996 ' },',
1997 '',
1998 ])
1999 return s
2000
2001
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002002#### gclient commands.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002003
2004
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002005@subcommand.usage('[command] [args ...]')
Edward Lemur3298e7b2018-07-17 18:21:27 +00002006@metrics.collector.collect_metrics('gclient recurse')
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00002007def CMDrecurse(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002008 """Operates [command args ...] on all the dependencies.
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00002009
2010 Runs a shell command on all entries.
qyearsley12fa6ff2016-08-24 09:18:40 -07002011 Sets GCLIENT_DEP_PATH environment variable as the dep's relative location to
ilevy@chromium.org37116242012-11-28 01:32:48 +00002012 root directory of the checkout.
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00002013 """
2014 # Stop parsing at the first non-arg so that these go through to the command
2015 parser.disable_interspersed_args()
2016 parser.add_option('-s', '--scm', action='append', default=[],
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00002017 help='Choose scm types to operate upon.')
maruel@chromium.org288054d2012-03-05 00:43:07 +00002018 parser.add_option('-i', '--ignore', action='store_true',
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00002019 help='Ignore non-zero return codes from subcommands.')
2020 parser.add_option('--prepend-dir', action='store_true',
2021 help='Prepend relative dir for use with git <cmd> --null.')
2022 parser.add_option('--no-progress', action='store_true',
2023 help='Disable progress bar that shows sub-command updates')
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00002024 options, args = parser.parse_args(args)
maruel@chromium.org45e9f2d2010-10-18 13:33:46 +00002025 if not args:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002026 print('Need to supply a command!', file=sys.stderr)
maruel@chromium.org45e9f2d2010-10-18 13:33:46 +00002027 return 1
maruel@chromium.org78cba522010-10-18 13:32:05 +00002028 root_and_entries = gclient_utils.GetGClientRootAndEntries()
2029 if not root_and_entries:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002030 print(
maruel@chromium.org78cba522010-10-18 13:32:05 +00002031 'You need to run gclient sync at least once to use \'recurse\'.\n'
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002032 'This is because .gclient_entries needs to exist and be up to date.',
2033 file=sys.stderr)
maruel@chromium.org78cba522010-10-18 13:32:05 +00002034 return 1
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00002035
2036 # Normalize options.scm to a set()
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00002037 scm_set = set()
2038 for scm in options.scm:
2039 scm_set.update(scm.split(','))
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00002040 options.scm = scm_set
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00002041
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00002042 options.nohooks = True
2043 client = GClient.LoadCurrentConfig(options)
Marc-Antoine Ruele6e06412017-10-18 13:47:02 -04002044 if not client:
2045 raise gclient_utils.Error('client not configured; see \'gclient config\'')
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00002046 return client.RunOnDeps('recurse', args, ignore_requirements=True,
2047 progress=not options.no_progress)
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00002048
2049
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002050@subcommand.usage('[args ...]')
Edward Lemur3298e7b2018-07-17 18:21:27 +00002051@metrics.collector.collect_metrics('gclient fetch')
davidbarr@chromium.org12f944e2012-03-01 02:18:31 +00002052def CMDfetch(parser, args):
2053 """Fetches upstream commits for all modules.
2054
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002055 Completely git-specific. Simply runs 'git fetch [args ...]' for each module.
2056 """
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00002057 (options, args) = parser.parse_args(args)
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002058 return CMDrecurse(OptionParser(), [
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00002059 '--jobs=%d' % options.jobs, '--scm=git', 'git', 'fetch'] + args)
2060
2061
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002062class Flattener(object):
2063 """Flattens a gclient solution."""
2064
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02002065 def __init__(self, client, pin_all_deps=False):
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002066 """Constructor.
2067
2068 Arguments:
2069 client (GClient): client to flatten
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02002070 pin_all_deps (bool): whether to pin all deps, even if they're not pinned
2071 in DEPS
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002072 """
2073 self._client = client
2074
2075 self._deps_string = None
Paweł Hajdan, Jrfeb01642017-09-12 15:50:46 +02002076 self._deps_files = set()
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002077
2078 self._allowed_hosts = set()
2079 self._deps = {}
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002080 self._hooks = []
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002081 self._pre_deps_hooks = []
Paweł Hajdan, Jrfb022012017-07-06 18:00:08 +02002082 self._vars = {}
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002083
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02002084 self._flatten(pin_all_deps=pin_all_deps)
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002085
2086 @property
2087 def deps_string(self):
2088 assert self._deps_string is not None
2089 return self._deps_string
2090
Paweł Hajdan, Jrfeb01642017-09-12 15:50:46 +02002091 @property
2092 def deps_files(self):
2093 return self._deps_files
2094
Paweł Hajdan, Jr5ec77132017-08-16 19:21:06 +02002095 def _pin_dep(self, dep):
2096 """Pins a dependency to specific full revision sha.
2097
2098 Arguments:
2099 dep (Dependency): dependency to process
2100 """
Michael Mossd683d7c2018-06-15 05:05:17 +00002101 if dep.url is None:
2102 return
2103
Paweł Hajdan, Jr5ec77132017-08-16 19:21:06 +02002104 # Make sure the revision is always fully specified (a hash),
2105 # as opposed to refs or tags which might change. Similarly,
2106 # shortened shas might become ambiguous; make sure to always
2107 # use full one for pinning.
Edward Lemure7273d22018-05-10 19:13:51 -04002108 revision = gclient_utils.SplitUrlRevision(dep.url)[1]
2109 if not revision or not gclient_utils.IsFullGitSha(revision):
2110 dep.PinToActualRevision()
Paweł Hajdan, Jr5ec77132017-08-16 19:21:06 +02002111
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02002112 def _flatten(self, pin_all_deps=False):
2113 """Runs the flattener. Saves resulting DEPS string.
2114
2115 Arguments:
2116 pin_all_deps (bool): whether to pin all deps, even if they're not pinned
2117 in DEPS
2118 """
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002119 for solution in self._client.dependencies:
Paweł Hajdan, Jr11eb7152017-08-10 12:50:11 +02002120 self._add_dep(solution)
Michael Mossd683d7c2018-06-15 05:05:17 +00002121 self._flatten_dep(solution)
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002122
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02002123 if pin_all_deps:
Edward Lemuree7b9dd2019-07-20 01:29:08 +00002124 for dep in six.itervalues(self._deps):
Paweł Hajdan, Jr5ec77132017-08-16 19:21:06 +02002125 self._pin_dep(dep)
Paweł Hajdan, Jr39300ba2017-08-11 16:52:38 +02002126
Paweł Hajdan, Jr76a9d042017-08-18 20:05:41 +02002127 def add_deps_file(dep):
Paweł Hajdan, Jr0870df22017-08-23 17:59:29 +02002128 # Only include DEPS files referenced by recursedeps.
Edward Lemurfbb06aa2018-06-11 20:43:06 +00002129 if not dep.should_recurse:
Paweł Hajdan, Jr0870df22017-08-23 17:59:29 +02002130 return
Paweł Hajdan, Jrfeb01642017-09-12 15:50:46 +02002131 deps_file = dep.deps_file
2132 deps_path = os.path.join(self._client.root_dir, dep.name, deps_file)
Paweł Hajdan, Jr76a9d042017-08-18 20:05:41 +02002133 if not os.path.exists(deps_path):
Paweł Hajdan, Jrfeb01642017-09-12 15:50:46 +02002134 # gclient has a fallback that if deps_file doesn't exist, it'll try
2135 # DEPS. Do the same here.
2136 deps_file = 'DEPS'
2137 deps_path = os.path.join(self._client.root_dir, dep.name, deps_file)
2138 if not os.path.exists(deps_path):
2139 return
Michael Mossd683d7c2018-06-15 05:05:17 +00002140 assert dep.url
Edward Lemure7273d22018-05-10 19:13:51 -04002141 self._deps_files.add((dep.url, deps_file, dep.hierarchy_data()))
Edward Lemuree7b9dd2019-07-20 01:29:08 +00002142 for dep in six.itervalues(self._deps):
Paweł Hajdan, Jr76a9d042017-08-18 20:05:41 +02002143 add_deps_file(dep)
Paweł Hajdan, Jr76a9d042017-08-18 20:05:41 +02002144
Michael Moss848c86e2018-05-03 16:05:50 -07002145 gn_args_dep = self._deps.get(self._client.dependencies[0]._gn_args_from,
2146 self._client.dependencies[0])
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002147 self._deps_string = '\n'.join(
Michael Moss848c86e2018-05-03 16:05:50 -07002148 _GNSettingsToLines(gn_args_dep._gn_args_file, gn_args_dep._gn_args) +
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002149 _AllowedHostsToLines(self._allowed_hosts) +
2150 _DepsToLines(self._deps) +
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002151 _HooksToLines('hooks', self._hooks) +
2152 _HooksToLines('pre_deps_hooks', self._pre_deps_hooks) +
Paweł Hajdan, Jrfb022012017-07-06 18:00:08 +02002153 _VarsToLines(self._vars) +
Paweł Hajdan, Jr76a9d042017-08-18 20:05:41 +02002154 ['# %s, %s' % (url, deps_file)
Michael Mossfe68c912018-03-22 19:19:35 -07002155 for url, deps_file, _ in sorted(self._deps_files)] +
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002156 ['']) # Ensure newline at end of file.
2157
Paweł Hajdan, Jr11eb7152017-08-10 12:50:11 +02002158 def _add_dep(self, dep):
2159 """Helper to add a dependency to flattened DEPS.
2160
2161 Arguments:
2162 dep (Dependency): dependency to add
2163 """
2164 assert dep.name not in self._deps or self._deps.get(dep.name) == dep, (
2165 dep.name, self._deps.get(dep.name))
Michael Mossd683d7c2018-06-15 05:05:17 +00002166 if dep.url:
2167 self._deps[dep.name] = dep
Paweł Hajdan, Jr11eb7152017-08-10 12:50:11 +02002168
Edward Lemur16f4bad2018-05-16 16:53:49 -04002169 def _flatten_dep(self, dep):
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002170 """Visits a dependency in order to flatten it (see CMDflatten).
2171
2172 Arguments:
2173 dep (Dependency): dependency to process
2174 """
Edward Lemur16f4bad2018-05-16 16:53:49 -04002175 logging.debug('_flatten_dep(%s)', dep.name)
Paweł Hajdan, Jradae2a62017-08-18 16:49:57 +02002176
Edward Lemur16f4bad2018-05-16 16:53:49 -04002177 assert dep.deps_parsed, (
2178 "Attempted to flatten %s but it has not been processed." % dep.name)
Paweł Hajdan, Jrc69b32e2017-08-17 18:47:48 +02002179
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002180 self._allowed_hosts.update(dep.allowed_hosts)
2181
Michael Mossce9f17f2018-01-31 13:16:35 -08002182 # Only include vars explicitly listed in the DEPS files or gclient solution,
2183 # not automatic, local overrides (i.e. not all of dep.get_vars()).
Michael Moss4e9b50a2018-05-23 22:35:06 -07002184 hierarchy = dep.hierarchy(include_url=False)
Edward Lemuree7b9dd2019-07-20 01:29:08 +00002185 for key, value in six.iteritems(dep._vars):
Paweł Hajdan, Jrc9353602017-08-02 17:52:08 +02002186 # Make sure there are no conflicting variables. It is fine however
2187 # to use same variable name, as long as the value is consistent.
Takuto Ikuta575872e2019-02-21 15:20:07 +00002188 assert key not in self._vars or self._vars[key][1] == value, (
2189 "dep:%s key:%s value:%s != %s" % (
2190 dep.name, key, value, self._vars[key][1]))
Michael Mossce9f17f2018-01-31 13:16:35 -08002191 self._vars[key] = (hierarchy, value)
2192 # Override explicit custom variables.
Edward Lemuree7b9dd2019-07-20 01:29:08 +00002193 for key, value in six.iteritems(dep.custom_vars):
Michael Mossce9f17f2018-01-31 13:16:35 -08002194 # Do custom_vars that don't correspond to DEPS vars ever make sense? DEPS
2195 # conditionals shouldn't be using vars that aren't also defined in the
2196 # DEPS (presubmit actually disallows this), so any new custom_var must be
2197 # unused in the DEPS, so no need to add it to the flattened output either.
2198 if key not in self._vars:
2199 continue
2200 # Don't "override" existing vars if it's actually the same value.
2201 elif self._vars[key][1] == value:
2202 continue
2203 # Anything else is overriding a default value from the DEPS.
2204 self._vars[key] = (hierarchy + ' [custom_var override]', value)
Paweł Hajdan, Jrfb022012017-07-06 18:00:08 +02002205
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002206 self._pre_deps_hooks.extend([(dep, hook) for hook in dep.pre_deps_hooks])
Edward Lemur16f4bad2018-05-16 16:53:49 -04002207 self._hooks.extend([(dep, hook) for hook in dep.deps_hooks])
Paweł Hajdan, Jradae2a62017-08-18 16:49:57 +02002208
Paweł Hajdan, Jrb0ad16e2017-08-03 15:33:21 +02002209 for sub_dep in dep.dependencies:
Edward Lemur16f4bad2018-05-16 16:53:49 -04002210 self._add_dep(sub_dep)
Paweł Hajdan, Jrb0ad16e2017-08-03 15:33:21 +02002211
Edward Lemurfbb06aa2018-06-11 20:43:06 +00002212 for d in dep.dependencies:
2213 if d.should_recurse:
2214 self._flatten_dep(d)
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002215
2216
Edward Lemur3298e7b2018-07-17 18:21:27 +00002217@metrics.collector.collect_metrics('gclient flatten')
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002218def CMDflatten(parser, args):
2219 """Flattens the solutions into a single DEPS file."""
2220 parser.add_option('--output-deps', help='Path to the output DEPS file')
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02002221 parser.add_option(
Paweł Hajdan, Jrfeb01642017-09-12 15:50:46 +02002222 '--output-deps-files',
2223 help=('Path to the output metadata about DEPS files referenced by '
2224 'recursedeps.'))
2225 parser.add_option(
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02002226 '--pin-all-deps', action='store_true',
2227 help=('Pin all deps, even if not pinned in DEPS. CAVEAT: only does so '
2228 'for checked out deps, NOT deps_os.'))
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002229 options, args = parser.parse_args(args)
2230
2231 options.nohooks = True
Paweł Hajdan, Jr5ec77132017-08-16 19:21:06 +02002232 options.process_all_deps = True
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002233 client = GClient.LoadCurrentConfig(options)
2234
2235 # Only print progress if we're writing to a file. Otherwise, progress updates
2236 # could obscure intended output.
2237 code = client.RunOnDeps('flatten', args, progress=options.output_deps)
2238 if code != 0:
2239 return code
2240
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02002241 flattener = Flattener(client, pin_all_deps=options.pin_all_deps)
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002242
2243 if options.output_deps:
2244 with open(options.output_deps, 'w') as f:
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002245 f.write(flattener.deps_string)
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002246 else:
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002247 print(flattener.deps_string)
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002248
Michael Mossfe68c912018-03-22 19:19:35 -07002249 deps_files = [{'url': d[0], 'deps_file': d[1], 'hierarchy': d[2]}
Paweł Hajdan, Jrfeb01642017-09-12 15:50:46 +02002250 for d in sorted(flattener.deps_files)]
2251 if options.output_deps_files:
2252 with open(options.output_deps_files, 'w') as f:
2253 json.dump(deps_files, f)
2254
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002255 return 0
2256
2257
Paweł Hajdan, Jr3c2aa832017-06-07 20:22:16 +02002258def _GNSettingsToLines(gn_args_file, gn_args):
2259 s = []
2260 if gn_args_file:
2261 s.extend([
2262 'gclient_gn_args_file = "%s"' % gn_args_file,
2263 'gclient_gn_args = %r' % gn_args,
2264 ])
2265 return s
2266
2267
Paweł Hajdan, Jr6014b562017-06-30 17:43:42 +02002268def _AllowedHostsToLines(allowed_hosts):
2269 """Converts |allowed_hosts| set to list of lines for output."""
2270 if not allowed_hosts:
2271 return []
2272 s = ['allowed_hosts = [']
2273 for h in sorted(allowed_hosts):
2274 s.append(' "%s",' % h)
2275 s.extend([']', ''])
2276 return s
2277
2278
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002279def _DepsToLines(deps):
2280 """Converts |deps| dict to list of lines for output."""
Paweł Hajdan, Jr5b593352017-06-29 18:37:45 +02002281 if not deps:
2282 return []
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002283 s = ['deps = {']
Edward Lemuree7b9dd2019-07-20 01:29:08 +00002284 for _, dep in sorted(deps.items()):
John Budorick0f7b2002018-01-19 15:46:17 -08002285 s.extend(dep.ToLines())
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002286 s.extend(['}', ''])
2287 return s
2288
2289
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02002290def _DepsOsToLines(deps_os):
2291 """Converts |deps_os| dict to list of lines for output."""
Paweł Hajdan, Jr5b593352017-06-29 18:37:45 +02002292 if not deps_os:
2293 return []
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02002294 s = ['deps_os = {']
Edward Lemuree7b9dd2019-07-20 01:29:08 +00002295 for dep_os, os_deps in sorted(deps_os.items()):
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02002296 s.append(' "%s": {' % dep_os)
Edward Lemuree7b9dd2019-07-20 01:29:08 +00002297 for name, dep in sorted(os_deps.items()):
Paweł Hajdan, Jr78ce24e2017-10-03 17:09:13 +02002298 condition_part = ([' "condition": %r,' % dep.condition]
Paweł Hajdan, Jrad30de62017-06-26 18:51:58 +02002299 if dep.condition else [])
2300 s.extend([
Michael Moss4e9b50a2018-05-23 22:35:06 -07002301 ' # %s' % dep.hierarchy(include_url=False),
Paweł Hajdan, Jrad30de62017-06-26 18:51:58 +02002302 ' "%s": {' % (name,),
Edward Lemure05f18d2018-06-08 17:36:53 +00002303 ' "url": "%s",' % (dep.url,),
Paweł Hajdan, Jrad30de62017-06-26 18:51:58 +02002304 ] + condition_part + [
2305 ' },',
2306 '',
2307 ])
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02002308 s.extend([' },', ''])
2309 s.extend(['}', ''])
2310 return s
2311
2312
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002313def _HooksToLines(name, hooks):
2314 """Converts |hooks| list to list of lines for output."""
Paweł Hajdan, Jr5b593352017-06-29 18:37:45 +02002315 if not hooks:
2316 return []
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002317 s = ['%s = [' % name]
2318 for dep, hook in hooks:
2319 s.extend([
Michael Moss4e9b50a2018-05-23 22:35:06 -07002320 ' # %s' % dep.hierarchy(include_url=False),
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002321 ' {',
2322 ])
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +02002323 if hook.name is not None:
2324 s.append(' "name": "%s",' % hook.name)
2325 if hook.pattern is not None:
2326 s.append(' "pattern": "%s",' % hook.pattern)
Paweł Hajdan, Jrecf53fe2017-09-29 18:28:49 +02002327 if hook.condition is not None:
Paweł Hajdan, Jr78ce24e2017-10-03 17:09:13 +02002328 s.append(' "condition": %r,' % hook.condition)
Corentin Walleza68660d2018-09-10 17:33:24 +00002329 # Flattened hooks need to be written relative to the root gclient dir
2330 cwd = os.path.relpath(os.path.normpath(hook.effective_cwd))
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002331 s.extend(
Corentin Walleza68660d2018-09-10 17:33:24 +00002332 [' "cwd": "%s",' % cwd] +
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002333 [' "action": ['] +
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +02002334 [' "%s",' % arg for arg in hook.action] +
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002335 [' ]', ' },', '']
2336 )
2337 s.extend([']', ''])
2338 return s
2339
2340
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +02002341def _HooksOsToLines(hooks_os):
2342 """Converts |hooks| list to list of lines for output."""
Paweł Hajdan, Jr5b593352017-06-29 18:37:45 +02002343 if not hooks_os:
2344 return []
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +02002345 s = ['hooks_os = {']
Edward Lemuree7b9dd2019-07-20 01:29:08 +00002346 for hook_os, os_hooks in six.iteritems(hooks_os):
Michael Moss017bcf62017-06-28 15:26:38 -07002347 s.append(' "%s": [' % hook_os)
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +02002348 for dep, hook in os_hooks:
2349 s.extend([
Michael Moss4e9b50a2018-05-23 22:35:06 -07002350 ' # %s' % dep.hierarchy(include_url=False),
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +02002351 ' {',
2352 ])
2353 if hook.name is not None:
2354 s.append(' "name": "%s",' % hook.name)
2355 if hook.pattern is not None:
2356 s.append(' "pattern": "%s",' % hook.pattern)
Paweł Hajdan, Jrecf53fe2017-09-29 18:28:49 +02002357 if hook.condition is not None:
Paweł Hajdan, Jr78ce24e2017-10-03 17:09:13 +02002358 s.append(' "condition": %r,' % hook.condition)
Corentin Walleza68660d2018-09-10 17:33:24 +00002359 # Flattened hooks need to be written relative to the root gclient dir
2360 cwd = os.path.relpath(os.path.normpath(hook.effective_cwd))
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +02002361 s.extend(
Corentin Walleza68660d2018-09-10 17:33:24 +00002362 [' "cwd": "%s",' % cwd] +
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +02002363 [' "action": ['] +
2364 [' "%s",' % arg for arg in hook.action] +
2365 [' ]', ' },', '']
2366 )
Michael Moss017bcf62017-06-28 15:26:38 -07002367 s.extend([' ],', ''])
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +02002368 s.extend(['}', ''])
2369 return s
2370
2371
Paweł Hajdan, Jrfb022012017-07-06 18:00:08 +02002372def _VarsToLines(variables):
2373 """Converts |variables| dict to list of lines for output."""
2374 if not variables:
2375 return []
2376 s = ['vars = {']
Edward Lemuree7b9dd2019-07-20 01:29:08 +00002377 for key, tup in sorted(variables.items()):
Michael Mossce9f17f2018-01-31 13:16:35 -08002378 hierarchy, value = tup
Paweł Hajdan, Jrfb022012017-07-06 18:00:08 +02002379 s.extend([
Michael Mossce9f17f2018-01-31 13:16:35 -08002380 ' # %s' % hierarchy,
Paweł Hajdan, Jrfb022012017-07-06 18:00:08 +02002381 ' "%s": %r,' % (key, value),
2382 '',
2383 ])
2384 s.extend(['}', ''])
2385 return s
2386
2387
Edward Lemur3298e7b2018-07-17 18:21:27 +00002388@metrics.collector.collect_metrics('gclient grep')
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00002389def CMDgrep(parser, args):
2390 """Greps through git repos managed by gclient.
2391
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002392 Runs 'git grep [args...]' for each module.
2393 """
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00002394 # We can't use optparse because it will try to parse arguments sent
2395 # to git grep and throw an error. :-(
2396 if not args or re.match('(-h|--help)$', args[0]):
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002397 print(
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00002398 'Usage: gclient grep [-j <N>] git-grep-args...\n\n'
2399 'Example: "gclient grep -j10 -A2 RefCountedBase" runs\n"git grep '
2400 '-A2 RefCountedBase" on each of gclient\'s git\nrepos with up to '
2401 '10 jobs.\n\nBonus: page output by appending "|& less -FRSX" to the'
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002402 ' end of your query.',
2403 file=sys.stderr)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00002404 return 1
2405
2406 jobs_arg = ['--jobs=1']
2407 if re.match(r'(-j|--jobs=)\d+$', args[0]):
2408 jobs_arg, args = args[:1], args[1:]
2409 elif re.match(r'(-j|--jobs)$', args[0]):
2410 jobs_arg, args = args[:2], args[2:]
2411
2412 return CMDrecurse(
2413 parser,
2414 jobs_arg + ['--ignore', '--prepend-dir', '--no-progress', '--scm=git',
2415 'git', 'grep', '--null', '--color=Always'] + args)
davidbarr@chromium.org12f944e2012-03-01 02:18:31 +00002416
2417
Edward Lemur3298e7b2018-07-17 18:21:27 +00002418@metrics.collector.collect_metrics('gclient root')
stip@chromium.orga735da22015-04-29 23:18:20 +00002419def CMDroot(parser, args):
2420 """Outputs the solution root (or current dir if there isn't one)."""
2421 (options, args) = parser.parse_args(args)
2422 client = GClient.LoadCurrentConfig(options)
2423 if client:
2424 print(os.path.abspath(client.root_dir))
2425 else:
2426 print(os.path.abspath('.'))
2427
2428
agablea98a6cd2016-11-15 14:30:10 -08002429@subcommand.usage('[url]')
Edward Lemur3298e7b2018-07-17 18:21:27 +00002430@metrics.collector.collect_metrics('gclient config')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002431def CMDconfig(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002432 """Creates a .gclient file in the current directory.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00002433
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002434 This specifies the configuration for further commands. After update/sync,
2435 top-level DEPS files in each module are read to determine dependent
2436 modules to operate on as well. If optional [url] parameter is
2437 provided, then configuration is read from a specified Subversion server
2438 URL.
2439 """
szager@chromium.orge2e03202012-07-31 18:05:16 +00002440 # We do a little dance with the --gclientfile option. 'gclient config' is the
2441 # only command where it's acceptable to have both '--gclientfile' and '--spec'
2442 # arguments. So, we temporarily stash any --gclientfile parameter into
2443 # options.output_config_file until after the (gclientfile xor spec) error
2444 # check.
2445 parser.remove_option('--gclientfile')
2446 parser.add_option('--gclientfile', dest='output_config_file',
2447 help='Specify an alternate .gclient file')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002448 parser.add_option('--name',
2449 help='overrides the default name for the solution')
nsylvain@google.comefc80932011-05-31 21:27:56 +00002450 parser.add_option('--deps-file', default='DEPS',
David Benjamin105e11e2017-10-16 10:39:35 -04002451 help='overrides the default name for the DEPS file for the '
nsylvain@google.comefc80932011-05-31 21:27:56 +00002452 'main solutions and all sub-dependencies')
smutae7ea312016-07-18 11:59:41 -07002453 parser.add_option('--unmanaged', action='store_true', default=False,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00002454 help='overrides the default behavior to make it possible '
smutae7ea312016-07-18 11:59:41 -07002455 'to have the main solution untouched by gclient '
2456 '(gclient will check out unmanaged dependencies but '
2457 'will never sync them)')
Robert Iannuccia19649b2018-06-29 16:31:45 +00002458 parser.add_option('--cache-dir', default=UNSET_CACHE_DIR,
2459 help='Cache all git repos into this dir and do shared '
2460 'clones from the cache, instead of cloning directly '
2461 'from the remote. Pass "None" to disable cache, even '
2462 'if globally enabled due to $GIT_CACHE_PATH.')
Paweł Hajdan, Jr3ba2a7c2017-10-04 19:24:46 +02002463 parser.add_option('--custom-var', action='append', dest='custom_vars',
2464 default=[],
2465 help='overrides variables; key=value syntax')
szager@chromium.orge2e03202012-07-31 18:05:16 +00002466 parser.set_defaults(config_filename=None)
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002467 (options, args) = parser.parse_args(args)
szager@chromium.orge2e03202012-07-31 18:05:16 +00002468 if options.output_config_file:
2469 setattr(options, 'config_filename', getattr(options, 'output_config_file'))
maruel@chromium.org5fc2a332010-05-26 19:37:15 +00002470 if ((options.spec and args) or len(args) > 2 or
2471 (not options.spec and not args)):
2472 parser.error('Inconsistent arguments. Use either --spec or one or 2 args')
2473
Robert Iannuccia19649b2018-06-29 16:31:45 +00002474 if (options.cache_dir is not UNSET_CACHE_DIR
2475 and options.cache_dir.lower() == 'none'):
2476 options.cache_dir = None
2477
Paweł Hajdan, Jr3ba2a7c2017-10-04 19:24:46 +02002478 custom_vars = {}
2479 for arg in options.custom_vars:
2480 kv = arg.split('=', 1)
2481 if len(kv) != 2:
2482 parser.error('Invalid --custom-var argument: %r' % arg)
2483 custom_vars[kv[0]] = gclient_eval.EvaluateCondition(kv[1], {})
2484
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002485 client = GClient('.', options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002486 if options.spec:
2487 client.SetConfig(options.spec)
2488 else:
maruel@chromium.org1ab7ffc2009-06-03 17:21:37 +00002489 base_url = args[0].rstrip('/')
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00002490 if not options.name:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002491 name = base_url.split('/')[-1]
nsylvain@google.com12649ef2011-06-01 17:11:20 +00002492 if name.endswith('.git'):
2493 name = name[:-4]
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00002494 else:
2495 # specify an alternate relpath for the given URL.
2496 name = options.name
agable@chromium.orgf2214672015-10-27 21:02:48 +00002497 if not os.path.abspath(os.path.join(os.getcwd(), name)).startswith(
2498 os.getcwd()):
2499 parser.error('Do not pass a relative path for --name.')
2500 if any(x in ('..', '.', '/', '\\') for x in name.split(os.sep)):
2501 parser.error('Do not include relative path components in --name.')
2502
nsylvain@google.comefc80932011-05-31 21:27:56 +00002503 deps_file = options.deps_file
agablea98a6cd2016-11-15 14:30:10 -08002504 client.SetDefaultConfig(name, deps_file, base_url,
smutae7ea312016-07-18 11:59:41 -07002505 managed=not options.unmanaged,
Paweł Hajdan, Jr3ba2a7c2017-10-04 19:24:46 +02002506 cache_dir=options.cache_dir,
2507 custom_vars=custom_vars)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002508 client.SaveConfig()
maruel@chromium.org79692d62010-05-14 18:57:13 +00002509 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002510
2511
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002512@subcommand.epilog("""Example:
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002513 gclient pack > patch.txt
2514 generate simple patch for configured client and dependences
2515""")
Edward Lemur3298e7b2018-07-17 18:21:27 +00002516@metrics.collector.collect_metrics('gclient pack')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002517def CMDpack(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002518 """Generates a patch which can be applied at the root of the tree.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00002519
agabled437d762016-10-17 09:35:11 -07002520 Internally, runs 'git diff' on each checked out module and
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002521 dependencies, and performs minimal postprocessing of the output. The
2522 resulting patch is printed to stdout and can be applied to a freshly
2523 checked out tree via 'patch -p0 < patchfile'.
2524 """
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002525 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2526 help='override deps for the specified (comma-separated) '
2527 'platform(s); \'all\' will process all deps_os '
2528 'references')
haitao.feng@intel.com306080c2012-05-04 13:11:29 +00002529 parser.remove_option('--jobs')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002530 (options, args) = parser.parse_args(args)
iannucci@chromium.org50395ea2013-04-04 04:47:42 +00002531 # Force jobs to 1 so the stdout is not annotated with the thread ids
haitao.feng@intel.com306080c2012-05-04 13:11:29 +00002532 options.jobs = 1
kbr@google.comab318592009-09-04 00:54:55 +00002533 client = GClient.LoadCurrentConfig(options)
2534 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002535 raise gclient_utils.Error('client not configured; see \'gclient config\'')
kbr@google.comab318592009-09-04 00:54:55 +00002536 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002537 client.PrintLocationAndContents()
kbr@google.comab318592009-09-04 00:54:55 +00002538 return client.RunOnDeps('pack', args)
2539
2540
Edward Lemur3298e7b2018-07-17 18:21:27 +00002541@metrics.collector.collect_metrics('gclient status')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002542def CMDstatus(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002543 """Shows modification status for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002544 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2545 help='override deps for the specified (comma-separated) '
2546 'platform(s); \'all\' will process all deps_os '
2547 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002548 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002549 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002550 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002551 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002552 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002553 client.PrintLocationAndContents()
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002554 return client.RunOnDeps('status', args)
2555
2556
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002557@subcommand.epilog("""Examples:
maruel@chromium.org79692d62010-05-14 18:57:13 +00002558 gclient sync
2559 update files from SCM according to current configuration,
2560 *for modules which have changed since last update or sync*
2561 gclient sync --force
2562 update files from SCM according to current configuration, for
2563 all modules (useful for recovering files deleted from local copy)
2564 gclient sync --revision src@31000
2565 update src directory to r31000
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002566
2567JSON output format:
2568If the --output-json option is specified, the following document structure will
2569be emitted to the provided file. 'null' entries may occur for subprojects which
2570are present in the gclient solution, but were not processed (due to custom_deps,
2571os_deps, etc.)
2572
2573{
2574 "solutions" : {
2575 "<name>": { # <name> is the posix-normalized path to the solution.
agabled437d762016-10-17 09:35:11 -07002576 "revision": [<git id hex string>|null],
2577 "scm": ["git"|null],
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002578 }
2579 }
2580}
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002581""")
Edward Lemur3298e7b2018-07-17 18:21:27 +00002582@metrics.collector.collect_metrics('gclient sync')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002583def CMDsync(parser, args):
2584 """Checkout/update all modules."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002585 parser.add_option('-f', '--force', action='store_true',
2586 help='force update even for unchanged modules')
2587 parser.add_option('-n', '--nohooks', action='store_true',
2588 help='don\'t run hooks after the update is complete')
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00002589 parser.add_option('-p', '--noprehooks', action='store_true',
2590 help='don\'t run pre-DEPS hooks', default=False)
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002591 parser.add_option('-r', '--revision', action='append',
2592 dest='revisions', metavar='REV', default=[],
2593 help='Enforces revision/hash for the solutions with the '
2594 'format src@rev. The src@ part is optional and can be '
Edward Lesmes53014652018-03-07 18:01:40 -05002595 'skipped. You can also specify URLs instead of paths '
2596 'and gclient will find the solution corresponding to '
2597 'the given URL. If a path is also specified, the URL '
2598 'takes precedence. -r can be used multiple times when '
2599 '.gclient has multiple solutions configured, and will '
2600 'work even if the src@ part is skipped.')
Edward Lesmesc621b212018-03-21 20:26:56 -04002601 parser.add_option('--patch-ref', action='append',
2602 dest='patch_refs', metavar='GERRIT_REF', default=[],
Edward Lemur6a4e31b2018-08-10 19:59:02 +00002603 help='Patches the given reference with the format '
Edward Lemur4c5c8ab2019-06-07 15:58:13 +00002604 'dep@target-ref:patch-ref. '
Edward Lemur6a4e31b2018-08-10 19:59:02 +00002605 'For |dep|, you can specify URLs as well as paths, '
2606 'with URLs taking preference. '
2607 '|patch-ref| will be applied to |dep|, rebased on top '
2608 'of what |dep| was synced to, and a soft reset will '
2609 'be done. Use --no-rebase-patch-ref and '
2610 '--no-reset-patch-ref to disable this behavior. '
2611 '|target-ref| is the target branch against which a '
2612 'patch was created, it is used to determine which '
2613 'commits from the |patch-ref| actually constitute a '
Edward Lemur4c5c8ab2019-06-07 15:58:13 +00002614 'patch.')
maruel@chromium.org794207e2013-03-08 15:29:43 +00002615 parser.add_option('--with_branch_heads', action='store_true',
2616 help='Clone git "branch_heads" refspecs in addition to '
2617 'the default refspecs. This adds about 1/2GB to a '
2618 'full checkout. (git only)')
szager@chromium.org8d3348f2014-08-19 22:49:16 +00002619 parser.add_option('--with_tags', action='store_true',
2620 help='Clone git tags in addition to the default refspecs.')
agable2697cd12016-06-28 10:23:53 -07002621 parser.add_option('-H', '--head', action='store_true',
agablea98a6cd2016-11-15 14:30:10 -08002622 help='DEPRECATED: only made sense with safesync urls.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002623 parser.add_option('-D', '--delete_unversioned_trees', action='store_true',
steveblock@chromium.org98e69452012-02-16 16:36:43 +00002624 help='Deletes from the working copy any dependencies that '
2625 'have been removed since the last sync, as long as '
2626 'there are no local modifications. When used with '
2627 '--force, such dependencies are removed even if they '
2628 'have local modifications. When used with --reset, '
2629 'all untracked directories are removed from the '
tnagel@chromium.orga2aaa632014-02-28 21:47:27 +00002630 'working copy, excluding those which are explicitly '
steveblock@chromium.org98e69452012-02-16 16:36:43 +00002631 'ignored in the repository.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002632 parser.add_option('-R', '--reset', action='store_true',
2633 help='resets any local changes before updating (git only)')
bauerb@chromium.org2aad1b22011-07-22 12:00:41 +00002634 parser.add_option('-M', '--merge', action='store_true',
2635 help='merge upstream changes instead of trying to '
2636 'fast-forward or rebase')
dnj@chromium.org5b23e872015-02-20 21:25:57 +00002637 parser.add_option('-A', '--auto_rebase', action='store_true',
2638 help='Automatically rebase repositories against local '
2639 'checkout during update (git only).')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002640 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2641 help='override deps for the specified (comma-separated) '
2642 'platform(s); \'all\' will process all deps_os '
2643 'references')
Paweł Hajdan, Jr5ec77132017-08-16 19:21:06 +02002644 parser.add_option('--process-all-deps', action='store_true',
2645 help='Check out all deps, even for different OS-es, '
2646 'or with conditions evaluating to false')
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +00002647 parser.add_option('--upstream', action='store_true',
2648 help='Make repo state match upstream branch.')
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002649 parser.add_option('--output-json',
2650 help='Output a json document to this path containing '
2651 'summary information about the sync.')
primiano@chromium.org5439ea52014-08-06 17:18:18 +00002652 parser.add_option('--no-history', action='store_true',
2653 help='GIT ONLY - Reduces the size/time of the checkout at '
2654 'the cost of no history. Requires Git 1.9+')
hinoka@chromium.org46b87412014-05-15 00:42:05 +00002655 parser.add_option('--shallow', action='store_true',
2656 help='GIT ONLY - Do a shallow clone into the cache dir. '
2657 'Requires Git 1.9+')
e.hakkinen@samsung.come8bc1aa2015-04-08 08:00:37 +00002658 parser.add_option('--no_bootstrap', '--no-bootstrap',
2659 action='store_true',
2660 help='Don\'t bootstrap from Google Storage.')
Vadim Shtayura08049e22017-10-11 00:14:52 +00002661 parser.add_option('--ignore_locks', action='store_true',
2662 help='GIT ONLY - Ignore cache locks.')
iannucci@chromium.org30a07982016-04-07 21:35:19 +00002663 parser.add_option('--break_repo_locks', action='store_true',
2664 help='GIT ONLY - Forcibly remove repo locks (e.g. '
2665 'index.lock). This should only be used if you know for '
2666 'certain that this invocation of gclient is the only '
2667 'thing operating on the git repos (e.g. on a bot).')
Vadim Shtayura08049e22017-10-11 00:14:52 +00002668 parser.add_option('--lock_timeout', type='int', default=5000,
2669 help='GIT ONLY - Deadline (in seconds) to wait for git '
2670 'cache lock to become available. Default is %default.')
agabled437d762016-10-17 09:35:11 -07002671 # TODO(agable): Remove these when the oldest CrOS release milestone is M56.
2672 parser.add_option('-t', '--transitive', action='store_true',
2673 help='DEPRECATED: This is a no-op.')
sdefresne69b1be12016-10-18 05:48:02 -07002674 parser.add_option('-m', '--manually_grab_svn_rev', action='store_true',
agabled437d762016-10-17 09:35:11 -07002675 help='DEPRECATED: This is a no-op.')
Paweł Hajdan, Jr7c7b5592017-05-23 15:06:05 +02002676 # TODO(phajdan.jr): Remove validation options once default (crbug/570091).
Paweł Hajdan, Jr694773d2017-05-29 16:06:23 +02002677 parser.add_option('--validate-syntax', action='store_true', default=True,
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +02002678 help='Validate the .gclient and DEPS syntax')
Paweł Hajdan, Jr7c7b5592017-05-23 15:06:05 +02002679 parser.add_option('--disable-syntax-validation', action='store_false',
2680 dest='validate_syntax',
2681 help='Disable validation of .gclient and DEPS syntax.')
Edward Lesmesc621b212018-03-21 20:26:56 -04002682 parser.add_option('--no-rebase-patch-ref', action='store_false',
2683 dest='rebase_patch_ref', default=True,
2684 help='Bypass rebase of the patch ref after checkout.')
2685 parser.add_option('--no-reset-patch-ref', action='store_false',
2686 dest='reset_patch_ref', default=True,
2687 help='Bypass calling reset after patching the ref.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002688 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002689 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002690
2691 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002692 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002693
smutae7ea312016-07-18 11:59:41 -07002694 if options.revisions and options.head:
2695 # TODO(maruel): Make it a parser.error if it doesn't break any builder.
2696 print('Warning: you cannot use both --head and --revision')
2697
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002698 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002699 client.PrintLocationAndContents()
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002700 ret = client.RunOnDeps('update', args)
2701 if options.output_json:
2702 slns = {}
Michael Mossd683d7c2018-06-15 05:05:17 +00002703 for d in client.subtree(True):
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002704 normed = d.name.replace('\\', '/').rstrip('/') + '/'
2705 slns[normed] = {
2706 'revision': d.got_revision,
2707 'scm': d.used_scm.name if d.used_scm else None,
Michael Mossd683d7c2018-06-15 05:05:17 +00002708 'url': str(d.url) if d.url else None,
Edward Lemur7ccf2f02018-06-26 20:41:56 +00002709 'was_processed': d.should_process,
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002710 }
Edward Lemurca879322019-09-09 20:18:13 +00002711 with open(options.output_json, 'w') as f:
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002712 json.dump({'solutions': slns}, f)
2713 return ret
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002714
2715
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002716CMDupdate = CMDsync
2717
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002718
Edward Lemur3298e7b2018-07-17 18:21:27 +00002719@metrics.collector.collect_metrics('gclient validate')
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +02002720def CMDvalidate(parser, args):
2721 """Validates the .gclient and DEPS syntax."""
2722 options, args = parser.parse_args(args)
2723 options.validate_syntax = True
2724 client = GClient.LoadCurrentConfig(options)
2725 rv = client.RunOnDeps('validate', args)
2726 if rv == 0:
2727 print('validate: SUCCESS')
2728 else:
2729 print('validate: FAILURE')
2730 return rv
2731
2732
Edward Lemur3298e7b2018-07-17 18:21:27 +00002733@metrics.collector.collect_metrics('gclient diff')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002734def CMDdiff(parser, args):
2735 """Displays local diff for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002736 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2737 help='override deps for the specified (comma-separated) '
2738 'platform(s); \'all\' will process all deps_os '
2739 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002740 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002741 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002742 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002743 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002744 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002745 client.PrintLocationAndContents()
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002746 return client.RunOnDeps('diff', args)
2747
2748
Edward Lemur3298e7b2018-07-17 18:21:27 +00002749@metrics.collector.collect_metrics('gclient revert')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002750def CMDrevert(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002751 """Reverts all modifications in every dependencies.
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00002752
2753 That's the nuclear option to get back to a 'clean' state. It removes anything
agabled437d762016-10-17 09:35:11 -07002754 that shows up in git status."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002755 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2756 help='override deps for the specified (comma-separated) '
2757 'platform(s); \'all\' will process all deps_os '
2758 'references')
2759 parser.add_option('-n', '--nohooks', action='store_true',
2760 help='don\'t run hooks after the revert is complete')
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00002761 parser.add_option('-p', '--noprehooks', action='store_true',
2762 help='don\'t run pre-DEPS hooks', default=False)
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +00002763 parser.add_option('--upstream', action='store_true',
2764 help='Make repo state match upstream branch.')
iannucci@chromium.orgbf525dc2016-04-07 22:00:28 +00002765 parser.add_option('--break_repo_locks', action='store_true',
2766 help='GIT ONLY - Forcibly remove repo locks (e.g. '
2767 'index.lock). This should only be used if you know for '
2768 'certain that this invocation of gclient is the only '
2769 'thing operating on the git repos (e.g. on a bot).')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002770 (options, args) = parser.parse_args(args)
2771 # --force is implied.
2772 options.force = True
steveblock@chromium.org98e69452012-02-16 16:36:43 +00002773 options.reset = False
2774 options.delete_unversioned_trees = False
agablec903d732016-07-26 09:07:24 -07002775 options.merge = False
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002776 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002777 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002778 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002779 return client.RunOnDeps('revert', args)
2780
2781
Edward Lemur3298e7b2018-07-17 18:21:27 +00002782@metrics.collector.collect_metrics('gclient runhooks')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002783def CMDrunhooks(parser, args):
2784 """Runs hooks for files that have been modified in the local working copy."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002785 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2786 help='override deps for the specified (comma-separated) '
2787 'platform(s); \'all\' will process all deps_os '
2788 'references')
2789 parser.add_option('-f', '--force', action='store_true', default=True,
2790 help='Deprecated. No effect.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002791 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002792 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002793 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002794 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002795 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002796 client.PrintLocationAndContents()
maruel@chromium.org5df6a462009-08-28 18:52:26 +00002797 options.force = True
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002798 options.nohooks = False
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002799 return client.RunOnDeps('runhooks', args)
2800
2801
Edward Lemur3298e7b2018-07-17 18:21:27 +00002802@metrics.collector.collect_metrics('gclient revinfo')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002803def CMDrevinfo(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002804 """Outputs revision info mapping for the client and its dependencies.
maruel@chromium.org9eda4112010-06-11 18:56:10 +00002805
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002806 This allows the capture of an overall 'revision' for the source tree that
maruel@chromium.org9eda4112010-06-11 18:56:10 +00002807 can be used to reproduce the same tree in the future. It is only useful for
agabled437d762016-10-17 09:35:11 -07002808 'unpinned dependencies', i.e. DEPS/deps references without a git hash.
2809 A git branch name isn't 'pinned' since the actual commit can change.
maruel@chromium.org9eda4112010-06-11 18:56:10 +00002810 """
2811 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2812 help='override deps for the specified (comma-separated) '
2813 'platform(s); \'all\' will process all deps_os '
2814 'references')
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00002815 parser.add_option('-a', '--actual', action='store_true',
2816 help='gets the actual checked out revisions instead of the '
2817 'ones specified in the DEPS and .gclient files')
maruel@chromium.org9eda4112010-06-11 18:56:10 +00002818 parser.add_option('-s', '--snapshot', action='store_true',
2819 help='creates a snapshot .gclient file of the current '
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00002820 'version of all repositories to reproduce the tree, '
2821 'implies -a')
Edward Lesmesbb16e332018-03-30 17:54:51 -04002822 parser.add_option('--filter', action='append', dest='filter',
Edward Lesmesdaa76d22018-03-06 14:56:57 -05002823 help='Display revision information only for the specified '
Edward Lesmesbb16e332018-03-30 17:54:51 -04002824 'dependencies (filtered by URL or path).')
Edward Lesmesc2960242018-03-06 20:50:15 -05002825 parser.add_option('--output-json',
2826 help='Output a json document to this path containing '
2827 'information about the revisions.')
Joey Scarr8d3925b2018-07-15 23:36:25 +00002828 parser.add_option('--ignore-dep-type', choices=['git', 'cipd'],
2829 help='Specify to skip processing of a certain type of dep.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002830 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002831 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002832 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002833 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002834 client.PrintRevInfo()
maruel@chromium.org79692d62010-05-14 18:57:13 +00002835 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002836
2837
Edward Lemur3298e7b2018-07-17 18:21:27 +00002838@metrics.collector.collect_metrics('gclient getdep')
Edward Lesmes411041f2018-04-05 20:12:55 -04002839def CMDgetdep(parser, args):
2840 """Gets revision information and variable values from a DEPS file."""
2841 parser.add_option('--var', action='append',
2842 dest='vars', metavar='VAR', default=[],
2843 help='Gets the value of a given variable.')
2844 parser.add_option('-r', '--revision', action='append',
Edward Lemuraf3328f2018-11-19 14:11:46 +00002845 dest='getdep_revisions', metavar='DEP', default=[],
Edward Lesmes411041f2018-04-05 20:12:55 -04002846 help='Gets the revision/version for the given dependency. '
2847 'If it is a git dependency, dep must be a path. If it '
2848 'is a CIPD dependency, dep must be of the form '
2849 'path:package.')
2850 parser.add_option('--deps-file', default='DEPS',
2851 # TODO(ehmaldonado): Try to find the DEPS file pointed by
2852 # .gclient first.
2853 help='The DEPS file to be edited. Defaults to the DEPS '
2854 'file in the current directory.')
2855 (options, args) = parser.parse_args(args)
2856
2857 if not os.path.isfile(options.deps_file):
2858 raise gclient_utils.Error(
2859 'DEPS file %s does not exist.' % options.deps_file)
2860 with open(options.deps_file) as f:
2861 contents = f.read()
Edward Lemuraf3328f2018-11-19 14:11:46 +00002862 client = GClient.LoadCurrentConfig(options)
2863 if client is not None:
2864 builtin_vars = client.get_builtin_vars()
2865 else:
Edward Lemurca879322019-09-09 20:18:13 +00002866 logging.warning(
Edward Lemuraf3328f2018-11-19 14:11:46 +00002867 'Couldn\'t find a valid gclient config. Will attempt to parse the DEPS '
2868 'file without support for built-in variables.')
2869 builtin_vars = None
2870 local_scope = gclient_eval.Exec(contents, options.deps_file,
2871 builtin_vars=builtin_vars)
Edward Lesmes411041f2018-04-05 20:12:55 -04002872
2873 for var in options.vars:
2874 print(gclient_eval.GetVar(local_scope, var))
2875
Edward Lemuraf3328f2018-11-19 14:11:46 +00002876 for name in options.getdep_revisions:
Edward Lesmes411041f2018-04-05 20:12:55 -04002877 if ':' in name:
2878 name, _, package = name.partition(':')
2879 if not name or not package:
2880 parser.error(
2881 'Wrong CIPD format: %s:%s should be of the form path:pkg.'
2882 % (name, package))
2883 print(gclient_eval.GetCIPD(local_scope, name, package))
2884 else:
2885 print(gclient_eval.GetRevision(local_scope, name))
2886
2887
Edward Lemur3298e7b2018-07-17 18:21:27 +00002888@metrics.collector.collect_metrics('gclient setdep')
Edward Lesmes6f64a052018-03-20 17:35:49 -04002889def CMDsetdep(parser, args):
Edward Lesmes0ecf6d62018-04-05 18:28:55 -04002890 """Modifies dependency revisions and variable values in a DEPS file"""
Edward Lesmes6f64a052018-03-20 17:35:49 -04002891 parser.add_option('--var', action='append',
2892 dest='vars', metavar='VAR=VAL', default=[],
2893 help='Sets a variable to the given value with the format '
2894 'name=value.')
2895 parser.add_option('-r', '--revision', action='append',
Edward Lemuraf3328f2018-11-19 14:11:46 +00002896 dest='setdep_revisions', metavar='DEP@REV', default=[],
Edward Lesmes6f64a052018-03-20 17:35:49 -04002897 help='Sets the revision/version for the dependency with '
2898 'the format dep@rev. If it is a git dependency, dep '
2899 'must be a path and rev must be a git hash or '
2900 'reference (e.g. src/dep@deadbeef). If it is a CIPD '
2901 'dependency, dep must be of the form path:package and '
2902 'rev must be the package version '
2903 '(e.g. src/pkg:chromium/pkg@2.1-cr0).')
2904 parser.add_option('--deps-file', default='DEPS',
2905 # TODO(ehmaldonado): Try to find the DEPS file pointed by
2906 # .gclient first.
2907 help='The DEPS file to be edited. Defaults to the DEPS '
2908 'file in the current directory.')
2909 (options, args) = parser.parse_args(args)
Edward Lesmes0ecf6d62018-04-05 18:28:55 -04002910 if args:
2911 parser.error('Unused arguments: "%s"' % '" "'.join(args))
Edward Lesmesae6836e2018-11-19 15:27:20 +00002912 if not options.setdep_revisions and not options.vars:
Edward Lesmes0ecf6d62018-04-05 18:28:55 -04002913 parser.error(
2914 'You must specify at least one variable or revision to modify.')
Edward Lesmes6f64a052018-03-20 17:35:49 -04002915
Edward Lesmes6f64a052018-03-20 17:35:49 -04002916 if not os.path.isfile(options.deps_file):
2917 raise gclient_utils.Error(
2918 'DEPS file %s does not exist.' % options.deps_file)
2919 with open(options.deps_file) as f:
2920 contents = f.read()
Edward Lemuraf3328f2018-11-19 14:11:46 +00002921
2922 client = GClient.LoadCurrentConfig(options)
2923 if client is not None:
2924 builtin_vars = client.get_builtin_vars()
2925 else:
Edward Lemurca879322019-09-09 20:18:13 +00002926 logging.warning(
Edward Lemuraf3328f2018-11-19 14:11:46 +00002927 'Couldn\'t find a valid gclient config. Will attempt to parse the DEPS '
2928 'file without support for built-in variables.')
2929 builtin_vars = None
2930
2931 local_scope = gclient_eval.Exec(contents, options.deps_file,
2932 builtin_vars=builtin_vars)
Edward Lesmes6f64a052018-03-20 17:35:49 -04002933
2934 for var in options.vars:
2935 name, _, value = var.partition('=')
2936 if not name or not value:
Edward Lesmes0ecf6d62018-04-05 18:28:55 -04002937 parser.error(
Edward Lesmes6f64a052018-03-20 17:35:49 -04002938 'Wrong var format: %s should be of the form name=value.' % var)
Edward Lesmes3d993812018-04-02 12:52:49 -04002939 if name in local_scope['vars']:
2940 gclient_eval.SetVar(local_scope, name, value)
2941 else:
2942 gclient_eval.AddVar(local_scope, name, value)
Edward Lesmes6f64a052018-03-20 17:35:49 -04002943
Edward Lemuraf3328f2018-11-19 14:11:46 +00002944 for revision in options.setdep_revisions:
Edward Lesmes6f64a052018-03-20 17:35:49 -04002945 name, _, value = revision.partition('@')
2946 if not name or not value:
Edward Lesmes0ecf6d62018-04-05 18:28:55 -04002947 parser.error(
Edward Lesmes6f64a052018-03-20 17:35:49 -04002948 'Wrong dep format: %s should be of the form dep@rev.' % revision)
2949 if ':' in name:
2950 name, _, package = name.partition(':')
2951 if not name or not package:
Edward Lesmes0ecf6d62018-04-05 18:28:55 -04002952 parser.error(
Edward Lesmes6f64a052018-03-20 17:35:49 -04002953 'Wrong CIPD format: %s:%s should be of the form path:pkg@version.'
2954 % (name, package))
2955 gclient_eval.SetCIPD(local_scope, name, package, value)
2956 else:
Edward Lesmes9f531292018-03-20 21:27:15 -04002957 gclient_eval.SetRevision(local_scope, name, value)
Edward Lesmes6f64a052018-03-20 17:35:49 -04002958
2959 with open(options.deps_file, 'w') as f:
2960 f.write(gclient_eval.RenderDEPSFile(local_scope))
2961
2962
Edward Lemur3298e7b2018-07-17 18:21:27 +00002963@metrics.collector.collect_metrics('gclient verify')
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00002964def CMDverify(parser, args):
2965 """Verifies the DEPS file deps are only from allowed_hosts."""
2966 (options, args) = parser.parse_args(args)
2967 client = GClient.LoadCurrentConfig(options)
2968 if not client:
2969 raise gclient_utils.Error('client not configured; see \'gclient config\'')
2970 client.RunOnDeps(None, [])
2971 # Look at each first-level dependency of this gclient only.
2972 for dep in client.dependencies:
2973 bad_deps = dep.findDepsFromNotAllowedHosts()
2974 if not bad_deps:
2975 continue
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002976 print("There are deps from not allowed hosts in file %s" % dep.deps_file)
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00002977 for bad_dep in bad_deps:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002978 print("\t%s at %s" % (bad_dep.name, bad_dep.url))
2979 print("allowed_hosts:", ', '.join(dep.allowed_hosts))
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00002980 sys.stdout.flush()
2981 raise gclient_utils.Error(
2982 'dependencies from disallowed hosts; check your DEPS file.')
2983 return 0
2984
Edward Lemur32e3d1e2018-07-12 00:54:05 +00002985
2986@subcommand.epilog("""For more information on what metrics are we collecting and
Edward Lemur8a2e3312018-07-12 21:15:09 +00002987why, please read metrics.README.md or visit https://bit.ly/2ufRS4p""")
Edward Lemur3298e7b2018-07-17 18:21:27 +00002988@metrics.collector.collect_metrics('gclient metrics')
Edward Lemur32e3d1e2018-07-12 00:54:05 +00002989def CMDmetrics(parser, args):
2990 """Reports, and optionally modifies, the status of metric collection."""
2991 parser.add_option('--opt-in', action='store_true', dest='enable_metrics',
2992 help='Opt-in to metrics collection.',
2993 default=None)
2994 parser.add_option('--opt-out', action='store_false', dest='enable_metrics',
2995 help='Opt-out of metrics collection.')
2996 options, args = parser.parse_args(args)
2997 if args:
2998 parser.error('Unused arguments: "%s"' % '" "'.join(args))
2999 if not metrics.collector.config.is_googler:
3000 print("You're not a Googler. Metrics collection is disabled for you.")
3001 return 0
3002
3003 if options.enable_metrics is not None:
3004 metrics.collector.config.opted_in = options.enable_metrics
3005
3006 if metrics.collector.config.opted_in is None:
3007 print("You haven't opted in or out of metrics collection.")
3008 elif metrics.collector.config.opted_in:
3009 print("You have opted in. Thanks!")
3010 else:
3011 print("You have opted out. Please consider opting in.")
3012 return 0
3013
3014
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00003015class OptionParser(optparse.OptionParser):
szager@chromium.orge2e03202012-07-31 18:05:16 +00003016 gclientfile_default = os.environ.get('GCLIENT_FILE', '.gclient')
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00003017
3018 def __init__(self, **kwargs):
3019 optparse.OptionParser.__init__(
3020 self, version='%prog ' + __version__, **kwargs)
3021
3022 # Some arm boards have issues with parallel sync.
3023 if platform.machine().startswith('arm'):
3024 jobs = 1
3025 else:
3026 jobs = max(8, gclient_utils.NumLocalCpus())
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00003027
3028 self.add_option(
3029 '-j', '--jobs', default=jobs, type='int',
3030 help='Specify how many SCM commands can run in parallel; defaults to '
tnagel@chromium.orga2aaa632014-02-28 21:47:27 +00003031 '%default on this machine')
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00003032 self.add_option(
3033 '-v', '--verbose', action='count', default=0,
3034 help='Produces additional output for diagnostics. Can be used up to '
3035 'three times for more logging info.')
3036 self.add_option(
3037 '--gclientfile', dest='config_filename',
3038 help='Specify an alternate %s file' % self.gclientfile_default)
3039 self.add_option(
3040 '--spec',
3041 help='create a gclient file containing the provided string. Due to '
3042 'Cygwin/Python brokenness, it can\'t contain any newlines.')
3043 self.add_option(
3044 '--no-nag-max', default=False, action='store_true',
scottmg@chromium.orgf547c802013-09-27 17:55:26 +00003045 help='Ignored for backwards compatibility.')
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00003046
Edward Lemur3298e7b2018-07-17 18:21:27 +00003047 def parse_args(self, args=None, _values=None):
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00003048 """Integrates standard options processing."""
Edward Lemur3298e7b2018-07-17 18:21:27 +00003049 # Create an optparse.Values object that will store only the actual passed
3050 # options, without the defaults.
3051 actual_options = optparse.Values()
3052 _, args = optparse.OptionParser.parse_args(self, args, actual_options)
3053 # Create an optparse.Values object with the default options.
3054 options = optparse.Values(self.get_default_values().__dict__)
3055 # Update it with the options passed by the user.
3056 options._update_careful(actual_options.__dict__)
3057 # Store the options passed by the user in an _actual_options attribute.
3058 # We store only the keys, and not the values, since the values can contain
3059 # arbitrary information, which might be PII.
Edward Lemuree7b9dd2019-07-20 01:29:08 +00003060 metrics.collector.add('arguments', list(actual_options.__dict__))
Edward Lemur3298e7b2018-07-17 18:21:27 +00003061
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00003062 levels = [logging.ERROR, logging.WARNING, logging.INFO, logging.DEBUG]
3063 logging.basicConfig(
3064 level=levels[min(options.verbose, len(levels) - 1)],
maruel@chromium.org0895b752011-08-26 20:40:33 +00003065 format='%(module)s(%(lineno)d) %(funcName)s:%(message)s')
szager@chromium.orge2e03202012-07-31 18:05:16 +00003066 if options.config_filename and options.spec:
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00003067 self.error('Cannot specifiy both --gclientfile and --spec')
rdsmith@chromium.orgd9591f02014-02-05 19:28:20 +00003068 if (options.config_filename and
3069 options.config_filename != os.path.basename(options.config_filename)):
3070 self.error('--gclientfile target must be a filename, not a path')
szager@chromium.orge2e03202012-07-31 18:05:16 +00003071 if not options.config_filename:
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00003072 options.config_filename = self.gclientfile_default
maruel@chromium.org0895b752011-08-26 20:40:33 +00003073 options.entries_filename = options.config_filename + '_entries'
3074 if options.jobs < 1:
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00003075 self.error('--jobs must be 1 or higher')
maruel@chromium.org0895b752011-08-26 20:40:33 +00003076
3077 # These hacks need to die.
3078 if not hasattr(options, 'revisions'):
3079 # GClient.RunOnDeps expects it even if not applicable.
3080 options.revisions = []
smutae7ea312016-07-18 11:59:41 -07003081 if not hasattr(options, 'head'):
3082 options.head = None
maruel@chromium.org0895b752011-08-26 20:40:33 +00003083 if not hasattr(options, 'nohooks'):
3084 options.nohooks = True
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00003085 if not hasattr(options, 'noprehooks'):
3086 options.noprehooks = True
maruel@chromium.org0895b752011-08-26 20:40:33 +00003087 if not hasattr(options, 'deps_os'):
3088 options.deps_os = None
maruel@chromium.org0895b752011-08-26 20:40:33 +00003089 if not hasattr(options, 'force'):
3090 options.force = None
3091 return (options, args)
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00003092
maruel@chromium.org39c0b222013-08-17 16:57:01 +00003093
3094def disable_buffering():
3095 # Make stdout auto-flush so buildbot doesn't kill us during lengthy
3096 # operations. Python as a strong tendency to buffer sys.stdout.
3097 sys.stdout = gclient_utils.MakeFileAutoFlush(sys.stdout)
3098 # Make stdout annotated with the thread ids.
3099 sys.stdout = gclient_utils.MakeFileAnnotated(sys.stdout)
maruel@chromium.org0895b752011-08-26 20:40:33 +00003100
3101
Elly Fong-Jones7b294392019-04-18 18:32:10 +00003102def path_contains_tilde():
3103 for element in os.environ['PATH'].split(os.pathsep):
Henrique Ferreiro4ef32212019-04-29 23:32:31 +00003104 if element.startswith('~') and os.path.abspath(
3105 os.path.realpath(os.path.expanduser(element))) == DEPOT_TOOLS_DIR:
Elly Fong-Jones7b294392019-04-18 18:32:10 +00003106 return True
3107 return False
3108
3109
3110def can_run_gclient_and_helpers():
maruel@chromium.org82798cb2012-02-23 18:16:12 +00003111 if sys.hexversion < 0x02060000:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00003112 print(
maruel@chromium.org82798cb2012-02-23 18:16:12 +00003113 '\nYour python version %s is unsupported, please upgrade.\n' %
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00003114 sys.version.split(' ', 1)[0],
3115 file=sys.stderr)
Elly Fong-Jones7b294392019-04-18 18:32:10 +00003116 return False
bcwhite@chromium.org6683ab42013-02-11 16:13:47 +00003117 if not sys.executable:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00003118 print(
3119 '\nPython cannot find the location of it\'s own executable.\n',
3120 file=sys.stderr)
Elly Fong-Jones7b294392019-04-18 18:32:10 +00003121 return False
3122 if path_contains_tilde():
3123 print(
3124 '\nYour PATH contains a literal "~", which works in some shells ' +
3125 'but will break when python tries to run subprocesses. ' +
3126 'Replace the "~" with $HOME.\n' +
3127 'See https://crbug.com/952865.\n',
3128 file=sys.stderr)
3129 return False
3130 return True
3131
3132
3133def main(argv):
3134 """Doesn't parse the arguments here, just find the right subcommand to
3135 execute."""
3136 if not can_run_gclient_and_helpers():
bcwhite@chromium.org6683ab42013-02-11 16:13:47 +00003137 return 2
maruel@chromium.org39c0b222013-08-17 16:57:01 +00003138 fix_encoding.fix_encoding()
3139 disable_buffering()
iannucci@chromium.org596cd5c2016-04-04 21:34:39 +00003140 setup_color.init()
maruel@chromium.org39c0b222013-08-17 16:57:01 +00003141 dispatcher = subcommand.CommandDispatcher(__name__)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00003142 try:
maruel@chromium.org39c0b222013-08-17 16:57:01 +00003143 return dispatcher.execute(OptionParser(), argv)
xusydoc@chromium.org2fd6c3f2013-05-03 21:57:55 +00003144 except KeyboardInterrupt:
3145 gclient_utils.GClientChildren.KillAllRemainingChildren()
3146 raise
vapier@chromium.orga81a56e2015-11-11 07:56:13 +00003147 except (gclient_utils.Error, subprocess2.CalledProcessError) as e:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00003148 print('Error: %s' % str(e), file=sys.stderr)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00003149 return 1
borenet@google.com6a9b1682014-03-24 18:35:23 +00003150 finally:
3151 gclient_utils.PrintWarnings()
sbc@chromium.org013731e2015-02-26 18:28:43 +00003152 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00003153
3154
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00003155if '__main__' == __name__:
Edward Lemur6f812e12018-07-31 22:45:57 +00003156 with metrics.collector.print_notice_and_exit():
sbc@chromium.org013731e2015-02-26 18:28:43 +00003157 sys.exit(main(sys.argv[1:]))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00003158
3159# vim: ts=2:sw=2:tw=80:et: