blob: 57dfff6164f62bcb8a16075b22694d8eb3fded82 [file] [log] [blame]
Josip Sokceviced6aa2b2022-01-26 18:14:05 +00001#!/usr/bin/env python3
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
Joanna Wang66286612022-06-30 19:59:13 +0000134PREVIOUS_CUSTOM_VARS = 'GCLIENT_PREVIOUS_CUSTOM_VARS'
135
136
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +0200137class GNException(Exception):
138 pass
139
140
141def ToGNString(value, allow_dicts = True):
142 """Returns a stringified GN equivalent of the Python value.
143
144 allow_dicts indicates if this function will allow converting dictionaries
145 to GN scopes. This is only possible at the top level, you can't nest a
146 GN scope in a list, so this should be set to False for recursive calls."""
Aaron Gableac9b0f32019-04-18 17:38:37 +0000147 if isinstance(value, basestring):
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +0200148 if value.find('\n') >= 0:
149 raise GNException("Trying to print a string with a newline in it.")
150 return '"' + \
151 value.replace('\\', '\\\\').replace('"', '\\"').replace('$', '\\$') + \
152 '"'
153
Raul Tambreb946b232019-03-26 14:48:46 +0000154 if sys.version_info.major == 2 and isinstance(value, unicode):
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +0200155 return ToGNString(value.encode('utf-8'))
156
157 if isinstance(value, bool):
158 if value:
159 return "true"
160 return "false"
161
162 # NOTE: some type handling removed compared to chromium/src copy.
163
164 raise GNException("Unsupported type when printing to GN.")
165
166
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200167class Hook(object):
168 """Descriptor of command ran before/after sync or on demand."""
169
Paweł Hajdan, Jr032d5452017-06-22 20:43:53 +0200170 def __init__(self, action, pattern=None, name=None, cwd=None, condition=None,
Corentin Walleza68660d2018-09-10 17:33:24 +0000171 variables=None, verbose=False, cwd_base=None):
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200172 """Constructor.
173
174 Arguments:
175 action (list of basestring): argv of the command to run
176 pattern (basestring regex): noop with git; deprecated
177 name (basestring): optional name; no effect on operation
Paweł Hajdan, Jrc9364392017-06-14 17:11:56 +0200178 cwd (basestring): working directory to use
Paweł Hajdan, Jr032d5452017-06-22 20:43:53 +0200179 condition (basestring): condition when to run the hook
180 variables (dict): variables for evaluating the condition
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200181 """
182 self._action = gclient_utils.freeze(action)
183 self._pattern = pattern
184 self._name = name
Paweł Hajdan, Jrc9364392017-06-14 17:11:56 +0200185 self._cwd = cwd
Paweł Hajdan, Jr032d5452017-06-22 20:43:53 +0200186 self._condition = condition
187 self._variables = variables
Daniel Chenga0c5f082017-10-19 13:35:19 -0700188 self._verbose = verbose
Corentin Walleza68660d2018-09-10 17:33:24 +0000189 self._cwd_base = cwd_base
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200190
191 @staticmethod
Corentin Walleza68660d2018-09-10 17:33:24 +0000192 def from_dict(d, variables=None, verbose=False, conditions=None,
193 cwd_base=None):
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200194 """Creates a Hook instance from a dict like in the DEPS file."""
Michael Moss42d02c22018-02-05 10:32:24 -0800195 # Merge any local and inherited conditions.
Edward Lemur16f4bad2018-05-16 16:53:49 -0400196 gclient_eval.UpdateCondition(d, 'and', conditions)
Paweł Hajdan, Jr032d5452017-06-22 20:43:53 +0200197 return Hook(
198 d['action'],
199 d.get('pattern'),
200 d.get('name'),
201 d.get('cwd'),
Edward Lemur16f4bad2018-05-16 16:53:49 -0400202 d.get('condition'),
Daniel Chenga0c5f082017-10-19 13:35:19 -0700203 variables=variables,
204 # Always print the header if not printing to a TTY.
Corentin Walleza68660d2018-09-10 17:33:24 +0000205 verbose=verbose or not setup_color.IS_TTY,
206 cwd_base=cwd_base)
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200207
208 @property
209 def action(self):
210 return self._action
211
212 @property
213 def pattern(self):
214 return self._pattern
215
216 @property
217 def name(self):
218 return self._name
219
Paweł Hajdan, Jrecf53fe2017-09-29 18:28:49 +0200220 @property
221 def condition(self):
222 return self._condition
223
Corentin Walleza68660d2018-09-10 17:33:24 +0000224 @property
225 def effective_cwd(self):
226 cwd = self._cwd_base
227 if self._cwd:
228 cwd = os.path.join(cwd, self._cwd)
229 return cwd
230
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200231 def matches(self, file_list):
232 """Returns true if the pattern matches any of files in the list."""
233 if not self._pattern:
234 return True
235 pattern = re.compile(self._pattern)
236 return bool([f for f in file_list if pattern.search(f)])
237
Corentin Walleza68660d2018-09-10 17:33:24 +0000238 def run(self):
Paweł Hajdan, Jr032d5452017-06-22 20:43:53 +0200239 """Executes the hook's command (provided the condition is met)."""
240 if (self._condition and
241 not gclient_eval.EvaluateCondition(self._condition, self._variables)):
242 return
243
Aravind Vasudevanc5f0cbb2022-01-24 23:56:57 +0000244 cmd = list(self._action)
Paweł Hajdan, Jrfc6196b2017-07-27 13:15:25 +0200245
Edward Lemurca879322019-09-09 20:18:13 +0000246 if cmd[0] == 'python':
Edward Lemur6f18e682019-09-23 21:02:35 +0000247 cmd[0] = 'vpython'
Darshan Sen1b4881c2022-03-09 17:42:49 +0000248 if (cmd[0] in ['vpython', 'vpython3']) and _detect_host_os() == 'win':
Nodir Turakulov0ffcc872017-11-09 16:44:58 -0800249 cmd[0] += '.bat'
Paweł Hajdan, Jrc9364392017-06-14 17:11:56 +0200250
Edward Lesmes58542b72021-06-10 20:50:37 +0000251 exit_code = 2
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200252 try:
253 start_time = time.time()
Edward Lemur24146be2019-08-01 21:44:52 +0000254 gclient_utils.CheckCallAndFilter(
255 cmd, cwd=self.effective_cwd, print_stdout=True, show_header=True,
256 always_show_header=self._verbose)
Edward Lesmes58542b72021-06-10 20:50:37 +0000257 exit_code = 0
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200258 except (gclient_utils.Error, subprocess2.CalledProcessError) as e:
259 # Use a discrete exit status code of 2 to indicate that a hook action
260 # failed. Users of this script may wish to treat hook action failures
261 # differently from VC failures.
262 print('Error: %s' % str(e), file=sys.stderr)
Edward Lesmes58542b72021-06-10 20:50:37 +0000263 sys.exit(exit_code)
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200264 finally:
265 elapsed_time = time.time() - start_time
Edward Lesmes58542b72021-06-10 20:50:37 +0000266 metrics.collector.add_repeated('hooks', {
267 'action': gclient_utils.CommandToStr(cmd),
268 'name': self._name,
269 'cwd': os.path.relpath(
270 os.path.normpath(self.effective_cwd),
271 self._cwd_base),
272 'condition': self._condition,
273 'execution_time': elapsed_time,
274 'exit_code': exit_code,
275 })
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200276 if elapsed_time > 10:
277 print("Hook '%s' took %.2f secs" % (
278 gclient_utils.CommandToStr(cmd), elapsed_time))
279
280
Paweł Hajdan, Jrfc6196b2017-07-27 13:15:25 +0200281class DependencySettings(object):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000282 """Immutable configuration settings."""
283 def __init__(
Edward Lemure05f18d2018-06-08 17:36:53 +0000284 self, parent, url, managed, custom_deps, custom_vars,
Michael Mossd683d7c2018-06-15 05:05:17 +0000285 custom_hooks, deps_file, should_process, relative, condition):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000286 # These are not mutable:
287 self._parent = parent
mmoss@chromium.org8f93f792014-08-26 23:24:09 +0000288 self._deps_file = deps_file
maruel@chromium.org064186c2011-09-27 23:53:33 +0000289 self._url = url
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +0200290 # The condition as string (or None). Useful to keep e.g. for flatten.
291 self._condition = condition
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000292 # 'managed' determines whether or not this dependency is synced/updated by
Michael Mossd683d7c2018-06-15 05:05:17 +0000293 # gclient after gclient checks it out initially. The difference between
294 # 'managed' and 'should_process' is that the user specifies 'managed' via
295 # the --unmanaged command-line flag or a .gclient config, where
296 # 'should_process' is dynamically set by gclient if it goes over its
297 # recursion limit and controls gclient's behavior so it does not misbehave.
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000298 self._managed = managed
Michael Mossd683d7c2018-06-15 05:05:17 +0000299 self._should_process = should_process
agabledce6ddc2016-09-08 10:02:16 -0700300 # If this is a recursed-upon sub-dependency, and the parent has
301 # use_relative_paths set, then this dependency should check out its own
302 # dependencies relative to that parent's path for this, rather than
303 # relative to the .gclient file.
304 self._relative = relative
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +0000305 # This is a mutable value which has the list of 'target_os' OSes listed in
306 # the current deps file.
307 self.local_target_os = None
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000308
309 # These are only set in .gclient and not in DEPS files.
310 self._custom_vars = custom_vars or {}
311 self._custom_deps = custom_deps or {}
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000312 self._custom_hooks = custom_hooks or []
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000313
Michael Mossd683d7c2018-06-15 05:05:17 +0000314 # Post process the url to remove trailing slashes.
Aaron Gableac9b0f32019-04-18 17:38:37 +0000315 if isinstance(self.url, basestring):
Michael Moss4e9b50a2018-05-23 22:35:06 -0700316 # urls are sometime incorrectly written as proto://host/path/@rev. Replace
317 # it to proto://host/path@rev.
318 self.set_url(self.url.replace('/@', '@'))
Michael Mossd683d7c2018-06-15 05:05:17 +0000319 elif not isinstance(self.url, (None.__class__)):
320 raise gclient_utils.Error(
321 ('dependency url must be either string or None, '
322 'instead of %s') % self.url.__class__.__name__)
Edward Lemure7273d22018-05-10 19:13:51 -0400323
mmoss@chromium.orgd0b272b2013-01-30 23:55:33 +0000324 # Make any deps_file path platform-appropriate.
John Budorick0f7b2002018-01-19 15:46:17 -0800325 if self._deps_file:
326 for sep in ['/', '\\']:
327 self._deps_file = self._deps_file.replace(sep, os.sep)
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000328
329 @property
330 def deps_file(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000331 return self._deps_file
332
333 @property
334 def managed(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000335 return self._managed
336
337 @property
338 def parent(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000339 return self._parent
340
341 @property
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000342 def root(self):
343 """Returns the root node, a GClient object."""
344 if not self.parent:
345 # This line is to signal pylint that it could be a GClient instance.
346 return self or GClient(None, None)
347 return self.parent.root
348
349 @property
Michael Mossd683d7c2018-06-15 05:05:17 +0000350 def should_process(self):
351 """True if this dependency should be processed, i.e. checked out."""
352 return self._should_process
353
354 @property
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000355 def custom_vars(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000356 return self._custom_vars.copy()
357
358 @property
359 def custom_deps(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000360 return self._custom_deps.copy()
361
maruel@chromium.org064186c2011-09-27 23:53:33 +0000362 @property
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000363 def custom_hooks(self):
364 return self._custom_hooks[:]
365
366 @property
maruel@chromium.org064186c2011-09-27 23:53:33 +0000367 def url(self):
Paweł Hajdan, Jrfc6196b2017-07-27 13:15:25 +0200368 """URL after variable expansion."""
maruel@chromium.org064186c2011-09-27 23:53:33 +0000369 return self._url
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000370
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000371 @property
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +0200372 def condition(self):
373 return self._condition
374
375 @property
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +0000376 def target_os(self):
377 if self.local_target_os is not None:
378 return tuple(set(self.local_target_os).union(self.parent.target_os))
Aravind Vasudevanc5f0cbb2022-01-24 23:56:57 +0000379
380 return self.parent.target_os
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +0000381
Tom Andersonc31ae0b2018-02-06 14:48:56 -0800382 @property
383 def target_cpu(self):
384 return self.parent.target_cpu
385
Edward Lemure7273d22018-05-10 19:13:51 -0400386 def set_url(self, url):
387 self._url = url
388
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000389 def get_custom_deps(self, name, url):
390 """Returns a custom deps if applicable."""
391 if self.parent:
392 url = self.parent.get_custom_deps(name, url)
393 # None is a valid return value to disable a dependency.
394 return self.custom_deps.get(name, url)
395
maruel@chromium.org064186c2011-09-27 23:53:33 +0000396
397class Dependency(gclient_utils.WorkItem, DependencySettings):
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000398 """Object that represents a dependency checkout."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000399
Edward Lemure05f18d2018-06-08 17:36:53 +0000400 def __init__(self, parent, name, url, managed, custom_deps,
Michael Mossd683d7c2018-06-15 05:05:17 +0000401 custom_vars, custom_hooks, deps_file, should_process,
Aravind Vasudevan5965d3e2022-06-01 21:51:30 +0000402 should_recurse, relative, condition, protocol='https',
403 print_outbuf=False):
maruel@chromium.org6ca8bf82011-09-19 23:04:30 +0000404 gclient_utils.WorkItem.__init__(self, name)
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000405 DependencySettings.__init__(
Michael Mossd683d7c2018-06-15 05:05:17 +0000406 self, parent, url, managed, custom_deps, custom_vars,
407 custom_hooks, deps_file, should_process, relative, condition)
maruel@chromium.org68988972011-09-20 14:11:42 +0000408
409 # This is in both .gclient and DEPS files:
maruel@chromium.org064186c2011-09-27 23:53:33 +0000410 self._deps_hooks = []
maruel@chromium.org68988972011-09-20 14:11:42 +0000411
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000412 self._pre_deps_hooks = []
413
maruel@chromium.org68988972011-09-20 14:11:42 +0000414 # Calculates properties:
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +0000415 self._dependencies = []
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +0200416 self._vars = {}
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200417
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000418 # A cache of the files affected by the current operation, necessary for
419 # hooks.
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000420 self._file_list = []
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000421 # List of host names from which dependencies are allowed.
422 # Default is an empty set, meaning unspecified in DEPS file, and hence all
Josip Sokcevic9c0dc302020-11-20 18:41:25 +0000423 # hosts will be allowed. Non-empty set means allowlist of hosts.
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000424 # allowed_hosts var is scoped to its DEPS file, and so it isn't recursive.
425 self._allowed_hosts = frozenset()
Michael Moss848c86e2018-05-03 16:05:50 -0700426 self._gn_args_from = None
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +0200427 # Spec for .gni output to write (if any).
428 self._gn_args_file = None
429 self._gn_args = []
maruel@chromium.org85c2a192010-07-22 21:14:43 +0000430 # If it is not set to True, the dependency wasn't processed for its child
431 # dependency, i.e. its DEPS wasn't read.
maruel@chromium.org064186c2011-09-27 23:53:33 +0000432 self._deps_parsed = False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000433 # This dependency has been processed, i.e. checked out
maruel@chromium.org064186c2011-09-27 23:53:33 +0000434 self._processed = False
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000435 # This dependency had its pre-DEPS hooks run
436 self._pre_deps_hooks_ran = False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000437 # This dependency had its hook run
maruel@chromium.org064186c2011-09-27 23:53:33 +0000438 self._hooks_ran = False
kustermann@google.coma692e8f2013-04-18 08:32:04 +0000439 # This is the scm used to checkout self.url. It may be used by dependencies
440 # to get the datetime of the revision we checked out.
441 self._used_scm = None
szager@chromium.org4ad264b2014-05-20 04:43:47 +0000442 self._used_revision = None
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +0000443 # The actual revision we ended up getting, or None if that information is
444 # unavailable
445 self._got_revision = None
Corentin Wallez271a78a2020-07-12 15:41:46 +0000446 # Whether this dependency should use relative paths.
447 self._use_relative_paths = False
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000448
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000449 # recursedeps is a mutable value that selectively overrides the default
Edward Lemurfbb06aa2018-06-11 20:43:06 +0000450 # 'no recursion' setting on a dep-by-dep basis.
iannucci@chromium.orgafa11ac2016-05-04 22:17:34 +0000451 #
Edward Lemurfbb06aa2018-06-11 20:43:06 +0000452 # It will be a dictionary of {deps_name: depfile_namee}
453 self.recursedeps = {}
454
455 # Whether we should process this dependency's DEPS file.
456 self._should_recurse = should_recurse
Edward Lemure7273d22018-05-10 19:13:51 -0400457
Joanna Wang18af7ef2022-07-01 16:51:00 +0000458 # Whether we should sync git/cipd dependencies and hooks from the
459 # DEPS file.
460 # This is set based on options.skip_sync_revisions and must be done
461 # after the patch refs are applied.
462 # If this is False, we will still run custom_hooks and process
463 # custom_deps, if any.
464 self._should_sync = True
465
Michael Mossd683d7c2018-06-15 05:05:17 +0000466 self._OverrideUrl()
467 # This is inherited from WorkItem. We want the URL to be a resource.
Aaron Gableac9b0f32019-04-18 17:38:37 +0000468 if self.url and isinstance(self.url, basestring):
Michael Moss4e9b50a2018-05-23 22:35:06 -0700469 # The url is usually given to gclient either as https://blah@123
470 # or just https://blah. The @123 portion is irrelevant.
471 self.resources.append(self.url.split('@')[0])
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000472
Edward Lemur231f5ea2018-01-31 19:02:36 +0100473 # Controls whether we want to print git's output when we first clone the
474 # dependency
475 self.print_outbuf = print_outbuf
476
Aravind Vasudevan5965d3e2022-06-01 21:51:30 +0000477 self.protocol = protocol
478
Michael Mossd683d7c2018-06-15 05:05:17 +0000479 if not self.name and self.parent:
480 raise gclient_utils.Error('Dependency without name')
481
482 def _OverrideUrl(self):
483 """Resolves the parsed url from the parent hierarchy."""
Aravind Vasudevanaae67252022-01-05 00:41:38 +0000484 parsed_url = self.get_custom_deps(
485 self._name.replace(os.sep, posixpath.sep) \
486 if self._name else self._name, self.url)
Michael Mossd683d7c2018-06-15 05:05:17 +0000487 if parsed_url != self.url:
488 logging.info('Dependency(%s)._OverrideUrl(%s) -> %s', self._name,
489 self.url, parsed_url)
490 self.set_url(parsed_url)
Edward Lemur1f392b82019-11-15 22:40:11 +0000491 return
Michael Mossd683d7c2018-06-15 05:05:17 +0000492
Edward Lemur1f392b82019-11-15 22:40:11 +0000493 if self.url is None:
Michael Mossd683d7c2018-06-15 05:05:17 +0000494 logging.info('Dependency(%s)._OverrideUrl(None) -> None', self._name)
Edward Lemur1f392b82019-11-15 22:40:11 +0000495 return
Michael Mossd683d7c2018-06-15 05:05:17 +0000496
Edward Lemur1f392b82019-11-15 22:40:11 +0000497 if not isinstance(self.url, basestring):
Michael Mossd683d7c2018-06-15 05:05:17 +0000498 raise gclient_utils.Error('Unknown url type')
499
Edward Lemur1f392b82019-11-15 22:40:11 +0000500 # self.url is a local path
501 path, at, rev = self.url.partition('@')
502 if os.path.isdir(path):
503 return
504
505 # self.url is a URL
506 parsed_url = urlparse.urlparse(self.url)
507 if parsed_url[0] or re.match(r'^\w+\@[\w\.-]+\:[\w\/]+', parsed_url[2]):
508 return
509
510 # self.url is relative to the parent's URL.
511 if not path.startswith('/'):
512 raise gclient_utils.Error(
513 'relative DEPS entry \'%s\' must begin with a slash' % self.url)
514
515 parent_url = self.parent.url
516 parent_path = self.parent.url.split('@')[0]
517 if os.path.isdir(parent_path):
518 # Parent's URL is a local path. Get parent's URL dirname and append
519 # self.url.
520 parent_path = os.path.dirname(parent_path)
521 parsed_url = parent_path + path.replace('/', os.sep) + at + rev
522 else:
523 # Parent's URL is a URL. Get parent's URL, strip from the last '/'
524 # (equivalent to unix dirname) and append self.url.
525 parsed_url = parent_url[:parent_url.rfind('/')] + self.url
526
527 logging.info('Dependency(%s)._OverrideUrl(%s) -> %s', self.name,
528 self.url, parsed_url)
529 self.set_url(parsed_url)
530
Edward Lemure7273d22018-05-10 19:13:51 -0400531 def PinToActualRevision(self):
Edward Lemure05f18d2018-06-08 17:36:53 +0000532 """Updates self.url to the revision checked out on disk."""
Michael Mossd683d7c2018-06-15 05:05:17 +0000533 if self.url is None:
534 return
Edward Lemure05f18d2018-06-08 17:36:53 +0000535 url = None
Edward Lemurbabd0982018-05-11 13:32:37 -0400536 scm = self.CreateSCM()
Edward Lemure7273d22018-05-10 19:13:51 -0400537 if os.path.isdir(scm.checkout_path):
538 revision = scm.revinfo(None, None, None)
539 url = '%s@%s' % (gclient_utils.SplitUrlRevision(self.url)[0], revision)
Edward Lemure7273d22018-05-10 19:13:51 -0400540 self.set_url(url)
Edward Lemure7273d22018-05-10 19:13:51 -0400541
John Budorick0f7b2002018-01-19 15:46:17 -0800542 def ToLines(self):
543 s = []
544 condition_part = ([' "condition": %r,' % self.condition]
545 if self.condition else [])
546 s.extend([
Michael Moss4e9b50a2018-05-23 22:35:06 -0700547 ' # %s' % self.hierarchy(include_url=False),
John Budorick0f7b2002018-01-19 15:46:17 -0800548 ' "%s": {' % (self.name,),
Edward Lemure05f18d2018-06-08 17:36:53 +0000549 ' "url": "%s",' % (self.url,),
John Budorick0f7b2002018-01-19 15:46:17 -0800550 ] + condition_part + [
551 ' },',
552 '',
553 ])
554 return s
555
maruel@chromium.org470b5432011-10-11 18:18:19 +0000556 @property
557 def requirements(self):
558 """Calculate the list of requirements."""
559 requirements = set()
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000560 # self.parent is implicitly a requirement. This will be recursive by
561 # definition.
562 if self.parent and self.parent.name:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000563 requirements.add(self.parent.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000564
565 # For a tree with at least 2 levels*, the leaf node needs to depend
566 # on the level higher up in an orderly way.
567 # This becomes messy for >2 depth as the DEPS file format is a dictionary,
568 # thus unsorted, while the .gclient format is a list thus sorted.
569 #
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000570 # Interestingly enough, the following condition only works in the case we
Quinten Yearsley925cedb2020-04-13 17:49:39 +0000571 # want: self is a 2nd level node. 3rd level node wouldn't need this since
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000572 # they already have their parent as a requirement.
maruel@chromium.org470b5432011-10-11 18:18:19 +0000573 if self.parent and self.parent.parent and not self.parent.parent.parent:
574 requirements |= set(i.name for i in self.root.dependencies if i.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000575
maruel@chromium.org470b5432011-10-11 18:18:19 +0000576 if self.name:
577 requirements |= set(
Michael Mossd683d7c2018-06-15 05:05:17 +0000578 obj.name for obj in self.root.subtree(False)
maruel@chromium.org470b5432011-10-11 18:18:19 +0000579 if (obj is not self
580 and obj.name and
581 self.name.startswith(posixpath.join(obj.name, ''))))
582 requirements = tuple(sorted(requirements))
583 logging.info('Dependency(%s).requirements = %s' % (self.name, requirements))
584 return requirements
585
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000586 @property
Edward Lemurfbb06aa2018-06-11 20:43:06 +0000587 def should_recurse(self):
588 return self._should_recurse
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000589
maruel@chromium.org470b5432011-10-11 18:18:19 +0000590 def verify_validity(self):
591 """Verifies that this Dependency is fine to add as a child of another one.
592
593 Returns True if this entry should be added, False if it is a duplicate of
594 another entry.
595 """
596 logging.info('Dependency(%s).verify_validity()' % self.name)
597 if self.name in [s.name for s in self.parent.dependencies]:
598 raise gclient_utils.Error(
599 'The same name "%s" appears multiple times in the deps section' %
600 self.name)
Michael Mossd683d7c2018-06-15 05:05:17 +0000601 if not self.should_process:
602 # Return early, no need to set requirements.
Edward Lemur7ccf2f02018-06-26 20:41:56 +0000603 return not any(d.name == self.name for d in self.root.subtree(True))
maruel@chromium.org470b5432011-10-11 18:18:19 +0000604
605 # This require a full tree traversal with locks.
Michael Mossd683d7c2018-06-15 05:05:17 +0000606 siblings = [d for d in self.root.subtree(False) if d.name == self.name]
maruel@chromium.org470b5432011-10-11 18:18:19 +0000607 for sibling in siblings:
Michael Mossd683d7c2018-06-15 05:05:17 +0000608 # Allow to have only one to be None or ''.
609 if self.url != sibling.url and bool(self.url) == bool(sibling.url):
maruel@chromium.org470b5432011-10-11 18:18:19 +0000610 raise gclient_utils.Error(
maruel@chromium.orgb848d5b2012-10-10 23:25:50 +0000611 ('Dependency %s specified more than once:\n'
612 ' %s [%s]\n'
613 'vs\n'
614 ' %s [%s]') % (
615 self.name,
616 sibling.hierarchy(),
Edward Lemure7273d22018-05-10 19:13:51 -0400617 sibling.url,
maruel@chromium.orgb848d5b2012-10-10 23:25:50 +0000618 self.hierarchy(),
Edward Lemure7273d22018-05-10 19:13:51 -0400619 self.url))
maruel@chromium.org470b5432011-10-11 18:18:19 +0000620 # In theory we could keep it as a shadow of the other one. In
621 # practice, simply ignore it.
John Budorickd94f8ea2020-03-27 15:55:24 +0000622 logging.warning("Won't process duplicate dependency %s" % sibling)
maruel@chromium.org470b5432011-10-11 18:18:19 +0000623 return False
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000624 return True
maruel@chromium.org064186c2011-09-27 23:53:33 +0000625
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200626 def _postprocess_deps(self, deps, rel_prefix):
Joanna Wang18af7ef2022-07-01 16:51:00 +0000627 # type: (Mapping[str, Mapping[str, str]], str) ->
628 # Mapping[str, Mapping[str, str]]
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200629 """Performs post-processing of deps compared to what's in the DEPS file."""
Joanna Wang18af7ef2022-07-01 16:51:00 +0000630 # If we don't need to sync, only process custom_deps, if any.
631 if not self._should_sync:
632 if not self.custom_deps:
633 return {}
Paweł Hajdan, Jr4426eaf2017-06-13 12:25:47 +0200634
Joanna Wang18af7ef2022-07-01 16:51:00 +0000635 processed_deps = {}
636 for dep_name, dep_info in self.custom_deps.items():
637 if dep_info and not dep_info.endswith('@unmanaged'):
638 if dep_name in deps:
639 # custom_deps that should override an existing deps gets applied
640 # in the Dependency itself with _OverrideUrl().
641 processed_deps[dep_name] = deps[dep_name]
642 else:
643 processed_deps[dep_name] = {'url': dep_info, 'dep_type': 'git'}
644 else:
645 processed_deps = dict(deps)
646
647 # If a line is in custom_deps, but not in the solution, we want to append
648 # this line to the solution.
649 for dep_name, dep_info in self.custom_deps.items():
Andrew Grievee8f9bdc2022-02-09 21:05:12 +0000650 # Don't add it to the solution for the values of "None" and "unmanaged"
651 # in order to force these kinds of custom_deps to act as revision
652 # overrides (via revision_overrides). Having them function as revision
653 # overrides allows them to be applied to recursive dependencies.
654 # https://crbug.com/1031185
Joanna Wang18af7ef2022-07-01 16:51:00 +0000655 if (dep_name not in processed_deps and dep_info
656 and not dep_info.endswith('@unmanaged')):
657 processed_deps[dep_name] = {'url': dep_info, 'dep_type': 'git'}
Edward Lemur16f4bad2018-05-16 16:53:49 -0400658
Michael Moss42d02c22018-02-05 10:32:24 -0800659 # Make child deps conditional on any parent conditions. This ensures that,
660 # when flattened, recursed entries have the correct restrictions, even if
661 # not explicitly set in the recursed DEPS file. For instance, if
662 # "src/ios_foo" is conditional on "checkout_ios=True", then anything
663 # recursively included by "src/ios_foo/DEPS" should also require
664 # "checkout_ios=True".
665 if self.condition:
Joanna Wang18af7ef2022-07-01 16:51:00 +0000666 for value in processed_deps.values():
Edward Lemur16f4bad2018-05-16 16:53:49 -0400667 gclient_eval.UpdateCondition(value, 'and', self.condition)
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200668
Joanna Wang18af7ef2022-07-01 16:51:00 +0000669 if not rel_prefix:
670 return processed_deps
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200671
Joanna Wang18af7ef2022-07-01 16:51:00 +0000672 logging.warning('use_relative_paths enabled.')
673 rel_deps = {}
674 for d, url in processed_deps.items():
675 # normpath is required to allow DEPS to use .. in their
676 # dependency local path.
677 rel_deps[os.path.normpath(os.path.join(rel_prefix, d))] = url
678 logging.warning('Updating deps by prepending %s.', rel_prefix)
679 return rel_deps
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200680
681 def _deps_to_objects(self, deps, use_relative_paths):
Joanna Wang18af7ef2022-07-01 16:51:00 +0000682 # type: (Mapping[str, Mapping[str, str]], bool) -> Sequence[Dependency]
683 """Convert a deps dict to a list of Dependency objects."""
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200684 deps_to_add = []
Josip Sokcevic9cbe9a02021-12-01 17:25:16 +0000685 cached_conditions = {}
Marc-Antoine Ruel8e57b4b2019-10-11 01:01:36 +0000686 for name, dep_value in deps.items():
Michael Mossd683d7c2018-06-15 05:05:17 +0000687 should_process = self.should_process
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200688 if dep_value is None:
689 continue
John Budorick0f7b2002018-01-19 15:46:17 -0800690
Edward Lemur16f4bad2018-05-16 16:53:49 -0400691 condition = dep_value.get('condition')
Michael Mossd683d7c2018-06-15 05:05:17 +0000692 dep_type = dep_value.get('dep_type')
Paweł Hajdan, Jrfc6196b2017-07-27 13:15:25 +0200693
Josip Sokcevic9cbe9a02021-12-01 17:25:16 +0000694
Michael Mossd683d7c2018-06-15 05:05:17 +0000695 if condition and not self._get_option('process_all_deps', False):
Josip Sokcevic9cbe9a02021-12-01 17:25:16 +0000696 if condition not in cached_conditions:
697 cached_conditions[condition] = gclient_eval.EvaluateCondition(
698 condition, self.get_vars())
699 should_process = should_process and cached_conditions[condition]
John Budorick0f7b2002018-01-19 15:46:17 -0800700
Joey Scarr8d3925b2018-07-15 23:36:25 +0000701 # The following option is only set by the 'revinfo' command.
702 if self._get_option('ignore_dep_type', None) == dep_type:
703 continue
704
John Budorick0f7b2002018-01-19 15:46:17 -0800705 if dep_type == 'cipd':
John Budorickd3ba72b2018-03-20 12:27:42 -0700706 cipd_root = self.GetCipdRoot()
John Budorick0f7b2002018-01-19 15:46:17 -0800707 for package in dep_value.get('packages', []):
708 deps_to_add.append(
709 CipdDependency(
Edward Lemure05f18d2018-06-08 17:36:53 +0000710 parent=self,
711 name=name,
712 dep_value=package,
713 cipd_root=cipd_root,
714 custom_vars=self.custom_vars,
Michael Mossd683d7c2018-06-15 05:05:17 +0000715 should_process=should_process,
Edward Lemure05f18d2018-06-08 17:36:53 +0000716 relative=use_relative_paths,
717 condition=condition))
John Budorick0f7b2002018-01-19 15:46:17 -0800718 else:
Michael Mossd683d7c2018-06-15 05:05:17 +0000719 url = dep_value.get('url')
720 deps_to_add.append(
721 GitDependency(
722 parent=self,
723 name=name,
Aravind Vasudevan810598d2022-06-13 21:23:47 +0000724 # Update URL with scheme in protocol_override
Aravind Vasudevan5965d3e2022-06-01 21:51:30 +0000725 url=GitDependency.updateProtocol(url, self.protocol),
Edward Lemure4213702018-06-21 21:15:50 +0000726 managed=True,
Michael Mossd683d7c2018-06-15 05:05:17 +0000727 custom_deps=None,
728 custom_vars=self.custom_vars,
729 custom_hooks=None,
730 deps_file=self.recursedeps.get(name, self.deps_file),
731 should_process=should_process,
732 should_recurse=name in self.recursedeps,
733 relative=use_relative_paths,
Aravind Vasudevan5965d3e2022-06-01 21:51:30 +0000734 condition=condition,
735 protocol=self.protocol))
John Budorick0f7b2002018-01-19 15:46:17 -0800736
Joanna Wang18af7ef2022-07-01 16:51:00 +0000737 # TODO(crbug.com/1341285): Understand why we need this and remove
738 # it if we don't.
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200739 deps_to_add.sort(key=lambda x: x.name)
740 return deps_to_add
741
Edward Lemure05f18d2018-06-08 17:36:53 +0000742 def ParseDepsFile(self):
Joanna Wang18af7ef2022-07-01 16:51:00 +0000743 # type: () -> None
maruel@chromium.org271375b2010-06-23 19:17:38 +0000744 """Parses the DEPS file for this dependency."""
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000745 assert not self.deps_parsed
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000746 assert not self.dependencies
iannucci@chromium.org0c3f3052014-05-22 00:29:22 +0000747
748 deps_content = None
cmp@chromium.org76ce73c2014-07-02 00:13:18 +0000749
750 # First try to locate the configured deps file. If it's missing, fallback
751 # to DEPS.
752 deps_files = [self.deps_file]
753 if 'DEPS' not in deps_files:
754 deps_files.append('DEPS')
755 for deps_file in deps_files:
756 filepath = os.path.join(self.root.root_dir, self.name, deps_file)
757 if os.path.isfile(filepath):
758 logging.info(
759 'ParseDepsFile(%s): %s file found at %s', self.name, deps_file,
760 filepath)
761 break
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000762 logging.info(
cmp@chromium.org76ce73c2014-07-02 00:13:18 +0000763 'ParseDepsFile(%s): No %s file found at %s', self.name, deps_file,
764 filepath)
765
766 if os.path.isfile(filepath):
maruel@chromium.org46304292010-10-28 11:42:00 +0000767 deps_content = gclient_utils.FileRead(filepath)
cmp@chromium.org76ce73c2014-07-02 00:13:18 +0000768 logging.debug('ParseDepsFile(%s) read:\n%s', self.name, deps_content)
iannucci@chromium.org0c3f3052014-05-22 00:29:22 +0000769
770 local_scope = {}
771 if deps_content:
maruel@chromium.org46304292010-10-28 11:42:00 +0000772 try:
Edward Lesmes6c24d372018-03-28 12:52:29 -0400773 local_scope = gclient_eval.Parse(
Edward Lemur67cabcd2020-03-03 19:31:15 +0000774 deps_content, filepath, self.get_vars(), self.get_builtin_vars())
vapier@chromium.orga81a56e2015-11-11 07:56:13 +0000775 except SyntaxError as e:
maruel@chromium.org46304292010-10-28 11:42:00 +0000776 gclient_utils.SyntaxErrorToError(filepath, e)
iannucci@chromium.org0c3f3052014-05-22 00:29:22 +0000777
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000778 if 'allowed_hosts' in local_scope:
779 try:
780 self._allowed_hosts = frozenset(local_scope.get('allowed_hosts'))
781 except TypeError: # raised if non-iterable
782 pass
783 if not self._allowed_hosts:
784 logging.warning("allowed_hosts is specified but empty %s",
785 self._allowed_hosts)
786 raise gclient_utils.Error(
787 'ParseDepsFile(%s): allowed_hosts must be absent '
788 'or a non-empty iterable' % self.name)
789
Michael Moss848c86e2018-05-03 16:05:50 -0700790 self._gn_args_from = local_scope.get('gclient_gn_args_from')
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +0200791 self._gn_args_file = local_scope.get('gclient_gn_args_file')
792 self._gn_args = local_scope.get('gclient_gn_args', [])
Michael Moss848c86e2018-05-03 16:05:50 -0700793 # It doesn't make sense to set all of these, since setting gn_args_from to
794 # another DEPS will make gclient ignore any other local gn_args* settings.
795 assert not (self._gn_args_from and self._gn_args_file), \
796 'Only specify one of "gclient_gn_args_from" or ' \
797 '"gclient_gn_args_file + gclient_gn_args".'
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +0200798
Edward Lesmes0b899352018-03-19 21:59:55 +0000799 self._vars = local_scope.get('vars', {})
Paweł Hajdan, Jr1407d002017-08-01 20:01:01 +0200800 if self.parent:
Marc-Antoine Ruel8e57b4b2019-10-11 01:01:36 +0000801 for key, value in self.parent.get_vars().items():
Paweł Hajdan, Jr1407d002017-08-01 20:01:01 +0200802 if key in self._vars:
803 self._vars[key] = value
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200804 # Since we heavily post-process things, freeze ones which should
805 # reflect original state of DEPS.
Paweł Hajdan, Jr1407d002017-08-01 20:01:01 +0200806 self._vars = gclient_utils.freeze(self._vars)
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200807
808 # If use_relative_paths is set in the DEPS file, regenerate
809 # the dictionary using paths relative to the directory containing
810 # the DEPS file. Also update recursedeps if use_relative_paths is
811 # enabled.
812 # If the deps file doesn't set use_relative_paths, but the parent did
813 # (and therefore set self.relative on this Dependency object), then we
814 # want to modify the deps and recursedeps by prepending the parent
815 # directory of this dependency.
Corentin Wallez271a78a2020-07-12 15:41:46 +0000816 self._use_relative_paths = local_scope.get('use_relative_paths', False)
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200817 rel_prefix = None
Corentin Wallez271a78a2020-07-12 15:41:46 +0000818 if self._use_relative_paths:
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200819 rel_prefix = self.name
820 elif self._relative:
821 rel_prefix = os.path.dirname(self.name)
822
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200823 if 'recursion' in local_scope:
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200824 logging.warning(
Edward Lemurfbb06aa2018-06-11 20:43:06 +0000825 '%s: Ignoring recursion = %d.', self.name, local_scope['recursion'])
826
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200827 if 'recursedeps' in local_scope:
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200828 for ent in local_scope['recursedeps']:
Aaron Gableac9b0f32019-04-18 17:38:37 +0000829 if isinstance(ent, basestring):
Edward Lemurfbb06aa2018-06-11 20:43:06 +0000830 self.recursedeps[ent] = self.deps_file
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200831 else: # (depname, depsfilename)
Edward Lemurfbb06aa2018-06-11 20:43:06 +0000832 self.recursedeps[ent[0]] = ent[1]
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200833 logging.warning('Found recursedeps %r.', repr(self.recursedeps))
834
835 if rel_prefix:
836 logging.warning('Updating recursedeps by prepending %s.', rel_prefix)
837 rel_deps = {}
Marc-Antoine Ruel8e57b4b2019-10-11 01:01:36 +0000838 for depname, options in self.recursedeps.items():
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200839 rel_deps[
840 os.path.normpath(os.path.join(rel_prefix, depname))] = options
841 self.recursedeps = rel_deps
Michael Moss848c86e2018-05-03 16:05:50 -0700842 # To get gn_args from another DEPS, that DEPS must be recursed into.
843 if self._gn_args_from:
844 assert self.recursedeps and self._gn_args_from in self.recursedeps, \
845 'The "gclient_gn_args_from" value must be in recursedeps.'
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200846
847 # If present, save 'target_os' in the local_target_os property.
848 if 'target_os' in local_scope:
849 self.local_target_os = local_scope['target_os']
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200850
Edward Lemur16f4bad2018-05-16 16:53:49 -0400851 deps = local_scope.get('deps', {})
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200852 deps_to_add = self._deps_to_objects(
Corentin Wallez271a78a2020-07-12 15:41:46 +0000853 self._postprocess_deps(deps, rel_prefix), self._use_relative_paths)
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000854
Corentin Walleza68660d2018-09-10 17:33:24 +0000855 # compute which working directory should be used for hooks
Michael Spang0e99b9b2020-08-12 13:34:48 +0000856 if local_scope.get('use_relative_hooks', False):
Joanna Wang4e6264c2022-06-30 19:10:43 +0000857 print('use_relative_hooks is deprecated, please remove it from '
858 '%s DEPS. (it was merged in use_relative_paths)' % self.name,
859 file=sys.stderr)
Michael Spang0e99b9b2020-08-12 13:34:48 +0000860
Corentin Walleza68660d2018-09-10 17:33:24 +0000861 hooks_cwd = self.root.root_dir
Corentin Wallez801c2022020-07-20 20:11:09 +0000862 if self._use_relative_paths:
Corentin Walleza68660d2018-09-10 17:33:24 +0000863 hooks_cwd = os.path.join(hooks_cwd, self.name)
864 logging.warning('Updating hook base working directory to %s.',
865 hooks_cwd)
866
Joanna Wang18af7ef2022-07-01 16:51:00 +0000867 # Only add all hooks if we should sync, otherwise just add custom hooks.
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000868 # override named sets of hooks by the custom hooks
869 hooks_to_run = []
Joanna Wang18af7ef2022-07-01 16:51:00 +0000870 if self._should_sync:
871 hook_names_to_suppress = [c.get('name', '') for c in self.custom_hooks]
872 for hook in local_scope.get('hooks', []):
873 if hook.get('name', '') not in hook_names_to_suppress:
874 hooks_to_run.append(hook)
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000875
876 # add the replacements and any additions
877 for hook in self.custom_hooks:
878 if 'action' in hook:
879 hooks_to_run.append(hook)
880
Joanna Wang18af7ef2022-07-01 16:51:00 +0000881 if self.should_recurse and deps_to_add:
Paweł Hajdan, Jr032d5452017-06-22 20:43:53 +0200882 self._pre_deps_hooks = [
Michael Moss42d02c22018-02-05 10:32:24 -0800883 Hook.from_dict(hook, variables=self.get_vars(), verbose=True,
Corentin Walleza68660d2018-09-10 17:33:24 +0000884 conditions=self.condition, cwd_base=hooks_cwd)
Daniel Chenga0c5f082017-10-19 13:35:19 -0700885 for hook in local_scope.get('pre_deps_hooks', [])
886 ]
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000887
Corentin Walleza68660d2018-09-10 17:33:24 +0000888 self.add_dependencies_and_close(deps_to_add, hooks_to_run,
889 hooks_cwd=hooks_cwd)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000890 logging.info('ParseDepsFile(%s) done' % self.name)
891
Michael Mossd683d7c2018-06-15 05:05:17 +0000892 def _get_option(self, attr, default):
893 obj = self
894 while not hasattr(obj, '_options'):
895 obj = obj.parent
896 return getattr(obj._options, attr, default)
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +0200897
Corentin Walleza68660d2018-09-10 17:33:24 +0000898 def add_dependencies_and_close(self, deps_to_add, hooks, hooks_cwd=None):
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000899 """Adds the dependencies, hooks and mark the parsing as done."""
Corentin Walleza68660d2018-09-10 17:33:24 +0000900 if hooks_cwd == None:
901 hooks_cwd = self.root.root_dir
902
maruel@chromium.orgb9be0652011-10-14 18:05:40 +0000903 for dep in deps_to_add:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000904 if dep.verify_validity():
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000905 self.add_dependency(dep)
Daniel Chenga0c5f082017-10-19 13:35:19 -0700906 self._mark_as_parsed([
907 Hook.from_dict(
Michael Moss42d02c22018-02-05 10:32:24 -0800908 h, variables=self.get_vars(), verbose=self.root._options.verbose,
Corentin Walleza68660d2018-09-10 17:33:24 +0000909 conditions=self.condition, cwd_base=hooks_cwd)
Daniel Chenga0c5f082017-10-19 13:35:19 -0700910 for h in hooks
911 ])
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000912
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000913 def findDepsFromNotAllowedHosts(self):
Corentin Wallezaca984c2018-09-07 21:52:14 +0000914 """Returns a list of dependencies from not allowed hosts.
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000915
916 If allowed_hosts is not set, allows all hosts and returns empty list.
917 """
918 if not self._allowed_hosts:
919 return []
920 bad_deps = []
921 for dep in self._dependencies:
szager@chromium.orgbd772dd2014-11-05 18:43:08 +0000922 # Don't enforce this for custom_deps.
923 if dep.name in self._custom_deps:
924 continue
Michael Mossd683d7c2018-06-15 05:05:17 +0000925 if isinstance(dep.url, basestring):
926 parsed_url = urlparse.urlparse(dep.url)
927 if parsed_url.netloc and parsed_url.netloc not in self._allowed_hosts:
928 bad_deps.append(dep)
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000929 return bad_deps
930
Edward Lemure7273d22018-05-10 19:13:51 -0400931 def FuzzyMatchUrl(self, candidates):
Joanna Wang66286612022-06-30 19:59:13 +0000932 # type: (Union[Mapping[str, str], Collection[str]]) -> Optional[str]
Edward Lesmesbb16e332018-03-30 17:54:51 -0400933 """Attempts to find this dependency in the list of candidates.
934
Edward Lemure7273d22018-05-10 19:13:51 -0400935 It looks first for the URL of this dependency in the list of
Edward Lesmesbb16e332018-03-30 17:54:51 -0400936 candidates. If it doesn't succeed, and the URL ends in '.git', it will try
937 looking for the URL minus '.git'. Finally it will try to look for the name
938 of the dependency.
939
940 Args:
Edward Lesmesbb16e332018-03-30 17:54:51 -0400941 candidates: list, dict. The list of candidates in which to look for this
942 dependency. It can contain URLs as above, or dependency names like
943 "src/some/dep".
944
945 Returns:
946 If this dependency is not found in the list of candidates, returns None.
947 Otherwise, it returns under which name did we find this dependency:
948 - Its parsed url: "https://example.com/src.git'
949 - Its parsed url minus '.git': "https://example.com/src"
950 - Its name: "src"
951 """
Edward Lemure7273d22018-05-10 19:13:51 -0400952 if self.url:
953 origin, _ = gclient_utils.SplitUrlRevision(self.url)
Joanna Wang66286612022-06-30 19:59:13 +0000954 match = gclient_utils.FuzzyMatchRepo(origin, candidates)
955 if match:
956 return match
Edward Lesmesbb16e332018-03-30 17:54:51 -0400957 if self.name in candidates:
958 return self.name
959 return None
960
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +0000961 # Arguments number differs from overridden method
Quinten Yearsleyb2cc4a92016-12-15 13:53:26 -0800962 # pylint: disable=arguments-differ
Joanna Wang18af7ef2022-07-01 16:51:00 +0000963 def run(
964 self,
965 revision_overrides, # type: Mapping[str, str]
966 command, # type: str
967 args, # type: Sequence[str]
968 work_queue, # type: ExecutionQueue
969 options, # type: optparse.Values
970 patch_refs, # type: Mapping[str, str]
971 target_branches # type: Mapping[str, str]
972 ):
973 # type: () -> None
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000974 """Runs |command| then parse the DEPS file."""
maruel@chromium.org470b5432011-10-11 18:18:19 +0000975 logging.info('Dependency(%s).run()' % self.name)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000976 assert self._file_list == []
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000977 # When running runhooks, there's no need to consult the SCM.
978 # All known hooks are expected to run unconditionally regardless of working
979 # copy state, so skip the SCM status check.
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +0200980 run_scm = command not in (
981 'flatten', 'runhooks', 'recurse', 'validate', None)
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000982 file_list = [] if not options.nohooks else None
Edward Lesmesbb16e332018-03-30 17:54:51 -0400983 revision_override = revision_overrides.pop(
Edward Lemure7273d22018-05-10 19:13:51 -0400984 self.FuzzyMatchUrl(revision_overrides), None)
Edward Lemure4213702018-06-21 21:15:50 +0000985 if not revision_override and not self.managed:
986 revision_override = 'unmanaged'
Michael Mossd683d7c2018-06-15 05:05:17 +0000987 if run_scm and self.url:
agabled437d762016-10-17 09:35:11 -0700988 # Create a shallow copy to mutate revision.
989 options = copy.copy(options)
990 options.revision = revision_override
991 self._used_revision = options.revision
Edward Lemurbabd0982018-05-11 13:32:37 -0400992 self._used_scm = self.CreateSCM(out_cb=work_queue.out_cb)
Edward Lesmesc8f63d32021-06-02 23:51:53 +0000993 if command != 'update' or self.GetScmName() != 'git':
994 self._got_revision = self._used_scm.RunCommand(command, options, args,
995 file_list)
996 else:
997 try:
998 start = time.time()
999 sync_status = metrics_utils.SYNC_STATUS_FAILURE
1000 self._got_revision = self._used_scm.RunCommand(command, options, args,
1001 file_list)
1002 sync_status = metrics_utils.SYNC_STATUS_SUCCESS
1003 finally:
1004 url, revision = gclient_utils.SplitUrlRevision(self.url)
1005 metrics.collector.add_repeated('git_deps', {
1006 'path': self.name,
1007 'url': url,
1008 'revision': revision,
1009 'execution_time': time.time() - start,
1010 'sync_status': sync_status,
1011 })
Edward Lesmesc621b212018-03-21 20:26:56 -04001012
Edward Lemure7273d22018-05-10 19:13:51 -04001013 patch_repo = self.url.split('@')[0]
1014 patch_ref = patch_refs.pop(self.FuzzyMatchUrl(patch_refs), None)
Edward Lemur6a4e31b2018-08-10 19:59:02 +00001015 target_branch = target_branches.pop(
1016 self.FuzzyMatchUrl(target_branches), None)
Edward Lesmesc621b212018-03-21 20:26:56 -04001017 if command == 'update' and patch_ref is not None:
Edward Lemur6a4e31b2018-08-10 19:59:02 +00001018 self._used_scm.apply_patch_ref(patch_repo, patch_ref, target_branch,
1019 options, file_list)
Edward Lesmesc621b212018-03-21 20:26:56 -04001020
agabled437d762016-10-17 09:35:11 -07001021 if file_list:
1022 file_list = [os.path.join(self.name, f.strip()) for f in file_list]
maruel@chromium.org68988972011-09-20 14:11:42 +00001023
1024 # TODO(phajdan.jr): We should know exactly when the paths are absolute.
1025 # Convert all absolute paths to relative.
iannucci@chromium.org396e1a62013-07-03 19:41:04 +00001026 for i in range(len(file_list or [])):
maruel@chromium.org68988972011-09-20 14:11:42 +00001027 # It depends on the command being executed (like runhooks vs sync).
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001028 if not os.path.isabs(file_list[i]):
maruel@chromium.org68988972011-09-20 14:11:42 +00001029 continue
1030 prefix = os.path.commonprefix(
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001031 [self.root.root_dir.lower(), file_list[i].lower()])
1032 file_list[i] = file_list[i][len(prefix):]
maruel@chromium.org68988972011-09-20 14:11:42 +00001033 # Strip any leading path separators.
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001034 while file_list[i].startswith(('\\', '/')):
1035 file_list[i] = file_list[i][1:]
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001036
Joanna Wang18af7ef2022-07-01 16:51:00 +00001037 # TODO(crbug.com/1339472): Pass skip_sync_revisions into this run()
1038 # and check for DEPS diffs to set self._should_sync.
Edward Lemurfbb06aa2018-06-11 20:43:06 +00001039 if self.should_recurse:
Edward Lemure05f18d2018-06-08 17:36:53 +00001040 self.ParseDepsFile()
Edward Lesmes5d6cde32018-04-12 18:32:46 -04001041
Edward Lemure7273d22018-05-10 19:13:51 -04001042 self._run_is_done(file_list or [])
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001043
Edward Lemurfbb06aa2018-06-11 20:43:06 +00001044 if self.should_recurse:
Edward Lesmes5d6cde32018-04-12 18:32:46 -04001045 if command in ('update', 'revert') and not options.noprehooks:
1046 self.RunPreDepsHooks()
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001047 # Parse the dependencies of this dependency.
1048 for s in self.dependencies:
Michael Mossd683d7c2018-06-15 05:05:17 +00001049 if s.should_process:
1050 work_queue.enqueue(s)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001051
1052 if command == 'recurse':
agabled437d762016-10-17 09:35:11 -07001053 # Skip file only checkout.
Edward Lemurbabd0982018-05-11 13:32:37 -04001054 scm = self.GetScmName()
agabled437d762016-10-17 09:35:11 -07001055 if not options.scm or scm in options.scm:
1056 cwd = os.path.normpath(os.path.join(self.root.root_dir, self.name))
1057 # Pass in the SCM type as an env variable. Make sure we don't put
1058 # unicode strings in the environment.
1059 env = os.environ.copy()
Michael Mossd683d7c2018-06-15 05:05:17 +00001060 if scm:
1061 env['GCLIENT_SCM'] = str(scm)
1062 if self.url:
1063 env['GCLIENT_URL'] = str(self.url)
agabled437d762016-10-17 09:35:11 -07001064 env['GCLIENT_DEP_PATH'] = str(self.name)
1065 if options.prepend_dir and scm == 'git':
1066 print_stdout = False
1067 def filter_fn(line):
1068 """Git-specific path marshaling. It is optimized for git-grep."""
ilevy@chromium.org0233ac22012-11-28 20:27:02 +00001069
agabled437d762016-10-17 09:35:11 -07001070 def mod_path(git_pathspec):
1071 match = re.match('^(\\S+?:)?([^\0]+)$', git_pathspec)
1072 modified_path = os.path.join(self.name, match.group(2))
1073 branch = match.group(1) or ''
1074 return '%s%s' % (branch, modified_path)
ilevy@chromium.org0233ac22012-11-28 20:27:02 +00001075
agabled437d762016-10-17 09:35:11 -07001076 match = re.match('^Binary file ([^\0]+) matches$', line)
1077 if match:
1078 print('Binary file %s matches\n' % mod_path(match.group(1)))
1079 return
ilevy@chromium.org0233ac22012-11-28 20:27:02 +00001080
agabled437d762016-10-17 09:35:11 -07001081 items = line.split('\0')
1082 if len(items) == 2 and items[1]:
1083 print('%s : %s' % (mod_path(items[0]), items[1]))
1084 elif len(items) >= 2:
1085 # Multiple null bytes or a single trailing null byte indicate
1086 # git is likely displaying filenames only (such as with -l)
1087 print('\n'.join(mod_path(path) for path in items if path))
1088 else:
1089 print(line)
1090 else:
1091 print_stdout = True
1092 filter_fn = None
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001093
Michael Mossd683d7c2018-06-15 05:05:17 +00001094 if self.url is None:
1095 print('Skipped omitted dependency %s' % cwd, file=sys.stderr)
1096 elif os.path.isdir(cwd):
agabled437d762016-10-17 09:35:11 -07001097 try:
1098 gclient_utils.CheckCallAndFilter(
Ben Masonfbd2c632020-06-22 14:59:13 +00001099 args, cwd=cwd, env=env, print_stdout=print_stdout,
agabled437d762016-10-17 09:35:11 -07001100 filter_fn=filter_fn,
Ben Masonfbd2c632020-06-22 14:59:13 +00001101 )
agabled437d762016-10-17 09:35:11 -07001102 except subprocess2.CalledProcessError:
1103 if not options.ignore:
1104 raise
1105 else:
1106 print('Skipped missing %s' % cwd, file=sys.stderr)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001107
Edward Lemurbabd0982018-05-11 13:32:37 -04001108 def GetScmName(self):
Edward Lemurb61d3872018-05-09 18:42:47 -04001109 raise NotImplementedError()
John Budorick0f7b2002018-01-19 15:46:17 -08001110
Edward Lemurbabd0982018-05-11 13:32:37 -04001111 def CreateSCM(self, out_cb=None):
Edward Lemurb61d3872018-05-09 18:42:47 -04001112 raise NotImplementedError()
John Budorick0f7b2002018-01-19 15:46:17 -08001113
Dirk Pranke9f20d022017-10-11 18:36:54 -07001114 def HasGNArgsFile(self):
1115 return self._gn_args_file is not None
1116
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +02001117 def WriteGNArgsFile(self):
1118 lines = ['# Generated from %r' % self.deps_file]
Paweł Hajdan, Jrb495bf52017-09-25 19:33:50 +02001119 variables = self.get_vars()
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +02001120 for arg in self._gn_args:
Paweł Hajdan, Jre0214742017-09-28 12:21:01 +02001121 value = variables[arg]
Dirk Prankefdd2cd62020-06-30 23:30:47 +00001122 if isinstance(value, gclient_eval.ConstantString):
1123 value = value.value
1124 elif isinstance(value, basestring):
Paweł Hajdan, Jre0214742017-09-28 12:21:01 +02001125 value = gclient_eval.EvaluateCondition(value, variables)
Paweł Hajdan, Jrb495bf52017-09-25 19:33:50 +02001126 lines.append('%s = %s' % (arg, ToGNString(value)))
Corentin Wallez271a78a2020-07-12 15:41:46 +00001127
1128 # When use_relative_paths is set, gn_args_file is relative to this DEPS
1129 path_prefix = self.root.root_dir
1130 if self._use_relative_paths:
Lei Zhang67283c02020-07-13 21:38:44 +00001131 path_prefix = os.path.join(path_prefix, self.name)
Corentin Wallez271a78a2020-07-12 15:41:46 +00001132
1133 with open(os.path.join(path_prefix, self._gn_args_file), 'wb') as f:
Edward Lesmes05934952019-12-19 20:38:09 +00001134 f.write('\n'.join(lines).encode('utf-8', 'replace'))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001135
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001136 @gclient_utils.lockedmethod
Edward Lemure7273d22018-05-10 19:13:51 -04001137 def _run_is_done(self, file_list):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001138 # Both these are kept for hooks that are run as a separate tree traversal.
1139 self._file_list = file_list
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001140 self._processed = True
1141
szager@google.comb9a78d32012-03-13 18:46:21 +00001142 def GetHooks(self, options):
1143 """Evaluates all hooks, and return them in a flat list.
1144
1145 RunOnDeps() must have been called before to load the DEPS.
1146 """
1147 result = []
Michael Mossd683d7c2018-06-15 05:05:17 +00001148 if not self.should_process or not self.should_recurse:
1149 # Don't run the hook when it is above recursion_limit.
1150 return result
maruel@chromium.orgdc7445d2010-07-09 21:05:29 +00001151 # If "--force" was specified, run all hooks regardless of what files have
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001152 # changed.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001153 if self.deps_hooks:
agabled437d762016-10-17 09:35:11 -07001154 # TODO(maruel): If the user is using git, then we don't know
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001155 # what files have changed so we always run all hooks. It'd be nice to fix
1156 # that.
Edward Lemurbabd0982018-05-11 13:32:37 -04001157 result.extend(self.deps_hooks)
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001158 for s in self.dependencies:
szager@google.comb9a78d32012-03-13 18:46:21 +00001159 result.extend(s.GetHooks(options))
1160 return result
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001161
Daniel Chenga0c5f082017-10-19 13:35:19 -07001162 def RunHooksRecursively(self, options, progress):
szager@google.comb9a78d32012-03-13 18:46:21 +00001163 assert self.hooks_ran == False
maruel@chromium.org064186c2011-09-27 23:53:33 +00001164 self._hooks_ran = True
Daniel Chenga0c5f082017-10-19 13:35:19 -07001165 hooks = self.GetHooks(options)
1166 if progress:
1167 progress._total = len(hooks)
1168 for hook in hooks:
Daniel Chenga0c5f082017-10-19 13:35:19 -07001169 if progress:
1170 progress.update(extra=hook.name or '')
Corentin Walleza68660d2018-09-10 17:33:24 +00001171 hook.run()
Daniel Chenga0c5f082017-10-19 13:35:19 -07001172 if progress:
1173 progress.end()
maruel@chromium.orgeaf61062010-07-07 18:42:39 +00001174
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001175 def RunPreDepsHooks(self):
1176 assert self.processed
1177 assert self.deps_parsed
1178 assert not self.pre_deps_hooks_ran
1179 assert not self.hooks_ran
1180 for s in self.dependencies:
1181 assert not s.processed
1182 self._pre_deps_hooks_ran = True
1183 for hook in self.pre_deps_hooks:
Corentin Walleza68660d2018-09-10 17:33:24 +00001184 hook.run()
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001185
John Budorickd3ba72b2018-03-20 12:27:42 -07001186 def GetCipdRoot(self):
1187 if self.root is self:
1188 # Let's not infinitely recurse. If this is root and isn't an
1189 # instance of GClient, do nothing.
1190 return None
1191 return self.root.GetCipdRoot()
1192
Michael Mossd683d7c2018-06-15 05:05:17 +00001193 def subtree(self, include_all):
maruel@chromium.orgad3287e2011-10-03 19:15:10 +00001194 """Breadth first recursion excluding root node."""
maruel@chromium.orgf13a4182011-09-22 00:26:15 +00001195 dependencies = self.dependencies
1196 for d in dependencies:
Michael Mossd683d7c2018-06-15 05:05:17 +00001197 if d.should_process or include_all:
1198 yield d
maruel@chromium.orgf13a4182011-09-22 00:26:15 +00001199 for d in dependencies:
Michael Mossd683d7c2018-06-15 05:05:17 +00001200 for i in d.subtree(include_all):
maruel@chromium.orgad3287e2011-10-03 19:15:10 +00001201 yield i
1202
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001203 @gclient_utils.lockedmethod
1204 def add_dependency(self, new_dep):
1205 self._dependencies.append(new_dep)
1206
1207 @gclient_utils.lockedmethod
Paweł Hajdan, Jr357415c2017-07-24 14:35:28 +02001208 def _mark_as_parsed(self, new_hooks):
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001209 self._deps_hooks.extend(new_hooks)
1210 self._deps_parsed = True
1211
maruel@chromium.org68988972011-09-20 14:11:42 +00001212 @property
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001213 @gclient_utils.lockedmethod
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +00001214 def dependencies(self):
1215 return tuple(self._dependencies)
1216
1217 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001218 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001219 def deps_hooks(self):
1220 return tuple(self._deps_hooks)
1221
1222 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001223 @gclient_utils.lockedmethod
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001224 def pre_deps_hooks(self):
1225 return tuple(self._pre_deps_hooks)
1226
1227 @property
1228 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001229 def deps_parsed(self):
maruel@chromium.org3223edd2011-10-10 23:17:39 +00001230 """This is purely for debugging purposes. It's not used anywhere."""
maruel@chromium.org064186c2011-09-27 23:53:33 +00001231 return self._deps_parsed
1232
1233 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001234 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001235 def processed(self):
1236 return self._processed
1237
1238 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001239 @gclient_utils.lockedmethod
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001240 def pre_deps_hooks_ran(self):
1241 return self._pre_deps_hooks_ran
1242
1243 @property
1244 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001245 def hooks_ran(self):
1246 return self._hooks_ran
1247
1248 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001249 @gclient_utils.lockedmethod
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00001250 def allowed_hosts(self):
1251 return self._allowed_hosts
1252
1253 @property
1254 @gclient_utils.lockedmethod
maruel@chromium.org861fd0f2010-07-23 03:05:05 +00001255 def file_list(self):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001256 return tuple(self._file_list)
1257
1258 @property
kustermann@google.coma692e8f2013-04-18 08:32:04 +00001259 def used_scm(self):
1260 """SCMWrapper instance for this dependency or None if not processed yet."""
1261 return self._used_scm
1262
1263 @property
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00001264 @gclient_utils.lockedmethod
1265 def got_revision(self):
1266 return self._got_revision
1267
1268 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001269 def file_list_and_children(self):
1270 result = list(self.file_list)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +00001271 for d in self.dependencies:
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001272 result.extend(d.file_list_and_children)
maruel@chromium.org68988972011-09-20 14:11:42 +00001273 return tuple(result)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +00001274
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001275 def __str__(self):
1276 out = []
Edward Lemure7273d22018-05-10 19:13:51 -04001277 for i in ('name', 'url', 'custom_deps',
Michael Mossd683d7c2018-06-15 05:05:17 +00001278 'custom_vars', 'deps_hooks', 'file_list', 'should_process',
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00001279 'processed', 'hooks_ran', 'deps_parsed', 'requirements',
1280 'allowed_hosts'):
maruel@chromium.org3c74bc92011-09-15 19:17:21 +00001281 # First try the native property if it exists.
1282 if hasattr(self, '_' + i):
1283 value = getattr(self, '_' + i, False)
1284 else:
1285 value = getattr(self, i, False)
1286 if value:
1287 out.append('%s: %s' % (i, value))
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001288
1289 for d in self.dependencies:
1290 out.extend([' ' + x for x in str(d).splitlines()])
1291 out.append('')
1292 return '\n'.join(out)
1293
1294 def __repr__(self):
1295 return '%s: %s' % (self.name, self.url)
1296
Michael Moss4e9b50a2018-05-23 22:35:06 -07001297 def hierarchy(self, include_url=True):
maruel@chromium.orgbc2d2f92010-07-22 21:26:48 +00001298 """Returns a human-readable hierarchical reference to a Dependency."""
Michael Moss4e9b50a2018-05-23 22:35:06 -07001299 def format_name(d):
1300 if include_url:
1301 return '%s(%s)' % (d.name, d.url)
1302 return d.name
1303 out = format_name(self)
maruel@chromium.orgbffb9042010-07-22 20:59:36 +00001304 i = self.parent
1305 while i and i.name:
Michael Moss4e9b50a2018-05-23 22:35:06 -07001306 out = '%s -> %s' % (format_name(i), out)
maruel@chromium.orgbffb9042010-07-22 20:59:36 +00001307 i = i.parent
1308 return out
1309
Michael Mossfe68c912018-03-22 19:19:35 -07001310 def hierarchy_data(self):
1311 """Returns a machine-readable hierarchical reference to a Dependency."""
1312 d = self
1313 out = []
1314 while d and d.name:
1315 out.insert(0, (d.name, d.url))
1316 d = d.parent
1317 return tuple(out)
1318
Edward Lemur8f8a50d2018-11-01 22:03:02 +00001319 def get_builtin_vars(self):
1320 return {
Paweł Hajdan, Jrd325eb32017-10-03 17:43:37 +02001321 'checkout_android': 'android' in self.target_os,
Benjamin Pastene6fe29412018-01-23 15:35:58 -08001322 'checkout_chromeos': 'chromeos' in self.target_os,
Paweł Hajdan, Jrd325eb32017-10-03 17:43:37 +02001323 'checkout_fuchsia': 'fuchsia' in self.target_os,
1324 'checkout_ios': 'ios' in self.target_os,
1325 'checkout_linux': 'unix' in self.target_os,
1326 'checkout_mac': 'mac' in self.target_os,
1327 'checkout_win': 'win' in self.target_os,
1328 'host_os': _detect_host_os(),
Tom Andersonc31ae0b2018-02-06 14:48:56 -08001329
1330 'checkout_arm': 'arm' in self.target_cpu,
1331 'checkout_arm64': 'arm64' in self.target_cpu,
1332 'checkout_x86': 'x86' in self.target_cpu,
1333 'checkout_mips': 'mips' in self.target_cpu,
Wang Qing254538b2018-07-26 02:23:53 +00001334 'checkout_mips64': 'mips64' in self.target_cpu,
Tom Andersonc31ae0b2018-02-06 14:48:56 -08001335 'checkout_ppc': 'ppc' in self.target_cpu,
1336 'checkout_s390': 's390' in self.target_cpu,
1337 'checkout_x64': 'x64' in self.target_cpu,
1338 'host_cpu': detect_host_arch.HostArch(),
Paweł Hajdan, Jra3b67ae2017-08-30 15:18:21 +02001339 }
Edward Lemur8f8a50d2018-11-01 22:03:02 +00001340
1341 def get_vars(self):
1342 """Returns a dictionary of effective variable values
1343 (DEPS file contents with applied custom_vars overrides)."""
1344 # Variable precedence (last has highest):
Michael Mossda55cdc2018-04-06 18:37:19 -07001345 # - DEPS vars
1346 # - parents, from first to last
Edward Lemur8f8a50d2018-11-01 22:03:02 +00001347 # - built-in
Michael Mossda55cdc2018-04-06 18:37:19 -07001348 # - custom_vars overrides
Edward Lemur8f8a50d2018-11-01 22:03:02 +00001349 result = {}
Paweł Hajdan, Jra3b67ae2017-08-30 15:18:21 +02001350 result.update(self._vars)
Michael Mossda55cdc2018-04-06 18:37:19 -07001351 if self.parent:
Dirk Prankefdd2cd62020-06-30 23:30:47 +00001352 merge_vars(result, self.parent.get_vars())
Edward Lemur8f8a50d2018-11-01 22:03:02 +00001353 # Provide some built-in variables.
1354 result.update(self.get_builtin_vars())
Dirk Prankefdd2cd62020-06-30 23:30:47 +00001355 merge_vars(result, self.custom_vars)
1356
Paweł Hajdan, Jrd3790252017-07-03 21:06:24 +02001357 return result
1358
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001359
Paweł Hajdan, Jra3b67ae2017-08-30 15:18:21 +02001360_PLATFORM_MAPPING = {
1361 'cygwin': 'win',
1362 'darwin': 'mac',
1363 'linux2': 'linux',
Edward Lemuree7b9dd2019-07-20 01:29:08 +00001364 'linux': 'linux',
Paweł Hajdan, Jra3b67ae2017-08-30 15:18:21 +02001365 'win32': 'win',
Jaideep Bajwad05f3582017-09-11 12:31:48 -04001366 'aix6': 'aix',
Paweł Hajdan, Jra3b67ae2017-08-30 15:18:21 +02001367}
1368
1369
Dirk Prankefdd2cd62020-06-30 23:30:47 +00001370def merge_vars(result, new_vars):
1371 for k, v in new_vars.items():
1372 if k in result:
1373 if isinstance(result[k], gclient_eval.ConstantString):
1374 if isinstance(v, gclient_eval.ConstantString):
1375 result[k] = v
1376 else:
1377 result[k].value = v
1378 else:
1379 result[k] = v
1380 else:
1381 result[k] = v
1382
1383
Paweł Hajdan, Jra3b67ae2017-08-30 15:18:21 +02001384def _detect_host_os():
1385 return _PLATFORM_MAPPING[sys.platform]
1386
1387
Edward Lemurb61d3872018-05-09 18:42:47 -04001388class GitDependency(Dependency):
1389 """A Dependency object that represents a single git checkout."""
1390
Aravind Vasudevan5965d3e2022-06-01 21:51:30 +00001391 @staticmethod
1392 def updateProtocol(url, protocol):
1393 """Updates given URL's protocol"""
1394 # only works on urls, skips local paths
1395 if not url or not protocol or not re.match('([a-z]+)://', url) or \
1396 re.match('file://', url):
1397 return url
1398
1399 return re.sub('^([a-z]+):', protocol + ':', url)
1400
Edward Lemurb61d3872018-05-09 18:42:47 -04001401 #override
Edward Lemurbabd0982018-05-11 13:32:37 -04001402 def GetScmName(self):
Edward Lemurb61d3872018-05-09 18:42:47 -04001403 """Always 'git'."""
Edward Lemurb61d3872018-05-09 18:42:47 -04001404 return 'git'
1405
1406 #override
Edward Lemurbabd0982018-05-11 13:32:37 -04001407 def CreateSCM(self, out_cb=None):
Edward Lemurb61d3872018-05-09 18:42:47 -04001408 """Create a Wrapper instance suitable for handling this git dependency."""
Edward Lemurbabd0982018-05-11 13:32:37 -04001409 return gclient_scm.GitWrapper(
1410 self.url, self.root.root_dir, self.name, self.outbuf, out_cb,
1411 print_outbuf=self.print_outbuf)
Edward Lemurb61d3872018-05-09 18:42:47 -04001412
1413
1414class GClient(GitDependency):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001415 """Object that represent a gclient checkout. A tree of Dependency(), one per
1416 solution or DEPS entry."""
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001417
1418 DEPS_OS_CHOICES = {
Jaideep Bajwad05f3582017-09-11 12:31:48 -04001419 "aix6": "unix",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001420 "win32": "win",
1421 "win": "win",
1422 "cygwin": "win",
1423 "darwin": "mac",
1424 "mac": "mac",
1425 "unix": "unix",
1426 "linux": "unix",
1427 "linux2": "unix",
maruel@chromium.org244e3442011-06-12 15:20:55 +00001428 "linux3": "unix",
szager@chromium.orgf8c95cd2012-06-01 22:26:52 +00001429 "android": "android",
Michael Mossc54fa812017-08-17 11:27:58 -07001430 "ios": "ios",
Sergiy Byelozyorov518bb682018-06-03 11:25:58 +02001431 "fuchsia": "fuchsia",
Michael Moss484d74f2019-02-06 01:55:43 +00001432 "chromeos": "chromeos",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001433 }
1434
1435 DEFAULT_CLIENT_FILE_TEXT = ("""\
1436solutions = [
Edward Lesmes05934952019-12-19 20:38:09 +00001437 { "name" : %(solution_name)r,
1438 "url" : %(solution_url)r,
1439 "deps_file" : %(deps_file)r,
1440 "managed" : %(managed)r,
smutae7ea312016-07-18 11:59:41 -07001441 "custom_deps" : {
1442 },
Paweł Hajdan, Jr3ba2a7c2017-10-04 19:24:46 +02001443 "custom_vars": %(custom_vars)r,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001444 },
1445]
Robert Iannuccia19649b2018-06-29 16:31:45 +00001446""")
1447
1448 DEFAULT_CLIENT_CACHE_DIR_TEXT = ("""\
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001449cache_dir = %(cache_dir)r
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001450""")
1451
Robert Iannuccia19649b2018-06-29 16:31:45 +00001452
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001453 DEFAULT_SNAPSHOT_FILE_TEXT = ("""\
1454# Snapshot generated with gclient revinfo --snapshot
Edward Lesmesc2960242018-03-06 20:50:15 -05001455solutions = %(solution_list)s
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001456""")
1457
1458 def __init__(self, root_dir, options):
maruel@chromium.org0d812442010-08-10 12:41:08 +00001459 # Do not change previous behavior. Only solution level and immediate DEPS
1460 # are processed.
1461 self._recursion_limit = 2
Edward Lemure05f18d2018-06-08 17:36:53 +00001462 super(GClient, self).__init__(
1463 parent=None,
1464 name=None,
Michael Mossd683d7c2018-06-15 05:05:17 +00001465 url=None,
Edward Lemure05f18d2018-06-08 17:36:53 +00001466 managed=True,
1467 custom_deps=None,
1468 custom_vars=None,
1469 custom_hooks=None,
1470 deps_file='unused',
Michael Mossd683d7c2018-06-15 05:05:17 +00001471 should_process=True,
Edward Lemurfbb06aa2018-06-11 20:43:06 +00001472 should_recurse=True,
Edward Lemure05f18d2018-06-08 17:36:53 +00001473 relative=None,
1474 condition=None,
1475 print_outbuf=True)
1476
maruel@chromium.org0d425922010-06-21 19:22:24 +00001477 self._options = options
maruel@chromium.org271375b2010-06-23 19:17:38 +00001478 if options.deps_os:
1479 enforced_os = options.deps_os.split(',')
1480 else:
1481 enforced_os = [self.DEPS_OS_CHOICES.get(sys.platform, 'unix')]
1482 if 'all' in enforced_os:
Edward Lemuree7b9dd2019-07-20 01:29:08 +00001483 enforced_os = self.DEPS_OS_CHOICES.values()
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001484 self._enforced_os = tuple(set(enforced_os))
Aravind Vasudevanc5f0cbb2022-01-24 23:56:57 +00001485 self._enforced_cpu = (detect_host_arch.HostArch(), )
maruel@chromium.org271375b2010-06-23 19:17:38 +00001486 self._root_dir = root_dir
John Budorickd3ba72b2018-03-20 12:27:42 -07001487 self._cipd_root = None
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001488 self.config_content = None
1489
borenet@google.com88d10082014-03-21 17:24:48 +00001490 def _CheckConfig(self):
1491 """Verify that the config matches the state of the existing checked-out
1492 solutions."""
1493 for dep in self.dependencies:
Michael Mossd683d7c2018-06-15 05:05:17 +00001494 if dep.managed and dep.url:
Edward Lemurbabd0982018-05-11 13:32:37 -04001495 scm = dep.CreateSCM()
smut@google.comd33eab32014-07-07 19:35:18 +00001496 actual_url = scm.GetActualRemoteURL(self._options)
borenet@google.com4e9be262014-04-08 19:40:30 +00001497 if actual_url and not scm.DoesRemoteURLMatch(self._options):
levarum@chromium.org27a6f9a2016-05-28 00:21:49 +00001498 mirror = scm.GetCacheMirror()
1499 if mirror:
1500 mirror_string = '%s (exists=%s)' % (mirror.mirror_path,
1501 mirror.exists())
1502 else:
1503 mirror_string = 'not used'
Raul Tambreb946b232019-03-26 14:48:46 +00001504 raise gclient_utils.Error(
1505 '''
borenet@google.com88d10082014-03-21 17:24:48 +00001506Your .gclient file seems to be broken. The requested URL is different from what
borenet@google.com0a427372014-04-02 19:12:13 +00001507is actually checked out in %(checkout_path)s.
borenet@google.com88d10082014-03-21 17:24:48 +00001508
borenet@google.com97882362014-04-07 20:06:02 +00001509The .gclient file contains:
levarum@chromium.org27a6f9a2016-05-28 00:21:49 +00001510URL: %(expected_url)s (%(expected_scm)s)
1511Cache mirror: %(mirror_string)s
borenet@google.com97882362014-04-07 20:06:02 +00001512
1513The local checkout in %(checkout_path)s reports:
1514%(actual_url)s (%(actual_scm)s)
borenet@google.com88d10082014-03-21 17:24:48 +00001515
1516You should ensure that the URL listed in .gclient is correct and either change
agabled437d762016-10-17 09:35:11 -07001517it or fix the checkout.
Josip Sokcevic14a83ae2020-05-21 01:36:34 +00001518''' % {
1519 'checkout_path': os.path.join(self.root_dir, dep.name),
1520 'expected_url': dep.url,
1521 'expected_scm': dep.GetScmName(),
1522 'mirror_string': mirror_string,
1523 'actual_url': actual_url,
1524 'actual_scm': dep.GetScmName()
1525 })
borenet@google.com88d10082014-03-21 17:24:48 +00001526
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001527 def SetConfig(self, content):
maruel@chromium.orgf13a4182011-09-22 00:26:15 +00001528 assert not self.dependencies
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001529 config_dict = {}
1530 self.config_content = content
1531 try:
1532 exec(content, config_dict)
vapier@chromium.orga81a56e2015-11-11 07:56:13 +00001533 except SyntaxError as e:
maruel@chromium.org5990f9d2010-07-07 18:02:58 +00001534 gclient_utils.SyntaxErrorToError('.gclient', e)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001535
peter@chromium.org1efccc82012-04-27 16:34:38 +00001536 # Append any target OS that is not already being enforced to the tuple.
1537 target_os = config_dict.get('target_os', [])
stuartmorgan@chromium.org18a4f6a2012-11-02 13:37:24 +00001538 if config_dict.get('target_os_only', False):
1539 self._enforced_os = tuple(set(target_os))
1540 else:
1541 self._enforced_os = tuple(set(self._enforced_os).union(target_os))
1542
Tom Andersonc31ae0b2018-02-06 14:48:56 -08001543 # Append any target CPU that is not already being enforced to the tuple.
1544 target_cpu = config_dict.get('target_cpu', [])
1545 if config_dict.get('target_cpu_only', False):
1546 self._enforced_cpu = tuple(set(target_cpu))
1547 else:
1548 self._enforced_cpu = tuple(set(self._enforced_cpu).union(target_cpu))
1549
Robert Iannuccia19649b2018-06-29 16:31:45 +00001550 cache_dir = config_dict.get('cache_dir', UNSET_CACHE_DIR)
1551 if cache_dir is not UNSET_CACHE_DIR:
1552 if cache_dir:
1553 cache_dir = os.path.join(self.root_dir, cache_dir)
1554 cache_dir = os.path.abspath(cache_dir)
Andrii Shyshkalov77ce4bd2017-11-27 12:38:18 -08001555
Robert Iannuccia19649b2018-06-29 16:31:45 +00001556 git_cache.Mirror.SetCachePath(cache_dir)
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001557
stuartmorgan@chromium.org18a4f6a2012-11-02 13:37:24 +00001558 if not target_os and config_dict.get('target_os_only', False):
1559 raise gclient_utils.Error('Can\'t use target_os_only if target_os is '
1560 'not specified')
peter@chromium.org1efccc82012-04-27 16:34:38 +00001561
Tom Andersonc31ae0b2018-02-06 14:48:56 -08001562 if not target_cpu and config_dict.get('target_cpu_only', False):
1563 raise gclient_utils.Error('Can\'t use target_cpu_only if target_cpu is '
1564 'not specified')
1565
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001566 deps_to_add = []
Michael Mossd683d7c2018-06-15 05:05:17 +00001567 for s in config_dict.get('solutions', []):
1568 try:
Michael Moss4e9b50a2018-05-23 22:35:06 -07001569 deps_to_add.append(GitDependency(
Edward Lemure05f18d2018-06-08 17:36:53 +00001570 parent=self,
1571 name=s['name'],
Aravind Vasudevan810598d2022-06-13 21:23:47 +00001572 # Update URL with scheme in protocol_override
1573 url=GitDependency.updateProtocol(
1574 s['url'], s.get('protocol_override', None)),
Edward Lemure05f18d2018-06-08 17:36:53 +00001575 managed=s.get('managed', True),
1576 custom_deps=s.get('custom_deps', {}),
1577 custom_vars=s.get('custom_vars', {}),
1578 custom_hooks=s.get('custom_hooks', []),
1579 deps_file=s.get('deps_file', 'DEPS'),
Michael Mossd683d7c2018-06-15 05:05:17 +00001580 should_process=True,
Edward Lemurfbb06aa2018-06-11 20:43:06 +00001581 should_recurse=True,
Edward Lemure05f18d2018-06-08 17:36:53 +00001582 relative=None,
1583 condition=None,
Aravind Vasudevan5965d3e2022-06-01 21:51:30 +00001584 print_outbuf=True,
Aravind Vasudevan810598d2022-06-13 21:23:47 +00001585 # Pass protocol_override down the tree for child deps to use.
1586 protocol=s.get('protocol_override', None)))
Michael Mossd683d7c2018-06-15 05:05:17 +00001587 except KeyError:
1588 raise gclient_utils.Error('Invalid .gclient file. Solution is '
1589 'incomplete: %s' % s)
Edward Lemur40764b02018-07-20 18:50:29 +00001590 metrics.collector.add(
1591 'project_urls',
1592 [
Edward Lemuraffd4102019-06-05 18:07:49 +00001593 dep.FuzzyMatchUrl(metrics_utils.KNOWN_PROJECT_URLS)
Edward Lemur40764b02018-07-20 18:50:29 +00001594 for dep in deps_to_add
1595 if dep.FuzzyMatchUrl(metrics_utils.KNOWN_PROJECT_URLS)
1596 ]
1597 )
1598
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001599 self.add_dependencies_and_close(deps_to_add, config_dict.get('hooks', []))
1600 logging.info('SetConfig() done')
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001601
1602 def SaveConfig(self):
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001603 gclient_utils.FileWrite(os.path.join(self.root_dir,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001604 self._options.config_filename),
1605 self.config_content)
1606
1607 @staticmethod
1608 def LoadCurrentConfig(options):
Joanna Wang66286612022-06-30 19:59:13 +00001609 # type: (optparse.Values) -> GClient
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001610 """Searches for and loads a .gclient file relative to the current working
Joanna Wang66286612022-06-30 19:59:13 +00001611 dir."""
szager@chromium.orge2e03202012-07-31 18:05:16 +00001612 if options.spec:
1613 client = GClient('.', options)
1614 client.SetConfig(options.spec)
1615 else:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00001616 if options.verbose:
1617 print('Looking for %s starting from %s\n' % (
1618 options.config_filename, os.getcwd()))
Nico Weber09e0b382019-03-11 16:54:07 +00001619 path = gclient_paths.FindGclientRoot(os.getcwd(), options.config_filename)
szager@chromium.orge2e03202012-07-31 18:05:16 +00001620 if not path:
Michael Achenbachb3ce73d2017-10-11 16:41:27 +02001621 if options.verbose:
1622 print('Couldn\'t find configuration file.')
szager@chromium.orge2e03202012-07-31 18:05:16 +00001623 return None
1624 client = GClient(path, options)
1625 client.SetConfig(gclient_utils.FileRead(
1626 os.path.join(path, options.config_filename)))
maruel@chromium.org69392e72011-10-13 22:09:00 +00001627
1628 if (options.revisions and
1629 len(client.dependencies) > 1 and
1630 any('@' not in r for r in options.revisions)):
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001631 print(
1632 ('You must specify the full solution name like --revision %s@%s\n'
1633 'when you have multiple solutions setup in your .gclient file.\n'
1634 'Other solutions present are: %s.') % (
maruel@chromium.org69392e72011-10-13 22:09:00 +00001635 client.dependencies[0].name,
1636 options.revisions[0],
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001637 ', '.join(s.name for s in client.dependencies[1:])),
1638 file=sys.stderr)
Joanna Wang66286612022-06-30 19:59:13 +00001639
1640 if any('@' not in r for r in options.skip_sync_revisions):
1641 raise gclient_utils.Error(
1642 "You must specify the full solution name like --revision src@abc")
1643 skip_sync_names = [rev.split('@')[0] for rev in options.skip_sync_revisions]
1644 sol_names = [s.name for s in client.dependencies]
1645 if any(name not in sol_names for name in skip_sync_names):
1646 raise gclient_utils.Error(
1647 "--skip_sync_revisions are only allowed for solutions.")
1648
maruel@chromium.org15804092010-09-02 17:07:37 +00001649 return client
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001650
nsylvain@google.comefc80932011-05-31 21:27:56 +00001651 def SetDefaultConfig(self, solution_name, deps_file, solution_url,
Robert Iannuccia19649b2018-06-29 16:31:45 +00001652 managed=True, cache_dir=UNSET_CACHE_DIR,
1653 custom_vars=None):
1654 text = self.DEFAULT_CLIENT_FILE_TEXT
1655 format_dict = {
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001656 'solution_name': solution_name,
1657 'solution_url': solution_url,
nsylvain@google.comefc80932011-05-31 21:27:56 +00001658 'deps_file': deps_file,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001659 'managed': managed,
Paweł Hajdan, Jr3ba2a7c2017-10-04 19:24:46 +02001660 'custom_vars': custom_vars or {},
Robert Iannuccia19649b2018-06-29 16:31:45 +00001661 }
1662
1663 if cache_dir is not UNSET_CACHE_DIR:
1664 text += self.DEFAULT_CLIENT_CACHE_DIR_TEXT
1665 format_dict['cache_dir'] = cache_dir
1666
1667 self.SetConfig(text % format_dict)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001668
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001669 def _SaveEntries(self):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001670 """Creates a .gclient_entries file to record the list of unique checkouts.
1671
1672 The .gclient_entries file lives in the same directory as .gclient.
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001673 """
1674 # Sometimes pprint.pformat will use {', sometimes it'll use { ' ... It
1675 # makes testing a bit too fun.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001676 result = 'entries = {\n'
Michael Mossd683d7c2018-06-15 05:05:17 +00001677 for entry in self.root.subtree(False):
agabled437d762016-10-17 09:35:11 -07001678 result += ' %s: %s,\n' % (pprint.pformat(entry.name),
Edward Lemure7273d22018-05-10 19:13:51 -04001679 pprint.pformat(entry.url))
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001680 result += '}\n'
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001681 file_path = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org1333cb32011-10-04 23:40:16 +00001682 logging.debug(result)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001683 gclient_utils.FileWrite(file_path, result)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001684
1685 def _ReadEntries(self):
1686 """Read the .gclient_entries file for the given client.
1687
1688 Returns:
1689 A sequence of solution names, which will be empty if there is the
1690 entries file hasn't been created yet.
1691 """
1692 scope = {}
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001693 filename = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001694 if not os.path.exists(filename):
maruel@chromium.org73e21142010-07-05 13:32:01 +00001695 return {}
maruel@chromium.org5990f9d2010-07-07 18:02:58 +00001696 try:
1697 exec(gclient_utils.FileRead(filename), scope)
vapier@chromium.orga81a56e2015-11-11 07:56:13 +00001698 except SyntaxError as e:
maruel@chromium.org5990f9d2010-07-07 18:02:58 +00001699 gclient_utils.SyntaxErrorToError(filename, e)
Aaron Gable3721ee92017-04-03 14:53:14 -07001700 return scope.get('entries', {})
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001701
Joanna Wang66286612022-06-30 19:59:13 +00001702 def _EnforceSkipSyncRevisions(self, patch_refs):
1703 # type: (Mapping[str, str]) -> Mapping[str, str]
1704 """Checks for and enforces revisions for skipping deps syncing."""
1705 if not self._options.skip_sync_revisions:
1706 return {}
1707
1708 # Current `self.dependencies` only contain solutions. If a patch_ref is
1709 # not for a solution, then it is for a solution's dependency or recursed
1710 # dependency which we cannot support with skip_sync_revisions.
1711 if patch_refs:
1712 unclaimed_prs = []
1713 candidates = []
1714 for dep in self.dependencies:
1715 origin, _ = gclient_utils.SplitUrlRevision(dep.url)
1716 candidates.extend([origin, dep.name])
1717 for patch_repo in patch_refs:
1718 if not gclient_utils.FuzzyMatchRepo(patch_repo, candidates):
1719 unclaimed_prs.append(patch_repo)
1720 if unclaimed_prs:
1721 print(
1722 'Ignoring all --skip-sync-revisions. It cannot be used when there '
1723 'are --patch-refs flags for non-solution dependencies. To skip '
1724 'syncing remove patch_refs for: \n%s' % '\n'.join(unclaimed_prs))
1725 return {}
1726
1727 # We cannot skip syncing if there are custom_vars that differ from the
1728 # previous run's custom_vars.
1729 previous_custom_vars = json.loads(os.environ.get(PREVIOUS_CUSTOM_VARS,
1730 '{}'))
1731 cvs_by_name = {s.name: s.custom_vars for s in self.dependencies}
1732 skip_sync_revisions = {}
1733 for revision in self._options.skip_sync_revisions:
1734 name, rev = revision.split('@', 1)
1735 previous_vars = previous_custom_vars.get(name, {})
1736 if previous_vars == cvs_by_name.get(name):
1737 skip_sync_revisions[name] = rev
1738 else:
1739 print('--skip-sync-revisions cannot be used for solutions where '
1740 'custom_vars is different from custom_vars of the last run on '
1741 'this machine.\nRemoving skip_sync_revision for:\n'
1742 'solution: %s, current: %r, previous: %r.' %
1743 (name, cvs_by_name.get(name), previous_vars))
1744 return skip_sync_revisions
1745
1746 # TODO(crbug.com/1340695): Remove handling revisions without '@'.
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001747 def _EnforceRevisions(self):
maruel@chromium.org918a9ae2010-05-28 15:50:30 +00001748 """Checks for revision overrides."""
1749 revision_overrides = {}
smutae7ea312016-07-18 11:59:41 -07001750 if self._options.head:
1751 return revision_overrides
joi@chromium.org792ea882010-11-10 02:37:27 +00001752 if not self._options.revisions:
maruel@chromium.org307d1792010-05-31 20:03:13 +00001753 return revision_overrides
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001754 solutions_names = [s.name for s in self.dependencies]
smutae7ea312016-07-18 11:59:41 -07001755 index = 0
1756 for revision in self._options.revisions:
1757 if not '@' in revision:
maruel@chromium.org307d1792010-05-31 20:03:13 +00001758 # Support for --revision 123
smutae7ea312016-07-18 11:59:41 -07001759 revision = '%s@%s' % (solutions_names[index], revision)
1760 name, rev = revision.split('@', 1)
szager@chromium.org4ad264b2014-05-20 04:43:47 +00001761 revision_overrides[name] = rev
smutae7ea312016-07-18 11:59:41 -07001762 index += 1
maruel@chromium.org918a9ae2010-05-28 15:50:30 +00001763 return revision_overrides
1764
Edward Lemur6a4e31b2018-08-10 19:59:02 +00001765 def _EnforcePatchRefsAndBranches(self):
Joanna Wang66286612022-06-30 19:59:13 +00001766 # type: () -> Tuple[Mapping[str, str], Mapping[str, str]]
Edward Lesmesc621b212018-03-21 20:26:56 -04001767 """Checks for patch refs."""
1768 patch_refs = {}
Edward Lemur6a4e31b2018-08-10 19:59:02 +00001769 target_branches = {}
Edward Lesmesc621b212018-03-21 20:26:56 -04001770 if not self._options.patch_refs:
Edward Lemur6a4e31b2018-08-10 19:59:02 +00001771 return patch_refs, target_branches
Edward Lesmesc621b212018-03-21 20:26:56 -04001772 for given_patch_ref in self._options.patch_refs:
1773 patch_repo, _, patch_ref = given_patch_ref.partition('@')
Edward Lemur4c5c8ab2019-06-07 15:58:13 +00001774 if not patch_repo or not patch_ref or ':' not in patch_ref:
Edward Lesmesc621b212018-03-21 20:26:56 -04001775 raise gclient_utils.Error(
1776 'Wrong revision format: %s should be of the form '
Edward Lemur4c5c8ab2019-06-07 15:58:13 +00001777 'patch_repo@target_branch:patch_ref.' % given_patch_ref)
1778 target_branch, _, patch_ref = patch_ref.partition(':')
1779 target_branches[patch_repo] = target_branch
Edward Lesmesc621b212018-03-21 20:26:56 -04001780 patch_refs[patch_repo] = patch_ref
Edward Lemur6a4e31b2018-08-10 19:59:02 +00001781 return patch_refs, target_branches
Edward Lesmesc621b212018-03-21 20:26:56 -04001782
Edward Lemur5b1fa942018-10-04 23:22:09 +00001783 def _RemoveUnversionedGitDirs(self):
1784 """Remove directories that are no longer part of the checkout.
1785
1786 Notify the user if there is an orphaned entry in their working copy.
1787 Only delete the directory if there are no changes in it, and
1788 delete_unversioned_trees is set to true.
1789 """
1790
1791 entries = [i.name for i in self.root.subtree(False) if i.url]
1792 full_entries = [os.path.join(self.root_dir, e.replace('/', os.path.sep))
1793 for e in entries]
1794
Marc-Antoine Ruel8e57b4b2019-10-11 01:01:36 +00001795 for entry, prev_url in self._ReadEntries().items():
Edward Lemur5b1fa942018-10-04 23:22:09 +00001796 if not prev_url:
1797 # entry must have been overridden via .gclient custom_deps
1798 continue
1799 # Fix path separator on Windows.
1800 entry_fixed = entry.replace('/', os.path.sep)
1801 e_dir = os.path.join(self.root_dir, entry_fixed)
1802 # Use entry and not entry_fixed there.
1803 if (entry not in entries and
1804 (not any(path.startswith(entry + '/') for path in entries)) and
1805 os.path.exists(e_dir)):
1806 # The entry has been removed from DEPS.
1807 scm = gclient_scm.GitWrapper(
1808 prev_url, self.root_dir, entry_fixed, self.outbuf)
1809
1810 # Check to see if this directory is now part of a higher-up checkout.
1811 scm_root = None
1812 try:
1813 scm_root = gclient_scm.scm.GIT.GetCheckoutRoot(scm.checkout_path)
1814 except subprocess2.CalledProcessError:
1815 pass
1816 if not scm_root:
1817 logging.warning('Could not find checkout root for %s. Unable to '
1818 'determine whether it is part of a higher-level '
1819 'checkout, so not removing.' % entry)
1820 continue
1821
1822 # This is to handle the case of third_party/WebKit migrating from
1823 # being a DEPS entry to being part of the main project.
1824 # If the subproject is a Git project, we need to remove its .git
1825 # folder. Otherwise git operations on that folder will have different
1826 # effects depending on the current working directory.
1827 if os.path.abspath(scm_root) == os.path.abspath(e_dir):
1828 e_par_dir = os.path.join(e_dir, os.pardir)
1829 if gclient_scm.scm.GIT.IsInsideWorkTree(e_par_dir):
1830 par_scm_root = gclient_scm.scm.GIT.GetCheckoutRoot(e_par_dir)
1831 # rel_e_dir : relative path of entry w.r.t. its parent repo.
1832 rel_e_dir = os.path.relpath(e_dir, par_scm_root)
1833 if gclient_scm.scm.GIT.IsDirectoryVersioned(
1834 par_scm_root, rel_e_dir):
1835 save_dir = scm.GetGitBackupDirPath()
1836 # Remove any eventual stale backup dir for the same project.
1837 if os.path.exists(save_dir):
1838 gclient_utils.rmtree(save_dir)
1839 os.rename(os.path.join(e_dir, '.git'), save_dir)
1840 # When switching between the two states (entry/ is a subproject
1841 # -> entry/ is part of the outer project), it is very likely
1842 # that some files are changed in the checkout, unless we are
1843 # jumping *exactly* across the commit which changed just DEPS.
1844 # In such case we want to cleanup any eventual stale files
1845 # (coming from the old subproject) in order to end up with a
1846 # clean checkout.
1847 gclient_scm.scm.GIT.CleanupDir(par_scm_root, rel_e_dir)
1848 assert not os.path.exists(os.path.join(e_dir, '.git'))
Raul Tambre80ee78e2019-05-06 22:41:05 +00001849 print('\nWARNING: \'%s\' has been moved from DEPS to a higher '
1850 'level checkout. The git folder containing all the local'
1851 ' branches has been saved to %s.\n'
1852 'If you don\'t care about its state you can safely '
1853 'remove that folder to free up space.' % (entry, save_dir))
Edward Lemur5b1fa942018-10-04 23:22:09 +00001854 continue
1855
1856 if scm_root in full_entries:
1857 logging.info('%s is part of a higher level checkout, not removing',
1858 scm.GetCheckoutRoot())
1859 continue
1860
1861 file_list = []
1862 scm.status(self._options, [], file_list)
1863 modified_files = file_list != []
1864 if (not self._options.delete_unversioned_trees or
1865 (modified_files and not self._options.force)):
1866 # There are modified files in this entry. Keep warning until
1867 # removed.
Henrique Ferreiroe72279d2019-04-17 12:01:50 +00001868 self.add_dependency(
1869 GitDependency(
1870 parent=self,
1871 name=entry,
Aravind Vasudevan810598d2022-06-13 21:23:47 +00001872 # Update URL with scheme in protocol_override
Aravind Vasudevan5965d3e2022-06-01 21:51:30 +00001873 url=GitDependency.updateProtocol(prev_url, self.protocol),
Henrique Ferreiroe72279d2019-04-17 12:01:50 +00001874 managed=False,
1875 custom_deps={},
1876 custom_vars={},
1877 custom_hooks=[],
1878 deps_file=None,
1879 should_process=True,
1880 should_recurse=False,
1881 relative=None,
Aravind Vasudevan5965d3e2022-06-01 21:51:30 +00001882 condition=None,
1883 protocol=self.protocol))
Anthony Politobb457342019-11-15 22:26:01 +00001884 if modified_files and self._options.delete_unversioned_trees:
1885 print('\nWARNING: \'%s\' is no longer part of this client.\n'
1886 'Despite running \'gclient sync -D\' no action was taken '
1887 'as there are modifications.\nIt is recommended you revert '
1888 'all changes or run \'gclient sync -D --force\' next '
1889 'time.' % entry_fixed)
1890 else:
1891 print('\nWARNING: \'%s\' is no longer part of this client.\n'
1892 'It is recommended that you manually remove it or use '
1893 '\'gclient sync -D\' next time.' % entry_fixed)
Edward Lemur5b1fa942018-10-04 23:22:09 +00001894 else:
1895 # Delete the entry
1896 print('\n________ deleting \'%s\' in \'%s\'' % (
1897 entry_fixed, self.root_dir))
1898 gclient_utils.rmtree(e_dir)
1899 # record the current list of entries for next time
1900 self._SaveEntries()
1901
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001902 def RunOnDeps(self, command, args, ignore_requirements=False, progress=True):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001903 """Runs a command on each dependency in a client and its dependencies.
1904
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001905 Args:
1906 command: The command to use (e.g., 'status' or 'diff')
1907 args: list of str - extra arguments to add to the command line.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001908 """
Michael Mossd683d7c2018-06-15 05:05:17 +00001909 if not self.dependencies:
1910 raise gclient_utils.Error('No solution specified')
1911
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001912 revision_overrides = {}
Edward Lesmesc621b212018-03-21 20:26:56 -04001913 patch_refs = {}
Edward Lemur6a4e31b2018-08-10 19:59:02 +00001914 target_branches = {}
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001915 # It's unnecessary to check for revision overrides for 'recurse'.
1916 # Save a few seconds by not calling _EnforceRevisions() in that case.
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +02001917 if command not in ('diff', 'recurse', 'runhooks', 'status', 'revert',
1918 'validate'):
szager@chromium.org5273b8a2014-08-21 15:10:10 +00001919 self._CheckConfig()
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001920 revision_overrides = self._EnforceRevisions()
Edward Lesmesc621b212018-03-21 20:26:56 -04001921
1922 if command == 'update':
Edward Lemur6a4e31b2018-08-10 19:59:02 +00001923 patch_refs, target_branches = self._EnforcePatchRefsAndBranches()
Joanna Wang66286612022-06-30 19:59:13 +00001924 # TODO(crbug.com/1339472): Pass skip_sync_revisions to flush()
1925 _skip_sync_revisions = self._EnforceSkipSyncRevisions(patch_refs)
1926
Daniel Chenga21b5b32017-10-19 20:07:48 +00001927 # Disable progress for non-tty stdout.
Daniel Chenga0c5f082017-10-19 13:35:19 -07001928 should_show_progress = (
1929 setup_color.IS_TTY and not self._options.verbose and progress)
1930 pm = None
1931 if should_show_progress:
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001932 if command in ('update', 'revert'):
1933 pm = Progress('Syncing projects', 1)
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +02001934 elif command in ('recurse', 'validate'):
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001935 pm = Progress(' '.join(args), 1)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001936 work_queue = gclient_utils.ExecutionQueue(
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001937 self._options.jobs, pm, ignore_requirements=ignore_requirements,
1938 verbose=self._options.verbose)
maruel@chromium.org049bced2010-08-12 13:37:20 +00001939 for s in self.dependencies:
Michael Mossd683d7c2018-06-15 05:05:17 +00001940 if s.should_process:
1941 work_queue.enqueue(s)
Edward Lesmesc621b212018-03-21 20:26:56 -04001942 work_queue.flush(revision_overrides, command, args, options=self._options,
Edward Lemur6a4e31b2018-08-10 19:59:02 +00001943 patch_refs=patch_refs, target_branches=target_branches)
Edward Lesmesc621b212018-03-21 20:26:56 -04001944
szager@chromium.org4ad264b2014-05-20 04:43:47 +00001945 if revision_overrides:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001946 print('Please fix your script, having invalid --revision flags will soon '
Edward Lesmesc621b212018-03-21 20:26:56 -04001947 'be considered an error.', file=sys.stderr)
1948
1949 if patch_refs:
1950 raise gclient_utils.Error(
1951 'The following --patch-ref flags were not used. Please fix it:\n%s' %
1952 ('\n'.join(
1953 patch_repo + '@' + patch_ref
Marc-Antoine Ruel8e57b4b2019-10-11 01:01:36 +00001954 for patch_repo, patch_ref in patch_refs.items())))
piman@chromium.org6f363722010-04-27 00:41:09 +00001955
Dirk Pranke9f20d022017-10-11 18:36:54 -07001956 # Once all the dependencies have been processed, it's now safe to write
Michael Moss848c86e2018-05-03 16:05:50 -07001957 # out the gn_args_file and run the hooks.
Dirk Pranke9f20d022017-10-11 18:36:54 -07001958 if command == 'update':
Ergün Erdoğmuş28190a22022-06-22 08:50:54 +00001959 for dependency in self.dependencies:
1960 gn_args_dep = dependency
1961 if gn_args_dep._gn_args_from:
1962 deps_map = {dep.name: dep for dep in gn_args_dep.dependencies}
1963 gn_args_dep = deps_map.get(gn_args_dep._gn_args_from)
1964 if gn_args_dep and gn_args_dep.HasGNArgsFile():
1965 gn_args_dep.WriteGNArgsFile()
Dirk Pranke9f20d022017-10-11 18:36:54 -07001966
Edward Lemur5b1fa942018-10-04 23:22:09 +00001967 self._RemoveUnversionedGitDirs()
Edward Lemur647e1e72018-09-19 18:15:29 +00001968
1969 # Sync CIPD dependencies once removed deps are deleted. In case a git
1970 # dependency was moved to CIPD, we want to remove the old git directory
1971 # first and then sync the CIPD dep.
1972 if self._cipd_root:
1973 self._cipd_root.run(command)
1974
Edward Lemur5b1fa942018-10-04 23:22:09 +00001975 if not self._options.nohooks:
1976 if should_show_progress:
1977 pm = Progress('Running hooks', 1)
1978 self.RunHooksRecursively(self._options, pm)
1979
Joanna Wang66286612022-06-30 19:59:13 +00001980 # Store custom_vars on disk to compare in the next run.
1981 custom_vars = {}
1982 for dep in self.dependencies:
1983 custom_vars[dep.name] = dep.custom_vars
1984 os.environ[PREVIOUS_CUSTOM_VARS] = json.dumps(sorted(custom_vars))
Edward Lemur5b1fa942018-10-04 23:22:09 +00001985
maruel@chromium.org17cdf762010-05-28 17:30:52 +00001986 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001987
1988 def PrintRevInfo(self):
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001989 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +00001990 raise gclient_utils.Error('No solution specified')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001991 # Load all the settings.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001992 work_queue = gclient_utils.ExecutionQueue(
1993 self._options.jobs, None, False, verbose=self._options.verbose)
maruel@chromium.org049bced2010-08-12 13:37:20 +00001994 for s in self.dependencies:
Michael Mossd683d7c2018-06-15 05:05:17 +00001995 if s.should_process:
1996 work_queue.enqueue(s)
Edward Lemur6a4e31b2018-08-10 19:59:02 +00001997 work_queue.flush({}, None, [], options=self._options, patch_refs=None,
1998 target_branches=None)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001999
Michael Mossd683d7c2018-06-15 05:05:17 +00002000 def ShouldPrintRevision(dep):
Edward Lesmesbb16e332018-03-30 17:54:51 -04002001 return (not self._options.filter
Edward Lemure7273d22018-05-10 19:13:51 -04002002 or dep.FuzzyMatchUrl(self._options.filter))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002003
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00002004 if self._options.snapshot:
Michael Mossd683d7c2018-06-15 05:05:17 +00002005 json_output = []
2006 # First level at .gclient
2007 for d in self.dependencies:
2008 entries = {}
2009 def GrabDeps(dep):
2010 """Recursively grab dependencies."""
Aravind Vasudevanc5f0cbb2022-01-24 23:56:57 +00002011 for rec_d in dep.dependencies:
2012 rec_d.PinToActualRevision()
2013 if ShouldPrintRevision(rec_d):
2014 entries[rec_d.name] = rec_d.url
2015 GrabDeps(rec_d)
2016
Michael Mossd683d7c2018-06-15 05:05:17 +00002017 GrabDeps(d)
2018 json_output.append({
2019 'name': d.name,
2020 'solution_url': d.url,
2021 'deps_file': d.deps_file,
2022 'managed': d.managed,
2023 'custom_deps': entries,
2024 })
2025 if self._options.output_json == '-':
2026 print(json.dumps(json_output, indent=2, separators=(',', ': ')))
2027 elif self._options.output_json:
2028 with open(self._options.output_json, 'w') as f:
2029 json.dump(json_output, f)
2030 else:
2031 # Print the snapshot configuration file
2032 print(self.DEFAULT_SNAPSHOT_FILE_TEXT % {
2033 'solution_list': pprint.pformat(json_output, indent=2),
2034 })
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +00002035 else:
Michael Mossd683d7c2018-06-15 05:05:17 +00002036 entries = {}
2037 for d in self.root.subtree(False):
2038 if self._options.actual:
2039 d.PinToActualRevision()
2040 if ShouldPrintRevision(d):
2041 entries[d.name] = d.url
2042 if self._options.output_json:
2043 json_output = {
2044 name: {
2045 'url': rev.split('@')[0] if rev else None,
2046 'rev': rev.split('@')[1] if rev and '@' in rev else None,
2047 }
Marc-Antoine Ruel8e57b4b2019-10-11 01:01:36 +00002048 for name, rev in entries.items()
Michael Mossd683d7c2018-06-15 05:05:17 +00002049 }
2050 if self._options.output_json == '-':
2051 print(json.dumps(json_output, indent=2, separators=(',', ': ')))
2052 else:
2053 with open(self._options.output_json, 'w') as f:
2054 json.dump(json_output, f)
2055 else:
2056 keys = sorted(entries.keys())
2057 for x in keys:
2058 print('%s: %s' % (x, entries[x]))
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +00002059 logging.info(str(self))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002060
Edward Lemure05f18d2018-06-08 17:36:53 +00002061 def ParseDepsFile(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00002062 """No DEPS to parse for a .gclient file."""
maruel@chromium.org049bced2010-08-12 13:37:20 +00002063 raise gclient_utils.Error('Internal error')
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00002064
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002065 def PrintLocationAndContents(self):
2066 # Print out the .gclient file. This is longer than if we just printed the
2067 # client dict, but more legible, and it might contain helpful comments.
2068 print('Loaded .gclient config in %s:\n%s' % (
2069 self.root_dir, self.config_content))
2070
John Budorickd3ba72b2018-03-20 12:27:42 -07002071 def GetCipdRoot(self):
2072 if not self._cipd_root:
2073 self._cipd_root = gclient_scm.CipdRoot(
2074 self.root_dir,
2075 # TODO(jbudorick): Support other service URLs as necessary.
2076 # Service URLs should be constant over the scope of a cipd
2077 # root, so a var per DEPS file specifying the service URL
2078 # should suffice.
2079 'https://chrome-infra-packages.appspot.com')
2080 return self._cipd_root
2081
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00002082 @property
maruel@chromium.org75a59272010-06-11 22:34:03 +00002083 def root_dir(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00002084 """Root directory of gclient checkout."""
maruel@chromium.org75a59272010-06-11 22:34:03 +00002085 return self._root_dir
2086
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00002087 @property
maruel@chromium.org271375b2010-06-23 19:17:38 +00002088 def enforced_os(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00002089 """What deps_os entries that are to be parsed."""
maruel@chromium.org271375b2010-06-23 19:17:38 +00002090 return self._enforced_os
2091
maruel@chromium.org68988972011-09-20 14:11:42 +00002092 @property
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +00002093 def target_os(self):
2094 return self._enforced_os
2095
Tom Andersonc31ae0b2018-02-06 14:48:56 -08002096 @property
2097 def target_cpu(self):
2098 return self._enforced_cpu
2099
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002100
John Budorick0f7b2002018-01-19 15:46:17 -08002101class CipdDependency(Dependency):
2102 """A Dependency object that represents a single CIPD package."""
2103
Michael Mossd683d7c2018-06-15 05:05:17 +00002104 def __init__(
2105 self, parent, name, dep_value, cipd_root,
2106 custom_vars, should_process, relative, condition):
John Budorick0f7b2002018-01-19 15:46:17 -08002107 package = dep_value['package']
2108 version = dep_value['version']
2109 url = urlparse.urljoin(
2110 cipd_root.service_url, '%s@%s' % (package, version))
2111 super(CipdDependency, self).__init__(
Edward Lemure05f18d2018-06-08 17:36:53 +00002112 parent=parent,
2113 name=name + ':' + package,
2114 url=url,
2115 managed=None,
2116 custom_deps=None,
2117 custom_vars=custom_vars,
2118 custom_hooks=None,
2119 deps_file=None,
Michael Mossd683d7c2018-06-15 05:05:17 +00002120 should_process=should_process,
Edward Lemurfbb06aa2018-06-11 20:43:06 +00002121 should_recurse=False,
Edward Lemure05f18d2018-06-08 17:36:53 +00002122 relative=relative,
2123 condition=condition)
John Budorickd3ba72b2018-03-20 12:27:42 -07002124 self._cipd_package = None
John Budorick0f7b2002018-01-19 15:46:17 -08002125 self._cipd_root = cipd_root
John Budorick4099daa2018-06-21 19:22:10 +00002126 # CIPD wants /-separated paths, even on Windows.
2127 native_subdir_path = os.path.relpath(
Shenghua Zhang6f830312018-02-26 11:45:07 -08002128 os.path.join(self.root.root_dir, name), cipd_root.root_dir)
John Budorick4099daa2018-06-21 19:22:10 +00002129 self._cipd_subdir = posixpath.join(*native_subdir_path.split(os.sep))
John Budorickd3ba72b2018-03-20 12:27:42 -07002130 self._package_name = package
2131 self._package_version = version
2132
2133 #override
Edward Lesmesc621b212018-03-21 20:26:56 -04002134 def run(self, revision_overrides, command, args, work_queue, options,
Edward Lemur6a4e31b2018-08-10 19:59:02 +00002135 patch_refs, target_branches):
John Budorickd3ba72b2018-03-20 12:27:42 -07002136 """Runs |command| then parse the DEPS file."""
2137 logging.info('CipdDependency(%s).run()' % self.name)
Michael Mossd683d7c2018-06-15 05:05:17 +00002138 if not self.should_process:
2139 return
John Budorickd3ba72b2018-03-20 12:27:42 -07002140 self._CreatePackageIfNecessary()
2141 super(CipdDependency, self).run(revision_overrides, command, args,
Edward Lemur6a4e31b2018-08-10 19:59:02 +00002142 work_queue, options, patch_refs,
2143 target_branches)
John Budorickd3ba72b2018-03-20 12:27:42 -07002144
2145 def _CreatePackageIfNecessary(self):
2146 # We lazily create the CIPD package to make sure that only packages
2147 # that we want (as opposed to all packages defined in all DEPS files
2148 # we parse) get added to the root and subsequently ensured.
2149 if not self._cipd_package:
2150 self._cipd_package = self._cipd_root.add_package(
2151 self._cipd_subdir, self._package_name, self._package_version)
John Budorick0f7b2002018-01-19 15:46:17 -08002152
Edward Lemure05f18d2018-06-08 17:36:53 +00002153 def ParseDepsFile(self):
John Budorick0f7b2002018-01-19 15:46:17 -08002154 """CIPD dependencies are not currently allowed to have nested deps."""
2155 self.add_dependencies_and_close([], [])
2156
2157 #override
Shenghua Zhang6f830312018-02-26 11:45:07 -08002158 def verify_validity(self):
2159 """CIPD dependencies allow duplicate name for packages in same directory."""
2160 logging.info('Dependency(%s).verify_validity()' % self.name)
2161 return True
2162
2163 #override
Edward Lemurbabd0982018-05-11 13:32:37 -04002164 def GetScmName(self):
John Budorick0f7b2002018-01-19 15:46:17 -08002165 """Always 'cipd'."""
John Budorick0f7b2002018-01-19 15:46:17 -08002166 return 'cipd'
2167
2168 #override
Edward Lemurbabd0982018-05-11 13:32:37 -04002169 def CreateSCM(self, out_cb=None):
John Budorick0f7b2002018-01-19 15:46:17 -08002170 """Create a Wrapper instance suitable for handling this CIPD dependency."""
John Budorickd3ba72b2018-03-20 12:27:42 -07002171 self._CreatePackageIfNecessary()
John Budorick0f7b2002018-01-19 15:46:17 -08002172 return gclient_scm.CipdWrapper(
Edward Lemurbabd0982018-05-11 13:32:37 -04002173 self.url, self.root.root_dir, self.name, self.outbuf, out_cb,
2174 root=self._cipd_root, package=self._cipd_package)
John Budorick0f7b2002018-01-19 15:46:17 -08002175
Edward Lemure4e15042018-06-28 18:07:00 +00002176 def hierarchy(self, include_url=False):
2177 return self.parent.hierarchy(include_url) + ' -> ' + self._cipd_subdir
2178
John Budorick0f7b2002018-01-19 15:46:17 -08002179 def ToLines(self):
2180 """Return a list of lines representing this in a DEPS file."""
John Budorickc35aba52018-06-28 20:57:03 +00002181 def escape_cipd_var(package):
2182 return package.replace('{', '{{').replace('}', '}}')
2183
John Budorick0f7b2002018-01-19 15:46:17 -08002184 s = []
John Budorickd3ba72b2018-03-20 12:27:42 -07002185 self._CreatePackageIfNecessary()
John Budorick0f7b2002018-01-19 15:46:17 -08002186 if self._cipd_package.authority_for_subdir:
2187 condition_part = ([' "condition": %r,' % self.condition]
2188 if self.condition else [])
2189 s.extend([
Michael Moss4e9b50a2018-05-23 22:35:06 -07002190 ' # %s' % self.hierarchy(include_url=False),
John Budorickd3ba72b2018-03-20 12:27:42 -07002191 ' "%s": {' % (self.name.split(':')[0],),
John Budorick0f7b2002018-01-19 15:46:17 -08002192 ' "packages": [',
2193 ])
John Budorick4099daa2018-06-21 19:22:10 +00002194 for p in sorted(
2195 self._cipd_root.packages(self._cipd_subdir),
Edward Lemur26a8b9f2019-08-15 20:46:44 +00002196 key=lambda x: x.name):
John Budorick0f7b2002018-01-19 15:46:17 -08002197 s.extend([
John Budorick64e33cb2018-02-20 09:40:30 -08002198 ' {',
John Budorickc35aba52018-06-28 20:57:03 +00002199 ' "package": "%s",' % escape_cipd_var(p.name),
John Budorick64e33cb2018-02-20 09:40:30 -08002200 ' "version": "%s",' % p.version,
2201 ' },',
John Budorick0f7b2002018-01-19 15:46:17 -08002202 ])
John Budorickd3ba72b2018-03-20 12:27:42 -07002203
John Budorick0f7b2002018-01-19 15:46:17 -08002204 s.extend([
2205 ' ],',
2206 ' "dep_type": "cipd",',
2207 ] + condition_part + [
2208 ' },',
2209 '',
2210 ])
2211 return s
2212
2213
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002214#### gclient commands.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002215
2216
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002217@subcommand.usage('[command] [args ...]')
Edward Lemur3298e7b2018-07-17 18:21:27 +00002218@metrics.collector.collect_metrics('gclient recurse')
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00002219def CMDrecurse(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002220 """Operates [command args ...] on all the dependencies.
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00002221
2222 Runs a shell command on all entries.
qyearsley12fa6ff2016-08-24 09:18:40 -07002223 Sets GCLIENT_DEP_PATH environment variable as the dep's relative location to
ilevy@chromium.org37116242012-11-28 01:32:48 +00002224 root directory of the checkout.
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00002225 """
2226 # Stop parsing at the first non-arg so that these go through to the command
2227 parser.disable_interspersed_args()
2228 parser.add_option('-s', '--scm', action='append', default=[],
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00002229 help='Choose scm types to operate upon.')
maruel@chromium.org288054d2012-03-05 00:43:07 +00002230 parser.add_option('-i', '--ignore', action='store_true',
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00002231 help='Ignore non-zero return codes from subcommands.')
2232 parser.add_option('--prepend-dir', action='store_true',
2233 help='Prepend relative dir for use with git <cmd> --null.')
2234 parser.add_option('--no-progress', action='store_true',
2235 help='Disable progress bar that shows sub-command updates')
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00002236 options, args = parser.parse_args(args)
maruel@chromium.org45e9f2d2010-10-18 13:33:46 +00002237 if not args:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002238 print('Need to supply a command!', file=sys.stderr)
maruel@chromium.org45e9f2d2010-10-18 13:33:46 +00002239 return 1
maruel@chromium.org78cba522010-10-18 13:32:05 +00002240 root_and_entries = gclient_utils.GetGClientRootAndEntries()
2241 if not root_and_entries:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002242 print(
maruel@chromium.org78cba522010-10-18 13:32:05 +00002243 'You need to run gclient sync at least once to use \'recurse\'.\n'
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002244 'This is because .gclient_entries needs to exist and be up to date.',
2245 file=sys.stderr)
maruel@chromium.org78cba522010-10-18 13:32:05 +00002246 return 1
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00002247
2248 # Normalize options.scm to a set()
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00002249 scm_set = set()
2250 for scm in options.scm:
2251 scm_set.update(scm.split(','))
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00002252 options.scm = scm_set
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00002253
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00002254 options.nohooks = True
2255 client = GClient.LoadCurrentConfig(options)
Marc-Antoine Ruele6e06412017-10-18 13:47:02 -04002256 if not client:
2257 raise gclient_utils.Error('client not configured; see \'gclient config\'')
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00002258 return client.RunOnDeps('recurse', args, ignore_requirements=True,
2259 progress=not options.no_progress)
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00002260
2261
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002262@subcommand.usage('[args ...]')
Edward Lemur3298e7b2018-07-17 18:21:27 +00002263@metrics.collector.collect_metrics('gclient fetch')
davidbarr@chromium.org12f944e2012-03-01 02:18:31 +00002264def CMDfetch(parser, args):
2265 """Fetches upstream commits for all modules.
2266
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002267 Completely git-specific. Simply runs 'git fetch [args ...]' for each module.
2268 """
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00002269 (options, args) = parser.parse_args(args)
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002270 return CMDrecurse(OptionParser(), [
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00002271 '--jobs=%d' % options.jobs, '--scm=git', 'git', 'fetch'] + args)
2272
2273
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002274class Flattener(object):
2275 """Flattens a gclient solution."""
2276
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02002277 def __init__(self, client, pin_all_deps=False):
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002278 """Constructor.
2279
2280 Arguments:
2281 client (GClient): client to flatten
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02002282 pin_all_deps (bool): whether to pin all deps, even if they're not pinned
2283 in DEPS
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002284 """
2285 self._client = client
2286
2287 self._deps_string = None
Paweł Hajdan, Jrfeb01642017-09-12 15:50:46 +02002288 self._deps_files = set()
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002289
2290 self._allowed_hosts = set()
2291 self._deps = {}
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002292 self._hooks = []
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002293 self._pre_deps_hooks = []
Paweł Hajdan, Jrfb022012017-07-06 18:00:08 +02002294 self._vars = {}
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002295
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02002296 self._flatten(pin_all_deps=pin_all_deps)
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002297
2298 @property
2299 def deps_string(self):
2300 assert self._deps_string is not None
2301 return self._deps_string
2302
Paweł Hajdan, Jrfeb01642017-09-12 15:50:46 +02002303 @property
2304 def deps_files(self):
2305 return self._deps_files
2306
Paweł Hajdan, Jr5ec77132017-08-16 19:21:06 +02002307 def _pin_dep(self, dep):
2308 """Pins a dependency to specific full revision sha.
2309
2310 Arguments:
2311 dep (Dependency): dependency to process
2312 """
Michael Mossd683d7c2018-06-15 05:05:17 +00002313 if dep.url is None:
2314 return
2315
Paweł Hajdan, Jr5ec77132017-08-16 19:21:06 +02002316 # Make sure the revision is always fully specified (a hash),
2317 # as opposed to refs or tags which might change. Similarly,
2318 # shortened shas might become ambiguous; make sure to always
2319 # use full one for pinning.
Edward Lemure7273d22018-05-10 19:13:51 -04002320 revision = gclient_utils.SplitUrlRevision(dep.url)[1]
2321 if not revision or not gclient_utils.IsFullGitSha(revision):
2322 dep.PinToActualRevision()
Paweł Hajdan, Jr5ec77132017-08-16 19:21:06 +02002323
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02002324 def _flatten(self, pin_all_deps=False):
2325 """Runs the flattener. Saves resulting DEPS string.
2326
2327 Arguments:
2328 pin_all_deps (bool): whether to pin all deps, even if they're not pinned
2329 in DEPS
2330 """
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002331 for solution in self._client.dependencies:
Paweł Hajdan, Jr11eb7152017-08-10 12:50:11 +02002332 self._add_dep(solution)
Michael Mossd683d7c2018-06-15 05:05:17 +00002333 self._flatten_dep(solution)
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002334
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02002335 if pin_all_deps:
Marc-Antoine Ruel8e57b4b2019-10-11 01:01:36 +00002336 for dep in self._deps.values():
Paweł Hajdan, Jr5ec77132017-08-16 19:21:06 +02002337 self._pin_dep(dep)
Paweł Hajdan, Jr39300ba2017-08-11 16:52:38 +02002338
Paweł Hajdan, Jr76a9d042017-08-18 20:05:41 +02002339 def add_deps_file(dep):
Paweł Hajdan, Jr0870df22017-08-23 17:59:29 +02002340 # Only include DEPS files referenced by recursedeps.
Edward Lemurfbb06aa2018-06-11 20:43:06 +00002341 if not dep.should_recurse:
Paweł Hajdan, Jr0870df22017-08-23 17:59:29 +02002342 return
Paweł Hajdan, Jrfeb01642017-09-12 15:50:46 +02002343 deps_file = dep.deps_file
2344 deps_path = os.path.join(self._client.root_dir, dep.name, deps_file)
Paweł Hajdan, Jr76a9d042017-08-18 20:05:41 +02002345 if not os.path.exists(deps_path):
Paweł Hajdan, Jrfeb01642017-09-12 15:50:46 +02002346 # gclient has a fallback that if deps_file doesn't exist, it'll try
2347 # DEPS. Do the same here.
2348 deps_file = 'DEPS'
2349 deps_path = os.path.join(self._client.root_dir, dep.name, deps_file)
2350 if not os.path.exists(deps_path):
2351 return
Michael Mossd683d7c2018-06-15 05:05:17 +00002352 assert dep.url
Edward Lemure7273d22018-05-10 19:13:51 -04002353 self._deps_files.add((dep.url, deps_file, dep.hierarchy_data()))
Marc-Antoine Ruel8e57b4b2019-10-11 01:01:36 +00002354 for dep in self._deps.values():
Paweł Hajdan, Jr76a9d042017-08-18 20:05:41 +02002355 add_deps_file(dep)
Paweł Hajdan, Jr76a9d042017-08-18 20:05:41 +02002356
Michael Moss848c86e2018-05-03 16:05:50 -07002357 gn_args_dep = self._deps.get(self._client.dependencies[0]._gn_args_from,
2358 self._client.dependencies[0])
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002359 self._deps_string = '\n'.join(
Michael Moss848c86e2018-05-03 16:05:50 -07002360 _GNSettingsToLines(gn_args_dep._gn_args_file, gn_args_dep._gn_args) +
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002361 _AllowedHostsToLines(self._allowed_hosts) +
2362 _DepsToLines(self._deps) +
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002363 _HooksToLines('hooks', self._hooks) +
2364 _HooksToLines('pre_deps_hooks', self._pre_deps_hooks) +
Paweł Hajdan, Jrfb022012017-07-06 18:00:08 +02002365 _VarsToLines(self._vars) +
Paweł Hajdan, Jr76a9d042017-08-18 20:05:41 +02002366 ['# %s, %s' % (url, deps_file)
Michael Mossfe68c912018-03-22 19:19:35 -07002367 for url, deps_file, _ in sorted(self._deps_files)] +
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002368 ['']) # Ensure newline at end of file.
2369
Paweł Hajdan, Jr11eb7152017-08-10 12:50:11 +02002370 def _add_dep(self, dep):
2371 """Helper to add a dependency to flattened DEPS.
2372
2373 Arguments:
2374 dep (Dependency): dependency to add
2375 """
2376 assert dep.name not in self._deps or self._deps.get(dep.name) == dep, (
2377 dep.name, self._deps.get(dep.name))
Michael Mossd683d7c2018-06-15 05:05:17 +00002378 if dep.url:
2379 self._deps[dep.name] = dep
Paweł Hajdan, Jr11eb7152017-08-10 12:50:11 +02002380
Edward Lemur16f4bad2018-05-16 16:53:49 -04002381 def _flatten_dep(self, dep):
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002382 """Visits a dependency in order to flatten it (see CMDflatten).
2383
2384 Arguments:
2385 dep (Dependency): dependency to process
2386 """
Edward Lemur16f4bad2018-05-16 16:53:49 -04002387 logging.debug('_flatten_dep(%s)', dep.name)
Paweł Hajdan, Jradae2a62017-08-18 16:49:57 +02002388
Edward Lemur16f4bad2018-05-16 16:53:49 -04002389 assert dep.deps_parsed, (
2390 "Attempted to flatten %s but it has not been processed." % dep.name)
Paweł Hajdan, Jrc69b32e2017-08-17 18:47:48 +02002391
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002392 self._allowed_hosts.update(dep.allowed_hosts)
2393
Michael Mossce9f17f2018-01-31 13:16:35 -08002394 # Only include vars explicitly listed in the DEPS files or gclient solution,
2395 # not automatic, local overrides (i.e. not all of dep.get_vars()).
Michael Moss4e9b50a2018-05-23 22:35:06 -07002396 hierarchy = dep.hierarchy(include_url=False)
Marc-Antoine Ruel8e57b4b2019-10-11 01:01:36 +00002397 for key, value in dep._vars.items():
Paweł Hajdan, Jrc9353602017-08-02 17:52:08 +02002398 # Make sure there are no conflicting variables. It is fine however
2399 # to use same variable name, as long as the value is consistent.
Takuto Ikuta575872e2019-02-21 15:20:07 +00002400 assert key not in self._vars or self._vars[key][1] == value, (
2401 "dep:%s key:%s value:%s != %s" % (
2402 dep.name, key, value, self._vars[key][1]))
Michael Mossce9f17f2018-01-31 13:16:35 -08002403 self._vars[key] = (hierarchy, value)
2404 # Override explicit custom variables.
Marc-Antoine Ruel8e57b4b2019-10-11 01:01:36 +00002405 for key, value in dep.custom_vars.items():
Michael Mossce9f17f2018-01-31 13:16:35 -08002406 # Do custom_vars that don't correspond to DEPS vars ever make sense? DEPS
2407 # conditionals shouldn't be using vars that aren't also defined in the
2408 # DEPS (presubmit actually disallows this), so any new custom_var must be
2409 # unused in the DEPS, so no need to add it to the flattened output either.
2410 if key not in self._vars:
2411 continue
2412 # Don't "override" existing vars if it's actually the same value.
Aravind Vasudevanc5f0cbb2022-01-24 23:56:57 +00002413 if self._vars[key][1] == value:
Michael Mossce9f17f2018-01-31 13:16:35 -08002414 continue
2415 # Anything else is overriding a default value from the DEPS.
2416 self._vars[key] = (hierarchy + ' [custom_var override]', value)
Paweł Hajdan, Jrfb022012017-07-06 18:00:08 +02002417
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002418 self._pre_deps_hooks.extend([(dep, hook) for hook in dep.pre_deps_hooks])
Edward Lemur16f4bad2018-05-16 16:53:49 -04002419 self._hooks.extend([(dep, hook) for hook in dep.deps_hooks])
Paweł Hajdan, Jradae2a62017-08-18 16:49:57 +02002420
Paweł Hajdan, Jrb0ad16e2017-08-03 15:33:21 +02002421 for sub_dep in dep.dependencies:
Edward Lemur16f4bad2018-05-16 16:53:49 -04002422 self._add_dep(sub_dep)
Paweł Hajdan, Jrb0ad16e2017-08-03 15:33:21 +02002423
Edward Lemurfbb06aa2018-06-11 20:43:06 +00002424 for d in dep.dependencies:
2425 if d.should_recurse:
2426 self._flatten_dep(d)
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002427
2428
Edward Lemur3298e7b2018-07-17 18:21:27 +00002429@metrics.collector.collect_metrics('gclient flatten')
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002430def CMDflatten(parser, args):
2431 """Flattens the solutions into a single DEPS file."""
2432 parser.add_option('--output-deps', help='Path to the output DEPS file')
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02002433 parser.add_option(
Paweł Hajdan, Jrfeb01642017-09-12 15:50:46 +02002434 '--output-deps-files',
2435 help=('Path to the output metadata about DEPS files referenced by '
2436 'recursedeps.'))
2437 parser.add_option(
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02002438 '--pin-all-deps', action='store_true',
2439 help=('Pin all deps, even if not pinned in DEPS. CAVEAT: only does so '
2440 'for checked out deps, NOT deps_os.'))
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002441 options, args = parser.parse_args(args)
2442
2443 options.nohooks = True
Paweł Hajdan, Jr5ec77132017-08-16 19:21:06 +02002444 options.process_all_deps = True
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002445 client = GClient.LoadCurrentConfig(options)
Gavin Makf6b414c2021-01-12 19:10:41 +00002446 if not client:
2447 raise gclient_utils.Error('client not configured; see \'gclient config\'')
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002448
2449 # Only print progress if we're writing to a file. Otherwise, progress updates
2450 # could obscure intended output.
2451 code = client.RunOnDeps('flatten', args, progress=options.output_deps)
2452 if code != 0:
2453 return code
2454
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02002455 flattener = Flattener(client, pin_all_deps=options.pin_all_deps)
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002456
2457 if options.output_deps:
2458 with open(options.output_deps, 'w') as f:
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002459 f.write(flattener.deps_string)
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002460 else:
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002461 print(flattener.deps_string)
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002462
Michael Mossfe68c912018-03-22 19:19:35 -07002463 deps_files = [{'url': d[0], 'deps_file': d[1], 'hierarchy': d[2]}
Paweł Hajdan, Jrfeb01642017-09-12 15:50:46 +02002464 for d in sorted(flattener.deps_files)]
2465 if options.output_deps_files:
2466 with open(options.output_deps_files, 'w') as f:
2467 json.dump(deps_files, f)
2468
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002469 return 0
2470
2471
Paweł Hajdan, Jr3c2aa832017-06-07 20:22:16 +02002472def _GNSettingsToLines(gn_args_file, gn_args):
2473 s = []
2474 if gn_args_file:
2475 s.extend([
2476 'gclient_gn_args_file = "%s"' % gn_args_file,
2477 'gclient_gn_args = %r' % gn_args,
2478 ])
2479 return s
2480
2481
Paweł Hajdan, Jr6014b562017-06-30 17:43:42 +02002482def _AllowedHostsToLines(allowed_hosts):
2483 """Converts |allowed_hosts| set to list of lines for output."""
2484 if not allowed_hosts:
2485 return []
2486 s = ['allowed_hosts = [']
2487 for h in sorted(allowed_hosts):
2488 s.append(' "%s",' % h)
2489 s.extend([']', ''])
2490 return s
2491
2492
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002493def _DepsToLines(deps):
2494 """Converts |deps| dict to list of lines for output."""
Paweł Hajdan, Jr5b593352017-06-29 18:37:45 +02002495 if not deps:
2496 return []
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002497 s = ['deps = {']
Edward Lemuree7b9dd2019-07-20 01:29:08 +00002498 for _, dep in sorted(deps.items()):
John Budorick0f7b2002018-01-19 15:46:17 -08002499 s.extend(dep.ToLines())
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002500 s.extend(['}', ''])
2501 return s
2502
2503
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02002504def _DepsOsToLines(deps_os):
2505 """Converts |deps_os| dict to list of lines for output."""
Paweł Hajdan, Jr5b593352017-06-29 18:37:45 +02002506 if not deps_os:
2507 return []
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02002508 s = ['deps_os = {']
Edward Lemuree7b9dd2019-07-20 01:29:08 +00002509 for dep_os, os_deps in sorted(deps_os.items()):
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02002510 s.append(' "%s": {' % dep_os)
Edward Lemuree7b9dd2019-07-20 01:29:08 +00002511 for name, dep in sorted(os_deps.items()):
Paweł Hajdan, Jr78ce24e2017-10-03 17:09:13 +02002512 condition_part = ([' "condition": %r,' % dep.condition]
Paweł Hajdan, Jrad30de62017-06-26 18:51:58 +02002513 if dep.condition else [])
2514 s.extend([
Michael Moss4e9b50a2018-05-23 22:35:06 -07002515 ' # %s' % dep.hierarchy(include_url=False),
Paweł Hajdan, Jrad30de62017-06-26 18:51:58 +02002516 ' "%s": {' % (name,),
Edward Lemure05f18d2018-06-08 17:36:53 +00002517 ' "url": "%s",' % (dep.url,),
Paweł Hajdan, Jrad30de62017-06-26 18:51:58 +02002518 ] + condition_part + [
2519 ' },',
2520 '',
2521 ])
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02002522 s.extend([' },', ''])
2523 s.extend(['}', ''])
2524 return s
2525
2526
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002527def _HooksToLines(name, hooks):
2528 """Converts |hooks| list to list of lines for output."""
Paweł Hajdan, Jr5b593352017-06-29 18:37:45 +02002529 if not hooks:
2530 return []
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002531 s = ['%s = [' % name]
2532 for dep, hook in hooks:
2533 s.extend([
Michael Moss4e9b50a2018-05-23 22:35:06 -07002534 ' # %s' % dep.hierarchy(include_url=False),
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002535 ' {',
2536 ])
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +02002537 if hook.name is not None:
2538 s.append(' "name": "%s",' % hook.name)
2539 if hook.pattern is not None:
2540 s.append(' "pattern": "%s",' % hook.pattern)
Paweł Hajdan, Jrecf53fe2017-09-29 18:28:49 +02002541 if hook.condition is not None:
Paweł Hajdan, Jr78ce24e2017-10-03 17:09:13 +02002542 s.append(' "condition": %r,' % hook.condition)
Corentin Walleza68660d2018-09-10 17:33:24 +00002543 # Flattened hooks need to be written relative to the root gclient dir
2544 cwd = os.path.relpath(os.path.normpath(hook.effective_cwd))
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002545 s.extend(
Corentin Walleza68660d2018-09-10 17:33:24 +00002546 [' "cwd": "%s",' % cwd] +
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002547 [' "action": ['] +
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +02002548 [' "%s",' % arg for arg in hook.action] +
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002549 [' ]', ' },', '']
2550 )
2551 s.extend([']', ''])
2552 return s
2553
2554
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +02002555def _HooksOsToLines(hooks_os):
2556 """Converts |hooks| list to list of lines for output."""
Paweł Hajdan, Jr5b593352017-06-29 18:37:45 +02002557 if not hooks_os:
2558 return []
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +02002559 s = ['hooks_os = {']
Marc-Antoine Ruel8e57b4b2019-10-11 01:01:36 +00002560 for hook_os, os_hooks in hooks_os.items():
Michael Moss017bcf62017-06-28 15:26:38 -07002561 s.append(' "%s": [' % hook_os)
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +02002562 for dep, hook in os_hooks:
2563 s.extend([
Michael Moss4e9b50a2018-05-23 22:35:06 -07002564 ' # %s' % dep.hierarchy(include_url=False),
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +02002565 ' {',
2566 ])
2567 if hook.name is not None:
2568 s.append(' "name": "%s",' % hook.name)
2569 if hook.pattern is not None:
2570 s.append(' "pattern": "%s",' % hook.pattern)
Paweł Hajdan, Jrecf53fe2017-09-29 18:28:49 +02002571 if hook.condition is not None:
Paweł Hajdan, Jr78ce24e2017-10-03 17:09:13 +02002572 s.append(' "condition": %r,' % hook.condition)
Corentin Walleza68660d2018-09-10 17:33:24 +00002573 # Flattened hooks need to be written relative to the root gclient dir
2574 cwd = os.path.relpath(os.path.normpath(hook.effective_cwd))
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +02002575 s.extend(
Corentin Walleza68660d2018-09-10 17:33:24 +00002576 [' "cwd": "%s",' % cwd] +
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +02002577 [' "action": ['] +
2578 [' "%s",' % arg for arg in hook.action] +
2579 [' ]', ' },', '']
2580 )
Michael Moss017bcf62017-06-28 15:26:38 -07002581 s.extend([' ],', ''])
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +02002582 s.extend(['}', ''])
2583 return s
2584
2585
Paweł Hajdan, Jrfb022012017-07-06 18:00:08 +02002586def _VarsToLines(variables):
2587 """Converts |variables| dict to list of lines for output."""
2588 if not variables:
2589 return []
2590 s = ['vars = {']
Edward Lemuree7b9dd2019-07-20 01:29:08 +00002591 for key, tup in sorted(variables.items()):
Michael Mossce9f17f2018-01-31 13:16:35 -08002592 hierarchy, value = tup
Paweł Hajdan, Jrfb022012017-07-06 18:00:08 +02002593 s.extend([
Michael Mossce9f17f2018-01-31 13:16:35 -08002594 ' # %s' % hierarchy,
Paweł Hajdan, Jrfb022012017-07-06 18:00:08 +02002595 ' "%s": %r,' % (key, value),
2596 '',
2597 ])
2598 s.extend(['}', ''])
2599 return s
2600
2601
Edward Lemur3298e7b2018-07-17 18:21:27 +00002602@metrics.collector.collect_metrics('gclient grep')
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00002603def CMDgrep(parser, args):
2604 """Greps through git repos managed by gclient.
2605
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002606 Runs 'git grep [args...]' for each module.
2607 """
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00002608 # We can't use optparse because it will try to parse arguments sent
2609 # to git grep and throw an error. :-(
2610 if not args or re.match('(-h|--help)$', args[0]):
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002611 print(
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00002612 'Usage: gclient grep [-j <N>] git-grep-args...\n\n'
2613 'Example: "gclient grep -j10 -A2 RefCountedBase" runs\n"git grep '
2614 '-A2 RefCountedBase" on each of gclient\'s git\nrepos with up to '
2615 '10 jobs.\n\nBonus: page output by appending "|& less -FRSX" to the'
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002616 ' end of your query.',
2617 file=sys.stderr)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00002618 return 1
2619
2620 jobs_arg = ['--jobs=1']
2621 if re.match(r'(-j|--jobs=)\d+$', args[0]):
2622 jobs_arg, args = args[:1], args[1:]
2623 elif re.match(r'(-j|--jobs)$', args[0]):
2624 jobs_arg, args = args[:2], args[2:]
2625
2626 return CMDrecurse(
2627 parser,
2628 jobs_arg + ['--ignore', '--prepend-dir', '--no-progress', '--scm=git',
2629 'git', 'grep', '--null', '--color=Always'] + args)
davidbarr@chromium.org12f944e2012-03-01 02:18:31 +00002630
2631
Edward Lemur3298e7b2018-07-17 18:21:27 +00002632@metrics.collector.collect_metrics('gclient root')
stip@chromium.orga735da22015-04-29 23:18:20 +00002633def CMDroot(parser, args):
2634 """Outputs the solution root (or current dir if there isn't one)."""
2635 (options, args) = parser.parse_args(args)
2636 client = GClient.LoadCurrentConfig(options)
2637 if client:
2638 print(os.path.abspath(client.root_dir))
2639 else:
2640 print(os.path.abspath('.'))
2641
2642
agablea98a6cd2016-11-15 14:30:10 -08002643@subcommand.usage('[url]')
Edward Lemur3298e7b2018-07-17 18:21:27 +00002644@metrics.collector.collect_metrics('gclient config')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002645def CMDconfig(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002646 """Creates a .gclient file in the current directory.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00002647
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002648 This specifies the configuration for further commands. After update/sync,
2649 top-level DEPS files in each module are read to determine dependent
2650 modules to operate on as well. If optional [url] parameter is
2651 provided, then configuration is read from a specified Subversion server
2652 URL.
2653 """
szager@chromium.orge2e03202012-07-31 18:05:16 +00002654 # We do a little dance with the --gclientfile option. 'gclient config' is the
2655 # only command where it's acceptable to have both '--gclientfile' and '--spec'
2656 # arguments. So, we temporarily stash any --gclientfile parameter into
2657 # options.output_config_file until after the (gclientfile xor spec) error
2658 # check.
2659 parser.remove_option('--gclientfile')
2660 parser.add_option('--gclientfile', dest='output_config_file',
2661 help='Specify an alternate .gclient file')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002662 parser.add_option('--name',
2663 help='overrides the default name for the solution')
nsylvain@google.comefc80932011-05-31 21:27:56 +00002664 parser.add_option('--deps-file', default='DEPS',
David Benjamin105e11e2017-10-16 10:39:35 -04002665 help='overrides the default name for the DEPS file for the '
nsylvain@google.comefc80932011-05-31 21:27:56 +00002666 'main solutions and all sub-dependencies')
smutae7ea312016-07-18 11:59:41 -07002667 parser.add_option('--unmanaged', action='store_true', default=False,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00002668 help='overrides the default behavior to make it possible '
smutae7ea312016-07-18 11:59:41 -07002669 'to have the main solution untouched by gclient '
2670 '(gclient will check out unmanaged dependencies but '
2671 'will never sync them)')
Robert Iannuccia19649b2018-06-29 16:31:45 +00002672 parser.add_option('--cache-dir', default=UNSET_CACHE_DIR,
2673 help='Cache all git repos into this dir and do shared '
2674 'clones from the cache, instead of cloning directly '
2675 'from the remote. Pass "None" to disable cache, even '
2676 'if globally enabled due to $GIT_CACHE_PATH.')
Paweł Hajdan, Jr3ba2a7c2017-10-04 19:24:46 +02002677 parser.add_option('--custom-var', action='append', dest='custom_vars',
2678 default=[],
2679 help='overrides variables; key=value syntax')
szager@chromium.orge2e03202012-07-31 18:05:16 +00002680 parser.set_defaults(config_filename=None)
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002681 (options, args) = parser.parse_args(args)
szager@chromium.orge2e03202012-07-31 18:05:16 +00002682 if options.output_config_file:
2683 setattr(options, 'config_filename', getattr(options, 'output_config_file'))
maruel@chromium.org5fc2a332010-05-26 19:37:15 +00002684 if ((options.spec and args) or len(args) > 2 or
2685 (not options.spec and not args)):
2686 parser.error('Inconsistent arguments. Use either --spec or one or 2 args')
2687
Robert Iannuccia19649b2018-06-29 16:31:45 +00002688 if (options.cache_dir is not UNSET_CACHE_DIR
2689 and options.cache_dir.lower() == 'none'):
2690 options.cache_dir = None
2691
Paweł Hajdan, Jr3ba2a7c2017-10-04 19:24:46 +02002692 custom_vars = {}
2693 for arg in options.custom_vars:
2694 kv = arg.split('=', 1)
2695 if len(kv) != 2:
2696 parser.error('Invalid --custom-var argument: %r' % arg)
2697 custom_vars[kv[0]] = gclient_eval.EvaluateCondition(kv[1], {})
2698
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002699 client = GClient('.', options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002700 if options.spec:
2701 client.SetConfig(options.spec)
2702 else:
maruel@chromium.org1ab7ffc2009-06-03 17:21:37 +00002703 base_url = args[0].rstrip('/')
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00002704 if not options.name:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002705 name = base_url.split('/')[-1]
nsylvain@google.com12649ef2011-06-01 17:11:20 +00002706 if name.endswith('.git'):
2707 name = name[:-4]
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00002708 else:
2709 # specify an alternate relpath for the given URL.
2710 name = options.name
agable@chromium.orgf2214672015-10-27 21:02:48 +00002711 if not os.path.abspath(os.path.join(os.getcwd(), name)).startswith(
2712 os.getcwd()):
2713 parser.error('Do not pass a relative path for --name.')
2714 if any(x in ('..', '.', '/', '\\') for x in name.split(os.sep)):
2715 parser.error('Do not include relative path components in --name.')
2716
nsylvain@google.comefc80932011-05-31 21:27:56 +00002717 deps_file = options.deps_file
agablea98a6cd2016-11-15 14:30:10 -08002718 client.SetDefaultConfig(name, deps_file, base_url,
smutae7ea312016-07-18 11:59:41 -07002719 managed=not options.unmanaged,
Paweł Hajdan, Jr3ba2a7c2017-10-04 19:24:46 +02002720 cache_dir=options.cache_dir,
2721 custom_vars=custom_vars)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002722 client.SaveConfig()
maruel@chromium.org79692d62010-05-14 18:57:13 +00002723 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002724
2725
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002726@subcommand.epilog("""Example:
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002727 gclient pack > patch.txt
2728 generate simple patch for configured client and dependences
2729""")
Edward Lemur3298e7b2018-07-17 18:21:27 +00002730@metrics.collector.collect_metrics('gclient pack')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002731def CMDpack(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002732 """Generates a patch which can be applied at the root of the tree.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00002733
agabled437d762016-10-17 09:35:11 -07002734 Internally, runs 'git diff' on each checked out module and
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002735 dependencies, and performs minimal postprocessing of the output. The
2736 resulting patch is printed to stdout and can be applied to a freshly
2737 checked out tree via 'patch -p0 < patchfile'.
2738 """
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002739 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2740 help='override deps for the specified (comma-separated) '
2741 'platform(s); \'all\' will process all deps_os '
2742 'references')
haitao.feng@intel.com306080c2012-05-04 13:11:29 +00002743 parser.remove_option('--jobs')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002744 (options, args) = parser.parse_args(args)
iannucci@chromium.org50395ea2013-04-04 04:47:42 +00002745 # Force jobs to 1 so the stdout is not annotated with the thread ids
haitao.feng@intel.com306080c2012-05-04 13:11:29 +00002746 options.jobs = 1
kbr@google.comab318592009-09-04 00:54:55 +00002747 client = GClient.LoadCurrentConfig(options)
2748 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002749 raise gclient_utils.Error('client not configured; see \'gclient config\'')
kbr@google.comab318592009-09-04 00:54:55 +00002750 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002751 client.PrintLocationAndContents()
kbr@google.comab318592009-09-04 00:54:55 +00002752 return client.RunOnDeps('pack', args)
2753
2754
Edward Lemur3298e7b2018-07-17 18:21:27 +00002755@metrics.collector.collect_metrics('gclient status')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002756def CMDstatus(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002757 """Shows modification status for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002758 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2759 help='override deps for the specified (comma-separated) '
2760 'platform(s); \'all\' will process all deps_os '
2761 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002762 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002763 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002764 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002765 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002766 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002767 client.PrintLocationAndContents()
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002768 return client.RunOnDeps('status', args)
2769
2770
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002771@subcommand.epilog("""Examples:
maruel@chromium.org79692d62010-05-14 18:57:13 +00002772 gclient sync
2773 update files from SCM according to current configuration,
2774 *for modules which have changed since last update or sync*
2775 gclient sync --force
2776 update files from SCM according to current configuration, for
2777 all modules (useful for recovering files deleted from local copy)
Edward Lesmes3ffca4b2021-05-19 19:36:17 +00002778 gclient sync --revision src@GIT_COMMIT_OR_REF
2779 update src directory to GIT_COMMIT_OR_REF
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002780
2781JSON output format:
2782If the --output-json option is specified, the following document structure will
2783be emitted to the provided file. 'null' entries may occur for subprojects which
2784are present in the gclient solution, but were not processed (due to custom_deps,
2785os_deps, etc.)
2786
2787{
2788 "solutions" : {
2789 "<name>": { # <name> is the posix-normalized path to the solution.
agabled437d762016-10-17 09:35:11 -07002790 "revision": [<git id hex string>|null],
2791 "scm": ["git"|null],
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002792 }
2793 }
2794}
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002795""")
Edward Lemur3298e7b2018-07-17 18:21:27 +00002796@metrics.collector.collect_metrics('gclient sync')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002797def CMDsync(parser, args):
2798 """Checkout/update all modules."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002799 parser.add_option('-f', '--force', action='store_true',
2800 help='force update even for unchanged modules')
2801 parser.add_option('-n', '--nohooks', action='store_true',
2802 help='don\'t run hooks after the update is complete')
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00002803 parser.add_option('-p', '--noprehooks', action='store_true',
2804 help='don\'t run pre-DEPS hooks', default=False)
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002805 parser.add_option('-r', '--revision', action='append',
2806 dest='revisions', metavar='REV', default=[],
Edward Lesmes3ffca4b2021-05-19 19:36:17 +00002807 help='Enforces git ref/hash for the solutions with the '
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002808 'format src@rev. The src@ part is optional and can be '
Edward Lesmes53014652018-03-07 18:01:40 -05002809 'skipped. You can also specify URLs instead of paths '
2810 'and gclient will find the solution corresponding to '
2811 'the given URL. If a path is also specified, the URL '
2812 'takes precedence. -r can be used multiple times when '
2813 '.gclient has multiple solutions configured, and will '
Edward Lesmes3ffca4b2021-05-19 19:36:17 +00002814 'work even if the src@ part is skipped. Revision '
2815 'numbers (e.g. 31000 or r31000) are not supported.')
Edward Lesmesc621b212018-03-21 20:26:56 -04002816 parser.add_option('--patch-ref', action='append',
2817 dest='patch_refs', metavar='GERRIT_REF', default=[],
Edward Lemur6a4e31b2018-08-10 19:59:02 +00002818 help='Patches the given reference with the format '
Edward Lemur4c5c8ab2019-06-07 15:58:13 +00002819 'dep@target-ref:patch-ref. '
Edward Lemur6a4e31b2018-08-10 19:59:02 +00002820 'For |dep|, you can specify URLs as well as paths, '
2821 'with URLs taking preference. '
2822 '|patch-ref| will be applied to |dep|, rebased on top '
2823 'of what |dep| was synced to, and a soft reset will '
2824 'be done. Use --no-rebase-patch-ref and '
2825 '--no-reset-patch-ref to disable this behavior. '
2826 '|target-ref| is the target branch against which a '
2827 'patch was created, it is used to determine which '
2828 'commits from the |patch-ref| actually constitute a '
Edward Lemur4c5c8ab2019-06-07 15:58:13 +00002829 'patch.')
Ravi Mistryecda7822022-02-28 16:22:20 +00002830 parser.add_option('-t', '--download-topics', action='store_true',
2831 help='Downloads and patches locally changes from all open '
2832 'Gerrit CLs that have the same topic as the changes '
2833 'in the specified patch_refs. Only works if atleast '
2834 'one --patch-ref is specified.')
maruel@chromium.org794207e2013-03-08 15:29:43 +00002835 parser.add_option('--with_branch_heads', action='store_true',
2836 help='Clone git "branch_heads" refspecs in addition to '
2837 'the default refspecs. This adds about 1/2GB to a '
2838 'full checkout. (git only)')
szager@chromium.org8d3348f2014-08-19 22:49:16 +00002839 parser.add_option('--with_tags', action='store_true',
2840 help='Clone git tags in addition to the default refspecs.')
agable2697cd12016-06-28 10:23:53 -07002841 parser.add_option('-H', '--head', action='store_true',
agablea98a6cd2016-11-15 14:30:10 -08002842 help='DEPRECATED: only made sense with safesync urls.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002843 parser.add_option('-D', '--delete_unversioned_trees', action='store_true',
steveblock@chromium.org98e69452012-02-16 16:36:43 +00002844 help='Deletes from the working copy any dependencies that '
2845 'have been removed since the last sync, as long as '
2846 'there are no local modifications. When used with '
2847 '--force, such dependencies are removed even if they '
2848 'have local modifications. When used with --reset, '
2849 'all untracked directories are removed from the '
tnagel@chromium.orga2aaa632014-02-28 21:47:27 +00002850 'working copy, excluding those which are explicitly '
steveblock@chromium.org98e69452012-02-16 16:36:43 +00002851 'ignored in the repository.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002852 parser.add_option('-R', '--reset', action='store_true',
2853 help='resets any local changes before updating (git only)')
bauerb@chromium.org2aad1b22011-07-22 12:00:41 +00002854 parser.add_option('-M', '--merge', action='store_true',
2855 help='merge upstream changes instead of trying to '
2856 'fast-forward or rebase')
dnj@chromium.org5b23e872015-02-20 21:25:57 +00002857 parser.add_option('-A', '--auto_rebase', action='store_true',
2858 help='Automatically rebase repositories against local '
2859 'checkout during update (git only).')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002860 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2861 help='override deps for the specified (comma-separated) '
2862 'platform(s); \'all\' will process all deps_os '
2863 'references')
Paweł Hajdan, Jr5ec77132017-08-16 19:21:06 +02002864 parser.add_option('--process-all-deps', action='store_true',
2865 help='Check out all deps, even for different OS-es, '
2866 'or with conditions evaluating to false')
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +00002867 parser.add_option('--upstream', action='store_true',
2868 help='Make repo state match upstream branch.')
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002869 parser.add_option('--output-json',
2870 help='Output a json document to this path containing '
2871 'summary information about the sync.')
primiano@chromium.org5439ea52014-08-06 17:18:18 +00002872 parser.add_option('--no-history', action='store_true',
2873 help='GIT ONLY - Reduces the size/time of the checkout at '
2874 'the cost of no history. Requires Git 1.9+')
hinoka@chromium.org46b87412014-05-15 00:42:05 +00002875 parser.add_option('--shallow', action='store_true',
2876 help='GIT ONLY - Do a shallow clone into the cache dir. '
2877 'Requires Git 1.9+')
e.hakkinen@samsung.come8bc1aa2015-04-08 08:00:37 +00002878 parser.add_option('--no_bootstrap', '--no-bootstrap',
2879 action='store_true',
2880 help='Don\'t bootstrap from Google Storage.')
Josip Sokcevic14a83ae2020-05-21 01:36:34 +00002881 parser.add_option('--ignore_locks',
2882 action='store_true',
2883 help='No longer used.')
2884 parser.add_option('--break_repo_locks',
2885 action='store_true',
2886 help='No longer used.')
Vadim Shtayura08049e22017-10-11 00:14:52 +00002887 parser.add_option('--lock_timeout', type='int', default=5000,
2888 help='GIT ONLY - Deadline (in seconds) to wait for git '
2889 'cache lock to become available. Default is %default.')
Edward Lesmesc621b212018-03-21 20:26:56 -04002890 parser.add_option('--no-rebase-patch-ref', action='store_false',
2891 dest='rebase_patch_ref', default=True,
2892 help='Bypass rebase of the patch ref after checkout.')
2893 parser.add_option('--no-reset-patch-ref', action='store_false',
2894 dest='reset_patch_ref', default=True,
2895 help='Bypass calling reset after patching the ref.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002896 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002897 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002898
2899 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002900 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002901
Ravi Mistryecda7822022-02-28 16:22:20 +00002902 if options.download_topics and not options.rebase_patch_ref:
2903 raise gclient_utils.Error(
2904 'Warning: You cannot download topics and not rebase each patch ref')
2905
Josip Sokcevic14a83ae2020-05-21 01:36:34 +00002906 if options.ignore_locks:
2907 print('Warning: ignore_locks is no longer used. Please remove its usage.')
2908
2909 if options.break_repo_locks:
2910 print('Warning: break_repo_locks is no longer used. Please remove its '
2911 'usage.')
2912
smutae7ea312016-07-18 11:59:41 -07002913 if options.revisions and options.head:
2914 # TODO(maruel): Make it a parser.error if it doesn't break any builder.
2915 print('Warning: you cannot use both --head and --revision')
2916
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002917 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002918 client.PrintLocationAndContents()
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002919 ret = client.RunOnDeps('update', args)
2920 if options.output_json:
2921 slns = {}
Michael Mossd683d7c2018-06-15 05:05:17 +00002922 for d in client.subtree(True):
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002923 normed = d.name.replace('\\', '/').rstrip('/') + '/'
2924 slns[normed] = {
2925 'revision': d.got_revision,
2926 'scm': d.used_scm.name if d.used_scm else None,
Michael Mossd683d7c2018-06-15 05:05:17 +00002927 'url': str(d.url) if d.url else None,
Edward Lemur7ccf2f02018-06-26 20:41:56 +00002928 'was_processed': d.should_process,
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002929 }
Edward Lemurca879322019-09-09 20:18:13 +00002930 with open(options.output_json, 'w') as f:
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002931 json.dump({'solutions': slns}, f)
2932 return ret
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002933
2934
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002935CMDupdate = CMDsync
2936
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002937
Edward Lemur3298e7b2018-07-17 18:21:27 +00002938@metrics.collector.collect_metrics('gclient validate')
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +02002939def CMDvalidate(parser, args):
2940 """Validates the .gclient and DEPS syntax."""
2941 options, args = parser.parse_args(args)
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +02002942 client = GClient.LoadCurrentConfig(options)
Gavin Makf6b414c2021-01-12 19:10:41 +00002943 if not client:
2944 raise gclient_utils.Error('client not configured; see \'gclient config\'')
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +02002945 rv = client.RunOnDeps('validate', args)
2946 if rv == 0:
2947 print('validate: SUCCESS')
2948 else:
2949 print('validate: FAILURE')
2950 return rv
2951
2952
Edward Lemur3298e7b2018-07-17 18:21:27 +00002953@metrics.collector.collect_metrics('gclient diff')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002954def CMDdiff(parser, args):
2955 """Displays local diff for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002956 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2957 help='override deps for the specified (comma-separated) '
2958 'platform(s); \'all\' will process all deps_os '
2959 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002960 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002961 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002962 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002963 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002964 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002965 client.PrintLocationAndContents()
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002966 return client.RunOnDeps('diff', args)
2967
2968
Edward Lemur3298e7b2018-07-17 18:21:27 +00002969@metrics.collector.collect_metrics('gclient revert')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002970def CMDrevert(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002971 """Reverts all modifications in every dependencies.
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00002972
2973 That's the nuclear option to get back to a 'clean' state. It removes anything
agabled437d762016-10-17 09:35:11 -07002974 that shows up in git status."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002975 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2976 help='override deps for the specified (comma-separated) '
2977 'platform(s); \'all\' will process all deps_os '
2978 'references')
2979 parser.add_option('-n', '--nohooks', action='store_true',
2980 help='don\'t run hooks after the revert is complete')
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00002981 parser.add_option('-p', '--noprehooks', action='store_true',
2982 help='don\'t run pre-DEPS hooks', default=False)
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +00002983 parser.add_option('--upstream', action='store_true',
2984 help='Make repo state match upstream branch.')
Josip Sokcevic14a83ae2020-05-21 01:36:34 +00002985 parser.add_option('--break_repo_locks',
2986 action='store_true',
2987 help='No longer used.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002988 (options, args) = parser.parse_args(args)
Josip Sokcevic14a83ae2020-05-21 01:36:34 +00002989 if options.break_repo_locks:
2990 print('Warning: break_repo_locks is no longer used. Please remove its ' +
2991 'usage.')
2992
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002993 # --force is implied.
2994 options.force = True
steveblock@chromium.org98e69452012-02-16 16:36:43 +00002995 options.reset = False
2996 options.delete_unversioned_trees = False
agablec903d732016-07-26 09:07:24 -07002997 options.merge = False
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002998 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002999 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00003000 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00003001 return client.RunOnDeps('revert', args)
3002
3003
Edward Lemur3298e7b2018-07-17 18:21:27 +00003004@metrics.collector.collect_metrics('gclient runhooks')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00003005def CMDrunhooks(parser, args):
3006 """Runs hooks for files that have been modified in the local working copy."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00003007 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
3008 help='override deps for the specified (comma-separated) '
3009 'platform(s); \'all\' will process all deps_os '
3010 'references')
3011 parser.add_option('-f', '--force', action='store_true', default=True,
3012 help='Deprecated. No effect.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00003013 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00003014 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00003015 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00003016 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00003017 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00003018 client.PrintLocationAndContents()
maruel@chromium.org5df6a462009-08-28 18:52:26 +00003019 options.force = True
maruel@chromium.org5ca27692010-05-26 19:32:41 +00003020 options.nohooks = False
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00003021 return client.RunOnDeps('runhooks', args)
3022
3023
Edward Lemur3298e7b2018-07-17 18:21:27 +00003024@metrics.collector.collect_metrics('gclient revinfo')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00003025def CMDrevinfo(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00003026 """Outputs revision info mapping for the client and its dependencies.
maruel@chromium.org9eda4112010-06-11 18:56:10 +00003027
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00003028 This allows the capture of an overall 'revision' for the source tree that
maruel@chromium.org9eda4112010-06-11 18:56:10 +00003029 can be used to reproduce the same tree in the future. It is only useful for
agabled437d762016-10-17 09:35:11 -07003030 'unpinned dependencies', i.e. DEPS/deps references without a git hash.
3031 A git branch name isn't 'pinned' since the actual commit can change.
maruel@chromium.org9eda4112010-06-11 18:56:10 +00003032 """
3033 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
3034 help='override deps for the specified (comma-separated) '
3035 'platform(s); \'all\' will process all deps_os '
3036 'references')
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00003037 parser.add_option('-a', '--actual', action='store_true',
3038 help='gets the actual checked out revisions instead of the '
3039 'ones specified in the DEPS and .gclient files')
maruel@chromium.org9eda4112010-06-11 18:56:10 +00003040 parser.add_option('-s', '--snapshot', action='store_true',
3041 help='creates a snapshot .gclient file of the current '
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00003042 'version of all repositories to reproduce the tree, '
3043 'implies -a')
Edward Lesmesbb16e332018-03-30 17:54:51 -04003044 parser.add_option('--filter', action='append', dest='filter',
Edward Lesmesdaa76d22018-03-06 14:56:57 -05003045 help='Display revision information only for the specified '
Edward Lesmesbb16e332018-03-30 17:54:51 -04003046 'dependencies (filtered by URL or path).')
Edward Lesmesc2960242018-03-06 20:50:15 -05003047 parser.add_option('--output-json',
3048 help='Output a json document to this path containing '
3049 'information about the revisions.')
Joey Scarr8d3925b2018-07-15 23:36:25 +00003050 parser.add_option('--ignore-dep-type', choices=['git', 'cipd'],
3051 help='Specify to skip processing of a certain type of dep.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00003052 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00003053 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00003054 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00003055 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00003056 client.PrintRevInfo()
maruel@chromium.org79692d62010-05-14 18:57:13 +00003057 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00003058
3059
Edward Lemur3298e7b2018-07-17 18:21:27 +00003060@metrics.collector.collect_metrics('gclient getdep')
Edward Lesmes411041f2018-04-05 20:12:55 -04003061def CMDgetdep(parser, args):
3062 """Gets revision information and variable values from a DEPS file."""
3063 parser.add_option('--var', action='append',
3064 dest='vars', metavar='VAR', default=[],
3065 help='Gets the value of a given variable.')
3066 parser.add_option('-r', '--revision', action='append',
Edward Lemuraf3328f2018-11-19 14:11:46 +00003067 dest='getdep_revisions', metavar='DEP', default=[],
Edward Lesmes411041f2018-04-05 20:12:55 -04003068 help='Gets the revision/version for the given dependency. '
3069 'If it is a git dependency, dep must be a path. If it '
3070 'is a CIPD dependency, dep must be of the form '
3071 'path:package.')
3072 parser.add_option('--deps-file', default='DEPS',
3073 # TODO(ehmaldonado): Try to find the DEPS file pointed by
3074 # .gclient first.
3075 help='The DEPS file to be edited. Defaults to the DEPS '
3076 'file in the current directory.')
3077 (options, args) = parser.parse_args(args)
3078
3079 if not os.path.isfile(options.deps_file):
3080 raise gclient_utils.Error(
3081 'DEPS file %s does not exist.' % options.deps_file)
3082 with open(options.deps_file) as f:
3083 contents = f.read()
Edward Lemuraf3328f2018-11-19 14:11:46 +00003084 client = GClient.LoadCurrentConfig(options)
3085 if client is not None:
3086 builtin_vars = client.get_builtin_vars()
3087 else:
Edward Lemurca879322019-09-09 20:18:13 +00003088 logging.warning(
Edward Lemuraf3328f2018-11-19 14:11:46 +00003089 'Couldn\'t find a valid gclient config. Will attempt to parse the DEPS '
3090 'file without support for built-in variables.')
3091 builtin_vars = None
3092 local_scope = gclient_eval.Exec(contents, options.deps_file,
3093 builtin_vars=builtin_vars)
Edward Lesmes411041f2018-04-05 20:12:55 -04003094
3095 for var in options.vars:
3096 print(gclient_eval.GetVar(local_scope, var))
3097
Edward Lemuraf3328f2018-11-19 14:11:46 +00003098 for name in options.getdep_revisions:
Edward Lesmes411041f2018-04-05 20:12:55 -04003099 if ':' in name:
3100 name, _, package = name.partition(':')
3101 if not name or not package:
3102 parser.error(
3103 'Wrong CIPD format: %s:%s should be of the form path:pkg.'
3104 % (name, package))
3105 print(gclient_eval.GetCIPD(local_scope, name, package))
3106 else:
3107 print(gclient_eval.GetRevision(local_scope, name))
3108
3109
Edward Lemur3298e7b2018-07-17 18:21:27 +00003110@metrics.collector.collect_metrics('gclient setdep')
Edward Lesmes6f64a052018-03-20 17:35:49 -04003111def CMDsetdep(parser, args):
Edward Lesmes0ecf6d62018-04-05 18:28:55 -04003112 """Modifies dependency revisions and variable values in a DEPS file"""
Edward Lesmes6f64a052018-03-20 17:35:49 -04003113 parser.add_option('--var', action='append',
3114 dest='vars', metavar='VAR=VAL', default=[],
3115 help='Sets a variable to the given value with the format '
3116 'name=value.')
3117 parser.add_option('-r', '--revision', action='append',
Edward Lemuraf3328f2018-11-19 14:11:46 +00003118 dest='setdep_revisions', metavar='DEP@REV', default=[],
Edward Lesmes6f64a052018-03-20 17:35:49 -04003119 help='Sets the revision/version for the dependency with '
3120 'the format dep@rev. If it is a git dependency, dep '
3121 'must be a path and rev must be a git hash or '
3122 'reference (e.g. src/dep@deadbeef). If it is a CIPD '
3123 'dependency, dep must be of the form path:package and '
3124 'rev must be the package version '
3125 '(e.g. src/pkg:chromium/pkg@2.1-cr0).')
3126 parser.add_option('--deps-file', default='DEPS',
3127 # TODO(ehmaldonado): Try to find the DEPS file pointed by
3128 # .gclient first.
3129 help='The DEPS file to be edited. Defaults to the DEPS '
3130 'file in the current directory.')
3131 (options, args) = parser.parse_args(args)
Edward Lesmes0ecf6d62018-04-05 18:28:55 -04003132 if args:
3133 parser.error('Unused arguments: "%s"' % '" "'.join(args))
Edward Lesmesae6836e2018-11-19 15:27:20 +00003134 if not options.setdep_revisions and not options.vars:
Edward Lesmes0ecf6d62018-04-05 18:28:55 -04003135 parser.error(
3136 'You must specify at least one variable or revision to modify.')
Edward Lesmes6f64a052018-03-20 17:35:49 -04003137
Edward Lesmes6f64a052018-03-20 17:35:49 -04003138 if not os.path.isfile(options.deps_file):
3139 raise gclient_utils.Error(
3140 'DEPS file %s does not exist.' % options.deps_file)
3141 with open(options.deps_file) as f:
3142 contents = f.read()
Edward Lemuraf3328f2018-11-19 14:11:46 +00003143
3144 client = GClient.LoadCurrentConfig(options)
3145 if client is not None:
3146 builtin_vars = client.get_builtin_vars()
3147 else:
Edward Lemurca879322019-09-09 20:18:13 +00003148 logging.warning(
Edward Lemuraf3328f2018-11-19 14:11:46 +00003149 'Couldn\'t find a valid gclient config. Will attempt to parse the DEPS '
3150 'file without support for built-in variables.')
3151 builtin_vars = None
3152
3153 local_scope = gclient_eval.Exec(contents, options.deps_file,
3154 builtin_vars=builtin_vars)
Edward Lesmes6f64a052018-03-20 17:35:49 -04003155
3156 for var in options.vars:
3157 name, _, value = var.partition('=')
3158 if not name or not value:
Edward Lesmes0ecf6d62018-04-05 18:28:55 -04003159 parser.error(
Edward Lesmes6f64a052018-03-20 17:35:49 -04003160 'Wrong var format: %s should be of the form name=value.' % var)
Edward Lesmes3d993812018-04-02 12:52:49 -04003161 if name in local_scope['vars']:
3162 gclient_eval.SetVar(local_scope, name, value)
3163 else:
3164 gclient_eval.AddVar(local_scope, name, value)
Edward Lesmes6f64a052018-03-20 17:35:49 -04003165
Edward Lemuraf3328f2018-11-19 14:11:46 +00003166 for revision in options.setdep_revisions:
Edward Lesmes6f64a052018-03-20 17:35:49 -04003167 name, _, value = revision.partition('@')
3168 if not name or not value:
Edward Lesmes0ecf6d62018-04-05 18:28:55 -04003169 parser.error(
Edward Lesmes6f64a052018-03-20 17:35:49 -04003170 'Wrong dep format: %s should be of the form dep@rev.' % revision)
3171 if ':' in name:
3172 name, _, package = name.partition(':')
3173 if not name or not package:
Edward Lesmes0ecf6d62018-04-05 18:28:55 -04003174 parser.error(
Edward Lesmes6f64a052018-03-20 17:35:49 -04003175 'Wrong CIPD format: %s:%s should be of the form path:pkg@version.'
3176 % (name, package))
3177 gclient_eval.SetCIPD(local_scope, name, package, value)
3178 else:
Edward Lesmes9f531292018-03-20 21:27:15 -04003179 gclient_eval.SetRevision(local_scope, name, value)
Edward Lesmes6f64a052018-03-20 17:35:49 -04003180
John Emau7aa68242020-02-20 19:44:53 +00003181 with open(options.deps_file, 'wb') as f:
3182 f.write(gclient_eval.RenderDEPSFile(local_scope).encode('utf-8'))
Edward Lesmes6f64a052018-03-20 17:35:49 -04003183
3184
Edward Lemur3298e7b2018-07-17 18:21:27 +00003185@metrics.collector.collect_metrics('gclient verify')
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00003186def CMDverify(parser, args):
3187 """Verifies the DEPS file deps are only from allowed_hosts."""
3188 (options, args) = parser.parse_args(args)
3189 client = GClient.LoadCurrentConfig(options)
3190 if not client:
3191 raise gclient_utils.Error('client not configured; see \'gclient config\'')
3192 client.RunOnDeps(None, [])
3193 # Look at each first-level dependency of this gclient only.
3194 for dep in client.dependencies:
3195 bad_deps = dep.findDepsFromNotAllowedHosts()
3196 if not bad_deps:
3197 continue
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00003198 print("There are deps from not allowed hosts in file %s" % dep.deps_file)
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00003199 for bad_dep in bad_deps:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00003200 print("\t%s at %s" % (bad_dep.name, bad_dep.url))
3201 print("allowed_hosts:", ', '.join(dep.allowed_hosts))
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00003202 sys.stdout.flush()
3203 raise gclient_utils.Error(
3204 'dependencies from disallowed hosts; check your DEPS file.')
3205 return 0
3206
Edward Lemur32e3d1e2018-07-12 00:54:05 +00003207
3208@subcommand.epilog("""For more information on what metrics are we collecting and
Edward Lemur8a2e3312018-07-12 21:15:09 +00003209why, please read metrics.README.md or visit https://bit.ly/2ufRS4p""")
Edward Lemur3298e7b2018-07-17 18:21:27 +00003210@metrics.collector.collect_metrics('gclient metrics')
Edward Lemur32e3d1e2018-07-12 00:54:05 +00003211def CMDmetrics(parser, args):
3212 """Reports, and optionally modifies, the status of metric collection."""
3213 parser.add_option('--opt-in', action='store_true', dest='enable_metrics',
3214 help='Opt-in to metrics collection.',
3215 default=None)
3216 parser.add_option('--opt-out', action='store_false', dest='enable_metrics',
3217 help='Opt-out of metrics collection.')
3218 options, args = parser.parse_args(args)
3219 if args:
3220 parser.error('Unused arguments: "%s"' % '" "'.join(args))
3221 if not metrics.collector.config.is_googler:
3222 print("You're not a Googler. Metrics collection is disabled for you.")
3223 return 0
3224
3225 if options.enable_metrics is not None:
3226 metrics.collector.config.opted_in = options.enable_metrics
3227
3228 if metrics.collector.config.opted_in is None:
3229 print("You haven't opted in or out of metrics collection.")
3230 elif metrics.collector.config.opted_in:
3231 print("You have opted in. Thanks!")
3232 else:
3233 print("You have opted out. Please consider opting in.")
3234 return 0
3235
3236
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00003237class OptionParser(optparse.OptionParser):
szager@chromium.orge2e03202012-07-31 18:05:16 +00003238 gclientfile_default = os.environ.get('GCLIENT_FILE', '.gclient')
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00003239
3240 def __init__(self, **kwargs):
3241 optparse.OptionParser.__init__(
3242 self, version='%prog ' + __version__, **kwargs)
3243
3244 # Some arm boards have issues with parallel sync.
3245 if platform.machine().startswith('arm'):
3246 jobs = 1
3247 else:
3248 jobs = max(8, gclient_utils.NumLocalCpus())
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00003249
3250 self.add_option(
3251 '-j', '--jobs', default=jobs, type='int',
3252 help='Specify how many SCM commands can run in parallel; defaults to '
tnagel@chromium.orga2aaa632014-02-28 21:47:27 +00003253 '%default on this machine')
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00003254 self.add_option(
3255 '-v', '--verbose', action='count', default=0,
3256 help='Produces additional output for diagnostics. Can be used up to '
3257 'three times for more logging info.')
3258 self.add_option(
3259 '--gclientfile', dest='config_filename',
3260 help='Specify an alternate %s file' % self.gclientfile_default)
3261 self.add_option(
3262 '--spec',
3263 help='create a gclient file containing the provided string. Due to '
3264 'Cygwin/Python brokenness, it can\'t contain any newlines.')
3265 self.add_option(
3266 '--no-nag-max', default=False, action='store_true',
scottmg@chromium.orgf547c802013-09-27 17:55:26 +00003267 help='Ignored for backwards compatibility.')
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00003268
Edward Lemur3298e7b2018-07-17 18:21:27 +00003269 def parse_args(self, args=None, _values=None):
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00003270 """Integrates standard options processing."""
Edward Lemur3298e7b2018-07-17 18:21:27 +00003271 # Create an optparse.Values object that will store only the actual passed
3272 # options, without the defaults.
3273 actual_options = optparse.Values()
3274 _, args = optparse.OptionParser.parse_args(self, args, actual_options)
3275 # Create an optparse.Values object with the default options.
3276 options = optparse.Values(self.get_default_values().__dict__)
3277 # Update it with the options passed by the user.
3278 options._update_careful(actual_options.__dict__)
3279 # Store the options passed by the user in an _actual_options attribute.
3280 # We store only the keys, and not the values, since the values can contain
3281 # arbitrary information, which might be PII.
Edward Lemuree7b9dd2019-07-20 01:29:08 +00003282 metrics.collector.add('arguments', list(actual_options.__dict__))
Edward Lemur3298e7b2018-07-17 18:21:27 +00003283
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00003284 levels = [logging.ERROR, logging.WARNING, logging.INFO, logging.DEBUG]
3285 logging.basicConfig(
3286 level=levels[min(options.verbose, len(levels) - 1)],
maruel@chromium.org0895b752011-08-26 20:40:33 +00003287 format='%(module)s(%(lineno)d) %(funcName)s:%(message)s')
szager@chromium.orge2e03202012-07-31 18:05:16 +00003288 if options.config_filename and options.spec:
Quinten Yearsley925cedb2020-04-13 17:49:39 +00003289 self.error('Cannot specify both --gclientfile and --spec')
rdsmith@chromium.orgd9591f02014-02-05 19:28:20 +00003290 if (options.config_filename and
3291 options.config_filename != os.path.basename(options.config_filename)):
3292 self.error('--gclientfile target must be a filename, not a path')
szager@chromium.orge2e03202012-07-31 18:05:16 +00003293 if not options.config_filename:
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00003294 options.config_filename = self.gclientfile_default
maruel@chromium.org0895b752011-08-26 20:40:33 +00003295 options.entries_filename = options.config_filename + '_entries'
3296 if options.jobs < 1:
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00003297 self.error('--jobs must be 1 or higher')
maruel@chromium.org0895b752011-08-26 20:40:33 +00003298
3299 # These hacks need to die.
3300 if not hasattr(options, 'revisions'):
3301 # GClient.RunOnDeps expects it even if not applicable.
3302 options.revisions = []
Joanna Wang66286612022-06-30 19:59:13 +00003303 if not hasattr(options, 'skip_sync_revisions'):
3304 options.skip_sync_revisions = []
smutae7ea312016-07-18 11:59:41 -07003305 if not hasattr(options, 'head'):
3306 options.head = None
maruel@chromium.org0895b752011-08-26 20:40:33 +00003307 if not hasattr(options, 'nohooks'):
3308 options.nohooks = True
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00003309 if not hasattr(options, 'noprehooks'):
3310 options.noprehooks = True
maruel@chromium.org0895b752011-08-26 20:40:33 +00003311 if not hasattr(options, 'deps_os'):
3312 options.deps_os = None
maruel@chromium.org0895b752011-08-26 20:40:33 +00003313 if not hasattr(options, 'force'):
3314 options.force = None
3315 return (options, args)
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00003316
maruel@chromium.org39c0b222013-08-17 16:57:01 +00003317
3318def disable_buffering():
3319 # Make stdout auto-flush so buildbot doesn't kill us during lengthy
3320 # operations. Python as a strong tendency to buffer sys.stdout.
3321 sys.stdout = gclient_utils.MakeFileAutoFlush(sys.stdout)
3322 # Make stdout annotated with the thread ids.
3323 sys.stdout = gclient_utils.MakeFileAnnotated(sys.stdout)
maruel@chromium.org0895b752011-08-26 20:40:33 +00003324
3325
Elly Fong-Jones7b294392019-04-18 18:32:10 +00003326def path_contains_tilde():
3327 for element in os.environ['PATH'].split(os.pathsep):
Henrique Ferreiro4ef32212019-04-29 23:32:31 +00003328 if element.startswith('~') and os.path.abspath(
3329 os.path.realpath(os.path.expanduser(element))) == DEPOT_TOOLS_DIR:
Elly Fong-Jones7b294392019-04-18 18:32:10 +00003330 return True
3331 return False
3332
3333
3334def can_run_gclient_and_helpers():
maruel@chromium.org82798cb2012-02-23 18:16:12 +00003335 if sys.hexversion < 0x02060000:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00003336 print(
maruel@chromium.org82798cb2012-02-23 18:16:12 +00003337 '\nYour python version %s is unsupported, please upgrade.\n' %
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00003338 sys.version.split(' ', 1)[0],
3339 file=sys.stderr)
Elly Fong-Jones7b294392019-04-18 18:32:10 +00003340 return False
bcwhite@chromium.org6683ab42013-02-11 16:13:47 +00003341 if not sys.executable:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00003342 print(
3343 '\nPython cannot find the location of it\'s own executable.\n',
3344 file=sys.stderr)
Elly Fong-Jones7b294392019-04-18 18:32:10 +00003345 return False
3346 if path_contains_tilde():
3347 print(
3348 '\nYour PATH contains a literal "~", which works in some shells ' +
3349 'but will break when python tries to run subprocesses. ' +
3350 'Replace the "~" with $HOME.\n' +
3351 'See https://crbug.com/952865.\n',
3352 file=sys.stderr)
3353 return False
3354 return True
3355
3356
3357def main(argv):
3358 """Doesn't parse the arguments here, just find the right subcommand to
3359 execute."""
3360 if not can_run_gclient_and_helpers():
bcwhite@chromium.org6683ab42013-02-11 16:13:47 +00003361 return 2
maruel@chromium.org39c0b222013-08-17 16:57:01 +00003362 fix_encoding.fix_encoding()
3363 disable_buffering()
iannucci@chromium.org596cd5c2016-04-04 21:34:39 +00003364 setup_color.init()
maruel@chromium.org39c0b222013-08-17 16:57:01 +00003365 dispatcher = subcommand.CommandDispatcher(__name__)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00003366 try:
maruel@chromium.org39c0b222013-08-17 16:57:01 +00003367 return dispatcher.execute(OptionParser(), argv)
xusydoc@chromium.org2fd6c3f2013-05-03 21:57:55 +00003368 except KeyboardInterrupt:
3369 gclient_utils.GClientChildren.KillAllRemainingChildren()
3370 raise
vapier@chromium.orga81a56e2015-11-11 07:56:13 +00003371 except (gclient_utils.Error, subprocess2.CalledProcessError) as e:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00003372 print('Error: %s' % str(e), file=sys.stderr)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00003373 return 1
borenet@google.com6a9b1682014-03-24 18:35:23 +00003374 finally:
3375 gclient_utils.PrintWarnings()
sbc@chromium.org013731e2015-02-26 18:28:43 +00003376 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00003377
3378
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00003379if '__main__' == __name__:
Edward Lemur6f812e12018-07-31 22:45:57 +00003380 with metrics.collector.print_notice_and_exit():
sbc@chromium.org013731e2015-02-26 18:28:43 +00003381 sys.exit(main(sys.argv[1:]))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00003382
3383# vim: ts=2:sw=2:tw=80:et: