blob: a8b1c87306dc780072ec247bb773c306ec019283 [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
796 use_relative_hooks = local_scope.get('use_relative_hooks', False)
797 hooks_cwd = self.root.root_dir
798 if use_relative_hooks:
Corentin Wallez271a78a2020-07-12 15:41:46 +0000799 if not self._use_relative_paths:
Corentin Walleza68660d2018-09-10 17:33:24 +0000800 raise gclient_utils.Error(
801 'ParseDepsFile(%s): use_relative_hooks must be used with '
802 'use_relative_paths' % self.name)
803 hooks_cwd = os.path.join(hooks_cwd, self.name)
804 logging.warning('Updating hook base working directory to %s.',
805 hooks_cwd)
806
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000807 # override named sets of hooks by the custom hooks
808 hooks_to_run = []
809 hook_names_to_suppress = [c.get('name', '') for c in self.custom_hooks]
810 for hook in local_scope.get('hooks', []):
811 if hook.get('name', '') not in hook_names_to_suppress:
812 hooks_to_run.append(hook)
813
814 # add the replacements and any additions
815 for hook in self.custom_hooks:
816 if 'action' in hook:
817 hooks_to_run.append(hook)
818
Edward Lemurfbb06aa2018-06-11 20:43:06 +0000819 if self.should_recurse:
Paweł Hajdan, Jr032d5452017-06-22 20:43:53 +0200820 self._pre_deps_hooks = [
Michael Moss42d02c22018-02-05 10:32:24 -0800821 Hook.from_dict(hook, variables=self.get_vars(), verbose=True,
Corentin Walleza68660d2018-09-10 17:33:24 +0000822 conditions=self.condition, cwd_base=hooks_cwd)
Daniel Chenga0c5f082017-10-19 13:35:19 -0700823 for hook in local_scope.get('pre_deps_hooks', [])
824 ]
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000825
Corentin Walleza68660d2018-09-10 17:33:24 +0000826 self.add_dependencies_and_close(deps_to_add, hooks_to_run,
827 hooks_cwd=hooks_cwd)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000828 logging.info('ParseDepsFile(%s) done' % self.name)
829
Michael Mossd683d7c2018-06-15 05:05:17 +0000830 def _get_option(self, attr, default):
831 obj = self
832 while not hasattr(obj, '_options'):
833 obj = obj.parent
834 return getattr(obj._options, attr, default)
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +0200835
Corentin Walleza68660d2018-09-10 17:33:24 +0000836 def add_dependencies_and_close(self, deps_to_add, hooks, hooks_cwd=None):
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000837 """Adds the dependencies, hooks and mark the parsing as done."""
Corentin Walleza68660d2018-09-10 17:33:24 +0000838 if hooks_cwd == None:
839 hooks_cwd = self.root.root_dir
840
maruel@chromium.orgb9be0652011-10-14 18:05:40 +0000841 for dep in deps_to_add:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000842 if dep.verify_validity():
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000843 self.add_dependency(dep)
Daniel Chenga0c5f082017-10-19 13:35:19 -0700844 self._mark_as_parsed([
845 Hook.from_dict(
Michael Moss42d02c22018-02-05 10:32:24 -0800846 h, variables=self.get_vars(), verbose=self.root._options.verbose,
Corentin Walleza68660d2018-09-10 17:33:24 +0000847 conditions=self.condition, cwd_base=hooks_cwd)
Daniel Chenga0c5f082017-10-19 13:35:19 -0700848 for h in hooks
849 ])
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000850
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000851 def findDepsFromNotAllowedHosts(self):
Corentin Wallezaca984c2018-09-07 21:52:14 +0000852 """Returns a list of dependencies from not allowed hosts.
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000853
854 If allowed_hosts is not set, allows all hosts and returns empty list.
855 """
856 if not self._allowed_hosts:
857 return []
858 bad_deps = []
859 for dep in self._dependencies:
szager@chromium.orgbd772dd2014-11-05 18:43:08 +0000860 # Don't enforce this for custom_deps.
861 if dep.name in self._custom_deps:
862 continue
Michael Mossd683d7c2018-06-15 05:05:17 +0000863 if isinstance(dep.url, basestring):
864 parsed_url = urlparse.urlparse(dep.url)
865 if parsed_url.netloc and parsed_url.netloc not in self._allowed_hosts:
866 bad_deps.append(dep)
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000867 return bad_deps
868
Edward Lemure7273d22018-05-10 19:13:51 -0400869 def FuzzyMatchUrl(self, candidates):
Edward Lesmesbb16e332018-03-30 17:54:51 -0400870 """Attempts to find this dependency in the list of candidates.
871
Edward Lemure7273d22018-05-10 19:13:51 -0400872 It looks first for the URL of this dependency in the list of
Edward Lesmesbb16e332018-03-30 17:54:51 -0400873 candidates. If it doesn't succeed, and the URL ends in '.git', it will try
874 looking for the URL minus '.git'. Finally it will try to look for the name
875 of the dependency.
876
877 Args:
Edward Lesmesbb16e332018-03-30 17:54:51 -0400878 candidates: list, dict. The list of candidates in which to look for this
879 dependency. It can contain URLs as above, or dependency names like
880 "src/some/dep".
881
882 Returns:
883 If this dependency is not found in the list of candidates, returns None.
884 Otherwise, it returns under which name did we find this dependency:
885 - Its parsed url: "https://example.com/src.git'
886 - Its parsed url minus '.git': "https://example.com/src"
887 - Its name: "src"
888 """
Edward Lemure7273d22018-05-10 19:13:51 -0400889 if self.url:
890 origin, _ = gclient_utils.SplitUrlRevision(self.url)
Edward Lesmesbb16e332018-03-30 17:54:51 -0400891 if origin in candidates:
892 return origin
893 if origin.endswith('.git') and origin[:-len('.git')] in candidates:
894 return origin[:-len('.git')]
Edward Lesmes990148e2018-04-26 14:56:55 -0400895 if origin + '.git' in candidates:
896 return origin + '.git'
Edward Lesmesbb16e332018-03-30 17:54:51 -0400897 if self.name in candidates:
898 return self.name
899 return None
900
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +0000901 # Arguments number differs from overridden method
Quinten Yearsleyb2cc4a92016-12-15 13:53:26 -0800902 # pylint: disable=arguments-differ
Edward Lesmesc621b212018-03-21 20:26:56 -0400903 def run(self, revision_overrides, command, args, work_queue, options,
Edward Lemur6a4e31b2018-08-10 19:59:02 +0000904 patch_refs, target_branches):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000905 """Runs |command| then parse the DEPS file."""
maruel@chromium.org470b5432011-10-11 18:18:19 +0000906 logging.info('Dependency(%s).run()' % self.name)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000907 assert self._file_list == []
Michael Mossd683d7c2018-06-15 05:05:17 +0000908 if not self.should_process:
909 return
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000910 # When running runhooks, there's no need to consult the SCM.
911 # All known hooks are expected to run unconditionally regardless of working
912 # copy state, so skip the SCM status check.
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +0200913 run_scm = command not in (
914 'flatten', 'runhooks', 'recurse', 'validate', None)
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000915 file_list = [] if not options.nohooks else None
Edward Lesmesbb16e332018-03-30 17:54:51 -0400916 revision_override = revision_overrides.pop(
Edward Lemure7273d22018-05-10 19:13:51 -0400917 self.FuzzyMatchUrl(revision_overrides), None)
Edward Lemure4213702018-06-21 21:15:50 +0000918 if not revision_override and not self.managed:
919 revision_override = 'unmanaged'
Michael Mossd683d7c2018-06-15 05:05:17 +0000920 if run_scm and self.url:
agabled437d762016-10-17 09:35:11 -0700921 # Create a shallow copy to mutate revision.
922 options = copy.copy(options)
923 options.revision = revision_override
924 self._used_revision = options.revision
Edward Lemurbabd0982018-05-11 13:32:37 -0400925 self._used_scm = self.CreateSCM(out_cb=work_queue.out_cb)
agabled437d762016-10-17 09:35:11 -0700926 self._got_revision = self._used_scm.RunCommand(command, options, args,
927 file_list)
Edward Lesmesc621b212018-03-21 20:26:56 -0400928
Edward Lemure7273d22018-05-10 19:13:51 -0400929 patch_repo = self.url.split('@')[0]
930 patch_ref = patch_refs.pop(self.FuzzyMatchUrl(patch_refs), None)
Edward Lemur6a4e31b2018-08-10 19:59:02 +0000931 target_branch = target_branches.pop(
932 self.FuzzyMatchUrl(target_branches), None)
Edward Lesmesc621b212018-03-21 20:26:56 -0400933 if command == 'update' and patch_ref is not None:
Edward Lemur6a4e31b2018-08-10 19:59:02 +0000934 self._used_scm.apply_patch_ref(patch_repo, patch_ref, target_branch,
935 options, file_list)
Edward Lesmesc621b212018-03-21 20:26:56 -0400936
agabled437d762016-10-17 09:35:11 -0700937 if file_list:
938 file_list = [os.path.join(self.name, f.strip()) for f in file_list]
maruel@chromium.org68988972011-09-20 14:11:42 +0000939
940 # TODO(phajdan.jr): We should know exactly when the paths are absolute.
941 # Convert all absolute paths to relative.
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000942 for i in range(len(file_list or [])):
maruel@chromium.org68988972011-09-20 14:11:42 +0000943 # It depends on the command being executed (like runhooks vs sync).
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000944 if not os.path.isabs(file_list[i]):
maruel@chromium.org68988972011-09-20 14:11:42 +0000945 continue
946 prefix = os.path.commonprefix(
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000947 [self.root.root_dir.lower(), file_list[i].lower()])
948 file_list[i] = file_list[i][len(prefix):]
maruel@chromium.org68988972011-09-20 14:11:42 +0000949 # Strip any leading path separators.
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000950 while file_list[i].startswith(('\\', '/')):
951 file_list[i] = file_list[i][1:]
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000952
Edward Lemurfbb06aa2018-06-11 20:43:06 +0000953 if self.should_recurse:
Edward Lemure05f18d2018-06-08 17:36:53 +0000954 self.ParseDepsFile()
Edward Lesmes5d6cde32018-04-12 18:32:46 -0400955
Edward Lemure7273d22018-05-10 19:13:51 -0400956 self._run_is_done(file_list or [])
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000957
Edward Lemurfbb06aa2018-06-11 20:43:06 +0000958 if self.should_recurse:
Edward Lesmes5d6cde32018-04-12 18:32:46 -0400959 if command in ('update', 'revert') and not options.noprehooks:
960 self.RunPreDepsHooks()
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000961 # Parse the dependencies of this dependency.
962 for s in self.dependencies:
Michael Mossd683d7c2018-06-15 05:05:17 +0000963 if s.should_process:
964 work_queue.enqueue(s)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000965
966 if command == 'recurse':
agabled437d762016-10-17 09:35:11 -0700967 # Skip file only checkout.
Edward Lemurbabd0982018-05-11 13:32:37 -0400968 scm = self.GetScmName()
agabled437d762016-10-17 09:35:11 -0700969 if not options.scm or scm in options.scm:
970 cwd = os.path.normpath(os.path.join(self.root.root_dir, self.name))
971 # Pass in the SCM type as an env variable. Make sure we don't put
972 # unicode strings in the environment.
973 env = os.environ.copy()
Michael Mossd683d7c2018-06-15 05:05:17 +0000974 if scm:
975 env['GCLIENT_SCM'] = str(scm)
976 if self.url:
977 env['GCLIENT_URL'] = str(self.url)
agabled437d762016-10-17 09:35:11 -0700978 env['GCLIENT_DEP_PATH'] = str(self.name)
979 if options.prepend_dir and scm == 'git':
980 print_stdout = False
981 def filter_fn(line):
982 """Git-specific path marshaling. It is optimized for git-grep."""
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000983
agabled437d762016-10-17 09:35:11 -0700984 def mod_path(git_pathspec):
985 match = re.match('^(\\S+?:)?([^\0]+)$', git_pathspec)
986 modified_path = os.path.join(self.name, match.group(2))
987 branch = match.group(1) or ''
988 return '%s%s' % (branch, modified_path)
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000989
agabled437d762016-10-17 09:35:11 -0700990 match = re.match('^Binary file ([^\0]+) matches$', line)
991 if match:
992 print('Binary file %s matches\n' % mod_path(match.group(1)))
993 return
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000994
agabled437d762016-10-17 09:35:11 -0700995 items = line.split('\0')
996 if len(items) == 2 and items[1]:
997 print('%s : %s' % (mod_path(items[0]), items[1]))
998 elif len(items) >= 2:
999 # Multiple null bytes or a single trailing null byte indicate
1000 # git is likely displaying filenames only (such as with -l)
1001 print('\n'.join(mod_path(path) for path in items if path))
1002 else:
1003 print(line)
1004 else:
1005 print_stdout = True
1006 filter_fn = None
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001007
Michael Mossd683d7c2018-06-15 05:05:17 +00001008 if self.url is None:
1009 print('Skipped omitted dependency %s' % cwd, file=sys.stderr)
1010 elif os.path.isdir(cwd):
agabled437d762016-10-17 09:35:11 -07001011 try:
1012 gclient_utils.CheckCallAndFilter(
Ben Masonfbd2c632020-06-22 14:59:13 +00001013 args, cwd=cwd, env=env, print_stdout=print_stdout,
agabled437d762016-10-17 09:35:11 -07001014 filter_fn=filter_fn,
Ben Masonfbd2c632020-06-22 14:59:13 +00001015 )
agabled437d762016-10-17 09:35:11 -07001016 except subprocess2.CalledProcessError:
1017 if not options.ignore:
1018 raise
1019 else:
1020 print('Skipped missing %s' % cwd, file=sys.stderr)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001021
Edward Lemurbabd0982018-05-11 13:32:37 -04001022 def GetScmName(self):
Edward Lemurb61d3872018-05-09 18:42:47 -04001023 raise NotImplementedError()
John Budorick0f7b2002018-01-19 15:46:17 -08001024
Edward Lemurbabd0982018-05-11 13:32:37 -04001025 def CreateSCM(self, out_cb=None):
Edward Lemurb61d3872018-05-09 18:42:47 -04001026 raise NotImplementedError()
John Budorick0f7b2002018-01-19 15:46:17 -08001027
Dirk Pranke9f20d022017-10-11 18:36:54 -07001028 def HasGNArgsFile(self):
1029 return self._gn_args_file is not None
1030
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +02001031 def WriteGNArgsFile(self):
1032 lines = ['# Generated from %r' % self.deps_file]
Paweł Hajdan, Jrb495bf52017-09-25 19:33:50 +02001033 variables = self.get_vars()
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +02001034 for arg in self._gn_args:
Paweł Hajdan, Jre0214742017-09-28 12:21:01 +02001035 value = variables[arg]
Dirk Prankefdd2cd62020-06-30 23:30:47 +00001036 if isinstance(value, gclient_eval.ConstantString):
1037 value = value.value
1038 elif isinstance(value, basestring):
Paweł Hajdan, Jre0214742017-09-28 12:21:01 +02001039 value = gclient_eval.EvaluateCondition(value, variables)
Paweł Hajdan, Jrb495bf52017-09-25 19:33:50 +02001040 lines.append('%s = %s' % (arg, ToGNString(value)))
Corentin Wallez271a78a2020-07-12 15:41:46 +00001041
1042 # When use_relative_paths is set, gn_args_file is relative to this DEPS
1043 path_prefix = self.root.root_dir
1044 if self._use_relative_paths:
Lei Zhang67283c02020-07-13 21:38:44 +00001045 path_prefix = os.path.join(path_prefix, self.name)
Corentin Wallez271a78a2020-07-12 15:41:46 +00001046
1047 with open(os.path.join(path_prefix, self._gn_args_file), 'wb') as f:
Edward Lesmes05934952019-12-19 20:38:09 +00001048 f.write('\n'.join(lines).encode('utf-8', 'replace'))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001049
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001050 @gclient_utils.lockedmethod
Edward Lemure7273d22018-05-10 19:13:51 -04001051 def _run_is_done(self, file_list):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001052 # Both these are kept for hooks that are run as a separate tree traversal.
1053 self._file_list = file_list
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001054 self._processed = True
1055
szager@google.comb9a78d32012-03-13 18:46:21 +00001056 def GetHooks(self, options):
1057 """Evaluates all hooks, and return them in a flat list.
1058
1059 RunOnDeps() must have been called before to load the DEPS.
1060 """
1061 result = []
Michael Mossd683d7c2018-06-15 05:05:17 +00001062 if not self.should_process or not self.should_recurse:
1063 # Don't run the hook when it is above recursion_limit.
1064 return result
maruel@chromium.orgdc7445d2010-07-09 21:05:29 +00001065 # If "--force" was specified, run all hooks regardless of what files have
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001066 # changed.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001067 if self.deps_hooks:
agabled437d762016-10-17 09:35:11 -07001068 # TODO(maruel): If the user is using git, then we don't know
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001069 # what files have changed so we always run all hooks. It'd be nice to fix
1070 # that.
Edward Lemurbabd0982018-05-11 13:32:37 -04001071 result.extend(self.deps_hooks)
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001072 for s in self.dependencies:
szager@google.comb9a78d32012-03-13 18:46:21 +00001073 result.extend(s.GetHooks(options))
1074 return result
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001075
Daniel Chenga0c5f082017-10-19 13:35:19 -07001076 def RunHooksRecursively(self, options, progress):
szager@google.comb9a78d32012-03-13 18:46:21 +00001077 assert self.hooks_ran == False
maruel@chromium.org064186c2011-09-27 23:53:33 +00001078 self._hooks_ran = True
Daniel Chenga0c5f082017-10-19 13:35:19 -07001079 hooks = self.GetHooks(options)
1080 if progress:
1081 progress._total = len(hooks)
1082 for hook in hooks:
Daniel Chenga0c5f082017-10-19 13:35:19 -07001083 if progress:
1084 progress.update(extra=hook.name or '')
Corentin Walleza68660d2018-09-10 17:33:24 +00001085 hook.run()
Daniel Chenga0c5f082017-10-19 13:35:19 -07001086 if progress:
1087 progress.end()
maruel@chromium.orgeaf61062010-07-07 18:42:39 +00001088
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001089 def RunPreDepsHooks(self):
1090 assert self.processed
1091 assert self.deps_parsed
1092 assert not self.pre_deps_hooks_ran
1093 assert not self.hooks_ran
1094 for s in self.dependencies:
1095 assert not s.processed
1096 self._pre_deps_hooks_ran = True
1097 for hook in self.pre_deps_hooks:
Corentin Walleza68660d2018-09-10 17:33:24 +00001098 hook.run()
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001099
John Budorickd3ba72b2018-03-20 12:27:42 -07001100 def GetCipdRoot(self):
1101 if self.root is self:
1102 # Let's not infinitely recurse. If this is root and isn't an
1103 # instance of GClient, do nothing.
1104 return None
1105 return self.root.GetCipdRoot()
1106
Michael Mossd683d7c2018-06-15 05:05:17 +00001107 def subtree(self, include_all):
maruel@chromium.orgad3287e2011-10-03 19:15:10 +00001108 """Breadth first recursion excluding root node."""
maruel@chromium.orgf13a4182011-09-22 00:26:15 +00001109 dependencies = self.dependencies
1110 for d in dependencies:
Michael Mossd683d7c2018-06-15 05:05:17 +00001111 if d.should_process or include_all:
1112 yield d
maruel@chromium.orgf13a4182011-09-22 00:26:15 +00001113 for d in dependencies:
Michael Mossd683d7c2018-06-15 05:05:17 +00001114 for i in d.subtree(include_all):
maruel@chromium.orgad3287e2011-10-03 19:15:10 +00001115 yield i
1116
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001117 @gclient_utils.lockedmethod
1118 def add_dependency(self, new_dep):
1119 self._dependencies.append(new_dep)
1120
1121 @gclient_utils.lockedmethod
Paweł Hajdan, Jr357415c2017-07-24 14:35:28 +02001122 def _mark_as_parsed(self, new_hooks):
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001123 self._deps_hooks.extend(new_hooks)
1124 self._deps_parsed = True
1125
maruel@chromium.org68988972011-09-20 14:11:42 +00001126 @property
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001127 @gclient_utils.lockedmethod
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +00001128 def dependencies(self):
1129 return tuple(self._dependencies)
1130
1131 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001132 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001133 def deps_hooks(self):
1134 return tuple(self._deps_hooks)
1135
1136 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001137 @gclient_utils.lockedmethod
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001138 def pre_deps_hooks(self):
1139 return tuple(self._pre_deps_hooks)
1140
1141 @property
1142 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001143 def deps_parsed(self):
maruel@chromium.org3223edd2011-10-10 23:17:39 +00001144 """This is purely for debugging purposes. It's not used anywhere."""
maruel@chromium.org064186c2011-09-27 23:53:33 +00001145 return self._deps_parsed
1146
1147 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001148 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001149 def processed(self):
1150 return self._processed
1151
1152 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001153 @gclient_utils.lockedmethod
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001154 def pre_deps_hooks_ran(self):
1155 return self._pre_deps_hooks_ran
1156
1157 @property
1158 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001159 def hooks_ran(self):
1160 return self._hooks_ran
1161
1162 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001163 @gclient_utils.lockedmethod
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00001164 def allowed_hosts(self):
1165 return self._allowed_hosts
1166
1167 @property
1168 @gclient_utils.lockedmethod
maruel@chromium.org861fd0f2010-07-23 03:05:05 +00001169 def file_list(self):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001170 return tuple(self._file_list)
1171
1172 @property
kustermann@google.coma692e8f2013-04-18 08:32:04 +00001173 def used_scm(self):
1174 """SCMWrapper instance for this dependency or None if not processed yet."""
1175 return self._used_scm
1176
1177 @property
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00001178 @gclient_utils.lockedmethod
1179 def got_revision(self):
1180 return self._got_revision
1181
1182 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001183 def file_list_and_children(self):
1184 result = list(self.file_list)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +00001185 for d in self.dependencies:
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001186 result.extend(d.file_list_and_children)
maruel@chromium.org68988972011-09-20 14:11:42 +00001187 return tuple(result)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +00001188
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001189 def __str__(self):
1190 out = []
Edward Lemure7273d22018-05-10 19:13:51 -04001191 for i in ('name', 'url', 'custom_deps',
Michael Mossd683d7c2018-06-15 05:05:17 +00001192 'custom_vars', 'deps_hooks', 'file_list', 'should_process',
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00001193 'processed', 'hooks_ran', 'deps_parsed', 'requirements',
1194 'allowed_hosts'):
maruel@chromium.org3c74bc92011-09-15 19:17:21 +00001195 # First try the native property if it exists.
1196 if hasattr(self, '_' + i):
1197 value = getattr(self, '_' + i, False)
1198 else:
1199 value = getattr(self, i, False)
1200 if value:
1201 out.append('%s: %s' % (i, value))
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001202
1203 for d in self.dependencies:
1204 out.extend([' ' + x for x in str(d).splitlines()])
1205 out.append('')
1206 return '\n'.join(out)
1207
1208 def __repr__(self):
1209 return '%s: %s' % (self.name, self.url)
1210
Michael Moss4e9b50a2018-05-23 22:35:06 -07001211 def hierarchy(self, include_url=True):
maruel@chromium.orgbc2d2f92010-07-22 21:26:48 +00001212 """Returns a human-readable hierarchical reference to a Dependency."""
Michael Moss4e9b50a2018-05-23 22:35:06 -07001213 def format_name(d):
1214 if include_url:
1215 return '%s(%s)' % (d.name, d.url)
1216 return d.name
1217 out = format_name(self)
maruel@chromium.orgbffb9042010-07-22 20:59:36 +00001218 i = self.parent
1219 while i and i.name:
Michael Moss4e9b50a2018-05-23 22:35:06 -07001220 out = '%s -> %s' % (format_name(i), out)
maruel@chromium.orgbffb9042010-07-22 20:59:36 +00001221 i = i.parent
1222 return out
1223
Michael Mossfe68c912018-03-22 19:19:35 -07001224 def hierarchy_data(self):
1225 """Returns a machine-readable hierarchical reference to a Dependency."""
1226 d = self
1227 out = []
1228 while d and d.name:
1229 out.insert(0, (d.name, d.url))
1230 d = d.parent
1231 return tuple(out)
1232
Edward Lemur8f8a50d2018-11-01 22:03:02 +00001233 def get_builtin_vars(self):
1234 return {
Paweł Hajdan, Jrd325eb32017-10-03 17:43:37 +02001235 'checkout_android': 'android' in self.target_os,
Benjamin Pastene6fe29412018-01-23 15:35:58 -08001236 'checkout_chromeos': 'chromeos' in self.target_os,
Paweł Hajdan, Jrd325eb32017-10-03 17:43:37 +02001237 'checkout_fuchsia': 'fuchsia' in self.target_os,
1238 'checkout_ios': 'ios' in self.target_os,
1239 'checkout_linux': 'unix' in self.target_os,
1240 'checkout_mac': 'mac' in self.target_os,
1241 'checkout_win': 'win' in self.target_os,
1242 'host_os': _detect_host_os(),
Tom Andersonc31ae0b2018-02-06 14:48:56 -08001243
1244 'checkout_arm': 'arm' in self.target_cpu,
1245 'checkout_arm64': 'arm64' in self.target_cpu,
1246 'checkout_x86': 'x86' in self.target_cpu,
1247 'checkout_mips': 'mips' in self.target_cpu,
Wang Qing254538b2018-07-26 02:23:53 +00001248 'checkout_mips64': 'mips64' in self.target_cpu,
Tom Andersonc31ae0b2018-02-06 14:48:56 -08001249 'checkout_ppc': 'ppc' in self.target_cpu,
1250 'checkout_s390': 's390' in self.target_cpu,
1251 'checkout_x64': 'x64' in self.target_cpu,
1252 'host_cpu': detect_host_arch.HostArch(),
Paweł Hajdan, Jra3b67ae2017-08-30 15:18:21 +02001253 }
Edward Lemur8f8a50d2018-11-01 22:03:02 +00001254
1255 def get_vars(self):
1256 """Returns a dictionary of effective variable values
1257 (DEPS file contents with applied custom_vars overrides)."""
1258 # Variable precedence (last has highest):
Michael Mossda55cdc2018-04-06 18:37:19 -07001259 # - DEPS vars
1260 # - parents, from first to last
Edward Lemur8f8a50d2018-11-01 22:03:02 +00001261 # - built-in
Michael Mossda55cdc2018-04-06 18:37:19 -07001262 # - custom_vars overrides
Edward Lemur8f8a50d2018-11-01 22:03:02 +00001263 result = {}
Paweł Hajdan, Jra3b67ae2017-08-30 15:18:21 +02001264 result.update(self._vars)
Michael Mossda55cdc2018-04-06 18:37:19 -07001265 if self.parent:
Dirk Prankefdd2cd62020-06-30 23:30:47 +00001266 merge_vars(result, self.parent.get_vars())
Edward Lemur8f8a50d2018-11-01 22:03:02 +00001267 # Provide some built-in variables.
1268 result.update(self.get_builtin_vars())
Dirk Prankefdd2cd62020-06-30 23:30:47 +00001269 merge_vars(result, self.custom_vars)
1270
Paweł Hajdan, Jrd3790252017-07-03 21:06:24 +02001271 return result
1272
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001273
Paweł Hajdan, Jra3b67ae2017-08-30 15:18:21 +02001274_PLATFORM_MAPPING = {
1275 'cygwin': 'win',
1276 'darwin': 'mac',
1277 'linux2': 'linux',
Edward Lemuree7b9dd2019-07-20 01:29:08 +00001278 'linux': 'linux',
Paweł Hajdan, Jra3b67ae2017-08-30 15:18:21 +02001279 'win32': 'win',
Jaideep Bajwad05f3582017-09-11 12:31:48 -04001280 'aix6': 'aix',
Paweł Hajdan, Jra3b67ae2017-08-30 15:18:21 +02001281}
1282
1283
Dirk Prankefdd2cd62020-06-30 23:30:47 +00001284def merge_vars(result, new_vars):
1285 for k, v in new_vars.items():
1286 if k in result:
1287 if isinstance(result[k], gclient_eval.ConstantString):
1288 if isinstance(v, gclient_eval.ConstantString):
1289 result[k] = v
1290 else:
1291 result[k].value = v
1292 else:
1293 result[k] = v
1294 else:
1295 result[k] = v
1296
1297
Paweł Hajdan, Jra3b67ae2017-08-30 15:18:21 +02001298def _detect_host_os():
1299 return _PLATFORM_MAPPING[sys.platform]
1300
1301
Edward Lemurb61d3872018-05-09 18:42:47 -04001302class GitDependency(Dependency):
1303 """A Dependency object that represents a single git checkout."""
1304
1305 #override
Edward Lemurbabd0982018-05-11 13:32:37 -04001306 def GetScmName(self):
Edward Lemurb61d3872018-05-09 18:42:47 -04001307 """Always 'git'."""
Edward Lemurb61d3872018-05-09 18:42:47 -04001308 return 'git'
1309
1310 #override
Edward Lemurbabd0982018-05-11 13:32:37 -04001311 def CreateSCM(self, out_cb=None):
Edward Lemurb61d3872018-05-09 18:42:47 -04001312 """Create a Wrapper instance suitable for handling this git dependency."""
Edward Lemurbabd0982018-05-11 13:32:37 -04001313 return gclient_scm.GitWrapper(
1314 self.url, self.root.root_dir, self.name, self.outbuf, out_cb,
1315 print_outbuf=self.print_outbuf)
Edward Lemurb61d3872018-05-09 18:42:47 -04001316
1317
1318class GClient(GitDependency):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001319 """Object that represent a gclient checkout. A tree of Dependency(), one per
1320 solution or DEPS entry."""
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001321
1322 DEPS_OS_CHOICES = {
Jaideep Bajwad05f3582017-09-11 12:31:48 -04001323 "aix6": "unix",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001324 "win32": "win",
1325 "win": "win",
1326 "cygwin": "win",
1327 "darwin": "mac",
1328 "mac": "mac",
1329 "unix": "unix",
1330 "linux": "unix",
1331 "linux2": "unix",
maruel@chromium.org244e3442011-06-12 15:20:55 +00001332 "linux3": "unix",
szager@chromium.orgf8c95cd2012-06-01 22:26:52 +00001333 "android": "android",
Michael Mossc54fa812017-08-17 11:27:58 -07001334 "ios": "ios",
Sergiy Byelozyorov518bb682018-06-03 11:25:58 +02001335 "fuchsia": "fuchsia",
Michael Moss484d74f2019-02-06 01:55:43 +00001336 "chromeos": "chromeos",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001337 }
1338
1339 DEFAULT_CLIENT_FILE_TEXT = ("""\
1340solutions = [
Edward Lesmes05934952019-12-19 20:38:09 +00001341 { "name" : %(solution_name)r,
1342 "url" : %(solution_url)r,
1343 "deps_file" : %(deps_file)r,
1344 "managed" : %(managed)r,
smutae7ea312016-07-18 11:59:41 -07001345 "custom_deps" : {
1346 },
Paweł Hajdan, Jr3ba2a7c2017-10-04 19:24:46 +02001347 "custom_vars": %(custom_vars)r,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001348 },
1349]
Robert Iannuccia19649b2018-06-29 16:31:45 +00001350""")
1351
1352 DEFAULT_CLIENT_CACHE_DIR_TEXT = ("""\
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001353cache_dir = %(cache_dir)r
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001354""")
1355
Robert Iannuccia19649b2018-06-29 16:31:45 +00001356
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001357 DEFAULT_SNAPSHOT_FILE_TEXT = ("""\
1358# Snapshot generated with gclient revinfo --snapshot
Edward Lesmesc2960242018-03-06 20:50:15 -05001359solutions = %(solution_list)s
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001360""")
1361
1362 def __init__(self, root_dir, options):
maruel@chromium.org0d812442010-08-10 12:41:08 +00001363 # Do not change previous behavior. Only solution level and immediate DEPS
1364 # are processed.
1365 self._recursion_limit = 2
Edward Lemure05f18d2018-06-08 17:36:53 +00001366 super(GClient, self).__init__(
1367 parent=None,
1368 name=None,
Michael Mossd683d7c2018-06-15 05:05:17 +00001369 url=None,
Edward Lemure05f18d2018-06-08 17:36:53 +00001370 managed=True,
1371 custom_deps=None,
1372 custom_vars=None,
1373 custom_hooks=None,
1374 deps_file='unused',
Michael Mossd683d7c2018-06-15 05:05:17 +00001375 should_process=True,
Edward Lemurfbb06aa2018-06-11 20:43:06 +00001376 should_recurse=True,
Edward Lemure05f18d2018-06-08 17:36:53 +00001377 relative=None,
1378 condition=None,
1379 print_outbuf=True)
1380
maruel@chromium.org0d425922010-06-21 19:22:24 +00001381 self._options = options
maruel@chromium.org271375b2010-06-23 19:17:38 +00001382 if options.deps_os:
1383 enforced_os = options.deps_os.split(',')
1384 else:
1385 enforced_os = [self.DEPS_OS_CHOICES.get(sys.platform, 'unix')]
1386 if 'all' in enforced_os:
Edward Lemuree7b9dd2019-07-20 01:29:08 +00001387 enforced_os = self.DEPS_OS_CHOICES.values()
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001388 self._enforced_os = tuple(set(enforced_os))
Tom Andersonc31ae0b2018-02-06 14:48:56 -08001389 self._enforced_cpu = detect_host_arch.HostArch(),
maruel@chromium.org271375b2010-06-23 19:17:38 +00001390 self._root_dir = root_dir
John Budorickd3ba72b2018-03-20 12:27:42 -07001391 self._cipd_root = None
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001392 self.config_content = None
1393
borenet@google.com88d10082014-03-21 17:24:48 +00001394 def _CheckConfig(self):
1395 """Verify that the config matches the state of the existing checked-out
1396 solutions."""
1397 for dep in self.dependencies:
Michael Mossd683d7c2018-06-15 05:05:17 +00001398 if dep.managed and dep.url:
Edward Lemurbabd0982018-05-11 13:32:37 -04001399 scm = dep.CreateSCM()
smut@google.comd33eab32014-07-07 19:35:18 +00001400 actual_url = scm.GetActualRemoteURL(self._options)
borenet@google.com4e9be262014-04-08 19:40:30 +00001401 if actual_url and not scm.DoesRemoteURLMatch(self._options):
levarum@chromium.org27a6f9a2016-05-28 00:21:49 +00001402 mirror = scm.GetCacheMirror()
1403 if mirror:
1404 mirror_string = '%s (exists=%s)' % (mirror.mirror_path,
1405 mirror.exists())
1406 else:
1407 mirror_string = 'not used'
Raul Tambreb946b232019-03-26 14:48:46 +00001408 raise gclient_utils.Error(
1409 '''
borenet@google.com88d10082014-03-21 17:24:48 +00001410Your .gclient file seems to be broken. The requested URL is different from what
borenet@google.com0a427372014-04-02 19:12:13 +00001411is actually checked out in %(checkout_path)s.
borenet@google.com88d10082014-03-21 17:24:48 +00001412
borenet@google.com97882362014-04-07 20:06:02 +00001413The .gclient file contains:
levarum@chromium.org27a6f9a2016-05-28 00:21:49 +00001414URL: %(expected_url)s (%(expected_scm)s)
1415Cache mirror: %(mirror_string)s
borenet@google.com97882362014-04-07 20:06:02 +00001416
1417The local checkout in %(checkout_path)s reports:
1418%(actual_url)s (%(actual_scm)s)
borenet@google.com88d10082014-03-21 17:24:48 +00001419
1420You should ensure that the URL listed in .gclient is correct and either change
agabled437d762016-10-17 09:35:11 -07001421it or fix the checkout.
Josip Sokcevic14a83ae2020-05-21 01:36:34 +00001422''' % {
1423 'checkout_path': os.path.join(self.root_dir, dep.name),
1424 'expected_url': dep.url,
1425 'expected_scm': dep.GetScmName(),
1426 'mirror_string': mirror_string,
1427 'actual_url': actual_url,
1428 'actual_scm': dep.GetScmName()
1429 })
borenet@google.com88d10082014-03-21 17:24:48 +00001430
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001431 def SetConfig(self, content):
maruel@chromium.orgf13a4182011-09-22 00:26:15 +00001432 assert not self.dependencies
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001433 config_dict = {}
1434 self.config_content = content
1435 try:
1436 exec(content, config_dict)
vapier@chromium.orga81a56e2015-11-11 07:56:13 +00001437 except SyntaxError as e:
maruel@chromium.org5990f9d2010-07-07 18:02:58 +00001438 gclient_utils.SyntaxErrorToError('.gclient', e)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001439
peter@chromium.org1efccc82012-04-27 16:34:38 +00001440 # Append any target OS that is not already being enforced to the tuple.
1441 target_os = config_dict.get('target_os', [])
stuartmorgan@chromium.org18a4f6a2012-11-02 13:37:24 +00001442 if config_dict.get('target_os_only', False):
1443 self._enforced_os = tuple(set(target_os))
1444 else:
1445 self._enforced_os = tuple(set(self._enforced_os).union(target_os))
1446
Tom Andersonc31ae0b2018-02-06 14:48:56 -08001447 # Append any target CPU that is not already being enforced to the tuple.
1448 target_cpu = config_dict.get('target_cpu', [])
1449 if config_dict.get('target_cpu_only', False):
1450 self._enforced_cpu = tuple(set(target_cpu))
1451 else:
1452 self._enforced_cpu = tuple(set(self._enforced_cpu).union(target_cpu))
1453
Robert Iannuccia19649b2018-06-29 16:31:45 +00001454 cache_dir = config_dict.get('cache_dir', UNSET_CACHE_DIR)
1455 if cache_dir is not UNSET_CACHE_DIR:
1456 if cache_dir:
1457 cache_dir = os.path.join(self.root_dir, cache_dir)
1458 cache_dir = os.path.abspath(cache_dir)
Andrii Shyshkalov77ce4bd2017-11-27 12:38:18 -08001459
Robert Iannuccia19649b2018-06-29 16:31:45 +00001460 git_cache.Mirror.SetCachePath(cache_dir)
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001461
stuartmorgan@chromium.org18a4f6a2012-11-02 13:37:24 +00001462 if not target_os and config_dict.get('target_os_only', False):
1463 raise gclient_utils.Error('Can\'t use target_os_only if target_os is '
1464 'not specified')
peter@chromium.org1efccc82012-04-27 16:34:38 +00001465
Tom Andersonc31ae0b2018-02-06 14:48:56 -08001466 if not target_cpu and config_dict.get('target_cpu_only', False):
1467 raise gclient_utils.Error('Can\'t use target_cpu_only if target_cpu is '
1468 'not specified')
1469
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001470 deps_to_add = []
Michael Mossd683d7c2018-06-15 05:05:17 +00001471 for s in config_dict.get('solutions', []):
1472 try:
Michael Moss4e9b50a2018-05-23 22:35:06 -07001473 deps_to_add.append(GitDependency(
Edward Lemure05f18d2018-06-08 17:36:53 +00001474 parent=self,
1475 name=s['name'],
1476 url=s['url'],
1477 managed=s.get('managed', True),
1478 custom_deps=s.get('custom_deps', {}),
1479 custom_vars=s.get('custom_vars', {}),
1480 custom_hooks=s.get('custom_hooks', []),
1481 deps_file=s.get('deps_file', 'DEPS'),
Michael Mossd683d7c2018-06-15 05:05:17 +00001482 should_process=True,
Edward Lemurfbb06aa2018-06-11 20:43:06 +00001483 should_recurse=True,
Edward Lemure05f18d2018-06-08 17:36:53 +00001484 relative=None,
1485 condition=None,
1486 print_outbuf=True))
Michael Mossd683d7c2018-06-15 05:05:17 +00001487 except KeyError:
1488 raise gclient_utils.Error('Invalid .gclient file. Solution is '
1489 'incomplete: %s' % s)
Edward Lemur40764b02018-07-20 18:50:29 +00001490 metrics.collector.add(
1491 'project_urls',
1492 [
Edward Lemuraffd4102019-06-05 18:07:49 +00001493 dep.FuzzyMatchUrl(metrics_utils.KNOWN_PROJECT_URLS)
Edward Lemur40764b02018-07-20 18:50:29 +00001494 for dep in deps_to_add
1495 if dep.FuzzyMatchUrl(metrics_utils.KNOWN_PROJECT_URLS)
1496 ]
1497 )
1498
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001499 self.add_dependencies_and_close(deps_to_add, config_dict.get('hooks', []))
1500 logging.info('SetConfig() done')
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001501
1502 def SaveConfig(self):
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001503 gclient_utils.FileWrite(os.path.join(self.root_dir,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001504 self._options.config_filename),
1505 self.config_content)
1506
1507 @staticmethod
1508 def LoadCurrentConfig(options):
1509 """Searches for and loads a .gclient file relative to the current working
1510 dir. Returns a GClient object."""
szager@chromium.orge2e03202012-07-31 18:05:16 +00001511 if options.spec:
1512 client = GClient('.', options)
1513 client.SetConfig(options.spec)
1514 else:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00001515 if options.verbose:
1516 print('Looking for %s starting from %s\n' % (
1517 options.config_filename, os.getcwd()))
Nico Weber09e0b382019-03-11 16:54:07 +00001518 path = gclient_paths.FindGclientRoot(os.getcwd(), options.config_filename)
szager@chromium.orge2e03202012-07-31 18:05:16 +00001519 if not path:
Michael Achenbachb3ce73d2017-10-11 16:41:27 +02001520 if options.verbose:
1521 print('Couldn\'t find configuration file.')
szager@chromium.orge2e03202012-07-31 18:05:16 +00001522 return None
1523 client = GClient(path, options)
1524 client.SetConfig(gclient_utils.FileRead(
1525 os.path.join(path, options.config_filename)))
maruel@chromium.org69392e72011-10-13 22:09:00 +00001526
1527 if (options.revisions and
1528 len(client.dependencies) > 1 and
1529 any('@' not in r for r in options.revisions)):
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001530 print(
1531 ('You must specify the full solution name like --revision %s@%s\n'
1532 'when you have multiple solutions setup in your .gclient file.\n'
1533 'Other solutions present are: %s.') % (
maruel@chromium.org69392e72011-10-13 22:09:00 +00001534 client.dependencies[0].name,
1535 options.revisions[0],
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001536 ', '.join(s.name for s in client.dependencies[1:])),
1537 file=sys.stderr)
maruel@chromium.org15804092010-09-02 17:07:37 +00001538 return client
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001539
nsylvain@google.comefc80932011-05-31 21:27:56 +00001540 def SetDefaultConfig(self, solution_name, deps_file, solution_url,
Robert Iannuccia19649b2018-06-29 16:31:45 +00001541 managed=True, cache_dir=UNSET_CACHE_DIR,
1542 custom_vars=None):
1543 text = self.DEFAULT_CLIENT_FILE_TEXT
1544 format_dict = {
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001545 'solution_name': solution_name,
1546 'solution_url': solution_url,
nsylvain@google.comefc80932011-05-31 21:27:56 +00001547 'deps_file': deps_file,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001548 'managed': managed,
Paweł Hajdan, Jr3ba2a7c2017-10-04 19:24:46 +02001549 'custom_vars': custom_vars or {},
Robert Iannuccia19649b2018-06-29 16:31:45 +00001550 }
1551
1552 if cache_dir is not UNSET_CACHE_DIR:
1553 text += self.DEFAULT_CLIENT_CACHE_DIR_TEXT
1554 format_dict['cache_dir'] = cache_dir
1555
1556 self.SetConfig(text % format_dict)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001557
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001558 def _SaveEntries(self):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001559 """Creates a .gclient_entries file to record the list of unique checkouts.
1560
1561 The .gclient_entries file lives in the same directory as .gclient.
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001562 """
1563 # Sometimes pprint.pformat will use {', sometimes it'll use { ' ... It
1564 # makes testing a bit too fun.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001565 result = 'entries = {\n'
Michael Mossd683d7c2018-06-15 05:05:17 +00001566 for entry in self.root.subtree(False):
agabled437d762016-10-17 09:35:11 -07001567 result += ' %s: %s,\n' % (pprint.pformat(entry.name),
Edward Lemure7273d22018-05-10 19:13:51 -04001568 pprint.pformat(entry.url))
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001569 result += '}\n'
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001570 file_path = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org1333cb32011-10-04 23:40:16 +00001571 logging.debug(result)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001572 gclient_utils.FileWrite(file_path, result)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001573
1574 def _ReadEntries(self):
1575 """Read the .gclient_entries file for the given client.
1576
1577 Returns:
1578 A sequence of solution names, which will be empty if there is the
1579 entries file hasn't been created yet.
1580 """
1581 scope = {}
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001582 filename = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001583 if not os.path.exists(filename):
maruel@chromium.org73e21142010-07-05 13:32:01 +00001584 return {}
maruel@chromium.org5990f9d2010-07-07 18:02:58 +00001585 try:
1586 exec(gclient_utils.FileRead(filename), scope)
vapier@chromium.orga81a56e2015-11-11 07:56:13 +00001587 except SyntaxError as e:
maruel@chromium.org5990f9d2010-07-07 18:02:58 +00001588 gclient_utils.SyntaxErrorToError(filename, e)
Aaron Gable3721ee92017-04-03 14:53:14 -07001589 return scope.get('entries', {})
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001590
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001591 def _EnforceRevisions(self):
maruel@chromium.org918a9ae2010-05-28 15:50:30 +00001592 """Checks for revision overrides."""
1593 revision_overrides = {}
smutae7ea312016-07-18 11:59:41 -07001594 if self._options.head:
1595 return revision_overrides
joi@chromium.org792ea882010-11-10 02:37:27 +00001596 if not self._options.revisions:
maruel@chromium.org307d1792010-05-31 20:03:13 +00001597 return revision_overrides
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001598 solutions_names = [s.name for s in self.dependencies]
smutae7ea312016-07-18 11:59:41 -07001599 index = 0
1600 for revision in self._options.revisions:
1601 if not '@' in revision:
maruel@chromium.org307d1792010-05-31 20:03:13 +00001602 # Support for --revision 123
smutae7ea312016-07-18 11:59:41 -07001603 revision = '%s@%s' % (solutions_names[index], revision)
1604 name, rev = revision.split('@', 1)
szager@chromium.org4ad264b2014-05-20 04:43:47 +00001605 revision_overrides[name] = rev
smutae7ea312016-07-18 11:59:41 -07001606 index += 1
maruel@chromium.org918a9ae2010-05-28 15:50:30 +00001607 return revision_overrides
1608
Edward Lemur6a4e31b2018-08-10 19:59:02 +00001609 def _EnforcePatchRefsAndBranches(self):
Edward Lesmesc621b212018-03-21 20:26:56 -04001610 """Checks for patch refs."""
1611 patch_refs = {}
Edward Lemur6a4e31b2018-08-10 19:59:02 +00001612 target_branches = {}
Edward Lesmesc621b212018-03-21 20:26:56 -04001613 if not self._options.patch_refs:
Edward Lemur6a4e31b2018-08-10 19:59:02 +00001614 return patch_refs, target_branches
Edward Lesmesc621b212018-03-21 20:26:56 -04001615 for given_patch_ref in self._options.patch_refs:
1616 patch_repo, _, patch_ref = given_patch_ref.partition('@')
Edward Lemur4c5c8ab2019-06-07 15:58:13 +00001617 if not patch_repo or not patch_ref or ':' not in patch_ref:
Edward Lesmesc621b212018-03-21 20:26:56 -04001618 raise gclient_utils.Error(
1619 'Wrong revision format: %s should be of the form '
Edward Lemur4c5c8ab2019-06-07 15:58:13 +00001620 'patch_repo@target_branch:patch_ref.' % given_patch_ref)
1621 target_branch, _, patch_ref = patch_ref.partition(':')
1622 target_branches[patch_repo] = target_branch
Edward Lesmesc621b212018-03-21 20:26:56 -04001623 patch_refs[patch_repo] = patch_ref
Edward Lemur6a4e31b2018-08-10 19:59:02 +00001624 return patch_refs, target_branches
Edward Lesmesc621b212018-03-21 20:26:56 -04001625
Edward Lemur5b1fa942018-10-04 23:22:09 +00001626 def _RemoveUnversionedGitDirs(self):
1627 """Remove directories that are no longer part of the checkout.
1628
1629 Notify the user if there is an orphaned entry in their working copy.
1630 Only delete the directory if there are no changes in it, and
1631 delete_unversioned_trees is set to true.
1632 """
1633
1634 entries = [i.name for i in self.root.subtree(False) if i.url]
1635 full_entries = [os.path.join(self.root_dir, e.replace('/', os.path.sep))
1636 for e in entries]
1637
Marc-Antoine Ruel8e57b4b2019-10-11 01:01:36 +00001638 for entry, prev_url in self._ReadEntries().items():
Edward Lemur5b1fa942018-10-04 23:22:09 +00001639 if not prev_url:
1640 # entry must have been overridden via .gclient custom_deps
1641 continue
1642 # Fix path separator on Windows.
1643 entry_fixed = entry.replace('/', os.path.sep)
1644 e_dir = os.path.join(self.root_dir, entry_fixed)
1645 # Use entry and not entry_fixed there.
1646 if (entry not in entries and
1647 (not any(path.startswith(entry + '/') for path in entries)) and
1648 os.path.exists(e_dir)):
1649 # The entry has been removed from DEPS.
1650 scm = gclient_scm.GitWrapper(
1651 prev_url, self.root_dir, entry_fixed, self.outbuf)
1652
1653 # Check to see if this directory is now part of a higher-up checkout.
1654 scm_root = None
1655 try:
1656 scm_root = gclient_scm.scm.GIT.GetCheckoutRoot(scm.checkout_path)
1657 except subprocess2.CalledProcessError:
1658 pass
1659 if not scm_root:
1660 logging.warning('Could not find checkout root for %s. Unable to '
1661 'determine whether it is part of a higher-level '
1662 'checkout, so not removing.' % entry)
1663 continue
1664
1665 # This is to handle the case of third_party/WebKit migrating from
1666 # being a DEPS entry to being part of the main project.
1667 # If the subproject is a Git project, we need to remove its .git
1668 # folder. Otherwise git operations on that folder will have different
1669 # effects depending on the current working directory.
1670 if os.path.abspath(scm_root) == os.path.abspath(e_dir):
1671 e_par_dir = os.path.join(e_dir, os.pardir)
1672 if gclient_scm.scm.GIT.IsInsideWorkTree(e_par_dir):
1673 par_scm_root = gclient_scm.scm.GIT.GetCheckoutRoot(e_par_dir)
1674 # rel_e_dir : relative path of entry w.r.t. its parent repo.
1675 rel_e_dir = os.path.relpath(e_dir, par_scm_root)
1676 if gclient_scm.scm.GIT.IsDirectoryVersioned(
1677 par_scm_root, rel_e_dir):
1678 save_dir = scm.GetGitBackupDirPath()
1679 # Remove any eventual stale backup dir for the same project.
1680 if os.path.exists(save_dir):
1681 gclient_utils.rmtree(save_dir)
1682 os.rename(os.path.join(e_dir, '.git'), save_dir)
1683 # When switching between the two states (entry/ is a subproject
1684 # -> entry/ is part of the outer project), it is very likely
1685 # that some files are changed in the checkout, unless we are
1686 # jumping *exactly* across the commit which changed just DEPS.
1687 # In such case we want to cleanup any eventual stale files
1688 # (coming from the old subproject) in order to end up with a
1689 # clean checkout.
1690 gclient_scm.scm.GIT.CleanupDir(par_scm_root, rel_e_dir)
1691 assert not os.path.exists(os.path.join(e_dir, '.git'))
Raul Tambre80ee78e2019-05-06 22:41:05 +00001692 print('\nWARNING: \'%s\' has been moved from DEPS to a higher '
1693 'level checkout. The git folder containing all the local'
1694 ' branches has been saved to %s.\n'
1695 'If you don\'t care about its state you can safely '
1696 'remove that folder to free up space.' % (entry, save_dir))
Edward Lemur5b1fa942018-10-04 23:22:09 +00001697 continue
1698
1699 if scm_root in full_entries:
1700 logging.info('%s is part of a higher level checkout, not removing',
1701 scm.GetCheckoutRoot())
1702 continue
1703
1704 file_list = []
1705 scm.status(self._options, [], file_list)
1706 modified_files = file_list != []
1707 if (not self._options.delete_unversioned_trees or
1708 (modified_files and not self._options.force)):
1709 # There are modified files in this entry. Keep warning until
1710 # removed.
Henrique Ferreiroe72279d2019-04-17 12:01:50 +00001711 self.add_dependency(
1712 GitDependency(
1713 parent=self,
1714 name=entry,
1715 url=prev_url,
1716 managed=False,
1717 custom_deps={},
1718 custom_vars={},
1719 custom_hooks=[],
1720 deps_file=None,
1721 should_process=True,
1722 should_recurse=False,
1723 relative=None,
1724 condition=None))
Anthony Politobb457342019-11-15 22:26:01 +00001725 if modified_files and self._options.delete_unversioned_trees:
1726 print('\nWARNING: \'%s\' is no longer part of this client.\n'
1727 'Despite running \'gclient sync -D\' no action was taken '
1728 'as there are modifications.\nIt is recommended you revert '
1729 'all changes or run \'gclient sync -D --force\' next '
1730 'time.' % entry_fixed)
1731 else:
1732 print('\nWARNING: \'%s\' is no longer part of this client.\n'
1733 'It is recommended that you manually remove it or use '
1734 '\'gclient sync -D\' next time.' % entry_fixed)
Edward Lemur5b1fa942018-10-04 23:22:09 +00001735 else:
1736 # Delete the entry
1737 print('\n________ deleting \'%s\' in \'%s\'' % (
1738 entry_fixed, self.root_dir))
1739 gclient_utils.rmtree(e_dir)
1740 # record the current list of entries for next time
1741 self._SaveEntries()
1742
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001743 def RunOnDeps(self, command, args, ignore_requirements=False, progress=True):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001744 """Runs a command on each dependency in a client and its dependencies.
1745
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001746 Args:
1747 command: The command to use (e.g., 'status' or 'diff')
1748 args: list of str - extra arguments to add to the command line.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001749 """
Michael Mossd683d7c2018-06-15 05:05:17 +00001750 if not self.dependencies:
1751 raise gclient_utils.Error('No solution specified')
1752
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001753 revision_overrides = {}
Edward Lesmesc621b212018-03-21 20:26:56 -04001754 patch_refs = {}
Edward Lemur6a4e31b2018-08-10 19:59:02 +00001755 target_branches = {}
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001756 # It's unnecessary to check for revision overrides for 'recurse'.
1757 # Save a few seconds by not calling _EnforceRevisions() in that case.
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +02001758 if command not in ('diff', 'recurse', 'runhooks', 'status', 'revert',
1759 'validate'):
szager@chromium.org5273b8a2014-08-21 15:10:10 +00001760 self._CheckConfig()
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001761 revision_overrides = self._EnforceRevisions()
Edward Lesmesc621b212018-03-21 20:26:56 -04001762
1763 if command == 'update':
Edward Lemur6a4e31b2018-08-10 19:59:02 +00001764 patch_refs, target_branches = self._EnforcePatchRefsAndBranches()
Daniel Chenga21b5b32017-10-19 20:07:48 +00001765 # Disable progress for non-tty stdout.
Daniel Chenga0c5f082017-10-19 13:35:19 -07001766 should_show_progress = (
1767 setup_color.IS_TTY and not self._options.verbose and progress)
1768 pm = None
1769 if should_show_progress:
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001770 if command in ('update', 'revert'):
1771 pm = Progress('Syncing projects', 1)
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +02001772 elif command in ('recurse', 'validate'):
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001773 pm = Progress(' '.join(args), 1)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001774 work_queue = gclient_utils.ExecutionQueue(
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001775 self._options.jobs, pm, ignore_requirements=ignore_requirements,
1776 verbose=self._options.verbose)
maruel@chromium.org049bced2010-08-12 13:37:20 +00001777 for s in self.dependencies:
Michael Mossd683d7c2018-06-15 05:05:17 +00001778 if s.should_process:
1779 work_queue.enqueue(s)
Edward Lesmesc621b212018-03-21 20:26:56 -04001780 work_queue.flush(revision_overrides, command, args, options=self._options,
Edward Lemur6a4e31b2018-08-10 19:59:02 +00001781 patch_refs=patch_refs, target_branches=target_branches)
Edward Lesmesc621b212018-03-21 20:26:56 -04001782
szager@chromium.org4ad264b2014-05-20 04:43:47 +00001783 if revision_overrides:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001784 print('Please fix your script, having invalid --revision flags will soon '
Edward Lesmesc621b212018-03-21 20:26:56 -04001785 'be considered an error.', file=sys.stderr)
1786
1787 if patch_refs:
1788 raise gclient_utils.Error(
1789 'The following --patch-ref flags were not used. Please fix it:\n%s' %
1790 ('\n'.join(
1791 patch_repo + '@' + patch_ref
Marc-Antoine Ruel8e57b4b2019-10-11 01:01:36 +00001792 for patch_repo, patch_ref in patch_refs.items())))
piman@chromium.org6f363722010-04-27 00:41:09 +00001793
Dirk Pranke9f20d022017-10-11 18:36:54 -07001794 # Once all the dependencies have been processed, it's now safe to write
Michael Moss848c86e2018-05-03 16:05:50 -07001795 # out the gn_args_file and run the hooks.
Dirk Pranke9f20d022017-10-11 18:36:54 -07001796 if command == 'update':
Michael Moss848c86e2018-05-03 16:05:50 -07001797 gn_args_dep = self.dependencies[0]
1798 if gn_args_dep._gn_args_from:
1799 deps_map = dict([(dep.name, dep) for dep in gn_args_dep.dependencies])
1800 gn_args_dep = deps_map.get(gn_args_dep._gn_args_from)
1801 if gn_args_dep and gn_args_dep.HasGNArgsFile():
1802 gn_args_dep.WriteGNArgsFile()
Dirk Pranke9f20d022017-10-11 18:36:54 -07001803
Edward Lemur5b1fa942018-10-04 23:22:09 +00001804 self._RemoveUnversionedGitDirs()
Edward Lemur647e1e72018-09-19 18:15:29 +00001805
1806 # Sync CIPD dependencies once removed deps are deleted. In case a git
1807 # dependency was moved to CIPD, we want to remove the old git directory
1808 # first and then sync the CIPD dep.
1809 if self._cipd_root:
1810 self._cipd_root.run(command)
1811
Edward Lemur5b1fa942018-10-04 23:22:09 +00001812 if not self._options.nohooks:
1813 if should_show_progress:
1814 pm = Progress('Running hooks', 1)
1815 self.RunHooksRecursively(self._options, pm)
1816
1817
maruel@chromium.org17cdf762010-05-28 17:30:52 +00001818 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001819
1820 def PrintRevInfo(self):
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001821 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +00001822 raise gclient_utils.Error('No solution specified')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001823 # Load all the settings.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001824 work_queue = gclient_utils.ExecutionQueue(
1825 self._options.jobs, None, False, verbose=self._options.verbose)
maruel@chromium.org049bced2010-08-12 13:37:20 +00001826 for s in self.dependencies:
Michael Mossd683d7c2018-06-15 05:05:17 +00001827 if s.should_process:
1828 work_queue.enqueue(s)
Edward Lemur6a4e31b2018-08-10 19:59:02 +00001829 work_queue.flush({}, None, [], options=self._options, patch_refs=None,
1830 target_branches=None)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001831
Michael Mossd683d7c2018-06-15 05:05:17 +00001832 def ShouldPrintRevision(dep):
Edward Lesmesbb16e332018-03-30 17:54:51 -04001833 return (not self._options.filter
Edward Lemure7273d22018-05-10 19:13:51 -04001834 or dep.FuzzyMatchUrl(self._options.filter))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001835
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001836 if self._options.snapshot:
Michael Mossd683d7c2018-06-15 05:05:17 +00001837 json_output = []
1838 # First level at .gclient
1839 for d in self.dependencies:
1840 entries = {}
1841 def GrabDeps(dep):
1842 """Recursively grab dependencies."""
1843 for d in dep.dependencies:
1844 d.PinToActualRevision()
1845 if ShouldPrintRevision(d):
1846 entries[d.name] = d.url
1847 GrabDeps(d)
1848 GrabDeps(d)
1849 json_output.append({
1850 'name': d.name,
1851 'solution_url': d.url,
1852 'deps_file': d.deps_file,
1853 'managed': d.managed,
1854 'custom_deps': entries,
1855 })
1856 if self._options.output_json == '-':
1857 print(json.dumps(json_output, indent=2, separators=(',', ': ')))
1858 elif self._options.output_json:
1859 with open(self._options.output_json, 'w') as f:
1860 json.dump(json_output, f)
1861 else:
1862 # Print the snapshot configuration file
1863 print(self.DEFAULT_SNAPSHOT_FILE_TEXT % {
1864 'solution_list': pprint.pformat(json_output, indent=2),
1865 })
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +00001866 else:
Michael Mossd683d7c2018-06-15 05:05:17 +00001867 entries = {}
1868 for d in self.root.subtree(False):
1869 if self._options.actual:
1870 d.PinToActualRevision()
1871 if ShouldPrintRevision(d):
1872 entries[d.name] = d.url
1873 if self._options.output_json:
1874 json_output = {
1875 name: {
1876 'url': rev.split('@')[0] if rev else None,
1877 'rev': rev.split('@')[1] if rev and '@' in rev else None,
1878 }
Marc-Antoine Ruel8e57b4b2019-10-11 01:01:36 +00001879 for name, rev in entries.items()
Michael Mossd683d7c2018-06-15 05:05:17 +00001880 }
1881 if self._options.output_json == '-':
1882 print(json.dumps(json_output, indent=2, separators=(',', ': ')))
1883 else:
1884 with open(self._options.output_json, 'w') as f:
1885 json.dump(json_output, f)
1886 else:
1887 keys = sorted(entries.keys())
1888 for x in keys:
1889 print('%s: %s' % (x, entries[x]))
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +00001890 logging.info(str(self))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001891
Edward Lemure05f18d2018-06-08 17:36:53 +00001892 def ParseDepsFile(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001893 """No DEPS to parse for a .gclient file."""
maruel@chromium.org049bced2010-08-12 13:37:20 +00001894 raise gclient_utils.Error('Internal error')
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001895
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00001896 def PrintLocationAndContents(self):
1897 # Print out the .gclient file. This is longer than if we just printed the
1898 # client dict, but more legible, and it might contain helpful comments.
1899 print('Loaded .gclient config in %s:\n%s' % (
1900 self.root_dir, self.config_content))
1901
John Budorickd3ba72b2018-03-20 12:27:42 -07001902 def GetCipdRoot(self):
1903 if not self._cipd_root:
1904 self._cipd_root = gclient_scm.CipdRoot(
1905 self.root_dir,
1906 # TODO(jbudorick): Support other service URLs as necessary.
1907 # Service URLs should be constant over the scope of a cipd
1908 # root, so a var per DEPS file specifying the service URL
1909 # should suffice.
1910 'https://chrome-infra-packages.appspot.com')
1911 return self._cipd_root
1912
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001913 @property
maruel@chromium.org75a59272010-06-11 22:34:03 +00001914 def root_dir(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001915 """Root directory of gclient checkout."""
maruel@chromium.org75a59272010-06-11 22:34:03 +00001916 return self._root_dir
1917
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001918 @property
maruel@chromium.org271375b2010-06-23 19:17:38 +00001919 def enforced_os(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001920 """What deps_os entries that are to be parsed."""
maruel@chromium.org271375b2010-06-23 19:17:38 +00001921 return self._enforced_os
1922
maruel@chromium.org68988972011-09-20 14:11:42 +00001923 @property
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +00001924 def target_os(self):
1925 return self._enforced_os
1926
Tom Andersonc31ae0b2018-02-06 14:48:56 -08001927 @property
1928 def target_cpu(self):
1929 return self._enforced_cpu
1930
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001931
John Budorick0f7b2002018-01-19 15:46:17 -08001932class CipdDependency(Dependency):
1933 """A Dependency object that represents a single CIPD package."""
1934
Michael Mossd683d7c2018-06-15 05:05:17 +00001935 def __init__(
1936 self, parent, name, dep_value, cipd_root,
1937 custom_vars, should_process, relative, condition):
John Budorick0f7b2002018-01-19 15:46:17 -08001938 package = dep_value['package']
1939 version = dep_value['version']
1940 url = urlparse.urljoin(
1941 cipd_root.service_url, '%s@%s' % (package, version))
1942 super(CipdDependency, self).__init__(
Edward Lemure05f18d2018-06-08 17:36:53 +00001943 parent=parent,
1944 name=name + ':' + package,
1945 url=url,
1946 managed=None,
1947 custom_deps=None,
1948 custom_vars=custom_vars,
1949 custom_hooks=None,
1950 deps_file=None,
Michael Mossd683d7c2018-06-15 05:05:17 +00001951 should_process=should_process,
Edward Lemurfbb06aa2018-06-11 20:43:06 +00001952 should_recurse=False,
Edward Lemure05f18d2018-06-08 17:36:53 +00001953 relative=relative,
1954 condition=condition)
John Budorickd3ba72b2018-03-20 12:27:42 -07001955 self._cipd_package = None
John Budorick0f7b2002018-01-19 15:46:17 -08001956 self._cipd_root = cipd_root
John Budorick4099daa2018-06-21 19:22:10 +00001957 # CIPD wants /-separated paths, even on Windows.
1958 native_subdir_path = os.path.relpath(
Shenghua Zhang6f830312018-02-26 11:45:07 -08001959 os.path.join(self.root.root_dir, name), cipd_root.root_dir)
John Budorick4099daa2018-06-21 19:22:10 +00001960 self._cipd_subdir = posixpath.join(*native_subdir_path.split(os.sep))
John Budorickd3ba72b2018-03-20 12:27:42 -07001961 self._package_name = package
1962 self._package_version = version
1963
1964 #override
Edward Lesmesc621b212018-03-21 20:26:56 -04001965 def run(self, revision_overrides, command, args, work_queue, options,
Edward Lemur6a4e31b2018-08-10 19:59:02 +00001966 patch_refs, target_branches):
John Budorickd3ba72b2018-03-20 12:27:42 -07001967 """Runs |command| then parse the DEPS file."""
1968 logging.info('CipdDependency(%s).run()' % self.name)
Michael Mossd683d7c2018-06-15 05:05:17 +00001969 if not self.should_process:
1970 return
John Budorickd3ba72b2018-03-20 12:27:42 -07001971 self._CreatePackageIfNecessary()
1972 super(CipdDependency, self).run(revision_overrides, command, args,
Edward Lemur6a4e31b2018-08-10 19:59:02 +00001973 work_queue, options, patch_refs,
1974 target_branches)
John Budorickd3ba72b2018-03-20 12:27:42 -07001975
1976 def _CreatePackageIfNecessary(self):
1977 # We lazily create the CIPD package to make sure that only packages
1978 # that we want (as opposed to all packages defined in all DEPS files
1979 # we parse) get added to the root and subsequently ensured.
1980 if not self._cipd_package:
1981 self._cipd_package = self._cipd_root.add_package(
1982 self._cipd_subdir, self._package_name, self._package_version)
John Budorick0f7b2002018-01-19 15:46:17 -08001983
Edward Lemure05f18d2018-06-08 17:36:53 +00001984 def ParseDepsFile(self):
John Budorick0f7b2002018-01-19 15:46:17 -08001985 """CIPD dependencies are not currently allowed to have nested deps."""
1986 self.add_dependencies_and_close([], [])
1987
1988 #override
Shenghua Zhang6f830312018-02-26 11:45:07 -08001989 def verify_validity(self):
1990 """CIPD dependencies allow duplicate name for packages in same directory."""
1991 logging.info('Dependency(%s).verify_validity()' % self.name)
1992 return True
1993
1994 #override
Edward Lemurbabd0982018-05-11 13:32:37 -04001995 def GetScmName(self):
John Budorick0f7b2002018-01-19 15:46:17 -08001996 """Always 'cipd'."""
John Budorick0f7b2002018-01-19 15:46:17 -08001997 return 'cipd'
1998
1999 #override
Edward Lemurbabd0982018-05-11 13:32:37 -04002000 def CreateSCM(self, out_cb=None):
John Budorick0f7b2002018-01-19 15:46:17 -08002001 """Create a Wrapper instance suitable for handling this CIPD dependency."""
John Budorickd3ba72b2018-03-20 12:27:42 -07002002 self._CreatePackageIfNecessary()
John Budorick0f7b2002018-01-19 15:46:17 -08002003 return gclient_scm.CipdWrapper(
Edward Lemurbabd0982018-05-11 13:32:37 -04002004 self.url, self.root.root_dir, self.name, self.outbuf, out_cb,
2005 root=self._cipd_root, package=self._cipd_package)
John Budorick0f7b2002018-01-19 15:46:17 -08002006
Edward Lemure4e15042018-06-28 18:07:00 +00002007 def hierarchy(self, include_url=False):
2008 return self.parent.hierarchy(include_url) + ' -> ' + self._cipd_subdir
2009
John Budorick0f7b2002018-01-19 15:46:17 -08002010 def ToLines(self):
2011 """Return a list of lines representing this in a DEPS file."""
John Budorickc35aba52018-06-28 20:57:03 +00002012 def escape_cipd_var(package):
2013 return package.replace('{', '{{').replace('}', '}}')
2014
John Budorick0f7b2002018-01-19 15:46:17 -08002015 s = []
John Budorickd3ba72b2018-03-20 12:27:42 -07002016 self._CreatePackageIfNecessary()
John Budorick0f7b2002018-01-19 15:46:17 -08002017 if self._cipd_package.authority_for_subdir:
2018 condition_part = ([' "condition": %r,' % self.condition]
2019 if self.condition else [])
2020 s.extend([
Michael Moss4e9b50a2018-05-23 22:35:06 -07002021 ' # %s' % self.hierarchy(include_url=False),
John Budorickd3ba72b2018-03-20 12:27:42 -07002022 ' "%s": {' % (self.name.split(':')[0],),
John Budorick0f7b2002018-01-19 15:46:17 -08002023 ' "packages": [',
2024 ])
John Budorick4099daa2018-06-21 19:22:10 +00002025 for p in sorted(
2026 self._cipd_root.packages(self._cipd_subdir),
Edward Lemur26a8b9f2019-08-15 20:46:44 +00002027 key=lambda x: x.name):
John Budorick0f7b2002018-01-19 15:46:17 -08002028 s.extend([
John Budorick64e33cb2018-02-20 09:40:30 -08002029 ' {',
John Budorickc35aba52018-06-28 20:57:03 +00002030 ' "package": "%s",' % escape_cipd_var(p.name),
John Budorick64e33cb2018-02-20 09:40:30 -08002031 ' "version": "%s",' % p.version,
2032 ' },',
John Budorick0f7b2002018-01-19 15:46:17 -08002033 ])
John Budorickd3ba72b2018-03-20 12:27:42 -07002034
John Budorick0f7b2002018-01-19 15:46:17 -08002035 s.extend([
2036 ' ],',
2037 ' "dep_type": "cipd",',
2038 ] + condition_part + [
2039 ' },',
2040 '',
2041 ])
2042 return s
2043
2044
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002045#### gclient commands.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002046
2047
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002048@subcommand.usage('[command] [args ...]')
Edward Lemur3298e7b2018-07-17 18:21:27 +00002049@metrics.collector.collect_metrics('gclient recurse')
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00002050def CMDrecurse(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002051 """Operates [command args ...] on all the dependencies.
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00002052
2053 Runs a shell command on all entries.
qyearsley12fa6ff2016-08-24 09:18:40 -07002054 Sets GCLIENT_DEP_PATH environment variable as the dep's relative location to
ilevy@chromium.org37116242012-11-28 01:32:48 +00002055 root directory of the checkout.
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00002056 """
2057 # Stop parsing at the first non-arg so that these go through to the command
2058 parser.disable_interspersed_args()
2059 parser.add_option('-s', '--scm', action='append', default=[],
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00002060 help='Choose scm types to operate upon.')
maruel@chromium.org288054d2012-03-05 00:43:07 +00002061 parser.add_option('-i', '--ignore', action='store_true',
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00002062 help='Ignore non-zero return codes from subcommands.')
2063 parser.add_option('--prepend-dir', action='store_true',
2064 help='Prepend relative dir for use with git <cmd> --null.')
2065 parser.add_option('--no-progress', action='store_true',
2066 help='Disable progress bar that shows sub-command updates')
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00002067 options, args = parser.parse_args(args)
maruel@chromium.org45e9f2d2010-10-18 13:33:46 +00002068 if not args:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002069 print('Need to supply a command!', file=sys.stderr)
maruel@chromium.org45e9f2d2010-10-18 13:33:46 +00002070 return 1
maruel@chromium.org78cba522010-10-18 13:32:05 +00002071 root_and_entries = gclient_utils.GetGClientRootAndEntries()
2072 if not root_and_entries:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002073 print(
maruel@chromium.org78cba522010-10-18 13:32:05 +00002074 'You need to run gclient sync at least once to use \'recurse\'.\n'
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002075 'This is because .gclient_entries needs to exist and be up to date.',
2076 file=sys.stderr)
maruel@chromium.org78cba522010-10-18 13:32:05 +00002077 return 1
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00002078
2079 # Normalize options.scm to a set()
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00002080 scm_set = set()
2081 for scm in options.scm:
2082 scm_set.update(scm.split(','))
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00002083 options.scm = scm_set
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00002084
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00002085 options.nohooks = True
2086 client = GClient.LoadCurrentConfig(options)
Marc-Antoine Ruele6e06412017-10-18 13:47:02 -04002087 if not client:
2088 raise gclient_utils.Error('client not configured; see \'gclient config\'')
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00002089 return client.RunOnDeps('recurse', args, ignore_requirements=True,
2090 progress=not options.no_progress)
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00002091
2092
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002093@subcommand.usage('[args ...]')
Edward Lemur3298e7b2018-07-17 18:21:27 +00002094@metrics.collector.collect_metrics('gclient fetch')
davidbarr@chromium.org12f944e2012-03-01 02:18:31 +00002095def CMDfetch(parser, args):
2096 """Fetches upstream commits for all modules.
2097
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002098 Completely git-specific. Simply runs 'git fetch [args ...]' for each module.
2099 """
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00002100 (options, args) = parser.parse_args(args)
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002101 return CMDrecurse(OptionParser(), [
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00002102 '--jobs=%d' % options.jobs, '--scm=git', 'git', 'fetch'] + args)
2103
2104
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002105class Flattener(object):
2106 """Flattens a gclient solution."""
2107
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02002108 def __init__(self, client, pin_all_deps=False):
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002109 """Constructor.
2110
2111 Arguments:
2112 client (GClient): client to flatten
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02002113 pin_all_deps (bool): whether to pin all deps, even if they're not pinned
2114 in DEPS
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002115 """
2116 self._client = client
2117
2118 self._deps_string = None
Paweł Hajdan, Jrfeb01642017-09-12 15:50:46 +02002119 self._deps_files = set()
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002120
2121 self._allowed_hosts = set()
2122 self._deps = {}
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002123 self._hooks = []
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002124 self._pre_deps_hooks = []
Paweł Hajdan, Jrfb022012017-07-06 18:00:08 +02002125 self._vars = {}
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002126
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02002127 self._flatten(pin_all_deps=pin_all_deps)
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002128
2129 @property
2130 def deps_string(self):
2131 assert self._deps_string is not None
2132 return self._deps_string
2133
Paweł Hajdan, Jrfeb01642017-09-12 15:50:46 +02002134 @property
2135 def deps_files(self):
2136 return self._deps_files
2137
Paweł Hajdan, Jr5ec77132017-08-16 19:21:06 +02002138 def _pin_dep(self, dep):
2139 """Pins a dependency to specific full revision sha.
2140
2141 Arguments:
2142 dep (Dependency): dependency to process
2143 """
Michael Mossd683d7c2018-06-15 05:05:17 +00002144 if dep.url is None:
2145 return
2146
Paweł Hajdan, Jr5ec77132017-08-16 19:21:06 +02002147 # Make sure the revision is always fully specified (a hash),
2148 # as opposed to refs or tags which might change. Similarly,
2149 # shortened shas might become ambiguous; make sure to always
2150 # use full one for pinning.
Edward Lemure7273d22018-05-10 19:13:51 -04002151 revision = gclient_utils.SplitUrlRevision(dep.url)[1]
2152 if not revision or not gclient_utils.IsFullGitSha(revision):
2153 dep.PinToActualRevision()
Paweł Hajdan, Jr5ec77132017-08-16 19:21:06 +02002154
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02002155 def _flatten(self, pin_all_deps=False):
2156 """Runs the flattener. Saves resulting DEPS string.
2157
2158 Arguments:
2159 pin_all_deps (bool): whether to pin all deps, even if they're not pinned
2160 in DEPS
2161 """
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002162 for solution in self._client.dependencies:
Paweł Hajdan, Jr11eb7152017-08-10 12:50:11 +02002163 self._add_dep(solution)
Michael Mossd683d7c2018-06-15 05:05:17 +00002164 self._flatten_dep(solution)
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002165
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02002166 if pin_all_deps:
Marc-Antoine Ruel8e57b4b2019-10-11 01:01:36 +00002167 for dep in self._deps.values():
Paweł Hajdan, Jr5ec77132017-08-16 19:21:06 +02002168 self._pin_dep(dep)
Paweł Hajdan, Jr39300ba2017-08-11 16:52:38 +02002169
Paweł Hajdan, Jr76a9d042017-08-18 20:05:41 +02002170 def add_deps_file(dep):
Paweł Hajdan, Jr0870df22017-08-23 17:59:29 +02002171 # Only include DEPS files referenced by recursedeps.
Edward Lemurfbb06aa2018-06-11 20:43:06 +00002172 if not dep.should_recurse:
Paweł Hajdan, Jr0870df22017-08-23 17:59:29 +02002173 return
Paweł Hajdan, Jrfeb01642017-09-12 15:50:46 +02002174 deps_file = dep.deps_file
2175 deps_path = os.path.join(self._client.root_dir, dep.name, deps_file)
Paweł Hajdan, Jr76a9d042017-08-18 20:05:41 +02002176 if not os.path.exists(deps_path):
Paweł Hajdan, Jrfeb01642017-09-12 15:50:46 +02002177 # gclient has a fallback that if deps_file doesn't exist, it'll try
2178 # DEPS. Do the same here.
2179 deps_file = 'DEPS'
2180 deps_path = os.path.join(self._client.root_dir, dep.name, deps_file)
2181 if not os.path.exists(deps_path):
2182 return
Michael Mossd683d7c2018-06-15 05:05:17 +00002183 assert dep.url
Edward Lemure7273d22018-05-10 19:13:51 -04002184 self._deps_files.add((dep.url, deps_file, dep.hierarchy_data()))
Marc-Antoine Ruel8e57b4b2019-10-11 01:01:36 +00002185 for dep in self._deps.values():
Paweł Hajdan, Jr76a9d042017-08-18 20:05:41 +02002186 add_deps_file(dep)
Paweł Hajdan, Jr76a9d042017-08-18 20:05:41 +02002187
Michael Moss848c86e2018-05-03 16:05:50 -07002188 gn_args_dep = self._deps.get(self._client.dependencies[0]._gn_args_from,
2189 self._client.dependencies[0])
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002190 self._deps_string = '\n'.join(
Michael Moss848c86e2018-05-03 16:05:50 -07002191 _GNSettingsToLines(gn_args_dep._gn_args_file, gn_args_dep._gn_args) +
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002192 _AllowedHostsToLines(self._allowed_hosts) +
2193 _DepsToLines(self._deps) +
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002194 _HooksToLines('hooks', self._hooks) +
2195 _HooksToLines('pre_deps_hooks', self._pre_deps_hooks) +
Paweł Hajdan, Jrfb022012017-07-06 18:00:08 +02002196 _VarsToLines(self._vars) +
Paweł Hajdan, Jr76a9d042017-08-18 20:05:41 +02002197 ['# %s, %s' % (url, deps_file)
Michael Mossfe68c912018-03-22 19:19:35 -07002198 for url, deps_file, _ in sorted(self._deps_files)] +
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002199 ['']) # Ensure newline at end of file.
2200
Paweł Hajdan, Jr11eb7152017-08-10 12:50:11 +02002201 def _add_dep(self, dep):
2202 """Helper to add a dependency to flattened DEPS.
2203
2204 Arguments:
2205 dep (Dependency): dependency to add
2206 """
2207 assert dep.name not in self._deps or self._deps.get(dep.name) == dep, (
2208 dep.name, self._deps.get(dep.name))
Michael Mossd683d7c2018-06-15 05:05:17 +00002209 if dep.url:
2210 self._deps[dep.name] = dep
Paweł Hajdan, Jr11eb7152017-08-10 12:50:11 +02002211
Edward Lemur16f4bad2018-05-16 16:53:49 -04002212 def _flatten_dep(self, dep):
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002213 """Visits a dependency in order to flatten it (see CMDflatten).
2214
2215 Arguments:
2216 dep (Dependency): dependency to process
2217 """
Edward Lemur16f4bad2018-05-16 16:53:49 -04002218 logging.debug('_flatten_dep(%s)', dep.name)
Paweł Hajdan, Jradae2a62017-08-18 16:49:57 +02002219
Edward Lemur16f4bad2018-05-16 16:53:49 -04002220 assert dep.deps_parsed, (
2221 "Attempted to flatten %s but it has not been processed." % dep.name)
Paweł Hajdan, Jrc69b32e2017-08-17 18:47:48 +02002222
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002223 self._allowed_hosts.update(dep.allowed_hosts)
2224
Michael Mossce9f17f2018-01-31 13:16:35 -08002225 # Only include vars explicitly listed in the DEPS files or gclient solution,
2226 # not automatic, local overrides (i.e. not all of dep.get_vars()).
Michael Moss4e9b50a2018-05-23 22:35:06 -07002227 hierarchy = dep.hierarchy(include_url=False)
Marc-Antoine Ruel8e57b4b2019-10-11 01:01:36 +00002228 for key, value in dep._vars.items():
Paweł Hajdan, Jrc9353602017-08-02 17:52:08 +02002229 # Make sure there are no conflicting variables. It is fine however
2230 # to use same variable name, as long as the value is consistent.
Takuto Ikuta575872e2019-02-21 15:20:07 +00002231 assert key not in self._vars or self._vars[key][1] == value, (
2232 "dep:%s key:%s value:%s != %s" % (
2233 dep.name, key, value, self._vars[key][1]))
Michael Mossce9f17f2018-01-31 13:16:35 -08002234 self._vars[key] = (hierarchy, value)
2235 # Override explicit custom variables.
Marc-Antoine Ruel8e57b4b2019-10-11 01:01:36 +00002236 for key, value in dep.custom_vars.items():
Michael Mossce9f17f2018-01-31 13:16:35 -08002237 # Do custom_vars that don't correspond to DEPS vars ever make sense? DEPS
2238 # conditionals shouldn't be using vars that aren't also defined in the
2239 # DEPS (presubmit actually disallows this), so any new custom_var must be
2240 # unused in the DEPS, so no need to add it to the flattened output either.
2241 if key not in self._vars:
2242 continue
2243 # Don't "override" existing vars if it's actually the same value.
2244 elif self._vars[key][1] == value:
2245 continue
2246 # Anything else is overriding a default value from the DEPS.
2247 self._vars[key] = (hierarchy + ' [custom_var override]', value)
Paweł Hajdan, Jrfb022012017-07-06 18:00:08 +02002248
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002249 self._pre_deps_hooks.extend([(dep, hook) for hook in dep.pre_deps_hooks])
Edward Lemur16f4bad2018-05-16 16:53:49 -04002250 self._hooks.extend([(dep, hook) for hook in dep.deps_hooks])
Paweł Hajdan, Jradae2a62017-08-18 16:49:57 +02002251
Paweł Hajdan, Jrb0ad16e2017-08-03 15:33:21 +02002252 for sub_dep in dep.dependencies:
Edward Lemur16f4bad2018-05-16 16:53:49 -04002253 self._add_dep(sub_dep)
Paweł Hajdan, Jrb0ad16e2017-08-03 15:33:21 +02002254
Edward Lemurfbb06aa2018-06-11 20:43:06 +00002255 for d in dep.dependencies:
2256 if d.should_recurse:
2257 self._flatten_dep(d)
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002258
2259
Edward Lemur3298e7b2018-07-17 18:21:27 +00002260@metrics.collector.collect_metrics('gclient flatten')
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002261def CMDflatten(parser, args):
2262 """Flattens the solutions into a single DEPS file."""
2263 parser.add_option('--output-deps', help='Path to the output DEPS file')
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02002264 parser.add_option(
Paweł Hajdan, Jrfeb01642017-09-12 15:50:46 +02002265 '--output-deps-files',
2266 help=('Path to the output metadata about DEPS files referenced by '
2267 'recursedeps.'))
2268 parser.add_option(
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02002269 '--pin-all-deps', action='store_true',
2270 help=('Pin all deps, even if not pinned in DEPS. CAVEAT: only does so '
2271 'for checked out deps, NOT deps_os.'))
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002272 options, args = parser.parse_args(args)
2273
2274 options.nohooks = True
Paweł Hajdan, Jr5ec77132017-08-16 19:21:06 +02002275 options.process_all_deps = True
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002276 client = GClient.LoadCurrentConfig(options)
2277
2278 # Only print progress if we're writing to a file. Otherwise, progress updates
2279 # could obscure intended output.
2280 code = client.RunOnDeps('flatten', args, progress=options.output_deps)
2281 if code != 0:
2282 return code
2283
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02002284 flattener = Flattener(client, pin_all_deps=options.pin_all_deps)
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002285
2286 if options.output_deps:
2287 with open(options.output_deps, 'w') as f:
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002288 f.write(flattener.deps_string)
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002289 else:
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002290 print(flattener.deps_string)
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002291
Michael Mossfe68c912018-03-22 19:19:35 -07002292 deps_files = [{'url': d[0], 'deps_file': d[1], 'hierarchy': d[2]}
Paweł Hajdan, Jrfeb01642017-09-12 15:50:46 +02002293 for d in sorted(flattener.deps_files)]
2294 if options.output_deps_files:
2295 with open(options.output_deps_files, 'w') as f:
2296 json.dump(deps_files, f)
2297
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002298 return 0
2299
2300
Paweł Hajdan, Jr3c2aa832017-06-07 20:22:16 +02002301def _GNSettingsToLines(gn_args_file, gn_args):
2302 s = []
2303 if gn_args_file:
2304 s.extend([
2305 'gclient_gn_args_file = "%s"' % gn_args_file,
2306 'gclient_gn_args = %r' % gn_args,
2307 ])
2308 return s
2309
2310
Paweł Hajdan, Jr6014b562017-06-30 17:43:42 +02002311def _AllowedHostsToLines(allowed_hosts):
2312 """Converts |allowed_hosts| set to list of lines for output."""
2313 if not allowed_hosts:
2314 return []
2315 s = ['allowed_hosts = [']
2316 for h in sorted(allowed_hosts):
2317 s.append(' "%s",' % h)
2318 s.extend([']', ''])
2319 return s
2320
2321
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002322def _DepsToLines(deps):
2323 """Converts |deps| dict to list of lines for output."""
Paweł Hajdan, Jr5b593352017-06-29 18:37:45 +02002324 if not deps:
2325 return []
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002326 s = ['deps = {']
Edward Lemuree7b9dd2019-07-20 01:29:08 +00002327 for _, dep in sorted(deps.items()):
John Budorick0f7b2002018-01-19 15:46:17 -08002328 s.extend(dep.ToLines())
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002329 s.extend(['}', ''])
2330 return s
2331
2332
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02002333def _DepsOsToLines(deps_os):
2334 """Converts |deps_os| dict to list of lines for output."""
Paweł Hajdan, Jr5b593352017-06-29 18:37:45 +02002335 if not deps_os:
2336 return []
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02002337 s = ['deps_os = {']
Edward Lemuree7b9dd2019-07-20 01:29:08 +00002338 for dep_os, os_deps in sorted(deps_os.items()):
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02002339 s.append(' "%s": {' % dep_os)
Edward Lemuree7b9dd2019-07-20 01:29:08 +00002340 for name, dep in sorted(os_deps.items()):
Paweł Hajdan, Jr78ce24e2017-10-03 17:09:13 +02002341 condition_part = ([' "condition": %r,' % dep.condition]
Paweł Hajdan, Jrad30de62017-06-26 18:51:58 +02002342 if dep.condition else [])
2343 s.extend([
Michael Moss4e9b50a2018-05-23 22:35:06 -07002344 ' # %s' % dep.hierarchy(include_url=False),
Paweł Hajdan, Jrad30de62017-06-26 18:51:58 +02002345 ' "%s": {' % (name,),
Edward Lemure05f18d2018-06-08 17:36:53 +00002346 ' "url": "%s",' % (dep.url,),
Paweł Hajdan, Jrad30de62017-06-26 18:51:58 +02002347 ] + condition_part + [
2348 ' },',
2349 '',
2350 ])
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02002351 s.extend([' },', ''])
2352 s.extend(['}', ''])
2353 return s
2354
2355
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002356def _HooksToLines(name, hooks):
2357 """Converts |hooks| list to list of lines for output."""
Paweł Hajdan, Jr5b593352017-06-29 18:37:45 +02002358 if not hooks:
2359 return []
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002360 s = ['%s = [' % name]
2361 for dep, hook in hooks:
2362 s.extend([
Michael Moss4e9b50a2018-05-23 22:35:06 -07002363 ' # %s' % dep.hierarchy(include_url=False),
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002364 ' {',
2365 ])
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +02002366 if hook.name is not None:
2367 s.append(' "name": "%s",' % hook.name)
2368 if hook.pattern is not None:
2369 s.append(' "pattern": "%s",' % hook.pattern)
Paweł Hajdan, Jrecf53fe2017-09-29 18:28:49 +02002370 if hook.condition is not None:
Paweł Hajdan, Jr78ce24e2017-10-03 17:09:13 +02002371 s.append(' "condition": %r,' % hook.condition)
Corentin Walleza68660d2018-09-10 17:33:24 +00002372 # Flattened hooks need to be written relative to the root gclient dir
2373 cwd = os.path.relpath(os.path.normpath(hook.effective_cwd))
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002374 s.extend(
Corentin Walleza68660d2018-09-10 17:33:24 +00002375 [' "cwd": "%s",' % cwd] +
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002376 [' "action": ['] +
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +02002377 [' "%s",' % arg for arg in hook.action] +
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002378 [' ]', ' },', '']
2379 )
2380 s.extend([']', ''])
2381 return s
2382
2383
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +02002384def _HooksOsToLines(hooks_os):
2385 """Converts |hooks| list to list of lines for output."""
Paweł Hajdan, Jr5b593352017-06-29 18:37:45 +02002386 if not hooks_os:
2387 return []
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +02002388 s = ['hooks_os = {']
Marc-Antoine Ruel8e57b4b2019-10-11 01:01:36 +00002389 for hook_os, os_hooks in hooks_os.items():
Michael Moss017bcf62017-06-28 15:26:38 -07002390 s.append(' "%s": [' % hook_os)
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +02002391 for dep, hook in os_hooks:
2392 s.extend([
Michael Moss4e9b50a2018-05-23 22:35:06 -07002393 ' # %s' % dep.hierarchy(include_url=False),
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +02002394 ' {',
2395 ])
2396 if hook.name is not None:
2397 s.append(' "name": "%s",' % hook.name)
2398 if hook.pattern is not None:
2399 s.append(' "pattern": "%s",' % hook.pattern)
Paweł Hajdan, Jrecf53fe2017-09-29 18:28:49 +02002400 if hook.condition is not None:
Paweł Hajdan, Jr78ce24e2017-10-03 17:09:13 +02002401 s.append(' "condition": %r,' % hook.condition)
Corentin Walleza68660d2018-09-10 17:33:24 +00002402 # Flattened hooks need to be written relative to the root gclient dir
2403 cwd = os.path.relpath(os.path.normpath(hook.effective_cwd))
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +02002404 s.extend(
Corentin Walleza68660d2018-09-10 17:33:24 +00002405 [' "cwd": "%s",' % cwd] +
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +02002406 [' "action": ['] +
2407 [' "%s",' % arg for arg in hook.action] +
2408 [' ]', ' },', '']
2409 )
Michael Moss017bcf62017-06-28 15:26:38 -07002410 s.extend([' ],', ''])
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +02002411 s.extend(['}', ''])
2412 return s
2413
2414
Paweł Hajdan, Jrfb022012017-07-06 18:00:08 +02002415def _VarsToLines(variables):
2416 """Converts |variables| dict to list of lines for output."""
2417 if not variables:
2418 return []
2419 s = ['vars = {']
Edward Lemuree7b9dd2019-07-20 01:29:08 +00002420 for key, tup in sorted(variables.items()):
Michael Mossce9f17f2018-01-31 13:16:35 -08002421 hierarchy, value = tup
Paweł Hajdan, Jrfb022012017-07-06 18:00:08 +02002422 s.extend([
Michael Mossce9f17f2018-01-31 13:16:35 -08002423 ' # %s' % hierarchy,
Paweł Hajdan, Jrfb022012017-07-06 18:00:08 +02002424 ' "%s": %r,' % (key, value),
2425 '',
2426 ])
2427 s.extend(['}', ''])
2428 return s
2429
2430
Edward Lemur3298e7b2018-07-17 18:21:27 +00002431@metrics.collector.collect_metrics('gclient grep')
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00002432def CMDgrep(parser, args):
2433 """Greps through git repos managed by gclient.
2434
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002435 Runs 'git grep [args...]' for each module.
2436 """
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00002437 # We can't use optparse because it will try to parse arguments sent
2438 # to git grep and throw an error. :-(
2439 if not args or re.match('(-h|--help)$', args[0]):
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002440 print(
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00002441 'Usage: gclient grep [-j <N>] git-grep-args...\n\n'
2442 'Example: "gclient grep -j10 -A2 RefCountedBase" runs\n"git grep '
2443 '-A2 RefCountedBase" on each of gclient\'s git\nrepos with up to '
2444 '10 jobs.\n\nBonus: page output by appending "|& less -FRSX" to the'
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002445 ' end of your query.',
2446 file=sys.stderr)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00002447 return 1
2448
2449 jobs_arg = ['--jobs=1']
2450 if re.match(r'(-j|--jobs=)\d+$', args[0]):
2451 jobs_arg, args = args[:1], args[1:]
2452 elif re.match(r'(-j|--jobs)$', args[0]):
2453 jobs_arg, args = args[:2], args[2:]
2454
2455 return CMDrecurse(
2456 parser,
2457 jobs_arg + ['--ignore', '--prepend-dir', '--no-progress', '--scm=git',
2458 'git', 'grep', '--null', '--color=Always'] + args)
davidbarr@chromium.org12f944e2012-03-01 02:18:31 +00002459
2460
Edward Lemur3298e7b2018-07-17 18:21:27 +00002461@metrics.collector.collect_metrics('gclient root')
stip@chromium.orga735da22015-04-29 23:18:20 +00002462def CMDroot(parser, args):
2463 """Outputs the solution root (or current dir if there isn't one)."""
2464 (options, args) = parser.parse_args(args)
2465 client = GClient.LoadCurrentConfig(options)
2466 if client:
2467 print(os.path.abspath(client.root_dir))
2468 else:
2469 print(os.path.abspath('.'))
2470
2471
agablea98a6cd2016-11-15 14:30:10 -08002472@subcommand.usage('[url]')
Edward Lemur3298e7b2018-07-17 18:21:27 +00002473@metrics.collector.collect_metrics('gclient config')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002474def CMDconfig(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002475 """Creates a .gclient file in the current directory.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00002476
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002477 This specifies the configuration for further commands. After update/sync,
2478 top-level DEPS files in each module are read to determine dependent
2479 modules to operate on as well. If optional [url] parameter is
2480 provided, then configuration is read from a specified Subversion server
2481 URL.
2482 """
szager@chromium.orge2e03202012-07-31 18:05:16 +00002483 # We do a little dance with the --gclientfile option. 'gclient config' is the
2484 # only command where it's acceptable to have both '--gclientfile' and '--spec'
2485 # arguments. So, we temporarily stash any --gclientfile parameter into
2486 # options.output_config_file until after the (gclientfile xor spec) error
2487 # check.
2488 parser.remove_option('--gclientfile')
2489 parser.add_option('--gclientfile', dest='output_config_file',
2490 help='Specify an alternate .gclient file')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002491 parser.add_option('--name',
2492 help='overrides the default name for the solution')
nsylvain@google.comefc80932011-05-31 21:27:56 +00002493 parser.add_option('--deps-file', default='DEPS',
David Benjamin105e11e2017-10-16 10:39:35 -04002494 help='overrides the default name for the DEPS file for the '
nsylvain@google.comefc80932011-05-31 21:27:56 +00002495 'main solutions and all sub-dependencies')
smutae7ea312016-07-18 11:59:41 -07002496 parser.add_option('--unmanaged', action='store_true', default=False,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00002497 help='overrides the default behavior to make it possible '
smutae7ea312016-07-18 11:59:41 -07002498 'to have the main solution untouched by gclient '
2499 '(gclient will check out unmanaged dependencies but '
2500 'will never sync them)')
Robert Iannuccia19649b2018-06-29 16:31:45 +00002501 parser.add_option('--cache-dir', default=UNSET_CACHE_DIR,
2502 help='Cache all git repos into this dir and do shared '
2503 'clones from the cache, instead of cloning directly '
2504 'from the remote. Pass "None" to disable cache, even '
2505 'if globally enabled due to $GIT_CACHE_PATH.')
Paweł Hajdan, Jr3ba2a7c2017-10-04 19:24:46 +02002506 parser.add_option('--custom-var', action='append', dest='custom_vars',
2507 default=[],
2508 help='overrides variables; key=value syntax')
szager@chromium.orge2e03202012-07-31 18:05:16 +00002509 parser.set_defaults(config_filename=None)
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002510 (options, args) = parser.parse_args(args)
szager@chromium.orge2e03202012-07-31 18:05:16 +00002511 if options.output_config_file:
2512 setattr(options, 'config_filename', getattr(options, 'output_config_file'))
maruel@chromium.org5fc2a332010-05-26 19:37:15 +00002513 if ((options.spec and args) or len(args) > 2 or
2514 (not options.spec and not args)):
2515 parser.error('Inconsistent arguments. Use either --spec or one or 2 args')
2516
Robert Iannuccia19649b2018-06-29 16:31:45 +00002517 if (options.cache_dir is not UNSET_CACHE_DIR
2518 and options.cache_dir.lower() == 'none'):
2519 options.cache_dir = None
2520
Paweł Hajdan, Jr3ba2a7c2017-10-04 19:24:46 +02002521 custom_vars = {}
2522 for arg in options.custom_vars:
2523 kv = arg.split('=', 1)
2524 if len(kv) != 2:
2525 parser.error('Invalid --custom-var argument: %r' % arg)
2526 custom_vars[kv[0]] = gclient_eval.EvaluateCondition(kv[1], {})
2527
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002528 client = GClient('.', options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002529 if options.spec:
2530 client.SetConfig(options.spec)
2531 else:
maruel@chromium.org1ab7ffc2009-06-03 17:21:37 +00002532 base_url = args[0].rstrip('/')
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00002533 if not options.name:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002534 name = base_url.split('/')[-1]
nsylvain@google.com12649ef2011-06-01 17:11:20 +00002535 if name.endswith('.git'):
2536 name = name[:-4]
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00002537 else:
2538 # specify an alternate relpath for the given URL.
2539 name = options.name
agable@chromium.orgf2214672015-10-27 21:02:48 +00002540 if not os.path.abspath(os.path.join(os.getcwd(), name)).startswith(
2541 os.getcwd()):
2542 parser.error('Do not pass a relative path for --name.')
2543 if any(x in ('..', '.', '/', '\\') for x in name.split(os.sep)):
2544 parser.error('Do not include relative path components in --name.')
2545
nsylvain@google.comefc80932011-05-31 21:27:56 +00002546 deps_file = options.deps_file
agablea98a6cd2016-11-15 14:30:10 -08002547 client.SetDefaultConfig(name, deps_file, base_url,
smutae7ea312016-07-18 11:59:41 -07002548 managed=not options.unmanaged,
Paweł Hajdan, Jr3ba2a7c2017-10-04 19:24:46 +02002549 cache_dir=options.cache_dir,
2550 custom_vars=custom_vars)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002551 client.SaveConfig()
maruel@chromium.org79692d62010-05-14 18:57:13 +00002552 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002553
2554
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002555@subcommand.epilog("""Example:
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002556 gclient pack > patch.txt
2557 generate simple patch for configured client and dependences
2558""")
Edward Lemur3298e7b2018-07-17 18:21:27 +00002559@metrics.collector.collect_metrics('gclient pack')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002560def CMDpack(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002561 """Generates a patch which can be applied at the root of the tree.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00002562
agabled437d762016-10-17 09:35:11 -07002563 Internally, runs 'git diff' on each checked out module and
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002564 dependencies, and performs minimal postprocessing of the output. The
2565 resulting patch is printed to stdout and can be applied to a freshly
2566 checked out tree via 'patch -p0 < patchfile'.
2567 """
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002568 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2569 help='override deps for the specified (comma-separated) '
2570 'platform(s); \'all\' will process all deps_os '
2571 'references')
haitao.feng@intel.com306080c2012-05-04 13:11:29 +00002572 parser.remove_option('--jobs')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002573 (options, args) = parser.parse_args(args)
iannucci@chromium.org50395ea2013-04-04 04:47:42 +00002574 # Force jobs to 1 so the stdout is not annotated with the thread ids
haitao.feng@intel.com306080c2012-05-04 13:11:29 +00002575 options.jobs = 1
kbr@google.comab318592009-09-04 00:54:55 +00002576 client = GClient.LoadCurrentConfig(options)
2577 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002578 raise gclient_utils.Error('client not configured; see \'gclient config\'')
kbr@google.comab318592009-09-04 00:54:55 +00002579 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002580 client.PrintLocationAndContents()
kbr@google.comab318592009-09-04 00:54:55 +00002581 return client.RunOnDeps('pack', args)
2582
2583
Edward Lemur3298e7b2018-07-17 18:21:27 +00002584@metrics.collector.collect_metrics('gclient status')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002585def CMDstatus(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002586 """Shows modification status for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002587 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2588 help='override deps for the specified (comma-separated) '
2589 'platform(s); \'all\' will process all deps_os '
2590 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002591 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002592 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002593 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002594 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002595 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002596 client.PrintLocationAndContents()
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002597 return client.RunOnDeps('status', args)
2598
2599
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002600@subcommand.epilog("""Examples:
maruel@chromium.org79692d62010-05-14 18:57:13 +00002601 gclient sync
2602 update files from SCM according to current configuration,
2603 *for modules which have changed since last update or sync*
2604 gclient sync --force
2605 update files from SCM according to current configuration, for
2606 all modules (useful for recovering files deleted from local copy)
2607 gclient sync --revision src@31000
2608 update src directory to r31000
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002609
2610JSON output format:
2611If the --output-json option is specified, the following document structure will
2612be emitted to the provided file. 'null' entries may occur for subprojects which
2613are present in the gclient solution, but were not processed (due to custom_deps,
2614os_deps, etc.)
2615
2616{
2617 "solutions" : {
2618 "<name>": { # <name> is the posix-normalized path to the solution.
agabled437d762016-10-17 09:35:11 -07002619 "revision": [<git id hex string>|null],
2620 "scm": ["git"|null],
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002621 }
2622 }
2623}
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002624""")
Edward Lemur3298e7b2018-07-17 18:21:27 +00002625@metrics.collector.collect_metrics('gclient sync')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002626def CMDsync(parser, args):
2627 """Checkout/update all modules."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002628 parser.add_option('-f', '--force', action='store_true',
2629 help='force update even for unchanged modules')
2630 parser.add_option('-n', '--nohooks', action='store_true',
2631 help='don\'t run hooks after the update is complete')
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00002632 parser.add_option('-p', '--noprehooks', action='store_true',
2633 help='don\'t run pre-DEPS hooks', default=False)
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002634 parser.add_option('-r', '--revision', action='append',
2635 dest='revisions', metavar='REV', default=[],
2636 help='Enforces revision/hash for the solutions with the '
2637 'format src@rev. The src@ part is optional and can be '
Edward Lesmes53014652018-03-07 18:01:40 -05002638 'skipped. You can also specify URLs instead of paths '
2639 'and gclient will find the solution corresponding to '
2640 'the given URL. If a path is also specified, the URL '
2641 'takes precedence. -r can be used multiple times when '
2642 '.gclient has multiple solutions configured, and will '
2643 'work even if the src@ part is skipped.')
Edward Lesmesc621b212018-03-21 20:26:56 -04002644 parser.add_option('--patch-ref', action='append',
2645 dest='patch_refs', metavar='GERRIT_REF', default=[],
Edward Lemur6a4e31b2018-08-10 19:59:02 +00002646 help='Patches the given reference with the format '
Edward Lemur4c5c8ab2019-06-07 15:58:13 +00002647 'dep@target-ref:patch-ref. '
Edward Lemur6a4e31b2018-08-10 19:59:02 +00002648 'For |dep|, you can specify URLs as well as paths, '
2649 'with URLs taking preference. '
2650 '|patch-ref| will be applied to |dep|, rebased on top '
2651 'of what |dep| was synced to, and a soft reset will '
2652 'be done. Use --no-rebase-patch-ref and '
2653 '--no-reset-patch-ref to disable this behavior. '
2654 '|target-ref| is the target branch against which a '
2655 'patch was created, it is used to determine which '
2656 'commits from the |patch-ref| actually constitute a '
Edward Lemur4c5c8ab2019-06-07 15:58:13 +00002657 'patch.')
maruel@chromium.org794207e2013-03-08 15:29:43 +00002658 parser.add_option('--with_branch_heads', action='store_true',
2659 help='Clone git "branch_heads" refspecs in addition to '
2660 'the default refspecs. This adds about 1/2GB to a '
2661 'full checkout. (git only)')
szager@chromium.org8d3348f2014-08-19 22:49:16 +00002662 parser.add_option('--with_tags', action='store_true',
2663 help='Clone git tags in addition to the default refspecs.')
agable2697cd12016-06-28 10:23:53 -07002664 parser.add_option('-H', '--head', action='store_true',
agablea98a6cd2016-11-15 14:30:10 -08002665 help='DEPRECATED: only made sense with safesync urls.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002666 parser.add_option('-D', '--delete_unversioned_trees', action='store_true',
steveblock@chromium.org98e69452012-02-16 16:36:43 +00002667 help='Deletes from the working copy any dependencies that '
2668 'have been removed since the last sync, as long as '
2669 'there are no local modifications. When used with '
2670 '--force, such dependencies are removed even if they '
2671 'have local modifications. When used with --reset, '
2672 'all untracked directories are removed from the '
tnagel@chromium.orga2aaa632014-02-28 21:47:27 +00002673 'working copy, excluding those which are explicitly '
steveblock@chromium.org98e69452012-02-16 16:36:43 +00002674 'ignored in the repository.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002675 parser.add_option('-R', '--reset', action='store_true',
2676 help='resets any local changes before updating (git only)')
bauerb@chromium.org2aad1b22011-07-22 12:00:41 +00002677 parser.add_option('-M', '--merge', action='store_true',
2678 help='merge upstream changes instead of trying to '
2679 'fast-forward or rebase')
dnj@chromium.org5b23e872015-02-20 21:25:57 +00002680 parser.add_option('-A', '--auto_rebase', action='store_true',
2681 help='Automatically rebase repositories against local '
2682 'checkout during update (git only).')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002683 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2684 help='override deps for the specified (comma-separated) '
2685 'platform(s); \'all\' will process all deps_os '
2686 'references')
Paweł Hajdan, Jr5ec77132017-08-16 19:21:06 +02002687 parser.add_option('--process-all-deps', action='store_true',
2688 help='Check out all deps, even for different OS-es, '
2689 'or with conditions evaluating to false')
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +00002690 parser.add_option('--upstream', action='store_true',
2691 help='Make repo state match upstream branch.')
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002692 parser.add_option('--output-json',
2693 help='Output a json document to this path containing '
2694 'summary information about the sync.')
primiano@chromium.org5439ea52014-08-06 17:18:18 +00002695 parser.add_option('--no-history', action='store_true',
2696 help='GIT ONLY - Reduces the size/time of the checkout at '
2697 'the cost of no history. Requires Git 1.9+')
hinoka@chromium.org46b87412014-05-15 00:42:05 +00002698 parser.add_option('--shallow', action='store_true',
2699 help='GIT ONLY - Do a shallow clone into the cache dir. '
2700 'Requires Git 1.9+')
e.hakkinen@samsung.come8bc1aa2015-04-08 08:00:37 +00002701 parser.add_option('--no_bootstrap', '--no-bootstrap',
2702 action='store_true',
2703 help='Don\'t bootstrap from Google Storage.')
Josip Sokcevic14a83ae2020-05-21 01:36:34 +00002704 parser.add_option('--ignore_locks',
2705 action='store_true',
2706 help='No longer used.')
2707 parser.add_option('--break_repo_locks',
2708 action='store_true',
2709 help='No longer used.')
Vadim Shtayura08049e22017-10-11 00:14:52 +00002710 parser.add_option('--lock_timeout', type='int', default=5000,
2711 help='GIT ONLY - Deadline (in seconds) to wait for git '
2712 'cache lock to become available. Default is %default.')
Edward Lesmesc621b212018-03-21 20:26:56 -04002713 parser.add_option('--no-rebase-patch-ref', action='store_false',
2714 dest='rebase_patch_ref', default=True,
2715 help='Bypass rebase of the patch ref after checkout.')
2716 parser.add_option('--no-reset-patch-ref', action='store_false',
2717 dest='reset_patch_ref', default=True,
2718 help='Bypass calling reset after patching the ref.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002719 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002720 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002721
2722 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002723 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002724
Josip Sokcevic14a83ae2020-05-21 01:36:34 +00002725 if options.ignore_locks:
2726 print('Warning: ignore_locks is no longer used. Please remove its usage.')
2727
2728 if options.break_repo_locks:
2729 print('Warning: break_repo_locks is no longer used. Please remove its '
2730 'usage.')
2731
smutae7ea312016-07-18 11:59:41 -07002732 if options.revisions and options.head:
2733 # TODO(maruel): Make it a parser.error if it doesn't break any builder.
2734 print('Warning: you cannot use both --head and --revision')
2735
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002736 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002737 client.PrintLocationAndContents()
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002738 ret = client.RunOnDeps('update', args)
2739 if options.output_json:
2740 slns = {}
Michael Mossd683d7c2018-06-15 05:05:17 +00002741 for d in client.subtree(True):
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002742 normed = d.name.replace('\\', '/').rstrip('/') + '/'
2743 slns[normed] = {
2744 'revision': d.got_revision,
2745 'scm': d.used_scm.name if d.used_scm else None,
Michael Mossd683d7c2018-06-15 05:05:17 +00002746 'url': str(d.url) if d.url else None,
Edward Lemur7ccf2f02018-06-26 20:41:56 +00002747 'was_processed': d.should_process,
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002748 }
Edward Lemurca879322019-09-09 20:18:13 +00002749 with open(options.output_json, 'w') as f:
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002750 json.dump({'solutions': slns}, f)
2751 return ret
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002752
2753
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002754CMDupdate = CMDsync
2755
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002756
Edward Lemur3298e7b2018-07-17 18:21:27 +00002757@metrics.collector.collect_metrics('gclient validate')
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +02002758def CMDvalidate(parser, args):
2759 """Validates the .gclient and DEPS syntax."""
2760 options, args = parser.parse_args(args)
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +02002761 client = GClient.LoadCurrentConfig(options)
2762 rv = client.RunOnDeps('validate', args)
2763 if rv == 0:
2764 print('validate: SUCCESS')
2765 else:
2766 print('validate: FAILURE')
2767 return rv
2768
2769
Edward Lemur3298e7b2018-07-17 18:21:27 +00002770@metrics.collector.collect_metrics('gclient diff')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002771def CMDdiff(parser, args):
2772 """Displays local diff for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002773 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2774 help='override deps for the specified (comma-separated) '
2775 'platform(s); \'all\' will process all deps_os '
2776 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002777 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002778 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002779 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002780 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002781 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002782 client.PrintLocationAndContents()
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002783 return client.RunOnDeps('diff', args)
2784
2785
Edward Lemur3298e7b2018-07-17 18:21:27 +00002786@metrics.collector.collect_metrics('gclient revert')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002787def CMDrevert(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002788 """Reverts all modifications in every dependencies.
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00002789
2790 That's the nuclear option to get back to a 'clean' state. It removes anything
agabled437d762016-10-17 09:35:11 -07002791 that shows up in git status."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002792 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2793 help='override deps for the specified (comma-separated) '
2794 'platform(s); \'all\' will process all deps_os '
2795 'references')
2796 parser.add_option('-n', '--nohooks', action='store_true',
2797 help='don\'t run hooks after the revert is complete')
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00002798 parser.add_option('-p', '--noprehooks', action='store_true',
2799 help='don\'t run pre-DEPS hooks', default=False)
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +00002800 parser.add_option('--upstream', action='store_true',
2801 help='Make repo state match upstream branch.')
Josip Sokcevic14a83ae2020-05-21 01:36:34 +00002802 parser.add_option('--break_repo_locks',
2803 action='store_true',
2804 help='No longer used.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002805 (options, args) = parser.parse_args(args)
Josip Sokcevic14a83ae2020-05-21 01:36:34 +00002806 if options.break_repo_locks:
2807 print('Warning: break_repo_locks is no longer used. Please remove its ' +
2808 'usage.')
2809
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002810 # --force is implied.
2811 options.force = True
steveblock@chromium.org98e69452012-02-16 16:36:43 +00002812 options.reset = False
2813 options.delete_unversioned_trees = False
agablec903d732016-07-26 09:07:24 -07002814 options.merge = False
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002815 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002816 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002817 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002818 return client.RunOnDeps('revert', args)
2819
2820
Edward Lemur3298e7b2018-07-17 18:21:27 +00002821@metrics.collector.collect_metrics('gclient runhooks')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002822def CMDrunhooks(parser, args):
2823 """Runs hooks for files that have been modified in the local working copy."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002824 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2825 help='override deps for the specified (comma-separated) '
2826 'platform(s); \'all\' will process all deps_os '
2827 'references')
2828 parser.add_option('-f', '--force', action='store_true', default=True,
2829 help='Deprecated. No effect.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002830 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002831 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002832 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002833 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002834 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002835 client.PrintLocationAndContents()
maruel@chromium.org5df6a462009-08-28 18:52:26 +00002836 options.force = True
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002837 options.nohooks = False
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002838 return client.RunOnDeps('runhooks', args)
2839
2840
Edward Lemur3298e7b2018-07-17 18:21:27 +00002841@metrics.collector.collect_metrics('gclient revinfo')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002842def CMDrevinfo(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002843 """Outputs revision info mapping for the client and its dependencies.
maruel@chromium.org9eda4112010-06-11 18:56:10 +00002844
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002845 This allows the capture of an overall 'revision' for the source tree that
maruel@chromium.org9eda4112010-06-11 18:56:10 +00002846 can be used to reproduce the same tree in the future. It is only useful for
agabled437d762016-10-17 09:35:11 -07002847 'unpinned dependencies', i.e. DEPS/deps references without a git hash.
2848 A git branch name isn't 'pinned' since the actual commit can change.
maruel@chromium.org9eda4112010-06-11 18:56:10 +00002849 """
2850 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2851 help='override deps for the specified (comma-separated) '
2852 'platform(s); \'all\' will process all deps_os '
2853 'references')
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00002854 parser.add_option('-a', '--actual', action='store_true',
2855 help='gets the actual checked out revisions instead of the '
2856 'ones specified in the DEPS and .gclient files')
maruel@chromium.org9eda4112010-06-11 18:56:10 +00002857 parser.add_option('-s', '--snapshot', action='store_true',
2858 help='creates a snapshot .gclient file of the current '
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00002859 'version of all repositories to reproduce the tree, '
2860 'implies -a')
Edward Lesmesbb16e332018-03-30 17:54:51 -04002861 parser.add_option('--filter', action='append', dest='filter',
Edward Lesmesdaa76d22018-03-06 14:56:57 -05002862 help='Display revision information only for the specified '
Edward Lesmesbb16e332018-03-30 17:54:51 -04002863 'dependencies (filtered by URL or path).')
Edward Lesmesc2960242018-03-06 20:50:15 -05002864 parser.add_option('--output-json',
2865 help='Output a json document to this path containing '
2866 'information about the revisions.')
Joey Scarr8d3925b2018-07-15 23:36:25 +00002867 parser.add_option('--ignore-dep-type', choices=['git', 'cipd'],
2868 help='Specify to skip processing of a certain type of dep.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002869 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002870 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002871 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002872 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002873 client.PrintRevInfo()
maruel@chromium.org79692d62010-05-14 18:57:13 +00002874 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002875
2876
Edward Lemur3298e7b2018-07-17 18:21:27 +00002877@metrics.collector.collect_metrics('gclient getdep')
Edward Lesmes411041f2018-04-05 20:12:55 -04002878def CMDgetdep(parser, args):
2879 """Gets revision information and variable values from a DEPS file."""
2880 parser.add_option('--var', action='append',
2881 dest='vars', metavar='VAR', default=[],
2882 help='Gets the value of a given variable.')
2883 parser.add_option('-r', '--revision', action='append',
Edward Lemuraf3328f2018-11-19 14:11:46 +00002884 dest='getdep_revisions', metavar='DEP', default=[],
Edward Lesmes411041f2018-04-05 20:12:55 -04002885 help='Gets the revision/version for the given dependency. '
2886 'If it is a git dependency, dep must be a path. If it '
2887 'is a CIPD dependency, dep must be of the form '
2888 'path:package.')
2889 parser.add_option('--deps-file', default='DEPS',
2890 # TODO(ehmaldonado): Try to find the DEPS file pointed by
2891 # .gclient first.
2892 help='The DEPS file to be edited. Defaults to the DEPS '
2893 'file in the current directory.')
2894 (options, args) = parser.parse_args(args)
2895
2896 if not os.path.isfile(options.deps_file):
2897 raise gclient_utils.Error(
2898 'DEPS file %s does not exist.' % options.deps_file)
2899 with open(options.deps_file) as f:
2900 contents = f.read()
Edward Lemuraf3328f2018-11-19 14:11:46 +00002901 client = GClient.LoadCurrentConfig(options)
2902 if client is not None:
2903 builtin_vars = client.get_builtin_vars()
2904 else:
Edward Lemurca879322019-09-09 20:18:13 +00002905 logging.warning(
Edward Lemuraf3328f2018-11-19 14:11:46 +00002906 'Couldn\'t find a valid gclient config. Will attempt to parse the DEPS '
2907 'file without support for built-in variables.')
2908 builtin_vars = None
2909 local_scope = gclient_eval.Exec(contents, options.deps_file,
2910 builtin_vars=builtin_vars)
Edward Lesmes411041f2018-04-05 20:12:55 -04002911
2912 for var in options.vars:
2913 print(gclient_eval.GetVar(local_scope, var))
2914
Edward Lemuraf3328f2018-11-19 14:11:46 +00002915 for name in options.getdep_revisions:
Edward Lesmes411041f2018-04-05 20:12:55 -04002916 if ':' in name:
2917 name, _, package = name.partition(':')
2918 if not name or not package:
2919 parser.error(
2920 'Wrong CIPD format: %s:%s should be of the form path:pkg.'
2921 % (name, package))
2922 print(gclient_eval.GetCIPD(local_scope, name, package))
2923 else:
2924 print(gclient_eval.GetRevision(local_scope, name))
2925
2926
Edward Lemur3298e7b2018-07-17 18:21:27 +00002927@metrics.collector.collect_metrics('gclient setdep')
Edward Lesmes6f64a052018-03-20 17:35:49 -04002928def CMDsetdep(parser, args):
Edward Lesmes0ecf6d62018-04-05 18:28:55 -04002929 """Modifies dependency revisions and variable values in a DEPS file"""
Edward Lesmes6f64a052018-03-20 17:35:49 -04002930 parser.add_option('--var', action='append',
2931 dest='vars', metavar='VAR=VAL', default=[],
2932 help='Sets a variable to the given value with the format '
2933 'name=value.')
2934 parser.add_option('-r', '--revision', action='append',
Edward Lemuraf3328f2018-11-19 14:11:46 +00002935 dest='setdep_revisions', metavar='DEP@REV', default=[],
Edward Lesmes6f64a052018-03-20 17:35:49 -04002936 help='Sets the revision/version for the dependency with '
2937 'the format dep@rev. If it is a git dependency, dep '
2938 'must be a path and rev must be a git hash or '
2939 'reference (e.g. src/dep@deadbeef). If it is a CIPD '
2940 'dependency, dep must be of the form path:package and '
2941 'rev must be the package version '
2942 '(e.g. src/pkg:chromium/pkg@2.1-cr0).')
2943 parser.add_option('--deps-file', default='DEPS',
2944 # TODO(ehmaldonado): Try to find the DEPS file pointed by
2945 # .gclient first.
2946 help='The DEPS file to be edited. Defaults to the DEPS '
2947 'file in the current directory.')
2948 (options, args) = parser.parse_args(args)
Edward Lesmes0ecf6d62018-04-05 18:28:55 -04002949 if args:
2950 parser.error('Unused arguments: "%s"' % '" "'.join(args))
Edward Lesmesae6836e2018-11-19 15:27:20 +00002951 if not options.setdep_revisions and not options.vars:
Edward Lesmes0ecf6d62018-04-05 18:28:55 -04002952 parser.error(
2953 'You must specify at least one variable or revision to modify.')
Edward Lesmes6f64a052018-03-20 17:35:49 -04002954
Edward Lesmes6f64a052018-03-20 17:35:49 -04002955 if not os.path.isfile(options.deps_file):
2956 raise gclient_utils.Error(
2957 'DEPS file %s does not exist.' % options.deps_file)
2958 with open(options.deps_file) as f:
2959 contents = f.read()
Edward Lemuraf3328f2018-11-19 14:11:46 +00002960
2961 client = GClient.LoadCurrentConfig(options)
2962 if client is not None:
2963 builtin_vars = client.get_builtin_vars()
2964 else:
Edward Lemurca879322019-09-09 20:18:13 +00002965 logging.warning(
Edward Lemuraf3328f2018-11-19 14:11:46 +00002966 'Couldn\'t find a valid gclient config. Will attempt to parse the DEPS '
2967 'file without support for built-in variables.')
2968 builtin_vars = None
2969
2970 local_scope = gclient_eval.Exec(contents, options.deps_file,
2971 builtin_vars=builtin_vars)
Edward Lesmes6f64a052018-03-20 17:35:49 -04002972
2973 for var in options.vars:
2974 name, _, value = var.partition('=')
2975 if not name or not value:
Edward Lesmes0ecf6d62018-04-05 18:28:55 -04002976 parser.error(
Edward Lesmes6f64a052018-03-20 17:35:49 -04002977 'Wrong var format: %s should be of the form name=value.' % var)
Edward Lesmes3d993812018-04-02 12:52:49 -04002978 if name in local_scope['vars']:
2979 gclient_eval.SetVar(local_scope, name, value)
2980 else:
2981 gclient_eval.AddVar(local_scope, name, value)
Edward Lesmes6f64a052018-03-20 17:35:49 -04002982
Edward Lemuraf3328f2018-11-19 14:11:46 +00002983 for revision in options.setdep_revisions:
Edward Lesmes6f64a052018-03-20 17:35:49 -04002984 name, _, value = revision.partition('@')
2985 if not name or not value:
Edward Lesmes0ecf6d62018-04-05 18:28:55 -04002986 parser.error(
Edward Lesmes6f64a052018-03-20 17:35:49 -04002987 'Wrong dep format: %s should be of the form dep@rev.' % revision)
2988 if ':' in name:
2989 name, _, package = name.partition(':')
2990 if not name or not package:
Edward Lesmes0ecf6d62018-04-05 18:28:55 -04002991 parser.error(
Edward Lesmes6f64a052018-03-20 17:35:49 -04002992 'Wrong CIPD format: %s:%s should be of the form path:pkg@version.'
2993 % (name, package))
2994 gclient_eval.SetCIPD(local_scope, name, package, value)
2995 else:
Edward Lesmes9f531292018-03-20 21:27:15 -04002996 gclient_eval.SetRevision(local_scope, name, value)
Edward Lesmes6f64a052018-03-20 17:35:49 -04002997
John Emau7aa68242020-02-20 19:44:53 +00002998 with open(options.deps_file, 'wb') as f:
2999 f.write(gclient_eval.RenderDEPSFile(local_scope).encode('utf-8'))
Edward Lesmes6f64a052018-03-20 17:35:49 -04003000
3001
Edward Lemur3298e7b2018-07-17 18:21:27 +00003002@metrics.collector.collect_metrics('gclient verify')
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00003003def CMDverify(parser, args):
3004 """Verifies the DEPS file deps are only from allowed_hosts."""
3005 (options, args) = parser.parse_args(args)
3006 client = GClient.LoadCurrentConfig(options)
3007 if not client:
3008 raise gclient_utils.Error('client not configured; see \'gclient config\'')
3009 client.RunOnDeps(None, [])
3010 # Look at each first-level dependency of this gclient only.
3011 for dep in client.dependencies:
3012 bad_deps = dep.findDepsFromNotAllowedHosts()
3013 if not bad_deps:
3014 continue
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00003015 print("There are deps from not allowed hosts in file %s" % dep.deps_file)
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00003016 for bad_dep in bad_deps:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00003017 print("\t%s at %s" % (bad_dep.name, bad_dep.url))
3018 print("allowed_hosts:", ', '.join(dep.allowed_hosts))
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00003019 sys.stdout.flush()
3020 raise gclient_utils.Error(
3021 'dependencies from disallowed hosts; check your DEPS file.')
3022 return 0
3023
Edward Lemur32e3d1e2018-07-12 00:54:05 +00003024
3025@subcommand.epilog("""For more information on what metrics are we collecting and
Edward Lemur8a2e3312018-07-12 21:15:09 +00003026why, please read metrics.README.md or visit https://bit.ly/2ufRS4p""")
Edward Lemur3298e7b2018-07-17 18:21:27 +00003027@metrics.collector.collect_metrics('gclient metrics')
Edward Lemur32e3d1e2018-07-12 00:54:05 +00003028def CMDmetrics(parser, args):
3029 """Reports, and optionally modifies, the status of metric collection."""
3030 parser.add_option('--opt-in', action='store_true', dest='enable_metrics',
3031 help='Opt-in to metrics collection.',
3032 default=None)
3033 parser.add_option('--opt-out', action='store_false', dest='enable_metrics',
3034 help='Opt-out of metrics collection.')
3035 options, args = parser.parse_args(args)
3036 if args:
3037 parser.error('Unused arguments: "%s"' % '" "'.join(args))
3038 if not metrics.collector.config.is_googler:
3039 print("You're not a Googler. Metrics collection is disabled for you.")
3040 return 0
3041
3042 if options.enable_metrics is not None:
3043 metrics.collector.config.opted_in = options.enable_metrics
3044
3045 if metrics.collector.config.opted_in is None:
3046 print("You haven't opted in or out of metrics collection.")
3047 elif metrics.collector.config.opted_in:
3048 print("You have opted in. Thanks!")
3049 else:
3050 print("You have opted out. Please consider opting in.")
3051 return 0
3052
3053
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00003054class OptionParser(optparse.OptionParser):
szager@chromium.orge2e03202012-07-31 18:05:16 +00003055 gclientfile_default = os.environ.get('GCLIENT_FILE', '.gclient')
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00003056
3057 def __init__(self, **kwargs):
3058 optparse.OptionParser.__init__(
3059 self, version='%prog ' + __version__, **kwargs)
3060
3061 # Some arm boards have issues with parallel sync.
3062 if platform.machine().startswith('arm'):
3063 jobs = 1
3064 else:
3065 jobs = max(8, gclient_utils.NumLocalCpus())
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00003066
3067 self.add_option(
3068 '-j', '--jobs', default=jobs, type='int',
3069 help='Specify how many SCM commands can run in parallel; defaults to '
tnagel@chromium.orga2aaa632014-02-28 21:47:27 +00003070 '%default on this machine')
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00003071 self.add_option(
3072 '-v', '--verbose', action='count', default=0,
3073 help='Produces additional output for diagnostics. Can be used up to '
3074 'three times for more logging info.')
3075 self.add_option(
3076 '--gclientfile', dest='config_filename',
3077 help='Specify an alternate %s file' % self.gclientfile_default)
3078 self.add_option(
3079 '--spec',
3080 help='create a gclient file containing the provided string. Due to '
3081 'Cygwin/Python brokenness, it can\'t contain any newlines.')
3082 self.add_option(
3083 '--no-nag-max', default=False, action='store_true',
scottmg@chromium.orgf547c802013-09-27 17:55:26 +00003084 help='Ignored for backwards compatibility.')
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00003085
Edward Lemur3298e7b2018-07-17 18:21:27 +00003086 def parse_args(self, args=None, _values=None):
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00003087 """Integrates standard options processing."""
Edward Lemur3298e7b2018-07-17 18:21:27 +00003088 # Create an optparse.Values object that will store only the actual passed
3089 # options, without the defaults.
3090 actual_options = optparse.Values()
3091 _, args = optparse.OptionParser.parse_args(self, args, actual_options)
3092 # Create an optparse.Values object with the default options.
3093 options = optparse.Values(self.get_default_values().__dict__)
3094 # Update it with the options passed by the user.
3095 options._update_careful(actual_options.__dict__)
3096 # Store the options passed by the user in an _actual_options attribute.
3097 # We store only the keys, and not the values, since the values can contain
3098 # arbitrary information, which might be PII.
Edward Lemuree7b9dd2019-07-20 01:29:08 +00003099 metrics.collector.add('arguments', list(actual_options.__dict__))
Edward Lemur3298e7b2018-07-17 18:21:27 +00003100
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00003101 levels = [logging.ERROR, logging.WARNING, logging.INFO, logging.DEBUG]
3102 logging.basicConfig(
3103 level=levels[min(options.verbose, len(levels) - 1)],
maruel@chromium.org0895b752011-08-26 20:40:33 +00003104 format='%(module)s(%(lineno)d) %(funcName)s:%(message)s')
szager@chromium.orge2e03202012-07-31 18:05:16 +00003105 if options.config_filename and options.spec:
Quinten Yearsley925cedb2020-04-13 17:49:39 +00003106 self.error('Cannot specify both --gclientfile and --spec')
rdsmith@chromium.orgd9591f02014-02-05 19:28:20 +00003107 if (options.config_filename and
3108 options.config_filename != os.path.basename(options.config_filename)):
3109 self.error('--gclientfile target must be a filename, not a path')
szager@chromium.orge2e03202012-07-31 18:05:16 +00003110 if not options.config_filename:
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00003111 options.config_filename = self.gclientfile_default
maruel@chromium.org0895b752011-08-26 20:40:33 +00003112 options.entries_filename = options.config_filename + '_entries'
3113 if options.jobs < 1:
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00003114 self.error('--jobs must be 1 or higher')
maruel@chromium.org0895b752011-08-26 20:40:33 +00003115
3116 # These hacks need to die.
3117 if not hasattr(options, 'revisions'):
3118 # GClient.RunOnDeps expects it even if not applicable.
3119 options.revisions = []
smutae7ea312016-07-18 11:59:41 -07003120 if not hasattr(options, 'head'):
3121 options.head = None
maruel@chromium.org0895b752011-08-26 20:40:33 +00003122 if not hasattr(options, 'nohooks'):
3123 options.nohooks = True
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00003124 if not hasattr(options, 'noprehooks'):
3125 options.noprehooks = True
maruel@chromium.org0895b752011-08-26 20:40:33 +00003126 if not hasattr(options, 'deps_os'):
3127 options.deps_os = None
maruel@chromium.org0895b752011-08-26 20:40:33 +00003128 if not hasattr(options, 'force'):
3129 options.force = None
3130 return (options, args)
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00003131
maruel@chromium.org39c0b222013-08-17 16:57:01 +00003132
3133def disable_buffering():
3134 # Make stdout auto-flush so buildbot doesn't kill us during lengthy
3135 # operations. Python as a strong tendency to buffer sys.stdout.
3136 sys.stdout = gclient_utils.MakeFileAutoFlush(sys.stdout)
3137 # Make stdout annotated with the thread ids.
3138 sys.stdout = gclient_utils.MakeFileAnnotated(sys.stdout)
maruel@chromium.org0895b752011-08-26 20:40:33 +00003139
3140
Elly Fong-Jones7b294392019-04-18 18:32:10 +00003141def path_contains_tilde():
3142 for element in os.environ['PATH'].split(os.pathsep):
Henrique Ferreiro4ef32212019-04-29 23:32:31 +00003143 if element.startswith('~') and os.path.abspath(
3144 os.path.realpath(os.path.expanduser(element))) == DEPOT_TOOLS_DIR:
Elly Fong-Jones7b294392019-04-18 18:32:10 +00003145 return True
3146 return False
3147
3148
3149def can_run_gclient_and_helpers():
maruel@chromium.org82798cb2012-02-23 18:16:12 +00003150 if sys.hexversion < 0x02060000:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00003151 print(
maruel@chromium.org82798cb2012-02-23 18:16:12 +00003152 '\nYour python version %s is unsupported, please upgrade.\n' %
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00003153 sys.version.split(' ', 1)[0],
3154 file=sys.stderr)
Elly Fong-Jones7b294392019-04-18 18:32:10 +00003155 return False
bcwhite@chromium.org6683ab42013-02-11 16:13:47 +00003156 if not sys.executable:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00003157 print(
3158 '\nPython cannot find the location of it\'s own executable.\n',
3159 file=sys.stderr)
Elly Fong-Jones7b294392019-04-18 18:32:10 +00003160 return False
3161 if path_contains_tilde():
3162 print(
3163 '\nYour PATH contains a literal "~", which works in some shells ' +
3164 'but will break when python tries to run subprocesses. ' +
3165 'Replace the "~" with $HOME.\n' +
3166 'See https://crbug.com/952865.\n',
3167 file=sys.stderr)
3168 return False
3169 return True
3170
3171
3172def main(argv):
3173 """Doesn't parse the arguments here, just find the right subcommand to
3174 execute."""
3175 if not can_run_gclient_and_helpers():
bcwhite@chromium.org6683ab42013-02-11 16:13:47 +00003176 return 2
maruel@chromium.org39c0b222013-08-17 16:57:01 +00003177 fix_encoding.fix_encoding()
3178 disable_buffering()
iannucci@chromium.org596cd5c2016-04-04 21:34:39 +00003179 setup_color.init()
maruel@chromium.org39c0b222013-08-17 16:57:01 +00003180 dispatcher = subcommand.CommandDispatcher(__name__)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00003181 try:
maruel@chromium.org39c0b222013-08-17 16:57:01 +00003182 return dispatcher.execute(OptionParser(), argv)
xusydoc@chromium.org2fd6c3f2013-05-03 21:57:55 +00003183 except KeyboardInterrupt:
3184 gclient_utils.GClientChildren.KillAllRemainingChildren()
3185 raise
vapier@chromium.orga81a56e2015-11-11 07:56:13 +00003186 except (gclient_utils.Error, subprocess2.CalledProcessError) as e:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00003187 print('Error: %s' % str(e), file=sys.stderr)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00003188 return 1
borenet@google.com6a9b1682014-03-24 18:35:23 +00003189 finally:
3190 gclient_utils.PrintWarnings()
sbc@chromium.org013731e2015-02-26 18:28:43 +00003191 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00003192
3193
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00003194if '__main__' == __name__:
Edward Lemur6f812e12018-07-31 22:45:57 +00003195 with metrics.collector.print_notice_and_exit():
sbc@chromium.org013731e2015-02-26 18:28:43 +00003196 sys.exit(main(sys.argv[1:]))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00003197
3198# vim: ts=2:sw=2:tw=80:et: