blob: ba75b782046e0b81399504cf64ddbadb76c9e16a [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
Quinten Yearsley925cedb2020-04-13 17:49:39 +000081# are available and are analogous 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
Aaron Gableac9b0f32019-04-18 17:38:37 +0000121# TODO(crbug.com/953884): Remove this when python3 migration is done.
Edward Lemuree7b9dd2019-07-20 01:29:08 +0000122if six.PY3:
Aaron Gableac9b0f32019-04-18 17:38:37 +0000123 # pylint: disable=redefined-builtin
124 basestring = str
125
126
Henrique Ferreiro4ef32212019-04-29 23:32:31 +0000127DEPOT_TOOLS_DIR = os.path.dirname(os.path.abspath(os.path.realpath(__file__)))
128
Robert Iannuccia19649b2018-06-29 16:31:45 +0000129# Singleton object to represent an unset cache_dir (as opposed to a disabled
130# one, e.g. if a spec explicitly says `cache_dir = None`.)
131UNSET_CACHE_DIR = object()
132
133
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +0200134class GNException(Exception):
135 pass
136
137
138def ToGNString(value, allow_dicts = True):
139 """Returns a stringified GN equivalent of the Python value.
140
141 allow_dicts indicates if this function will allow converting dictionaries
142 to GN scopes. This is only possible at the top level, you can't nest a
143 GN scope in a list, so this should be set to False for recursive calls."""
Aaron Gableac9b0f32019-04-18 17:38:37 +0000144 if isinstance(value, basestring):
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +0200145 if value.find('\n') >= 0:
146 raise GNException("Trying to print a string with a newline in it.")
147 return '"' + \
148 value.replace('\\', '\\\\').replace('"', '\\"').replace('$', '\\$') + \
149 '"'
150
Raul Tambreb946b232019-03-26 14:48:46 +0000151 if sys.version_info.major == 2 and isinstance(value, unicode):
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +0200152 return ToGNString(value.encode('utf-8'))
153
154 if isinstance(value, bool):
155 if value:
156 return "true"
157 return "false"
158
159 # NOTE: some type handling removed compared to chromium/src copy.
160
161 raise GNException("Unsupported type when printing to GN.")
162
163
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200164class Hook(object):
165 """Descriptor of command ran before/after sync or on demand."""
166
Paweł Hajdan, Jr032d5452017-06-22 20:43:53 +0200167 def __init__(self, action, pattern=None, name=None, cwd=None, condition=None,
Corentin Walleza68660d2018-09-10 17:33:24 +0000168 variables=None, verbose=False, cwd_base=None):
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200169 """Constructor.
170
171 Arguments:
172 action (list of basestring): argv of the command to run
173 pattern (basestring regex): noop with git; deprecated
174 name (basestring): optional name; no effect on operation
Paweł Hajdan, Jrc9364392017-06-14 17:11:56 +0200175 cwd (basestring): working directory to use
Paweł Hajdan, Jr032d5452017-06-22 20:43:53 +0200176 condition (basestring): condition when to run the hook
177 variables (dict): variables for evaluating the condition
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200178 """
179 self._action = gclient_utils.freeze(action)
180 self._pattern = pattern
181 self._name = name
Paweł Hajdan, Jrc9364392017-06-14 17:11:56 +0200182 self._cwd = cwd
Paweł Hajdan, Jr032d5452017-06-22 20:43:53 +0200183 self._condition = condition
184 self._variables = variables
Daniel Chenga0c5f082017-10-19 13:35:19 -0700185 self._verbose = verbose
Corentin Walleza68660d2018-09-10 17:33:24 +0000186 self._cwd_base = cwd_base
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200187
188 @staticmethod
Corentin Walleza68660d2018-09-10 17:33:24 +0000189 def from_dict(d, variables=None, verbose=False, conditions=None,
190 cwd_base=None):
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200191 """Creates a Hook instance from a dict like in the DEPS file."""
Michael Moss42d02c22018-02-05 10:32:24 -0800192 # Merge any local and inherited conditions.
Edward Lemur16f4bad2018-05-16 16:53:49 -0400193 gclient_eval.UpdateCondition(d, 'and', conditions)
Paweł Hajdan, Jr032d5452017-06-22 20:43:53 +0200194 return Hook(
195 d['action'],
196 d.get('pattern'),
197 d.get('name'),
198 d.get('cwd'),
Edward Lemur16f4bad2018-05-16 16:53:49 -0400199 d.get('condition'),
Daniel Chenga0c5f082017-10-19 13:35:19 -0700200 variables=variables,
201 # Always print the header if not printing to a TTY.
Corentin Walleza68660d2018-09-10 17:33:24 +0000202 verbose=verbose or not setup_color.IS_TTY,
203 cwd_base=cwd_base)
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200204
205 @property
206 def action(self):
207 return self._action
208
209 @property
210 def pattern(self):
211 return self._pattern
212
213 @property
214 def name(self):
215 return self._name
216
Paweł Hajdan, Jrecf53fe2017-09-29 18:28:49 +0200217 @property
218 def condition(self):
219 return self._condition
220
Corentin Walleza68660d2018-09-10 17:33:24 +0000221 @property
222 def effective_cwd(self):
223 cwd = self._cwd_base
224 if self._cwd:
225 cwd = os.path.join(cwd, self._cwd)
226 return cwd
227
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200228 def matches(self, file_list):
229 """Returns true if the pattern matches any of files in the list."""
230 if not self._pattern:
231 return True
232 pattern = re.compile(self._pattern)
233 return bool([f for f in file_list if pattern.search(f)])
234
Corentin Walleza68660d2018-09-10 17:33:24 +0000235 def run(self):
Paweł Hajdan, Jr032d5452017-06-22 20:43:53 +0200236 """Executes the hook's command (provided the condition is met)."""
237 if (self._condition and
238 not gclient_eval.EvaluateCondition(self._condition, self._variables)):
239 return
240
Edward Lemure05f18d2018-06-08 17:36:53 +0000241 cmd = [arg for arg in self._action]
Paweł Hajdan, Jrfc6196b2017-07-27 13:15:25 +0200242
Edward Lemurca879322019-09-09 20:18:13 +0000243 if cmd[0] == 'python':
Edward Lemur6f18e682019-09-23 21:02:35 +0000244 cmd[0] = 'vpython'
Edward Lemurca879322019-09-09 20:18:13 +0000245 if cmd[0] == 'vpython' and _detect_host_os() == 'win':
Nodir Turakulov0ffcc872017-11-09 16:44:58 -0800246 cmd[0] += '.bat'
Paweł Hajdan, Jrc9364392017-06-14 17:11:56 +0200247
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200248 try:
249 start_time = time.time()
Edward Lemur24146be2019-08-01 21:44:52 +0000250 gclient_utils.CheckCallAndFilter(
251 cmd, cwd=self.effective_cwd, print_stdout=True, show_header=True,
252 always_show_header=self._verbose)
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200253 except (gclient_utils.Error, subprocess2.CalledProcessError) as e:
254 # Use a discrete exit status code of 2 to indicate that a hook action
255 # failed. Users of this script may wish to treat hook action failures
256 # differently from VC failures.
257 print('Error: %s' % str(e), file=sys.stderr)
258 sys.exit(2)
259 finally:
260 elapsed_time = time.time() - start_time
261 if elapsed_time > 10:
262 print("Hook '%s' took %.2f secs" % (
263 gclient_utils.CommandToStr(cmd), elapsed_time))
264
265
Paweł Hajdan, Jrfc6196b2017-07-27 13:15:25 +0200266class DependencySettings(object):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000267 """Immutable configuration settings."""
268 def __init__(
Edward Lemure05f18d2018-06-08 17:36:53 +0000269 self, parent, url, managed, custom_deps, custom_vars,
Michael Mossd683d7c2018-06-15 05:05:17 +0000270 custom_hooks, deps_file, should_process, relative, condition):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000271 # These are not mutable:
272 self._parent = parent
mmoss@chromium.org8f93f792014-08-26 23:24:09 +0000273 self._deps_file = deps_file
maruel@chromium.org064186c2011-09-27 23:53:33 +0000274 self._url = url
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +0200275 # The condition as string (or None). Useful to keep e.g. for flatten.
276 self._condition = condition
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000277 # 'managed' determines whether or not this dependency is synced/updated by
Michael Mossd683d7c2018-06-15 05:05:17 +0000278 # gclient after gclient checks it out initially. The difference between
279 # 'managed' and 'should_process' is that the user specifies 'managed' via
280 # the --unmanaged command-line flag or a .gclient config, where
281 # 'should_process' is dynamically set by gclient if it goes over its
282 # recursion limit and controls gclient's behavior so it does not misbehave.
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000283 self._managed = managed
Michael Mossd683d7c2018-06-15 05:05:17 +0000284 self._should_process = should_process
agabledce6ddc2016-09-08 10:02:16 -0700285 # If this is a recursed-upon sub-dependency, and the parent has
286 # use_relative_paths set, then this dependency should check out its own
287 # dependencies relative to that parent's path for this, rather than
288 # relative to the .gclient file.
289 self._relative = relative
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +0000290 # This is a mutable value which has the list of 'target_os' OSes listed in
291 # the current deps file.
292 self.local_target_os = None
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000293
294 # These are only set in .gclient and not in DEPS files.
295 self._custom_vars = custom_vars or {}
296 self._custom_deps = custom_deps or {}
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000297 self._custom_hooks = custom_hooks or []
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000298
Michael Mossd683d7c2018-06-15 05:05:17 +0000299 # Post process the url to remove trailing slashes.
Aaron Gableac9b0f32019-04-18 17:38:37 +0000300 if isinstance(self.url, basestring):
Michael Moss4e9b50a2018-05-23 22:35:06 -0700301 # urls are sometime incorrectly written as proto://host/path/@rev. Replace
302 # it to proto://host/path@rev.
303 self.set_url(self.url.replace('/@', '@'))
Michael Mossd683d7c2018-06-15 05:05:17 +0000304 elif not isinstance(self.url, (None.__class__)):
305 raise gclient_utils.Error(
306 ('dependency url must be either string or None, '
307 'instead of %s') % self.url.__class__.__name__)
Edward Lemure7273d22018-05-10 19:13:51 -0400308
mmoss@chromium.orgd0b272b2013-01-30 23:55:33 +0000309 # Make any deps_file path platform-appropriate.
John Budorick0f7b2002018-01-19 15:46:17 -0800310 if self._deps_file:
311 for sep in ['/', '\\']:
312 self._deps_file = self._deps_file.replace(sep, os.sep)
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000313
314 @property
315 def deps_file(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000316 return self._deps_file
317
318 @property
319 def managed(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000320 return self._managed
321
322 @property
323 def parent(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000324 return self._parent
325
326 @property
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000327 def root(self):
328 """Returns the root node, a GClient object."""
329 if not self.parent:
330 # This line is to signal pylint that it could be a GClient instance.
331 return self or GClient(None, None)
332 return self.parent.root
333
334 @property
Michael Mossd683d7c2018-06-15 05:05:17 +0000335 def should_process(self):
336 """True if this dependency should be processed, i.e. checked out."""
337 return self._should_process
338
339 @property
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000340 def custom_vars(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000341 return self._custom_vars.copy()
342
343 @property
344 def custom_deps(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000345 return self._custom_deps.copy()
346
maruel@chromium.org064186c2011-09-27 23:53:33 +0000347 @property
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000348 def custom_hooks(self):
349 return self._custom_hooks[:]
350
351 @property
maruel@chromium.org064186c2011-09-27 23:53:33 +0000352 def url(self):
Paweł Hajdan, Jrfc6196b2017-07-27 13:15:25 +0200353 """URL after variable expansion."""
maruel@chromium.org064186c2011-09-27 23:53:33 +0000354 return self._url
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000355
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000356 @property
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +0200357 def condition(self):
358 return self._condition
359
360 @property
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +0000361 def target_os(self):
362 if self.local_target_os is not None:
363 return tuple(set(self.local_target_os).union(self.parent.target_os))
364 else:
365 return self.parent.target_os
366
Tom Andersonc31ae0b2018-02-06 14:48:56 -0800367 @property
368 def target_cpu(self):
369 return self.parent.target_cpu
370
Edward Lemure7273d22018-05-10 19:13:51 -0400371 def set_url(self, url):
372 self._url = url
373
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000374 def get_custom_deps(self, name, url):
375 """Returns a custom deps if applicable."""
376 if self.parent:
377 url = self.parent.get_custom_deps(name, url)
378 # None is a valid return value to disable a dependency.
379 return self.custom_deps.get(name, url)
380
maruel@chromium.org064186c2011-09-27 23:53:33 +0000381
382class Dependency(gclient_utils.WorkItem, DependencySettings):
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000383 """Object that represents a dependency checkout."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000384
Edward Lemure05f18d2018-06-08 17:36:53 +0000385 def __init__(self, parent, name, url, managed, custom_deps,
Michael Mossd683d7c2018-06-15 05:05:17 +0000386 custom_vars, custom_hooks, deps_file, should_process,
387 should_recurse, relative, condition, print_outbuf=False):
maruel@chromium.org6ca8bf82011-09-19 23:04:30 +0000388 gclient_utils.WorkItem.__init__(self, name)
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000389 DependencySettings.__init__(
Michael Mossd683d7c2018-06-15 05:05:17 +0000390 self, parent, url, managed, custom_deps, custom_vars,
391 custom_hooks, deps_file, should_process, relative, condition)
maruel@chromium.org68988972011-09-20 14:11:42 +0000392
393 # This is in both .gclient and DEPS files:
maruel@chromium.org064186c2011-09-27 23:53:33 +0000394 self._deps_hooks = []
maruel@chromium.org68988972011-09-20 14:11:42 +0000395
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000396 self._pre_deps_hooks = []
397
maruel@chromium.org68988972011-09-20 14:11:42 +0000398 # Calculates properties:
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +0000399 self._dependencies = []
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +0200400 self._vars = {}
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200401
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000402 # A cache of the files affected by the current operation, necessary for
403 # hooks.
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000404 self._file_list = []
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000405 # List of host names from which dependencies are allowed.
406 # Default is an empty set, meaning unspecified in DEPS file, and hence all
407 # hosts will be allowed. Non-empty set means whitelist of hosts.
408 # allowed_hosts var is scoped to its DEPS file, and so it isn't recursive.
409 self._allowed_hosts = frozenset()
Michael Moss848c86e2018-05-03 16:05:50 -0700410 self._gn_args_from = None
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +0200411 # Spec for .gni output to write (if any).
412 self._gn_args_file = None
413 self._gn_args = []
maruel@chromium.org85c2a192010-07-22 21:14:43 +0000414 # If it is not set to True, the dependency wasn't processed for its child
415 # dependency, i.e. its DEPS wasn't read.
maruel@chromium.org064186c2011-09-27 23:53:33 +0000416 self._deps_parsed = False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000417 # This dependency has been processed, i.e. checked out
maruel@chromium.org064186c2011-09-27 23:53:33 +0000418 self._processed = False
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000419 # This dependency had its pre-DEPS hooks run
420 self._pre_deps_hooks_ran = False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000421 # This dependency had its hook run
maruel@chromium.org064186c2011-09-27 23:53:33 +0000422 self._hooks_ran = False
kustermann@google.coma692e8f2013-04-18 08:32:04 +0000423 # This is the scm used to checkout self.url. It may be used by dependencies
424 # to get the datetime of the revision we checked out.
425 self._used_scm = None
szager@chromium.org4ad264b2014-05-20 04:43:47 +0000426 self._used_revision = None
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +0000427 # The actual revision we ended up getting, or None if that information is
428 # unavailable
429 self._got_revision = None
Corentin Wallez271a78a2020-07-12 15:41:46 +0000430 # Whether this dependency should use relative paths.
431 self._use_relative_paths = False
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000432
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000433 # recursedeps is a mutable value that selectively overrides the default
Edward Lemurfbb06aa2018-06-11 20:43:06 +0000434 # 'no recursion' setting on a dep-by-dep basis.
iannucci@chromium.orgafa11ac2016-05-04 22:17:34 +0000435 #
Edward Lemurfbb06aa2018-06-11 20:43:06 +0000436 # It will be a dictionary of {deps_name: depfile_namee}
437 self.recursedeps = {}
438
439 # Whether we should process this dependency's DEPS file.
440 self._should_recurse = should_recurse
Edward Lemure7273d22018-05-10 19:13:51 -0400441
Michael Mossd683d7c2018-06-15 05:05:17 +0000442 self._OverrideUrl()
443 # This is inherited from WorkItem. We want the URL to be a resource.
Aaron Gableac9b0f32019-04-18 17:38:37 +0000444 if self.url and isinstance(self.url, basestring):
Michael Moss4e9b50a2018-05-23 22:35:06 -0700445 # The url is usually given to gclient either as https://blah@123
446 # or just https://blah. The @123 portion is irrelevant.
447 self.resources.append(self.url.split('@')[0])
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000448
Edward Lemur231f5ea2018-01-31 19:02:36 +0100449 # Controls whether we want to print git's output when we first clone the
450 # dependency
451 self.print_outbuf = print_outbuf
452
Michael Mossd683d7c2018-06-15 05:05:17 +0000453 if not self.name and self.parent:
454 raise gclient_utils.Error('Dependency without name')
455
456 def _OverrideUrl(self):
457 """Resolves the parsed url from the parent hierarchy."""
458 parsed_url = self.get_custom_deps(self._name, self.url)
459 if parsed_url != self.url:
460 logging.info('Dependency(%s)._OverrideUrl(%s) -> %s', self._name,
461 self.url, parsed_url)
462 self.set_url(parsed_url)
Edward Lemur1f392b82019-11-15 22:40:11 +0000463 return
Michael Mossd683d7c2018-06-15 05:05:17 +0000464
Edward Lemur1f392b82019-11-15 22:40:11 +0000465 if self.url is None:
Michael Mossd683d7c2018-06-15 05:05:17 +0000466 logging.info('Dependency(%s)._OverrideUrl(None) -> None', self._name)
Edward Lemur1f392b82019-11-15 22:40:11 +0000467 return
Michael Mossd683d7c2018-06-15 05:05:17 +0000468
Edward Lemur1f392b82019-11-15 22:40:11 +0000469 if not isinstance(self.url, basestring):
Michael Mossd683d7c2018-06-15 05:05:17 +0000470 raise gclient_utils.Error('Unknown url type')
471
Edward Lemur1f392b82019-11-15 22:40:11 +0000472 # self.url is a local path
473 path, at, rev = self.url.partition('@')
474 if os.path.isdir(path):
475 return
476
477 # self.url is a URL
478 parsed_url = urlparse.urlparse(self.url)
479 if parsed_url[0] or re.match(r'^\w+\@[\w\.-]+\:[\w\/]+', parsed_url[2]):
480 return
481
482 # self.url is relative to the parent's URL.
483 if not path.startswith('/'):
484 raise gclient_utils.Error(
485 'relative DEPS entry \'%s\' must begin with a slash' % self.url)
486
487 parent_url = self.parent.url
488 parent_path = self.parent.url.split('@')[0]
489 if os.path.isdir(parent_path):
490 # Parent's URL is a local path. Get parent's URL dirname and append
491 # self.url.
492 parent_path = os.path.dirname(parent_path)
493 parsed_url = parent_path + path.replace('/', os.sep) + at + rev
494 else:
495 # Parent's URL is a URL. Get parent's URL, strip from the last '/'
496 # (equivalent to unix dirname) and append self.url.
497 parsed_url = parent_url[:parent_url.rfind('/')] + self.url
498
499 logging.info('Dependency(%s)._OverrideUrl(%s) -> %s', self.name,
500 self.url, parsed_url)
501 self.set_url(parsed_url)
502
Edward Lemure7273d22018-05-10 19:13:51 -0400503 def PinToActualRevision(self):
Edward Lemure05f18d2018-06-08 17:36:53 +0000504 """Updates self.url to the revision checked out on disk."""
Michael Mossd683d7c2018-06-15 05:05:17 +0000505 if self.url is None:
506 return
Edward Lemure05f18d2018-06-08 17:36:53 +0000507 url = None
Edward Lemurbabd0982018-05-11 13:32:37 -0400508 scm = self.CreateSCM()
Edward Lemure7273d22018-05-10 19:13:51 -0400509 if os.path.isdir(scm.checkout_path):
510 revision = scm.revinfo(None, None, None)
511 url = '%s@%s' % (gclient_utils.SplitUrlRevision(self.url)[0], revision)
Edward Lemure7273d22018-05-10 19:13:51 -0400512 self.set_url(url)
Edward Lemure7273d22018-05-10 19:13:51 -0400513
John Budorick0f7b2002018-01-19 15:46:17 -0800514 def ToLines(self):
515 s = []
516 condition_part = ([' "condition": %r,' % self.condition]
517 if self.condition else [])
518 s.extend([
Michael Moss4e9b50a2018-05-23 22:35:06 -0700519 ' # %s' % self.hierarchy(include_url=False),
John Budorick0f7b2002018-01-19 15:46:17 -0800520 ' "%s": {' % (self.name,),
Edward Lemure05f18d2018-06-08 17:36:53 +0000521 ' "url": "%s",' % (self.url,),
John Budorick0f7b2002018-01-19 15:46:17 -0800522 ] + condition_part + [
523 ' },',
524 '',
525 ])
526 return s
527
maruel@chromium.org470b5432011-10-11 18:18:19 +0000528 @property
529 def requirements(self):
530 """Calculate the list of requirements."""
531 requirements = set()
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000532 # self.parent is implicitly a requirement. This will be recursive by
533 # definition.
534 if self.parent and self.parent.name:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000535 requirements.add(self.parent.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000536
537 # For a tree with at least 2 levels*, the leaf node needs to depend
538 # on the level higher up in an orderly way.
539 # This becomes messy for >2 depth as the DEPS file format is a dictionary,
540 # thus unsorted, while the .gclient format is a list thus sorted.
541 #
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000542 # Interestingly enough, the following condition only works in the case we
Quinten Yearsley925cedb2020-04-13 17:49:39 +0000543 # want: self is a 2nd level node. 3rd level node wouldn't need this since
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000544 # they already have their parent as a requirement.
maruel@chromium.org470b5432011-10-11 18:18:19 +0000545 if self.parent and self.parent.parent and not self.parent.parent.parent:
546 requirements |= set(i.name for i in self.root.dependencies if i.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000547
maruel@chromium.org470b5432011-10-11 18:18:19 +0000548 if self.name:
549 requirements |= set(
Michael Mossd683d7c2018-06-15 05:05:17 +0000550 obj.name for obj in self.root.subtree(False)
maruel@chromium.org470b5432011-10-11 18:18:19 +0000551 if (obj is not self
552 and obj.name and
553 self.name.startswith(posixpath.join(obj.name, ''))))
554 requirements = tuple(sorted(requirements))
555 logging.info('Dependency(%s).requirements = %s' % (self.name, requirements))
556 return requirements
557
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000558 @property
Edward Lemurfbb06aa2018-06-11 20:43:06 +0000559 def should_recurse(self):
560 return self._should_recurse
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000561
maruel@chromium.org470b5432011-10-11 18:18:19 +0000562 def verify_validity(self):
563 """Verifies that this Dependency is fine to add as a child of another one.
564
565 Returns True if this entry should be added, False if it is a duplicate of
566 another entry.
567 """
568 logging.info('Dependency(%s).verify_validity()' % self.name)
569 if self.name in [s.name for s in self.parent.dependencies]:
570 raise gclient_utils.Error(
571 'The same name "%s" appears multiple times in the deps section' %
572 self.name)
Michael Mossd683d7c2018-06-15 05:05:17 +0000573 if not self.should_process:
574 # Return early, no need to set requirements.
Edward Lemur7ccf2f02018-06-26 20:41:56 +0000575 return not any(d.name == self.name for d in self.root.subtree(True))
maruel@chromium.org470b5432011-10-11 18:18:19 +0000576
577 # This require a full tree traversal with locks.
Michael Mossd683d7c2018-06-15 05:05:17 +0000578 siblings = [d for d in self.root.subtree(False) if d.name == self.name]
maruel@chromium.org470b5432011-10-11 18:18:19 +0000579 for sibling in siblings:
Michael Mossd683d7c2018-06-15 05:05:17 +0000580 # Allow to have only one to be None or ''.
581 if self.url != sibling.url and bool(self.url) == bool(sibling.url):
maruel@chromium.org470b5432011-10-11 18:18:19 +0000582 raise gclient_utils.Error(
maruel@chromium.orgb848d5b2012-10-10 23:25:50 +0000583 ('Dependency %s specified more than once:\n'
584 ' %s [%s]\n'
585 'vs\n'
586 ' %s [%s]') % (
587 self.name,
588 sibling.hierarchy(),
Edward Lemure7273d22018-05-10 19:13:51 -0400589 sibling.url,
maruel@chromium.orgb848d5b2012-10-10 23:25:50 +0000590 self.hierarchy(),
Edward Lemure7273d22018-05-10 19:13:51 -0400591 self.url))
maruel@chromium.org470b5432011-10-11 18:18:19 +0000592 # In theory we could keep it as a shadow of the other one. In
593 # practice, simply ignore it.
John Budorickd94f8ea2020-03-27 15:55:24 +0000594 logging.warning("Won't process duplicate dependency %s" % sibling)
maruel@chromium.org470b5432011-10-11 18:18:19 +0000595 return False
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000596 return True
maruel@chromium.org064186c2011-09-27 23:53:33 +0000597
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200598 def _postprocess_deps(self, deps, rel_prefix):
599 """Performs post-processing of deps compared to what's in the DEPS file."""
Paweł Hajdan, Jr4426eaf2017-06-13 12:25:47 +0200600 # Make sure the dict is mutable, e.g. in case it's frozen.
601 deps = dict(deps)
602
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200603 # If a line is in custom_deps, but not in the solution, we want to append
604 # this line to the solution.
Marc-Antoine Ruel8e57b4b2019-10-11 01:01:36 +0000605 for dep_name, dep_info in self.custom_deps.items():
Edward Lemur23a35872018-05-17 01:57:06 -0400606 if dep_name not in deps:
607 deps[dep_name] = {'url': dep_info, 'dep_type': 'git'}
Edward Lemur16f4bad2018-05-16 16:53:49 -0400608
Michael Moss42d02c22018-02-05 10:32:24 -0800609 # Make child deps conditional on any parent conditions. This ensures that,
610 # when flattened, recursed entries have the correct restrictions, even if
611 # not explicitly set in the recursed DEPS file. For instance, if
612 # "src/ios_foo" is conditional on "checkout_ios=True", then anything
613 # recursively included by "src/ios_foo/DEPS" should also require
614 # "checkout_ios=True".
615 if self.condition:
Marc-Antoine Ruel8e57b4b2019-10-11 01:01:36 +0000616 for value in deps.values():
Edward Lemur16f4bad2018-05-16 16:53:49 -0400617 gclient_eval.UpdateCondition(value, 'and', self.condition)
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200618
619 if rel_prefix:
620 logging.warning('use_relative_paths enabled.')
621 rel_deps = {}
Marc-Antoine Ruel8e57b4b2019-10-11 01:01:36 +0000622 for d, url in deps.items():
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200623 # normpath is required to allow DEPS to use .. in their
624 # dependency local path.
625 rel_deps[os.path.normpath(os.path.join(rel_prefix, d))] = url
626 logging.warning('Updating deps by prepending %s.', rel_prefix)
627 deps = rel_deps
628
629 return deps
630
631 def _deps_to_objects(self, deps, use_relative_paths):
632 """Convert a deps dict to a dict of Dependency objects."""
633 deps_to_add = []
Marc-Antoine Ruel8e57b4b2019-10-11 01:01:36 +0000634 for name, dep_value in deps.items():
Michael Mossd683d7c2018-06-15 05:05:17 +0000635 should_process = self.should_process
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200636 if dep_value is None:
637 continue
John Budorick0f7b2002018-01-19 15:46:17 -0800638
Edward Lemur16f4bad2018-05-16 16:53:49 -0400639 condition = dep_value.get('condition')
Michael Mossd683d7c2018-06-15 05:05:17 +0000640 dep_type = dep_value.get('dep_type')
Paweł Hajdan, Jrfc6196b2017-07-27 13:15:25 +0200641
Michael Mossd683d7c2018-06-15 05:05:17 +0000642 if condition and not self._get_option('process_all_deps', False):
643 should_process = should_process and gclient_eval.EvaluateCondition(
Paweł Hajdan, Jrd3790252017-07-03 21:06:24 +0200644 condition, self.get_vars())
John Budorick0f7b2002018-01-19 15:46:17 -0800645
Joey Scarr8d3925b2018-07-15 23:36:25 +0000646 # The following option is only set by the 'revinfo' command.
647 if self._get_option('ignore_dep_type', None) == dep_type:
648 continue
649
John Budorick0f7b2002018-01-19 15:46:17 -0800650 if dep_type == 'cipd':
John Budorickd3ba72b2018-03-20 12:27:42 -0700651 cipd_root = self.GetCipdRoot()
John Budorick0f7b2002018-01-19 15:46:17 -0800652 for package in dep_value.get('packages', []):
653 deps_to_add.append(
654 CipdDependency(
Edward Lemure05f18d2018-06-08 17:36:53 +0000655 parent=self,
656 name=name,
657 dep_value=package,
658 cipd_root=cipd_root,
659 custom_vars=self.custom_vars,
Michael Mossd683d7c2018-06-15 05:05:17 +0000660 should_process=should_process,
Edward Lemure05f18d2018-06-08 17:36:53 +0000661 relative=use_relative_paths,
662 condition=condition))
John Budorick0f7b2002018-01-19 15:46:17 -0800663 else:
Michael Mossd683d7c2018-06-15 05:05:17 +0000664 url = dep_value.get('url')
665 deps_to_add.append(
666 GitDependency(
667 parent=self,
668 name=name,
669 url=url,
Edward Lemure4213702018-06-21 21:15:50 +0000670 managed=True,
Michael Mossd683d7c2018-06-15 05:05:17 +0000671 custom_deps=None,
672 custom_vars=self.custom_vars,
673 custom_hooks=None,
674 deps_file=self.recursedeps.get(name, self.deps_file),
675 should_process=should_process,
676 should_recurse=name in self.recursedeps,
677 relative=use_relative_paths,
678 condition=condition))
John Budorick0f7b2002018-01-19 15:46:17 -0800679
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200680 deps_to_add.sort(key=lambda x: x.name)
681 return deps_to_add
682
Edward Lemure05f18d2018-06-08 17:36:53 +0000683 def ParseDepsFile(self):
maruel@chromium.org271375b2010-06-23 19:17:38 +0000684 """Parses the DEPS file for this dependency."""
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000685 assert not self.deps_parsed
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000686 assert not self.dependencies
iannucci@chromium.org0c3f3052014-05-22 00:29:22 +0000687
688 deps_content = None
cmp@chromium.org76ce73c2014-07-02 00:13:18 +0000689
690 # First try to locate the configured deps file. If it's missing, fallback
691 # to DEPS.
692 deps_files = [self.deps_file]
693 if 'DEPS' not in deps_files:
694 deps_files.append('DEPS')
695 for deps_file in deps_files:
696 filepath = os.path.join(self.root.root_dir, self.name, deps_file)
697 if os.path.isfile(filepath):
698 logging.info(
699 'ParseDepsFile(%s): %s file found at %s', self.name, deps_file,
700 filepath)
701 break
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000702 logging.info(
cmp@chromium.org76ce73c2014-07-02 00:13:18 +0000703 'ParseDepsFile(%s): No %s file found at %s', self.name, deps_file,
704 filepath)
705
706 if os.path.isfile(filepath):
maruel@chromium.org46304292010-10-28 11:42:00 +0000707 deps_content = gclient_utils.FileRead(filepath)
cmp@chromium.org76ce73c2014-07-02 00:13:18 +0000708 logging.debug('ParseDepsFile(%s) read:\n%s', self.name, deps_content)
iannucci@chromium.org0c3f3052014-05-22 00:29:22 +0000709
710 local_scope = {}
711 if deps_content:
maruel@chromium.org46304292010-10-28 11:42:00 +0000712 try:
Edward Lesmes6c24d372018-03-28 12:52:29 -0400713 local_scope = gclient_eval.Parse(
Edward Lemur67cabcd2020-03-03 19:31:15 +0000714 deps_content, filepath, self.get_vars(), self.get_builtin_vars())
vapier@chromium.orga81a56e2015-11-11 07:56:13 +0000715 except SyntaxError as e:
maruel@chromium.org46304292010-10-28 11:42:00 +0000716 gclient_utils.SyntaxErrorToError(filepath, e)
iannucci@chromium.org0c3f3052014-05-22 00:29:22 +0000717
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000718 if 'allowed_hosts' in local_scope:
719 try:
720 self._allowed_hosts = frozenset(local_scope.get('allowed_hosts'))
721 except TypeError: # raised if non-iterable
722 pass
723 if not self._allowed_hosts:
724 logging.warning("allowed_hosts is specified but empty %s",
725 self._allowed_hosts)
726 raise gclient_utils.Error(
727 'ParseDepsFile(%s): allowed_hosts must be absent '
728 'or a non-empty iterable' % self.name)
729
Michael Moss848c86e2018-05-03 16:05:50 -0700730 self._gn_args_from = local_scope.get('gclient_gn_args_from')
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +0200731 self._gn_args_file = local_scope.get('gclient_gn_args_file')
732 self._gn_args = local_scope.get('gclient_gn_args', [])
Michael Moss848c86e2018-05-03 16:05:50 -0700733 # It doesn't make sense to set all of these, since setting gn_args_from to
734 # another DEPS will make gclient ignore any other local gn_args* settings.
735 assert not (self._gn_args_from and self._gn_args_file), \
736 'Only specify one of "gclient_gn_args_from" or ' \
737 '"gclient_gn_args_file + gclient_gn_args".'
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +0200738
Edward Lesmes0b899352018-03-19 21:59:55 +0000739 self._vars = local_scope.get('vars', {})
Paweł Hajdan, Jr1407d002017-08-01 20:01:01 +0200740 if self.parent:
Marc-Antoine Ruel8e57b4b2019-10-11 01:01:36 +0000741 for key, value in self.parent.get_vars().items():
Paweł Hajdan, Jr1407d002017-08-01 20:01:01 +0200742 if key in self._vars:
743 self._vars[key] = value
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200744 # Since we heavily post-process things, freeze ones which should
745 # reflect original state of DEPS.
Paweł Hajdan, Jr1407d002017-08-01 20:01:01 +0200746 self._vars = gclient_utils.freeze(self._vars)
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200747
748 # If use_relative_paths is set in the DEPS file, regenerate
749 # the dictionary using paths relative to the directory containing
750 # the DEPS file. Also update recursedeps if use_relative_paths is
751 # enabled.
752 # If the deps file doesn't set use_relative_paths, but the parent did
753 # (and therefore set self.relative on this Dependency object), then we
754 # want to modify the deps and recursedeps by prepending the parent
755 # directory of this dependency.
Corentin Wallez271a78a2020-07-12 15:41:46 +0000756 self._use_relative_paths = local_scope.get('use_relative_paths', False)
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200757 rel_prefix = None
Corentin Wallez271a78a2020-07-12 15:41:46 +0000758 if self._use_relative_paths:
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200759 rel_prefix = self.name
760 elif self._relative:
761 rel_prefix = os.path.dirname(self.name)
762
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200763 if 'recursion' in local_scope:
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200764 logging.warning(
Edward Lemurfbb06aa2018-06-11 20:43:06 +0000765 '%s: Ignoring recursion = %d.', self.name, local_scope['recursion'])
766
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200767 if 'recursedeps' in local_scope:
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200768 for ent in local_scope['recursedeps']:
Aaron Gableac9b0f32019-04-18 17:38:37 +0000769 if isinstance(ent, basestring):
Edward Lemurfbb06aa2018-06-11 20:43:06 +0000770 self.recursedeps[ent] = self.deps_file
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200771 else: # (depname, depsfilename)
Edward Lemurfbb06aa2018-06-11 20:43:06 +0000772 self.recursedeps[ent[0]] = ent[1]
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200773 logging.warning('Found recursedeps %r.', repr(self.recursedeps))
774
775 if rel_prefix:
776 logging.warning('Updating recursedeps by prepending %s.', rel_prefix)
777 rel_deps = {}
Marc-Antoine Ruel8e57b4b2019-10-11 01:01:36 +0000778 for depname, options in self.recursedeps.items():
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200779 rel_deps[
780 os.path.normpath(os.path.join(rel_prefix, depname))] = options
781 self.recursedeps = rel_deps
Michael Moss848c86e2018-05-03 16:05:50 -0700782 # To get gn_args from another DEPS, that DEPS must be recursed into.
783 if self._gn_args_from:
784 assert self.recursedeps and self._gn_args_from in self.recursedeps, \
785 'The "gclient_gn_args_from" value must be in recursedeps.'
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200786
787 # If present, save 'target_os' in the local_target_os property.
788 if 'target_os' in local_scope:
789 self.local_target_os = local_scope['target_os']
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200790
Edward Lemur16f4bad2018-05-16 16:53:49 -0400791 deps = local_scope.get('deps', {})
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200792 deps_to_add = self._deps_to_objects(
Corentin Wallez271a78a2020-07-12 15:41:46 +0000793 self._postprocess_deps(deps, rel_prefix), self._use_relative_paths)
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000794
Corentin Walleza68660d2018-09-10 17:33:24 +0000795 # compute which working directory should be used for hooks
Michael Spang0e99b9b2020-08-12 13:34:48 +0000796 if local_scope.get('use_relative_hooks', False):
797 print('use_relative_hooks is deprecated, please remove it from DEPS. ' +
798 '(it was merged in use_relative_paths)', file=sys.stderr)
799
Corentin Walleza68660d2018-09-10 17:33:24 +0000800 hooks_cwd = self.root.root_dir
Corentin Wallez801c2022020-07-20 20:11:09 +0000801 if self._use_relative_paths:
Corentin Walleza68660d2018-09-10 17:33:24 +0000802 hooks_cwd = os.path.join(hooks_cwd, self.name)
803 logging.warning('Updating hook base working directory to %s.',
804 hooks_cwd)
805
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000806 # override named sets of hooks by the custom hooks
807 hooks_to_run = []
808 hook_names_to_suppress = [c.get('name', '') for c in self.custom_hooks]
809 for hook in local_scope.get('hooks', []):
810 if hook.get('name', '') not in hook_names_to_suppress:
811 hooks_to_run.append(hook)
812
813 # add the replacements and any additions
814 for hook in self.custom_hooks:
815 if 'action' in hook:
816 hooks_to_run.append(hook)
817
Edward Lemurfbb06aa2018-06-11 20:43:06 +0000818 if self.should_recurse:
Paweł Hajdan, Jr032d5452017-06-22 20:43:53 +0200819 self._pre_deps_hooks = [
Michael Moss42d02c22018-02-05 10:32:24 -0800820 Hook.from_dict(hook, variables=self.get_vars(), verbose=True,
Corentin Walleza68660d2018-09-10 17:33:24 +0000821 conditions=self.condition, cwd_base=hooks_cwd)
Daniel Chenga0c5f082017-10-19 13:35:19 -0700822 for hook in local_scope.get('pre_deps_hooks', [])
823 ]
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000824
Corentin Walleza68660d2018-09-10 17:33:24 +0000825 self.add_dependencies_and_close(deps_to_add, hooks_to_run,
826 hooks_cwd=hooks_cwd)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000827 logging.info('ParseDepsFile(%s) done' % self.name)
828
Michael Mossd683d7c2018-06-15 05:05:17 +0000829 def _get_option(self, attr, default):
830 obj = self
831 while not hasattr(obj, '_options'):
832 obj = obj.parent
833 return getattr(obj._options, attr, default)
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +0200834
Corentin Walleza68660d2018-09-10 17:33:24 +0000835 def add_dependencies_and_close(self, deps_to_add, hooks, hooks_cwd=None):
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000836 """Adds the dependencies, hooks and mark the parsing as done."""
Corentin Walleza68660d2018-09-10 17:33:24 +0000837 if hooks_cwd == None:
838 hooks_cwd = self.root.root_dir
839
maruel@chromium.orgb9be0652011-10-14 18:05:40 +0000840 for dep in deps_to_add:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000841 if dep.verify_validity():
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000842 self.add_dependency(dep)
Daniel Chenga0c5f082017-10-19 13:35:19 -0700843 self._mark_as_parsed([
844 Hook.from_dict(
Michael Moss42d02c22018-02-05 10:32:24 -0800845 h, variables=self.get_vars(), verbose=self.root._options.verbose,
Corentin Walleza68660d2018-09-10 17:33:24 +0000846 conditions=self.condition, cwd_base=hooks_cwd)
Daniel Chenga0c5f082017-10-19 13:35:19 -0700847 for h in hooks
848 ])
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000849
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000850 def findDepsFromNotAllowedHosts(self):
Corentin Wallezaca984c2018-09-07 21:52:14 +0000851 """Returns a list of dependencies from not allowed hosts.
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000852
853 If allowed_hosts is not set, allows all hosts and returns empty list.
854 """
855 if not self._allowed_hosts:
856 return []
857 bad_deps = []
858 for dep in self._dependencies:
szager@chromium.orgbd772dd2014-11-05 18:43:08 +0000859 # Don't enforce this for custom_deps.
860 if dep.name in self._custom_deps:
861 continue
Michael Mossd683d7c2018-06-15 05:05:17 +0000862 if isinstance(dep.url, basestring):
863 parsed_url = urlparse.urlparse(dep.url)
864 if parsed_url.netloc and parsed_url.netloc not in self._allowed_hosts:
865 bad_deps.append(dep)
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000866 return bad_deps
867
Edward Lemure7273d22018-05-10 19:13:51 -0400868 def FuzzyMatchUrl(self, candidates):
Edward Lesmesbb16e332018-03-30 17:54:51 -0400869 """Attempts to find this dependency in the list of candidates.
870
Edward Lemure7273d22018-05-10 19:13:51 -0400871 It looks first for the URL of this dependency in the list of
Edward Lesmesbb16e332018-03-30 17:54:51 -0400872 candidates. If it doesn't succeed, and the URL ends in '.git', it will try
873 looking for the URL minus '.git'. Finally it will try to look for the name
874 of the dependency.
875
876 Args:
Edward Lesmesbb16e332018-03-30 17:54:51 -0400877 candidates: list, dict. The list of candidates in which to look for this
878 dependency. It can contain URLs as above, or dependency names like
879 "src/some/dep".
880
881 Returns:
882 If this dependency is not found in the list of candidates, returns None.
883 Otherwise, it returns under which name did we find this dependency:
884 - Its parsed url: "https://example.com/src.git'
885 - Its parsed url minus '.git': "https://example.com/src"
886 - Its name: "src"
887 """
Edward Lemure7273d22018-05-10 19:13:51 -0400888 if self.url:
889 origin, _ = gclient_utils.SplitUrlRevision(self.url)
Edward Lesmesbb16e332018-03-30 17:54:51 -0400890 if origin in candidates:
891 return origin
892 if origin.endswith('.git') and origin[:-len('.git')] in candidates:
893 return origin[:-len('.git')]
Edward Lesmes990148e2018-04-26 14:56:55 -0400894 if origin + '.git' in candidates:
895 return origin + '.git'
Edward Lesmesbb16e332018-03-30 17:54:51 -0400896 if self.name in candidates:
897 return self.name
898 return None
899
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +0000900 # Arguments number differs from overridden method
Quinten Yearsleyb2cc4a92016-12-15 13:53:26 -0800901 # pylint: disable=arguments-differ
Edward Lesmesc621b212018-03-21 20:26:56 -0400902 def run(self, revision_overrides, command, args, work_queue, options,
Edward Lemur6a4e31b2018-08-10 19:59:02 +0000903 patch_refs, target_branches):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000904 """Runs |command| then parse the DEPS file."""
maruel@chromium.org470b5432011-10-11 18:18:19 +0000905 logging.info('Dependency(%s).run()' % self.name)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000906 assert self._file_list == []
Michael Mossd683d7c2018-06-15 05:05:17 +0000907 if not self.should_process:
908 return
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000909 # When running runhooks, there's no need to consult the SCM.
910 # All known hooks are expected to run unconditionally regardless of working
911 # copy state, so skip the SCM status check.
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +0200912 run_scm = command not in (
913 'flatten', 'runhooks', 'recurse', 'validate', None)
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000914 file_list = [] if not options.nohooks else None
Edward Lesmesbb16e332018-03-30 17:54:51 -0400915 revision_override = revision_overrides.pop(
Edward Lemure7273d22018-05-10 19:13:51 -0400916 self.FuzzyMatchUrl(revision_overrides), None)
Edward Lemure4213702018-06-21 21:15:50 +0000917 if not revision_override and not self.managed:
918 revision_override = 'unmanaged'
Michael Mossd683d7c2018-06-15 05:05:17 +0000919 if run_scm and self.url:
agabled437d762016-10-17 09:35:11 -0700920 # Create a shallow copy to mutate revision.
921 options = copy.copy(options)
922 options.revision = revision_override
923 self._used_revision = options.revision
Edward Lemurbabd0982018-05-11 13:32:37 -0400924 self._used_scm = self.CreateSCM(out_cb=work_queue.out_cb)
agabled437d762016-10-17 09:35:11 -0700925 self._got_revision = self._used_scm.RunCommand(command, options, args,
926 file_list)
Edward Lesmesc621b212018-03-21 20:26:56 -0400927
Edward Lemure7273d22018-05-10 19:13:51 -0400928 patch_repo = self.url.split('@')[0]
929 patch_ref = patch_refs.pop(self.FuzzyMatchUrl(patch_refs), None)
Edward Lemur6a4e31b2018-08-10 19:59:02 +0000930 target_branch = target_branches.pop(
931 self.FuzzyMatchUrl(target_branches), None)
Edward Lesmesc621b212018-03-21 20:26:56 -0400932 if command == 'update' and patch_ref is not None:
Edward Lemur6a4e31b2018-08-10 19:59:02 +0000933 self._used_scm.apply_patch_ref(patch_repo, patch_ref, target_branch,
934 options, file_list)
Edward Lesmesc621b212018-03-21 20:26:56 -0400935
agabled437d762016-10-17 09:35:11 -0700936 if file_list:
937 file_list = [os.path.join(self.name, f.strip()) for f in file_list]
maruel@chromium.org68988972011-09-20 14:11:42 +0000938
939 # TODO(phajdan.jr): We should know exactly when the paths are absolute.
940 # Convert all absolute paths to relative.
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000941 for i in range(len(file_list or [])):
maruel@chromium.org68988972011-09-20 14:11:42 +0000942 # It depends on the command being executed (like runhooks vs sync).
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000943 if not os.path.isabs(file_list[i]):
maruel@chromium.org68988972011-09-20 14:11:42 +0000944 continue
945 prefix = os.path.commonprefix(
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000946 [self.root.root_dir.lower(), file_list[i].lower()])
947 file_list[i] = file_list[i][len(prefix):]
maruel@chromium.org68988972011-09-20 14:11:42 +0000948 # Strip any leading path separators.
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000949 while file_list[i].startswith(('\\', '/')):
950 file_list[i] = file_list[i][1:]
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000951
Edward Lemurfbb06aa2018-06-11 20:43:06 +0000952 if self.should_recurse:
Edward Lemure05f18d2018-06-08 17:36:53 +0000953 self.ParseDepsFile()
Edward Lesmes5d6cde32018-04-12 18:32:46 -0400954
Edward Lemure7273d22018-05-10 19:13:51 -0400955 self._run_is_done(file_list or [])
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000956
Edward Lemurfbb06aa2018-06-11 20:43:06 +0000957 if self.should_recurse:
Edward Lesmes5d6cde32018-04-12 18:32:46 -0400958 if command in ('update', 'revert') and not options.noprehooks:
959 self.RunPreDepsHooks()
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000960 # Parse the dependencies of this dependency.
961 for s in self.dependencies:
Michael Mossd683d7c2018-06-15 05:05:17 +0000962 if s.should_process:
963 work_queue.enqueue(s)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000964
965 if command == 'recurse':
agabled437d762016-10-17 09:35:11 -0700966 # Skip file only checkout.
Edward Lemurbabd0982018-05-11 13:32:37 -0400967 scm = self.GetScmName()
agabled437d762016-10-17 09:35:11 -0700968 if not options.scm or scm in options.scm:
969 cwd = os.path.normpath(os.path.join(self.root.root_dir, self.name))
970 # Pass in the SCM type as an env variable. Make sure we don't put
971 # unicode strings in the environment.
972 env = os.environ.copy()
Michael Mossd683d7c2018-06-15 05:05:17 +0000973 if scm:
974 env['GCLIENT_SCM'] = str(scm)
975 if self.url:
976 env['GCLIENT_URL'] = str(self.url)
agabled437d762016-10-17 09:35:11 -0700977 env['GCLIENT_DEP_PATH'] = str(self.name)
978 if options.prepend_dir and scm == 'git':
979 print_stdout = False
980 def filter_fn(line):
981 """Git-specific path marshaling. It is optimized for git-grep."""
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000982
agabled437d762016-10-17 09:35:11 -0700983 def mod_path(git_pathspec):
984 match = re.match('^(\\S+?:)?([^\0]+)$', git_pathspec)
985 modified_path = os.path.join(self.name, match.group(2))
986 branch = match.group(1) or ''
987 return '%s%s' % (branch, modified_path)
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000988
agabled437d762016-10-17 09:35:11 -0700989 match = re.match('^Binary file ([^\0]+) matches$', line)
990 if match:
991 print('Binary file %s matches\n' % mod_path(match.group(1)))
992 return
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000993
agabled437d762016-10-17 09:35:11 -0700994 items = line.split('\0')
995 if len(items) == 2 and items[1]:
996 print('%s : %s' % (mod_path(items[0]), items[1]))
997 elif len(items) >= 2:
998 # Multiple null bytes or a single trailing null byte indicate
999 # git is likely displaying filenames only (such as with -l)
1000 print('\n'.join(mod_path(path) for path in items if path))
1001 else:
1002 print(line)
1003 else:
1004 print_stdout = True
1005 filter_fn = None
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001006
Michael Mossd683d7c2018-06-15 05:05:17 +00001007 if self.url is None:
1008 print('Skipped omitted dependency %s' % cwd, file=sys.stderr)
1009 elif os.path.isdir(cwd):
agabled437d762016-10-17 09:35:11 -07001010 try:
1011 gclient_utils.CheckCallAndFilter(
Ben Masonfbd2c632020-06-22 14:59:13 +00001012 args, cwd=cwd, env=env, print_stdout=print_stdout,
agabled437d762016-10-17 09:35:11 -07001013 filter_fn=filter_fn,
Ben Masonfbd2c632020-06-22 14:59:13 +00001014 )
agabled437d762016-10-17 09:35:11 -07001015 except subprocess2.CalledProcessError:
1016 if not options.ignore:
1017 raise
1018 else:
1019 print('Skipped missing %s' % cwd, file=sys.stderr)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001020
Edward Lemurbabd0982018-05-11 13:32:37 -04001021 def GetScmName(self):
Edward Lemurb61d3872018-05-09 18:42:47 -04001022 raise NotImplementedError()
John Budorick0f7b2002018-01-19 15:46:17 -08001023
Edward Lemurbabd0982018-05-11 13:32:37 -04001024 def CreateSCM(self, out_cb=None):
Edward Lemurb61d3872018-05-09 18:42:47 -04001025 raise NotImplementedError()
John Budorick0f7b2002018-01-19 15:46:17 -08001026
Dirk Pranke9f20d022017-10-11 18:36:54 -07001027 def HasGNArgsFile(self):
1028 return self._gn_args_file is not None
1029
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +02001030 def WriteGNArgsFile(self):
1031 lines = ['# Generated from %r' % self.deps_file]
Paweł Hajdan, Jrb495bf52017-09-25 19:33:50 +02001032 variables = self.get_vars()
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +02001033 for arg in self._gn_args:
Paweł Hajdan, Jre0214742017-09-28 12:21:01 +02001034 value = variables[arg]
Dirk Prankefdd2cd62020-06-30 23:30:47 +00001035 if isinstance(value, gclient_eval.ConstantString):
1036 value = value.value
1037 elif isinstance(value, basestring):
Paweł Hajdan, Jre0214742017-09-28 12:21:01 +02001038 value = gclient_eval.EvaluateCondition(value, variables)
Paweł Hajdan, Jrb495bf52017-09-25 19:33:50 +02001039 lines.append('%s = %s' % (arg, ToGNString(value)))
Corentin Wallez271a78a2020-07-12 15:41:46 +00001040
1041 # When use_relative_paths is set, gn_args_file is relative to this DEPS
1042 path_prefix = self.root.root_dir
1043 if self._use_relative_paths:
Lei Zhang67283c02020-07-13 21:38:44 +00001044 path_prefix = os.path.join(path_prefix, self.name)
Corentin Wallez271a78a2020-07-12 15:41:46 +00001045
1046 with open(os.path.join(path_prefix, self._gn_args_file), 'wb') as f:
Edward Lesmes05934952019-12-19 20:38:09 +00001047 f.write('\n'.join(lines).encode('utf-8', 'replace'))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001048
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001049 @gclient_utils.lockedmethod
Edward Lemure7273d22018-05-10 19:13:51 -04001050 def _run_is_done(self, file_list):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001051 # Both these are kept for hooks that are run as a separate tree traversal.
1052 self._file_list = file_list
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001053 self._processed = True
1054
szager@google.comb9a78d32012-03-13 18:46:21 +00001055 def GetHooks(self, options):
1056 """Evaluates all hooks, and return them in a flat list.
1057
1058 RunOnDeps() must have been called before to load the DEPS.
1059 """
1060 result = []
Michael Mossd683d7c2018-06-15 05:05:17 +00001061 if not self.should_process or not self.should_recurse:
1062 # Don't run the hook when it is above recursion_limit.
1063 return result
maruel@chromium.orgdc7445d2010-07-09 21:05:29 +00001064 # If "--force" was specified, run all hooks regardless of what files have
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001065 # changed.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001066 if self.deps_hooks:
agabled437d762016-10-17 09:35:11 -07001067 # TODO(maruel): If the user is using git, then we don't know
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001068 # what files have changed so we always run all hooks. It'd be nice to fix
1069 # that.
Edward Lemurbabd0982018-05-11 13:32:37 -04001070 result.extend(self.deps_hooks)
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001071 for s in self.dependencies:
szager@google.comb9a78d32012-03-13 18:46:21 +00001072 result.extend(s.GetHooks(options))
1073 return result
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001074
Daniel Chenga0c5f082017-10-19 13:35:19 -07001075 def RunHooksRecursively(self, options, progress):
szager@google.comb9a78d32012-03-13 18:46:21 +00001076 assert self.hooks_ran == False
maruel@chromium.org064186c2011-09-27 23:53:33 +00001077 self._hooks_ran = True
Daniel Chenga0c5f082017-10-19 13:35:19 -07001078 hooks = self.GetHooks(options)
1079 if progress:
1080 progress._total = len(hooks)
1081 for hook in hooks:
Daniel Chenga0c5f082017-10-19 13:35:19 -07001082 if progress:
1083 progress.update(extra=hook.name or '')
Corentin Walleza68660d2018-09-10 17:33:24 +00001084 hook.run()
Daniel Chenga0c5f082017-10-19 13:35:19 -07001085 if progress:
1086 progress.end()
maruel@chromium.orgeaf61062010-07-07 18:42:39 +00001087
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001088 def RunPreDepsHooks(self):
1089 assert self.processed
1090 assert self.deps_parsed
1091 assert not self.pre_deps_hooks_ran
1092 assert not self.hooks_ran
1093 for s in self.dependencies:
1094 assert not s.processed
1095 self._pre_deps_hooks_ran = True
1096 for hook in self.pre_deps_hooks:
Corentin Walleza68660d2018-09-10 17:33:24 +00001097 hook.run()
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001098
John Budorickd3ba72b2018-03-20 12:27:42 -07001099 def GetCipdRoot(self):
1100 if self.root is self:
1101 # Let's not infinitely recurse. If this is root and isn't an
1102 # instance of GClient, do nothing.
1103 return None
1104 return self.root.GetCipdRoot()
1105
Michael Mossd683d7c2018-06-15 05:05:17 +00001106 def subtree(self, include_all):
maruel@chromium.orgad3287e2011-10-03 19:15:10 +00001107 """Breadth first recursion excluding root node."""
maruel@chromium.orgf13a4182011-09-22 00:26:15 +00001108 dependencies = self.dependencies
1109 for d in dependencies:
Michael Mossd683d7c2018-06-15 05:05:17 +00001110 if d.should_process or include_all:
1111 yield d
maruel@chromium.orgf13a4182011-09-22 00:26:15 +00001112 for d in dependencies:
Michael Mossd683d7c2018-06-15 05:05:17 +00001113 for i in d.subtree(include_all):
maruel@chromium.orgad3287e2011-10-03 19:15:10 +00001114 yield i
1115
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001116 @gclient_utils.lockedmethod
1117 def add_dependency(self, new_dep):
1118 self._dependencies.append(new_dep)
1119
1120 @gclient_utils.lockedmethod
Paweł Hajdan, Jr357415c2017-07-24 14:35:28 +02001121 def _mark_as_parsed(self, new_hooks):
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001122 self._deps_hooks.extend(new_hooks)
1123 self._deps_parsed = True
1124
maruel@chromium.org68988972011-09-20 14:11:42 +00001125 @property
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001126 @gclient_utils.lockedmethod
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +00001127 def dependencies(self):
1128 return tuple(self._dependencies)
1129
1130 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001131 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001132 def deps_hooks(self):
1133 return tuple(self._deps_hooks)
1134
1135 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001136 @gclient_utils.lockedmethod
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001137 def pre_deps_hooks(self):
1138 return tuple(self._pre_deps_hooks)
1139
1140 @property
1141 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001142 def deps_parsed(self):
maruel@chromium.org3223edd2011-10-10 23:17:39 +00001143 """This is purely for debugging purposes. It's not used anywhere."""
maruel@chromium.org064186c2011-09-27 23:53:33 +00001144 return self._deps_parsed
1145
1146 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001147 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001148 def processed(self):
1149 return self._processed
1150
1151 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001152 @gclient_utils.lockedmethod
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001153 def pre_deps_hooks_ran(self):
1154 return self._pre_deps_hooks_ran
1155
1156 @property
1157 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001158 def hooks_ran(self):
1159 return self._hooks_ran
1160
1161 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001162 @gclient_utils.lockedmethod
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00001163 def allowed_hosts(self):
1164 return self._allowed_hosts
1165
1166 @property
1167 @gclient_utils.lockedmethod
maruel@chromium.org861fd0f2010-07-23 03:05:05 +00001168 def file_list(self):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001169 return tuple(self._file_list)
1170
1171 @property
kustermann@google.coma692e8f2013-04-18 08:32:04 +00001172 def used_scm(self):
1173 """SCMWrapper instance for this dependency or None if not processed yet."""
1174 return self._used_scm
1175
1176 @property
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00001177 @gclient_utils.lockedmethod
1178 def got_revision(self):
1179 return self._got_revision
1180
1181 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001182 def file_list_and_children(self):
1183 result = list(self.file_list)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +00001184 for d in self.dependencies:
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001185 result.extend(d.file_list_and_children)
maruel@chromium.org68988972011-09-20 14:11:42 +00001186 return tuple(result)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +00001187
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001188 def __str__(self):
1189 out = []
Edward Lemure7273d22018-05-10 19:13:51 -04001190 for i in ('name', 'url', 'custom_deps',
Michael Mossd683d7c2018-06-15 05:05:17 +00001191 'custom_vars', 'deps_hooks', 'file_list', 'should_process',
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00001192 'processed', 'hooks_ran', 'deps_parsed', 'requirements',
1193 'allowed_hosts'):
maruel@chromium.org3c74bc92011-09-15 19:17:21 +00001194 # First try the native property if it exists.
1195 if hasattr(self, '_' + i):
1196 value = getattr(self, '_' + i, False)
1197 else:
1198 value = getattr(self, i, False)
1199 if value:
1200 out.append('%s: %s' % (i, value))
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001201
1202 for d in self.dependencies:
1203 out.extend([' ' + x for x in str(d).splitlines()])
1204 out.append('')
1205 return '\n'.join(out)
1206
1207 def __repr__(self):
1208 return '%s: %s' % (self.name, self.url)
1209
Michael Moss4e9b50a2018-05-23 22:35:06 -07001210 def hierarchy(self, include_url=True):
maruel@chromium.orgbc2d2f92010-07-22 21:26:48 +00001211 """Returns a human-readable hierarchical reference to a Dependency."""
Michael Moss4e9b50a2018-05-23 22:35:06 -07001212 def format_name(d):
1213 if include_url:
1214 return '%s(%s)' % (d.name, d.url)
1215 return d.name
1216 out = format_name(self)
maruel@chromium.orgbffb9042010-07-22 20:59:36 +00001217 i = self.parent
1218 while i and i.name:
Michael Moss4e9b50a2018-05-23 22:35:06 -07001219 out = '%s -> %s' % (format_name(i), out)
maruel@chromium.orgbffb9042010-07-22 20:59:36 +00001220 i = i.parent
1221 return out
1222
Michael Mossfe68c912018-03-22 19:19:35 -07001223 def hierarchy_data(self):
1224 """Returns a machine-readable hierarchical reference to a Dependency."""
1225 d = self
1226 out = []
1227 while d and d.name:
1228 out.insert(0, (d.name, d.url))
1229 d = d.parent
1230 return tuple(out)
1231
Edward Lemur8f8a50d2018-11-01 22:03:02 +00001232 def get_builtin_vars(self):
1233 return {
Paweł Hajdan, Jrd325eb32017-10-03 17:43:37 +02001234 'checkout_android': 'android' in self.target_os,
Benjamin Pastene6fe29412018-01-23 15:35:58 -08001235 'checkout_chromeos': 'chromeos' in self.target_os,
Paweł Hajdan, Jrd325eb32017-10-03 17:43:37 +02001236 'checkout_fuchsia': 'fuchsia' in self.target_os,
1237 'checkout_ios': 'ios' in self.target_os,
1238 'checkout_linux': 'unix' in self.target_os,
1239 'checkout_mac': 'mac' in self.target_os,
1240 'checkout_win': 'win' in self.target_os,
1241 'host_os': _detect_host_os(),
Tom Andersonc31ae0b2018-02-06 14:48:56 -08001242
1243 'checkout_arm': 'arm' in self.target_cpu,
1244 'checkout_arm64': 'arm64' in self.target_cpu,
1245 'checkout_x86': 'x86' in self.target_cpu,
1246 'checkout_mips': 'mips' in self.target_cpu,
Wang Qing254538b2018-07-26 02:23:53 +00001247 'checkout_mips64': 'mips64' in self.target_cpu,
Tom Andersonc31ae0b2018-02-06 14:48:56 -08001248 'checkout_ppc': 'ppc' in self.target_cpu,
1249 'checkout_s390': 's390' in self.target_cpu,
1250 'checkout_x64': 'x64' in self.target_cpu,
1251 'host_cpu': detect_host_arch.HostArch(),
Paweł Hajdan, Jra3b67ae2017-08-30 15:18:21 +02001252 }
Edward Lemur8f8a50d2018-11-01 22:03:02 +00001253
1254 def get_vars(self):
1255 """Returns a dictionary of effective variable values
1256 (DEPS file contents with applied custom_vars overrides)."""
1257 # Variable precedence (last has highest):
Michael Mossda55cdc2018-04-06 18:37:19 -07001258 # - DEPS vars
1259 # - parents, from first to last
Edward Lemur8f8a50d2018-11-01 22:03:02 +00001260 # - built-in
Michael Mossda55cdc2018-04-06 18:37:19 -07001261 # - custom_vars overrides
Edward Lemur8f8a50d2018-11-01 22:03:02 +00001262 result = {}
Paweł Hajdan, Jra3b67ae2017-08-30 15:18:21 +02001263 result.update(self._vars)
Michael Mossda55cdc2018-04-06 18:37:19 -07001264 if self.parent:
Dirk Prankefdd2cd62020-06-30 23:30:47 +00001265 merge_vars(result, self.parent.get_vars())
Edward Lemur8f8a50d2018-11-01 22:03:02 +00001266 # Provide some built-in variables.
1267 result.update(self.get_builtin_vars())
Dirk Prankefdd2cd62020-06-30 23:30:47 +00001268 merge_vars(result, self.custom_vars)
1269
Paweł Hajdan, Jrd3790252017-07-03 21:06:24 +02001270 return result
1271
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001272
Paweł Hajdan, Jra3b67ae2017-08-30 15:18:21 +02001273_PLATFORM_MAPPING = {
1274 'cygwin': 'win',
1275 'darwin': 'mac',
1276 'linux2': 'linux',
Edward Lemuree7b9dd2019-07-20 01:29:08 +00001277 'linux': 'linux',
Paweł Hajdan, Jra3b67ae2017-08-30 15:18:21 +02001278 'win32': 'win',
Jaideep Bajwad05f3582017-09-11 12:31:48 -04001279 'aix6': 'aix',
Paweł Hajdan, Jra3b67ae2017-08-30 15:18:21 +02001280}
1281
1282
Dirk Prankefdd2cd62020-06-30 23:30:47 +00001283def merge_vars(result, new_vars):
1284 for k, v in new_vars.items():
1285 if k in result:
1286 if isinstance(result[k], gclient_eval.ConstantString):
1287 if isinstance(v, gclient_eval.ConstantString):
1288 result[k] = v
1289 else:
1290 result[k].value = v
1291 else:
1292 result[k] = v
1293 else:
1294 result[k] = v
1295
1296
Paweł Hajdan, Jra3b67ae2017-08-30 15:18:21 +02001297def _detect_host_os():
1298 return _PLATFORM_MAPPING[sys.platform]
1299
1300
Edward Lemurb61d3872018-05-09 18:42:47 -04001301class GitDependency(Dependency):
1302 """A Dependency object that represents a single git checkout."""
1303
1304 #override
Edward Lemurbabd0982018-05-11 13:32:37 -04001305 def GetScmName(self):
Edward Lemurb61d3872018-05-09 18:42:47 -04001306 """Always 'git'."""
Edward Lemurb61d3872018-05-09 18:42:47 -04001307 return 'git'
1308
1309 #override
Edward Lemurbabd0982018-05-11 13:32:37 -04001310 def CreateSCM(self, out_cb=None):
Edward Lemurb61d3872018-05-09 18:42:47 -04001311 """Create a Wrapper instance suitable for handling this git dependency."""
Edward Lemurbabd0982018-05-11 13:32:37 -04001312 return gclient_scm.GitWrapper(
1313 self.url, self.root.root_dir, self.name, self.outbuf, out_cb,
1314 print_outbuf=self.print_outbuf)
Edward Lemurb61d3872018-05-09 18:42:47 -04001315
1316
1317class GClient(GitDependency):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001318 """Object that represent a gclient checkout. A tree of Dependency(), one per
1319 solution or DEPS entry."""
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001320
1321 DEPS_OS_CHOICES = {
Jaideep Bajwad05f3582017-09-11 12:31:48 -04001322 "aix6": "unix",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001323 "win32": "win",
1324 "win": "win",
1325 "cygwin": "win",
1326 "darwin": "mac",
1327 "mac": "mac",
1328 "unix": "unix",
1329 "linux": "unix",
1330 "linux2": "unix",
maruel@chromium.org244e3442011-06-12 15:20:55 +00001331 "linux3": "unix",
szager@chromium.orgf8c95cd2012-06-01 22:26:52 +00001332 "android": "android",
Michael Mossc54fa812017-08-17 11:27:58 -07001333 "ios": "ios",
Sergiy Byelozyorov518bb682018-06-03 11:25:58 +02001334 "fuchsia": "fuchsia",
Michael Moss484d74f2019-02-06 01:55:43 +00001335 "chromeos": "chromeos",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001336 }
1337
1338 DEFAULT_CLIENT_FILE_TEXT = ("""\
1339solutions = [
Edward Lesmes05934952019-12-19 20:38:09 +00001340 { "name" : %(solution_name)r,
1341 "url" : %(solution_url)r,
1342 "deps_file" : %(deps_file)r,
1343 "managed" : %(managed)r,
smutae7ea312016-07-18 11:59:41 -07001344 "custom_deps" : {
1345 },
Paweł Hajdan, Jr3ba2a7c2017-10-04 19:24:46 +02001346 "custom_vars": %(custom_vars)r,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001347 },
1348]
Robert Iannuccia19649b2018-06-29 16:31:45 +00001349""")
1350
1351 DEFAULT_CLIENT_CACHE_DIR_TEXT = ("""\
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001352cache_dir = %(cache_dir)r
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001353""")
1354
Robert Iannuccia19649b2018-06-29 16:31:45 +00001355
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001356 DEFAULT_SNAPSHOT_FILE_TEXT = ("""\
1357# Snapshot generated with gclient revinfo --snapshot
Edward Lesmesc2960242018-03-06 20:50:15 -05001358solutions = %(solution_list)s
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001359""")
1360
1361 def __init__(self, root_dir, options):
maruel@chromium.org0d812442010-08-10 12:41:08 +00001362 # Do not change previous behavior. Only solution level and immediate DEPS
1363 # are processed.
1364 self._recursion_limit = 2
Edward Lemure05f18d2018-06-08 17:36:53 +00001365 super(GClient, self).__init__(
1366 parent=None,
1367 name=None,
Michael Mossd683d7c2018-06-15 05:05:17 +00001368 url=None,
Edward Lemure05f18d2018-06-08 17:36:53 +00001369 managed=True,
1370 custom_deps=None,
1371 custom_vars=None,
1372 custom_hooks=None,
1373 deps_file='unused',
Michael Mossd683d7c2018-06-15 05:05:17 +00001374 should_process=True,
Edward Lemurfbb06aa2018-06-11 20:43:06 +00001375 should_recurse=True,
Edward Lemure05f18d2018-06-08 17:36:53 +00001376 relative=None,
1377 condition=None,
1378 print_outbuf=True)
1379
maruel@chromium.org0d425922010-06-21 19:22:24 +00001380 self._options = options
maruel@chromium.org271375b2010-06-23 19:17:38 +00001381 if options.deps_os:
1382 enforced_os = options.deps_os.split(',')
1383 else:
1384 enforced_os = [self.DEPS_OS_CHOICES.get(sys.platform, 'unix')]
1385 if 'all' in enforced_os:
Edward Lemuree7b9dd2019-07-20 01:29:08 +00001386 enforced_os = self.DEPS_OS_CHOICES.values()
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001387 self._enforced_os = tuple(set(enforced_os))
Tom Andersonc31ae0b2018-02-06 14:48:56 -08001388 self._enforced_cpu = detect_host_arch.HostArch(),
maruel@chromium.org271375b2010-06-23 19:17:38 +00001389 self._root_dir = root_dir
John Budorickd3ba72b2018-03-20 12:27:42 -07001390 self._cipd_root = None
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001391 self.config_content = None
1392
borenet@google.com88d10082014-03-21 17:24:48 +00001393 def _CheckConfig(self):
1394 """Verify that the config matches the state of the existing checked-out
1395 solutions."""
1396 for dep in self.dependencies:
Michael Mossd683d7c2018-06-15 05:05:17 +00001397 if dep.managed and dep.url:
Edward Lemurbabd0982018-05-11 13:32:37 -04001398 scm = dep.CreateSCM()
smut@google.comd33eab32014-07-07 19:35:18 +00001399 actual_url = scm.GetActualRemoteURL(self._options)
borenet@google.com4e9be262014-04-08 19:40:30 +00001400 if actual_url and not scm.DoesRemoteURLMatch(self._options):
levarum@chromium.org27a6f9a2016-05-28 00:21:49 +00001401 mirror = scm.GetCacheMirror()
1402 if mirror:
1403 mirror_string = '%s (exists=%s)' % (mirror.mirror_path,
1404 mirror.exists())
1405 else:
1406 mirror_string = 'not used'
Raul Tambreb946b232019-03-26 14:48:46 +00001407 raise gclient_utils.Error(
1408 '''
borenet@google.com88d10082014-03-21 17:24:48 +00001409Your .gclient file seems to be broken. The requested URL is different from what
borenet@google.com0a427372014-04-02 19:12:13 +00001410is actually checked out in %(checkout_path)s.
borenet@google.com88d10082014-03-21 17:24:48 +00001411
borenet@google.com97882362014-04-07 20:06:02 +00001412The .gclient file contains:
levarum@chromium.org27a6f9a2016-05-28 00:21:49 +00001413URL: %(expected_url)s (%(expected_scm)s)
1414Cache mirror: %(mirror_string)s
borenet@google.com97882362014-04-07 20:06:02 +00001415
1416The local checkout in %(checkout_path)s reports:
1417%(actual_url)s (%(actual_scm)s)
borenet@google.com88d10082014-03-21 17:24:48 +00001418
1419You should ensure that the URL listed in .gclient is correct and either change
agabled437d762016-10-17 09:35:11 -07001420it or fix the checkout.
Josip Sokcevic14a83ae2020-05-21 01:36:34 +00001421''' % {
1422 'checkout_path': os.path.join(self.root_dir, dep.name),
1423 'expected_url': dep.url,
1424 'expected_scm': dep.GetScmName(),
1425 'mirror_string': mirror_string,
1426 'actual_url': actual_url,
1427 'actual_scm': dep.GetScmName()
1428 })
borenet@google.com88d10082014-03-21 17:24:48 +00001429
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001430 def SetConfig(self, content):
maruel@chromium.orgf13a4182011-09-22 00:26:15 +00001431 assert not self.dependencies
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001432 config_dict = {}
1433 self.config_content = content
1434 try:
1435 exec(content, config_dict)
vapier@chromium.orga81a56e2015-11-11 07:56:13 +00001436 except SyntaxError as e:
maruel@chromium.org5990f9d2010-07-07 18:02:58 +00001437 gclient_utils.SyntaxErrorToError('.gclient', e)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001438
peter@chromium.org1efccc82012-04-27 16:34:38 +00001439 # Append any target OS that is not already being enforced to the tuple.
1440 target_os = config_dict.get('target_os', [])
stuartmorgan@chromium.org18a4f6a2012-11-02 13:37:24 +00001441 if config_dict.get('target_os_only', False):
1442 self._enforced_os = tuple(set(target_os))
1443 else:
1444 self._enforced_os = tuple(set(self._enforced_os).union(target_os))
1445
Tom Andersonc31ae0b2018-02-06 14:48:56 -08001446 # Append any target CPU that is not already being enforced to the tuple.
1447 target_cpu = config_dict.get('target_cpu', [])
1448 if config_dict.get('target_cpu_only', False):
1449 self._enforced_cpu = tuple(set(target_cpu))
1450 else:
1451 self._enforced_cpu = tuple(set(self._enforced_cpu).union(target_cpu))
1452
Robert Iannuccia19649b2018-06-29 16:31:45 +00001453 cache_dir = config_dict.get('cache_dir', UNSET_CACHE_DIR)
1454 if cache_dir is not UNSET_CACHE_DIR:
1455 if cache_dir:
1456 cache_dir = os.path.join(self.root_dir, cache_dir)
1457 cache_dir = os.path.abspath(cache_dir)
Andrii Shyshkalov77ce4bd2017-11-27 12:38:18 -08001458
Robert Iannuccia19649b2018-06-29 16:31:45 +00001459 git_cache.Mirror.SetCachePath(cache_dir)
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001460
stuartmorgan@chromium.org18a4f6a2012-11-02 13:37:24 +00001461 if not target_os and config_dict.get('target_os_only', False):
1462 raise gclient_utils.Error('Can\'t use target_os_only if target_os is '
1463 'not specified')
peter@chromium.org1efccc82012-04-27 16:34:38 +00001464
Tom Andersonc31ae0b2018-02-06 14:48:56 -08001465 if not target_cpu and config_dict.get('target_cpu_only', False):
1466 raise gclient_utils.Error('Can\'t use target_cpu_only if target_cpu is '
1467 'not specified')
1468
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001469 deps_to_add = []
Michael Mossd683d7c2018-06-15 05:05:17 +00001470 for s in config_dict.get('solutions', []):
1471 try:
Michael Moss4e9b50a2018-05-23 22:35:06 -07001472 deps_to_add.append(GitDependency(
Edward Lemure05f18d2018-06-08 17:36:53 +00001473 parent=self,
1474 name=s['name'],
1475 url=s['url'],
1476 managed=s.get('managed', True),
1477 custom_deps=s.get('custom_deps', {}),
1478 custom_vars=s.get('custom_vars', {}),
1479 custom_hooks=s.get('custom_hooks', []),
1480 deps_file=s.get('deps_file', 'DEPS'),
Michael Mossd683d7c2018-06-15 05:05:17 +00001481 should_process=True,
Edward Lemurfbb06aa2018-06-11 20:43:06 +00001482 should_recurse=True,
Edward Lemure05f18d2018-06-08 17:36:53 +00001483 relative=None,
1484 condition=None,
1485 print_outbuf=True))
Michael Mossd683d7c2018-06-15 05:05:17 +00001486 except KeyError:
1487 raise gclient_utils.Error('Invalid .gclient file. Solution is '
1488 'incomplete: %s' % s)
Edward Lemur40764b02018-07-20 18:50:29 +00001489 metrics.collector.add(
1490 'project_urls',
1491 [
Edward Lemuraffd4102019-06-05 18:07:49 +00001492 dep.FuzzyMatchUrl(metrics_utils.KNOWN_PROJECT_URLS)
Edward Lemur40764b02018-07-20 18:50:29 +00001493 for dep in deps_to_add
1494 if dep.FuzzyMatchUrl(metrics_utils.KNOWN_PROJECT_URLS)
1495 ]
1496 )
1497
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001498 self.add_dependencies_and_close(deps_to_add, config_dict.get('hooks', []))
1499 logging.info('SetConfig() done')
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001500
1501 def SaveConfig(self):
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001502 gclient_utils.FileWrite(os.path.join(self.root_dir,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001503 self._options.config_filename),
1504 self.config_content)
1505
1506 @staticmethod
1507 def LoadCurrentConfig(options):
1508 """Searches for and loads a .gclient file relative to the current working
1509 dir. Returns a GClient object."""
szager@chromium.orge2e03202012-07-31 18:05:16 +00001510 if options.spec:
1511 client = GClient('.', options)
1512 client.SetConfig(options.spec)
1513 else:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00001514 if options.verbose:
1515 print('Looking for %s starting from %s\n' % (
1516 options.config_filename, os.getcwd()))
Nico Weber09e0b382019-03-11 16:54:07 +00001517 path = gclient_paths.FindGclientRoot(os.getcwd(), options.config_filename)
szager@chromium.orge2e03202012-07-31 18:05:16 +00001518 if not path:
Michael Achenbachb3ce73d2017-10-11 16:41:27 +02001519 if options.verbose:
1520 print('Couldn\'t find configuration file.')
szager@chromium.orge2e03202012-07-31 18:05:16 +00001521 return None
1522 client = GClient(path, options)
1523 client.SetConfig(gclient_utils.FileRead(
1524 os.path.join(path, options.config_filename)))
maruel@chromium.org69392e72011-10-13 22:09:00 +00001525
1526 if (options.revisions and
1527 len(client.dependencies) > 1 and
1528 any('@' not in r for r in options.revisions)):
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001529 print(
1530 ('You must specify the full solution name like --revision %s@%s\n'
1531 'when you have multiple solutions setup in your .gclient file.\n'
1532 'Other solutions present are: %s.') % (
maruel@chromium.org69392e72011-10-13 22:09:00 +00001533 client.dependencies[0].name,
1534 options.revisions[0],
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001535 ', '.join(s.name for s in client.dependencies[1:])),
1536 file=sys.stderr)
maruel@chromium.org15804092010-09-02 17:07:37 +00001537 return client
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001538
nsylvain@google.comefc80932011-05-31 21:27:56 +00001539 def SetDefaultConfig(self, solution_name, deps_file, solution_url,
Robert Iannuccia19649b2018-06-29 16:31:45 +00001540 managed=True, cache_dir=UNSET_CACHE_DIR,
1541 custom_vars=None):
1542 text = self.DEFAULT_CLIENT_FILE_TEXT
1543 format_dict = {
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001544 'solution_name': solution_name,
1545 'solution_url': solution_url,
nsylvain@google.comefc80932011-05-31 21:27:56 +00001546 'deps_file': deps_file,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001547 'managed': managed,
Paweł Hajdan, Jr3ba2a7c2017-10-04 19:24:46 +02001548 'custom_vars': custom_vars or {},
Robert Iannuccia19649b2018-06-29 16:31:45 +00001549 }
1550
1551 if cache_dir is not UNSET_CACHE_DIR:
1552 text += self.DEFAULT_CLIENT_CACHE_DIR_TEXT
1553 format_dict['cache_dir'] = cache_dir
1554
1555 self.SetConfig(text % format_dict)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001556
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001557 def _SaveEntries(self):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001558 """Creates a .gclient_entries file to record the list of unique checkouts.
1559
1560 The .gclient_entries file lives in the same directory as .gclient.
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001561 """
1562 # Sometimes pprint.pformat will use {', sometimes it'll use { ' ... It
1563 # makes testing a bit too fun.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001564 result = 'entries = {\n'
Michael Mossd683d7c2018-06-15 05:05:17 +00001565 for entry in self.root.subtree(False):
agabled437d762016-10-17 09:35:11 -07001566 result += ' %s: %s,\n' % (pprint.pformat(entry.name),
Edward Lemure7273d22018-05-10 19:13:51 -04001567 pprint.pformat(entry.url))
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001568 result += '}\n'
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001569 file_path = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org1333cb32011-10-04 23:40:16 +00001570 logging.debug(result)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001571 gclient_utils.FileWrite(file_path, result)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001572
1573 def _ReadEntries(self):
1574 """Read the .gclient_entries file for the given client.
1575
1576 Returns:
1577 A sequence of solution names, which will be empty if there is the
1578 entries file hasn't been created yet.
1579 """
1580 scope = {}
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001581 filename = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001582 if not os.path.exists(filename):
maruel@chromium.org73e21142010-07-05 13:32:01 +00001583 return {}
maruel@chromium.org5990f9d2010-07-07 18:02:58 +00001584 try:
1585 exec(gclient_utils.FileRead(filename), scope)
vapier@chromium.orga81a56e2015-11-11 07:56:13 +00001586 except SyntaxError as e:
maruel@chromium.org5990f9d2010-07-07 18:02:58 +00001587 gclient_utils.SyntaxErrorToError(filename, e)
Aaron Gable3721ee92017-04-03 14:53:14 -07001588 return scope.get('entries', {})
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001589
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001590 def _EnforceRevisions(self):
maruel@chromium.org918a9ae2010-05-28 15:50:30 +00001591 """Checks for revision overrides."""
1592 revision_overrides = {}
smutae7ea312016-07-18 11:59:41 -07001593 if self._options.head:
1594 return revision_overrides
joi@chromium.org792ea882010-11-10 02:37:27 +00001595 if not self._options.revisions:
maruel@chromium.org307d1792010-05-31 20:03:13 +00001596 return revision_overrides
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001597 solutions_names = [s.name for s in self.dependencies]
smutae7ea312016-07-18 11:59:41 -07001598 index = 0
1599 for revision in self._options.revisions:
1600 if not '@' in revision:
maruel@chromium.org307d1792010-05-31 20:03:13 +00001601 # Support for --revision 123
smutae7ea312016-07-18 11:59:41 -07001602 revision = '%s@%s' % (solutions_names[index], revision)
1603 name, rev = revision.split('@', 1)
szager@chromium.org4ad264b2014-05-20 04:43:47 +00001604 revision_overrides[name] = rev
smutae7ea312016-07-18 11:59:41 -07001605 index += 1
maruel@chromium.org918a9ae2010-05-28 15:50:30 +00001606 return revision_overrides
1607
Edward Lemur6a4e31b2018-08-10 19:59:02 +00001608 def _EnforcePatchRefsAndBranches(self):
Edward Lesmesc621b212018-03-21 20:26:56 -04001609 """Checks for patch refs."""
1610 patch_refs = {}
Edward Lemur6a4e31b2018-08-10 19:59:02 +00001611 target_branches = {}
Edward Lesmesc621b212018-03-21 20:26:56 -04001612 if not self._options.patch_refs:
Edward Lemur6a4e31b2018-08-10 19:59:02 +00001613 return patch_refs, target_branches
Edward Lesmesc621b212018-03-21 20:26:56 -04001614 for given_patch_ref in self._options.patch_refs:
1615 patch_repo, _, patch_ref = given_patch_ref.partition('@')
Edward Lemur4c5c8ab2019-06-07 15:58:13 +00001616 if not patch_repo or not patch_ref or ':' not in patch_ref:
Edward Lesmesc621b212018-03-21 20:26:56 -04001617 raise gclient_utils.Error(
1618 'Wrong revision format: %s should be of the form '
Edward Lemur4c5c8ab2019-06-07 15:58:13 +00001619 'patch_repo@target_branch:patch_ref.' % given_patch_ref)
1620 target_branch, _, patch_ref = patch_ref.partition(':')
1621 target_branches[patch_repo] = target_branch
Edward Lesmesc621b212018-03-21 20:26:56 -04001622 patch_refs[patch_repo] = patch_ref
Edward Lemur6a4e31b2018-08-10 19:59:02 +00001623 return patch_refs, target_branches
Edward Lesmesc621b212018-03-21 20:26:56 -04001624
Edward Lemur5b1fa942018-10-04 23:22:09 +00001625 def _RemoveUnversionedGitDirs(self):
1626 """Remove directories that are no longer part of the checkout.
1627
1628 Notify the user if there is an orphaned entry in their working copy.
1629 Only delete the directory if there are no changes in it, and
1630 delete_unversioned_trees is set to true.
1631 """
1632
1633 entries = [i.name for i in self.root.subtree(False) if i.url]
1634 full_entries = [os.path.join(self.root_dir, e.replace('/', os.path.sep))
1635 for e in entries]
1636
Marc-Antoine Ruel8e57b4b2019-10-11 01:01:36 +00001637 for entry, prev_url in self._ReadEntries().items():
Edward Lemur5b1fa942018-10-04 23:22:09 +00001638 if not prev_url:
1639 # entry must have been overridden via .gclient custom_deps
1640 continue
1641 # Fix path separator on Windows.
1642 entry_fixed = entry.replace('/', os.path.sep)
1643 e_dir = os.path.join(self.root_dir, entry_fixed)
1644 # Use entry and not entry_fixed there.
1645 if (entry not in entries and
1646 (not any(path.startswith(entry + '/') for path in entries)) and
1647 os.path.exists(e_dir)):
1648 # The entry has been removed from DEPS.
1649 scm = gclient_scm.GitWrapper(
1650 prev_url, self.root_dir, entry_fixed, self.outbuf)
1651
1652 # Check to see if this directory is now part of a higher-up checkout.
1653 scm_root = None
1654 try:
1655 scm_root = gclient_scm.scm.GIT.GetCheckoutRoot(scm.checkout_path)
1656 except subprocess2.CalledProcessError:
1657 pass
1658 if not scm_root:
1659 logging.warning('Could not find checkout root for %s. Unable to '
1660 'determine whether it is part of a higher-level '
1661 'checkout, so not removing.' % entry)
1662 continue
1663
1664 # This is to handle the case of third_party/WebKit migrating from
1665 # being a DEPS entry to being part of the main project.
1666 # If the subproject is a Git project, we need to remove its .git
1667 # folder. Otherwise git operations on that folder will have different
1668 # effects depending on the current working directory.
1669 if os.path.abspath(scm_root) == os.path.abspath(e_dir):
1670 e_par_dir = os.path.join(e_dir, os.pardir)
1671 if gclient_scm.scm.GIT.IsInsideWorkTree(e_par_dir):
1672 par_scm_root = gclient_scm.scm.GIT.GetCheckoutRoot(e_par_dir)
1673 # rel_e_dir : relative path of entry w.r.t. its parent repo.
1674 rel_e_dir = os.path.relpath(e_dir, par_scm_root)
1675 if gclient_scm.scm.GIT.IsDirectoryVersioned(
1676 par_scm_root, rel_e_dir):
1677 save_dir = scm.GetGitBackupDirPath()
1678 # Remove any eventual stale backup dir for the same project.
1679 if os.path.exists(save_dir):
1680 gclient_utils.rmtree(save_dir)
1681 os.rename(os.path.join(e_dir, '.git'), save_dir)
1682 # When switching between the two states (entry/ is a subproject
1683 # -> entry/ is part of the outer project), it is very likely
1684 # that some files are changed in the checkout, unless we are
1685 # jumping *exactly* across the commit which changed just DEPS.
1686 # In such case we want to cleanup any eventual stale files
1687 # (coming from the old subproject) in order to end up with a
1688 # clean checkout.
1689 gclient_scm.scm.GIT.CleanupDir(par_scm_root, rel_e_dir)
1690 assert not os.path.exists(os.path.join(e_dir, '.git'))
Raul Tambre80ee78e2019-05-06 22:41:05 +00001691 print('\nWARNING: \'%s\' has been moved from DEPS to a higher '
1692 'level checkout. The git folder containing all the local'
1693 ' branches has been saved to %s.\n'
1694 'If you don\'t care about its state you can safely '
1695 'remove that folder to free up space.' % (entry, save_dir))
Edward Lemur5b1fa942018-10-04 23:22:09 +00001696 continue
1697
1698 if scm_root in full_entries:
1699 logging.info('%s is part of a higher level checkout, not removing',
1700 scm.GetCheckoutRoot())
1701 continue
1702
1703 file_list = []
1704 scm.status(self._options, [], file_list)
1705 modified_files = file_list != []
1706 if (not self._options.delete_unversioned_trees or
1707 (modified_files and not self._options.force)):
1708 # There are modified files in this entry. Keep warning until
1709 # removed.
Henrique Ferreiroe72279d2019-04-17 12:01:50 +00001710 self.add_dependency(
1711 GitDependency(
1712 parent=self,
1713 name=entry,
1714 url=prev_url,
1715 managed=False,
1716 custom_deps={},
1717 custom_vars={},
1718 custom_hooks=[],
1719 deps_file=None,
1720 should_process=True,
1721 should_recurse=False,
1722 relative=None,
1723 condition=None))
Anthony Politobb457342019-11-15 22:26:01 +00001724 if modified_files and self._options.delete_unversioned_trees:
1725 print('\nWARNING: \'%s\' is no longer part of this client.\n'
1726 'Despite running \'gclient sync -D\' no action was taken '
1727 'as there are modifications.\nIt is recommended you revert '
1728 'all changes or run \'gclient sync -D --force\' next '
1729 'time.' % entry_fixed)
1730 else:
1731 print('\nWARNING: \'%s\' is no longer part of this client.\n'
1732 'It is recommended that you manually remove it or use '
1733 '\'gclient sync -D\' next time.' % entry_fixed)
Edward Lemur5b1fa942018-10-04 23:22:09 +00001734 else:
1735 # Delete the entry
1736 print('\n________ deleting \'%s\' in \'%s\'' % (
1737 entry_fixed, self.root_dir))
1738 gclient_utils.rmtree(e_dir)
1739 # record the current list of entries for next time
1740 self._SaveEntries()
1741
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001742 def RunOnDeps(self, command, args, ignore_requirements=False, progress=True):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001743 """Runs a command on each dependency in a client and its dependencies.
1744
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001745 Args:
1746 command: The command to use (e.g., 'status' or 'diff')
1747 args: list of str - extra arguments to add to the command line.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001748 """
Michael Mossd683d7c2018-06-15 05:05:17 +00001749 if not self.dependencies:
1750 raise gclient_utils.Error('No solution specified')
1751
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001752 revision_overrides = {}
Edward Lesmesc621b212018-03-21 20:26:56 -04001753 patch_refs = {}
Edward Lemur6a4e31b2018-08-10 19:59:02 +00001754 target_branches = {}
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001755 # It's unnecessary to check for revision overrides for 'recurse'.
1756 # Save a few seconds by not calling _EnforceRevisions() in that case.
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +02001757 if command not in ('diff', 'recurse', 'runhooks', 'status', 'revert',
1758 'validate'):
szager@chromium.org5273b8a2014-08-21 15:10:10 +00001759 self._CheckConfig()
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001760 revision_overrides = self._EnforceRevisions()
Edward Lesmesc621b212018-03-21 20:26:56 -04001761
1762 if command == 'update':
Edward Lemur6a4e31b2018-08-10 19:59:02 +00001763 patch_refs, target_branches = self._EnforcePatchRefsAndBranches()
Daniel Chenga21b5b32017-10-19 20:07:48 +00001764 # Disable progress for non-tty stdout.
Daniel Chenga0c5f082017-10-19 13:35:19 -07001765 should_show_progress = (
1766 setup_color.IS_TTY and not self._options.verbose and progress)
1767 pm = None
1768 if should_show_progress:
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001769 if command in ('update', 'revert'):
1770 pm = Progress('Syncing projects', 1)
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +02001771 elif command in ('recurse', 'validate'):
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001772 pm = Progress(' '.join(args), 1)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001773 work_queue = gclient_utils.ExecutionQueue(
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001774 self._options.jobs, pm, ignore_requirements=ignore_requirements,
1775 verbose=self._options.verbose)
maruel@chromium.org049bced2010-08-12 13:37:20 +00001776 for s in self.dependencies:
Michael Mossd683d7c2018-06-15 05:05:17 +00001777 if s.should_process:
1778 work_queue.enqueue(s)
Edward Lesmesc621b212018-03-21 20:26:56 -04001779 work_queue.flush(revision_overrides, command, args, options=self._options,
Edward Lemur6a4e31b2018-08-10 19:59:02 +00001780 patch_refs=patch_refs, target_branches=target_branches)
Edward Lesmesc621b212018-03-21 20:26:56 -04001781
szager@chromium.org4ad264b2014-05-20 04:43:47 +00001782 if revision_overrides:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001783 print('Please fix your script, having invalid --revision flags will soon '
Edward Lesmesc621b212018-03-21 20:26:56 -04001784 'be considered an error.', file=sys.stderr)
1785
1786 if patch_refs:
1787 raise gclient_utils.Error(
1788 'The following --patch-ref flags were not used. Please fix it:\n%s' %
1789 ('\n'.join(
1790 patch_repo + '@' + patch_ref
Marc-Antoine Ruel8e57b4b2019-10-11 01:01:36 +00001791 for patch_repo, patch_ref in patch_refs.items())))
piman@chromium.org6f363722010-04-27 00:41:09 +00001792
Dirk Pranke9f20d022017-10-11 18:36:54 -07001793 # Once all the dependencies have been processed, it's now safe to write
Michael Moss848c86e2018-05-03 16:05:50 -07001794 # out the gn_args_file and run the hooks.
Dirk Pranke9f20d022017-10-11 18:36:54 -07001795 if command == 'update':
Michael Moss848c86e2018-05-03 16:05:50 -07001796 gn_args_dep = self.dependencies[0]
1797 if gn_args_dep._gn_args_from:
1798 deps_map = dict([(dep.name, dep) for dep in gn_args_dep.dependencies])
1799 gn_args_dep = deps_map.get(gn_args_dep._gn_args_from)
1800 if gn_args_dep and gn_args_dep.HasGNArgsFile():
1801 gn_args_dep.WriteGNArgsFile()
Dirk Pranke9f20d022017-10-11 18:36:54 -07001802
Edward Lemur5b1fa942018-10-04 23:22:09 +00001803 self._RemoveUnversionedGitDirs()
Edward Lemur647e1e72018-09-19 18:15:29 +00001804
1805 # Sync CIPD dependencies once removed deps are deleted. In case a git
1806 # dependency was moved to CIPD, we want to remove the old git directory
1807 # first and then sync the CIPD dep.
1808 if self._cipd_root:
1809 self._cipd_root.run(command)
1810
Edward Lemur5b1fa942018-10-04 23:22:09 +00001811 if not self._options.nohooks:
1812 if should_show_progress:
1813 pm = Progress('Running hooks', 1)
1814 self.RunHooksRecursively(self._options, pm)
1815
1816
maruel@chromium.org17cdf762010-05-28 17:30:52 +00001817 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001818
1819 def PrintRevInfo(self):
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001820 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +00001821 raise gclient_utils.Error('No solution specified')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001822 # Load all the settings.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001823 work_queue = gclient_utils.ExecutionQueue(
1824 self._options.jobs, None, False, verbose=self._options.verbose)
maruel@chromium.org049bced2010-08-12 13:37:20 +00001825 for s in self.dependencies:
Michael Mossd683d7c2018-06-15 05:05:17 +00001826 if s.should_process:
1827 work_queue.enqueue(s)
Edward Lemur6a4e31b2018-08-10 19:59:02 +00001828 work_queue.flush({}, None, [], options=self._options, patch_refs=None,
1829 target_branches=None)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001830
Michael Mossd683d7c2018-06-15 05:05:17 +00001831 def ShouldPrintRevision(dep):
Edward Lesmesbb16e332018-03-30 17:54:51 -04001832 return (not self._options.filter
Edward Lemure7273d22018-05-10 19:13:51 -04001833 or dep.FuzzyMatchUrl(self._options.filter))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001834
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001835 if self._options.snapshot:
Michael Mossd683d7c2018-06-15 05:05:17 +00001836 json_output = []
1837 # First level at .gclient
1838 for d in self.dependencies:
1839 entries = {}
1840 def GrabDeps(dep):
1841 """Recursively grab dependencies."""
1842 for d in dep.dependencies:
1843 d.PinToActualRevision()
1844 if ShouldPrintRevision(d):
1845 entries[d.name] = d.url
1846 GrabDeps(d)
1847 GrabDeps(d)
1848 json_output.append({
1849 'name': d.name,
1850 'solution_url': d.url,
1851 'deps_file': d.deps_file,
1852 'managed': d.managed,
1853 'custom_deps': entries,
1854 })
1855 if self._options.output_json == '-':
1856 print(json.dumps(json_output, indent=2, separators=(',', ': ')))
1857 elif self._options.output_json:
1858 with open(self._options.output_json, 'w') as f:
1859 json.dump(json_output, f)
1860 else:
1861 # Print the snapshot configuration file
1862 print(self.DEFAULT_SNAPSHOT_FILE_TEXT % {
1863 'solution_list': pprint.pformat(json_output, indent=2),
1864 })
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +00001865 else:
Michael Mossd683d7c2018-06-15 05:05:17 +00001866 entries = {}
1867 for d in self.root.subtree(False):
1868 if self._options.actual:
1869 d.PinToActualRevision()
1870 if ShouldPrintRevision(d):
1871 entries[d.name] = d.url
1872 if self._options.output_json:
1873 json_output = {
1874 name: {
1875 'url': rev.split('@')[0] if rev else None,
1876 'rev': rev.split('@')[1] if rev and '@' in rev else None,
1877 }
Marc-Antoine Ruel8e57b4b2019-10-11 01:01:36 +00001878 for name, rev in entries.items()
Michael Mossd683d7c2018-06-15 05:05:17 +00001879 }
1880 if self._options.output_json == '-':
1881 print(json.dumps(json_output, indent=2, separators=(',', ': ')))
1882 else:
1883 with open(self._options.output_json, 'w') as f:
1884 json.dump(json_output, f)
1885 else:
1886 keys = sorted(entries.keys())
1887 for x in keys:
1888 print('%s: %s' % (x, entries[x]))
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +00001889 logging.info(str(self))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001890
Edward Lemure05f18d2018-06-08 17:36:53 +00001891 def ParseDepsFile(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001892 """No DEPS to parse for a .gclient file."""
maruel@chromium.org049bced2010-08-12 13:37:20 +00001893 raise gclient_utils.Error('Internal error')
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001894
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00001895 def PrintLocationAndContents(self):
1896 # Print out the .gclient file. This is longer than if we just printed the
1897 # client dict, but more legible, and it might contain helpful comments.
1898 print('Loaded .gclient config in %s:\n%s' % (
1899 self.root_dir, self.config_content))
1900
John Budorickd3ba72b2018-03-20 12:27:42 -07001901 def GetCipdRoot(self):
1902 if not self._cipd_root:
1903 self._cipd_root = gclient_scm.CipdRoot(
1904 self.root_dir,
1905 # TODO(jbudorick): Support other service URLs as necessary.
1906 # Service URLs should be constant over the scope of a cipd
1907 # root, so a var per DEPS file specifying the service URL
1908 # should suffice.
1909 'https://chrome-infra-packages.appspot.com')
1910 return self._cipd_root
1911
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001912 @property
maruel@chromium.org75a59272010-06-11 22:34:03 +00001913 def root_dir(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001914 """Root directory of gclient checkout."""
maruel@chromium.org75a59272010-06-11 22:34:03 +00001915 return self._root_dir
1916
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001917 @property
maruel@chromium.org271375b2010-06-23 19:17:38 +00001918 def enforced_os(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001919 """What deps_os entries that are to be parsed."""
maruel@chromium.org271375b2010-06-23 19:17:38 +00001920 return self._enforced_os
1921
maruel@chromium.org68988972011-09-20 14:11:42 +00001922 @property
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +00001923 def target_os(self):
1924 return self._enforced_os
1925
Tom Andersonc31ae0b2018-02-06 14:48:56 -08001926 @property
1927 def target_cpu(self):
1928 return self._enforced_cpu
1929
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001930
John Budorick0f7b2002018-01-19 15:46:17 -08001931class CipdDependency(Dependency):
1932 """A Dependency object that represents a single CIPD package."""
1933
Michael Mossd683d7c2018-06-15 05:05:17 +00001934 def __init__(
1935 self, parent, name, dep_value, cipd_root,
1936 custom_vars, should_process, relative, condition):
John Budorick0f7b2002018-01-19 15:46:17 -08001937 package = dep_value['package']
1938 version = dep_value['version']
1939 url = urlparse.urljoin(
1940 cipd_root.service_url, '%s@%s' % (package, version))
1941 super(CipdDependency, self).__init__(
Edward Lemure05f18d2018-06-08 17:36:53 +00001942 parent=parent,
1943 name=name + ':' + package,
1944 url=url,
1945 managed=None,
1946 custom_deps=None,
1947 custom_vars=custom_vars,
1948 custom_hooks=None,
1949 deps_file=None,
Michael Mossd683d7c2018-06-15 05:05:17 +00001950 should_process=should_process,
Edward Lemurfbb06aa2018-06-11 20:43:06 +00001951 should_recurse=False,
Edward Lemure05f18d2018-06-08 17:36:53 +00001952 relative=relative,
1953 condition=condition)
John Budorickd3ba72b2018-03-20 12:27:42 -07001954 self._cipd_package = None
John Budorick0f7b2002018-01-19 15:46:17 -08001955 self._cipd_root = cipd_root
John Budorick4099daa2018-06-21 19:22:10 +00001956 # CIPD wants /-separated paths, even on Windows.
1957 native_subdir_path = os.path.relpath(
Shenghua Zhang6f830312018-02-26 11:45:07 -08001958 os.path.join(self.root.root_dir, name), cipd_root.root_dir)
John Budorick4099daa2018-06-21 19:22:10 +00001959 self._cipd_subdir = posixpath.join(*native_subdir_path.split(os.sep))
John Budorickd3ba72b2018-03-20 12:27:42 -07001960 self._package_name = package
1961 self._package_version = version
1962
1963 #override
Edward Lesmesc621b212018-03-21 20:26:56 -04001964 def run(self, revision_overrides, command, args, work_queue, options,
Edward Lemur6a4e31b2018-08-10 19:59:02 +00001965 patch_refs, target_branches):
John Budorickd3ba72b2018-03-20 12:27:42 -07001966 """Runs |command| then parse the DEPS file."""
1967 logging.info('CipdDependency(%s).run()' % self.name)
Michael Mossd683d7c2018-06-15 05:05:17 +00001968 if not self.should_process:
1969 return
John Budorickd3ba72b2018-03-20 12:27:42 -07001970 self._CreatePackageIfNecessary()
1971 super(CipdDependency, self).run(revision_overrides, command, args,
Edward Lemur6a4e31b2018-08-10 19:59:02 +00001972 work_queue, options, patch_refs,
1973 target_branches)
John Budorickd3ba72b2018-03-20 12:27:42 -07001974
1975 def _CreatePackageIfNecessary(self):
1976 # We lazily create the CIPD package to make sure that only packages
1977 # that we want (as opposed to all packages defined in all DEPS files
1978 # we parse) get added to the root and subsequently ensured.
1979 if not self._cipd_package:
1980 self._cipd_package = self._cipd_root.add_package(
1981 self._cipd_subdir, self._package_name, self._package_version)
John Budorick0f7b2002018-01-19 15:46:17 -08001982
Edward Lemure05f18d2018-06-08 17:36:53 +00001983 def ParseDepsFile(self):
John Budorick0f7b2002018-01-19 15:46:17 -08001984 """CIPD dependencies are not currently allowed to have nested deps."""
1985 self.add_dependencies_and_close([], [])
1986
1987 #override
Shenghua Zhang6f830312018-02-26 11:45:07 -08001988 def verify_validity(self):
1989 """CIPD dependencies allow duplicate name for packages in same directory."""
1990 logging.info('Dependency(%s).verify_validity()' % self.name)
1991 return True
1992
1993 #override
Edward Lemurbabd0982018-05-11 13:32:37 -04001994 def GetScmName(self):
John Budorick0f7b2002018-01-19 15:46:17 -08001995 """Always 'cipd'."""
John Budorick0f7b2002018-01-19 15:46:17 -08001996 return 'cipd'
1997
1998 #override
Edward Lemurbabd0982018-05-11 13:32:37 -04001999 def CreateSCM(self, out_cb=None):
John Budorick0f7b2002018-01-19 15:46:17 -08002000 """Create a Wrapper instance suitable for handling this CIPD dependency."""
John Budorickd3ba72b2018-03-20 12:27:42 -07002001 self._CreatePackageIfNecessary()
John Budorick0f7b2002018-01-19 15:46:17 -08002002 return gclient_scm.CipdWrapper(
Edward Lemurbabd0982018-05-11 13:32:37 -04002003 self.url, self.root.root_dir, self.name, self.outbuf, out_cb,
2004 root=self._cipd_root, package=self._cipd_package)
John Budorick0f7b2002018-01-19 15:46:17 -08002005
Edward Lemure4e15042018-06-28 18:07:00 +00002006 def hierarchy(self, include_url=False):
2007 return self.parent.hierarchy(include_url) + ' -> ' + self._cipd_subdir
2008
John Budorick0f7b2002018-01-19 15:46:17 -08002009 def ToLines(self):
2010 """Return a list of lines representing this in a DEPS file."""
John Budorickc35aba52018-06-28 20:57:03 +00002011 def escape_cipd_var(package):
2012 return package.replace('{', '{{').replace('}', '}}')
2013
John Budorick0f7b2002018-01-19 15:46:17 -08002014 s = []
John Budorickd3ba72b2018-03-20 12:27:42 -07002015 self._CreatePackageIfNecessary()
John Budorick0f7b2002018-01-19 15:46:17 -08002016 if self._cipd_package.authority_for_subdir:
2017 condition_part = ([' "condition": %r,' % self.condition]
2018 if self.condition else [])
2019 s.extend([
Michael Moss4e9b50a2018-05-23 22:35:06 -07002020 ' # %s' % self.hierarchy(include_url=False),
John Budorickd3ba72b2018-03-20 12:27:42 -07002021 ' "%s": {' % (self.name.split(':')[0],),
John Budorick0f7b2002018-01-19 15:46:17 -08002022 ' "packages": [',
2023 ])
John Budorick4099daa2018-06-21 19:22:10 +00002024 for p in sorted(
2025 self._cipd_root.packages(self._cipd_subdir),
Edward Lemur26a8b9f2019-08-15 20:46:44 +00002026 key=lambda x: x.name):
John Budorick0f7b2002018-01-19 15:46:17 -08002027 s.extend([
John Budorick64e33cb2018-02-20 09:40:30 -08002028 ' {',
John Budorickc35aba52018-06-28 20:57:03 +00002029 ' "package": "%s",' % escape_cipd_var(p.name),
John Budorick64e33cb2018-02-20 09:40:30 -08002030 ' "version": "%s",' % p.version,
2031 ' },',
John Budorick0f7b2002018-01-19 15:46:17 -08002032 ])
John Budorickd3ba72b2018-03-20 12:27:42 -07002033
John Budorick0f7b2002018-01-19 15:46:17 -08002034 s.extend([
2035 ' ],',
2036 ' "dep_type": "cipd",',
2037 ] + condition_part + [
2038 ' },',
2039 '',
2040 ])
2041 return s
2042
2043
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002044#### gclient commands.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002045
2046
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002047@subcommand.usage('[command] [args ...]')
Edward Lemur3298e7b2018-07-17 18:21:27 +00002048@metrics.collector.collect_metrics('gclient recurse')
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00002049def CMDrecurse(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002050 """Operates [command args ...] on all the dependencies.
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00002051
2052 Runs a shell command on all entries.
qyearsley12fa6ff2016-08-24 09:18:40 -07002053 Sets GCLIENT_DEP_PATH environment variable as the dep's relative location to
ilevy@chromium.org37116242012-11-28 01:32:48 +00002054 root directory of the checkout.
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00002055 """
2056 # Stop parsing at the first non-arg so that these go through to the command
2057 parser.disable_interspersed_args()
2058 parser.add_option('-s', '--scm', action='append', default=[],
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00002059 help='Choose scm types to operate upon.')
maruel@chromium.org288054d2012-03-05 00:43:07 +00002060 parser.add_option('-i', '--ignore', action='store_true',
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00002061 help='Ignore non-zero return codes from subcommands.')
2062 parser.add_option('--prepend-dir', action='store_true',
2063 help='Prepend relative dir for use with git <cmd> --null.')
2064 parser.add_option('--no-progress', action='store_true',
2065 help='Disable progress bar that shows sub-command updates')
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00002066 options, args = parser.parse_args(args)
maruel@chromium.org45e9f2d2010-10-18 13:33:46 +00002067 if not args:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002068 print('Need to supply a command!', file=sys.stderr)
maruel@chromium.org45e9f2d2010-10-18 13:33:46 +00002069 return 1
maruel@chromium.org78cba522010-10-18 13:32:05 +00002070 root_and_entries = gclient_utils.GetGClientRootAndEntries()
2071 if not root_and_entries:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002072 print(
maruel@chromium.org78cba522010-10-18 13:32:05 +00002073 'You need to run gclient sync at least once to use \'recurse\'.\n'
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002074 'This is because .gclient_entries needs to exist and be up to date.',
2075 file=sys.stderr)
maruel@chromium.org78cba522010-10-18 13:32:05 +00002076 return 1
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00002077
2078 # Normalize options.scm to a set()
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00002079 scm_set = set()
2080 for scm in options.scm:
2081 scm_set.update(scm.split(','))
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00002082 options.scm = scm_set
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00002083
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00002084 options.nohooks = True
2085 client = GClient.LoadCurrentConfig(options)
Marc-Antoine Ruele6e06412017-10-18 13:47:02 -04002086 if not client:
2087 raise gclient_utils.Error('client not configured; see \'gclient config\'')
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00002088 return client.RunOnDeps('recurse', args, ignore_requirements=True,
2089 progress=not options.no_progress)
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00002090
2091
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002092@subcommand.usage('[args ...]')
Edward Lemur3298e7b2018-07-17 18:21:27 +00002093@metrics.collector.collect_metrics('gclient fetch')
davidbarr@chromium.org12f944e2012-03-01 02:18:31 +00002094def CMDfetch(parser, args):
2095 """Fetches upstream commits for all modules.
2096
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002097 Completely git-specific. Simply runs 'git fetch [args ...]' for each module.
2098 """
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00002099 (options, args) = parser.parse_args(args)
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002100 return CMDrecurse(OptionParser(), [
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00002101 '--jobs=%d' % options.jobs, '--scm=git', 'git', 'fetch'] + args)
2102
2103
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002104class Flattener(object):
2105 """Flattens a gclient solution."""
2106
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02002107 def __init__(self, client, pin_all_deps=False):
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002108 """Constructor.
2109
2110 Arguments:
2111 client (GClient): client to flatten
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02002112 pin_all_deps (bool): whether to pin all deps, even if they're not pinned
2113 in DEPS
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002114 """
2115 self._client = client
2116
2117 self._deps_string = None
Paweł Hajdan, Jrfeb01642017-09-12 15:50:46 +02002118 self._deps_files = set()
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002119
2120 self._allowed_hosts = set()
2121 self._deps = {}
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002122 self._hooks = []
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002123 self._pre_deps_hooks = []
Paweł Hajdan, Jrfb022012017-07-06 18:00:08 +02002124 self._vars = {}
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002125
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02002126 self._flatten(pin_all_deps=pin_all_deps)
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002127
2128 @property
2129 def deps_string(self):
2130 assert self._deps_string is not None
2131 return self._deps_string
2132
Paweł Hajdan, Jrfeb01642017-09-12 15:50:46 +02002133 @property
2134 def deps_files(self):
2135 return self._deps_files
2136
Paweł Hajdan, Jr5ec77132017-08-16 19:21:06 +02002137 def _pin_dep(self, dep):
2138 """Pins a dependency to specific full revision sha.
2139
2140 Arguments:
2141 dep (Dependency): dependency to process
2142 """
Michael Mossd683d7c2018-06-15 05:05:17 +00002143 if dep.url is None:
2144 return
2145
Paweł Hajdan, Jr5ec77132017-08-16 19:21:06 +02002146 # Make sure the revision is always fully specified (a hash),
2147 # as opposed to refs or tags which might change. Similarly,
2148 # shortened shas might become ambiguous; make sure to always
2149 # use full one for pinning.
Edward Lemure7273d22018-05-10 19:13:51 -04002150 revision = gclient_utils.SplitUrlRevision(dep.url)[1]
2151 if not revision or not gclient_utils.IsFullGitSha(revision):
2152 dep.PinToActualRevision()
Paweł Hajdan, Jr5ec77132017-08-16 19:21:06 +02002153
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02002154 def _flatten(self, pin_all_deps=False):
2155 """Runs the flattener. Saves resulting DEPS string.
2156
2157 Arguments:
2158 pin_all_deps (bool): whether to pin all deps, even if they're not pinned
2159 in DEPS
2160 """
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002161 for solution in self._client.dependencies:
Paweł Hajdan, Jr11eb7152017-08-10 12:50:11 +02002162 self._add_dep(solution)
Michael Mossd683d7c2018-06-15 05:05:17 +00002163 self._flatten_dep(solution)
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002164
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02002165 if pin_all_deps:
Marc-Antoine Ruel8e57b4b2019-10-11 01:01:36 +00002166 for dep in self._deps.values():
Paweł Hajdan, Jr5ec77132017-08-16 19:21:06 +02002167 self._pin_dep(dep)
Paweł Hajdan, Jr39300ba2017-08-11 16:52:38 +02002168
Paweł Hajdan, Jr76a9d042017-08-18 20:05:41 +02002169 def add_deps_file(dep):
Paweł Hajdan, Jr0870df22017-08-23 17:59:29 +02002170 # Only include DEPS files referenced by recursedeps.
Edward Lemurfbb06aa2018-06-11 20:43:06 +00002171 if not dep.should_recurse:
Paweł Hajdan, Jr0870df22017-08-23 17:59:29 +02002172 return
Paweł Hajdan, Jrfeb01642017-09-12 15:50:46 +02002173 deps_file = dep.deps_file
2174 deps_path = os.path.join(self._client.root_dir, dep.name, deps_file)
Paweł Hajdan, Jr76a9d042017-08-18 20:05:41 +02002175 if not os.path.exists(deps_path):
Paweł Hajdan, Jrfeb01642017-09-12 15:50:46 +02002176 # gclient has a fallback that if deps_file doesn't exist, it'll try
2177 # DEPS. Do the same here.
2178 deps_file = 'DEPS'
2179 deps_path = os.path.join(self._client.root_dir, dep.name, deps_file)
2180 if not os.path.exists(deps_path):
2181 return
Michael Mossd683d7c2018-06-15 05:05:17 +00002182 assert dep.url
Edward Lemure7273d22018-05-10 19:13:51 -04002183 self._deps_files.add((dep.url, deps_file, dep.hierarchy_data()))
Marc-Antoine Ruel8e57b4b2019-10-11 01:01:36 +00002184 for dep in self._deps.values():
Paweł Hajdan, Jr76a9d042017-08-18 20:05:41 +02002185 add_deps_file(dep)
Paweł Hajdan, Jr76a9d042017-08-18 20:05:41 +02002186
Michael Moss848c86e2018-05-03 16:05:50 -07002187 gn_args_dep = self._deps.get(self._client.dependencies[0]._gn_args_from,
2188 self._client.dependencies[0])
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002189 self._deps_string = '\n'.join(
Michael Moss848c86e2018-05-03 16:05:50 -07002190 _GNSettingsToLines(gn_args_dep._gn_args_file, gn_args_dep._gn_args) +
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002191 _AllowedHostsToLines(self._allowed_hosts) +
2192 _DepsToLines(self._deps) +
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002193 _HooksToLines('hooks', self._hooks) +
2194 _HooksToLines('pre_deps_hooks', self._pre_deps_hooks) +
Paweł Hajdan, Jrfb022012017-07-06 18:00:08 +02002195 _VarsToLines(self._vars) +
Paweł Hajdan, Jr76a9d042017-08-18 20:05:41 +02002196 ['# %s, %s' % (url, deps_file)
Michael Mossfe68c912018-03-22 19:19:35 -07002197 for url, deps_file, _ in sorted(self._deps_files)] +
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002198 ['']) # Ensure newline at end of file.
2199
Paweł Hajdan, Jr11eb7152017-08-10 12:50:11 +02002200 def _add_dep(self, dep):
2201 """Helper to add a dependency to flattened DEPS.
2202
2203 Arguments:
2204 dep (Dependency): dependency to add
2205 """
2206 assert dep.name not in self._deps or self._deps.get(dep.name) == dep, (
2207 dep.name, self._deps.get(dep.name))
Michael Mossd683d7c2018-06-15 05:05:17 +00002208 if dep.url:
2209 self._deps[dep.name] = dep
Paweł Hajdan, Jr11eb7152017-08-10 12:50:11 +02002210
Edward Lemur16f4bad2018-05-16 16:53:49 -04002211 def _flatten_dep(self, dep):
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002212 """Visits a dependency in order to flatten it (see CMDflatten).
2213
2214 Arguments:
2215 dep (Dependency): dependency to process
2216 """
Edward Lemur16f4bad2018-05-16 16:53:49 -04002217 logging.debug('_flatten_dep(%s)', dep.name)
Paweł Hajdan, Jradae2a62017-08-18 16:49:57 +02002218
Edward Lemur16f4bad2018-05-16 16:53:49 -04002219 assert dep.deps_parsed, (
2220 "Attempted to flatten %s but it has not been processed." % dep.name)
Paweł Hajdan, Jrc69b32e2017-08-17 18:47:48 +02002221
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002222 self._allowed_hosts.update(dep.allowed_hosts)
2223
Michael Mossce9f17f2018-01-31 13:16:35 -08002224 # Only include vars explicitly listed in the DEPS files or gclient solution,
2225 # not automatic, local overrides (i.e. not all of dep.get_vars()).
Michael Moss4e9b50a2018-05-23 22:35:06 -07002226 hierarchy = dep.hierarchy(include_url=False)
Marc-Antoine Ruel8e57b4b2019-10-11 01:01:36 +00002227 for key, value in dep._vars.items():
Paweł Hajdan, Jrc9353602017-08-02 17:52:08 +02002228 # Make sure there are no conflicting variables. It is fine however
2229 # to use same variable name, as long as the value is consistent.
Takuto Ikuta575872e2019-02-21 15:20:07 +00002230 assert key not in self._vars or self._vars[key][1] == value, (
2231 "dep:%s key:%s value:%s != %s" % (
2232 dep.name, key, value, self._vars[key][1]))
Michael Mossce9f17f2018-01-31 13:16:35 -08002233 self._vars[key] = (hierarchy, value)
2234 # Override explicit custom variables.
Marc-Antoine Ruel8e57b4b2019-10-11 01:01:36 +00002235 for key, value in dep.custom_vars.items():
Michael Mossce9f17f2018-01-31 13:16:35 -08002236 # Do custom_vars that don't correspond to DEPS vars ever make sense? DEPS
2237 # conditionals shouldn't be using vars that aren't also defined in the
2238 # DEPS (presubmit actually disallows this), so any new custom_var must be
2239 # unused in the DEPS, so no need to add it to the flattened output either.
2240 if key not in self._vars:
2241 continue
2242 # Don't "override" existing vars if it's actually the same value.
2243 elif self._vars[key][1] == value:
2244 continue
2245 # Anything else is overriding a default value from the DEPS.
2246 self._vars[key] = (hierarchy + ' [custom_var override]', value)
Paweł Hajdan, Jrfb022012017-07-06 18:00:08 +02002247
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002248 self._pre_deps_hooks.extend([(dep, hook) for hook in dep.pre_deps_hooks])
Edward Lemur16f4bad2018-05-16 16:53:49 -04002249 self._hooks.extend([(dep, hook) for hook in dep.deps_hooks])
Paweł Hajdan, Jradae2a62017-08-18 16:49:57 +02002250
Paweł Hajdan, Jrb0ad16e2017-08-03 15:33:21 +02002251 for sub_dep in dep.dependencies:
Edward Lemur16f4bad2018-05-16 16:53:49 -04002252 self._add_dep(sub_dep)
Paweł Hajdan, Jrb0ad16e2017-08-03 15:33:21 +02002253
Edward Lemurfbb06aa2018-06-11 20:43:06 +00002254 for d in dep.dependencies:
2255 if d.should_recurse:
2256 self._flatten_dep(d)
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002257
2258
Edward Lemur3298e7b2018-07-17 18:21:27 +00002259@metrics.collector.collect_metrics('gclient flatten')
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002260def CMDflatten(parser, args):
2261 """Flattens the solutions into a single DEPS file."""
2262 parser.add_option('--output-deps', help='Path to the output DEPS file')
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02002263 parser.add_option(
Paweł Hajdan, Jrfeb01642017-09-12 15:50:46 +02002264 '--output-deps-files',
2265 help=('Path to the output metadata about DEPS files referenced by '
2266 'recursedeps.'))
2267 parser.add_option(
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02002268 '--pin-all-deps', action='store_true',
2269 help=('Pin all deps, even if not pinned in DEPS. CAVEAT: only does so '
2270 'for checked out deps, NOT deps_os.'))
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002271 options, args = parser.parse_args(args)
2272
2273 options.nohooks = True
Paweł Hajdan, Jr5ec77132017-08-16 19:21:06 +02002274 options.process_all_deps = True
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002275 client = GClient.LoadCurrentConfig(options)
2276
2277 # Only print progress if we're writing to a file. Otherwise, progress updates
2278 # could obscure intended output.
2279 code = client.RunOnDeps('flatten', args, progress=options.output_deps)
2280 if code != 0:
2281 return code
2282
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02002283 flattener = Flattener(client, pin_all_deps=options.pin_all_deps)
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002284
2285 if options.output_deps:
2286 with open(options.output_deps, 'w') as f:
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002287 f.write(flattener.deps_string)
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002288 else:
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002289 print(flattener.deps_string)
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002290
Michael Mossfe68c912018-03-22 19:19:35 -07002291 deps_files = [{'url': d[0], 'deps_file': d[1], 'hierarchy': d[2]}
Paweł Hajdan, Jrfeb01642017-09-12 15:50:46 +02002292 for d in sorted(flattener.deps_files)]
2293 if options.output_deps_files:
2294 with open(options.output_deps_files, 'w') as f:
2295 json.dump(deps_files, f)
2296
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002297 return 0
2298
2299
Paweł Hajdan, Jr3c2aa832017-06-07 20:22:16 +02002300def _GNSettingsToLines(gn_args_file, gn_args):
2301 s = []
2302 if gn_args_file:
2303 s.extend([
2304 'gclient_gn_args_file = "%s"' % gn_args_file,
2305 'gclient_gn_args = %r' % gn_args,
2306 ])
2307 return s
2308
2309
Paweł Hajdan, Jr6014b562017-06-30 17:43:42 +02002310def _AllowedHostsToLines(allowed_hosts):
2311 """Converts |allowed_hosts| set to list of lines for output."""
2312 if not allowed_hosts:
2313 return []
2314 s = ['allowed_hosts = [']
2315 for h in sorted(allowed_hosts):
2316 s.append(' "%s",' % h)
2317 s.extend([']', ''])
2318 return s
2319
2320
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002321def _DepsToLines(deps):
2322 """Converts |deps| dict to list of lines for output."""
Paweł Hajdan, Jr5b593352017-06-29 18:37:45 +02002323 if not deps:
2324 return []
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002325 s = ['deps = {']
Edward Lemuree7b9dd2019-07-20 01:29:08 +00002326 for _, dep in sorted(deps.items()):
John Budorick0f7b2002018-01-19 15:46:17 -08002327 s.extend(dep.ToLines())
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002328 s.extend(['}', ''])
2329 return s
2330
2331
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02002332def _DepsOsToLines(deps_os):
2333 """Converts |deps_os| dict to list of lines for output."""
Paweł Hajdan, Jr5b593352017-06-29 18:37:45 +02002334 if not deps_os:
2335 return []
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02002336 s = ['deps_os = {']
Edward Lemuree7b9dd2019-07-20 01:29:08 +00002337 for dep_os, os_deps in sorted(deps_os.items()):
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02002338 s.append(' "%s": {' % dep_os)
Edward Lemuree7b9dd2019-07-20 01:29:08 +00002339 for name, dep in sorted(os_deps.items()):
Paweł Hajdan, Jr78ce24e2017-10-03 17:09:13 +02002340 condition_part = ([' "condition": %r,' % dep.condition]
Paweł Hajdan, Jrad30de62017-06-26 18:51:58 +02002341 if dep.condition else [])
2342 s.extend([
Michael Moss4e9b50a2018-05-23 22:35:06 -07002343 ' # %s' % dep.hierarchy(include_url=False),
Paweł Hajdan, Jrad30de62017-06-26 18:51:58 +02002344 ' "%s": {' % (name,),
Edward Lemure05f18d2018-06-08 17:36:53 +00002345 ' "url": "%s",' % (dep.url,),
Paweł Hajdan, Jrad30de62017-06-26 18:51:58 +02002346 ] + condition_part + [
2347 ' },',
2348 '',
2349 ])
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02002350 s.extend([' },', ''])
2351 s.extend(['}', ''])
2352 return s
2353
2354
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002355def _HooksToLines(name, hooks):
2356 """Converts |hooks| list to list of lines for output."""
Paweł Hajdan, Jr5b593352017-06-29 18:37:45 +02002357 if not hooks:
2358 return []
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002359 s = ['%s = [' % name]
2360 for dep, hook in hooks:
2361 s.extend([
Michael Moss4e9b50a2018-05-23 22:35:06 -07002362 ' # %s' % dep.hierarchy(include_url=False),
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002363 ' {',
2364 ])
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +02002365 if hook.name is not None:
2366 s.append(' "name": "%s",' % hook.name)
2367 if hook.pattern is not None:
2368 s.append(' "pattern": "%s",' % hook.pattern)
Paweł Hajdan, Jrecf53fe2017-09-29 18:28:49 +02002369 if hook.condition is not None:
Paweł Hajdan, Jr78ce24e2017-10-03 17:09:13 +02002370 s.append(' "condition": %r,' % hook.condition)
Corentin Walleza68660d2018-09-10 17:33:24 +00002371 # Flattened hooks need to be written relative to the root gclient dir
2372 cwd = os.path.relpath(os.path.normpath(hook.effective_cwd))
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002373 s.extend(
Corentin Walleza68660d2018-09-10 17:33:24 +00002374 [' "cwd": "%s",' % cwd] +
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002375 [' "action": ['] +
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +02002376 [' "%s",' % arg for arg in hook.action] +
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002377 [' ]', ' },', '']
2378 )
2379 s.extend([']', ''])
2380 return s
2381
2382
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +02002383def _HooksOsToLines(hooks_os):
2384 """Converts |hooks| list to list of lines for output."""
Paweł Hajdan, Jr5b593352017-06-29 18:37:45 +02002385 if not hooks_os:
2386 return []
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +02002387 s = ['hooks_os = {']
Marc-Antoine Ruel8e57b4b2019-10-11 01:01:36 +00002388 for hook_os, os_hooks in hooks_os.items():
Michael Moss017bcf62017-06-28 15:26:38 -07002389 s.append(' "%s": [' % hook_os)
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +02002390 for dep, hook in os_hooks:
2391 s.extend([
Michael Moss4e9b50a2018-05-23 22:35:06 -07002392 ' # %s' % dep.hierarchy(include_url=False),
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +02002393 ' {',
2394 ])
2395 if hook.name is not None:
2396 s.append(' "name": "%s",' % hook.name)
2397 if hook.pattern is not None:
2398 s.append(' "pattern": "%s",' % hook.pattern)
Paweł Hajdan, Jrecf53fe2017-09-29 18:28:49 +02002399 if hook.condition is not None:
Paweł Hajdan, Jr78ce24e2017-10-03 17:09:13 +02002400 s.append(' "condition": %r,' % hook.condition)
Corentin Walleza68660d2018-09-10 17:33:24 +00002401 # Flattened hooks need to be written relative to the root gclient dir
2402 cwd = os.path.relpath(os.path.normpath(hook.effective_cwd))
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +02002403 s.extend(
Corentin Walleza68660d2018-09-10 17:33:24 +00002404 [' "cwd": "%s",' % cwd] +
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +02002405 [' "action": ['] +
2406 [' "%s",' % arg for arg in hook.action] +
2407 [' ]', ' },', '']
2408 )
Michael Moss017bcf62017-06-28 15:26:38 -07002409 s.extend([' ],', ''])
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +02002410 s.extend(['}', ''])
2411 return s
2412
2413
Paweł Hajdan, Jrfb022012017-07-06 18:00:08 +02002414def _VarsToLines(variables):
2415 """Converts |variables| dict to list of lines for output."""
2416 if not variables:
2417 return []
2418 s = ['vars = {']
Edward Lemuree7b9dd2019-07-20 01:29:08 +00002419 for key, tup in sorted(variables.items()):
Michael Mossce9f17f2018-01-31 13:16:35 -08002420 hierarchy, value = tup
Paweł Hajdan, Jrfb022012017-07-06 18:00:08 +02002421 s.extend([
Michael Mossce9f17f2018-01-31 13:16:35 -08002422 ' # %s' % hierarchy,
Paweł Hajdan, Jrfb022012017-07-06 18:00:08 +02002423 ' "%s": %r,' % (key, value),
2424 '',
2425 ])
2426 s.extend(['}', ''])
2427 return s
2428
2429
Edward Lemur3298e7b2018-07-17 18:21:27 +00002430@metrics.collector.collect_metrics('gclient grep')
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00002431def CMDgrep(parser, args):
2432 """Greps through git repos managed by gclient.
2433
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002434 Runs 'git grep [args...]' for each module.
2435 """
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00002436 # We can't use optparse because it will try to parse arguments sent
2437 # to git grep and throw an error. :-(
2438 if not args or re.match('(-h|--help)$', args[0]):
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002439 print(
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00002440 'Usage: gclient grep [-j <N>] git-grep-args...\n\n'
2441 'Example: "gclient grep -j10 -A2 RefCountedBase" runs\n"git grep '
2442 '-A2 RefCountedBase" on each of gclient\'s git\nrepos with up to '
2443 '10 jobs.\n\nBonus: page output by appending "|& less -FRSX" to the'
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002444 ' end of your query.',
2445 file=sys.stderr)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00002446 return 1
2447
2448 jobs_arg = ['--jobs=1']
2449 if re.match(r'(-j|--jobs=)\d+$', args[0]):
2450 jobs_arg, args = args[:1], args[1:]
2451 elif re.match(r'(-j|--jobs)$', args[0]):
2452 jobs_arg, args = args[:2], args[2:]
2453
2454 return CMDrecurse(
2455 parser,
2456 jobs_arg + ['--ignore', '--prepend-dir', '--no-progress', '--scm=git',
2457 'git', 'grep', '--null', '--color=Always'] + args)
davidbarr@chromium.org12f944e2012-03-01 02:18:31 +00002458
2459
Edward Lemur3298e7b2018-07-17 18:21:27 +00002460@metrics.collector.collect_metrics('gclient root')
stip@chromium.orga735da22015-04-29 23:18:20 +00002461def CMDroot(parser, args):
2462 """Outputs the solution root (or current dir if there isn't one)."""
2463 (options, args) = parser.parse_args(args)
2464 client = GClient.LoadCurrentConfig(options)
2465 if client:
2466 print(os.path.abspath(client.root_dir))
2467 else:
2468 print(os.path.abspath('.'))
2469
2470
agablea98a6cd2016-11-15 14:30:10 -08002471@subcommand.usage('[url]')
Edward Lemur3298e7b2018-07-17 18:21:27 +00002472@metrics.collector.collect_metrics('gclient config')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002473def CMDconfig(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002474 """Creates a .gclient file in the current directory.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00002475
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002476 This specifies the configuration for further commands. After update/sync,
2477 top-level DEPS files in each module are read to determine dependent
2478 modules to operate on as well. If optional [url] parameter is
2479 provided, then configuration is read from a specified Subversion server
2480 URL.
2481 """
szager@chromium.orge2e03202012-07-31 18:05:16 +00002482 # We do a little dance with the --gclientfile option. 'gclient config' is the
2483 # only command where it's acceptable to have both '--gclientfile' and '--spec'
2484 # arguments. So, we temporarily stash any --gclientfile parameter into
2485 # options.output_config_file until after the (gclientfile xor spec) error
2486 # check.
2487 parser.remove_option('--gclientfile')
2488 parser.add_option('--gclientfile', dest='output_config_file',
2489 help='Specify an alternate .gclient file')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002490 parser.add_option('--name',
2491 help='overrides the default name for the solution')
nsylvain@google.comefc80932011-05-31 21:27:56 +00002492 parser.add_option('--deps-file', default='DEPS',
David Benjamin105e11e2017-10-16 10:39:35 -04002493 help='overrides the default name for the DEPS file for the '
nsylvain@google.comefc80932011-05-31 21:27:56 +00002494 'main solutions and all sub-dependencies')
smutae7ea312016-07-18 11:59:41 -07002495 parser.add_option('--unmanaged', action='store_true', default=False,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00002496 help='overrides the default behavior to make it possible '
smutae7ea312016-07-18 11:59:41 -07002497 'to have the main solution untouched by gclient '
2498 '(gclient will check out unmanaged dependencies but '
2499 'will never sync them)')
Robert Iannuccia19649b2018-06-29 16:31:45 +00002500 parser.add_option('--cache-dir', default=UNSET_CACHE_DIR,
2501 help='Cache all git repos into this dir and do shared '
2502 'clones from the cache, instead of cloning directly '
2503 'from the remote. Pass "None" to disable cache, even '
2504 'if globally enabled due to $GIT_CACHE_PATH.')
Paweł Hajdan, Jr3ba2a7c2017-10-04 19:24:46 +02002505 parser.add_option('--custom-var', action='append', dest='custom_vars',
2506 default=[],
2507 help='overrides variables; key=value syntax')
szager@chromium.orge2e03202012-07-31 18:05:16 +00002508 parser.set_defaults(config_filename=None)
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002509 (options, args) = parser.parse_args(args)
szager@chromium.orge2e03202012-07-31 18:05:16 +00002510 if options.output_config_file:
2511 setattr(options, 'config_filename', getattr(options, 'output_config_file'))
maruel@chromium.org5fc2a332010-05-26 19:37:15 +00002512 if ((options.spec and args) or len(args) > 2 or
2513 (not options.spec and not args)):
2514 parser.error('Inconsistent arguments. Use either --spec or one or 2 args')
2515
Robert Iannuccia19649b2018-06-29 16:31:45 +00002516 if (options.cache_dir is not UNSET_CACHE_DIR
2517 and options.cache_dir.lower() == 'none'):
2518 options.cache_dir = None
2519
Paweł Hajdan, Jr3ba2a7c2017-10-04 19:24:46 +02002520 custom_vars = {}
2521 for arg in options.custom_vars:
2522 kv = arg.split('=', 1)
2523 if len(kv) != 2:
2524 parser.error('Invalid --custom-var argument: %r' % arg)
2525 custom_vars[kv[0]] = gclient_eval.EvaluateCondition(kv[1], {})
2526
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002527 client = GClient('.', options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002528 if options.spec:
2529 client.SetConfig(options.spec)
2530 else:
maruel@chromium.org1ab7ffc2009-06-03 17:21:37 +00002531 base_url = args[0].rstrip('/')
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00002532 if not options.name:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002533 name = base_url.split('/')[-1]
nsylvain@google.com12649ef2011-06-01 17:11:20 +00002534 if name.endswith('.git'):
2535 name = name[:-4]
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00002536 else:
2537 # specify an alternate relpath for the given URL.
2538 name = options.name
agable@chromium.orgf2214672015-10-27 21:02:48 +00002539 if not os.path.abspath(os.path.join(os.getcwd(), name)).startswith(
2540 os.getcwd()):
2541 parser.error('Do not pass a relative path for --name.')
2542 if any(x in ('..', '.', '/', '\\') for x in name.split(os.sep)):
2543 parser.error('Do not include relative path components in --name.')
2544
nsylvain@google.comefc80932011-05-31 21:27:56 +00002545 deps_file = options.deps_file
agablea98a6cd2016-11-15 14:30:10 -08002546 client.SetDefaultConfig(name, deps_file, base_url,
smutae7ea312016-07-18 11:59:41 -07002547 managed=not options.unmanaged,
Paweł Hajdan, Jr3ba2a7c2017-10-04 19:24:46 +02002548 cache_dir=options.cache_dir,
2549 custom_vars=custom_vars)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002550 client.SaveConfig()
maruel@chromium.org79692d62010-05-14 18:57:13 +00002551 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002552
2553
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002554@subcommand.epilog("""Example:
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002555 gclient pack > patch.txt
2556 generate simple patch for configured client and dependences
2557""")
Edward Lemur3298e7b2018-07-17 18:21:27 +00002558@metrics.collector.collect_metrics('gclient pack')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002559def CMDpack(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002560 """Generates a patch which can be applied at the root of the tree.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00002561
agabled437d762016-10-17 09:35:11 -07002562 Internally, runs 'git diff' on each checked out module and
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002563 dependencies, and performs minimal postprocessing of the output. The
2564 resulting patch is printed to stdout and can be applied to a freshly
2565 checked out tree via 'patch -p0 < patchfile'.
2566 """
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002567 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2568 help='override deps for the specified (comma-separated) '
2569 'platform(s); \'all\' will process all deps_os '
2570 'references')
haitao.feng@intel.com306080c2012-05-04 13:11:29 +00002571 parser.remove_option('--jobs')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002572 (options, args) = parser.parse_args(args)
iannucci@chromium.org50395ea2013-04-04 04:47:42 +00002573 # Force jobs to 1 so the stdout is not annotated with the thread ids
haitao.feng@intel.com306080c2012-05-04 13:11:29 +00002574 options.jobs = 1
kbr@google.comab318592009-09-04 00:54:55 +00002575 client = GClient.LoadCurrentConfig(options)
2576 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002577 raise gclient_utils.Error('client not configured; see \'gclient config\'')
kbr@google.comab318592009-09-04 00:54:55 +00002578 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002579 client.PrintLocationAndContents()
kbr@google.comab318592009-09-04 00:54:55 +00002580 return client.RunOnDeps('pack', args)
2581
2582
Edward Lemur3298e7b2018-07-17 18:21:27 +00002583@metrics.collector.collect_metrics('gclient status')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002584def CMDstatus(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002585 """Shows modification status for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002586 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2587 help='override deps for the specified (comma-separated) '
2588 'platform(s); \'all\' will process all deps_os '
2589 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002590 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002591 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002592 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002593 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002594 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002595 client.PrintLocationAndContents()
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002596 return client.RunOnDeps('status', args)
2597
2598
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002599@subcommand.epilog("""Examples:
maruel@chromium.org79692d62010-05-14 18:57:13 +00002600 gclient sync
2601 update files from SCM according to current configuration,
2602 *for modules which have changed since last update or sync*
2603 gclient sync --force
2604 update files from SCM according to current configuration, for
2605 all modules (useful for recovering files deleted from local copy)
2606 gclient sync --revision src@31000
2607 update src directory to r31000
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002608
2609JSON output format:
2610If the --output-json option is specified, the following document structure will
2611be emitted to the provided file. 'null' entries may occur for subprojects which
2612are present in the gclient solution, but were not processed (due to custom_deps,
2613os_deps, etc.)
2614
2615{
2616 "solutions" : {
2617 "<name>": { # <name> is the posix-normalized path to the solution.
agabled437d762016-10-17 09:35:11 -07002618 "revision": [<git id hex string>|null],
2619 "scm": ["git"|null],
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002620 }
2621 }
2622}
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002623""")
Edward Lemur3298e7b2018-07-17 18:21:27 +00002624@metrics.collector.collect_metrics('gclient sync')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002625def CMDsync(parser, args):
2626 """Checkout/update all modules."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002627 parser.add_option('-f', '--force', action='store_true',
2628 help='force update even for unchanged modules')
2629 parser.add_option('-n', '--nohooks', action='store_true',
2630 help='don\'t run hooks after the update is complete')
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00002631 parser.add_option('-p', '--noprehooks', action='store_true',
2632 help='don\'t run pre-DEPS hooks', default=False)
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002633 parser.add_option('-r', '--revision', action='append',
2634 dest='revisions', metavar='REV', default=[],
2635 help='Enforces revision/hash for the solutions with the '
2636 'format src@rev. The src@ part is optional and can be '
Edward Lesmes53014652018-03-07 18:01:40 -05002637 'skipped. You can also specify URLs instead of paths '
2638 'and gclient will find the solution corresponding to '
2639 'the given URL. If a path is also specified, the URL '
2640 'takes precedence. -r can be used multiple times when '
2641 '.gclient has multiple solutions configured, and will '
2642 'work even if the src@ part is skipped.')
Edward Lesmesc621b212018-03-21 20:26:56 -04002643 parser.add_option('--patch-ref', action='append',
2644 dest='patch_refs', metavar='GERRIT_REF', default=[],
Edward Lemur6a4e31b2018-08-10 19:59:02 +00002645 help='Patches the given reference with the format '
Edward Lemur4c5c8ab2019-06-07 15:58:13 +00002646 'dep@target-ref:patch-ref. '
Edward Lemur6a4e31b2018-08-10 19:59:02 +00002647 'For |dep|, you can specify URLs as well as paths, '
2648 'with URLs taking preference. '
2649 '|patch-ref| will be applied to |dep|, rebased on top '
2650 'of what |dep| was synced to, and a soft reset will '
2651 'be done. Use --no-rebase-patch-ref and '
2652 '--no-reset-patch-ref to disable this behavior. '
2653 '|target-ref| is the target branch against which a '
2654 'patch was created, it is used to determine which '
2655 'commits from the |patch-ref| actually constitute a '
Edward Lemur4c5c8ab2019-06-07 15:58:13 +00002656 'patch.')
maruel@chromium.org794207e2013-03-08 15:29:43 +00002657 parser.add_option('--with_branch_heads', action='store_true',
2658 help='Clone git "branch_heads" refspecs in addition to '
2659 'the default refspecs. This adds about 1/2GB to a '
2660 'full checkout. (git only)')
szager@chromium.org8d3348f2014-08-19 22:49:16 +00002661 parser.add_option('--with_tags', action='store_true',
2662 help='Clone git tags in addition to the default refspecs.')
agable2697cd12016-06-28 10:23:53 -07002663 parser.add_option('-H', '--head', action='store_true',
agablea98a6cd2016-11-15 14:30:10 -08002664 help='DEPRECATED: only made sense with safesync urls.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002665 parser.add_option('-D', '--delete_unversioned_trees', action='store_true',
steveblock@chromium.org98e69452012-02-16 16:36:43 +00002666 help='Deletes from the working copy any dependencies that '
2667 'have been removed since the last sync, as long as '
2668 'there are no local modifications. When used with '
2669 '--force, such dependencies are removed even if they '
2670 'have local modifications. When used with --reset, '
2671 'all untracked directories are removed from the '
tnagel@chromium.orga2aaa632014-02-28 21:47:27 +00002672 'working copy, excluding those which are explicitly '
steveblock@chromium.org98e69452012-02-16 16:36:43 +00002673 'ignored in the repository.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002674 parser.add_option('-R', '--reset', action='store_true',
2675 help='resets any local changes before updating (git only)')
bauerb@chromium.org2aad1b22011-07-22 12:00:41 +00002676 parser.add_option('-M', '--merge', action='store_true',
2677 help='merge upstream changes instead of trying to '
2678 'fast-forward or rebase')
dnj@chromium.org5b23e872015-02-20 21:25:57 +00002679 parser.add_option('-A', '--auto_rebase', action='store_true',
2680 help='Automatically rebase repositories against local '
2681 'checkout during update (git only).')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002682 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2683 help='override deps for the specified (comma-separated) '
2684 'platform(s); \'all\' will process all deps_os '
2685 'references')
Paweł Hajdan, Jr5ec77132017-08-16 19:21:06 +02002686 parser.add_option('--process-all-deps', action='store_true',
2687 help='Check out all deps, even for different OS-es, '
2688 'or with conditions evaluating to false')
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +00002689 parser.add_option('--upstream', action='store_true',
2690 help='Make repo state match upstream branch.')
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002691 parser.add_option('--output-json',
2692 help='Output a json document to this path containing '
2693 'summary information about the sync.')
primiano@chromium.org5439ea52014-08-06 17:18:18 +00002694 parser.add_option('--no-history', action='store_true',
2695 help='GIT ONLY - Reduces the size/time of the checkout at '
2696 'the cost of no history. Requires Git 1.9+')
hinoka@chromium.org46b87412014-05-15 00:42:05 +00002697 parser.add_option('--shallow', action='store_true',
2698 help='GIT ONLY - Do a shallow clone into the cache dir. '
2699 'Requires Git 1.9+')
e.hakkinen@samsung.come8bc1aa2015-04-08 08:00:37 +00002700 parser.add_option('--no_bootstrap', '--no-bootstrap',
2701 action='store_true',
2702 help='Don\'t bootstrap from Google Storage.')
Josip Sokcevic14a83ae2020-05-21 01:36:34 +00002703 parser.add_option('--ignore_locks',
2704 action='store_true',
2705 help='No longer used.')
2706 parser.add_option('--break_repo_locks',
2707 action='store_true',
2708 help='No longer used.')
Vadim Shtayura08049e22017-10-11 00:14:52 +00002709 parser.add_option('--lock_timeout', type='int', default=5000,
2710 help='GIT ONLY - Deadline (in seconds) to wait for git '
2711 'cache lock to become available. Default is %default.')
Edward Lesmesc621b212018-03-21 20:26:56 -04002712 parser.add_option('--no-rebase-patch-ref', action='store_false',
2713 dest='rebase_patch_ref', default=True,
2714 help='Bypass rebase of the patch ref after checkout.')
2715 parser.add_option('--no-reset-patch-ref', action='store_false',
2716 dest='reset_patch_ref', default=True,
2717 help='Bypass calling reset after patching the ref.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002718 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002719 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002720
2721 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002722 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002723
Josip Sokcevic14a83ae2020-05-21 01:36:34 +00002724 if options.ignore_locks:
2725 print('Warning: ignore_locks is no longer used. Please remove its usage.')
2726
2727 if options.break_repo_locks:
2728 print('Warning: break_repo_locks is no longer used. Please remove its '
2729 'usage.')
2730
smutae7ea312016-07-18 11:59:41 -07002731 if options.revisions and options.head:
2732 # TODO(maruel): Make it a parser.error if it doesn't break any builder.
2733 print('Warning: you cannot use both --head and --revision')
2734
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002735 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002736 client.PrintLocationAndContents()
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002737 ret = client.RunOnDeps('update', args)
2738 if options.output_json:
2739 slns = {}
Michael Mossd683d7c2018-06-15 05:05:17 +00002740 for d in client.subtree(True):
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002741 normed = d.name.replace('\\', '/').rstrip('/') + '/'
2742 slns[normed] = {
2743 'revision': d.got_revision,
2744 'scm': d.used_scm.name if d.used_scm else None,
Michael Mossd683d7c2018-06-15 05:05:17 +00002745 'url': str(d.url) if d.url else None,
Edward Lemur7ccf2f02018-06-26 20:41:56 +00002746 'was_processed': d.should_process,
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002747 }
Edward Lemurca879322019-09-09 20:18:13 +00002748 with open(options.output_json, 'w') as f:
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002749 json.dump({'solutions': slns}, f)
2750 return ret
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002751
2752
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002753CMDupdate = CMDsync
2754
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002755
Edward Lemur3298e7b2018-07-17 18:21:27 +00002756@metrics.collector.collect_metrics('gclient validate')
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +02002757def CMDvalidate(parser, args):
2758 """Validates the .gclient and DEPS syntax."""
2759 options, args = parser.parse_args(args)
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +02002760 client = GClient.LoadCurrentConfig(options)
2761 rv = client.RunOnDeps('validate', args)
2762 if rv == 0:
2763 print('validate: SUCCESS')
2764 else:
2765 print('validate: FAILURE')
2766 return rv
2767
2768
Edward Lemur3298e7b2018-07-17 18:21:27 +00002769@metrics.collector.collect_metrics('gclient diff')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002770def CMDdiff(parser, args):
2771 """Displays local diff for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002772 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2773 help='override deps for the specified (comma-separated) '
2774 'platform(s); \'all\' will process all deps_os '
2775 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002776 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002777 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002778 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002779 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002780 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002781 client.PrintLocationAndContents()
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002782 return client.RunOnDeps('diff', args)
2783
2784
Edward Lemur3298e7b2018-07-17 18:21:27 +00002785@metrics.collector.collect_metrics('gclient revert')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002786def CMDrevert(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002787 """Reverts all modifications in every dependencies.
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00002788
2789 That's the nuclear option to get back to a 'clean' state. It removes anything
agabled437d762016-10-17 09:35:11 -07002790 that shows up in git status."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002791 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2792 help='override deps for the specified (comma-separated) '
2793 'platform(s); \'all\' will process all deps_os '
2794 'references')
2795 parser.add_option('-n', '--nohooks', action='store_true',
2796 help='don\'t run hooks after the revert is complete')
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00002797 parser.add_option('-p', '--noprehooks', action='store_true',
2798 help='don\'t run pre-DEPS hooks', default=False)
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +00002799 parser.add_option('--upstream', action='store_true',
2800 help='Make repo state match upstream branch.')
Josip Sokcevic14a83ae2020-05-21 01:36:34 +00002801 parser.add_option('--break_repo_locks',
2802 action='store_true',
2803 help='No longer used.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002804 (options, args) = parser.parse_args(args)
Josip Sokcevic14a83ae2020-05-21 01:36:34 +00002805 if options.break_repo_locks:
2806 print('Warning: break_repo_locks is no longer used. Please remove its ' +
2807 'usage.')
2808
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002809 # --force is implied.
2810 options.force = True
steveblock@chromium.org98e69452012-02-16 16:36:43 +00002811 options.reset = False
2812 options.delete_unversioned_trees = False
agablec903d732016-07-26 09:07:24 -07002813 options.merge = False
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002814 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002815 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002816 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002817 return client.RunOnDeps('revert', args)
2818
2819
Edward Lemur3298e7b2018-07-17 18:21:27 +00002820@metrics.collector.collect_metrics('gclient runhooks')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002821def CMDrunhooks(parser, args):
2822 """Runs hooks for files that have been modified in the local working copy."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002823 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2824 help='override deps for the specified (comma-separated) '
2825 'platform(s); \'all\' will process all deps_os '
2826 'references')
2827 parser.add_option('-f', '--force', action='store_true', default=True,
2828 help='Deprecated. No effect.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002829 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002830 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002831 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002832 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002833 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002834 client.PrintLocationAndContents()
maruel@chromium.org5df6a462009-08-28 18:52:26 +00002835 options.force = True
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002836 options.nohooks = False
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002837 return client.RunOnDeps('runhooks', args)
2838
2839
Edward Lemur3298e7b2018-07-17 18:21:27 +00002840@metrics.collector.collect_metrics('gclient revinfo')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002841def CMDrevinfo(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002842 """Outputs revision info mapping for the client and its dependencies.
maruel@chromium.org9eda4112010-06-11 18:56:10 +00002843
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002844 This allows the capture of an overall 'revision' for the source tree that
maruel@chromium.org9eda4112010-06-11 18:56:10 +00002845 can be used to reproduce the same tree in the future. It is only useful for
agabled437d762016-10-17 09:35:11 -07002846 'unpinned dependencies', i.e. DEPS/deps references without a git hash.
2847 A git branch name isn't 'pinned' since the actual commit can change.
maruel@chromium.org9eda4112010-06-11 18:56:10 +00002848 """
2849 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2850 help='override deps for the specified (comma-separated) '
2851 'platform(s); \'all\' will process all deps_os '
2852 'references')
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00002853 parser.add_option('-a', '--actual', action='store_true',
2854 help='gets the actual checked out revisions instead of the '
2855 'ones specified in the DEPS and .gclient files')
maruel@chromium.org9eda4112010-06-11 18:56:10 +00002856 parser.add_option('-s', '--snapshot', action='store_true',
2857 help='creates a snapshot .gclient file of the current '
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00002858 'version of all repositories to reproduce the tree, '
2859 'implies -a')
Edward Lesmesbb16e332018-03-30 17:54:51 -04002860 parser.add_option('--filter', action='append', dest='filter',
Edward Lesmesdaa76d22018-03-06 14:56:57 -05002861 help='Display revision information only for the specified '
Edward Lesmesbb16e332018-03-30 17:54:51 -04002862 'dependencies (filtered by URL or path).')
Edward Lesmesc2960242018-03-06 20:50:15 -05002863 parser.add_option('--output-json',
2864 help='Output a json document to this path containing '
2865 'information about the revisions.')
Joey Scarr8d3925b2018-07-15 23:36:25 +00002866 parser.add_option('--ignore-dep-type', choices=['git', 'cipd'],
2867 help='Specify to skip processing of a certain type of dep.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002868 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002869 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002870 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002871 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002872 client.PrintRevInfo()
maruel@chromium.org79692d62010-05-14 18:57:13 +00002873 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002874
2875
Edward Lemur3298e7b2018-07-17 18:21:27 +00002876@metrics.collector.collect_metrics('gclient getdep')
Edward Lesmes411041f2018-04-05 20:12:55 -04002877def CMDgetdep(parser, args):
2878 """Gets revision information and variable values from a DEPS file."""
2879 parser.add_option('--var', action='append',
2880 dest='vars', metavar='VAR', default=[],
2881 help='Gets the value of a given variable.')
2882 parser.add_option('-r', '--revision', action='append',
Edward Lemuraf3328f2018-11-19 14:11:46 +00002883 dest='getdep_revisions', metavar='DEP', default=[],
Edward Lesmes411041f2018-04-05 20:12:55 -04002884 help='Gets the revision/version for the given dependency. '
2885 'If it is a git dependency, dep must be a path. If it '
2886 'is a CIPD dependency, dep must be of the form '
2887 'path:package.')
2888 parser.add_option('--deps-file', default='DEPS',
2889 # TODO(ehmaldonado): Try to find the DEPS file pointed by
2890 # .gclient first.
2891 help='The DEPS file to be edited. Defaults to the DEPS '
2892 'file in the current directory.')
2893 (options, args) = parser.parse_args(args)
2894
2895 if not os.path.isfile(options.deps_file):
2896 raise gclient_utils.Error(
2897 'DEPS file %s does not exist.' % options.deps_file)
2898 with open(options.deps_file) as f:
2899 contents = f.read()
Edward Lemuraf3328f2018-11-19 14:11:46 +00002900 client = GClient.LoadCurrentConfig(options)
2901 if client is not None:
2902 builtin_vars = client.get_builtin_vars()
2903 else:
Edward Lemurca879322019-09-09 20:18:13 +00002904 logging.warning(
Edward Lemuraf3328f2018-11-19 14:11:46 +00002905 'Couldn\'t find a valid gclient config. Will attempt to parse the DEPS '
2906 'file without support for built-in variables.')
2907 builtin_vars = None
2908 local_scope = gclient_eval.Exec(contents, options.deps_file,
2909 builtin_vars=builtin_vars)
Edward Lesmes411041f2018-04-05 20:12:55 -04002910
2911 for var in options.vars:
2912 print(gclient_eval.GetVar(local_scope, var))
2913
Edward Lemuraf3328f2018-11-19 14:11:46 +00002914 for name in options.getdep_revisions:
Edward Lesmes411041f2018-04-05 20:12:55 -04002915 if ':' in name:
2916 name, _, package = name.partition(':')
2917 if not name or not package:
2918 parser.error(
2919 'Wrong CIPD format: %s:%s should be of the form path:pkg.'
2920 % (name, package))
2921 print(gclient_eval.GetCIPD(local_scope, name, package))
2922 else:
2923 print(gclient_eval.GetRevision(local_scope, name))
2924
2925
Edward Lemur3298e7b2018-07-17 18:21:27 +00002926@metrics.collector.collect_metrics('gclient setdep')
Edward Lesmes6f64a052018-03-20 17:35:49 -04002927def CMDsetdep(parser, args):
Edward Lesmes0ecf6d62018-04-05 18:28:55 -04002928 """Modifies dependency revisions and variable values in a DEPS file"""
Edward Lesmes6f64a052018-03-20 17:35:49 -04002929 parser.add_option('--var', action='append',
2930 dest='vars', metavar='VAR=VAL', default=[],
2931 help='Sets a variable to the given value with the format '
2932 'name=value.')
2933 parser.add_option('-r', '--revision', action='append',
Edward Lemuraf3328f2018-11-19 14:11:46 +00002934 dest='setdep_revisions', metavar='DEP@REV', default=[],
Edward Lesmes6f64a052018-03-20 17:35:49 -04002935 help='Sets the revision/version for the dependency with '
2936 'the format dep@rev. If it is a git dependency, dep '
2937 'must be a path and rev must be a git hash or '
2938 'reference (e.g. src/dep@deadbeef). If it is a CIPD '
2939 'dependency, dep must be of the form path:package and '
2940 'rev must be the package version '
2941 '(e.g. src/pkg:chromium/pkg@2.1-cr0).')
2942 parser.add_option('--deps-file', default='DEPS',
2943 # TODO(ehmaldonado): Try to find the DEPS file pointed by
2944 # .gclient first.
2945 help='The DEPS file to be edited. Defaults to the DEPS '
2946 'file in the current directory.')
2947 (options, args) = parser.parse_args(args)
Edward Lesmes0ecf6d62018-04-05 18:28:55 -04002948 if args:
2949 parser.error('Unused arguments: "%s"' % '" "'.join(args))
Edward Lesmesae6836e2018-11-19 15:27:20 +00002950 if not options.setdep_revisions and not options.vars:
Edward Lesmes0ecf6d62018-04-05 18:28:55 -04002951 parser.error(
2952 'You must specify at least one variable or revision to modify.')
Edward Lesmes6f64a052018-03-20 17:35:49 -04002953
Edward Lesmes6f64a052018-03-20 17:35:49 -04002954 if not os.path.isfile(options.deps_file):
2955 raise gclient_utils.Error(
2956 'DEPS file %s does not exist.' % options.deps_file)
2957 with open(options.deps_file) as f:
2958 contents = f.read()
Edward Lemuraf3328f2018-11-19 14:11:46 +00002959
2960 client = GClient.LoadCurrentConfig(options)
2961 if client is not None:
2962 builtin_vars = client.get_builtin_vars()
2963 else:
Edward Lemurca879322019-09-09 20:18:13 +00002964 logging.warning(
Edward Lemuraf3328f2018-11-19 14:11:46 +00002965 'Couldn\'t find a valid gclient config. Will attempt to parse the DEPS '
2966 'file without support for built-in variables.')
2967 builtin_vars = None
2968
2969 local_scope = gclient_eval.Exec(contents, options.deps_file,
2970 builtin_vars=builtin_vars)
Edward Lesmes6f64a052018-03-20 17:35:49 -04002971
2972 for var in options.vars:
2973 name, _, value = var.partition('=')
2974 if not name or not value:
Edward Lesmes0ecf6d62018-04-05 18:28:55 -04002975 parser.error(
Edward Lesmes6f64a052018-03-20 17:35:49 -04002976 'Wrong var format: %s should be of the form name=value.' % var)
Edward Lesmes3d993812018-04-02 12:52:49 -04002977 if name in local_scope['vars']:
2978 gclient_eval.SetVar(local_scope, name, value)
2979 else:
2980 gclient_eval.AddVar(local_scope, name, value)
Edward Lesmes6f64a052018-03-20 17:35:49 -04002981
Edward Lemuraf3328f2018-11-19 14:11:46 +00002982 for revision in options.setdep_revisions:
Edward Lesmes6f64a052018-03-20 17:35:49 -04002983 name, _, value = revision.partition('@')
2984 if not name or not value:
Edward Lesmes0ecf6d62018-04-05 18:28:55 -04002985 parser.error(
Edward Lesmes6f64a052018-03-20 17:35:49 -04002986 'Wrong dep format: %s should be of the form dep@rev.' % revision)
2987 if ':' in name:
2988 name, _, package = name.partition(':')
2989 if not name or not package:
Edward Lesmes0ecf6d62018-04-05 18:28:55 -04002990 parser.error(
Edward Lesmes6f64a052018-03-20 17:35:49 -04002991 'Wrong CIPD format: %s:%s should be of the form path:pkg@version.'
2992 % (name, package))
2993 gclient_eval.SetCIPD(local_scope, name, package, value)
2994 else:
Edward Lesmes9f531292018-03-20 21:27:15 -04002995 gclient_eval.SetRevision(local_scope, name, value)
Edward Lesmes6f64a052018-03-20 17:35:49 -04002996
John Emau7aa68242020-02-20 19:44:53 +00002997 with open(options.deps_file, 'wb') as f:
2998 f.write(gclient_eval.RenderDEPSFile(local_scope).encode('utf-8'))
Edward Lesmes6f64a052018-03-20 17:35:49 -04002999
3000
Edward Lemur3298e7b2018-07-17 18:21:27 +00003001@metrics.collector.collect_metrics('gclient verify')
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00003002def CMDverify(parser, args):
3003 """Verifies the DEPS file deps are only from allowed_hosts."""
3004 (options, args) = parser.parse_args(args)
3005 client = GClient.LoadCurrentConfig(options)
3006 if not client:
3007 raise gclient_utils.Error('client not configured; see \'gclient config\'')
3008 client.RunOnDeps(None, [])
3009 # Look at each first-level dependency of this gclient only.
3010 for dep in client.dependencies:
3011 bad_deps = dep.findDepsFromNotAllowedHosts()
3012 if not bad_deps:
3013 continue
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00003014 print("There are deps from not allowed hosts in file %s" % dep.deps_file)
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00003015 for bad_dep in bad_deps:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00003016 print("\t%s at %s" % (bad_dep.name, bad_dep.url))
3017 print("allowed_hosts:", ', '.join(dep.allowed_hosts))
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00003018 sys.stdout.flush()
3019 raise gclient_utils.Error(
3020 'dependencies from disallowed hosts; check your DEPS file.')
3021 return 0
3022
Edward Lemur32e3d1e2018-07-12 00:54:05 +00003023
3024@subcommand.epilog("""For more information on what metrics are we collecting and
Edward Lemur8a2e3312018-07-12 21:15:09 +00003025why, please read metrics.README.md or visit https://bit.ly/2ufRS4p""")
Edward Lemur3298e7b2018-07-17 18:21:27 +00003026@metrics.collector.collect_metrics('gclient metrics')
Edward Lemur32e3d1e2018-07-12 00:54:05 +00003027def CMDmetrics(parser, args):
3028 """Reports, and optionally modifies, the status of metric collection."""
3029 parser.add_option('--opt-in', action='store_true', dest='enable_metrics',
3030 help='Opt-in to metrics collection.',
3031 default=None)
3032 parser.add_option('--opt-out', action='store_false', dest='enable_metrics',
3033 help='Opt-out of metrics collection.')
3034 options, args = parser.parse_args(args)
3035 if args:
3036 parser.error('Unused arguments: "%s"' % '" "'.join(args))
3037 if not metrics.collector.config.is_googler:
3038 print("You're not a Googler. Metrics collection is disabled for you.")
3039 return 0
3040
3041 if options.enable_metrics is not None:
3042 metrics.collector.config.opted_in = options.enable_metrics
3043
3044 if metrics.collector.config.opted_in is None:
3045 print("You haven't opted in or out of metrics collection.")
3046 elif metrics.collector.config.opted_in:
3047 print("You have opted in. Thanks!")
3048 else:
3049 print("You have opted out. Please consider opting in.")
3050 return 0
3051
3052
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00003053class OptionParser(optparse.OptionParser):
szager@chromium.orge2e03202012-07-31 18:05:16 +00003054 gclientfile_default = os.environ.get('GCLIENT_FILE', '.gclient')
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00003055
3056 def __init__(self, **kwargs):
3057 optparse.OptionParser.__init__(
3058 self, version='%prog ' + __version__, **kwargs)
3059
3060 # Some arm boards have issues with parallel sync.
3061 if platform.machine().startswith('arm'):
3062 jobs = 1
3063 else:
3064 jobs = max(8, gclient_utils.NumLocalCpus())
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00003065
3066 self.add_option(
3067 '-j', '--jobs', default=jobs, type='int',
3068 help='Specify how many SCM commands can run in parallel; defaults to '
tnagel@chromium.orga2aaa632014-02-28 21:47:27 +00003069 '%default on this machine')
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00003070 self.add_option(
3071 '-v', '--verbose', action='count', default=0,
3072 help='Produces additional output for diagnostics. Can be used up to '
3073 'three times for more logging info.')
3074 self.add_option(
3075 '--gclientfile', dest='config_filename',
3076 help='Specify an alternate %s file' % self.gclientfile_default)
3077 self.add_option(
3078 '--spec',
3079 help='create a gclient file containing the provided string. Due to '
3080 'Cygwin/Python brokenness, it can\'t contain any newlines.')
3081 self.add_option(
3082 '--no-nag-max', default=False, action='store_true',
scottmg@chromium.orgf547c802013-09-27 17:55:26 +00003083 help='Ignored for backwards compatibility.')
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00003084
Edward Lemur3298e7b2018-07-17 18:21:27 +00003085 def parse_args(self, args=None, _values=None):
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00003086 """Integrates standard options processing."""
Edward Lemur3298e7b2018-07-17 18:21:27 +00003087 # Create an optparse.Values object that will store only the actual passed
3088 # options, without the defaults.
3089 actual_options = optparse.Values()
3090 _, args = optparse.OptionParser.parse_args(self, args, actual_options)
3091 # Create an optparse.Values object with the default options.
3092 options = optparse.Values(self.get_default_values().__dict__)
3093 # Update it with the options passed by the user.
3094 options._update_careful(actual_options.__dict__)
3095 # Store the options passed by the user in an _actual_options attribute.
3096 # We store only the keys, and not the values, since the values can contain
3097 # arbitrary information, which might be PII.
Edward Lemuree7b9dd2019-07-20 01:29:08 +00003098 metrics.collector.add('arguments', list(actual_options.__dict__))
Edward Lemur3298e7b2018-07-17 18:21:27 +00003099
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00003100 levels = [logging.ERROR, logging.WARNING, logging.INFO, logging.DEBUG]
3101 logging.basicConfig(
3102 level=levels[min(options.verbose, len(levels) - 1)],
maruel@chromium.org0895b752011-08-26 20:40:33 +00003103 format='%(module)s(%(lineno)d) %(funcName)s:%(message)s')
szager@chromium.orge2e03202012-07-31 18:05:16 +00003104 if options.config_filename and options.spec:
Quinten Yearsley925cedb2020-04-13 17:49:39 +00003105 self.error('Cannot specify both --gclientfile and --spec')
rdsmith@chromium.orgd9591f02014-02-05 19:28:20 +00003106 if (options.config_filename and
3107 options.config_filename != os.path.basename(options.config_filename)):
3108 self.error('--gclientfile target must be a filename, not a path')
szager@chromium.orge2e03202012-07-31 18:05:16 +00003109 if not options.config_filename:
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00003110 options.config_filename = self.gclientfile_default
maruel@chromium.org0895b752011-08-26 20:40:33 +00003111 options.entries_filename = options.config_filename + '_entries'
3112 if options.jobs < 1:
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00003113 self.error('--jobs must be 1 or higher')
maruel@chromium.org0895b752011-08-26 20:40:33 +00003114
3115 # These hacks need to die.
3116 if not hasattr(options, 'revisions'):
3117 # GClient.RunOnDeps expects it even if not applicable.
3118 options.revisions = []
smutae7ea312016-07-18 11:59:41 -07003119 if not hasattr(options, 'head'):
3120 options.head = None
maruel@chromium.org0895b752011-08-26 20:40:33 +00003121 if not hasattr(options, 'nohooks'):
3122 options.nohooks = True
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00003123 if not hasattr(options, 'noprehooks'):
3124 options.noprehooks = True
maruel@chromium.org0895b752011-08-26 20:40:33 +00003125 if not hasattr(options, 'deps_os'):
3126 options.deps_os = None
maruel@chromium.org0895b752011-08-26 20:40:33 +00003127 if not hasattr(options, 'force'):
3128 options.force = None
3129 return (options, args)
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00003130
maruel@chromium.org39c0b222013-08-17 16:57:01 +00003131
3132def disable_buffering():
3133 # Make stdout auto-flush so buildbot doesn't kill us during lengthy
3134 # operations. Python as a strong tendency to buffer sys.stdout.
3135 sys.stdout = gclient_utils.MakeFileAutoFlush(sys.stdout)
3136 # Make stdout annotated with the thread ids.
3137 sys.stdout = gclient_utils.MakeFileAnnotated(sys.stdout)
maruel@chromium.org0895b752011-08-26 20:40:33 +00003138
3139
Elly Fong-Jones7b294392019-04-18 18:32:10 +00003140def path_contains_tilde():
3141 for element in os.environ['PATH'].split(os.pathsep):
Henrique Ferreiro4ef32212019-04-29 23:32:31 +00003142 if element.startswith('~') and os.path.abspath(
3143 os.path.realpath(os.path.expanduser(element))) == DEPOT_TOOLS_DIR:
Elly Fong-Jones7b294392019-04-18 18:32:10 +00003144 return True
3145 return False
3146
3147
3148def can_run_gclient_and_helpers():
maruel@chromium.org82798cb2012-02-23 18:16:12 +00003149 if sys.hexversion < 0x02060000:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00003150 print(
maruel@chromium.org82798cb2012-02-23 18:16:12 +00003151 '\nYour python version %s is unsupported, please upgrade.\n' %
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00003152 sys.version.split(' ', 1)[0],
3153 file=sys.stderr)
Elly Fong-Jones7b294392019-04-18 18:32:10 +00003154 return False
bcwhite@chromium.org6683ab42013-02-11 16:13:47 +00003155 if not sys.executable:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00003156 print(
3157 '\nPython cannot find the location of it\'s own executable.\n',
3158 file=sys.stderr)
Elly Fong-Jones7b294392019-04-18 18:32:10 +00003159 return False
3160 if path_contains_tilde():
3161 print(
3162 '\nYour PATH contains a literal "~", which works in some shells ' +
3163 'but will break when python tries to run subprocesses. ' +
3164 'Replace the "~" with $HOME.\n' +
3165 'See https://crbug.com/952865.\n',
3166 file=sys.stderr)
3167 return False
3168 return True
3169
3170
3171def main(argv):
3172 """Doesn't parse the arguments here, just find the right subcommand to
3173 execute."""
3174 if not can_run_gclient_and_helpers():
bcwhite@chromium.org6683ab42013-02-11 16:13:47 +00003175 return 2
maruel@chromium.org39c0b222013-08-17 16:57:01 +00003176 fix_encoding.fix_encoding()
3177 disable_buffering()
iannucci@chromium.org596cd5c2016-04-04 21:34:39 +00003178 setup_color.init()
maruel@chromium.org39c0b222013-08-17 16:57:01 +00003179 dispatcher = subcommand.CommandDispatcher(__name__)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00003180 try:
maruel@chromium.org39c0b222013-08-17 16:57:01 +00003181 return dispatcher.execute(OptionParser(), argv)
xusydoc@chromium.org2fd6c3f2013-05-03 21:57:55 +00003182 except KeyboardInterrupt:
3183 gclient_utils.GClientChildren.KillAllRemainingChildren()
3184 raise
vapier@chromium.orga81a56e2015-11-11 07:56:13 +00003185 except (gclient_utils.Error, subprocess2.CalledProcessError) as e:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00003186 print('Error: %s' % str(e), file=sys.stderr)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00003187 return 1
borenet@google.com6a9b1682014-03-24 18:35:23 +00003188 finally:
3189 gclient_utils.PrintWarnings()
sbc@chromium.org013731e2015-02-26 18:28:43 +00003190 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00003191
3192
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00003193if '__main__' == __name__:
Edward Lemur6f812e12018-07-31 22:45:57 +00003194 with metrics.collector.print_notice_and_exit():
sbc@chromium.org013731e2015-02-26 18:28:43 +00003195 sys.exit(main(sys.argv[1:]))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00003196
3197# vim: ts=2:sw=2:tw=80:et: