blob: 003afca4f4889f1547550a709721a0442630eb9d [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
Joanna Wang01870792022-08-01 19:02:57 +0000133PREVIOUS_CUSTOM_VARS_FILE = '.gclient_previous_custom_vars'
134PREVIOUS_SYNC_COMMITS_FILE = '.gclient_previous_sync_commits'
Robert Iannuccia19649b2018-06-29 16:31:45 +0000135
Joanna Wangf3edc502022-07-20 00:12:10 +0000136PREVIOUS_SYNC_COMMITS = 'GCLIENT_PREVIOUS_SYNC_COMMITS'
Joanna Wang66286612022-06-30 19:59:13 +0000137
Joanna Wanga84a16b2022-07-27 18:52:17 +0000138NO_SYNC_EXPERIMENT = 'no-sync'
139
Joanna Wang66286612022-06-30 19:59:13 +0000140
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +0200141class GNException(Exception):
142 pass
143
144
145def ToGNString(value, allow_dicts = True):
146 """Returns a stringified GN equivalent of the Python value.
147
148 allow_dicts indicates if this function will allow converting dictionaries
149 to GN scopes. This is only possible at the top level, you can't nest a
150 GN scope in a list, so this should be set to False for recursive calls."""
Aaron Gableac9b0f32019-04-18 17:38:37 +0000151 if isinstance(value, basestring):
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +0200152 if value.find('\n') >= 0:
153 raise GNException("Trying to print a string with a newline in it.")
154 return '"' + \
155 value.replace('\\', '\\\\').replace('"', '\\"').replace('$', '\\$') + \
156 '"'
157
Raul Tambreb946b232019-03-26 14:48:46 +0000158 if sys.version_info.major == 2 and isinstance(value, unicode):
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +0200159 return ToGNString(value.encode('utf-8'))
160
161 if isinstance(value, bool):
162 if value:
163 return "true"
164 return "false"
165
166 # NOTE: some type handling removed compared to chromium/src copy.
167
168 raise GNException("Unsupported type when printing to GN.")
169
170
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200171class Hook(object):
172 """Descriptor of command ran before/after sync or on demand."""
173
Paweł Hajdan, Jr032d5452017-06-22 20:43:53 +0200174 def __init__(self, action, pattern=None, name=None, cwd=None, condition=None,
Corentin Walleza68660d2018-09-10 17:33:24 +0000175 variables=None, verbose=False, cwd_base=None):
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200176 """Constructor.
177
178 Arguments:
179 action (list of basestring): argv of the command to run
180 pattern (basestring regex): noop with git; deprecated
181 name (basestring): optional name; no effect on operation
Paweł Hajdan, Jrc9364392017-06-14 17:11:56 +0200182 cwd (basestring): working directory to use
Paweł Hajdan, Jr032d5452017-06-22 20:43:53 +0200183 condition (basestring): condition when to run the hook
184 variables (dict): variables for evaluating the condition
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200185 """
186 self._action = gclient_utils.freeze(action)
187 self._pattern = pattern
188 self._name = name
Paweł Hajdan, Jrc9364392017-06-14 17:11:56 +0200189 self._cwd = cwd
Paweł Hajdan, Jr032d5452017-06-22 20:43:53 +0200190 self._condition = condition
191 self._variables = variables
Daniel Chenga0c5f082017-10-19 13:35:19 -0700192 self._verbose = verbose
Corentin Walleza68660d2018-09-10 17:33:24 +0000193 self._cwd_base = cwd_base
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200194
195 @staticmethod
Corentin Walleza68660d2018-09-10 17:33:24 +0000196 def from_dict(d, variables=None, verbose=False, conditions=None,
197 cwd_base=None):
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200198 """Creates a Hook instance from a dict like in the DEPS file."""
Michael Moss42d02c22018-02-05 10:32:24 -0800199 # Merge any local and inherited conditions.
Edward Lemur16f4bad2018-05-16 16:53:49 -0400200 gclient_eval.UpdateCondition(d, 'and', conditions)
Paweł Hajdan, Jr032d5452017-06-22 20:43:53 +0200201 return Hook(
202 d['action'],
203 d.get('pattern'),
204 d.get('name'),
205 d.get('cwd'),
Edward Lemur16f4bad2018-05-16 16:53:49 -0400206 d.get('condition'),
Daniel Chenga0c5f082017-10-19 13:35:19 -0700207 variables=variables,
208 # Always print the header if not printing to a TTY.
Corentin Walleza68660d2018-09-10 17:33:24 +0000209 verbose=verbose or not setup_color.IS_TTY,
210 cwd_base=cwd_base)
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200211
212 @property
213 def action(self):
214 return self._action
215
216 @property
217 def pattern(self):
218 return self._pattern
219
220 @property
221 def name(self):
222 return self._name
223
Paweł Hajdan, Jrecf53fe2017-09-29 18:28:49 +0200224 @property
225 def condition(self):
226 return self._condition
227
Corentin Walleza68660d2018-09-10 17:33:24 +0000228 @property
229 def effective_cwd(self):
230 cwd = self._cwd_base
231 if self._cwd:
232 cwd = os.path.join(cwd, self._cwd)
233 return cwd
234
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200235 def matches(self, file_list):
236 """Returns true if the pattern matches any of files in the list."""
237 if not self._pattern:
238 return True
239 pattern = re.compile(self._pattern)
240 return bool([f for f in file_list if pattern.search(f)])
241
Corentin Walleza68660d2018-09-10 17:33:24 +0000242 def run(self):
Paweł Hajdan, Jr032d5452017-06-22 20:43:53 +0200243 """Executes the hook's command (provided the condition is met)."""
244 if (self._condition and
245 not gclient_eval.EvaluateCondition(self._condition, self._variables)):
246 return
247
Aravind Vasudevanc5f0cbb2022-01-24 23:56:57 +0000248 cmd = list(self._action)
Paweł Hajdan, Jrfc6196b2017-07-27 13:15:25 +0200249
Michael Spang3c7d6f12023-05-17 19:32:51 +0000250 if cmd[0] == 'python':
251 cmd[0] = 'vpython'
252 if (cmd[0] in ['vpython', 'vpython3']) and _detect_host_os() == 'win':
Nodir Turakulov0ffcc872017-11-09 16:44:58 -0800253 cmd[0] += '.bat'
Paweł Hajdan, Jrc9364392017-06-14 17:11:56 +0200254
Edward Lesmes58542b72021-06-10 20:50:37 +0000255 exit_code = 2
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200256 try:
257 start_time = time.time()
Edward Lemur24146be2019-08-01 21:44:52 +0000258 gclient_utils.CheckCallAndFilter(
259 cmd, cwd=self.effective_cwd, print_stdout=True, show_header=True,
260 always_show_header=self._verbose)
Edward Lesmes58542b72021-06-10 20:50:37 +0000261 exit_code = 0
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200262 except (gclient_utils.Error, subprocess2.CalledProcessError) as e:
263 # Use a discrete exit status code of 2 to indicate that a hook action
264 # failed. Users of this script may wish to treat hook action failures
265 # differently from VC failures.
266 print('Error: %s' % str(e), file=sys.stderr)
Edward Lesmes58542b72021-06-10 20:50:37 +0000267 sys.exit(exit_code)
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200268 finally:
269 elapsed_time = time.time() - start_time
Edward Lesmes58542b72021-06-10 20:50:37 +0000270 metrics.collector.add_repeated('hooks', {
271 'action': gclient_utils.CommandToStr(cmd),
272 'name': self._name,
273 'cwd': os.path.relpath(
274 os.path.normpath(self.effective_cwd),
275 self._cwd_base),
276 'condition': self._condition,
277 'execution_time': elapsed_time,
278 'exit_code': exit_code,
279 })
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200280 if elapsed_time > 10:
281 print("Hook '%s' took %.2f secs" % (
282 gclient_utils.CommandToStr(cmd), elapsed_time))
283
284
Paweł Hajdan, Jrfc6196b2017-07-27 13:15:25 +0200285class DependencySettings(object):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000286 """Immutable configuration settings."""
287 def __init__(
Edward Lemure05f18d2018-06-08 17:36:53 +0000288 self, parent, url, managed, custom_deps, custom_vars,
Michael Mossd683d7c2018-06-15 05:05:17 +0000289 custom_hooks, deps_file, should_process, relative, condition):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000290 # These are not mutable:
291 self._parent = parent
mmoss@chromium.org8f93f792014-08-26 23:24:09 +0000292 self._deps_file = deps_file
maruel@chromium.org064186c2011-09-27 23:53:33 +0000293 self._url = url
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +0200294 # The condition as string (or None). Useful to keep e.g. for flatten.
295 self._condition = condition
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000296 # 'managed' determines whether or not this dependency is synced/updated by
Michael Mossd683d7c2018-06-15 05:05:17 +0000297 # gclient after gclient checks it out initially. The difference between
298 # 'managed' and 'should_process' is that the user specifies 'managed' via
299 # the --unmanaged command-line flag or a .gclient config, where
300 # 'should_process' is dynamically set by gclient if it goes over its
301 # recursion limit and controls gclient's behavior so it does not misbehave.
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000302 self._managed = managed
Michael Mossd683d7c2018-06-15 05:05:17 +0000303 self._should_process = should_process
agabledce6ddc2016-09-08 10:02:16 -0700304 # If this is a recursed-upon sub-dependency, and the parent has
305 # use_relative_paths set, then this dependency should check out its own
306 # dependencies relative to that parent's path for this, rather than
307 # relative to the .gclient file.
308 self._relative = relative
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +0000309 # This is a mutable value which has the list of 'target_os' OSes listed in
310 # the current deps file.
311 self.local_target_os = None
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000312
313 # These are only set in .gclient and not in DEPS files.
314 self._custom_vars = custom_vars or {}
315 self._custom_deps = custom_deps or {}
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000316 self._custom_hooks = custom_hooks or []
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000317
Michael Mossd683d7c2018-06-15 05:05:17 +0000318 # Post process the url to remove trailing slashes.
Aaron Gableac9b0f32019-04-18 17:38:37 +0000319 if isinstance(self.url, basestring):
Michael Moss4e9b50a2018-05-23 22:35:06 -0700320 # urls are sometime incorrectly written as proto://host/path/@rev. Replace
321 # it to proto://host/path@rev.
322 self.set_url(self.url.replace('/@', '@'))
Michael Mossd683d7c2018-06-15 05:05:17 +0000323 elif not isinstance(self.url, (None.__class__)):
324 raise gclient_utils.Error(
325 ('dependency url must be either string or None, '
326 'instead of %s') % self.url.__class__.__name__)
Edward Lemure7273d22018-05-10 19:13:51 -0400327
mmoss@chromium.orgd0b272b2013-01-30 23:55:33 +0000328 # Make any deps_file path platform-appropriate.
John Budorick0f7b2002018-01-19 15:46:17 -0800329 if self._deps_file:
330 for sep in ['/', '\\']:
331 self._deps_file = self._deps_file.replace(sep, os.sep)
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000332
333 @property
334 def deps_file(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000335 return self._deps_file
336
337 @property
338 def managed(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000339 return self._managed
340
341 @property
342 def parent(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000343 return self._parent
344
345 @property
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000346 def root(self):
347 """Returns the root node, a GClient object."""
348 if not self.parent:
349 # This line is to signal pylint that it could be a GClient instance.
350 return self or GClient(None, None)
351 return self.parent.root
352
353 @property
Michael Mossd683d7c2018-06-15 05:05:17 +0000354 def should_process(self):
355 """True if this dependency should be processed, i.e. checked out."""
356 return self._should_process
357
358 @property
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000359 def custom_vars(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000360 return self._custom_vars.copy()
361
362 @property
363 def custom_deps(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000364 return self._custom_deps.copy()
365
maruel@chromium.org064186c2011-09-27 23:53:33 +0000366 @property
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000367 def custom_hooks(self):
368 return self._custom_hooks[:]
369
370 @property
maruel@chromium.org064186c2011-09-27 23:53:33 +0000371 def url(self):
Paweł Hajdan, Jrfc6196b2017-07-27 13:15:25 +0200372 """URL after variable expansion."""
maruel@chromium.org064186c2011-09-27 23:53:33 +0000373 return self._url
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000374
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000375 @property
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +0200376 def condition(self):
377 return self._condition
378
379 @property
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +0000380 def target_os(self):
381 if self.local_target_os is not None:
382 return tuple(set(self.local_target_os).union(self.parent.target_os))
Aravind Vasudevanc5f0cbb2022-01-24 23:56:57 +0000383
384 return self.parent.target_os
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +0000385
Tom Andersonc31ae0b2018-02-06 14:48:56 -0800386 @property
387 def target_cpu(self):
388 return self.parent.target_cpu
389
Edward Lemure7273d22018-05-10 19:13:51 -0400390 def set_url(self, url):
391 self._url = url
392
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000393 def get_custom_deps(self, name, url):
394 """Returns a custom deps if applicable."""
395 if self.parent:
396 url = self.parent.get_custom_deps(name, url)
397 # None is a valid return value to disable a dependency.
398 return self.custom_deps.get(name, url)
399
maruel@chromium.org064186c2011-09-27 23:53:33 +0000400
401class Dependency(gclient_utils.WorkItem, DependencySettings):
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000402 """Object that represents a dependency checkout."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000403
Aravind Vasudevanb6eaed22023-07-06 20:50:42 +0000404 def __init__(self,
405 parent,
406 name,
407 url,
408 managed,
409 custom_deps,
410 custom_vars,
411 custom_hooks,
412 deps_file,
413 should_process,
414 should_recurse,
415 relative,
416 condition,
417 protocol='https',
418 git_dependencies_state=gclient_eval.DEPS,
Aravind Vasudevan5965d3e2022-06-01 21:51:30 +0000419 print_outbuf=False):
maruel@chromium.org6ca8bf82011-09-19 23:04:30 +0000420 gclient_utils.WorkItem.__init__(self, name)
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000421 DependencySettings.__init__(
Michael Mossd683d7c2018-06-15 05:05:17 +0000422 self, parent, url, managed, custom_deps, custom_vars,
423 custom_hooks, deps_file, should_process, relative, condition)
maruel@chromium.org68988972011-09-20 14:11:42 +0000424
425 # This is in both .gclient and DEPS files:
maruel@chromium.org064186c2011-09-27 23:53:33 +0000426 self._deps_hooks = []
maruel@chromium.org68988972011-09-20 14:11:42 +0000427
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000428 self._pre_deps_hooks = []
429
maruel@chromium.org68988972011-09-20 14:11:42 +0000430 # Calculates properties:
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +0000431 self._dependencies = []
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +0200432 self._vars = {}
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200433
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000434 # A cache of the files affected by the current operation, necessary for
435 # hooks.
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000436 self._file_list = []
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000437 # List of host names from which dependencies are allowed.
438 # Default is an empty set, meaning unspecified in DEPS file, and hence all
Josip Sokcevic9c0dc302020-11-20 18:41:25 +0000439 # hosts will be allowed. Non-empty set means allowlist of hosts.
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000440 # allowed_hosts var is scoped to its DEPS file, and so it isn't recursive.
441 self._allowed_hosts = frozenset()
Michael Moss848c86e2018-05-03 16:05:50 -0700442 self._gn_args_from = None
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +0200443 # Spec for .gni output to write (if any).
444 self._gn_args_file = None
445 self._gn_args = []
maruel@chromium.org85c2a192010-07-22 21:14:43 +0000446 # If it is not set to True, the dependency wasn't processed for its child
447 # dependency, i.e. its DEPS wasn't read.
maruel@chromium.org064186c2011-09-27 23:53:33 +0000448 self._deps_parsed = False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000449 # This dependency has been processed, i.e. checked out
maruel@chromium.org064186c2011-09-27 23:53:33 +0000450 self._processed = False
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000451 # This dependency had its pre-DEPS hooks run
452 self._pre_deps_hooks_ran = False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000453 # This dependency had its hook run
maruel@chromium.org064186c2011-09-27 23:53:33 +0000454 self._hooks_ran = False
kustermann@google.coma692e8f2013-04-18 08:32:04 +0000455 # This is the scm used to checkout self.url. It may be used by dependencies
456 # to get the datetime of the revision we checked out.
457 self._used_scm = None
szager@chromium.org4ad264b2014-05-20 04:43:47 +0000458 self._used_revision = None
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +0000459 # The actual revision we ended up getting, or None if that information is
460 # unavailable
461 self._got_revision = None
Corentin Wallez271a78a2020-07-12 15:41:46 +0000462 # Whether this dependency should use relative paths.
463 self._use_relative_paths = False
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000464
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000465 # recursedeps is a mutable value that selectively overrides the default
Edward Lemurfbb06aa2018-06-11 20:43:06 +0000466 # 'no recursion' setting on a dep-by-dep basis.
iannucci@chromium.orgafa11ac2016-05-04 22:17:34 +0000467 #
Edward Lemurfbb06aa2018-06-11 20:43:06 +0000468 # It will be a dictionary of {deps_name: depfile_namee}
469 self.recursedeps = {}
470
471 # Whether we should process this dependency's DEPS file.
472 self._should_recurse = should_recurse
Edward Lemure7273d22018-05-10 19:13:51 -0400473
Joanna Wang18af7ef2022-07-01 16:51:00 +0000474 # Whether we should sync git/cipd dependencies and hooks from the
475 # DEPS file.
Joanna Wangf3edc502022-07-20 00:12:10 +0000476 # This is set based on skip_sync_revisions and must be done
Joanna Wang18af7ef2022-07-01 16:51:00 +0000477 # after the patch refs are applied.
478 # If this is False, we will still run custom_hooks and process
479 # custom_deps, if any.
480 self._should_sync = True
481
Michael Mossd683d7c2018-06-15 05:05:17 +0000482 self._OverrideUrl()
483 # This is inherited from WorkItem. We want the URL to be a resource.
Aaron Gableac9b0f32019-04-18 17:38:37 +0000484 if self.url and isinstance(self.url, basestring):
Michael Moss4e9b50a2018-05-23 22:35:06 -0700485 # The url is usually given to gclient either as https://blah@123
486 # or just https://blah. The @123 portion is irrelevant.
487 self.resources.append(self.url.split('@')[0])
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000488
Edward Lemur231f5ea2018-01-31 19:02:36 +0100489 # Controls whether we want to print git's output when we first clone the
490 # dependency
491 self.print_outbuf = print_outbuf
492
Aravind Vasudevan5965d3e2022-06-01 21:51:30 +0000493 self.protocol = protocol
Aravind Vasudevanb6eaed22023-07-06 20:50:42 +0000494 self.git_dependencies_state = git_dependencies_state
Aravind Vasudevan5965d3e2022-06-01 21:51:30 +0000495
Michael Mossd683d7c2018-06-15 05:05:17 +0000496 if not self.name and self.parent:
497 raise gclient_utils.Error('Dependency without name')
498
499 def _OverrideUrl(self):
500 """Resolves the parsed url from the parent hierarchy."""
Aravind Vasudevanaae67252022-01-05 00:41:38 +0000501 parsed_url = self.get_custom_deps(
502 self._name.replace(os.sep, posixpath.sep) \
503 if self._name else self._name, self.url)
Michael Mossd683d7c2018-06-15 05:05:17 +0000504 if parsed_url != self.url:
505 logging.info('Dependency(%s)._OverrideUrl(%s) -> %s', self._name,
506 self.url, parsed_url)
507 self.set_url(parsed_url)
Edward Lemur1f392b82019-11-15 22:40:11 +0000508 return
Michael Mossd683d7c2018-06-15 05:05:17 +0000509
Edward Lemur1f392b82019-11-15 22:40:11 +0000510 if self.url is None:
Michael Mossd683d7c2018-06-15 05:05:17 +0000511 logging.info('Dependency(%s)._OverrideUrl(None) -> None', self._name)
Edward Lemur1f392b82019-11-15 22:40:11 +0000512 return
Michael Mossd683d7c2018-06-15 05:05:17 +0000513
Edward Lemur1f392b82019-11-15 22:40:11 +0000514 if not isinstance(self.url, basestring):
Michael Mossd683d7c2018-06-15 05:05:17 +0000515 raise gclient_utils.Error('Unknown url type')
516
Edward Lemur1f392b82019-11-15 22:40:11 +0000517 # self.url is a local path
518 path, at, rev = self.url.partition('@')
519 if os.path.isdir(path):
520 return
521
522 # self.url is a URL
523 parsed_url = urlparse.urlparse(self.url)
524 if parsed_url[0] or re.match(r'^\w+\@[\w\.-]+\:[\w\/]+', parsed_url[2]):
525 return
526
527 # self.url is relative to the parent's URL.
528 if not path.startswith('/'):
529 raise gclient_utils.Error(
530 'relative DEPS entry \'%s\' must begin with a slash' % self.url)
531
532 parent_url = self.parent.url
533 parent_path = self.parent.url.split('@')[0]
534 if os.path.isdir(parent_path):
535 # Parent's URL is a local path. Get parent's URL dirname and append
536 # self.url.
537 parent_path = os.path.dirname(parent_path)
538 parsed_url = parent_path + path.replace('/', os.sep) + at + rev
539 else:
540 # Parent's URL is a URL. Get parent's URL, strip from the last '/'
541 # (equivalent to unix dirname) and append self.url.
542 parsed_url = parent_url[:parent_url.rfind('/')] + self.url
543
544 logging.info('Dependency(%s)._OverrideUrl(%s) -> %s', self.name,
545 self.url, parsed_url)
546 self.set_url(parsed_url)
547
Edward Lemure7273d22018-05-10 19:13:51 -0400548 def PinToActualRevision(self):
Edward Lemure05f18d2018-06-08 17:36:53 +0000549 """Updates self.url to the revision checked out on disk."""
Michael Mossd683d7c2018-06-15 05:05:17 +0000550 if self.url is None:
551 return
Edward Lemure05f18d2018-06-08 17:36:53 +0000552 url = None
Edward Lemurbabd0982018-05-11 13:32:37 -0400553 scm = self.CreateSCM()
Dan Le Febvreb0e8e7a2023-05-18 23:36:46 +0000554 if scm.name == 'cipd':
555 revision = scm.revinfo(None, None, None)
556 package = self.GetExpandedPackageName()
557 url = '%s/p/%s/+/%s' % (scm.GetActualRemoteURL(None), package, revision)
558
Edward Lemure7273d22018-05-10 19:13:51 -0400559 if os.path.isdir(scm.checkout_path):
560 revision = scm.revinfo(None, None, None)
561 url = '%s@%s' % (gclient_utils.SplitUrlRevision(self.url)[0], revision)
Edward Lemure7273d22018-05-10 19:13:51 -0400562 self.set_url(url)
Edward Lemure7273d22018-05-10 19:13:51 -0400563
John Budorick0f7b2002018-01-19 15:46:17 -0800564 def ToLines(self):
Joanna Wang9144b672023-02-24 23:36:17 +0000565 # () -> Sequence[str]
566 """Returns strings representing the deps (info, graphviz line)"""
John Budorick0f7b2002018-01-19 15:46:17 -0800567 s = []
568 condition_part = ([' "condition": %r,' % self.condition]
569 if self.condition else [])
570 s.extend([
Michael Moss4e9b50a2018-05-23 22:35:06 -0700571 ' # %s' % self.hierarchy(include_url=False),
John Budorick0f7b2002018-01-19 15:46:17 -0800572 ' "%s": {' % (self.name,),
Edward Lemure05f18d2018-06-08 17:36:53 +0000573 ' "url": "%s",' % (self.url,),
John Budorick0f7b2002018-01-19 15:46:17 -0800574 ] + condition_part + [
575 ' },',
576 '',
577 ])
578 return s
579
maruel@chromium.org470b5432011-10-11 18:18:19 +0000580 @property
581 def requirements(self):
582 """Calculate the list of requirements."""
583 requirements = set()
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000584 # self.parent is implicitly a requirement. This will be recursive by
585 # definition.
586 if self.parent and self.parent.name:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000587 requirements.add(self.parent.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000588
589 # For a tree with at least 2 levels*, the leaf node needs to depend
590 # on the level higher up in an orderly way.
591 # This becomes messy for >2 depth as the DEPS file format is a dictionary,
592 # thus unsorted, while the .gclient format is a list thus sorted.
593 #
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000594 # Interestingly enough, the following condition only works in the case we
Quinten Yearsley925cedb2020-04-13 17:49:39 +0000595 # want: self is a 2nd level node. 3rd level node wouldn't need this since
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000596 # they already have their parent as a requirement.
maruel@chromium.org470b5432011-10-11 18:18:19 +0000597 if self.parent and self.parent.parent and not self.parent.parent.parent:
598 requirements |= set(i.name for i in self.root.dependencies if i.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000599
maruel@chromium.org470b5432011-10-11 18:18:19 +0000600 if self.name:
601 requirements |= set(
Michael Mossd683d7c2018-06-15 05:05:17 +0000602 obj.name for obj in self.root.subtree(False)
maruel@chromium.org470b5432011-10-11 18:18:19 +0000603 if (obj is not self
604 and obj.name and
605 self.name.startswith(posixpath.join(obj.name, ''))))
606 requirements = tuple(sorted(requirements))
607 logging.info('Dependency(%s).requirements = %s' % (self.name, requirements))
608 return requirements
609
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000610 @property
Edward Lemurfbb06aa2018-06-11 20:43:06 +0000611 def should_recurse(self):
612 return self._should_recurse
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000613
maruel@chromium.org470b5432011-10-11 18:18:19 +0000614 def verify_validity(self):
615 """Verifies that this Dependency is fine to add as a child of another one.
616
617 Returns True if this entry should be added, False if it is a duplicate of
618 another entry.
619 """
620 logging.info('Dependency(%s).verify_validity()' % self.name)
621 if self.name in [s.name for s in self.parent.dependencies]:
622 raise gclient_utils.Error(
623 'The same name "%s" appears multiple times in the deps section' %
624 self.name)
Michael Mossd683d7c2018-06-15 05:05:17 +0000625 if not self.should_process:
626 # Return early, no need to set requirements.
Edward Lemur7ccf2f02018-06-26 20:41:56 +0000627 return not any(d.name == self.name for d in self.root.subtree(True))
maruel@chromium.org470b5432011-10-11 18:18:19 +0000628
629 # This require a full tree traversal with locks.
Michael Mossd683d7c2018-06-15 05:05:17 +0000630 siblings = [d for d in self.root.subtree(False) if d.name == self.name]
maruel@chromium.org470b5432011-10-11 18:18:19 +0000631 for sibling in siblings:
Michael Mossd683d7c2018-06-15 05:05:17 +0000632 # Allow to have only one to be None or ''.
633 if self.url != sibling.url and bool(self.url) == bool(sibling.url):
maruel@chromium.org470b5432011-10-11 18:18:19 +0000634 raise gclient_utils.Error(
maruel@chromium.orgb848d5b2012-10-10 23:25:50 +0000635 ('Dependency %s specified more than once:\n'
636 ' %s [%s]\n'
637 'vs\n'
638 ' %s [%s]') % (
639 self.name,
640 sibling.hierarchy(),
Edward Lemure7273d22018-05-10 19:13:51 -0400641 sibling.url,
maruel@chromium.orgb848d5b2012-10-10 23:25:50 +0000642 self.hierarchy(),
Edward Lemure7273d22018-05-10 19:13:51 -0400643 self.url))
maruel@chromium.org470b5432011-10-11 18:18:19 +0000644 # In theory we could keep it as a shadow of the other one. In
645 # practice, simply ignore it.
John Budorickd94f8ea2020-03-27 15:55:24 +0000646 logging.warning("Won't process duplicate dependency %s" % sibling)
maruel@chromium.org470b5432011-10-11 18:18:19 +0000647 return False
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000648 return True
maruel@chromium.org064186c2011-09-27 23:53:33 +0000649
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200650 def _postprocess_deps(self, deps, rel_prefix):
Joanna Wang18af7ef2022-07-01 16:51:00 +0000651 # type: (Mapping[str, Mapping[str, str]], str) ->
652 # Mapping[str, Mapping[str, str]]
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200653 """Performs post-processing of deps compared to what's in the DEPS file."""
Joanna Wang18af7ef2022-07-01 16:51:00 +0000654 # If we don't need to sync, only process custom_deps, if any.
655 if not self._should_sync:
656 if not self.custom_deps:
657 return {}
Paweł Hajdan, Jr4426eaf2017-06-13 12:25:47 +0200658
Joanna Wang18af7ef2022-07-01 16:51:00 +0000659 processed_deps = {}
660 for dep_name, dep_info in self.custom_deps.items():
661 if dep_info and not dep_info.endswith('@unmanaged'):
662 if dep_name in deps:
663 # custom_deps that should override an existing deps gets applied
664 # in the Dependency itself with _OverrideUrl().
665 processed_deps[dep_name] = deps[dep_name]
666 else:
667 processed_deps[dep_name] = {'url': dep_info, 'dep_type': 'git'}
668 else:
669 processed_deps = dict(deps)
670
671 # If a line is in custom_deps, but not in the solution, we want to append
672 # this line to the solution.
673 for dep_name, dep_info in self.custom_deps.items():
Andrew Grievee8f9bdc2022-02-09 21:05:12 +0000674 # Don't add it to the solution for the values of "None" and "unmanaged"
675 # in order to force these kinds of custom_deps to act as revision
676 # overrides (via revision_overrides). Having them function as revision
677 # overrides allows them to be applied to recursive dependencies.
678 # https://crbug.com/1031185
Joanna Wang18af7ef2022-07-01 16:51:00 +0000679 if (dep_name not in processed_deps and dep_info
680 and not dep_info.endswith('@unmanaged')):
681 processed_deps[dep_name] = {'url': dep_info, 'dep_type': 'git'}
Edward Lemur16f4bad2018-05-16 16:53:49 -0400682
Michael Moss42d02c22018-02-05 10:32:24 -0800683 # Make child deps conditional on any parent conditions. This ensures that,
684 # when flattened, recursed entries have the correct restrictions, even if
685 # not explicitly set in the recursed DEPS file. For instance, if
686 # "src/ios_foo" is conditional on "checkout_ios=True", then anything
687 # recursively included by "src/ios_foo/DEPS" should also require
688 # "checkout_ios=True".
689 if self.condition:
Joanna Wang18af7ef2022-07-01 16:51:00 +0000690 for value in processed_deps.values():
Edward Lemur16f4bad2018-05-16 16:53:49 -0400691 gclient_eval.UpdateCondition(value, 'and', self.condition)
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200692
Joanna Wang18af7ef2022-07-01 16:51:00 +0000693 if not rel_prefix:
694 return processed_deps
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200695
Joanna Wang18af7ef2022-07-01 16:51:00 +0000696 logging.warning('use_relative_paths enabled.')
697 rel_deps = {}
698 for d, url in processed_deps.items():
699 # normpath is required to allow DEPS to use .. in their
700 # dependency local path.
sokcevic71b606e2023-03-16 23:28:36 +0000701 # We are following the same pattern when use_relative_paths = False,
702 # which uses slashes.
703 rel_deps[os.path.normpath(os.path.join(rel_prefix,
704 d)).replace(os.path.sep,
705 '/')] = url
Joanna Wang18af7ef2022-07-01 16:51:00 +0000706 logging.warning('Updating deps by prepending %s.', rel_prefix)
707 return rel_deps
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200708
709 def _deps_to_objects(self, deps, use_relative_paths):
Joanna Wang18af7ef2022-07-01 16:51:00 +0000710 # type: (Mapping[str, Mapping[str, str]], bool) -> Sequence[Dependency]
711 """Convert a deps dict to a list of Dependency objects."""
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200712 deps_to_add = []
Josip Sokcevic9cbe9a02021-12-01 17:25:16 +0000713 cached_conditions = {}
Marc-Antoine Ruel8e57b4b2019-10-11 01:01:36 +0000714 for name, dep_value in deps.items():
Michael Mossd683d7c2018-06-15 05:05:17 +0000715 should_process = self.should_process
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200716 if dep_value is None:
717 continue
John Budorick0f7b2002018-01-19 15:46:17 -0800718
Edward Lemur16f4bad2018-05-16 16:53:49 -0400719 condition = dep_value.get('condition')
Michael Mossd683d7c2018-06-15 05:05:17 +0000720 dep_type = dep_value.get('dep_type')
Paweł Hajdan, Jrfc6196b2017-07-27 13:15:25 +0200721
Josip Sokcevic9cbe9a02021-12-01 17:25:16 +0000722
Michael Mossd683d7c2018-06-15 05:05:17 +0000723 if condition and not self._get_option('process_all_deps', False):
Josip Sokcevic9cbe9a02021-12-01 17:25:16 +0000724 if condition not in cached_conditions:
725 cached_conditions[condition] = gclient_eval.EvaluateCondition(
726 condition, self.get_vars())
727 should_process = should_process and cached_conditions[condition]
John Budorick0f7b2002018-01-19 15:46:17 -0800728
Joey Scarr8d3925b2018-07-15 23:36:25 +0000729 # The following option is only set by the 'revinfo' command.
730 if self._get_option('ignore_dep_type', None) == dep_type:
731 continue
732
John Budorick0f7b2002018-01-19 15:46:17 -0800733 if dep_type == 'cipd':
John Budorickd3ba72b2018-03-20 12:27:42 -0700734 cipd_root = self.GetCipdRoot()
John Budorick0f7b2002018-01-19 15:46:17 -0800735 for package in dep_value.get('packages', []):
736 deps_to_add.append(
737 CipdDependency(
Edward Lemure05f18d2018-06-08 17:36:53 +0000738 parent=self,
739 name=name,
740 dep_value=package,
741 cipd_root=cipd_root,
742 custom_vars=self.custom_vars,
Michael Mossd683d7c2018-06-15 05:05:17 +0000743 should_process=should_process,
Edward Lemure05f18d2018-06-08 17:36:53 +0000744 relative=use_relative_paths,
745 condition=condition))
John Budorick0f7b2002018-01-19 15:46:17 -0800746 else:
Michael Mossd683d7c2018-06-15 05:05:17 +0000747 url = dep_value.get('url')
748 deps_to_add.append(
749 GitDependency(
750 parent=self,
751 name=name,
Aravind Vasudevan810598d2022-06-13 21:23:47 +0000752 # Update URL with scheme in protocol_override
Aravind Vasudevan5965d3e2022-06-01 21:51:30 +0000753 url=GitDependency.updateProtocol(url, self.protocol),
Edward Lemure4213702018-06-21 21:15:50 +0000754 managed=True,
Michael Mossd683d7c2018-06-15 05:05:17 +0000755 custom_deps=None,
756 custom_vars=self.custom_vars,
757 custom_hooks=None,
758 deps_file=self.recursedeps.get(name, self.deps_file),
759 should_process=should_process,
760 should_recurse=name in self.recursedeps,
761 relative=use_relative_paths,
Aravind Vasudevan5965d3e2022-06-01 21:51:30 +0000762 condition=condition,
Aravind Vasudevanb6eaed22023-07-06 20:50:42 +0000763 protocol=self.protocol,
764 git_dependencies_state=self.git_dependencies_state))
John Budorick0f7b2002018-01-19 15:46:17 -0800765
Joanna Wang18af7ef2022-07-01 16:51:00 +0000766 # TODO(crbug.com/1341285): Understand why we need this and remove
767 # it if we don't.
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200768 deps_to_add.sort(key=lambda x: x.name)
769 return deps_to_add
770
Edward Lemure05f18d2018-06-08 17:36:53 +0000771 def ParseDepsFile(self):
Joanna Wang18af7ef2022-07-01 16:51:00 +0000772 # type: () -> None
maruel@chromium.org271375b2010-06-23 19:17:38 +0000773 """Parses the DEPS file for this dependency."""
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000774 assert not self.deps_parsed
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000775 assert not self.dependencies
iannucci@chromium.org0c3f3052014-05-22 00:29:22 +0000776
777 deps_content = None
cmp@chromium.org76ce73c2014-07-02 00:13:18 +0000778
779 # First try to locate the configured deps file. If it's missing, fallback
780 # to DEPS.
781 deps_files = [self.deps_file]
782 if 'DEPS' not in deps_files:
783 deps_files.append('DEPS')
784 for deps_file in deps_files:
785 filepath = os.path.join(self.root.root_dir, self.name, deps_file)
786 if os.path.isfile(filepath):
787 logging.info(
788 'ParseDepsFile(%s): %s file found at %s', self.name, deps_file,
789 filepath)
790 break
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000791 logging.info(
cmp@chromium.org76ce73c2014-07-02 00:13:18 +0000792 'ParseDepsFile(%s): No %s file found at %s', self.name, deps_file,
793 filepath)
794
795 if os.path.isfile(filepath):
maruel@chromium.org46304292010-10-28 11:42:00 +0000796 deps_content = gclient_utils.FileRead(filepath)
cmp@chromium.org76ce73c2014-07-02 00:13:18 +0000797 logging.debug('ParseDepsFile(%s) read:\n%s', self.name, deps_content)
iannucci@chromium.org0c3f3052014-05-22 00:29:22 +0000798
799 local_scope = {}
800 if deps_content:
maruel@chromium.org46304292010-10-28 11:42:00 +0000801 try:
Edward Lesmes6c24d372018-03-28 12:52:29 -0400802 local_scope = gclient_eval.Parse(
Edward Lemur67cabcd2020-03-03 19:31:15 +0000803 deps_content, filepath, self.get_vars(), self.get_builtin_vars())
vapier@chromium.orga81a56e2015-11-11 07:56:13 +0000804 except SyntaxError as e:
maruel@chromium.org46304292010-10-28 11:42:00 +0000805 gclient_utils.SyntaxErrorToError(filepath, e)
iannucci@chromium.org0c3f3052014-05-22 00:29:22 +0000806
Aravind Vasudevanb6eaed22023-07-06 20:50:42 +0000807 if 'git_dependencies' in local_scope:
808 self.git_dependencies_state = local_scope['git_dependencies']
809
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000810 if 'allowed_hosts' in local_scope:
811 try:
812 self._allowed_hosts = frozenset(local_scope.get('allowed_hosts'))
813 except TypeError: # raised if non-iterable
814 pass
815 if not self._allowed_hosts:
816 logging.warning("allowed_hosts is specified but empty %s",
817 self._allowed_hosts)
818 raise gclient_utils.Error(
819 'ParseDepsFile(%s): allowed_hosts must be absent '
820 'or a non-empty iterable' % self.name)
821
Michael Moss848c86e2018-05-03 16:05:50 -0700822 self._gn_args_from = local_scope.get('gclient_gn_args_from')
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +0200823 self._gn_args_file = local_scope.get('gclient_gn_args_file')
824 self._gn_args = local_scope.get('gclient_gn_args', [])
Michael Moss848c86e2018-05-03 16:05:50 -0700825 # It doesn't make sense to set all of these, since setting gn_args_from to
826 # another DEPS will make gclient ignore any other local gn_args* settings.
827 assert not (self._gn_args_from and self._gn_args_file), \
828 'Only specify one of "gclient_gn_args_from" or ' \
829 '"gclient_gn_args_file + gclient_gn_args".'
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +0200830
Edward Lesmes0b899352018-03-19 21:59:55 +0000831 self._vars = local_scope.get('vars', {})
Paweł Hajdan, Jr1407d002017-08-01 20:01:01 +0200832 if self.parent:
Marc-Antoine Ruel8e57b4b2019-10-11 01:01:36 +0000833 for key, value in self.parent.get_vars().items():
Paweł Hajdan, Jr1407d002017-08-01 20:01:01 +0200834 if key in self._vars:
835 self._vars[key] = value
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200836 # Since we heavily post-process things, freeze ones which should
837 # reflect original state of DEPS.
Paweł Hajdan, Jr1407d002017-08-01 20:01:01 +0200838 self._vars = gclient_utils.freeze(self._vars)
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200839
840 # If use_relative_paths is set in the DEPS file, regenerate
841 # the dictionary using paths relative to the directory containing
842 # the DEPS file. Also update recursedeps if use_relative_paths is
843 # enabled.
844 # If the deps file doesn't set use_relative_paths, but the parent did
845 # (and therefore set self.relative on this Dependency object), then we
846 # want to modify the deps and recursedeps by prepending the parent
847 # directory of this dependency.
Corentin Wallez271a78a2020-07-12 15:41:46 +0000848 self._use_relative_paths = local_scope.get('use_relative_paths', False)
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200849 rel_prefix = None
Corentin Wallez271a78a2020-07-12 15:41:46 +0000850 if self._use_relative_paths:
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200851 rel_prefix = self.name
852 elif self._relative:
853 rel_prefix = os.path.dirname(self.name)
854
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200855 if 'recursion' in local_scope:
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200856 logging.warning(
Edward Lemurfbb06aa2018-06-11 20:43:06 +0000857 '%s: Ignoring recursion = %d.', self.name, local_scope['recursion'])
858
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200859 if 'recursedeps' in local_scope:
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200860 for ent in local_scope['recursedeps']:
Aaron Gableac9b0f32019-04-18 17:38:37 +0000861 if isinstance(ent, basestring):
Edward Lemurfbb06aa2018-06-11 20:43:06 +0000862 self.recursedeps[ent] = self.deps_file
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200863 else: # (depname, depsfilename)
Edward Lemurfbb06aa2018-06-11 20:43:06 +0000864 self.recursedeps[ent[0]] = ent[1]
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200865 logging.warning('Found recursedeps %r.', repr(self.recursedeps))
866
867 if rel_prefix:
868 logging.warning('Updating recursedeps by prepending %s.', rel_prefix)
869 rel_deps = {}
Marc-Antoine Ruel8e57b4b2019-10-11 01:01:36 +0000870 for depname, options in self.recursedeps.items():
sokcevic71b606e2023-03-16 23:28:36 +0000871 rel_deps[os.path.normpath(os.path.join(rel_prefix, depname)).replace(
872 os.path.sep, '/')] = options
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200873 self.recursedeps = rel_deps
Michael Moss848c86e2018-05-03 16:05:50 -0700874 # To get gn_args from another DEPS, that DEPS must be recursed into.
875 if self._gn_args_from:
876 assert self.recursedeps and self._gn_args_from in self.recursedeps, \
877 'The "gclient_gn_args_from" value must be in recursedeps.'
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200878
879 # If present, save 'target_os' in the local_target_os property.
880 if 'target_os' in local_scope:
881 self.local_target_os = local_scope['target_os']
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200882
Edward Lemur16f4bad2018-05-16 16:53:49 -0400883 deps = local_scope.get('deps', {})
Aravind Vasudevanaf456fd2023-07-07 00:17:33 +0000884
885 # If dependencies are configured within git submodules, add them to DEPS.
886 if self.git_dependencies_state in (gclient_eval.SUBMODULES,
887 gclient_eval.SYNC):
888 deps.update(self.ParseGitSubmodules())
889
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200890 deps_to_add = self._deps_to_objects(
Corentin Wallez271a78a2020-07-12 15:41:46 +0000891 self._postprocess_deps(deps, rel_prefix), self._use_relative_paths)
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000892
Corentin Walleza68660d2018-09-10 17:33:24 +0000893 # compute which working directory should be used for hooks
Michael Spang0e99b9b2020-08-12 13:34:48 +0000894 if local_scope.get('use_relative_hooks', False):
Joanna Wang4e6264c2022-06-30 19:10:43 +0000895 print('use_relative_hooks is deprecated, please remove it from '
896 '%s DEPS. (it was merged in use_relative_paths)' % self.name,
897 file=sys.stderr)
Michael Spang0e99b9b2020-08-12 13:34:48 +0000898
Corentin Walleza68660d2018-09-10 17:33:24 +0000899 hooks_cwd = self.root.root_dir
Corentin Wallez801c2022020-07-20 20:11:09 +0000900 if self._use_relative_paths:
Corentin Walleza68660d2018-09-10 17:33:24 +0000901 hooks_cwd = os.path.join(hooks_cwd, self.name)
902 logging.warning('Updating hook base working directory to %s.',
903 hooks_cwd)
904
Joanna Wang18af7ef2022-07-01 16:51:00 +0000905 # Only add all hooks if we should sync, otherwise just add custom hooks.
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000906 # override named sets of hooks by the custom hooks
907 hooks_to_run = []
Joanna Wang18af7ef2022-07-01 16:51:00 +0000908 if self._should_sync:
909 hook_names_to_suppress = [c.get('name', '') for c in self.custom_hooks]
910 for hook in local_scope.get('hooks', []):
911 if hook.get('name', '') not in hook_names_to_suppress:
912 hooks_to_run.append(hook)
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000913
914 # add the replacements and any additions
915 for hook in self.custom_hooks:
916 if 'action' in hook:
917 hooks_to_run.append(hook)
918
Joanna Wang18af7ef2022-07-01 16:51:00 +0000919 if self.should_recurse and deps_to_add:
Paweł Hajdan, Jr032d5452017-06-22 20:43:53 +0200920 self._pre_deps_hooks = [
Michael Moss42d02c22018-02-05 10:32:24 -0800921 Hook.from_dict(hook, variables=self.get_vars(), verbose=True,
Corentin Walleza68660d2018-09-10 17:33:24 +0000922 conditions=self.condition, cwd_base=hooks_cwd)
Daniel Chenga0c5f082017-10-19 13:35:19 -0700923 for hook in local_scope.get('pre_deps_hooks', [])
924 ]
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000925
Corentin Walleza68660d2018-09-10 17:33:24 +0000926 self.add_dependencies_and_close(deps_to_add, hooks_to_run,
927 hooks_cwd=hooks_cwd)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000928 logging.info('ParseDepsFile(%s) done' % self.name)
929
Aravind Vasudevanaf456fd2023-07-07 00:17:33 +0000930 def ParseGitSubmodules(self):
931 # type: () -> Mapping[str, str]
932 """
933 Parses git submodules and returns a dict of path to DEPS git url entries.
934
935 e.g {<path>: <url>@<commit_hash>}
936 """
937 cwd = os.path.join(self.root.root_dir, self.name)
938 filepath = os.path.join(cwd, '.gitmodules')
939 if not os.path.isfile(filepath):
940 logging.warning('ParseGitSubmodules(): No .gitmodules found at %s',
941 filepath)
942 return {}
943
944 # Get submodule commit hashes
945 result = subprocess2.check_output(['git', 'submodule', 'status'],
946 cwd=cwd).decode('utf-8')
947 commit_hashes = {}
948 for record in result.splitlines():
949 commit, module = record.split(maxsplit=1)
950 commit_hashes[module] = commit[1:]
951
952 # Get .gitmodules fields
953 gitmodules_entries = subprocess2.check_output(
954 ['git', 'config', '--file', filepath, '-l']).decode('utf-8')
955
956 gitmodules = {}
957 for entry in gitmodules_entries.splitlines():
958 key, value = entry.split('=', maxsplit=1)
959
960 # git config keys consist of section.name.key, e.g., submodule.foo.path
961 section, submodule_key = key.split('.', maxsplit=1)
962
963 # Only parse [submodule "foo"] sections from .gitmodules.
964 if section != 'submodule':
965 continue
966
967 # The name of the submodule can contain '.', hence split from the back.
968 submodule, sub_key = submodule_key.rsplit('.', maxsplit=1)
969
970 if submodule not in gitmodules:
971 gitmodules[submodule] = {}
972
973 if sub_key in ('url', 'gclient-condition', 'path'):
974 gitmodules[submodule][sub_key] = value
975
976 # Structure git submodules into a dict of DEPS git url entries.
977 submodules = {}
978 for name, module in gitmodules.items():
979 submodules[module['path']] = {
980 'dep_type': 'git',
981 'url': '{}@{}'.format(module['url'], commit_hashes[name])
982 }
983
984 if 'gclient-condition' in module:
985 submodules[module['path']]['condition'] = module['gclient-condition']
986
987 return submodules
988
Michael Mossd683d7c2018-06-15 05:05:17 +0000989 def _get_option(self, attr, default):
990 obj = self
991 while not hasattr(obj, '_options'):
992 obj = obj.parent
993 return getattr(obj._options, attr, default)
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +0200994
Corentin Walleza68660d2018-09-10 17:33:24 +0000995 def add_dependencies_and_close(self, deps_to_add, hooks, hooks_cwd=None):
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000996 """Adds the dependencies, hooks and mark the parsing as done."""
Corentin Walleza68660d2018-09-10 17:33:24 +0000997 if hooks_cwd == None:
998 hooks_cwd = self.root.root_dir
999
maruel@chromium.orgb9be0652011-10-14 18:05:40 +00001000 for dep in deps_to_add:
maruel@chromium.org470b5432011-10-11 18:18:19 +00001001 if dep.verify_validity():
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001002 self.add_dependency(dep)
Daniel Chenga0c5f082017-10-19 13:35:19 -07001003 self._mark_as_parsed([
1004 Hook.from_dict(
Michael Moss42d02c22018-02-05 10:32:24 -08001005 h, variables=self.get_vars(), verbose=self.root._options.verbose,
Corentin Walleza68660d2018-09-10 17:33:24 +00001006 conditions=self.condition, cwd_base=hooks_cwd)
Daniel Chenga0c5f082017-10-19 13:35:19 -07001007 for h in hooks
1008 ])
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001009
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00001010 def findDepsFromNotAllowedHosts(self):
Corentin Wallezaca984c2018-09-07 21:52:14 +00001011 """Returns a list of dependencies from not allowed hosts.
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00001012
1013 If allowed_hosts is not set, allows all hosts and returns empty list.
1014 """
1015 if not self._allowed_hosts:
1016 return []
1017 bad_deps = []
1018 for dep in self._dependencies:
szager@chromium.orgbd772dd2014-11-05 18:43:08 +00001019 # Don't enforce this for custom_deps.
1020 if dep.name in self._custom_deps:
1021 continue
Michael Mossd683d7c2018-06-15 05:05:17 +00001022 if isinstance(dep.url, basestring):
1023 parsed_url = urlparse.urlparse(dep.url)
1024 if parsed_url.netloc and parsed_url.netloc not in self._allowed_hosts:
1025 bad_deps.append(dep)
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00001026 return bad_deps
1027
Edward Lemure7273d22018-05-10 19:13:51 -04001028 def FuzzyMatchUrl(self, candidates):
Joanna Wang66286612022-06-30 19:59:13 +00001029 # type: (Union[Mapping[str, str], Collection[str]]) -> Optional[str]
Edward Lesmesbb16e332018-03-30 17:54:51 -04001030 """Attempts to find this dependency in the list of candidates.
1031
Edward Lemure7273d22018-05-10 19:13:51 -04001032 It looks first for the URL of this dependency in the list of
Edward Lesmesbb16e332018-03-30 17:54:51 -04001033 candidates. If it doesn't succeed, and the URL ends in '.git', it will try
1034 looking for the URL minus '.git'. Finally it will try to look for the name
1035 of the dependency.
1036
1037 Args:
Edward Lesmesbb16e332018-03-30 17:54:51 -04001038 candidates: list, dict. The list of candidates in which to look for this
1039 dependency. It can contain URLs as above, or dependency names like
1040 "src/some/dep".
1041
1042 Returns:
1043 If this dependency is not found in the list of candidates, returns None.
1044 Otherwise, it returns under which name did we find this dependency:
1045 - Its parsed url: "https://example.com/src.git'
1046 - Its parsed url minus '.git': "https://example.com/src"
1047 - Its name: "src"
1048 """
Edward Lemure7273d22018-05-10 19:13:51 -04001049 if self.url:
1050 origin, _ = gclient_utils.SplitUrlRevision(self.url)
Joanna Wang66286612022-06-30 19:59:13 +00001051 match = gclient_utils.FuzzyMatchRepo(origin, candidates)
1052 if match:
1053 return match
Edward Lesmesbb16e332018-03-30 17:54:51 -04001054 if self.name in candidates:
1055 return self.name
1056 return None
1057
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +00001058 # Arguments number differs from overridden method
Quinten Yearsleyb2cc4a92016-12-15 13:53:26 -08001059 # pylint: disable=arguments-differ
Joanna Wang18af7ef2022-07-01 16:51:00 +00001060 def run(
1061 self,
1062 revision_overrides, # type: Mapping[str, str]
1063 command, # type: str
1064 args, # type: Sequence[str]
1065 work_queue, # type: ExecutionQueue
1066 options, # type: optparse.Values
1067 patch_refs, # type: Mapping[str, str]
Joanna Wanga84a16b2022-07-27 18:52:17 +00001068 target_branches, # type: Mapping[str, str]
1069 skip_sync_revisions, # type: Mapping[str, str]
Joanna Wang18af7ef2022-07-01 16:51:00 +00001070 ):
1071 # type: () -> None
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001072 """Runs |command| then parse the DEPS file."""
maruel@chromium.org470b5432011-10-11 18:18:19 +00001073 logging.info('Dependency(%s).run()' % self.name)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +00001074 assert self._file_list == []
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001075 # When running runhooks, there's no need to consult the SCM.
1076 # All known hooks are expected to run unconditionally regardless of working
1077 # copy state, so skip the SCM status check.
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001078 run_scm = command not in (
1079 'flatten', 'runhooks', 'recurse', 'validate', None)
iannucci@chromium.org396e1a62013-07-03 19:41:04 +00001080 file_list = [] if not options.nohooks else None
Edward Lesmesbb16e332018-03-30 17:54:51 -04001081 revision_override = revision_overrides.pop(
Edward Lemure7273d22018-05-10 19:13:51 -04001082 self.FuzzyMatchUrl(revision_overrides), None)
Edward Lemure4213702018-06-21 21:15:50 +00001083 if not revision_override and not self.managed:
1084 revision_override = 'unmanaged'
Michael Mossd683d7c2018-06-15 05:05:17 +00001085 if run_scm and self.url:
agabled437d762016-10-17 09:35:11 -07001086 # Create a shallow copy to mutate revision.
1087 options = copy.copy(options)
1088 options.revision = revision_override
1089 self._used_revision = options.revision
Edward Lemurbabd0982018-05-11 13:32:37 -04001090 self._used_scm = self.CreateSCM(out_cb=work_queue.out_cb)
Edward Lesmesc8f63d32021-06-02 23:51:53 +00001091 if command != 'update' or self.GetScmName() != 'git':
1092 self._got_revision = self._used_scm.RunCommand(command, options, args,
1093 file_list)
1094 else:
1095 try:
1096 start = time.time()
1097 sync_status = metrics_utils.SYNC_STATUS_FAILURE
1098 self._got_revision = self._used_scm.RunCommand(command, options, args,
1099 file_list)
1100 sync_status = metrics_utils.SYNC_STATUS_SUCCESS
1101 finally:
1102 url, revision = gclient_utils.SplitUrlRevision(self.url)
1103 metrics.collector.add_repeated('git_deps', {
1104 'path': self.name,
1105 'url': url,
1106 'revision': revision,
1107 'execution_time': time.time() - start,
1108 'sync_status': sync_status,
1109 })
Edward Lesmesc621b212018-03-21 20:26:56 -04001110
Joanna Wangf3edc502022-07-20 00:12:10 +00001111 if isinstance(self, GitDependency) and command == 'update':
1112 patch_repo = self.url.split('@')[0]
Josip Sokcevicd47a9c22023-06-22 05:14:35 +00001113 patch_ref = patch_refs.pop(self.FuzzyMatchUrl(patch_refs), None)
1114 target_branch = target_branches.pop(
1115 self.FuzzyMatchUrl(target_branches), None)
Joanna Wangf3edc502022-07-20 00:12:10 +00001116 if patch_ref:
1117 latest_commit = self._used_scm.apply_patch_ref(
1118 patch_repo, patch_ref, target_branch, options, file_list)
1119 else:
1120 latest_commit = self._used_scm.revinfo(None, None, None)
1121 existing_sync_commits = json.loads(
1122 os.environ.get(PREVIOUS_SYNC_COMMITS, '{}'))
1123 existing_sync_commits[self.name] = latest_commit
1124 os.environ[PREVIOUS_SYNC_COMMITS] = json.dumps(existing_sync_commits)
Edward Lesmesc621b212018-03-21 20:26:56 -04001125
agabled437d762016-10-17 09:35:11 -07001126 if file_list:
1127 file_list = [os.path.join(self.name, f.strip()) for f in file_list]
maruel@chromium.org68988972011-09-20 14:11:42 +00001128
1129 # TODO(phajdan.jr): We should know exactly when the paths are absolute.
1130 # Convert all absolute paths to relative.
iannucci@chromium.org396e1a62013-07-03 19:41:04 +00001131 for i in range(len(file_list or [])):
maruel@chromium.org68988972011-09-20 14:11:42 +00001132 # It depends on the command being executed (like runhooks vs sync).
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001133 if not os.path.isabs(file_list[i]):
maruel@chromium.org68988972011-09-20 14:11:42 +00001134 continue
1135 prefix = os.path.commonprefix(
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001136 [self.root.root_dir.lower(), file_list[i].lower()])
1137 file_list[i] = file_list[i][len(prefix):]
maruel@chromium.org68988972011-09-20 14:11:42 +00001138 # Strip any leading path separators.
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001139 while file_list[i].startswith(('\\', '/')):
1140 file_list[i] = file_list[i][1:]
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001141
Joanna Wanga84a16b2022-07-27 18:52:17 +00001142 # We must check for diffs AFTER any patch_refs have been applied.
1143 if skip_sync_revisions:
1144 skip_sync_rev = skip_sync_revisions.pop(
1145 self.FuzzyMatchUrl(skip_sync_revisions), None)
1146 self._should_sync = (skip_sync_rev is None
1147 or self._used_scm.check_diff(skip_sync_rev,
1148 files=['DEPS']))
1149 if not self._should_sync:
1150 logging.debug('Skipping sync for %s. No DEPS changes since last '
1151 'sync at %s' % (self.name, skip_sync_rev))
1152 else:
1153 logging.debug('DEPS changes detected for %s since last sync at '
1154 '%s. Not skipping deps sync' % (
1155 self.name, skip_sync_rev))
1156
Edward Lemurfbb06aa2018-06-11 20:43:06 +00001157 if self.should_recurse:
Edward Lemure05f18d2018-06-08 17:36:53 +00001158 self.ParseDepsFile()
Edward Lesmes5d6cde32018-04-12 18:32:46 -04001159
Edward Lemure7273d22018-05-10 19:13:51 -04001160 self._run_is_done(file_list or [])
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001161
Joanna Wanga84a16b2022-07-27 18:52:17 +00001162 # TODO(crbug.com/1339471): If should_recurse is false, ParseDepsFile never
1163 # gets called meaning we never fetch hooks and dependencies. So there's
1164 # no need to check should_recurse again here.
Edward Lemurfbb06aa2018-06-11 20:43:06 +00001165 if self.should_recurse:
Edward Lesmes5d6cde32018-04-12 18:32:46 -04001166 if command in ('update', 'revert') and not options.noprehooks:
1167 self.RunPreDepsHooks()
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001168 # Parse the dependencies of this dependency.
1169 for s in self.dependencies:
Michael Mossd683d7c2018-06-15 05:05:17 +00001170 if s.should_process:
1171 work_queue.enqueue(s)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001172
1173 if command == 'recurse':
agabled437d762016-10-17 09:35:11 -07001174 # Skip file only checkout.
Edward Lemurbabd0982018-05-11 13:32:37 -04001175 scm = self.GetScmName()
agabled437d762016-10-17 09:35:11 -07001176 if not options.scm or scm in options.scm:
1177 cwd = os.path.normpath(os.path.join(self.root.root_dir, self.name))
1178 # Pass in the SCM type as an env variable. Make sure we don't put
1179 # unicode strings in the environment.
1180 env = os.environ.copy()
Michael Mossd683d7c2018-06-15 05:05:17 +00001181 if scm:
1182 env['GCLIENT_SCM'] = str(scm)
1183 if self.url:
1184 env['GCLIENT_URL'] = str(self.url)
agabled437d762016-10-17 09:35:11 -07001185 env['GCLIENT_DEP_PATH'] = str(self.name)
1186 if options.prepend_dir and scm == 'git':
1187 print_stdout = False
1188 def filter_fn(line):
1189 """Git-specific path marshaling. It is optimized for git-grep."""
ilevy@chromium.org0233ac22012-11-28 20:27:02 +00001190
agabled437d762016-10-17 09:35:11 -07001191 def mod_path(git_pathspec):
1192 match = re.match('^(\\S+?:)?([^\0]+)$', git_pathspec)
1193 modified_path = os.path.join(self.name, match.group(2))
1194 branch = match.group(1) or ''
1195 return '%s%s' % (branch, modified_path)
ilevy@chromium.org0233ac22012-11-28 20:27:02 +00001196
agabled437d762016-10-17 09:35:11 -07001197 match = re.match('^Binary file ([^\0]+) matches$', line)
1198 if match:
1199 print('Binary file %s matches\n' % mod_path(match.group(1)))
1200 return
ilevy@chromium.org0233ac22012-11-28 20:27:02 +00001201
agabled437d762016-10-17 09:35:11 -07001202 items = line.split('\0')
1203 if len(items) == 2 and items[1]:
1204 print('%s : %s' % (mod_path(items[0]), items[1]))
1205 elif len(items) >= 2:
1206 # Multiple null bytes or a single trailing null byte indicate
1207 # git is likely displaying filenames only (such as with -l)
1208 print('\n'.join(mod_path(path) for path in items if path))
1209 else:
1210 print(line)
1211 else:
1212 print_stdout = True
1213 filter_fn = None
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001214
Michael Mossd683d7c2018-06-15 05:05:17 +00001215 if self.url is None:
1216 print('Skipped omitted dependency %s' % cwd, file=sys.stderr)
1217 elif os.path.isdir(cwd):
agabled437d762016-10-17 09:35:11 -07001218 try:
1219 gclient_utils.CheckCallAndFilter(
Ben Masonfbd2c632020-06-22 14:59:13 +00001220 args, cwd=cwd, env=env, print_stdout=print_stdout,
agabled437d762016-10-17 09:35:11 -07001221 filter_fn=filter_fn,
Ben Masonfbd2c632020-06-22 14:59:13 +00001222 )
agabled437d762016-10-17 09:35:11 -07001223 except subprocess2.CalledProcessError:
1224 if not options.ignore:
1225 raise
1226 else:
1227 print('Skipped missing %s' % cwd, file=sys.stderr)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001228
Edward Lemurbabd0982018-05-11 13:32:37 -04001229 def GetScmName(self):
Edward Lemurb61d3872018-05-09 18:42:47 -04001230 raise NotImplementedError()
John Budorick0f7b2002018-01-19 15:46:17 -08001231
Edward Lemurbabd0982018-05-11 13:32:37 -04001232 def CreateSCM(self, out_cb=None):
Edward Lemurb61d3872018-05-09 18:42:47 -04001233 raise NotImplementedError()
John Budorick0f7b2002018-01-19 15:46:17 -08001234
Dirk Pranke9f20d022017-10-11 18:36:54 -07001235 def HasGNArgsFile(self):
1236 return self._gn_args_file is not None
1237
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +02001238 def WriteGNArgsFile(self):
1239 lines = ['# Generated from %r' % self.deps_file]
Paweł Hajdan, Jrb495bf52017-09-25 19:33:50 +02001240 variables = self.get_vars()
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +02001241 for arg in self._gn_args:
Paweł Hajdan, Jre0214742017-09-28 12:21:01 +02001242 value = variables[arg]
Dirk Prankefdd2cd62020-06-30 23:30:47 +00001243 if isinstance(value, gclient_eval.ConstantString):
1244 value = value.value
1245 elif isinstance(value, basestring):
Paweł Hajdan, Jre0214742017-09-28 12:21:01 +02001246 value = gclient_eval.EvaluateCondition(value, variables)
Paweł Hajdan, Jrb495bf52017-09-25 19:33:50 +02001247 lines.append('%s = %s' % (arg, ToGNString(value)))
Corentin Wallez271a78a2020-07-12 15:41:46 +00001248
1249 # When use_relative_paths is set, gn_args_file is relative to this DEPS
1250 path_prefix = self.root.root_dir
1251 if self._use_relative_paths:
Lei Zhang67283c02020-07-13 21:38:44 +00001252 path_prefix = os.path.join(path_prefix, self.name)
Corentin Wallez271a78a2020-07-12 15:41:46 +00001253
1254 with open(os.path.join(path_prefix, self._gn_args_file), 'wb') as f:
Edward Lesmes05934952019-12-19 20:38:09 +00001255 f.write('\n'.join(lines).encode('utf-8', 'replace'))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001256
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001257 @gclient_utils.lockedmethod
Edward Lemure7273d22018-05-10 19:13:51 -04001258 def _run_is_done(self, file_list):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001259 # Both these are kept for hooks that are run as a separate tree traversal.
1260 self._file_list = file_list
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001261 self._processed = True
1262
szager@google.comb9a78d32012-03-13 18:46:21 +00001263 def GetHooks(self, options):
1264 """Evaluates all hooks, and return them in a flat list.
1265
1266 RunOnDeps() must have been called before to load the DEPS.
1267 """
1268 result = []
Michael Mossd683d7c2018-06-15 05:05:17 +00001269 if not self.should_process or not self.should_recurse:
1270 # Don't run the hook when it is above recursion_limit.
1271 return result
maruel@chromium.orgdc7445d2010-07-09 21:05:29 +00001272 # If "--force" was specified, run all hooks regardless of what files have
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001273 # changed.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001274 if self.deps_hooks:
agabled437d762016-10-17 09:35:11 -07001275 # TODO(maruel): If the user is using git, then we don't know
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001276 # what files have changed so we always run all hooks. It'd be nice to fix
1277 # that.
Edward Lemurbabd0982018-05-11 13:32:37 -04001278 result.extend(self.deps_hooks)
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001279 for s in self.dependencies:
szager@google.comb9a78d32012-03-13 18:46:21 +00001280 result.extend(s.GetHooks(options))
1281 return result
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001282
Daniel Chenga0c5f082017-10-19 13:35:19 -07001283 def RunHooksRecursively(self, options, progress):
szager@google.comb9a78d32012-03-13 18:46:21 +00001284 assert self.hooks_ran == False
maruel@chromium.org064186c2011-09-27 23:53:33 +00001285 self._hooks_ran = True
Daniel Chenga0c5f082017-10-19 13:35:19 -07001286 hooks = self.GetHooks(options)
1287 if progress:
1288 progress._total = len(hooks)
1289 for hook in hooks:
Daniel Chenga0c5f082017-10-19 13:35:19 -07001290 if progress:
1291 progress.update(extra=hook.name or '')
Corentin Walleza68660d2018-09-10 17:33:24 +00001292 hook.run()
Daniel Chenga0c5f082017-10-19 13:35:19 -07001293 if progress:
1294 progress.end()
maruel@chromium.orgeaf61062010-07-07 18:42:39 +00001295
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001296 def RunPreDepsHooks(self):
1297 assert self.processed
1298 assert self.deps_parsed
1299 assert not self.pre_deps_hooks_ran
1300 assert not self.hooks_ran
1301 for s in self.dependencies:
1302 assert not s.processed
1303 self._pre_deps_hooks_ran = True
1304 for hook in self.pre_deps_hooks:
Corentin Walleza68660d2018-09-10 17:33:24 +00001305 hook.run()
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001306
John Budorickd3ba72b2018-03-20 12:27:42 -07001307 def GetCipdRoot(self):
1308 if self.root is self:
1309 # Let's not infinitely recurse. If this is root and isn't an
1310 # instance of GClient, do nothing.
1311 return None
1312 return self.root.GetCipdRoot()
1313
Michael Mossd683d7c2018-06-15 05:05:17 +00001314 def subtree(self, include_all):
maruel@chromium.orgad3287e2011-10-03 19:15:10 +00001315 """Breadth first recursion excluding root node."""
maruel@chromium.orgf13a4182011-09-22 00:26:15 +00001316 dependencies = self.dependencies
1317 for d in dependencies:
Michael Mossd683d7c2018-06-15 05:05:17 +00001318 if d.should_process or include_all:
1319 yield d
maruel@chromium.orgf13a4182011-09-22 00:26:15 +00001320 for d in dependencies:
Michael Mossd683d7c2018-06-15 05:05:17 +00001321 for i in d.subtree(include_all):
maruel@chromium.orgad3287e2011-10-03 19:15:10 +00001322 yield i
1323
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001324 @gclient_utils.lockedmethod
1325 def add_dependency(self, new_dep):
1326 self._dependencies.append(new_dep)
1327
1328 @gclient_utils.lockedmethod
Paweł Hajdan, Jr357415c2017-07-24 14:35:28 +02001329 def _mark_as_parsed(self, new_hooks):
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001330 self._deps_hooks.extend(new_hooks)
1331 self._deps_parsed = True
1332
maruel@chromium.org68988972011-09-20 14:11:42 +00001333 @property
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001334 @gclient_utils.lockedmethod
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +00001335 def dependencies(self):
1336 return tuple(self._dependencies)
1337
1338 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001339 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001340 def deps_hooks(self):
1341 return tuple(self._deps_hooks)
1342
1343 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001344 @gclient_utils.lockedmethod
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001345 def pre_deps_hooks(self):
1346 return tuple(self._pre_deps_hooks)
1347
1348 @property
1349 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001350 def deps_parsed(self):
maruel@chromium.org3223edd2011-10-10 23:17:39 +00001351 """This is purely for debugging purposes. It's not used anywhere."""
maruel@chromium.org064186c2011-09-27 23:53:33 +00001352 return self._deps_parsed
1353
1354 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001355 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001356 def processed(self):
1357 return self._processed
1358
1359 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001360 @gclient_utils.lockedmethod
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001361 def pre_deps_hooks_ran(self):
1362 return self._pre_deps_hooks_ran
1363
1364 @property
1365 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001366 def hooks_ran(self):
1367 return self._hooks_ran
1368
1369 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001370 @gclient_utils.lockedmethod
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00001371 def allowed_hosts(self):
1372 return self._allowed_hosts
1373
1374 @property
1375 @gclient_utils.lockedmethod
maruel@chromium.org861fd0f2010-07-23 03:05:05 +00001376 def file_list(self):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001377 return tuple(self._file_list)
1378
1379 @property
kustermann@google.coma692e8f2013-04-18 08:32:04 +00001380 def used_scm(self):
1381 """SCMWrapper instance for this dependency or None if not processed yet."""
1382 return self._used_scm
1383
1384 @property
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00001385 @gclient_utils.lockedmethod
1386 def got_revision(self):
1387 return self._got_revision
1388
1389 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001390 def file_list_and_children(self):
1391 result = list(self.file_list)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +00001392 for d in self.dependencies:
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001393 result.extend(d.file_list_and_children)
maruel@chromium.org68988972011-09-20 14:11:42 +00001394 return tuple(result)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +00001395
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001396 def __str__(self):
1397 out = []
Edward Lemure7273d22018-05-10 19:13:51 -04001398 for i in ('name', 'url', 'custom_deps',
Michael Mossd683d7c2018-06-15 05:05:17 +00001399 'custom_vars', 'deps_hooks', 'file_list', 'should_process',
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00001400 'processed', 'hooks_ran', 'deps_parsed', 'requirements',
1401 'allowed_hosts'):
maruel@chromium.org3c74bc92011-09-15 19:17:21 +00001402 # First try the native property if it exists.
1403 if hasattr(self, '_' + i):
1404 value = getattr(self, '_' + i, False)
1405 else:
1406 value = getattr(self, i, False)
1407 if value:
1408 out.append('%s: %s' % (i, value))
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001409
1410 for d in self.dependencies:
1411 out.extend([' ' + x for x in str(d).splitlines()])
1412 out.append('')
1413 return '\n'.join(out)
1414
1415 def __repr__(self):
1416 return '%s: %s' % (self.name, self.url)
1417
Joanna Wang9144b672023-02-24 23:36:17 +00001418 def hierarchy(self, include_url=True, graphviz=False):
maruel@chromium.orgbc2d2f92010-07-22 21:26:48 +00001419 """Returns a human-readable hierarchical reference to a Dependency."""
Michael Moss4e9b50a2018-05-23 22:35:06 -07001420 def format_name(d):
1421 if include_url:
1422 return '%s(%s)' % (d.name, d.url)
Joanna Wang9144b672023-02-24 23:36:17 +00001423 return '"%s"' % d.name # quotes required for graph dot file.
1424
Michael Moss4e9b50a2018-05-23 22:35:06 -07001425 out = format_name(self)
maruel@chromium.orgbffb9042010-07-22 20:59:36 +00001426 i = self.parent
1427 while i and i.name:
Michael Moss4e9b50a2018-05-23 22:35:06 -07001428 out = '%s -> %s' % (format_name(i), out)
Joanna Wang9144b672023-02-24 23:36:17 +00001429 if graphviz:
1430 # for graphviz we just need each parent->child relationship listed once.
1431 return out
maruel@chromium.orgbffb9042010-07-22 20:59:36 +00001432 i = i.parent
1433 return out
1434
Michael Mossfe68c912018-03-22 19:19:35 -07001435 def hierarchy_data(self):
1436 """Returns a machine-readable hierarchical reference to a Dependency."""
1437 d = self
1438 out = []
1439 while d and d.name:
1440 out.insert(0, (d.name, d.url))
1441 d = d.parent
1442 return tuple(out)
1443
Edward Lemur8f8a50d2018-11-01 22:03:02 +00001444 def get_builtin_vars(self):
1445 return {
Paweł Hajdan, Jrd325eb32017-10-03 17:43:37 +02001446 'checkout_android': 'android' in self.target_os,
Benjamin Pastene6fe29412018-01-23 15:35:58 -08001447 'checkout_chromeos': 'chromeos' in self.target_os,
Paweł Hajdan, Jrd325eb32017-10-03 17:43:37 +02001448 'checkout_fuchsia': 'fuchsia' in self.target_os,
1449 'checkout_ios': 'ios' in self.target_os,
1450 'checkout_linux': 'unix' in self.target_os,
1451 'checkout_mac': 'mac' in self.target_os,
1452 'checkout_win': 'win' in self.target_os,
1453 'host_os': _detect_host_os(),
Robbie Iannucci3db32762023-07-05 19:02:44 +00001454
Tom Andersonc31ae0b2018-02-06 14:48:56 -08001455 'checkout_arm': 'arm' in self.target_cpu,
1456 'checkout_arm64': 'arm64' in self.target_cpu,
1457 'checkout_x86': 'x86' in self.target_cpu,
1458 'checkout_mips': 'mips' in self.target_cpu,
Wang Qing254538b2018-07-26 02:23:53 +00001459 'checkout_mips64': 'mips64' in self.target_cpu,
Tom Andersonc31ae0b2018-02-06 14:48:56 -08001460 'checkout_ppc': 'ppc' in self.target_cpu,
1461 'checkout_s390': 's390' in self.target_cpu,
1462 'checkout_x64': 'x64' in self.target_cpu,
1463 'host_cpu': detect_host_arch.HostArch(),
Paweł Hajdan, Jra3b67ae2017-08-30 15:18:21 +02001464 }
Edward Lemur8f8a50d2018-11-01 22:03:02 +00001465
1466 def get_vars(self):
1467 """Returns a dictionary of effective variable values
1468 (DEPS file contents with applied custom_vars overrides)."""
1469 # Variable precedence (last has highest):
Michael Mossda55cdc2018-04-06 18:37:19 -07001470 # - DEPS vars
1471 # - parents, from first to last
Edward Lemur8f8a50d2018-11-01 22:03:02 +00001472 # - built-in
Michael Mossda55cdc2018-04-06 18:37:19 -07001473 # - custom_vars overrides
Edward Lemur8f8a50d2018-11-01 22:03:02 +00001474 result = {}
Paweł Hajdan, Jra3b67ae2017-08-30 15:18:21 +02001475 result.update(self._vars)
Michael Mossda55cdc2018-04-06 18:37:19 -07001476 if self.parent:
Dirk Prankefdd2cd62020-06-30 23:30:47 +00001477 merge_vars(result, self.parent.get_vars())
Edward Lemur8f8a50d2018-11-01 22:03:02 +00001478 # Provide some built-in variables.
1479 result.update(self.get_builtin_vars())
Dirk Prankefdd2cd62020-06-30 23:30:47 +00001480 merge_vars(result, self.custom_vars)
1481
Paweł Hajdan, Jrd3790252017-07-03 21:06:24 +02001482 return result
1483
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001484
Paweł Hajdan, Jra3b67ae2017-08-30 15:18:21 +02001485_PLATFORM_MAPPING = {
1486 'cygwin': 'win',
1487 'darwin': 'mac',
1488 'linux2': 'linux',
Edward Lemuree7b9dd2019-07-20 01:29:08 +00001489 'linux': 'linux',
Paweł Hajdan, Jra3b67ae2017-08-30 15:18:21 +02001490 'win32': 'win',
Jaideep Bajwad05f3582017-09-11 12:31:48 -04001491 'aix6': 'aix',
Paweł Hajdan, Jra3b67ae2017-08-30 15:18:21 +02001492}
1493
1494
Dirk Prankefdd2cd62020-06-30 23:30:47 +00001495def merge_vars(result, new_vars):
1496 for k, v in new_vars.items():
1497 if k in result:
1498 if isinstance(result[k], gclient_eval.ConstantString):
1499 if isinstance(v, gclient_eval.ConstantString):
1500 result[k] = v
1501 else:
1502 result[k].value = v
1503 else:
1504 result[k] = v
1505 else:
1506 result[k] = v
1507
1508
Paweł Hajdan, Jra3b67ae2017-08-30 15:18:21 +02001509def _detect_host_os():
Jonas Termansenbf7eb522023-01-19 17:56:40 +00001510 if sys.platform in _PLATFORM_MAPPING:
1511 return _PLATFORM_MAPPING[sys.platform]
1512
1513 try:
1514 return os.uname().sysname.lower()
1515 except AttributeError:
1516 return sys.platform
Paweł Hajdan, Jra3b67ae2017-08-30 15:18:21 +02001517
1518
Edward Lemurb61d3872018-05-09 18:42:47 -04001519class GitDependency(Dependency):
1520 """A Dependency object that represents a single git checkout."""
1521
Aravind Vasudevan5965d3e2022-06-01 21:51:30 +00001522 @staticmethod
1523 def updateProtocol(url, protocol):
1524 """Updates given URL's protocol"""
1525 # only works on urls, skips local paths
1526 if not url or not protocol or not re.match('([a-z]+)://', url) or \
1527 re.match('file://', url):
1528 return url
1529
1530 return re.sub('^([a-z]+):', protocol + ':', url)
1531
Edward Lemurb61d3872018-05-09 18:42:47 -04001532 #override
Edward Lemurbabd0982018-05-11 13:32:37 -04001533 def GetScmName(self):
Edward Lemurb61d3872018-05-09 18:42:47 -04001534 """Always 'git'."""
Edward Lemurb61d3872018-05-09 18:42:47 -04001535 return 'git'
1536
1537 #override
Edward Lemurbabd0982018-05-11 13:32:37 -04001538 def CreateSCM(self, out_cb=None):
Edward Lemurb61d3872018-05-09 18:42:47 -04001539 """Create a Wrapper instance suitable for handling this git dependency."""
Edward Lemurbabd0982018-05-11 13:32:37 -04001540 return gclient_scm.GitWrapper(
1541 self.url, self.root.root_dir, self.name, self.outbuf, out_cb,
1542 print_outbuf=self.print_outbuf)
Edward Lemurb61d3872018-05-09 18:42:47 -04001543
1544
1545class GClient(GitDependency):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001546 """Object that represent a gclient checkout. A tree of Dependency(), one per
1547 solution or DEPS entry."""
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001548
1549 DEPS_OS_CHOICES = {
Jaideep Bajwad05f3582017-09-11 12:31:48 -04001550 "aix6": "unix",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001551 "win32": "win",
1552 "win": "win",
1553 "cygwin": "win",
1554 "darwin": "mac",
1555 "mac": "mac",
1556 "unix": "unix",
1557 "linux": "unix",
1558 "linux2": "unix",
maruel@chromium.org244e3442011-06-12 15:20:55 +00001559 "linux3": "unix",
szager@chromium.orgf8c95cd2012-06-01 22:26:52 +00001560 "android": "android",
Michael Mossc54fa812017-08-17 11:27:58 -07001561 "ios": "ios",
Sergiy Byelozyorov518bb682018-06-03 11:25:58 +02001562 "fuchsia": "fuchsia",
Michael Moss484d74f2019-02-06 01:55:43 +00001563 "chromeos": "chromeos",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001564 }
1565
1566 DEFAULT_CLIENT_FILE_TEXT = ("""\
1567solutions = [
Edward Lesmes05934952019-12-19 20:38:09 +00001568 { "name" : %(solution_name)r,
1569 "url" : %(solution_url)r,
1570 "deps_file" : %(deps_file)r,
1571 "managed" : %(managed)r,
smutae7ea312016-07-18 11:59:41 -07001572 "custom_deps" : {
1573 },
Paweł Hajdan, Jr3ba2a7c2017-10-04 19:24:46 +02001574 "custom_vars": %(custom_vars)r,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001575 },
1576]
Robert Iannuccia19649b2018-06-29 16:31:45 +00001577""")
1578
1579 DEFAULT_CLIENT_CACHE_DIR_TEXT = ("""\
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001580cache_dir = %(cache_dir)r
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001581""")
1582
Robert Iannuccia19649b2018-06-29 16:31:45 +00001583
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001584 DEFAULT_SNAPSHOT_FILE_TEXT = ("""\
1585# Snapshot generated with gclient revinfo --snapshot
Edward Lesmesc2960242018-03-06 20:50:15 -05001586solutions = %(solution_list)s
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001587""")
1588
1589 def __init__(self, root_dir, options):
maruel@chromium.org0d812442010-08-10 12:41:08 +00001590 # Do not change previous behavior. Only solution level and immediate DEPS
1591 # are processed.
1592 self._recursion_limit = 2
Edward Lemure05f18d2018-06-08 17:36:53 +00001593 super(GClient, self).__init__(
1594 parent=None,
1595 name=None,
Michael Mossd683d7c2018-06-15 05:05:17 +00001596 url=None,
Edward Lemure05f18d2018-06-08 17:36:53 +00001597 managed=True,
1598 custom_deps=None,
1599 custom_vars=None,
1600 custom_hooks=None,
1601 deps_file='unused',
Michael Mossd683d7c2018-06-15 05:05:17 +00001602 should_process=True,
Edward Lemurfbb06aa2018-06-11 20:43:06 +00001603 should_recurse=True,
Edward Lemure05f18d2018-06-08 17:36:53 +00001604 relative=None,
1605 condition=None,
1606 print_outbuf=True)
1607
maruel@chromium.org0d425922010-06-21 19:22:24 +00001608 self._options = options
maruel@chromium.org271375b2010-06-23 19:17:38 +00001609 if options.deps_os:
1610 enforced_os = options.deps_os.split(',')
1611 else:
1612 enforced_os = [self.DEPS_OS_CHOICES.get(sys.platform, 'unix')]
1613 if 'all' in enforced_os:
Edward Lemuree7b9dd2019-07-20 01:29:08 +00001614 enforced_os = self.DEPS_OS_CHOICES.values()
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001615 self._enforced_os = tuple(set(enforced_os))
Aravind Vasudevanc5f0cbb2022-01-24 23:56:57 +00001616 self._enforced_cpu = (detect_host_arch.HostArch(), )
maruel@chromium.org271375b2010-06-23 19:17:38 +00001617 self._root_dir = root_dir
John Budorickd3ba72b2018-03-20 12:27:42 -07001618 self._cipd_root = None
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001619 self.config_content = None
1620
borenet@google.com88d10082014-03-21 17:24:48 +00001621 def _CheckConfig(self):
1622 """Verify that the config matches the state of the existing checked-out
1623 solutions."""
1624 for dep in self.dependencies:
Michael Mossd683d7c2018-06-15 05:05:17 +00001625 if dep.managed and dep.url:
Edward Lemurbabd0982018-05-11 13:32:37 -04001626 scm = dep.CreateSCM()
smut@google.comd33eab32014-07-07 19:35:18 +00001627 actual_url = scm.GetActualRemoteURL(self._options)
borenet@google.com4e9be262014-04-08 19:40:30 +00001628 if actual_url and not scm.DoesRemoteURLMatch(self._options):
levarum@chromium.org27a6f9a2016-05-28 00:21:49 +00001629 mirror = scm.GetCacheMirror()
1630 if mirror:
1631 mirror_string = '%s (exists=%s)' % (mirror.mirror_path,
1632 mirror.exists())
1633 else:
1634 mirror_string = 'not used'
Raul Tambreb946b232019-03-26 14:48:46 +00001635 raise gclient_utils.Error(
1636 '''
borenet@google.com88d10082014-03-21 17:24:48 +00001637Your .gclient file seems to be broken. The requested URL is different from what
borenet@google.com0a427372014-04-02 19:12:13 +00001638is actually checked out in %(checkout_path)s.
borenet@google.com88d10082014-03-21 17:24:48 +00001639
borenet@google.com97882362014-04-07 20:06:02 +00001640The .gclient file contains:
levarum@chromium.org27a6f9a2016-05-28 00:21:49 +00001641URL: %(expected_url)s (%(expected_scm)s)
1642Cache mirror: %(mirror_string)s
borenet@google.com97882362014-04-07 20:06:02 +00001643
1644The local checkout in %(checkout_path)s reports:
1645%(actual_url)s (%(actual_scm)s)
borenet@google.com88d10082014-03-21 17:24:48 +00001646
1647You should ensure that the URL listed in .gclient is correct and either change
agabled437d762016-10-17 09:35:11 -07001648it or fix the checkout.
Josip Sokcevic14a83ae2020-05-21 01:36:34 +00001649''' % {
1650 'checkout_path': os.path.join(self.root_dir, dep.name),
1651 'expected_url': dep.url,
1652 'expected_scm': dep.GetScmName(),
1653 'mirror_string': mirror_string,
1654 'actual_url': actual_url,
1655 'actual_scm': dep.GetScmName()
1656 })
borenet@google.com88d10082014-03-21 17:24:48 +00001657
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001658 def SetConfig(self, content):
maruel@chromium.orgf13a4182011-09-22 00:26:15 +00001659 assert not self.dependencies
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001660 config_dict = {}
1661 self.config_content = content
1662 try:
1663 exec(content, config_dict)
vapier@chromium.orga81a56e2015-11-11 07:56:13 +00001664 except SyntaxError as e:
maruel@chromium.org5990f9d2010-07-07 18:02:58 +00001665 gclient_utils.SyntaxErrorToError('.gclient', e)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001666
peter@chromium.org1efccc82012-04-27 16:34:38 +00001667 # Append any target OS that is not already being enforced to the tuple.
1668 target_os = config_dict.get('target_os', [])
stuartmorgan@chromium.org18a4f6a2012-11-02 13:37:24 +00001669 if config_dict.get('target_os_only', False):
1670 self._enforced_os = tuple(set(target_os))
1671 else:
1672 self._enforced_os = tuple(set(self._enforced_os).union(target_os))
1673
Tom Andersonc31ae0b2018-02-06 14:48:56 -08001674 # Append any target CPU that is not already being enforced to the tuple.
1675 target_cpu = config_dict.get('target_cpu', [])
1676 if config_dict.get('target_cpu_only', False):
1677 self._enforced_cpu = tuple(set(target_cpu))
1678 else:
1679 self._enforced_cpu = tuple(set(self._enforced_cpu).union(target_cpu))
1680
Robert Iannuccia19649b2018-06-29 16:31:45 +00001681 cache_dir = config_dict.get('cache_dir', UNSET_CACHE_DIR)
1682 if cache_dir is not UNSET_CACHE_DIR:
1683 if cache_dir:
1684 cache_dir = os.path.join(self.root_dir, cache_dir)
1685 cache_dir = os.path.abspath(cache_dir)
Andrii Shyshkalov77ce4bd2017-11-27 12:38:18 -08001686
Robert Iannuccia19649b2018-06-29 16:31:45 +00001687 git_cache.Mirror.SetCachePath(cache_dir)
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001688
stuartmorgan@chromium.org18a4f6a2012-11-02 13:37:24 +00001689 if not target_os and config_dict.get('target_os_only', False):
1690 raise gclient_utils.Error('Can\'t use target_os_only if target_os is '
1691 'not specified')
peter@chromium.org1efccc82012-04-27 16:34:38 +00001692
Tom Andersonc31ae0b2018-02-06 14:48:56 -08001693 if not target_cpu and config_dict.get('target_cpu_only', False):
1694 raise gclient_utils.Error('Can\'t use target_cpu_only if target_cpu is '
1695 'not specified')
1696
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001697 deps_to_add = []
Michael Mossd683d7c2018-06-15 05:05:17 +00001698 for s in config_dict.get('solutions', []):
1699 try:
Aravind Vasudevanb6eaed22023-07-06 20:50:42 +00001700 deps_to_add.append(
1701 GitDependency(
1702 parent=self,
1703 name=s['name'],
1704 # Update URL with scheme in protocol_override
1705 url=GitDependency.updateProtocol(
1706 s['url'], s.get('protocol_override', None)),
1707 managed=s.get('managed', True),
1708 custom_deps=s.get('custom_deps', {}),
1709 custom_vars=s.get('custom_vars', {}),
1710 custom_hooks=s.get('custom_hooks', []),
1711 deps_file=s.get('deps_file', 'DEPS'),
1712 should_process=True,
1713 should_recurse=True,
1714 relative=None,
1715 condition=None,
1716 print_outbuf=True,
1717 # Pass protocol_override down the tree for child deps to use.
1718 protocol=s.get('protocol_override', None),
1719 git_dependencies_state=self.git_dependencies_state))
Michael Mossd683d7c2018-06-15 05:05:17 +00001720 except KeyError:
1721 raise gclient_utils.Error('Invalid .gclient file. Solution is '
1722 'incomplete: %s' % s)
Edward Lemur40764b02018-07-20 18:50:29 +00001723 metrics.collector.add(
1724 'project_urls',
1725 [
Edward Lemuraffd4102019-06-05 18:07:49 +00001726 dep.FuzzyMatchUrl(metrics_utils.KNOWN_PROJECT_URLS)
Edward Lemur40764b02018-07-20 18:50:29 +00001727 for dep in deps_to_add
1728 if dep.FuzzyMatchUrl(metrics_utils.KNOWN_PROJECT_URLS)
1729 ]
1730 )
1731
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001732 self.add_dependencies_and_close(deps_to_add, config_dict.get('hooks', []))
1733 logging.info('SetConfig() done')
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001734
1735 def SaveConfig(self):
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001736 gclient_utils.FileWrite(os.path.join(self.root_dir,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001737 self._options.config_filename),
1738 self.config_content)
1739
1740 @staticmethod
1741 def LoadCurrentConfig(options):
Joanna Wang66286612022-06-30 19:59:13 +00001742 # type: (optparse.Values) -> GClient
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001743 """Searches for and loads a .gclient file relative to the current working
Joanna Wang66286612022-06-30 19:59:13 +00001744 dir."""
szager@chromium.orge2e03202012-07-31 18:05:16 +00001745 if options.spec:
1746 client = GClient('.', options)
1747 client.SetConfig(options.spec)
1748 else:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00001749 if options.verbose:
1750 print('Looking for %s starting from %s\n' % (
1751 options.config_filename, os.getcwd()))
Nico Weber09e0b382019-03-11 16:54:07 +00001752 path = gclient_paths.FindGclientRoot(os.getcwd(), options.config_filename)
szager@chromium.orge2e03202012-07-31 18:05:16 +00001753 if not path:
Michael Achenbachb3ce73d2017-10-11 16:41:27 +02001754 if options.verbose:
1755 print('Couldn\'t find configuration file.')
szager@chromium.orge2e03202012-07-31 18:05:16 +00001756 return None
1757 client = GClient(path, options)
1758 client.SetConfig(gclient_utils.FileRead(
1759 os.path.join(path, options.config_filename)))
maruel@chromium.org69392e72011-10-13 22:09:00 +00001760
1761 if (options.revisions and
1762 len(client.dependencies) > 1 and
1763 any('@' not in r for r in options.revisions)):
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001764 print(
1765 ('You must specify the full solution name like --revision %s@%s\n'
1766 'when you have multiple solutions setup in your .gclient file.\n'
1767 'Other solutions present are: %s.') % (
maruel@chromium.org69392e72011-10-13 22:09:00 +00001768 client.dependencies[0].name,
1769 options.revisions[0],
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001770 ', '.join(s.name for s in client.dependencies[1:])),
1771 file=sys.stderr)
Joanna Wang66286612022-06-30 19:59:13 +00001772
maruel@chromium.org15804092010-09-02 17:07:37 +00001773 return client
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001774
nsylvain@google.comefc80932011-05-31 21:27:56 +00001775 def SetDefaultConfig(self, solution_name, deps_file, solution_url,
Robert Iannuccia19649b2018-06-29 16:31:45 +00001776 managed=True, cache_dir=UNSET_CACHE_DIR,
1777 custom_vars=None):
1778 text = self.DEFAULT_CLIENT_FILE_TEXT
1779 format_dict = {
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001780 'solution_name': solution_name,
1781 'solution_url': solution_url,
nsylvain@google.comefc80932011-05-31 21:27:56 +00001782 'deps_file': deps_file,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001783 'managed': managed,
Paweł Hajdan, Jr3ba2a7c2017-10-04 19:24:46 +02001784 'custom_vars': custom_vars or {},
Robert Iannuccia19649b2018-06-29 16:31:45 +00001785 }
1786
1787 if cache_dir is not UNSET_CACHE_DIR:
1788 text += self.DEFAULT_CLIENT_CACHE_DIR_TEXT
1789 format_dict['cache_dir'] = cache_dir
1790
1791 self.SetConfig(text % format_dict)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001792
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001793 def _SaveEntries(self):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001794 """Creates a .gclient_entries file to record the list of unique checkouts.
1795
1796 The .gclient_entries file lives in the same directory as .gclient.
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001797 """
1798 # Sometimes pprint.pformat will use {', sometimes it'll use { ' ... It
1799 # makes testing a bit too fun.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001800 result = 'entries = {\n'
Michael Mossd683d7c2018-06-15 05:05:17 +00001801 for entry in self.root.subtree(False):
agabled437d762016-10-17 09:35:11 -07001802 result += ' %s: %s,\n' % (pprint.pformat(entry.name),
Edward Lemure7273d22018-05-10 19:13:51 -04001803 pprint.pformat(entry.url))
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001804 result += '}\n'
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001805 file_path = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org1333cb32011-10-04 23:40:16 +00001806 logging.debug(result)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001807 gclient_utils.FileWrite(file_path, result)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001808
1809 def _ReadEntries(self):
1810 """Read the .gclient_entries file for the given client.
1811
1812 Returns:
1813 A sequence of solution names, which will be empty if there is the
1814 entries file hasn't been created yet.
1815 """
1816 scope = {}
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001817 filename = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001818 if not os.path.exists(filename):
maruel@chromium.org73e21142010-07-05 13:32:01 +00001819 return {}
maruel@chromium.org5990f9d2010-07-07 18:02:58 +00001820 try:
1821 exec(gclient_utils.FileRead(filename), scope)
vapier@chromium.orga81a56e2015-11-11 07:56:13 +00001822 except SyntaxError as e:
maruel@chromium.org5990f9d2010-07-07 18:02:58 +00001823 gclient_utils.SyntaxErrorToError(filename, e)
Aaron Gable3721ee92017-04-03 14:53:14 -07001824 return scope.get('entries', {})
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001825
Joanna Wang01870792022-08-01 19:02:57 +00001826 def _ExtractFileJsonContents(self, default_filename):
1827 # type: (str) -> Mapping[str,Any]
1828 f = os.path.join(self.root_dir, default_filename)
1829
1830 if not os.path.exists(f):
1831 logging.info('File %s does not exist.' % f)
1832 return {}
1833
1834 with open(f, 'r') as open_f:
1835 logging.info('Reading content from file %s' % f)
1836 content = open_f.read().rstrip()
1837 if content:
1838 return json.loads(content)
1839 return {}
1840
1841 def _WriteFileContents(self, default_filename, content):
1842 # type: (str, str) -> None
1843 f = os.path.join(self.root_dir, default_filename)
1844
1845 with open(f, 'w') as open_f:
1846 logging.info('Writing to file %s' % f)
1847 open_f.write(content)
1848
Joanna Wang66286612022-06-30 19:59:13 +00001849 def _EnforceSkipSyncRevisions(self, patch_refs):
1850 # type: (Mapping[str, str]) -> Mapping[str, str]
1851 """Checks for and enforces revisions for skipping deps syncing."""
Joanna Wang01870792022-08-01 19:02:57 +00001852 previous_sync_commits = self._ExtractFileJsonContents(
1853 PREVIOUS_SYNC_COMMITS_FILE)
Joanna Wangf3edc502022-07-20 00:12:10 +00001854
1855 if not previous_sync_commits:
Joanna Wang66286612022-06-30 19:59:13 +00001856 return {}
1857
1858 # Current `self.dependencies` only contain solutions. If a patch_ref is
1859 # not for a solution, then it is for a solution's dependency or recursed
Joanna Wangf3edc502022-07-20 00:12:10 +00001860 # dependency which we cannot support while skipping sync.
Joanna Wang66286612022-06-30 19:59:13 +00001861 if patch_refs:
1862 unclaimed_prs = []
1863 candidates = []
1864 for dep in self.dependencies:
1865 origin, _ = gclient_utils.SplitUrlRevision(dep.url)
1866 candidates.extend([origin, dep.name])
1867 for patch_repo in patch_refs:
1868 if not gclient_utils.FuzzyMatchRepo(patch_repo, candidates):
1869 unclaimed_prs.append(patch_repo)
1870 if unclaimed_prs:
1871 print(
Joanna Wangf3edc502022-07-20 00:12:10 +00001872 'We cannot skip syncs when there are --patch-refs flags for '
1873 'non-solution dependencies. To skip syncing, remove patch_refs '
1874 'for: \n%s' % '\n'.join(unclaimed_prs))
Joanna Wang66286612022-06-30 19:59:13 +00001875 return {}
1876
1877 # We cannot skip syncing if there are custom_vars that differ from the
1878 # previous run's custom_vars.
Joanna Wang01870792022-08-01 19:02:57 +00001879 previous_custom_vars = self._ExtractFileJsonContents(
1880 PREVIOUS_CUSTOM_VARS_FILE)
1881
Joanna Wang66286612022-06-30 19:59:13 +00001882 cvs_by_name = {s.name: s.custom_vars for s in self.dependencies}
Joanna Wangf3edc502022-07-20 00:12:10 +00001883
Joanna Wang66286612022-06-30 19:59:13 +00001884 skip_sync_revisions = {}
Joanna Wangf3edc502022-07-20 00:12:10 +00001885 for name, commit in previous_sync_commits.items():
Joanna Wang01870792022-08-01 19:02:57 +00001886 previous_vars = previous_custom_vars.get(name)
1887 if previous_vars == cvs_by_name.get(name) or (not previous_vars and
1888 not cvs_by_name.get(name)):
Joanna Wangf3edc502022-07-20 00:12:10 +00001889 skip_sync_revisions[name] = commit
Joanna Wang66286612022-06-30 19:59:13 +00001890 else:
Joanna Wangf3edc502022-07-20 00:12:10 +00001891 print('We cannot skip syncs when custom_vars for solutions have '
1892 'changed since the last sync run on this machine.\n'
1893 '\nRemoving skip_sync_revision for:\n'
Joanna Wang66286612022-06-30 19:59:13 +00001894 'solution: %s, current: %r, previous: %r.' %
1895 (name, cvs_by_name.get(name), previous_vars))
Joanna Wanga84a16b2022-07-27 18:52:17 +00001896 print('no-sync experiment enabled with %r' % skip_sync_revisions)
Joanna Wang66286612022-06-30 19:59:13 +00001897 return skip_sync_revisions
1898
1899 # TODO(crbug.com/1340695): Remove handling revisions without '@'.
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001900 def _EnforceRevisions(self):
maruel@chromium.org918a9ae2010-05-28 15:50:30 +00001901 """Checks for revision overrides."""
1902 revision_overrides = {}
smutae7ea312016-07-18 11:59:41 -07001903 if self._options.head:
1904 return revision_overrides
joi@chromium.org792ea882010-11-10 02:37:27 +00001905 if not self._options.revisions:
maruel@chromium.org307d1792010-05-31 20:03:13 +00001906 return revision_overrides
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001907 solutions_names = [s.name for s in self.dependencies]
Joanna Wanga84a16b2022-07-27 18:52:17 +00001908 for index, revision in enumerate(self._options.revisions):
smutae7ea312016-07-18 11:59:41 -07001909 if not '@' in revision:
maruel@chromium.org307d1792010-05-31 20:03:13 +00001910 # Support for --revision 123
smutae7ea312016-07-18 11:59:41 -07001911 revision = '%s@%s' % (solutions_names[index], revision)
1912 name, rev = revision.split('@', 1)
szager@chromium.org4ad264b2014-05-20 04:43:47 +00001913 revision_overrides[name] = rev
maruel@chromium.org918a9ae2010-05-28 15:50:30 +00001914 return revision_overrides
1915
Edward Lemur6a4e31b2018-08-10 19:59:02 +00001916 def _EnforcePatchRefsAndBranches(self):
Joanna Wang66286612022-06-30 19:59:13 +00001917 # type: () -> Tuple[Mapping[str, str], Mapping[str, str]]
Edward Lesmesc621b212018-03-21 20:26:56 -04001918 """Checks for patch refs."""
1919 patch_refs = {}
Edward Lemur6a4e31b2018-08-10 19:59:02 +00001920 target_branches = {}
Edward Lesmesc621b212018-03-21 20:26:56 -04001921 if not self._options.patch_refs:
Edward Lemur6a4e31b2018-08-10 19:59:02 +00001922 return patch_refs, target_branches
Edward Lesmesc621b212018-03-21 20:26:56 -04001923 for given_patch_ref in self._options.patch_refs:
1924 patch_repo, _, patch_ref = given_patch_ref.partition('@')
Edward Lemur4c5c8ab2019-06-07 15:58:13 +00001925 if not patch_repo or not patch_ref or ':' not in patch_ref:
Edward Lesmesc621b212018-03-21 20:26:56 -04001926 raise gclient_utils.Error(
1927 'Wrong revision format: %s should be of the form '
Edward Lemur4c5c8ab2019-06-07 15:58:13 +00001928 'patch_repo@target_branch:patch_ref.' % given_patch_ref)
1929 target_branch, _, patch_ref = patch_ref.partition(':')
1930 target_branches[patch_repo] = target_branch
Edward Lesmesc621b212018-03-21 20:26:56 -04001931 patch_refs[patch_repo] = patch_ref
Edward Lemur6a4e31b2018-08-10 19:59:02 +00001932 return patch_refs, target_branches
Edward Lesmesc621b212018-03-21 20:26:56 -04001933
Edward Lemur5b1fa942018-10-04 23:22:09 +00001934 def _RemoveUnversionedGitDirs(self):
1935 """Remove directories that are no longer part of the checkout.
1936
1937 Notify the user if there is an orphaned entry in their working copy.
1938 Only delete the directory if there are no changes in it, and
1939 delete_unversioned_trees is set to true.
Josip Sokcevic1b8211f2022-09-30 17:46:53 +00001940
1941 Returns CIPD packages that are no longer versioned.
Edward Lemur5b1fa942018-10-04 23:22:09 +00001942 """
1943
Joanna Wang01870792022-08-01 19:02:57 +00001944 entry_names_and_sync = [(i.name, i._should_sync)
1945 for i in self.root.subtree(False) if i.url]
1946 entries = []
1947 if entry_names_and_sync:
1948 entries, _ = zip(*entry_names_and_sync)
Edward Lemur5b1fa942018-10-04 23:22:09 +00001949 full_entries = [os.path.join(self.root_dir, e.replace('/', os.path.sep))
1950 for e in entries]
Joanna Wang01870792022-08-01 19:02:57 +00001951 no_sync_entries = [
1952 name for name, should_sync in entry_names_and_sync if not should_sync
1953 ]
Edward Lemur5b1fa942018-10-04 23:22:09 +00001954
Josip Sokcevic1b8211f2022-09-30 17:46:53 +00001955 removed_cipd_entries = []
Marc-Antoine Ruel8e57b4b2019-10-11 01:01:36 +00001956 for entry, prev_url in self._ReadEntries().items():
Edward Lemur5b1fa942018-10-04 23:22:09 +00001957 if not prev_url:
1958 # entry must have been overridden via .gclient custom_deps
1959 continue
Joanna Wang01870792022-08-01 19:02:57 +00001960 if any(entry.startswith(sln) for sln in no_sync_entries):
1961 # Dependencies of solutions that skipped syncing would not
1962 # show up in `entries`.
1963 continue
Josip Sokcevic1b8211f2022-09-30 17:46:53 +00001964 if (':' in entry):
1965 # This is a cipd package. Don't clean it up, but prepare for return
1966 if entry not in entries:
1967 removed_cipd_entries.append(entry)
1968 continue
Edward Lemur5b1fa942018-10-04 23:22:09 +00001969 # Fix path separator on Windows.
1970 entry_fixed = entry.replace('/', os.path.sep)
1971 e_dir = os.path.join(self.root_dir, entry_fixed)
1972 # Use entry and not entry_fixed there.
1973 if (entry not in entries and
1974 (not any(path.startswith(entry + '/') for path in entries)) and
1975 os.path.exists(e_dir)):
1976 # The entry has been removed from DEPS.
1977 scm = gclient_scm.GitWrapper(
1978 prev_url, self.root_dir, entry_fixed, self.outbuf)
1979
1980 # Check to see if this directory is now part of a higher-up checkout.
1981 scm_root = None
1982 try:
1983 scm_root = gclient_scm.scm.GIT.GetCheckoutRoot(scm.checkout_path)
1984 except subprocess2.CalledProcessError:
1985 pass
1986 if not scm_root:
1987 logging.warning('Could not find checkout root for %s. Unable to '
1988 'determine whether it is part of a higher-level '
1989 'checkout, so not removing.' % entry)
1990 continue
1991
1992 # This is to handle the case of third_party/WebKit migrating from
1993 # being a DEPS entry to being part of the main project.
1994 # If the subproject is a Git project, we need to remove its .git
1995 # folder. Otherwise git operations on that folder will have different
1996 # effects depending on the current working directory.
1997 if os.path.abspath(scm_root) == os.path.abspath(e_dir):
1998 e_par_dir = os.path.join(e_dir, os.pardir)
1999 if gclient_scm.scm.GIT.IsInsideWorkTree(e_par_dir):
2000 par_scm_root = gclient_scm.scm.GIT.GetCheckoutRoot(e_par_dir)
2001 # rel_e_dir : relative path of entry w.r.t. its parent repo.
2002 rel_e_dir = os.path.relpath(e_dir, par_scm_root)
2003 if gclient_scm.scm.GIT.IsDirectoryVersioned(
2004 par_scm_root, rel_e_dir):
2005 save_dir = scm.GetGitBackupDirPath()
2006 # Remove any eventual stale backup dir for the same project.
2007 if os.path.exists(save_dir):
2008 gclient_utils.rmtree(save_dir)
2009 os.rename(os.path.join(e_dir, '.git'), save_dir)
2010 # When switching between the two states (entry/ is a subproject
2011 # -> entry/ is part of the outer project), it is very likely
2012 # that some files are changed in the checkout, unless we are
2013 # jumping *exactly* across the commit which changed just DEPS.
2014 # In such case we want to cleanup any eventual stale files
2015 # (coming from the old subproject) in order to end up with a
2016 # clean checkout.
2017 gclient_scm.scm.GIT.CleanupDir(par_scm_root, rel_e_dir)
2018 assert not os.path.exists(os.path.join(e_dir, '.git'))
Raul Tambre80ee78e2019-05-06 22:41:05 +00002019 print('\nWARNING: \'%s\' has been moved from DEPS to a higher '
2020 'level checkout. The git folder containing all the local'
2021 ' branches has been saved to %s.\n'
2022 'If you don\'t care about its state you can safely '
2023 'remove that folder to free up space.' % (entry, save_dir))
Edward Lemur5b1fa942018-10-04 23:22:09 +00002024 continue
2025
2026 if scm_root in full_entries:
2027 logging.info('%s is part of a higher level checkout, not removing',
2028 scm.GetCheckoutRoot())
2029 continue
2030
2031 file_list = []
2032 scm.status(self._options, [], file_list)
2033 modified_files = file_list != []
2034 if (not self._options.delete_unversioned_trees or
2035 (modified_files and not self._options.force)):
2036 # There are modified files in this entry. Keep warning until
2037 # removed.
Henrique Ferreiroe72279d2019-04-17 12:01:50 +00002038 self.add_dependency(
2039 GitDependency(
2040 parent=self,
2041 name=entry,
Aravind Vasudevan810598d2022-06-13 21:23:47 +00002042 # Update URL with scheme in protocol_override
Aravind Vasudevan5965d3e2022-06-01 21:51:30 +00002043 url=GitDependency.updateProtocol(prev_url, self.protocol),
Henrique Ferreiroe72279d2019-04-17 12:01:50 +00002044 managed=False,
2045 custom_deps={},
2046 custom_vars={},
2047 custom_hooks=[],
2048 deps_file=None,
2049 should_process=True,
2050 should_recurse=False,
2051 relative=None,
Aravind Vasudevan5965d3e2022-06-01 21:51:30 +00002052 condition=None,
Aravind Vasudevanb6eaed22023-07-06 20:50:42 +00002053 protocol=self.protocol,
2054 git_dependencies_state=self.git_dependencies_state))
Anthony Politobb457342019-11-15 22:26:01 +00002055 if modified_files and self._options.delete_unversioned_trees:
2056 print('\nWARNING: \'%s\' is no longer part of this client.\n'
2057 'Despite running \'gclient sync -D\' no action was taken '
2058 'as there are modifications.\nIt is recommended you revert '
2059 'all changes or run \'gclient sync -D --force\' next '
2060 'time.' % entry_fixed)
2061 else:
2062 print('\nWARNING: \'%s\' is no longer part of this client.\n'
2063 'It is recommended that you manually remove it or use '
2064 '\'gclient sync -D\' next time.' % entry_fixed)
Edward Lemur5b1fa942018-10-04 23:22:09 +00002065 else:
2066 # Delete the entry
2067 print('\n________ deleting \'%s\' in \'%s\'' % (
2068 entry_fixed, self.root_dir))
2069 gclient_utils.rmtree(e_dir)
2070 # record the current list of entries for next time
2071 self._SaveEntries()
Josip Sokcevic1b8211f2022-09-30 17:46:53 +00002072 return removed_cipd_entries
Edward Lemur5b1fa942018-10-04 23:22:09 +00002073
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00002074 def RunOnDeps(self, command, args, ignore_requirements=False, progress=True):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002075 """Runs a command on each dependency in a client and its dependencies.
2076
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002077 Args:
2078 command: The command to use (e.g., 'status' or 'diff')
2079 args: list of str - extra arguments to add to the command line.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002080 """
Michael Mossd683d7c2018-06-15 05:05:17 +00002081 if not self.dependencies:
2082 raise gclient_utils.Error('No solution specified')
2083
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00002084 revision_overrides = {}
Edward Lesmesc621b212018-03-21 20:26:56 -04002085 patch_refs = {}
Edward Lemur6a4e31b2018-08-10 19:59:02 +00002086 target_branches = {}
Joanna Wanga84a16b2022-07-27 18:52:17 +00002087 skip_sync_revisions = {}
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00002088 # It's unnecessary to check for revision overrides for 'recurse'.
2089 # Save a few seconds by not calling _EnforceRevisions() in that case.
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +02002090 if command not in ('diff', 'recurse', 'runhooks', 'status', 'revert',
2091 'validate'):
szager@chromium.org5273b8a2014-08-21 15:10:10 +00002092 self._CheckConfig()
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00002093 revision_overrides = self._EnforceRevisions()
Edward Lesmesc621b212018-03-21 20:26:56 -04002094
2095 if command == 'update':
Edward Lemur6a4e31b2018-08-10 19:59:02 +00002096 patch_refs, target_branches = self._EnforcePatchRefsAndBranches()
Joanna Wanga84a16b2022-07-27 18:52:17 +00002097 if NO_SYNC_EXPERIMENT in self._options.experiments:
2098 skip_sync_revisions = self._EnforceSkipSyncRevisions(patch_refs)
Joanna Wang66286612022-06-30 19:59:13 +00002099
Joanna Wang01870792022-08-01 19:02:57 +00002100 # Store solutions' custom_vars on memory to compare in the next run.
2101 # All dependencies added later are inherited from the current
2102 # self.dependencies.
2103 custom_vars = {
2104 dep.name: dep.custom_vars
2105 for dep in self.dependencies if dep.custom_vars
2106 }
2107 if custom_vars:
2108 self._WriteFileContents(PREVIOUS_CUSTOM_VARS_FILE,
2109 json.dumps(custom_vars))
Joanna Wangf3edc502022-07-20 00:12:10 +00002110
Daniel Chenga21b5b32017-10-19 20:07:48 +00002111 # Disable progress for non-tty stdout.
Daniel Chenga0c5f082017-10-19 13:35:19 -07002112 should_show_progress = (
2113 setup_color.IS_TTY and not self._options.verbose and progress)
2114 pm = None
2115 if should_show_progress:
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00002116 if command in ('update', 'revert'):
2117 pm = Progress('Syncing projects', 1)
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +02002118 elif command in ('recurse', 'validate'):
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00002119 pm = Progress(' '.join(args), 1)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00002120 work_queue = gclient_utils.ExecutionQueue(
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00002121 self._options.jobs, pm, ignore_requirements=ignore_requirements,
2122 verbose=self._options.verbose)
maruel@chromium.org049bced2010-08-12 13:37:20 +00002123 for s in self.dependencies:
Michael Mossd683d7c2018-06-15 05:05:17 +00002124 if s.should_process:
2125 work_queue.enqueue(s)
Joanna Wanga84a16b2022-07-27 18:52:17 +00002126 work_queue.flush(revision_overrides,
2127 command,
2128 args,
2129 options=self._options,
2130 patch_refs=patch_refs,
2131 target_branches=target_branches,
Josip Sokcevicd47a9c22023-06-22 05:14:35 +00002132 skip_sync_revisions=skip_sync_revisions)
Edward Lesmesc621b212018-03-21 20:26:56 -04002133
szager@chromium.org4ad264b2014-05-20 04:43:47 +00002134 if revision_overrides:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002135 print('Please fix your script, having invalid --revision flags will soon '
Edward Lesmesc621b212018-03-21 20:26:56 -04002136 'be considered an error.', file=sys.stderr)
2137
Josip Sokcevicd47a9c22023-06-22 05:14:35 +00002138 if patch_refs:
Edward Lesmesc621b212018-03-21 20:26:56 -04002139 raise gclient_utils.Error(
2140 'The following --patch-ref flags were not used. Please fix it:\n%s' %
2141 ('\n'.join(
2142 patch_repo + '@' + patch_ref
Marc-Antoine Ruel8e57b4b2019-10-11 01:01:36 +00002143 for patch_repo, patch_ref in patch_refs.items())))
piman@chromium.org6f363722010-04-27 00:41:09 +00002144
Dirk Pranke9f20d022017-10-11 18:36:54 -07002145 # Once all the dependencies have been processed, it's now safe to write
Michael Moss848c86e2018-05-03 16:05:50 -07002146 # out the gn_args_file and run the hooks.
Josip Sokcevic1b8211f2022-09-30 17:46:53 +00002147 removed_cipd_entries = []
Dirk Pranke9f20d022017-10-11 18:36:54 -07002148 if command == 'update':
Ergün Erdoğmuş28190a22022-06-22 08:50:54 +00002149 for dependency in self.dependencies:
2150 gn_args_dep = dependency
2151 if gn_args_dep._gn_args_from:
2152 deps_map = {dep.name: dep for dep in gn_args_dep.dependencies}
2153 gn_args_dep = deps_map.get(gn_args_dep._gn_args_from)
2154 if gn_args_dep and gn_args_dep.HasGNArgsFile():
2155 gn_args_dep.WriteGNArgsFile()
Dirk Pranke9f20d022017-10-11 18:36:54 -07002156
Josip Sokcevic1b8211f2022-09-30 17:46:53 +00002157 removed_cipd_entries = self._RemoveUnversionedGitDirs()
Edward Lemur647e1e72018-09-19 18:15:29 +00002158
2159 # Sync CIPD dependencies once removed deps are deleted. In case a git
2160 # dependency was moved to CIPD, we want to remove the old git directory
2161 # first and then sync the CIPD dep.
2162 if self._cipd_root:
2163 self._cipd_root.run(command)
Josip Sokcevic1b8211f2022-09-30 17:46:53 +00002164 # It's possible that CIPD removed some entries that are now part of git
2165 # worktree. Try to checkout those directories
2166 if removed_cipd_entries:
2167 for cipd_entry in removed_cipd_entries:
2168 cwd = os.path.join(self._root_dir, cipd_entry.split(':')[0])
2169 cwd, tail = os.path.split(cwd)
2170 if cwd:
2171 try:
2172 gclient_scm.scm.GIT.Capture(['checkout', tail], cwd=cwd)
2173 except subprocess2.CalledProcessError:
2174 pass
Edward Lemur647e1e72018-09-19 18:15:29 +00002175
Edward Lemur5b1fa942018-10-04 23:22:09 +00002176 if not self._options.nohooks:
2177 if should_show_progress:
2178 pm = Progress('Running hooks', 1)
2179 self.RunHooksRecursively(self._options, pm)
2180
Joanna Wang01870792022-08-01 19:02:57 +00002181 self._WriteFileContents(PREVIOUS_SYNC_COMMITS_FILE,
2182 os.environ.get(PREVIOUS_SYNC_COMMITS, '{}'))
2183
maruel@chromium.org17cdf762010-05-28 17:30:52 +00002184 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002185
2186 def PrintRevInfo(self):
maruel@chromium.org54a07a22010-06-14 19:07:39 +00002187 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +00002188 raise gclient_utils.Error('No solution specified')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00002189 # Load all the settings.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00002190 work_queue = gclient_utils.ExecutionQueue(
2191 self._options.jobs, None, False, verbose=self._options.verbose)
maruel@chromium.org049bced2010-08-12 13:37:20 +00002192 for s in self.dependencies:
Michael Mossd683d7c2018-06-15 05:05:17 +00002193 if s.should_process:
2194 work_queue.enqueue(s)
Joanna Wanga84a16b2022-07-27 18:52:17 +00002195 work_queue.flush({},
2196 None, [],
2197 options=self._options,
2198 patch_refs=None,
2199 target_branches=None,
2200 skip_sync_revisions=None)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002201
Michael Mossd683d7c2018-06-15 05:05:17 +00002202 def ShouldPrintRevision(dep):
Edward Lesmesbb16e332018-03-30 17:54:51 -04002203 return (not self._options.filter
Edward Lemure7273d22018-05-10 19:13:51 -04002204 or dep.FuzzyMatchUrl(self._options.filter))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002205
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00002206 if self._options.snapshot:
Michael Mossd683d7c2018-06-15 05:05:17 +00002207 json_output = []
2208 # First level at .gclient
2209 for d in self.dependencies:
2210 entries = {}
2211 def GrabDeps(dep):
2212 """Recursively grab dependencies."""
Aravind Vasudevanc5f0cbb2022-01-24 23:56:57 +00002213 for rec_d in dep.dependencies:
2214 rec_d.PinToActualRevision()
2215 if ShouldPrintRevision(rec_d):
2216 entries[rec_d.name] = rec_d.url
2217 GrabDeps(rec_d)
2218
Michael Mossd683d7c2018-06-15 05:05:17 +00002219 GrabDeps(d)
2220 json_output.append({
2221 'name': d.name,
2222 'solution_url': d.url,
2223 'deps_file': d.deps_file,
2224 'managed': d.managed,
2225 'custom_deps': entries,
2226 })
2227 if self._options.output_json == '-':
2228 print(json.dumps(json_output, indent=2, separators=(',', ': ')))
2229 elif self._options.output_json:
2230 with open(self._options.output_json, 'w') as f:
2231 json.dump(json_output, f)
2232 else:
2233 # Print the snapshot configuration file
2234 print(self.DEFAULT_SNAPSHOT_FILE_TEXT % {
2235 'solution_list': pprint.pformat(json_output, indent=2),
2236 })
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +00002237 else:
Michael Mossd683d7c2018-06-15 05:05:17 +00002238 entries = {}
2239 for d in self.root.subtree(False):
2240 if self._options.actual:
2241 d.PinToActualRevision()
2242 if ShouldPrintRevision(d):
2243 entries[d.name] = d.url
2244 if self._options.output_json:
2245 json_output = {
2246 name: {
2247 'url': rev.split('@')[0] if rev else None,
2248 'rev': rev.split('@')[1] if rev and '@' in rev else None,
2249 }
Marc-Antoine Ruel8e57b4b2019-10-11 01:01:36 +00002250 for name, rev in entries.items()
Michael Mossd683d7c2018-06-15 05:05:17 +00002251 }
2252 if self._options.output_json == '-':
2253 print(json.dumps(json_output, indent=2, separators=(',', ': ')))
2254 else:
2255 with open(self._options.output_json, 'w') as f:
2256 json.dump(json_output, f)
2257 else:
2258 keys = sorted(entries.keys())
2259 for x in keys:
2260 print('%s: %s' % (x, entries[x]))
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +00002261 logging.info(str(self))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002262
Edward Lemure05f18d2018-06-08 17:36:53 +00002263 def ParseDepsFile(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00002264 """No DEPS to parse for a .gclient file."""
maruel@chromium.org049bced2010-08-12 13:37:20 +00002265 raise gclient_utils.Error('Internal error')
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00002266
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002267 def PrintLocationAndContents(self):
2268 # Print out the .gclient file. This is longer than if we just printed the
2269 # client dict, but more legible, and it might contain helpful comments.
2270 print('Loaded .gclient config in %s:\n%s' % (
2271 self.root_dir, self.config_content))
2272
John Budorickd3ba72b2018-03-20 12:27:42 -07002273 def GetCipdRoot(self):
2274 if not self._cipd_root:
2275 self._cipd_root = gclient_scm.CipdRoot(
2276 self.root_dir,
2277 # TODO(jbudorick): Support other service URLs as necessary.
2278 # Service URLs should be constant over the scope of a cipd
2279 # root, so a var per DEPS file specifying the service URL
2280 # should suffice.
2281 'https://chrome-infra-packages.appspot.com')
2282 return self._cipd_root
2283
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00002284 @property
maruel@chromium.org75a59272010-06-11 22:34:03 +00002285 def root_dir(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00002286 """Root directory of gclient checkout."""
maruel@chromium.org75a59272010-06-11 22:34:03 +00002287 return self._root_dir
2288
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00002289 @property
maruel@chromium.org271375b2010-06-23 19:17:38 +00002290 def enforced_os(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00002291 """What deps_os entries that are to be parsed."""
maruel@chromium.org271375b2010-06-23 19:17:38 +00002292 return self._enforced_os
2293
maruel@chromium.org68988972011-09-20 14:11:42 +00002294 @property
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +00002295 def target_os(self):
2296 return self._enforced_os
2297
Tom Andersonc31ae0b2018-02-06 14:48:56 -08002298 @property
2299 def target_cpu(self):
2300 return self._enforced_cpu
2301
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002302
John Budorick0f7b2002018-01-19 15:46:17 -08002303class CipdDependency(Dependency):
2304 """A Dependency object that represents a single CIPD package."""
2305
Michael Mossd683d7c2018-06-15 05:05:17 +00002306 def __init__(
2307 self, parent, name, dep_value, cipd_root,
2308 custom_vars, should_process, relative, condition):
Dan Le Febvref2da9062023-05-03 00:35:09 +00002309 package = dep_value['package']
John Budorick0f7b2002018-01-19 15:46:17 -08002310 version = dep_value['version']
2311 url = urlparse.urljoin(
2312 cipd_root.service_url, '%s@%s' % (package, version))
2313 super(CipdDependency, self).__init__(
Edward Lemure05f18d2018-06-08 17:36:53 +00002314 parent=parent,
2315 name=name + ':' + package,
2316 url=url,
2317 managed=None,
2318 custom_deps=None,
2319 custom_vars=custom_vars,
2320 custom_hooks=None,
2321 deps_file=None,
Michael Mossd683d7c2018-06-15 05:05:17 +00002322 should_process=should_process,
Edward Lemurfbb06aa2018-06-11 20:43:06 +00002323 should_recurse=False,
Edward Lemure05f18d2018-06-08 17:36:53 +00002324 relative=relative,
2325 condition=condition)
John Budorickd3ba72b2018-03-20 12:27:42 -07002326 self._cipd_package = None
John Budorick0f7b2002018-01-19 15:46:17 -08002327 self._cipd_root = cipd_root
John Budorick4099daa2018-06-21 19:22:10 +00002328 # CIPD wants /-separated paths, even on Windows.
2329 native_subdir_path = os.path.relpath(
Shenghua Zhang6f830312018-02-26 11:45:07 -08002330 os.path.join(self.root.root_dir, name), cipd_root.root_dir)
John Budorick4099daa2018-06-21 19:22:10 +00002331 self._cipd_subdir = posixpath.join(*native_subdir_path.split(os.sep))
John Budorickd3ba72b2018-03-20 12:27:42 -07002332 self._package_name = package
2333 self._package_version = version
2334
2335 #override
Josip Sokcevicd47a9c22023-06-22 05:14:35 +00002336 def run(self, revision_overrides, command, args, work_queue, options,
2337 patch_refs, target_branches, skip_sync_revisions):
John Budorickd3ba72b2018-03-20 12:27:42 -07002338 """Runs |command| then parse the DEPS file."""
2339 logging.info('CipdDependency(%s).run()' % self.name)
Michael Mossd683d7c2018-06-15 05:05:17 +00002340 if not self.should_process:
2341 return
John Budorickd3ba72b2018-03-20 12:27:42 -07002342 self._CreatePackageIfNecessary()
Joanna Wanga84a16b2022-07-27 18:52:17 +00002343 super(CipdDependency,
2344 self).run(revision_overrides, command, args, work_queue, options,
2345 patch_refs, target_branches, skip_sync_revisions)
John Budorickd3ba72b2018-03-20 12:27:42 -07002346
2347 def _CreatePackageIfNecessary(self):
2348 # We lazily create the CIPD package to make sure that only packages
2349 # that we want (as opposed to all packages defined in all DEPS files
2350 # we parse) get added to the root and subsequently ensured.
2351 if not self._cipd_package:
2352 self._cipd_package = self._cipd_root.add_package(
2353 self._cipd_subdir, self._package_name, self._package_version)
John Budorick0f7b2002018-01-19 15:46:17 -08002354
Edward Lemure05f18d2018-06-08 17:36:53 +00002355 def ParseDepsFile(self):
John Budorick0f7b2002018-01-19 15:46:17 -08002356 """CIPD dependencies are not currently allowed to have nested deps."""
2357 self.add_dependencies_and_close([], [])
2358
2359 #override
Shenghua Zhang6f830312018-02-26 11:45:07 -08002360 def verify_validity(self):
2361 """CIPD dependencies allow duplicate name for packages in same directory."""
2362 logging.info('Dependency(%s).verify_validity()' % self.name)
2363 return True
2364
2365 #override
Edward Lemurbabd0982018-05-11 13:32:37 -04002366 def GetScmName(self):
John Budorick0f7b2002018-01-19 15:46:17 -08002367 """Always 'cipd'."""
John Budorick0f7b2002018-01-19 15:46:17 -08002368 return 'cipd'
2369
Dan Le Febvreb0e8e7a2023-05-18 23:36:46 +00002370 def GetExpandedPackageName(self):
2371 """Return the CIPD package name with the variables evaluated."""
2372 package = self._cipd_root.expand_package_name(self._package_name)
2373 if package:
2374 return package
2375 return self._package_name
2376
John Budorick0f7b2002018-01-19 15:46:17 -08002377 #override
Edward Lemurbabd0982018-05-11 13:32:37 -04002378 def CreateSCM(self, out_cb=None):
John Budorick0f7b2002018-01-19 15:46:17 -08002379 """Create a Wrapper instance suitable for handling this CIPD dependency."""
John Budorickd3ba72b2018-03-20 12:27:42 -07002380 self._CreatePackageIfNecessary()
John Budorick0f7b2002018-01-19 15:46:17 -08002381 return gclient_scm.CipdWrapper(
Edward Lemurbabd0982018-05-11 13:32:37 -04002382 self.url, self.root.root_dir, self.name, self.outbuf, out_cb,
2383 root=self._cipd_root, package=self._cipd_package)
John Budorick0f7b2002018-01-19 15:46:17 -08002384
Joanna Wang9144b672023-02-24 23:36:17 +00002385 def hierarchy(self, include_url=False, graphviz=False):
2386 if graphviz:
2387 return '' # graphviz lines not implemented for cipd deps.
Edward Lemure4e15042018-06-28 18:07:00 +00002388 return self.parent.hierarchy(include_url) + ' -> ' + self._cipd_subdir
2389
John Budorick0f7b2002018-01-19 15:46:17 -08002390 def ToLines(self):
Joanna Wang9144b672023-02-24 23:36:17 +00002391 # () -> Sequence[str]
John Budorick0f7b2002018-01-19 15:46:17 -08002392 """Return a list of lines representing this in a DEPS file."""
John Budorickc35aba52018-06-28 20:57:03 +00002393 def escape_cipd_var(package):
2394 return package.replace('{', '{{').replace('}', '}}')
2395
John Budorick0f7b2002018-01-19 15:46:17 -08002396 s = []
John Budorickd3ba72b2018-03-20 12:27:42 -07002397 self._CreatePackageIfNecessary()
John Budorick0f7b2002018-01-19 15:46:17 -08002398 if self._cipd_package.authority_for_subdir:
2399 condition_part = ([' "condition": %r,' % self.condition]
2400 if self.condition else [])
2401 s.extend([
Michael Moss4e9b50a2018-05-23 22:35:06 -07002402 ' # %s' % self.hierarchy(include_url=False),
John Budorickd3ba72b2018-03-20 12:27:42 -07002403 ' "%s": {' % (self.name.split(':')[0],),
John Budorick0f7b2002018-01-19 15:46:17 -08002404 ' "packages": [',
2405 ])
John Budorick4099daa2018-06-21 19:22:10 +00002406 for p in sorted(
2407 self._cipd_root.packages(self._cipd_subdir),
Edward Lemur26a8b9f2019-08-15 20:46:44 +00002408 key=lambda x: x.name):
John Budorick0f7b2002018-01-19 15:46:17 -08002409 s.extend([
John Budorick64e33cb2018-02-20 09:40:30 -08002410 ' {',
John Budorickc35aba52018-06-28 20:57:03 +00002411 ' "package": "%s",' % escape_cipd_var(p.name),
John Budorick64e33cb2018-02-20 09:40:30 -08002412 ' "version": "%s",' % p.version,
2413 ' },',
John Budorick0f7b2002018-01-19 15:46:17 -08002414 ])
John Budorickd3ba72b2018-03-20 12:27:42 -07002415
John Budorick0f7b2002018-01-19 15:46:17 -08002416 s.extend([
2417 ' ],',
2418 ' "dep_type": "cipd",',
2419 ] + condition_part + [
2420 ' },',
2421 '',
2422 ])
2423 return s
2424
2425
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002426#### gclient commands.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002427
2428
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002429@subcommand.usage('[command] [args ...]')
Edward Lemur3298e7b2018-07-17 18:21:27 +00002430@metrics.collector.collect_metrics('gclient recurse')
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00002431def CMDrecurse(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002432 """Operates [command args ...] on all the dependencies.
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00002433
Arthur Milchior08cd5fe2022-07-28 20:38:47 +00002434 Change directory to each dependency's directory, and call [command
2435 args ...] there. Sets GCLIENT_DEP_PATH environment variable as the
2436 dep's relative location to root directory of the checkout.
2437
2438 Examples:
2439 * `gclient recurse --no-progress -j1 sh -c 'echo "$GCLIENT_DEP_PATH"'`
2440 print the relative path of each dependency.
2441 * `gclient recurse --no-progress -j1 sh -c "pwd"`
2442 print the absolute path of each dependency.
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00002443 """
2444 # Stop parsing at the first non-arg so that these go through to the command
2445 parser.disable_interspersed_args()
2446 parser.add_option('-s', '--scm', action='append', default=[],
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00002447 help='Choose scm types to operate upon.')
maruel@chromium.org288054d2012-03-05 00:43:07 +00002448 parser.add_option('-i', '--ignore', action='store_true',
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00002449 help='Ignore non-zero return codes from subcommands.')
2450 parser.add_option('--prepend-dir', action='store_true',
2451 help='Prepend relative dir for use with git <cmd> --null.')
2452 parser.add_option('--no-progress', action='store_true',
2453 help='Disable progress bar that shows sub-command updates')
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00002454 options, args = parser.parse_args(args)
maruel@chromium.org45e9f2d2010-10-18 13:33:46 +00002455 if not args:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002456 print('Need to supply a command!', file=sys.stderr)
maruel@chromium.org45e9f2d2010-10-18 13:33:46 +00002457 return 1
maruel@chromium.org78cba522010-10-18 13:32:05 +00002458 root_and_entries = gclient_utils.GetGClientRootAndEntries()
2459 if not root_and_entries:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002460 print(
maruel@chromium.org78cba522010-10-18 13:32:05 +00002461 'You need to run gclient sync at least once to use \'recurse\'.\n'
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002462 'This is because .gclient_entries needs to exist and be up to date.',
2463 file=sys.stderr)
maruel@chromium.org78cba522010-10-18 13:32:05 +00002464 return 1
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00002465
2466 # Normalize options.scm to a set()
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00002467 scm_set = set()
2468 for scm in options.scm:
2469 scm_set.update(scm.split(','))
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00002470 options.scm = scm_set
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00002471
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00002472 options.nohooks = True
2473 client = GClient.LoadCurrentConfig(options)
Marc-Antoine Ruele6e06412017-10-18 13:47:02 -04002474 if not client:
2475 raise gclient_utils.Error('client not configured; see \'gclient config\'')
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00002476 return client.RunOnDeps('recurse', args, ignore_requirements=True,
2477 progress=not options.no_progress)
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00002478
2479
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002480@subcommand.usage('[args ...]')
Edward Lemur3298e7b2018-07-17 18:21:27 +00002481@metrics.collector.collect_metrics('gclient fetch')
davidbarr@chromium.org12f944e2012-03-01 02:18:31 +00002482def CMDfetch(parser, args):
2483 """Fetches upstream commits for all modules.
2484
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002485 Completely git-specific. Simply runs 'git fetch [args ...]' for each module.
2486 """
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00002487 (options, args) = parser.parse_args(args)
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002488 return CMDrecurse(OptionParser(), [
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00002489 '--jobs=%d' % options.jobs, '--scm=git', 'git', 'fetch'] + args)
2490
2491
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002492class Flattener(object):
2493 """Flattens a gclient solution."""
2494
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02002495 def __init__(self, client, pin_all_deps=False):
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002496 """Constructor.
2497
2498 Arguments:
2499 client (GClient): client to flatten
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02002500 pin_all_deps (bool): whether to pin all deps, even if they're not pinned
2501 in DEPS
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002502 """
2503 self._client = client
2504
2505 self._deps_string = None
Joanna Wang9144b672023-02-24 23:36:17 +00002506 self._deps_graph_lines = None
Paweł Hajdan, Jrfeb01642017-09-12 15:50:46 +02002507 self._deps_files = set()
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002508
2509 self._allowed_hosts = set()
2510 self._deps = {}
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002511 self._hooks = []
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002512 self._pre_deps_hooks = []
Paweł Hajdan, Jrfb022012017-07-06 18:00:08 +02002513 self._vars = {}
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002514
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02002515 self._flatten(pin_all_deps=pin_all_deps)
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002516
2517 @property
2518 def deps_string(self):
2519 assert self._deps_string is not None
2520 return self._deps_string
2521
Paweł Hajdan, Jrfeb01642017-09-12 15:50:46 +02002522 @property
Joanna Wang9144b672023-02-24 23:36:17 +00002523 def deps_graph_lines(self):
2524 assert self._deps_graph_lines is not None
2525 return self._deps_graph_lines
2526
2527 @property
Paweł Hajdan, Jrfeb01642017-09-12 15:50:46 +02002528 def deps_files(self):
2529 return self._deps_files
2530
Paweł Hajdan, Jr5ec77132017-08-16 19:21:06 +02002531 def _pin_dep(self, dep):
2532 """Pins a dependency to specific full revision sha.
2533
2534 Arguments:
2535 dep (Dependency): dependency to process
2536 """
Michael Mossd683d7c2018-06-15 05:05:17 +00002537 if dep.url is None:
2538 return
2539
Paweł Hajdan, Jr5ec77132017-08-16 19:21:06 +02002540 # Make sure the revision is always fully specified (a hash),
2541 # as opposed to refs or tags which might change. Similarly,
2542 # shortened shas might become ambiguous; make sure to always
2543 # use full one for pinning.
Edward Lemure7273d22018-05-10 19:13:51 -04002544 revision = gclient_utils.SplitUrlRevision(dep.url)[1]
2545 if not revision or not gclient_utils.IsFullGitSha(revision):
2546 dep.PinToActualRevision()
Paweł Hajdan, Jr5ec77132017-08-16 19:21:06 +02002547
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02002548 def _flatten(self, pin_all_deps=False):
2549 """Runs the flattener. Saves resulting DEPS string.
2550
2551 Arguments:
2552 pin_all_deps (bool): whether to pin all deps, even if they're not pinned
2553 in DEPS
2554 """
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002555 for solution in self._client.dependencies:
Paweł Hajdan, Jr11eb7152017-08-10 12:50:11 +02002556 self._add_dep(solution)
Michael Mossd683d7c2018-06-15 05:05:17 +00002557 self._flatten_dep(solution)
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002558
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02002559 if pin_all_deps:
Marc-Antoine Ruel8e57b4b2019-10-11 01:01:36 +00002560 for dep in self._deps.values():
Paweł Hajdan, Jr5ec77132017-08-16 19:21:06 +02002561 self._pin_dep(dep)
Paweł Hajdan, Jr39300ba2017-08-11 16:52:38 +02002562
Paweł Hajdan, Jr76a9d042017-08-18 20:05:41 +02002563 def add_deps_file(dep):
Paweł Hajdan, Jr0870df22017-08-23 17:59:29 +02002564 # Only include DEPS files referenced by recursedeps.
Edward Lemurfbb06aa2018-06-11 20:43:06 +00002565 if not dep.should_recurse:
Paweł Hajdan, Jr0870df22017-08-23 17:59:29 +02002566 return
Paweł Hajdan, Jrfeb01642017-09-12 15:50:46 +02002567 deps_file = dep.deps_file
2568 deps_path = os.path.join(self._client.root_dir, dep.name, deps_file)
Paweł Hajdan, Jr76a9d042017-08-18 20:05:41 +02002569 if not os.path.exists(deps_path):
Paweł Hajdan, Jrfeb01642017-09-12 15:50:46 +02002570 # gclient has a fallback that if deps_file doesn't exist, it'll try
2571 # DEPS. Do the same here.
2572 deps_file = 'DEPS'
2573 deps_path = os.path.join(self._client.root_dir, dep.name, deps_file)
2574 if not os.path.exists(deps_path):
2575 return
Michael Mossd683d7c2018-06-15 05:05:17 +00002576 assert dep.url
Edward Lemure7273d22018-05-10 19:13:51 -04002577 self._deps_files.add((dep.url, deps_file, dep.hierarchy_data()))
Marc-Antoine Ruel8e57b4b2019-10-11 01:01:36 +00002578 for dep in self._deps.values():
Paweł Hajdan, Jr76a9d042017-08-18 20:05:41 +02002579 add_deps_file(dep)
Paweł Hajdan, Jr76a9d042017-08-18 20:05:41 +02002580
Michael Moss848c86e2018-05-03 16:05:50 -07002581 gn_args_dep = self._deps.get(self._client.dependencies[0]._gn_args_from,
2582 self._client.dependencies[0])
Joanna Wang9144b672023-02-24 23:36:17 +00002583
2584 self._deps_graph_lines = _DepsToDotGraphLines(self._deps)
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002585 self._deps_string = '\n'.join(
Michael Moss848c86e2018-05-03 16:05:50 -07002586 _GNSettingsToLines(gn_args_dep._gn_args_file, gn_args_dep._gn_args) +
Joanna Wang9144b672023-02-24 23:36:17 +00002587 _AllowedHostsToLines(self._allowed_hosts) + _DepsToLines(self._deps) +
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002588 _HooksToLines('hooks', self._hooks) +
2589 _HooksToLines('pre_deps_hooks', self._pre_deps_hooks) +
Joanna Wang9144b672023-02-24 23:36:17 +00002590 _VarsToLines(self._vars) + [
2591 '# %s, %s' % (url, deps_file)
2592 for url, deps_file, _ in sorted(self._deps_files)
2593 ] + ['']) # Ensure newline at end of file.
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002594
Paweł Hajdan, Jr11eb7152017-08-10 12:50:11 +02002595 def _add_dep(self, dep):
2596 """Helper to add a dependency to flattened DEPS.
2597
2598 Arguments:
2599 dep (Dependency): dependency to add
2600 """
2601 assert dep.name not in self._deps or self._deps.get(dep.name) == dep, (
2602 dep.name, self._deps.get(dep.name))
Michael Mossd683d7c2018-06-15 05:05:17 +00002603 if dep.url:
2604 self._deps[dep.name] = dep
Paweł Hajdan, Jr11eb7152017-08-10 12:50:11 +02002605
Edward Lemur16f4bad2018-05-16 16:53:49 -04002606 def _flatten_dep(self, dep):
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002607 """Visits a dependency in order to flatten it (see CMDflatten).
2608
2609 Arguments:
2610 dep (Dependency): dependency to process
2611 """
Edward Lemur16f4bad2018-05-16 16:53:49 -04002612 logging.debug('_flatten_dep(%s)', dep.name)
Paweł Hajdan, Jradae2a62017-08-18 16:49:57 +02002613
Edward Lemur16f4bad2018-05-16 16:53:49 -04002614 assert dep.deps_parsed, (
2615 "Attempted to flatten %s but it has not been processed." % dep.name)
Paweł Hajdan, Jrc69b32e2017-08-17 18:47:48 +02002616
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002617 self._allowed_hosts.update(dep.allowed_hosts)
2618
Michael Mossce9f17f2018-01-31 13:16:35 -08002619 # Only include vars explicitly listed in the DEPS files or gclient solution,
2620 # not automatic, local overrides (i.e. not all of dep.get_vars()).
Michael Moss4e9b50a2018-05-23 22:35:06 -07002621 hierarchy = dep.hierarchy(include_url=False)
Marc-Antoine Ruel8e57b4b2019-10-11 01:01:36 +00002622 for key, value in dep._vars.items():
Paweł Hajdan, Jrc9353602017-08-02 17:52:08 +02002623 # Make sure there are no conflicting variables. It is fine however
2624 # to use same variable name, as long as the value is consistent.
Takuto Ikuta575872e2019-02-21 15:20:07 +00002625 assert key not in self._vars or self._vars[key][1] == value, (
2626 "dep:%s key:%s value:%s != %s" % (
2627 dep.name, key, value, self._vars[key][1]))
Michael Mossce9f17f2018-01-31 13:16:35 -08002628 self._vars[key] = (hierarchy, value)
2629 # Override explicit custom variables.
Marc-Antoine Ruel8e57b4b2019-10-11 01:01:36 +00002630 for key, value in dep.custom_vars.items():
Michael Mossce9f17f2018-01-31 13:16:35 -08002631 # Do custom_vars that don't correspond to DEPS vars ever make sense? DEPS
2632 # conditionals shouldn't be using vars that aren't also defined in the
2633 # DEPS (presubmit actually disallows this), so any new custom_var must be
2634 # unused in the DEPS, so no need to add it to the flattened output either.
2635 if key not in self._vars:
2636 continue
2637 # Don't "override" existing vars if it's actually the same value.
Aravind Vasudevanc5f0cbb2022-01-24 23:56:57 +00002638 if self._vars[key][1] == value:
Michael Mossce9f17f2018-01-31 13:16:35 -08002639 continue
2640 # Anything else is overriding a default value from the DEPS.
2641 self._vars[key] = (hierarchy + ' [custom_var override]', value)
Paweł Hajdan, Jrfb022012017-07-06 18:00:08 +02002642
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002643 self._pre_deps_hooks.extend([(dep, hook) for hook in dep.pre_deps_hooks])
Edward Lemur16f4bad2018-05-16 16:53:49 -04002644 self._hooks.extend([(dep, hook) for hook in dep.deps_hooks])
Paweł Hajdan, Jradae2a62017-08-18 16:49:57 +02002645
Paweł Hajdan, Jrb0ad16e2017-08-03 15:33:21 +02002646 for sub_dep in dep.dependencies:
Edward Lemur16f4bad2018-05-16 16:53:49 -04002647 self._add_dep(sub_dep)
Paweł Hajdan, Jrb0ad16e2017-08-03 15:33:21 +02002648
Edward Lemurfbb06aa2018-06-11 20:43:06 +00002649 for d in dep.dependencies:
2650 if d.should_recurse:
2651 self._flatten_dep(d)
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002652
2653
Edward Lemur3298e7b2018-07-17 18:21:27 +00002654@metrics.collector.collect_metrics('gclient flatten')
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002655def CMDflatten(parser, args):
2656 """Flattens the solutions into a single DEPS file."""
2657 parser.add_option('--output-deps', help='Path to the output DEPS file')
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02002658 parser.add_option(
Paweł Hajdan, Jrfeb01642017-09-12 15:50:46 +02002659 '--output-deps-files',
2660 help=('Path to the output metadata about DEPS files referenced by '
2661 'recursedeps.'))
2662 parser.add_option(
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02002663 '--pin-all-deps', action='store_true',
2664 help=('Pin all deps, even if not pinned in DEPS. CAVEAT: only does so '
2665 'for checked out deps, NOT deps_os.'))
Joanna Wang9144b672023-02-24 23:36:17 +00002666 parser.add_option('--deps-graph-file',
2667 help='Provide a path for the output graph file')
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002668 options, args = parser.parse_args(args)
2669
2670 options.nohooks = True
Paweł Hajdan, Jr5ec77132017-08-16 19:21:06 +02002671 options.process_all_deps = True
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002672 client = GClient.LoadCurrentConfig(options)
Gavin Makf6b414c2021-01-12 19:10:41 +00002673 if not client:
2674 raise gclient_utils.Error('client not configured; see \'gclient config\'')
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002675
2676 # Only print progress if we're writing to a file. Otherwise, progress updates
2677 # could obscure intended output.
2678 code = client.RunOnDeps('flatten', args, progress=options.output_deps)
2679 if code != 0:
2680 return code
2681
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02002682 flattener = Flattener(client, pin_all_deps=options.pin_all_deps)
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002683
2684 if options.output_deps:
2685 with open(options.output_deps, 'w') as f:
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002686 f.write(flattener.deps_string)
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002687 else:
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002688 print(flattener.deps_string)
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002689
Joanna Wang9144b672023-02-24 23:36:17 +00002690 if options.deps_graph_file:
2691 with open(options.deps_graph_file, 'w') as f:
2692 f.write('\n'.join(flattener.deps_graph_lines))
2693
Michael Mossfe68c912018-03-22 19:19:35 -07002694 deps_files = [{'url': d[0], 'deps_file': d[1], 'hierarchy': d[2]}
Paweł Hajdan, Jrfeb01642017-09-12 15:50:46 +02002695 for d in sorted(flattener.deps_files)]
2696 if options.output_deps_files:
2697 with open(options.output_deps_files, 'w') as f:
2698 json.dump(deps_files, f)
2699
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002700 return 0
2701
2702
Paweł Hajdan, Jr3c2aa832017-06-07 20:22:16 +02002703def _GNSettingsToLines(gn_args_file, gn_args):
2704 s = []
2705 if gn_args_file:
2706 s.extend([
2707 'gclient_gn_args_file = "%s"' % gn_args_file,
2708 'gclient_gn_args = %r' % gn_args,
2709 ])
2710 return s
2711
2712
Paweł Hajdan, Jr6014b562017-06-30 17:43:42 +02002713def _AllowedHostsToLines(allowed_hosts):
2714 """Converts |allowed_hosts| set to list of lines for output."""
2715 if not allowed_hosts:
2716 return []
2717 s = ['allowed_hosts = [']
2718 for h in sorted(allowed_hosts):
2719 s.append(' "%s",' % h)
2720 s.extend([']', ''])
2721 return s
2722
2723
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002724def _DepsToLines(deps):
Joanna Wang9144b672023-02-24 23:36:17 +00002725 # type: (Mapping[str, Dependency]) -> Sequence[str]
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002726 """Converts |deps| dict to list of lines for output."""
Paweł Hajdan, Jr5b593352017-06-29 18:37:45 +02002727 if not deps:
2728 return []
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002729 s = ['deps = {']
Edward Lemuree7b9dd2019-07-20 01:29:08 +00002730 for _, dep in sorted(deps.items()):
John Budorick0f7b2002018-01-19 15:46:17 -08002731 s.extend(dep.ToLines())
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002732 s.extend(['}', ''])
2733 return s
2734
2735
Joanna Wang9144b672023-02-24 23:36:17 +00002736def _DepsToDotGraphLines(deps):
2737 # type: (Mapping[str, Dependency]) -> Sequence[str]
2738 """Converts |deps| dict to list of lines for dot graphs"""
2739 if not deps:
2740 return []
2741 graph_lines = ["digraph {\n\trankdir=\"LR\";"]
2742 for _, dep in sorted(deps.items()):
2743 line = dep.hierarchy(include_url=False, graphviz=True)
2744 if line:
2745 graph_lines.append("\t%s" % line)
2746 graph_lines.append("}")
2747 return graph_lines
2748
2749
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02002750def _DepsOsToLines(deps_os):
2751 """Converts |deps_os| dict to list of lines for output."""
Paweł Hajdan, Jr5b593352017-06-29 18:37:45 +02002752 if not deps_os:
2753 return []
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02002754 s = ['deps_os = {']
Edward Lemuree7b9dd2019-07-20 01:29:08 +00002755 for dep_os, os_deps in sorted(deps_os.items()):
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02002756 s.append(' "%s": {' % dep_os)
Edward Lemuree7b9dd2019-07-20 01:29:08 +00002757 for name, dep in sorted(os_deps.items()):
Paweł Hajdan, Jr78ce24e2017-10-03 17:09:13 +02002758 condition_part = ([' "condition": %r,' % dep.condition]
Paweł Hajdan, Jrad30de62017-06-26 18:51:58 +02002759 if dep.condition else [])
2760 s.extend([
Michael Moss4e9b50a2018-05-23 22:35:06 -07002761 ' # %s' % dep.hierarchy(include_url=False),
Paweł Hajdan, Jrad30de62017-06-26 18:51:58 +02002762 ' "%s": {' % (name,),
Edward Lemure05f18d2018-06-08 17:36:53 +00002763 ' "url": "%s",' % (dep.url,),
Paweł Hajdan, Jrad30de62017-06-26 18:51:58 +02002764 ] + condition_part + [
2765 ' },',
2766 '',
2767 ])
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02002768 s.extend([' },', ''])
2769 s.extend(['}', ''])
2770 return s
2771
2772
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002773def _HooksToLines(name, hooks):
2774 """Converts |hooks| list to list of lines for output."""
Paweł Hajdan, Jr5b593352017-06-29 18:37:45 +02002775 if not hooks:
2776 return []
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002777 s = ['%s = [' % name]
2778 for dep, hook in hooks:
2779 s.extend([
Michael Moss4e9b50a2018-05-23 22:35:06 -07002780 ' # %s' % dep.hierarchy(include_url=False),
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002781 ' {',
2782 ])
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +02002783 if hook.name is not None:
2784 s.append(' "name": "%s",' % hook.name)
2785 if hook.pattern is not None:
2786 s.append(' "pattern": "%s",' % hook.pattern)
Paweł Hajdan, Jrecf53fe2017-09-29 18:28:49 +02002787 if hook.condition is not None:
Paweł Hajdan, Jr78ce24e2017-10-03 17:09:13 +02002788 s.append(' "condition": %r,' % hook.condition)
Corentin Walleza68660d2018-09-10 17:33:24 +00002789 # Flattened hooks need to be written relative to the root gclient dir
2790 cwd = os.path.relpath(os.path.normpath(hook.effective_cwd))
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002791 s.extend(
Corentin Walleza68660d2018-09-10 17:33:24 +00002792 [' "cwd": "%s",' % cwd] +
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002793 [' "action": ['] +
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +02002794 [' "%s",' % arg for arg in hook.action] +
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002795 [' ]', ' },', '']
2796 )
2797 s.extend([']', ''])
2798 return s
2799
2800
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +02002801def _HooksOsToLines(hooks_os):
2802 """Converts |hooks| list to list of lines for output."""
Paweł Hajdan, Jr5b593352017-06-29 18:37:45 +02002803 if not hooks_os:
2804 return []
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +02002805 s = ['hooks_os = {']
Marc-Antoine Ruel8e57b4b2019-10-11 01:01:36 +00002806 for hook_os, os_hooks in hooks_os.items():
Michael Moss017bcf62017-06-28 15:26:38 -07002807 s.append(' "%s": [' % hook_os)
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +02002808 for dep, hook in os_hooks:
2809 s.extend([
Michael Moss4e9b50a2018-05-23 22:35:06 -07002810 ' # %s' % dep.hierarchy(include_url=False),
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +02002811 ' {',
2812 ])
2813 if hook.name is not None:
2814 s.append(' "name": "%s",' % hook.name)
2815 if hook.pattern is not None:
2816 s.append(' "pattern": "%s",' % hook.pattern)
Paweł Hajdan, Jrecf53fe2017-09-29 18:28:49 +02002817 if hook.condition is not None:
Paweł Hajdan, Jr78ce24e2017-10-03 17:09:13 +02002818 s.append(' "condition": %r,' % hook.condition)
Corentin Walleza68660d2018-09-10 17:33:24 +00002819 # Flattened hooks need to be written relative to the root gclient dir
2820 cwd = os.path.relpath(os.path.normpath(hook.effective_cwd))
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +02002821 s.extend(
Corentin Walleza68660d2018-09-10 17:33:24 +00002822 [' "cwd": "%s",' % cwd] +
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +02002823 [' "action": ['] +
2824 [' "%s",' % arg for arg in hook.action] +
2825 [' ]', ' },', '']
2826 )
Michael Moss017bcf62017-06-28 15:26:38 -07002827 s.extend([' ],', ''])
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +02002828 s.extend(['}', ''])
2829 return s
2830
2831
Paweł Hajdan, Jrfb022012017-07-06 18:00:08 +02002832def _VarsToLines(variables):
2833 """Converts |variables| dict to list of lines for output."""
2834 if not variables:
2835 return []
2836 s = ['vars = {']
Edward Lemuree7b9dd2019-07-20 01:29:08 +00002837 for key, tup in sorted(variables.items()):
Michael Mossce9f17f2018-01-31 13:16:35 -08002838 hierarchy, value = tup
Paweł Hajdan, Jrfb022012017-07-06 18:00:08 +02002839 s.extend([
Michael Mossce9f17f2018-01-31 13:16:35 -08002840 ' # %s' % hierarchy,
Paweł Hajdan, Jrfb022012017-07-06 18:00:08 +02002841 ' "%s": %r,' % (key, value),
2842 '',
2843 ])
2844 s.extend(['}', ''])
2845 return s
2846
2847
Edward Lemur3298e7b2018-07-17 18:21:27 +00002848@metrics.collector.collect_metrics('gclient grep')
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00002849def CMDgrep(parser, args):
2850 """Greps through git repos managed by gclient.
2851
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002852 Runs 'git grep [args...]' for each module.
2853 """
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00002854 # We can't use optparse because it will try to parse arguments sent
2855 # to git grep and throw an error. :-(
2856 if not args or re.match('(-h|--help)$', args[0]):
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002857 print(
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00002858 'Usage: gclient grep [-j <N>] git-grep-args...\n\n'
2859 'Example: "gclient grep -j10 -A2 RefCountedBase" runs\n"git grep '
2860 '-A2 RefCountedBase" on each of gclient\'s git\nrepos with up to '
2861 '10 jobs.\n\nBonus: page output by appending "|& less -FRSX" to the'
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002862 ' end of your query.',
2863 file=sys.stderr)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00002864 return 1
2865
2866 jobs_arg = ['--jobs=1']
2867 if re.match(r'(-j|--jobs=)\d+$', args[0]):
2868 jobs_arg, args = args[:1], args[1:]
2869 elif re.match(r'(-j|--jobs)$', args[0]):
2870 jobs_arg, args = args[:2], args[2:]
2871
2872 return CMDrecurse(
2873 parser,
2874 jobs_arg + ['--ignore', '--prepend-dir', '--no-progress', '--scm=git',
2875 'git', 'grep', '--null', '--color=Always'] + args)
davidbarr@chromium.org12f944e2012-03-01 02:18:31 +00002876
2877
Edward Lemur3298e7b2018-07-17 18:21:27 +00002878@metrics.collector.collect_metrics('gclient root')
stip@chromium.orga735da22015-04-29 23:18:20 +00002879def CMDroot(parser, args):
2880 """Outputs the solution root (or current dir if there isn't one)."""
2881 (options, args) = parser.parse_args(args)
2882 client = GClient.LoadCurrentConfig(options)
2883 if client:
2884 print(os.path.abspath(client.root_dir))
2885 else:
2886 print(os.path.abspath('.'))
2887
2888
agablea98a6cd2016-11-15 14:30:10 -08002889@subcommand.usage('[url]')
Edward Lemur3298e7b2018-07-17 18:21:27 +00002890@metrics.collector.collect_metrics('gclient config')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002891def CMDconfig(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002892 """Creates a .gclient file in the current directory.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00002893
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002894 This specifies the configuration for further commands. After update/sync,
2895 top-level DEPS files in each module are read to determine dependent
2896 modules to operate on as well. If optional [url] parameter is
2897 provided, then configuration is read from a specified Subversion server
2898 URL.
2899 """
szager@chromium.orge2e03202012-07-31 18:05:16 +00002900 # We do a little dance with the --gclientfile option. 'gclient config' is the
2901 # only command where it's acceptable to have both '--gclientfile' and '--spec'
2902 # arguments. So, we temporarily stash any --gclientfile parameter into
2903 # options.output_config_file until after the (gclientfile xor spec) error
2904 # check.
2905 parser.remove_option('--gclientfile')
2906 parser.add_option('--gclientfile', dest='output_config_file',
2907 help='Specify an alternate .gclient file')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002908 parser.add_option('--name',
2909 help='overrides the default name for the solution')
nsylvain@google.comefc80932011-05-31 21:27:56 +00002910 parser.add_option('--deps-file', default='DEPS',
David Benjamin105e11e2017-10-16 10:39:35 -04002911 help='overrides the default name for the DEPS file for the '
nsylvain@google.comefc80932011-05-31 21:27:56 +00002912 'main solutions and all sub-dependencies')
smutae7ea312016-07-18 11:59:41 -07002913 parser.add_option('--unmanaged', action='store_true', default=False,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00002914 help='overrides the default behavior to make it possible '
smutae7ea312016-07-18 11:59:41 -07002915 'to have the main solution untouched by gclient '
2916 '(gclient will check out unmanaged dependencies but '
2917 'will never sync them)')
Robert Iannuccia19649b2018-06-29 16:31:45 +00002918 parser.add_option('--cache-dir', default=UNSET_CACHE_DIR,
2919 help='Cache all git repos into this dir and do shared '
2920 'clones from the cache, instead of cloning directly '
2921 'from the remote. Pass "None" to disable cache, even '
2922 'if globally enabled due to $GIT_CACHE_PATH.')
Paweł Hajdan, Jr3ba2a7c2017-10-04 19:24:46 +02002923 parser.add_option('--custom-var', action='append', dest='custom_vars',
2924 default=[],
2925 help='overrides variables; key=value syntax')
szager@chromium.orge2e03202012-07-31 18:05:16 +00002926 parser.set_defaults(config_filename=None)
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002927 (options, args) = parser.parse_args(args)
szager@chromium.orge2e03202012-07-31 18:05:16 +00002928 if options.output_config_file:
2929 setattr(options, 'config_filename', getattr(options, 'output_config_file'))
maruel@chromium.org5fc2a332010-05-26 19:37:15 +00002930 if ((options.spec and args) or len(args) > 2 or
2931 (not options.spec and not args)):
2932 parser.error('Inconsistent arguments. Use either --spec or one or 2 args')
2933
Robert Iannuccia19649b2018-06-29 16:31:45 +00002934 if (options.cache_dir is not UNSET_CACHE_DIR
2935 and options.cache_dir.lower() == 'none'):
2936 options.cache_dir = None
2937
Paweł Hajdan, Jr3ba2a7c2017-10-04 19:24:46 +02002938 custom_vars = {}
2939 for arg in options.custom_vars:
2940 kv = arg.split('=', 1)
2941 if len(kv) != 2:
2942 parser.error('Invalid --custom-var argument: %r' % arg)
2943 custom_vars[kv[0]] = gclient_eval.EvaluateCondition(kv[1], {})
2944
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002945 client = GClient('.', options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002946 if options.spec:
2947 client.SetConfig(options.spec)
2948 else:
maruel@chromium.org1ab7ffc2009-06-03 17:21:37 +00002949 base_url = args[0].rstrip('/')
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00002950 if not options.name:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002951 name = base_url.split('/')[-1]
nsylvain@google.com12649ef2011-06-01 17:11:20 +00002952 if name.endswith('.git'):
2953 name = name[:-4]
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00002954 else:
2955 # specify an alternate relpath for the given URL.
2956 name = options.name
agable@chromium.orgf2214672015-10-27 21:02:48 +00002957 if not os.path.abspath(os.path.join(os.getcwd(), name)).startswith(
2958 os.getcwd()):
2959 parser.error('Do not pass a relative path for --name.')
2960 if any(x in ('..', '.', '/', '\\') for x in name.split(os.sep)):
2961 parser.error('Do not include relative path components in --name.')
2962
nsylvain@google.comefc80932011-05-31 21:27:56 +00002963 deps_file = options.deps_file
agablea98a6cd2016-11-15 14:30:10 -08002964 client.SetDefaultConfig(name, deps_file, base_url,
smutae7ea312016-07-18 11:59:41 -07002965 managed=not options.unmanaged,
Paweł Hajdan, Jr3ba2a7c2017-10-04 19:24:46 +02002966 cache_dir=options.cache_dir,
2967 custom_vars=custom_vars)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002968 client.SaveConfig()
maruel@chromium.org79692d62010-05-14 18:57:13 +00002969 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002970
2971
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002972@subcommand.epilog("""Example:
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002973 gclient pack > patch.txt
2974 generate simple patch for configured client and dependences
2975""")
Edward Lemur3298e7b2018-07-17 18:21:27 +00002976@metrics.collector.collect_metrics('gclient pack')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002977def CMDpack(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002978 """Generates a patch which can be applied at the root of the tree.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00002979
agabled437d762016-10-17 09:35:11 -07002980 Internally, runs 'git diff' on each checked out module and
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002981 dependencies, and performs minimal postprocessing of the output. The
2982 resulting patch is printed to stdout and can be applied to a freshly
2983 checked out tree via 'patch -p0 < patchfile'.
2984 """
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002985 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2986 help='override deps for the specified (comma-separated) '
2987 'platform(s); \'all\' will process all deps_os '
2988 'references')
haitao.feng@intel.com306080c2012-05-04 13:11:29 +00002989 parser.remove_option('--jobs')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002990 (options, args) = parser.parse_args(args)
iannucci@chromium.org50395ea2013-04-04 04:47:42 +00002991 # Force jobs to 1 so the stdout is not annotated with the thread ids
haitao.feng@intel.com306080c2012-05-04 13:11:29 +00002992 options.jobs = 1
kbr@google.comab318592009-09-04 00:54:55 +00002993 client = GClient.LoadCurrentConfig(options)
2994 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002995 raise gclient_utils.Error('client not configured; see \'gclient config\'')
kbr@google.comab318592009-09-04 00:54:55 +00002996 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002997 client.PrintLocationAndContents()
kbr@google.comab318592009-09-04 00:54:55 +00002998 return client.RunOnDeps('pack', args)
2999
3000
Edward Lemur3298e7b2018-07-17 18:21:27 +00003001@metrics.collector.collect_metrics('gclient status')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00003002def CMDstatus(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00003003 """Shows modification status for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00003004 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
3005 help='override deps for the specified (comma-separated) '
3006 'platform(s); \'all\' will process all deps_os '
3007 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00003008 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00003009 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00003010 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00003011 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00003012 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00003013 client.PrintLocationAndContents()
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00003014 return client.RunOnDeps('status', args)
3015
3016
maruel@chromium.org39c0b222013-08-17 16:57:01 +00003017@subcommand.epilog("""Examples:
maruel@chromium.org79692d62010-05-14 18:57:13 +00003018 gclient sync
3019 update files from SCM according to current configuration,
3020 *for modules which have changed since last update or sync*
3021 gclient sync --force
3022 update files from SCM according to current configuration, for
3023 all modules (useful for recovering files deleted from local copy)
Edward Lesmes3ffca4b2021-05-19 19:36:17 +00003024 gclient sync --revision src@GIT_COMMIT_OR_REF
3025 update src directory to GIT_COMMIT_OR_REF
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00003026
3027JSON output format:
3028If the --output-json option is specified, the following document structure will
3029be emitted to the provided file. 'null' entries may occur for subprojects which
3030are present in the gclient solution, but were not processed (due to custom_deps,
3031os_deps, etc.)
3032
3033{
3034 "solutions" : {
3035 "<name>": { # <name> is the posix-normalized path to the solution.
agabled437d762016-10-17 09:35:11 -07003036 "revision": [<git id hex string>|null],
3037 "scm": ["git"|null],
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00003038 }
3039 }
3040}
maruel@chromium.org5ca27692010-05-26 19:32:41 +00003041""")
Edward Lemur3298e7b2018-07-17 18:21:27 +00003042@metrics.collector.collect_metrics('gclient sync')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00003043def CMDsync(parser, args):
3044 """Checkout/update all modules."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00003045 parser.add_option('-f', '--force', action='store_true',
3046 help='force update even for unchanged modules')
3047 parser.add_option('-n', '--nohooks', action='store_true',
3048 help='don\'t run hooks after the update is complete')
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00003049 parser.add_option('-p', '--noprehooks', action='store_true',
3050 help='don\'t run pre-DEPS hooks', default=False)
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00003051 parser.add_option('-r', '--revision', action='append',
3052 dest='revisions', metavar='REV', default=[],
Edward Lesmes3ffca4b2021-05-19 19:36:17 +00003053 help='Enforces git ref/hash for the solutions with the '
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00003054 'format src@rev. The src@ part is optional and can be '
Edward Lesmes53014652018-03-07 18:01:40 -05003055 'skipped. You can also specify URLs instead of paths '
3056 'and gclient will find the solution corresponding to '
3057 'the given URL. If a path is also specified, the URL '
3058 'takes precedence. -r can be used multiple times when '
3059 '.gclient has multiple solutions configured, and will '
Edward Lesmes3ffca4b2021-05-19 19:36:17 +00003060 'work even if the src@ part is skipped. Revision '
3061 'numbers (e.g. 31000 or r31000) are not supported.')
Edward Lesmesc621b212018-03-21 20:26:56 -04003062 parser.add_option('--patch-ref', action='append',
3063 dest='patch_refs', metavar='GERRIT_REF', default=[],
Edward Lemur6a4e31b2018-08-10 19:59:02 +00003064 help='Patches the given reference with the format '
Edward Lemur4c5c8ab2019-06-07 15:58:13 +00003065 'dep@target-ref:patch-ref. '
Edward Lemur6a4e31b2018-08-10 19:59:02 +00003066 'For |dep|, you can specify URLs as well as paths, '
3067 'with URLs taking preference. '
3068 '|patch-ref| will be applied to |dep|, rebased on top '
3069 'of what |dep| was synced to, and a soft reset will '
3070 'be done. Use --no-rebase-patch-ref and '
3071 '--no-reset-patch-ref to disable this behavior. '
3072 '|target-ref| is the target branch against which a '
3073 'patch was created, it is used to determine which '
3074 'commits from the |patch-ref| actually constitute a '
Edward Lemur4c5c8ab2019-06-07 15:58:13 +00003075 'patch.')
Ravi Mistryecda7822022-02-28 16:22:20 +00003076 parser.add_option('-t', '--download-topics', action='store_true',
3077 help='Downloads and patches locally changes from all open '
3078 'Gerrit CLs that have the same topic as the changes '
3079 'in the specified patch_refs. Only works if atleast '
3080 'one --patch-ref is specified.')
maruel@chromium.org794207e2013-03-08 15:29:43 +00003081 parser.add_option('--with_branch_heads', action='store_true',
3082 help='Clone git "branch_heads" refspecs in addition to '
3083 'the default refspecs. This adds about 1/2GB to a '
3084 'full checkout. (git only)')
szager@chromium.org8d3348f2014-08-19 22:49:16 +00003085 parser.add_option('--with_tags', action='store_true',
3086 help='Clone git tags in addition to the default refspecs.')
agable2697cd12016-06-28 10:23:53 -07003087 parser.add_option('-H', '--head', action='store_true',
agablea98a6cd2016-11-15 14:30:10 -08003088 help='DEPRECATED: only made sense with safesync urls.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00003089 parser.add_option('-D', '--delete_unversioned_trees', action='store_true',
steveblock@chromium.org98e69452012-02-16 16:36:43 +00003090 help='Deletes from the working copy any dependencies that '
3091 'have been removed since the last sync, as long as '
3092 'there are no local modifications. When used with '
3093 '--force, such dependencies are removed even if they '
3094 'have local modifications. When used with --reset, '
3095 'all untracked directories are removed from the '
tnagel@chromium.orga2aaa632014-02-28 21:47:27 +00003096 'working copy, excluding those which are explicitly '
steveblock@chromium.org98e69452012-02-16 16:36:43 +00003097 'ignored in the repository.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00003098 parser.add_option('-R', '--reset', action='store_true',
3099 help='resets any local changes before updating (git only)')
bauerb@chromium.org2aad1b22011-07-22 12:00:41 +00003100 parser.add_option('-M', '--merge', action='store_true',
3101 help='merge upstream changes instead of trying to '
3102 'fast-forward or rebase')
dnj@chromium.org5b23e872015-02-20 21:25:57 +00003103 parser.add_option('-A', '--auto_rebase', action='store_true',
3104 help='Automatically rebase repositories against local '
3105 'checkout during update (git only).')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00003106 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
3107 help='override deps for the specified (comma-separated) '
3108 'platform(s); \'all\' will process all deps_os '
3109 'references')
Paweł Hajdan, Jr5ec77132017-08-16 19:21:06 +02003110 parser.add_option('--process-all-deps', action='store_true',
3111 help='Check out all deps, even for different OS-es, '
3112 'or with conditions evaluating to false')
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +00003113 parser.add_option('--upstream', action='store_true',
3114 help='Make repo state match upstream branch.')
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00003115 parser.add_option('--output-json',
3116 help='Output a json document to this path containing '
3117 'summary information about the sync.')
primiano@chromium.org5439ea52014-08-06 17:18:18 +00003118 parser.add_option('--no-history', action='store_true',
3119 help='GIT ONLY - Reduces the size/time of the checkout at '
3120 'the cost of no history. Requires Git 1.9+')
hinoka@chromium.org46b87412014-05-15 00:42:05 +00003121 parser.add_option('--shallow', action='store_true',
3122 help='GIT ONLY - Do a shallow clone into the cache dir. '
3123 'Requires Git 1.9+')
e.hakkinen@samsung.come8bc1aa2015-04-08 08:00:37 +00003124 parser.add_option('--no_bootstrap', '--no-bootstrap',
3125 action='store_true',
3126 help='Don\'t bootstrap from Google Storage.')
Josip Sokcevic14a83ae2020-05-21 01:36:34 +00003127 parser.add_option('--ignore_locks',
3128 action='store_true',
3129 help='No longer used.')
3130 parser.add_option('--break_repo_locks',
3131 action='store_true',
3132 help='No longer used.')
Vadim Shtayura08049e22017-10-11 00:14:52 +00003133 parser.add_option('--lock_timeout', type='int', default=5000,
3134 help='GIT ONLY - Deadline (in seconds) to wait for git '
3135 'cache lock to become available. Default is %default.')
Edward Lesmesc621b212018-03-21 20:26:56 -04003136 parser.add_option('--no-rebase-patch-ref', action='store_false',
3137 dest='rebase_patch_ref', default=True,
3138 help='Bypass rebase of the patch ref after checkout.')
3139 parser.add_option('--no-reset-patch-ref', action='store_false',
3140 dest='reset_patch_ref', default=True,
3141 help='Bypass calling reset after patching the ref.')
Joanna Wanga84a16b2022-07-27 18:52:17 +00003142 parser.add_option('--experiment',
3143 action='append',
3144 dest='experiments',
3145 default=[],
3146 help='Which experiments should be enabled.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00003147 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00003148 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00003149
3150 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00003151 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00003152
Ravi Mistryecda7822022-02-28 16:22:20 +00003153 if options.download_topics and not options.rebase_patch_ref:
3154 raise gclient_utils.Error(
3155 'Warning: You cannot download topics and not rebase each patch ref')
3156
Josip Sokcevic14a83ae2020-05-21 01:36:34 +00003157 if options.ignore_locks:
3158 print('Warning: ignore_locks is no longer used. Please remove its usage.')
3159
3160 if options.break_repo_locks:
3161 print('Warning: break_repo_locks is no longer used. Please remove its '
3162 'usage.')
3163
smutae7ea312016-07-18 11:59:41 -07003164 if options.revisions and options.head:
3165 # TODO(maruel): Make it a parser.error if it doesn't break any builder.
3166 print('Warning: you cannot use both --head and --revision')
3167
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00003168 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00003169 client.PrintLocationAndContents()
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00003170 ret = client.RunOnDeps('update', args)
3171 if options.output_json:
3172 slns = {}
Michael Mossd683d7c2018-06-15 05:05:17 +00003173 for d in client.subtree(True):
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00003174 normed = d.name.replace('\\', '/').rstrip('/') + '/'
3175 slns[normed] = {
3176 'revision': d.got_revision,
3177 'scm': d.used_scm.name if d.used_scm else None,
Michael Mossd683d7c2018-06-15 05:05:17 +00003178 'url': str(d.url) if d.url else None,
Edward Lemur7ccf2f02018-06-26 20:41:56 +00003179 'was_processed': d.should_process,
Joanna Wanga84a16b2022-07-27 18:52:17 +00003180 'was_synced': d._should_sync,
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00003181 }
Edward Lemurca879322019-09-09 20:18:13 +00003182 with open(options.output_json, 'w') as f:
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00003183 json.dump({'solutions': slns}, f)
3184 return ret
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00003185
3186
maruel@chromium.org39c0b222013-08-17 16:57:01 +00003187CMDupdate = CMDsync
3188
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00003189
Edward Lemur3298e7b2018-07-17 18:21:27 +00003190@metrics.collector.collect_metrics('gclient validate')
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +02003191def CMDvalidate(parser, args):
3192 """Validates the .gclient and DEPS syntax."""
3193 options, args = parser.parse_args(args)
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +02003194 client = GClient.LoadCurrentConfig(options)
Gavin Makf6b414c2021-01-12 19:10:41 +00003195 if not client:
3196 raise gclient_utils.Error('client not configured; see \'gclient config\'')
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +02003197 rv = client.RunOnDeps('validate', args)
3198 if rv == 0:
3199 print('validate: SUCCESS')
3200 else:
3201 print('validate: FAILURE')
3202 return rv
3203
3204
Edward Lemur3298e7b2018-07-17 18:21:27 +00003205@metrics.collector.collect_metrics('gclient diff')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00003206def CMDdiff(parser, args):
3207 """Displays local diff for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00003208 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
3209 help='override deps for the specified (comma-separated) '
3210 'platform(s); \'all\' will process all deps_os '
3211 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00003212 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00003213 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00003214 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00003215 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00003216 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00003217 client.PrintLocationAndContents()
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00003218 return client.RunOnDeps('diff', args)
3219
3220
Edward Lemur3298e7b2018-07-17 18:21:27 +00003221@metrics.collector.collect_metrics('gclient revert')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00003222def CMDrevert(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00003223 """Reverts all modifications in every dependencies.
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00003224
3225 That's the nuclear option to get back to a 'clean' state. It removes anything
agabled437d762016-10-17 09:35:11 -07003226 that shows up in git status."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00003227 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
3228 help='override deps for the specified (comma-separated) '
3229 'platform(s); \'all\' will process all deps_os '
3230 'references')
3231 parser.add_option('-n', '--nohooks', action='store_true',
3232 help='don\'t run hooks after the revert is complete')
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00003233 parser.add_option('-p', '--noprehooks', action='store_true',
3234 help='don\'t run pre-DEPS hooks', default=False)
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +00003235 parser.add_option('--upstream', action='store_true',
3236 help='Make repo state match upstream branch.')
Josip Sokcevic14a83ae2020-05-21 01:36:34 +00003237 parser.add_option('--break_repo_locks',
3238 action='store_true',
3239 help='No longer used.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00003240 (options, args) = parser.parse_args(args)
Josip Sokcevic14a83ae2020-05-21 01:36:34 +00003241 if options.break_repo_locks:
3242 print('Warning: break_repo_locks is no longer used. Please remove its ' +
3243 'usage.')
3244
maruel@chromium.org5ca27692010-05-26 19:32:41 +00003245 # --force is implied.
3246 options.force = True
steveblock@chromium.org98e69452012-02-16 16:36:43 +00003247 options.reset = False
3248 options.delete_unversioned_trees = False
agablec903d732016-07-26 09:07:24 -07003249 options.merge = False
maruel@chromium.org2806acc2009-05-15 12:33:34 +00003250 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00003251 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00003252 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00003253 return client.RunOnDeps('revert', args)
3254
3255
Edward Lemur3298e7b2018-07-17 18:21:27 +00003256@metrics.collector.collect_metrics('gclient runhooks')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00003257def CMDrunhooks(parser, args):
3258 """Runs hooks for files that have been modified in the local working copy."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00003259 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
3260 help='override deps for the specified (comma-separated) '
3261 'platform(s); \'all\' will process all deps_os '
3262 'references')
3263 parser.add_option('-f', '--force', action='store_true', default=True,
3264 help='Deprecated. No effect.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00003265 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00003266 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00003267 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00003268 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00003269 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00003270 client.PrintLocationAndContents()
maruel@chromium.org5df6a462009-08-28 18:52:26 +00003271 options.force = True
maruel@chromium.org5ca27692010-05-26 19:32:41 +00003272 options.nohooks = False
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00003273 return client.RunOnDeps('runhooks', args)
3274
3275
Edward Lemur3298e7b2018-07-17 18:21:27 +00003276@metrics.collector.collect_metrics('gclient revinfo')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00003277def CMDrevinfo(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00003278 """Outputs revision info mapping for the client and its dependencies.
maruel@chromium.org9eda4112010-06-11 18:56:10 +00003279
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00003280 This allows the capture of an overall 'revision' for the source tree that
maruel@chromium.org9eda4112010-06-11 18:56:10 +00003281 can be used to reproduce the same tree in the future. It is only useful for
agabled437d762016-10-17 09:35:11 -07003282 'unpinned dependencies', i.e. DEPS/deps references without a git hash.
3283 A git branch name isn't 'pinned' since the actual commit can change.
maruel@chromium.org9eda4112010-06-11 18:56:10 +00003284 """
3285 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
3286 help='override deps for the specified (comma-separated) '
3287 'platform(s); \'all\' will process all deps_os '
3288 'references')
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00003289 parser.add_option('-a', '--actual', action='store_true',
3290 help='gets the actual checked out revisions instead of the '
3291 'ones specified in the DEPS and .gclient files')
maruel@chromium.org9eda4112010-06-11 18:56:10 +00003292 parser.add_option('-s', '--snapshot', action='store_true',
3293 help='creates a snapshot .gclient file of the current '
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00003294 'version of all repositories to reproduce the tree, '
3295 'implies -a')
Edward Lesmesbb16e332018-03-30 17:54:51 -04003296 parser.add_option('--filter', action='append', dest='filter',
Edward Lesmesdaa76d22018-03-06 14:56:57 -05003297 help='Display revision information only for the specified '
Edward Lesmesbb16e332018-03-30 17:54:51 -04003298 'dependencies (filtered by URL or path).')
Edward Lesmesc2960242018-03-06 20:50:15 -05003299 parser.add_option('--output-json',
3300 help='Output a json document to this path containing '
3301 'information about the revisions.')
Joey Scarr8d3925b2018-07-15 23:36:25 +00003302 parser.add_option('--ignore-dep-type', choices=['git', 'cipd'],
3303 help='Specify to skip processing of a certain type of dep.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00003304 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00003305 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00003306 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00003307 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00003308 client.PrintRevInfo()
maruel@chromium.org79692d62010-05-14 18:57:13 +00003309 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00003310
3311
Edward Lemur3298e7b2018-07-17 18:21:27 +00003312@metrics.collector.collect_metrics('gclient getdep')
Edward Lesmes411041f2018-04-05 20:12:55 -04003313def CMDgetdep(parser, args):
Josip Sokcevic7b5e3d72023-06-13 00:28:23 +00003314 """Gets revision information and variable values from a DEPS file.
3315
3316 If key doesn't exist or is incorrectly declared, this script exits with exit
3317 code 2."""
Edward Lesmes411041f2018-04-05 20:12:55 -04003318 parser.add_option('--var', action='append',
3319 dest='vars', metavar='VAR', default=[],
3320 help='Gets the value of a given variable.')
3321 parser.add_option('-r', '--revision', action='append',
Edward Lemuraf3328f2018-11-19 14:11:46 +00003322 dest='getdep_revisions', metavar='DEP', default=[],
Edward Lesmes411041f2018-04-05 20:12:55 -04003323 help='Gets the revision/version for the given dependency. '
3324 'If it is a git dependency, dep must be a path. If it '
3325 'is a CIPD dependency, dep must be of the form '
3326 'path:package.')
3327 parser.add_option('--deps-file', default='DEPS',
3328 # TODO(ehmaldonado): Try to find the DEPS file pointed by
3329 # .gclient first.
3330 help='The DEPS file to be edited. Defaults to the DEPS '
3331 'file in the current directory.')
3332 (options, args) = parser.parse_args(args)
3333
3334 if not os.path.isfile(options.deps_file):
3335 raise gclient_utils.Error(
3336 'DEPS file %s does not exist.' % options.deps_file)
3337 with open(options.deps_file) as f:
3338 contents = f.read()
Edward Lemuraf3328f2018-11-19 14:11:46 +00003339 client = GClient.LoadCurrentConfig(options)
3340 if client is not None:
3341 builtin_vars = client.get_builtin_vars()
3342 else:
Edward Lemurca879322019-09-09 20:18:13 +00003343 logging.warning(
Edward Lemuraf3328f2018-11-19 14:11:46 +00003344 'Couldn\'t find a valid gclient config. Will attempt to parse the DEPS '
3345 'file without support for built-in variables.')
3346 builtin_vars = None
3347 local_scope = gclient_eval.Exec(contents, options.deps_file,
3348 builtin_vars=builtin_vars)
Edward Lesmes411041f2018-04-05 20:12:55 -04003349
3350 for var in options.vars:
3351 print(gclient_eval.GetVar(local_scope, var))
3352
Edward Lemuraf3328f2018-11-19 14:11:46 +00003353 for name in options.getdep_revisions:
Edward Lesmes411041f2018-04-05 20:12:55 -04003354 if ':' in name:
3355 name, _, package = name.partition(':')
3356 if not name or not package:
3357 parser.error(
3358 'Wrong CIPD format: %s:%s should be of the form path:pkg.'
3359 % (name, package))
3360 print(gclient_eval.GetCIPD(local_scope, name, package))
3361 else:
Josip Sokcevic7b5e3d72023-06-13 00:28:23 +00003362 try:
3363 print(gclient_eval.GetRevision(local_scope, name))
3364 except KeyError as e:
3365 print(repr(e), file=sys.stderr)
3366 sys.exit(2)
Edward Lesmes411041f2018-04-05 20:12:55 -04003367
3368
Edward Lemur3298e7b2018-07-17 18:21:27 +00003369@metrics.collector.collect_metrics('gclient setdep')
Edward Lesmes6f64a052018-03-20 17:35:49 -04003370def CMDsetdep(parser, args):
Edward Lesmes0ecf6d62018-04-05 18:28:55 -04003371 """Modifies dependency revisions and variable values in a DEPS file"""
Edward Lesmes6f64a052018-03-20 17:35:49 -04003372 parser.add_option('--var', action='append',
3373 dest='vars', metavar='VAR=VAL', default=[],
3374 help='Sets a variable to the given value with the format '
3375 'name=value.')
3376 parser.add_option('-r', '--revision', action='append',
Edward Lemuraf3328f2018-11-19 14:11:46 +00003377 dest='setdep_revisions', metavar='DEP@REV', default=[],
Edward Lesmes6f64a052018-03-20 17:35:49 -04003378 help='Sets the revision/version for the dependency with '
3379 'the format dep@rev. If it is a git dependency, dep '
3380 'must be a path and rev must be a git hash or '
3381 'reference (e.g. src/dep@deadbeef). If it is a CIPD '
3382 'dependency, dep must be of the form path:package and '
3383 'rev must be the package version '
3384 '(e.g. src/pkg:chromium/pkg@2.1-cr0).')
3385 parser.add_option('--deps-file', default='DEPS',
3386 # TODO(ehmaldonado): Try to find the DEPS file pointed by
3387 # .gclient first.
3388 help='The DEPS file to be edited. Defaults to the DEPS '
3389 'file in the current directory.')
3390 (options, args) = parser.parse_args(args)
Edward Lesmes0ecf6d62018-04-05 18:28:55 -04003391 if args:
3392 parser.error('Unused arguments: "%s"' % '" "'.join(args))
Edward Lesmesae6836e2018-11-19 15:27:20 +00003393 if not options.setdep_revisions and not options.vars:
Edward Lesmes0ecf6d62018-04-05 18:28:55 -04003394 parser.error(
3395 'You must specify at least one variable or revision to modify.')
Edward Lesmes6f64a052018-03-20 17:35:49 -04003396
Edward Lesmes6f64a052018-03-20 17:35:49 -04003397 if not os.path.isfile(options.deps_file):
3398 raise gclient_utils.Error(
3399 'DEPS file %s does not exist.' % options.deps_file)
3400 with open(options.deps_file) as f:
3401 contents = f.read()
Edward Lemuraf3328f2018-11-19 14:11:46 +00003402
3403 client = GClient.LoadCurrentConfig(options)
3404 if client is not None:
3405 builtin_vars = client.get_builtin_vars()
3406 else:
Edward Lemurca879322019-09-09 20:18:13 +00003407 logging.warning(
Edward Lemuraf3328f2018-11-19 14:11:46 +00003408 'Couldn\'t find a valid gclient config. Will attempt to parse the DEPS '
3409 'file without support for built-in variables.')
3410 builtin_vars = None
3411
3412 local_scope = gclient_eval.Exec(contents, options.deps_file,
3413 builtin_vars=builtin_vars)
Edward Lesmes6f64a052018-03-20 17:35:49 -04003414
Aravind Vasudevancb8023d2023-07-07 00:03:45 +00003415 # Create a set of all git submodules.
3416 submodule_status = subprocess2.check_output(['git', 'submodule',
3417 'status']).decode('utf-8')
3418 git_modules = {l.split()[1] for l in submodule_status.splitlines()}
3419
Edward Lesmes6f64a052018-03-20 17:35:49 -04003420 for var in options.vars:
3421 name, _, value = var.partition('=')
3422 if not name or not value:
Edward Lesmes0ecf6d62018-04-05 18:28:55 -04003423 parser.error(
Edward Lesmes6f64a052018-03-20 17:35:49 -04003424 'Wrong var format: %s should be of the form name=value.' % var)
Edward Lesmes3d993812018-04-02 12:52:49 -04003425 if name in local_scope['vars']:
3426 gclient_eval.SetVar(local_scope, name, value)
3427 else:
3428 gclient_eval.AddVar(local_scope, name, value)
Edward Lesmes6f64a052018-03-20 17:35:49 -04003429
Edward Lemuraf3328f2018-11-19 14:11:46 +00003430 for revision in options.setdep_revisions:
Edward Lesmes6f64a052018-03-20 17:35:49 -04003431 name, _, value = revision.partition('@')
3432 if not name or not value:
Edward Lesmes0ecf6d62018-04-05 18:28:55 -04003433 parser.error(
Edward Lesmes6f64a052018-03-20 17:35:49 -04003434 'Wrong dep format: %s should be of the form dep@rev.' % revision)
3435 if ':' in name:
3436 name, _, package = name.partition(':')
3437 if not name or not package:
Edward Lesmes0ecf6d62018-04-05 18:28:55 -04003438 parser.error(
Edward Lesmes6f64a052018-03-20 17:35:49 -04003439 'Wrong CIPD format: %s:%s should be of the form path:pkg@version.'
3440 % (name, package))
3441 gclient_eval.SetCIPD(local_scope, name, package, value)
3442 else:
Aravind Vasudevancb8023d2023-07-07 00:03:45 +00003443 # Update DEPS only when `git_dependencies` == DEPS or SYNC.
3444 # git_dependencies is defaulted to DEPS when not set.
3445 if 'git_dependencies' not in local_scope or local_scope[
3446 'git_dependencies'] in (gclient_eval.DEPS, gclient_eval.SYNC):
3447 gclient_eval.SetRevision(local_scope, name, value)
3448
3449 # Update git submodules when `git_dependencies` == SYNC or SUBMODULES.
3450 if 'git_dependencies' in local_scope and local_scope[
3451 'git_dependencies'] in (gclient_eval.SUBMODULES, gclient_eval.SYNC):
3452 # gclient setdep should update the revision, i.e., the gitlink only
3453 # when the submodule entry is already present within .gitmodules.
3454 if name not in git_modules:
3455 raise KeyError(
3456 'Could not find any dependency called %s in .gitmodules.' % name)
3457
3458 # Update the gitlink for the submodule.
3459 subprocess2.call([
3460 'git', 'update-index', '--add', '--cacheinfo',
3461 f'160000,{value},{name}'
3462 ])
Edward Lesmes6f64a052018-03-20 17:35:49 -04003463
John Emau7aa68242020-02-20 19:44:53 +00003464 with open(options.deps_file, 'wb') as f:
3465 f.write(gclient_eval.RenderDEPSFile(local_scope).encode('utf-8'))
Edward Lesmes6f64a052018-03-20 17:35:49 -04003466
3467
Edward Lemur3298e7b2018-07-17 18:21:27 +00003468@metrics.collector.collect_metrics('gclient verify')
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00003469def CMDverify(parser, args):
3470 """Verifies the DEPS file deps are only from allowed_hosts."""
3471 (options, args) = parser.parse_args(args)
3472 client = GClient.LoadCurrentConfig(options)
3473 if not client:
3474 raise gclient_utils.Error('client not configured; see \'gclient config\'')
3475 client.RunOnDeps(None, [])
3476 # Look at each first-level dependency of this gclient only.
3477 for dep in client.dependencies:
3478 bad_deps = dep.findDepsFromNotAllowedHosts()
3479 if not bad_deps:
3480 continue
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00003481 print("There are deps from not allowed hosts in file %s" % dep.deps_file)
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00003482 for bad_dep in bad_deps:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00003483 print("\t%s at %s" % (bad_dep.name, bad_dep.url))
3484 print("allowed_hosts:", ', '.join(dep.allowed_hosts))
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00003485 sys.stdout.flush()
3486 raise gclient_utils.Error(
3487 'dependencies from disallowed hosts; check your DEPS file.')
3488 return 0
3489
Edward Lemur32e3d1e2018-07-12 00:54:05 +00003490
3491@subcommand.epilog("""For more information on what metrics are we collecting and
Edward Lemur8a2e3312018-07-12 21:15:09 +00003492why, please read metrics.README.md or visit https://bit.ly/2ufRS4p""")
Edward Lemur3298e7b2018-07-17 18:21:27 +00003493@metrics.collector.collect_metrics('gclient metrics')
Edward Lemur32e3d1e2018-07-12 00:54:05 +00003494def CMDmetrics(parser, args):
3495 """Reports, and optionally modifies, the status of metric collection."""
3496 parser.add_option('--opt-in', action='store_true', dest='enable_metrics',
3497 help='Opt-in to metrics collection.',
3498 default=None)
3499 parser.add_option('--opt-out', action='store_false', dest='enable_metrics',
3500 help='Opt-out of metrics collection.')
3501 options, args = parser.parse_args(args)
3502 if args:
3503 parser.error('Unused arguments: "%s"' % '" "'.join(args))
3504 if not metrics.collector.config.is_googler:
3505 print("You're not a Googler. Metrics collection is disabled for you.")
3506 return 0
3507
3508 if options.enable_metrics is not None:
3509 metrics.collector.config.opted_in = options.enable_metrics
3510
3511 if metrics.collector.config.opted_in is None:
3512 print("You haven't opted in or out of metrics collection.")
3513 elif metrics.collector.config.opted_in:
3514 print("You have opted in. Thanks!")
3515 else:
3516 print("You have opted out. Please consider opting in.")
3517 return 0
3518
3519
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00003520class OptionParser(optparse.OptionParser):
szager@chromium.orge2e03202012-07-31 18:05:16 +00003521 gclientfile_default = os.environ.get('GCLIENT_FILE', '.gclient')
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00003522
3523 def __init__(self, **kwargs):
3524 optparse.OptionParser.__init__(
3525 self, version='%prog ' + __version__, **kwargs)
3526
Aravind Vasudevan3d760cc2023-03-30 20:36:14 +00003527 # Some arm boards have issues with parallel sync.
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00003528 if platform.machine().startswith('arm'):
Aravind Vasudevan3d760cc2023-03-30 20:36:14 +00003529 jobs = 1
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00003530 else:
3531 jobs = max(8, gclient_utils.NumLocalCpus())
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00003532
3533 self.add_option(
3534 '-j', '--jobs', default=jobs, type='int',
3535 help='Specify how many SCM commands can run in parallel; defaults to '
tnagel@chromium.orga2aaa632014-02-28 21:47:27 +00003536 '%default on this machine')
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00003537 self.add_option(
3538 '-v', '--verbose', action='count', default=0,
3539 help='Produces additional output for diagnostics. Can be used up to '
3540 'three times for more logging info.')
3541 self.add_option(
3542 '--gclientfile', dest='config_filename',
3543 help='Specify an alternate %s file' % self.gclientfile_default)
3544 self.add_option(
3545 '--spec',
3546 help='create a gclient file containing the provided string. Due to '
3547 'Cygwin/Python brokenness, it can\'t contain any newlines.')
3548 self.add_option(
3549 '--no-nag-max', default=False, action='store_true',
scottmg@chromium.orgf547c802013-09-27 17:55:26 +00003550 help='Ignored for backwards compatibility.')
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00003551
Edward Lemur3298e7b2018-07-17 18:21:27 +00003552 def parse_args(self, args=None, _values=None):
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00003553 """Integrates standard options processing."""
Edward Lemur3298e7b2018-07-17 18:21:27 +00003554 # Create an optparse.Values object that will store only the actual passed
3555 # options, without the defaults.
3556 actual_options = optparse.Values()
3557 _, args = optparse.OptionParser.parse_args(self, args, actual_options)
3558 # Create an optparse.Values object with the default options.
3559 options = optparse.Values(self.get_default_values().__dict__)
3560 # Update it with the options passed by the user.
3561 options._update_careful(actual_options.__dict__)
3562 # Store the options passed by the user in an _actual_options attribute.
3563 # We store only the keys, and not the values, since the values can contain
3564 # arbitrary information, which might be PII.
Edward Lemuree7b9dd2019-07-20 01:29:08 +00003565 metrics.collector.add('arguments', list(actual_options.__dict__))
Edward Lemur3298e7b2018-07-17 18:21:27 +00003566
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00003567 levels = [logging.ERROR, logging.WARNING, logging.INFO, logging.DEBUG]
3568 logging.basicConfig(
3569 level=levels[min(options.verbose, len(levels) - 1)],
maruel@chromium.org0895b752011-08-26 20:40:33 +00003570 format='%(module)s(%(lineno)d) %(funcName)s:%(message)s')
szager@chromium.orge2e03202012-07-31 18:05:16 +00003571 if options.config_filename and options.spec:
Quinten Yearsley925cedb2020-04-13 17:49:39 +00003572 self.error('Cannot specify both --gclientfile and --spec')
rdsmith@chromium.orgd9591f02014-02-05 19:28:20 +00003573 if (options.config_filename and
3574 options.config_filename != os.path.basename(options.config_filename)):
3575 self.error('--gclientfile target must be a filename, not a path')
szager@chromium.orge2e03202012-07-31 18:05:16 +00003576 if not options.config_filename:
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00003577 options.config_filename = self.gclientfile_default
maruel@chromium.org0895b752011-08-26 20:40:33 +00003578 options.entries_filename = options.config_filename + '_entries'
3579 if options.jobs < 1:
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00003580 self.error('--jobs must be 1 or higher')
maruel@chromium.org0895b752011-08-26 20:40:33 +00003581
3582 # These hacks need to die.
3583 if not hasattr(options, 'revisions'):
3584 # GClient.RunOnDeps expects it even if not applicable.
3585 options.revisions = []
Joanna Wanga84a16b2022-07-27 18:52:17 +00003586 if not hasattr(options, 'experiments'):
3587 options.experiments = []
smutae7ea312016-07-18 11:59:41 -07003588 if not hasattr(options, 'head'):
3589 options.head = None
maruel@chromium.org0895b752011-08-26 20:40:33 +00003590 if not hasattr(options, 'nohooks'):
3591 options.nohooks = True
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00003592 if not hasattr(options, 'noprehooks'):
3593 options.noprehooks = True
maruel@chromium.org0895b752011-08-26 20:40:33 +00003594 if not hasattr(options, 'deps_os'):
3595 options.deps_os = None
maruel@chromium.org0895b752011-08-26 20:40:33 +00003596 if not hasattr(options, 'force'):
3597 options.force = None
3598 return (options, args)
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00003599
maruel@chromium.org39c0b222013-08-17 16:57:01 +00003600
3601def disable_buffering():
3602 # Make stdout auto-flush so buildbot doesn't kill us during lengthy
3603 # operations. Python as a strong tendency to buffer sys.stdout.
3604 sys.stdout = gclient_utils.MakeFileAutoFlush(sys.stdout)
3605 # Make stdout annotated with the thread ids.
3606 sys.stdout = gclient_utils.MakeFileAnnotated(sys.stdout)
maruel@chromium.org0895b752011-08-26 20:40:33 +00003607
3608
Elly Fong-Jones7b294392019-04-18 18:32:10 +00003609def path_contains_tilde():
3610 for element in os.environ['PATH'].split(os.pathsep):
Henrique Ferreiro4ef32212019-04-29 23:32:31 +00003611 if element.startswith('~') and os.path.abspath(
3612 os.path.realpath(os.path.expanduser(element))) == DEPOT_TOOLS_DIR:
Elly Fong-Jones7b294392019-04-18 18:32:10 +00003613 return True
3614 return False
3615
3616
3617def can_run_gclient_and_helpers():
maruel@chromium.org82798cb2012-02-23 18:16:12 +00003618 if sys.hexversion < 0x02060000:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00003619 print(
maruel@chromium.org82798cb2012-02-23 18:16:12 +00003620 '\nYour python version %s is unsupported, please upgrade.\n' %
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00003621 sys.version.split(' ', 1)[0],
3622 file=sys.stderr)
Elly Fong-Jones7b294392019-04-18 18:32:10 +00003623 return False
bcwhite@chromium.org6683ab42013-02-11 16:13:47 +00003624 if not sys.executable:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00003625 print(
3626 '\nPython cannot find the location of it\'s own executable.\n',
3627 file=sys.stderr)
Elly Fong-Jones7b294392019-04-18 18:32:10 +00003628 return False
3629 if path_contains_tilde():
3630 print(
3631 '\nYour PATH contains a literal "~", which works in some shells ' +
3632 'but will break when python tries to run subprocesses. ' +
3633 'Replace the "~" with $HOME.\n' +
3634 'See https://crbug.com/952865.\n',
3635 file=sys.stderr)
3636 return False
3637 return True
3638
3639
3640def main(argv):
3641 """Doesn't parse the arguments here, just find the right subcommand to
3642 execute."""
3643 if not can_run_gclient_and_helpers():
bcwhite@chromium.org6683ab42013-02-11 16:13:47 +00003644 return 2
maruel@chromium.org39c0b222013-08-17 16:57:01 +00003645 fix_encoding.fix_encoding()
3646 disable_buffering()
iannucci@chromium.org596cd5c2016-04-04 21:34:39 +00003647 setup_color.init()
maruel@chromium.org39c0b222013-08-17 16:57:01 +00003648 dispatcher = subcommand.CommandDispatcher(__name__)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00003649 try:
maruel@chromium.org39c0b222013-08-17 16:57:01 +00003650 return dispatcher.execute(OptionParser(), argv)
xusydoc@chromium.org2fd6c3f2013-05-03 21:57:55 +00003651 except KeyboardInterrupt:
3652 gclient_utils.GClientChildren.KillAllRemainingChildren()
3653 raise
vapier@chromium.orga81a56e2015-11-11 07:56:13 +00003654 except (gclient_utils.Error, subprocess2.CalledProcessError) as e:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00003655 print('Error: %s' % str(e), file=sys.stderr)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00003656 return 1
borenet@google.com6a9b1682014-03-24 18:35:23 +00003657 finally:
3658 gclient_utils.PrintWarnings()
sbc@chromium.org013731e2015-02-26 18:28:43 +00003659 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00003660
3661
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00003662if '__main__' == __name__:
Edward Lemur6f812e12018-07-31 22:45:57 +00003663 with metrics.collector.print_notice_and_exit():
sbc@chromium.org013731e2015-02-26 18:28:43 +00003664 sys.exit(main(sys.argv[1:]))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00003665
3666# vim: ts=2:sw=2:tw=80:et: