blob: 3c8ed8382376b466ff46c68b2905902feaa77823 [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', {})
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200884 deps_to_add = self._deps_to_objects(
Corentin Wallez271a78a2020-07-12 15:41:46 +0000885 self._postprocess_deps(deps, rel_prefix), self._use_relative_paths)
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000886
Corentin Walleza68660d2018-09-10 17:33:24 +0000887 # compute which working directory should be used for hooks
Michael Spang0e99b9b2020-08-12 13:34:48 +0000888 if local_scope.get('use_relative_hooks', False):
Joanna Wang4e6264c2022-06-30 19:10:43 +0000889 print('use_relative_hooks is deprecated, please remove it from '
890 '%s DEPS. (it was merged in use_relative_paths)' % self.name,
891 file=sys.stderr)
Michael Spang0e99b9b2020-08-12 13:34:48 +0000892
Corentin Walleza68660d2018-09-10 17:33:24 +0000893 hooks_cwd = self.root.root_dir
Corentin Wallez801c2022020-07-20 20:11:09 +0000894 if self._use_relative_paths:
Corentin Walleza68660d2018-09-10 17:33:24 +0000895 hooks_cwd = os.path.join(hooks_cwd, self.name)
896 logging.warning('Updating hook base working directory to %s.',
897 hooks_cwd)
898
Joanna Wang18af7ef2022-07-01 16:51:00 +0000899 # Only add all hooks if we should sync, otherwise just add custom hooks.
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000900 # override named sets of hooks by the custom hooks
901 hooks_to_run = []
Joanna Wang18af7ef2022-07-01 16:51:00 +0000902 if self._should_sync:
903 hook_names_to_suppress = [c.get('name', '') for c in self.custom_hooks]
904 for hook in local_scope.get('hooks', []):
905 if hook.get('name', '') not in hook_names_to_suppress:
906 hooks_to_run.append(hook)
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000907
908 # add the replacements and any additions
909 for hook in self.custom_hooks:
910 if 'action' in hook:
911 hooks_to_run.append(hook)
912
Joanna Wang18af7ef2022-07-01 16:51:00 +0000913 if self.should_recurse and deps_to_add:
Paweł Hajdan, Jr032d5452017-06-22 20:43:53 +0200914 self._pre_deps_hooks = [
Michael Moss42d02c22018-02-05 10:32:24 -0800915 Hook.from_dict(hook, variables=self.get_vars(), verbose=True,
Corentin Walleza68660d2018-09-10 17:33:24 +0000916 conditions=self.condition, cwd_base=hooks_cwd)
Daniel Chenga0c5f082017-10-19 13:35:19 -0700917 for hook in local_scope.get('pre_deps_hooks', [])
918 ]
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000919
Corentin Walleza68660d2018-09-10 17:33:24 +0000920 self.add_dependencies_and_close(deps_to_add, hooks_to_run,
921 hooks_cwd=hooks_cwd)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000922 logging.info('ParseDepsFile(%s) done' % self.name)
923
Michael Mossd683d7c2018-06-15 05:05:17 +0000924 def _get_option(self, attr, default):
925 obj = self
926 while not hasattr(obj, '_options'):
927 obj = obj.parent
928 return getattr(obj._options, attr, default)
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +0200929
Corentin Walleza68660d2018-09-10 17:33:24 +0000930 def add_dependencies_and_close(self, deps_to_add, hooks, hooks_cwd=None):
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000931 """Adds the dependencies, hooks and mark the parsing as done."""
Corentin Walleza68660d2018-09-10 17:33:24 +0000932 if hooks_cwd == None:
933 hooks_cwd = self.root.root_dir
934
maruel@chromium.orgb9be0652011-10-14 18:05:40 +0000935 for dep in deps_to_add:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000936 if dep.verify_validity():
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000937 self.add_dependency(dep)
Daniel Chenga0c5f082017-10-19 13:35:19 -0700938 self._mark_as_parsed([
939 Hook.from_dict(
Michael Moss42d02c22018-02-05 10:32:24 -0800940 h, variables=self.get_vars(), verbose=self.root._options.verbose,
Corentin Walleza68660d2018-09-10 17:33:24 +0000941 conditions=self.condition, cwd_base=hooks_cwd)
Daniel Chenga0c5f082017-10-19 13:35:19 -0700942 for h in hooks
943 ])
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000944
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000945 def findDepsFromNotAllowedHosts(self):
Corentin Wallezaca984c2018-09-07 21:52:14 +0000946 """Returns a list of dependencies from not allowed hosts.
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000947
948 If allowed_hosts is not set, allows all hosts and returns empty list.
949 """
950 if not self._allowed_hosts:
951 return []
952 bad_deps = []
953 for dep in self._dependencies:
szager@chromium.orgbd772dd2014-11-05 18:43:08 +0000954 # Don't enforce this for custom_deps.
955 if dep.name in self._custom_deps:
956 continue
Michael Mossd683d7c2018-06-15 05:05:17 +0000957 if isinstance(dep.url, basestring):
958 parsed_url = urlparse.urlparse(dep.url)
959 if parsed_url.netloc and parsed_url.netloc not in self._allowed_hosts:
960 bad_deps.append(dep)
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000961 return bad_deps
962
Edward Lemure7273d22018-05-10 19:13:51 -0400963 def FuzzyMatchUrl(self, candidates):
Joanna Wang66286612022-06-30 19:59:13 +0000964 # type: (Union[Mapping[str, str], Collection[str]]) -> Optional[str]
Edward Lesmesbb16e332018-03-30 17:54:51 -0400965 """Attempts to find this dependency in the list of candidates.
966
Edward Lemure7273d22018-05-10 19:13:51 -0400967 It looks first for the URL of this dependency in the list of
Edward Lesmesbb16e332018-03-30 17:54:51 -0400968 candidates. If it doesn't succeed, and the URL ends in '.git', it will try
969 looking for the URL minus '.git'. Finally it will try to look for the name
970 of the dependency.
971
972 Args:
Edward Lesmesbb16e332018-03-30 17:54:51 -0400973 candidates: list, dict. The list of candidates in which to look for this
974 dependency. It can contain URLs as above, or dependency names like
975 "src/some/dep".
976
977 Returns:
978 If this dependency is not found in the list of candidates, returns None.
979 Otherwise, it returns under which name did we find this dependency:
980 - Its parsed url: "https://example.com/src.git'
981 - Its parsed url minus '.git': "https://example.com/src"
982 - Its name: "src"
983 """
Edward Lemure7273d22018-05-10 19:13:51 -0400984 if self.url:
985 origin, _ = gclient_utils.SplitUrlRevision(self.url)
Joanna Wang66286612022-06-30 19:59:13 +0000986 match = gclient_utils.FuzzyMatchRepo(origin, candidates)
987 if match:
988 return match
Edward Lesmesbb16e332018-03-30 17:54:51 -0400989 if self.name in candidates:
990 return self.name
991 return None
992
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +0000993 # Arguments number differs from overridden method
Quinten Yearsleyb2cc4a92016-12-15 13:53:26 -0800994 # pylint: disable=arguments-differ
Joanna Wang18af7ef2022-07-01 16:51:00 +0000995 def run(
996 self,
997 revision_overrides, # type: Mapping[str, str]
998 command, # type: str
999 args, # type: Sequence[str]
1000 work_queue, # type: ExecutionQueue
1001 options, # type: optparse.Values
1002 patch_refs, # type: Mapping[str, str]
Joanna Wanga84a16b2022-07-27 18:52:17 +00001003 target_branches, # type: Mapping[str, str]
1004 skip_sync_revisions, # type: Mapping[str, str]
Joanna Wang18af7ef2022-07-01 16:51:00 +00001005 ):
1006 # type: () -> None
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001007 """Runs |command| then parse the DEPS file."""
maruel@chromium.org470b5432011-10-11 18:18:19 +00001008 logging.info('Dependency(%s).run()' % self.name)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +00001009 assert self._file_list == []
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001010 # When running runhooks, there's no need to consult the SCM.
1011 # All known hooks are expected to run unconditionally regardless of working
1012 # copy state, so skip the SCM status check.
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001013 run_scm = command not in (
1014 'flatten', 'runhooks', 'recurse', 'validate', None)
iannucci@chromium.org396e1a62013-07-03 19:41:04 +00001015 file_list = [] if not options.nohooks else None
Edward Lesmesbb16e332018-03-30 17:54:51 -04001016 revision_override = revision_overrides.pop(
Edward Lemure7273d22018-05-10 19:13:51 -04001017 self.FuzzyMatchUrl(revision_overrides), None)
Edward Lemure4213702018-06-21 21:15:50 +00001018 if not revision_override and not self.managed:
1019 revision_override = 'unmanaged'
Michael Mossd683d7c2018-06-15 05:05:17 +00001020 if run_scm and self.url:
agabled437d762016-10-17 09:35:11 -07001021 # Create a shallow copy to mutate revision.
1022 options = copy.copy(options)
1023 options.revision = revision_override
1024 self._used_revision = options.revision
Edward Lemurbabd0982018-05-11 13:32:37 -04001025 self._used_scm = self.CreateSCM(out_cb=work_queue.out_cb)
Edward Lesmesc8f63d32021-06-02 23:51:53 +00001026 if command != 'update' or self.GetScmName() != 'git':
1027 self._got_revision = self._used_scm.RunCommand(command, options, args,
1028 file_list)
1029 else:
1030 try:
1031 start = time.time()
1032 sync_status = metrics_utils.SYNC_STATUS_FAILURE
1033 self._got_revision = self._used_scm.RunCommand(command, options, args,
1034 file_list)
1035 sync_status = metrics_utils.SYNC_STATUS_SUCCESS
1036 finally:
1037 url, revision = gclient_utils.SplitUrlRevision(self.url)
1038 metrics.collector.add_repeated('git_deps', {
1039 'path': self.name,
1040 'url': url,
1041 'revision': revision,
1042 'execution_time': time.time() - start,
1043 'sync_status': sync_status,
1044 })
Edward Lesmesc621b212018-03-21 20:26:56 -04001045
Joanna Wangf3edc502022-07-20 00:12:10 +00001046 if isinstance(self, GitDependency) and command == 'update':
1047 patch_repo = self.url.split('@')[0]
Josip Sokcevicd47a9c22023-06-22 05:14:35 +00001048 patch_ref = patch_refs.pop(self.FuzzyMatchUrl(patch_refs), None)
1049 target_branch = target_branches.pop(
1050 self.FuzzyMatchUrl(target_branches), None)
Joanna Wangf3edc502022-07-20 00:12:10 +00001051 if patch_ref:
1052 latest_commit = self._used_scm.apply_patch_ref(
1053 patch_repo, patch_ref, target_branch, options, file_list)
1054 else:
1055 latest_commit = self._used_scm.revinfo(None, None, None)
1056 existing_sync_commits = json.loads(
1057 os.environ.get(PREVIOUS_SYNC_COMMITS, '{}'))
1058 existing_sync_commits[self.name] = latest_commit
1059 os.environ[PREVIOUS_SYNC_COMMITS] = json.dumps(existing_sync_commits)
Edward Lesmesc621b212018-03-21 20:26:56 -04001060
agabled437d762016-10-17 09:35:11 -07001061 if file_list:
1062 file_list = [os.path.join(self.name, f.strip()) for f in file_list]
maruel@chromium.org68988972011-09-20 14:11:42 +00001063
1064 # TODO(phajdan.jr): We should know exactly when the paths are absolute.
1065 # Convert all absolute paths to relative.
iannucci@chromium.org396e1a62013-07-03 19:41:04 +00001066 for i in range(len(file_list or [])):
maruel@chromium.org68988972011-09-20 14:11:42 +00001067 # It depends on the command being executed (like runhooks vs sync).
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001068 if not os.path.isabs(file_list[i]):
maruel@chromium.org68988972011-09-20 14:11:42 +00001069 continue
1070 prefix = os.path.commonprefix(
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001071 [self.root.root_dir.lower(), file_list[i].lower()])
1072 file_list[i] = file_list[i][len(prefix):]
maruel@chromium.org68988972011-09-20 14:11:42 +00001073 # Strip any leading path separators.
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001074 while file_list[i].startswith(('\\', '/')):
1075 file_list[i] = file_list[i][1:]
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001076
Joanna Wanga84a16b2022-07-27 18:52:17 +00001077 # We must check for diffs AFTER any patch_refs have been applied.
1078 if skip_sync_revisions:
1079 skip_sync_rev = skip_sync_revisions.pop(
1080 self.FuzzyMatchUrl(skip_sync_revisions), None)
1081 self._should_sync = (skip_sync_rev is None
1082 or self._used_scm.check_diff(skip_sync_rev,
1083 files=['DEPS']))
1084 if not self._should_sync:
1085 logging.debug('Skipping sync for %s. No DEPS changes since last '
1086 'sync at %s' % (self.name, skip_sync_rev))
1087 else:
1088 logging.debug('DEPS changes detected for %s since last sync at '
1089 '%s. Not skipping deps sync' % (
1090 self.name, skip_sync_rev))
1091
Edward Lemurfbb06aa2018-06-11 20:43:06 +00001092 if self.should_recurse:
Edward Lemure05f18d2018-06-08 17:36:53 +00001093 self.ParseDepsFile()
Edward Lesmes5d6cde32018-04-12 18:32:46 -04001094
Edward Lemure7273d22018-05-10 19:13:51 -04001095 self._run_is_done(file_list or [])
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001096
Joanna Wanga84a16b2022-07-27 18:52:17 +00001097 # TODO(crbug.com/1339471): If should_recurse is false, ParseDepsFile never
1098 # gets called meaning we never fetch hooks and dependencies. So there's
1099 # no need to check should_recurse again here.
Edward Lemurfbb06aa2018-06-11 20:43:06 +00001100 if self.should_recurse:
Edward Lesmes5d6cde32018-04-12 18:32:46 -04001101 if command in ('update', 'revert') and not options.noprehooks:
1102 self.RunPreDepsHooks()
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001103 # Parse the dependencies of this dependency.
1104 for s in self.dependencies:
Michael Mossd683d7c2018-06-15 05:05:17 +00001105 if s.should_process:
1106 work_queue.enqueue(s)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001107
1108 if command == 'recurse':
agabled437d762016-10-17 09:35:11 -07001109 # Skip file only checkout.
Edward Lemurbabd0982018-05-11 13:32:37 -04001110 scm = self.GetScmName()
agabled437d762016-10-17 09:35:11 -07001111 if not options.scm or scm in options.scm:
1112 cwd = os.path.normpath(os.path.join(self.root.root_dir, self.name))
1113 # Pass in the SCM type as an env variable. Make sure we don't put
1114 # unicode strings in the environment.
1115 env = os.environ.copy()
Michael Mossd683d7c2018-06-15 05:05:17 +00001116 if scm:
1117 env['GCLIENT_SCM'] = str(scm)
1118 if self.url:
1119 env['GCLIENT_URL'] = str(self.url)
agabled437d762016-10-17 09:35:11 -07001120 env['GCLIENT_DEP_PATH'] = str(self.name)
1121 if options.prepend_dir and scm == 'git':
1122 print_stdout = False
1123 def filter_fn(line):
1124 """Git-specific path marshaling. It is optimized for git-grep."""
ilevy@chromium.org0233ac22012-11-28 20:27:02 +00001125
agabled437d762016-10-17 09:35:11 -07001126 def mod_path(git_pathspec):
1127 match = re.match('^(\\S+?:)?([^\0]+)$', git_pathspec)
1128 modified_path = os.path.join(self.name, match.group(2))
1129 branch = match.group(1) or ''
1130 return '%s%s' % (branch, modified_path)
ilevy@chromium.org0233ac22012-11-28 20:27:02 +00001131
agabled437d762016-10-17 09:35:11 -07001132 match = re.match('^Binary file ([^\0]+) matches$', line)
1133 if match:
1134 print('Binary file %s matches\n' % mod_path(match.group(1)))
1135 return
ilevy@chromium.org0233ac22012-11-28 20:27:02 +00001136
agabled437d762016-10-17 09:35:11 -07001137 items = line.split('\0')
1138 if len(items) == 2 and items[1]:
1139 print('%s : %s' % (mod_path(items[0]), items[1]))
1140 elif len(items) >= 2:
1141 # Multiple null bytes or a single trailing null byte indicate
1142 # git is likely displaying filenames only (such as with -l)
1143 print('\n'.join(mod_path(path) for path in items if path))
1144 else:
1145 print(line)
1146 else:
1147 print_stdout = True
1148 filter_fn = None
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001149
Michael Mossd683d7c2018-06-15 05:05:17 +00001150 if self.url is None:
1151 print('Skipped omitted dependency %s' % cwd, file=sys.stderr)
1152 elif os.path.isdir(cwd):
agabled437d762016-10-17 09:35:11 -07001153 try:
1154 gclient_utils.CheckCallAndFilter(
Ben Masonfbd2c632020-06-22 14:59:13 +00001155 args, cwd=cwd, env=env, print_stdout=print_stdout,
agabled437d762016-10-17 09:35:11 -07001156 filter_fn=filter_fn,
Ben Masonfbd2c632020-06-22 14:59:13 +00001157 )
agabled437d762016-10-17 09:35:11 -07001158 except subprocess2.CalledProcessError:
1159 if not options.ignore:
1160 raise
1161 else:
1162 print('Skipped missing %s' % cwd, file=sys.stderr)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001163
Edward Lemurbabd0982018-05-11 13:32:37 -04001164 def GetScmName(self):
Edward Lemurb61d3872018-05-09 18:42:47 -04001165 raise NotImplementedError()
John Budorick0f7b2002018-01-19 15:46:17 -08001166
Edward Lemurbabd0982018-05-11 13:32:37 -04001167 def CreateSCM(self, out_cb=None):
Edward Lemurb61d3872018-05-09 18:42:47 -04001168 raise NotImplementedError()
John Budorick0f7b2002018-01-19 15:46:17 -08001169
Dirk Pranke9f20d022017-10-11 18:36:54 -07001170 def HasGNArgsFile(self):
1171 return self._gn_args_file is not None
1172
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +02001173 def WriteGNArgsFile(self):
1174 lines = ['# Generated from %r' % self.deps_file]
Paweł Hajdan, Jrb495bf52017-09-25 19:33:50 +02001175 variables = self.get_vars()
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +02001176 for arg in self._gn_args:
Paweł Hajdan, Jre0214742017-09-28 12:21:01 +02001177 value = variables[arg]
Dirk Prankefdd2cd62020-06-30 23:30:47 +00001178 if isinstance(value, gclient_eval.ConstantString):
1179 value = value.value
1180 elif isinstance(value, basestring):
Paweł Hajdan, Jre0214742017-09-28 12:21:01 +02001181 value = gclient_eval.EvaluateCondition(value, variables)
Paweł Hajdan, Jrb495bf52017-09-25 19:33:50 +02001182 lines.append('%s = %s' % (arg, ToGNString(value)))
Corentin Wallez271a78a2020-07-12 15:41:46 +00001183
1184 # When use_relative_paths is set, gn_args_file is relative to this DEPS
1185 path_prefix = self.root.root_dir
1186 if self._use_relative_paths:
Lei Zhang67283c02020-07-13 21:38:44 +00001187 path_prefix = os.path.join(path_prefix, self.name)
Corentin Wallez271a78a2020-07-12 15:41:46 +00001188
1189 with open(os.path.join(path_prefix, self._gn_args_file), 'wb') as f:
Edward Lesmes05934952019-12-19 20:38:09 +00001190 f.write('\n'.join(lines).encode('utf-8', 'replace'))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001191
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001192 @gclient_utils.lockedmethod
Edward Lemure7273d22018-05-10 19:13:51 -04001193 def _run_is_done(self, file_list):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001194 # Both these are kept for hooks that are run as a separate tree traversal.
1195 self._file_list = file_list
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001196 self._processed = True
1197
szager@google.comb9a78d32012-03-13 18:46:21 +00001198 def GetHooks(self, options):
1199 """Evaluates all hooks, and return them in a flat list.
1200
1201 RunOnDeps() must have been called before to load the DEPS.
1202 """
1203 result = []
Michael Mossd683d7c2018-06-15 05:05:17 +00001204 if not self.should_process or not self.should_recurse:
1205 # Don't run the hook when it is above recursion_limit.
1206 return result
maruel@chromium.orgdc7445d2010-07-09 21:05:29 +00001207 # If "--force" was specified, run all hooks regardless of what files have
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001208 # changed.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001209 if self.deps_hooks:
agabled437d762016-10-17 09:35:11 -07001210 # TODO(maruel): If the user is using git, then we don't know
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001211 # what files have changed so we always run all hooks. It'd be nice to fix
1212 # that.
Edward Lemurbabd0982018-05-11 13:32:37 -04001213 result.extend(self.deps_hooks)
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001214 for s in self.dependencies:
szager@google.comb9a78d32012-03-13 18:46:21 +00001215 result.extend(s.GetHooks(options))
1216 return result
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001217
Daniel Chenga0c5f082017-10-19 13:35:19 -07001218 def RunHooksRecursively(self, options, progress):
szager@google.comb9a78d32012-03-13 18:46:21 +00001219 assert self.hooks_ran == False
maruel@chromium.org064186c2011-09-27 23:53:33 +00001220 self._hooks_ran = True
Daniel Chenga0c5f082017-10-19 13:35:19 -07001221 hooks = self.GetHooks(options)
1222 if progress:
1223 progress._total = len(hooks)
1224 for hook in hooks:
Daniel Chenga0c5f082017-10-19 13:35:19 -07001225 if progress:
1226 progress.update(extra=hook.name or '')
Corentin Walleza68660d2018-09-10 17:33:24 +00001227 hook.run()
Daniel Chenga0c5f082017-10-19 13:35:19 -07001228 if progress:
1229 progress.end()
maruel@chromium.orgeaf61062010-07-07 18:42:39 +00001230
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001231 def RunPreDepsHooks(self):
1232 assert self.processed
1233 assert self.deps_parsed
1234 assert not self.pre_deps_hooks_ran
1235 assert not self.hooks_ran
1236 for s in self.dependencies:
1237 assert not s.processed
1238 self._pre_deps_hooks_ran = True
1239 for hook in self.pre_deps_hooks:
Corentin Walleza68660d2018-09-10 17:33:24 +00001240 hook.run()
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001241
John Budorickd3ba72b2018-03-20 12:27:42 -07001242 def GetCipdRoot(self):
1243 if self.root is self:
1244 # Let's not infinitely recurse. If this is root and isn't an
1245 # instance of GClient, do nothing.
1246 return None
1247 return self.root.GetCipdRoot()
1248
Michael Mossd683d7c2018-06-15 05:05:17 +00001249 def subtree(self, include_all):
maruel@chromium.orgad3287e2011-10-03 19:15:10 +00001250 """Breadth first recursion excluding root node."""
maruel@chromium.orgf13a4182011-09-22 00:26:15 +00001251 dependencies = self.dependencies
1252 for d in dependencies:
Michael Mossd683d7c2018-06-15 05:05:17 +00001253 if d.should_process or include_all:
1254 yield d
maruel@chromium.orgf13a4182011-09-22 00:26:15 +00001255 for d in dependencies:
Michael Mossd683d7c2018-06-15 05:05:17 +00001256 for i in d.subtree(include_all):
maruel@chromium.orgad3287e2011-10-03 19:15:10 +00001257 yield i
1258
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001259 @gclient_utils.lockedmethod
1260 def add_dependency(self, new_dep):
1261 self._dependencies.append(new_dep)
1262
1263 @gclient_utils.lockedmethod
Paweł Hajdan, Jr357415c2017-07-24 14:35:28 +02001264 def _mark_as_parsed(self, new_hooks):
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001265 self._deps_hooks.extend(new_hooks)
1266 self._deps_parsed = True
1267
maruel@chromium.org68988972011-09-20 14:11:42 +00001268 @property
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001269 @gclient_utils.lockedmethod
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +00001270 def dependencies(self):
1271 return tuple(self._dependencies)
1272
1273 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001274 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001275 def deps_hooks(self):
1276 return tuple(self._deps_hooks)
1277
1278 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001279 @gclient_utils.lockedmethod
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001280 def pre_deps_hooks(self):
1281 return tuple(self._pre_deps_hooks)
1282
1283 @property
1284 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001285 def deps_parsed(self):
maruel@chromium.org3223edd2011-10-10 23:17:39 +00001286 """This is purely for debugging purposes. It's not used anywhere."""
maruel@chromium.org064186c2011-09-27 23:53:33 +00001287 return self._deps_parsed
1288
1289 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001290 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001291 def processed(self):
1292 return self._processed
1293
1294 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001295 @gclient_utils.lockedmethod
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001296 def pre_deps_hooks_ran(self):
1297 return self._pre_deps_hooks_ran
1298
1299 @property
1300 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001301 def hooks_ran(self):
1302 return self._hooks_ran
1303
1304 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001305 @gclient_utils.lockedmethod
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00001306 def allowed_hosts(self):
1307 return self._allowed_hosts
1308
1309 @property
1310 @gclient_utils.lockedmethod
maruel@chromium.org861fd0f2010-07-23 03:05:05 +00001311 def file_list(self):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001312 return tuple(self._file_list)
1313
1314 @property
kustermann@google.coma692e8f2013-04-18 08:32:04 +00001315 def used_scm(self):
1316 """SCMWrapper instance for this dependency or None if not processed yet."""
1317 return self._used_scm
1318
1319 @property
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00001320 @gclient_utils.lockedmethod
1321 def got_revision(self):
1322 return self._got_revision
1323
1324 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001325 def file_list_and_children(self):
1326 result = list(self.file_list)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +00001327 for d in self.dependencies:
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001328 result.extend(d.file_list_and_children)
maruel@chromium.org68988972011-09-20 14:11:42 +00001329 return tuple(result)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +00001330
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001331 def __str__(self):
1332 out = []
Edward Lemure7273d22018-05-10 19:13:51 -04001333 for i in ('name', 'url', 'custom_deps',
Michael Mossd683d7c2018-06-15 05:05:17 +00001334 'custom_vars', 'deps_hooks', 'file_list', 'should_process',
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00001335 'processed', 'hooks_ran', 'deps_parsed', 'requirements',
1336 'allowed_hosts'):
maruel@chromium.org3c74bc92011-09-15 19:17:21 +00001337 # First try the native property if it exists.
1338 if hasattr(self, '_' + i):
1339 value = getattr(self, '_' + i, False)
1340 else:
1341 value = getattr(self, i, False)
1342 if value:
1343 out.append('%s: %s' % (i, value))
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001344
1345 for d in self.dependencies:
1346 out.extend([' ' + x for x in str(d).splitlines()])
1347 out.append('')
1348 return '\n'.join(out)
1349
1350 def __repr__(self):
1351 return '%s: %s' % (self.name, self.url)
1352
Joanna Wang9144b672023-02-24 23:36:17 +00001353 def hierarchy(self, include_url=True, graphviz=False):
maruel@chromium.orgbc2d2f92010-07-22 21:26:48 +00001354 """Returns a human-readable hierarchical reference to a Dependency."""
Michael Moss4e9b50a2018-05-23 22:35:06 -07001355 def format_name(d):
1356 if include_url:
1357 return '%s(%s)' % (d.name, d.url)
Joanna Wang9144b672023-02-24 23:36:17 +00001358 return '"%s"' % d.name # quotes required for graph dot file.
1359
Michael Moss4e9b50a2018-05-23 22:35:06 -07001360 out = format_name(self)
maruel@chromium.orgbffb9042010-07-22 20:59:36 +00001361 i = self.parent
1362 while i and i.name:
Michael Moss4e9b50a2018-05-23 22:35:06 -07001363 out = '%s -> %s' % (format_name(i), out)
Joanna Wang9144b672023-02-24 23:36:17 +00001364 if graphviz:
1365 # for graphviz we just need each parent->child relationship listed once.
1366 return out
maruel@chromium.orgbffb9042010-07-22 20:59:36 +00001367 i = i.parent
1368 return out
1369
Michael Mossfe68c912018-03-22 19:19:35 -07001370 def hierarchy_data(self):
1371 """Returns a machine-readable hierarchical reference to a Dependency."""
1372 d = self
1373 out = []
1374 while d and d.name:
1375 out.insert(0, (d.name, d.url))
1376 d = d.parent
1377 return tuple(out)
1378
Edward Lemur8f8a50d2018-11-01 22:03:02 +00001379 def get_builtin_vars(self):
1380 return {
Paweł Hajdan, Jrd325eb32017-10-03 17:43:37 +02001381 'checkout_android': 'android' in self.target_os,
Benjamin Pastene6fe29412018-01-23 15:35:58 -08001382 'checkout_chromeos': 'chromeos' in self.target_os,
Paweł Hajdan, Jrd325eb32017-10-03 17:43:37 +02001383 'checkout_fuchsia': 'fuchsia' in self.target_os,
1384 'checkout_ios': 'ios' in self.target_os,
1385 'checkout_linux': 'unix' in self.target_os,
1386 'checkout_mac': 'mac' in self.target_os,
1387 'checkout_win': 'win' in self.target_os,
1388 'host_os': _detect_host_os(),
Robbie Iannucci3db32762023-07-05 19:02:44 +00001389
Tom Andersonc31ae0b2018-02-06 14:48:56 -08001390 'checkout_arm': 'arm' in self.target_cpu,
1391 'checkout_arm64': 'arm64' in self.target_cpu,
1392 'checkout_x86': 'x86' in self.target_cpu,
1393 'checkout_mips': 'mips' in self.target_cpu,
Wang Qing254538b2018-07-26 02:23:53 +00001394 'checkout_mips64': 'mips64' in self.target_cpu,
Tom Andersonc31ae0b2018-02-06 14:48:56 -08001395 'checkout_ppc': 'ppc' in self.target_cpu,
1396 'checkout_s390': 's390' in self.target_cpu,
1397 'checkout_x64': 'x64' in self.target_cpu,
1398 'host_cpu': detect_host_arch.HostArch(),
Paweł Hajdan, Jra3b67ae2017-08-30 15:18:21 +02001399 }
Edward Lemur8f8a50d2018-11-01 22:03:02 +00001400
1401 def get_vars(self):
1402 """Returns a dictionary of effective variable values
1403 (DEPS file contents with applied custom_vars overrides)."""
1404 # Variable precedence (last has highest):
Michael Mossda55cdc2018-04-06 18:37:19 -07001405 # - DEPS vars
1406 # - parents, from first to last
Edward Lemur8f8a50d2018-11-01 22:03:02 +00001407 # - built-in
Michael Mossda55cdc2018-04-06 18:37:19 -07001408 # - custom_vars overrides
Edward Lemur8f8a50d2018-11-01 22:03:02 +00001409 result = {}
Paweł Hajdan, Jra3b67ae2017-08-30 15:18:21 +02001410 result.update(self._vars)
Michael Mossda55cdc2018-04-06 18:37:19 -07001411 if self.parent:
Dirk Prankefdd2cd62020-06-30 23:30:47 +00001412 merge_vars(result, self.parent.get_vars())
Edward Lemur8f8a50d2018-11-01 22:03:02 +00001413 # Provide some built-in variables.
1414 result.update(self.get_builtin_vars())
Dirk Prankefdd2cd62020-06-30 23:30:47 +00001415 merge_vars(result, self.custom_vars)
1416
Paweł Hajdan, Jrd3790252017-07-03 21:06:24 +02001417 return result
1418
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001419
Paweł Hajdan, Jra3b67ae2017-08-30 15:18:21 +02001420_PLATFORM_MAPPING = {
1421 'cygwin': 'win',
1422 'darwin': 'mac',
1423 'linux2': 'linux',
Edward Lemuree7b9dd2019-07-20 01:29:08 +00001424 'linux': 'linux',
Paweł Hajdan, Jra3b67ae2017-08-30 15:18:21 +02001425 'win32': 'win',
Jaideep Bajwad05f3582017-09-11 12:31:48 -04001426 'aix6': 'aix',
Paweł Hajdan, Jra3b67ae2017-08-30 15:18:21 +02001427}
1428
1429
Dirk Prankefdd2cd62020-06-30 23:30:47 +00001430def merge_vars(result, new_vars):
1431 for k, v in new_vars.items():
1432 if k in result:
1433 if isinstance(result[k], gclient_eval.ConstantString):
1434 if isinstance(v, gclient_eval.ConstantString):
1435 result[k] = v
1436 else:
1437 result[k].value = v
1438 else:
1439 result[k] = v
1440 else:
1441 result[k] = v
1442
1443
Paweł Hajdan, Jra3b67ae2017-08-30 15:18:21 +02001444def _detect_host_os():
Jonas Termansenbf7eb522023-01-19 17:56:40 +00001445 if sys.platform in _PLATFORM_MAPPING:
1446 return _PLATFORM_MAPPING[sys.platform]
1447
1448 try:
1449 return os.uname().sysname.lower()
1450 except AttributeError:
1451 return sys.platform
Paweł Hajdan, Jra3b67ae2017-08-30 15:18:21 +02001452
1453
Edward Lemurb61d3872018-05-09 18:42:47 -04001454class GitDependency(Dependency):
1455 """A Dependency object that represents a single git checkout."""
1456
Aravind Vasudevan5965d3e2022-06-01 21:51:30 +00001457 @staticmethod
1458 def updateProtocol(url, protocol):
1459 """Updates given URL's protocol"""
1460 # only works on urls, skips local paths
1461 if not url or not protocol or not re.match('([a-z]+)://', url) or \
1462 re.match('file://', url):
1463 return url
1464
1465 return re.sub('^([a-z]+):', protocol + ':', url)
1466
Edward Lemurb61d3872018-05-09 18:42:47 -04001467 #override
Edward Lemurbabd0982018-05-11 13:32:37 -04001468 def GetScmName(self):
Edward Lemurb61d3872018-05-09 18:42:47 -04001469 """Always 'git'."""
Edward Lemurb61d3872018-05-09 18:42:47 -04001470 return 'git'
1471
1472 #override
Edward Lemurbabd0982018-05-11 13:32:37 -04001473 def CreateSCM(self, out_cb=None):
Edward Lemurb61d3872018-05-09 18:42:47 -04001474 """Create a Wrapper instance suitable for handling this git dependency."""
Edward Lemurbabd0982018-05-11 13:32:37 -04001475 return gclient_scm.GitWrapper(
1476 self.url, self.root.root_dir, self.name, self.outbuf, out_cb,
1477 print_outbuf=self.print_outbuf)
Edward Lemurb61d3872018-05-09 18:42:47 -04001478
1479
1480class GClient(GitDependency):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001481 """Object that represent a gclient checkout. A tree of Dependency(), one per
1482 solution or DEPS entry."""
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001483
1484 DEPS_OS_CHOICES = {
Jaideep Bajwad05f3582017-09-11 12:31:48 -04001485 "aix6": "unix",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001486 "win32": "win",
1487 "win": "win",
1488 "cygwin": "win",
1489 "darwin": "mac",
1490 "mac": "mac",
1491 "unix": "unix",
1492 "linux": "unix",
1493 "linux2": "unix",
maruel@chromium.org244e3442011-06-12 15:20:55 +00001494 "linux3": "unix",
szager@chromium.orgf8c95cd2012-06-01 22:26:52 +00001495 "android": "android",
Michael Mossc54fa812017-08-17 11:27:58 -07001496 "ios": "ios",
Sergiy Byelozyorov518bb682018-06-03 11:25:58 +02001497 "fuchsia": "fuchsia",
Michael Moss484d74f2019-02-06 01:55:43 +00001498 "chromeos": "chromeos",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001499 }
1500
1501 DEFAULT_CLIENT_FILE_TEXT = ("""\
1502solutions = [
Edward Lesmes05934952019-12-19 20:38:09 +00001503 { "name" : %(solution_name)r,
1504 "url" : %(solution_url)r,
1505 "deps_file" : %(deps_file)r,
1506 "managed" : %(managed)r,
smutae7ea312016-07-18 11:59:41 -07001507 "custom_deps" : {
1508 },
Paweł Hajdan, Jr3ba2a7c2017-10-04 19:24:46 +02001509 "custom_vars": %(custom_vars)r,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001510 },
1511]
Robert Iannuccia19649b2018-06-29 16:31:45 +00001512""")
1513
1514 DEFAULT_CLIENT_CACHE_DIR_TEXT = ("""\
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001515cache_dir = %(cache_dir)r
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001516""")
1517
Robert Iannuccia19649b2018-06-29 16:31:45 +00001518
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001519 DEFAULT_SNAPSHOT_FILE_TEXT = ("""\
1520# Snapshot generated with gclient revinfo --snapshot
Edward Lesmesc2960242018-03-06 20:50:15 -05001521solutions = %(solution_list)s
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001522""")
1523
1524 def __init__(self, root_dir, options):
maruel@chromium.org0d812442010-08-10 12:41:08 +00001525 # Do not change previous behavior. Only solution level and immediate DEPS
1526 # are processed.
1527 self._recursion_limit = 2
Edward Lemure05f18d2018-06-08 17:36:53 +00001528 super(GClient, self).__init__(
1529 parent=None,
1530 name=None,
Michael Mossd683d7c2018-06-15 05:05:17 +00001531 url=None,
Edward Lemure05f18d2018-06-08 17:36:53 +00001532 managed=True,
1533 custom_deps=None,
1534 custom_vars=None,
1535 custom_hooks=None,
1536 deps_file='unused',
Michael Mossd683d7c2018-06-15 05:05:17 +00001537 should_process=True,
Edward Lemurfbb06aa2018-06-11 20:43:06 +00001538 should_recurse=True,
Edward Lemure05f18d2018-06-08 17:36:53 +00001539 relative=None,
1540 condition=None,
1541 print_outbuf=True)
1542
maruel@chromium.org0d425922010-06-21 19:22:24 +00001543 self._options = options
maruel@chromium.org271375b2010-06-23 19:17:38 +00001544 if options.deps_os:
1545 enforced_os = options.deps_os.split(',')
1546 else:
1547 enforced_os = [self.DEPS_OS_CHOICES.get(sys.platform, 'unix')]
1548 if 'all' in enforced_os:
Edward Lemuree7b9dd2019-07-20 01:29:08 +00001549 enforced_os = self.DEPS_OS_CHOICES.values()
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001550 self._enforced_os = tuple(set(enforced_os))
Aravind Vasudevanc5f0cbb2022-01-24 23:56:57 +00001551 self._enforced_cpu = (detect_host_arch.HostArch(), )
maruel@chromium.org271375b2010-06-23 19:17:38 +00001552 self._root_dir = root_dir
John Budorickd3ba72b2018-03-20 12:27:42 -07001553 self._cipd_root = None
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001554 self.config_content = None
1555
borenet@google.com88d10082014-03-21 17:24:48 +00001556 def _CheckConfig(self):
1557 """Verify that the config matches the state of the existing checked-out
1558 solutions."""
1559 for dep in self.dependencies:
Michael Mossd683d7c2018-06-15 05:05:17 +00001560 if dep.managed and dep.url:
Edward Lemurbabd0982018-05-11 13:32:37 -04001561 scm = dep.CreateSCM()
smut@google.comd33eab32014-07-07 19:35:18 +00001562 actual_url = scm.GetActualRemoteURL(self._options)
borenet@google.com4e9be262014-04-08 19:40:30 +00001563 if actual_url and not scm.DoesRemoteURLMatch(self._options):
levarum@chromium.org27a6f9a2016-05-28 00:21:49 +00001564 mirror = scm.GetCacheMirror()
1565 if mirror:
1566 mirror_string = '%s (exists=%s)' % (mirror.mirror_path,
1567 mirror.exists())
1568 else:
1569 mirror_string = 'not used'
Raul Tambreb946b232019-03-26 14:48:46 +00001570 raise gclient_utils.Error(
1571 '''
borenet@google.com88d10082014-03-21 17:24:48 +00001572Your .gclient file seems to be broken. The requested URL is different from what
borenet@google.com0a427372014-04-02 19:12:13 +00001573is actually checked out in %(checkout_path)s.
borenet@google.com88d10082014-03-21 17:24:48 +00001574
borenet@google.com97882362014-04-07 20:06:02 +00001575The .gclient file contains:
levarum@chromium.org27a6f9a2016-05-28 00:21:49 +00001576URL: %(expected_url)s (%(expected_scm)s)
1577Cache mirror: %(mirror_string)s
borenet@google.com97882362014-04-07 20:06:02 +00001578
1579The local checkout in %(checkout_path)s reports:
1580%(actual_url)s (%(actual_scm)s)
borenet@google.com88d10082014-03-21 17:24:48 +00001581
1582You should ensure that the URL listed in .gclient is correct and either change
agabled437d762016-10-17 09:35:11 -07001583it or fix the checkout.
Josip Sokcevic14a83ae2020-05-21 01:36:34 +00001584''' % {
1585 'checkout_path': os.path.join(self.root_dir, dep.name),
1586 'expected_url': dep.url,
1587 'expected_scm': dep.GetScmName(),
1588 'mirror_string': mirror_string,
1589 'actual_url': actual_url,
1590 'actual_scm': dep.GetScmName()
1591 })
borenet@google.com88d10082014-03-21 17:24:48 +00001592
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001593 def SetConfig(self, content):
maruel@chromium.orgf13a4182011-09-22 00:26:15 +00001594 assert not self.dependencies
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001595 config_dict = {}
1596 self.config_content = content
1597 try:
1598 exec(content, config_dict)
vapier@chromium.orga81a56e2015-11-11 07:56:13 +00001599 except SyntaxError as e:
maruel@chromium.org5990f9d2010-07-07 18:02:58 +00001600 gclient_utils.SyntaxErrorToError('.gclient', e)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001601
peter@chromium.org1efccc82012-04-27 16:34:38 +00001602 # Append any target OS that is not already being enforced to the tuple.
1603 target_os = config_dict.get('target_os', [])
stuartmorgan@chromium.org18a4f6a2012-11-02 13:37:24 +00001604 if config_dict.get('target_os_only', False):
1605 self._enforced_os = tuple(set(target_os))
1606 else:
1607 self._enforced_os = tuple(set(self._enforced_os).union(target_os))
1608
Tom Andersonc31ae0b2018-02-06 14:48:56 -08001609 # Append any target CPU that is not already being enforced to the tuple.
1610 target_cpu = config_dict.get('target_cpu', [])
1611 if config_dict.get('target_cpu_only', False):
1612 self._enforced_cpu = tuple(set(target_cpu))
1613 else:
1614 self._enforced_cpu = tuple(set(self._enforced_cpu).union(target_cpu))
1615
Robert Iannuccia19649b2018-06-29 16:31:45 +00001616 cache_dir = config_dict.get('cache_dir', UNSET_CACHE_DIR)
1617 if cache_dir is not UNSET_CACHE_DIR:
1618 if cache_dir:
1619 cache_dir = os.path.join(self.root_dir, cache_dir)
1620 cache_dir = os.path.abspath(cache_dir)
Andrii Shyshkalov77ce4bd2017-11-27 12:38:18 -08001621
Robert Iannuccia19649b2018-06-29 16:31:45 +00001622 git_cache.Mirror.SetCachePath(cache_dir)
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001623
stuartmorgan@chromium.org18a4f6a2012-11-02 13:37:24 +00001624 if not target_os and config_dict.get('target_os_only', False):
1625 raise gclient_utils.Error('Can\'t use target_os_only if target_os is '
1626 'not specified')
peter@chromium.org1efccc82012-04-27 16:34:38 +00001627
Tom Andersonc31ae0b2018-02-06 14:48:56 -08001628 if not target_cpu and config_dict.get('target_cpu_only', False):
1629 raise gclient_utils.Error('Can\'t use target_cpu_only if target_cpu is '
1630 'not specified')
1631
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001632 deps_to_add = []
Michael Mossd683d7c2018-06-15 05:05:17 +00001633 for s in config_dict.get('solutions', []):
1634 try:
Aravind Vasudevanb6eaed22023-07-06 20:50:42 +00001635 deps_to_add.append(
1636 GitDependency(
1637 parent=self,
1638 name=s['name'],
1639 # Update URL with scheme in protocol_override
1640 url=GitDependency.updateProtocol(
1641 s['url'], s.get('protocol_override', None)),
1642 managed=s.get('managed', True),
1643 custom_deps=s.get('custom_deps', {}),
1644 custom_vars=s.get('custom_vars', {}),
1645 custom_hooks=s.get('custom_hooks', []),
1646 deps_file=s.get('deps_file', 'DEPS'),
1647 should_process=True,
1648 should_recurse=True,
1649 relative=None,
1650 condition=None,
1651 print_outbuf=True,
1652 # Pass protocol_override down the tree for child deps to use.
1653 protocol=s.get('protocol_override', None),
1654 git_dependencies_state=self.git_dependencies_state))
Michael Mossd683d7c2018-06-15 05:05:17 +00001655 except KeyError:
1656 raise gclient_utils.Error('Invalid .gclient file. Solution is '
1657 'incomplete: %s' % s)
Edward Lemur40764b02018-07-20 18:50:29 +00001658 metrics.collector.add(
1659 'project_urls',
1660 [
Edward Lemuraffd4102019-06-05 18:07:49 +00001661 dep.FuzzyMatchUrl(metrics_utils.KNOWN_PROJECT_URLS)
Edward Lemur40764b02018-07-20 18:50:29 +00001662 for dep in deps_to_add
1663 if dep.FuzzyMatchUrl(metrics_utils.KNOWN_PROJECT_URLS)
1664 ]
1665 )
1666
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001667 self.add_dependencies_and_close(deps_to_add, config_dict.get('hooks', []))
1668 logging.info('SetConfig() done')
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001669
1670 def SaveConfig(self):
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001671 gclient_utils.FileWrite(os.path.join(self.root_dir,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001672 self._options.config_filename),
1673 self.config_content)
1674
1675 @staticmethod
1676 def LoadCurrentConfig(options):
Joanna Wang66286612022-06-30 19:59:13 +00001677 # type: (optparse.Values) -> GClient
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001678 """Searches for and loads a .gclient file relative to the current working
Joanna Wang66286612022-06-30 19:59:13 +00001679 dir."""
szager@chromium.orge2e03202012-07-31 18:05:16 +00001680 if options.spec:
1681 client = GClient('.', options)
1682 client.SetConfig(options.spec)
1683 else:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00001684 if options.verbose:
1685 print('Looking for %s starting from %s\n' % (
1686 options.config_filename, os.getcwd()))
Nico Weber09e0b382019-03-11 16:54:07 +00001687 path = gclient_paths.FindGclientRoot(os.getcwd(), options.config_filename)
szager@chromium.orge2e03202012-07-31 18:05:16 +00001688 if not path:
Michael Achenbachb3ce73d2017-10-11 16:41:27 +02001689 if options.verbose:
1690 print('Couldn\'t find configuration file.')
szager@chromium.orge2e03202012-07-31 18:05:16 +00001691 return None
1692 client = GClient(path, options)
1693 client.SetConfig(gclient_utils.FileRead(
1694 os.path.join(path, options.config_filename)))
maruel@chromium.org69392e72011-10-13 22:09:00 +00001695
1696 if (options.revisions and
1697 len(client.dependencies) > 1 and
1698 any('@' not in r for r in options.revisions)):
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001699 print(
1700 ('You must specify the full solution name like --revision %s@%s\n'
1701 'when you have multiple solutions setup in your .gclient file.\n'
1702 'Other solutions present are: %s.') % (
maruel@chromium.org69392e72011-10-13 22:09:00 +00001703 client.dependencies[0].name,
1704 options.revisions[0],
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001705 ', '.join(s.name for s in client.dependencies[1:])),
1706 file=sys.stderr)
Joanna Wang66286612022-06-30 19:59:13 +00001707
maruel@chromium.org15804092010-09-02 17:07:37 +00001708 return client
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001709
nsylvain@google.comefc80932011-05-31 21:27:56 +00001710 def SetDefaultConfig(self, solution_name, deps_file, solution_url,
Robert Iannuccia19649b2018-06-29 16:31:45 +00001711 managed=True, cache_dir=UNSET_CACHE_DIR,
1712 custom_vars=None):
1713 text = self.DEFAULT_CLIENT_FILE_TEXT
1714 format_dict = {
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001715 'solution_name': solution_name,
1716 'solution_url': solution_url,
nsylvain@google.comefc80932011-05-31 21:27:56 +00001717 'deps_file': deps_file,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001718 'managed': managed,
Paweł Hajdan, Jr3ba2a7c2017-10-04 19:24:46 +02001719 'custom_vars': custom_vars or {},
Robert Iannuccia19649b2018-06-29 16:31:45 +00001720 }
1721
1722 if cache_dir is not UNSET_CACHE_DIR:
1723 text += self.DEFAULT_CLIENT_CACHE_DIR_TEXT
1724 format_dict['cache_dir'] = cache_dir
1725
1726 self.SetConfig(text % format_dict)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001727
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001728 def _SaveEntries(self):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001729 """Creates a .gclient_entries file to record the list of unique checkouts.
1730
1731 The .gclient_entries file lives in the same directory as .gclient.
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001732 """
1733 # Sometimes pprint.pformat will use {', sometimes it'll use { ' ... It
1734 # makes testing a bit too fun.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001735 result = 'entries = {\n'
Michael Mossd683d7c2018-06-15 05:05:17 +00001736 for entry in self.root.subtree(False):
agabled437d762016-10-17 09:35:11 -07001737 result += ' %s: %s,\n' % (pprint.pformat(entry.name),
Edward Lemure7273d22018-05-10 19:13:51 -04001738 pprint.pformat(entry.url))
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001739 result += '}\n'
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001740 file_path = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org1333cb32011-10-04 23:40:16 +00001741 logging.debug(result)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001742 gclient_utils.FileWrite(file_path, result)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001743
1744 def _ReadEntries(self):
1745 """Read the .gclient_entries file for the given client.
1746
1747 Returns:
1748 A sequence of solution names, which will be empty if there is the
1749 entries file hasn't been created yet.
1750 """
1751 scope = {}
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001752 filename = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001753 if not os.path.exists(filename):
maruel@chromium.org73e21142010-07-05 13:32:01 +00001754 return {}
maruel@chromium.org5990f9d2010-07-07 18:02:58 +00001755 try:
1756 exec(gclient_utils.FileRead(filename), scope)
vapier@chromium.orga81a56e2015-11-11 07:56:13 +00001757 except SyntaxError as e:
maruel@chromium.org5990f9d2010-07-07 18:02:58 +00001758 gclient_utils.SyntaxErrorToError(filename, e)
Aaron Gable3721ee92017-04-03 14:53:14 -07001759 return scope.get('entries', {})
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001760
Joanna Wang01870792022-08-01 19:02:57 +00001761 def _ExtractFileJsonContents(self, default_filename):
1762 # type: (str) -> Mapping[str,Any]
1763 f = os.path.join(self.root_dir, default_filename)
1764
1765 if not os.path.exists(f):
1766 logging.info('File %s does not exist.' % f)
1767 return {}
1768
1769 with open(f, 'r') as open_f:
1770 logging.info('Reading content from file %s' % f)
1771 content = open_f.read().rstrip()
1772 if content:
1773 return json.loads(content)
1774 return {}
1775
1776 def _WriteFileContents(self, default_filename, content):
1777 # type: (str, str) -> None
1778 f = os.path.join(self.root_dir, default_filename)
1779
1780 with open(f, 'w') as open_f:
1781 logging.info('Writing to file %s' % f)
1782 open_f.write(content)
1783
Joanna Wang66286612022-06-30 19:59:13 +00001784 def _EnforceSkipSyncRevisions(self, patch_refs):
1785 # type: (Mapping[str, str]) -> Mapping[str, str]
1786 """Checks for and enforces revisions for skipping deps syncing."""
Joanna Wang01870792022-08-01 19:02:57 +00001787 previous_sync_commits = self._ExtractFileJsonContents(
1788 PREVIOUS_SYNC_COMMITS_FILE)
Joanna Wangf3edc502022-07-20 00:12:10 +00001789
1790 if not previous_sync_commits:
Joanna Wang66286612022-06-30 19:59:13 +00001791 return {}
1792
1793 # Current `self.dependencies` only contain solutions. If a patch_ref is
1794 # not for a solution, then it is for a solution's dependency or recursed
Joanna Wangf3edc502022-07-20 00:12:10 +00001795 # dependency which we cannot support while skipping sync.
Joanna Wang66286612022-06-30 19:59:13 +00001796 if patch_refs:
1797 unclaimed_prs = []
1798 candidates = []
1799 for dep in self.dependencies:
1800 origin, _ = gclient_utils.SplitUrlRevision(dep.url)
1801 candidates.extend([origin, dep.name])
1802 for patch_repo in patch_refs:
1803 if not gclient_utils.FuzzyMatchRepo(patch_repo, candidates):
1804 unclaimed_prs.append(patch_repo)
1805 if unclaimed_prs:
1806 print(
Joanna Wangf3edc502022-07-20 00:12:10 +00001807 'We cannot skip syncs when there are --patch-refs flags for '
1808 'non-solution dependencies. To skip syncing, remove patch_refs '
1809 'for: \n%s' % '\n'.join(unclaimed_prs))
Joanna Wang66286612022-06-30 19:59:13 +00001810 return {}
1811
1812 # We cannot skip syncing if there are custom_vars that differ from the
1813 # previous run's custom_vars.
Joanna Wang01870792022-08-01 19:02:57 +00001814 previous_custom_vars = self._ExtractFileJsonContents(
1815 PREVIOUS_CUSTOM_VARS_FILE)
1816
Joanna Wang66286612022-06-30 19:59:13 +00001817 cvs_by_name = {s.name: s.custom_vars for s in self.dependencies}
Joanna Wangf3edc502022-07-20 00:12:10 +00001818
Joanna Wang66286612022-06-30 19:59:13 +00001819 skip_sync_revisions = {}
Joanna Wangf3edc502022-07-20 00:12:10 +00001820 for name, commit in previous_sync_commits.items():
Joanna Wang01870792022-08-01 19:02:57 +00001821 previous_vars = previous_custom_vars.get(name)
1822 if previous_vars == cvs_by_name.get(name) or (not previous_vars and
1823 not cvs_by_name.get(name)):
Joanna Wangf3edc502022-07-20 00:12:10 +00001824 skip_sync_revisions[name] = commit
Joanna Wang66286612022-06-30 19:59:13 +00001825 else:
Joanna Wangf3edc502022-07-20 00:12:10 +00001826 print('We cannot skip syncs when custom_vars for solutions have '
1827 'changed since the last sync run on this machine.\n'
1828 '\nRemoving skip_sync_revision for:\n'
Joanna Wang66286612022-06-30 19:59:13 +00001829 'solution: %s, current: %r, previous: %r.' %
1830 (name, cvs_by_name.get(name), previous_vars))
Joanna Wanga84a16b2022-07-27 18:52:17 +00001831 print('no-sync experiment enabled with %r' % skip_sync_revisions)
Joanna Wang66286612022-06-30 19:59:13 +00001832 return skip_sync_revisions
1833
1834 # TODO(crbug.com/1340695): Remove handling revisions without '@'.
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001835 def _EnforceRevisions(self):
maruel@chromium.org918a9ae2010-05-28 15:50:30 +00001836 """Checks for revision overrides."""
1837 revision_overrides = {}
smutae7ea312016-07-18 11:59:41 -07001838 if self._options.head:
1839 return revision_overrides
joi@chromium.org792ea882010-11-10 02:37:27 +00001840 if not self._options.revisions:
maruel@chromium.org307d1792010-05-31 20:03:13 +00001841 return revision_overrides
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001842 solutions_names = [s.name for s in self.dependencies]
Joanna Wanga84a16b2022-07-27 18:52:17 +00001843 for index, revision in enumerate(self._options.revisions):
smutae7ea312016-07-18 11:59:41 -07001844 if not '@' in revision:
maruel@chromium.org307d1792010-05-31 20:03:13 +00001845 # Support for --revision 123
smutae7ea312016-07-18 11:59:41 -07001846 revision = '%s@%s' % (solutions_names[index], revision)
1847 name, rev = revision.split('@', 1)
szager@chromium.org4ad264b2014-05-20 04:43:47 +00001848 revision_overrides[name] = rev
maruel@chromium.org918a9ae2010-05-28 15:50:30 +00001849 return revision_overrides
1850
Edward Lemur6a4e31b2018-08-10 19:59:02 +00001851 def _EnforcePatchRefsAndBranches(self):
Joanna Wang66286612022-06-30 19:59:13 +00001852 # type: () -> Tuple[Mapping[str, str], Mapping[str, str]]
Edward Lesmesc621b212018-03-21 20:26:56 -04001853 """Checks for patch refs."""
1854 patch_refs = {}
Edward Lemur6a4e31b2018-08-10 19:59:02 +00001855 target_branches = {}
Edward Lesmesc621b212018-03-21 20:26:56 -04001856 if not self._options.patch_refs:
Edward Lemur6a4e31b2018-08-10 19:59:02 +00001857 return patch_refs, target_branches
Edward Lesmesc621b212018-03-21 20:26:56 -04001858 for given_patch_ref in self._options.patch_refs:
1859 patch_repo, _, patch_ref = given_patch_ref.partition('@')
Edward Lemur4c5c8ab2019-06-07 15:58:13 +00001860 if not patch_repo or not patch_ref or ':' not in patch_ref:
Edward Lesmesc621b212018-03-21 20:26:56 -04001861 raise gclient_utils.Error(
1862 'Wrong revision format: %s should be of the form '
Edward Lemur4c5c8ab2019-06-07 15:58:13 +00001863 'patch_repo@target_branch:patch_ref.' % given_patch_ref)
1864 target_branch, _, patch_ref = patch_ref.partition(':')
1865 target_branches[patch_repo] = target_branch
Edward Lesmesc621b212018-03-21 20:26:56 -04001866 patch_refs[patch_repo] = patch_ref
Edward Lemur6a4e31b2018-08-10 19:59:02 +00001867 return patch_refs, target_branches
Edward Lesmesc621b212018-03-21 20:26:56 -04001868
Edward Lemur5b1fa942018-10-04 23:22:09 +00001869 def _RemoveUnversionedGitDirs(self):
1870 """Remove directories that are no longer part of the checkout.
1871
1872 Notify the user if there is an orphaned entry in their working copy.
1873 Only delete the directory if there are no changes in it, and
1874 delete_unversioned_trees is set to true.
Josip Sokcevic1b8211f2022-09-30 17:46:53 +00001875
1876 Returns CIPD packages that are no longer versioned.
Edward Lemur5b1fa942018-10-04 23:22:09 +00001877 """
1878
Joanna Wang01870792022-08-01 19:02:57 +00001879 entry_names_and_sync = [(i.name, i._should_sync)
1880 for i in self.root.subtree(False) if i.url]
1881 entries = []
1882 if entry_names_and_sync:
1883 entries, _ = zip(*entry_names_and_sync)
Edward Lemur5b1fa942018-10-04 23:22:09 +00001884 full_entries = [os.path.join(self.root_dir, e.replace('/', os.path.sep))
1885 for e in entries]
Joanna Wang01870792022-08-01 19:02:57 +00001886 no_sync_entries = [
1887 name for name, should_sync in entry_names_and_sync if not should_sync
1888 ]
Edward Lemur5b1fa942018-10-04 23:22:09 +00001889
Josip Sokcevic1b8211f2022-09-30 17:46:53 +00001890 removed_cipd_entries = []
Marc-Antoine Ruel8e57b4b2019-10-11 01:01:36 +00001891 for entry, prev_url in self._ReadEntries().items():
Edward Lemur5b1fa942018-10-04 23:22:09 +00001892 if not prev_url:
1893 # entry must have been overridden via .gclient custom_deps
1894 continue
Joanna Wang01870792022-08-01 19:02:57 +00001895 if any(entry.startswith(sln) for sln in no_sync_entries):
1896 # Dependencies of solutions that skipped syncing would not
1897 # show up in `entries`.
1898 continue
Josip Sokcevic1b8211f2022-09-30 17:46:53 +00001899 if (':' in entry):
1900 # This is a cipd package. Don't clean it up, but prepare for return
1901 if entry not in entries:
1902 removed_cipd_entries.append(entry)
1903 continue
Edward Lemur5b1fa942018-10-04 23:22:09 +00001904 # Fix path separator on Windows.
1905 entry_fixed = entry.replace('/', os.path.sep)
1906 e_dir = os.path.join(self.root_dir, entry_fixed)
1907 # Use entry and not entry_fixed there.
1908 if (entry not in entries and
1909 (not any(path.startswith(entry + '/') for path in entries)) and
1910 os.path.exists(e_dir)):
1911 # The entry has been removed from DEPS.
1912 scm = gclient_scm.GitWrapper(
1913 prev_url, self.root_dir, entry_fixed, self.outbuf)
1914
1915 # Check to see if this directory is now part of a higher-up checkout.
1916 scm_root = None
1917 try:
1918 scm_root = gclient_scm.scm.GIT.GetCheckoutRoot(scm.checkout_path)
1919 except subprocess2.CalledProcessError:
1920 pass
1921 if not scm_root:
1922 logging.warning('Could not find checkout root for %s. Unable to '
1923 'determine whether it is part of a higher-level '
1924 'checkout, so not removing.' % entry)
1925 continue
1926
1927 # This is to handle the case of third_party/WebKit migrating from
1928 # being a DEPS entry to being part of the main project.
1929 # If the subproject is a Git project, we need to remove its .git
1930 # folder. Otherwise git operations on that folder will have different
1931 # effects depending on the current working directory.
1932 if os.path.abspath(scm_root) == os.path.abspath(e_dir):
1933 e_par_dir = os.path.join(e_dir, os.pardir)
1934 if gclient_scm.scm.GIT.IsInsideWorkTree(e_par_dir):
1935 par_scm_root = gclient_scm.scm.GIT.GetCheckoutRoot(e_par_dir)
1936 # rel_e_dir : relative path of entry w.r.t. its parent repo.
1937 rel_e_dir = os.path.relpath(e_dir, par_scm_root)
1938 if gclient_scm.scm.GIT.IsDirectoryVersioned(
1939 par_scm_root, rel_e_dir):
1940 save_dir = scm.GetGitBackupDirPath()
1941 # Remove any eventual stale backup dir for the same project.
1942 if os.path.exists(save_dir):
1943 gclient_utils.rmtree(save_dir)
1944 os.rename(os.path.join(e_dir, '.git'), save_dir)
1945 # When switching between the two states (entry/ is a subproject
1946 # -> entry/ is part of the outer project), it is very likely
1947 # that some files are changed in the checkout, unless we are
1948 # jumping *exactly* across the commit which changed just DEPS.
1949 # In such case we want to cleanup any eventual stale files
1950 # (coming from the old subproject) in order to end up with a
1951 # clean checkout.
1952 gclient_scm.scm.GIT.CleanupDir(par_scm_root, rel_e_dir)
1953 assert not os.path.exists(os.path.join(e_dir, '.git'))
Raul Tambre80ee78e2019-05-06 22:41:05 +00001954 print('\nWARNING: \'%s\' has been moved from DEPS to a higher '
1955 'level checkout. The git folder containing all the local'
1956 ' branches has been saved to %s.\n'
1957 'If you don\'t care about its state you can safely '
1958 'remove that folder to free up space.' % (entry, save_dir))
Edward Lemur5b1fa942018-10-04 23:22:09 +00001959 continue
1960
1961 if scm_root in full_entries:
1962 logging.info('%s is part of a higher level checkout, not removing',
1963 scm.GetCheckoutRoot())
1964 continue
1965
1966 file_list = []
1967 scm.status(self._options, [], file_list)
1968 modified_files = file_list != []
1969 if (not self._options.delete_unversioned_trees or
1970 (modified_files and not self._options.force)):
1971 # There are modified files in this entry. Keep warning until
1972 # removed.
Henrique Ferreiroe72279d2019-04-17 12:01:50 +00001973 self.add_dependency(
1974 GitDependency(
1975 parent=self,
1976 name=entry,
Aravind Vasudevan810598d2022-06-13 21:23:47 +00001977 # Update URL with scheme in protocol_override
Aravind Vasudevan5965d3e2022-06-01 21:51:30 +00001978 url=GitDependency.updateProtocol(prev_url, self.protocol),
Henrique Ferreiroe72279d2019-04-17 12:01:50 +00001979 managed=False,
1980 custom_deps={},
1981 custom_vars={},
1982 custom_hooks=[],
1983 deps_file=None,
1984 should_process=True,
1985 should_recurse=False,
1986 relative=None,
Aravind Vasudevan5965d3e2022-06-01 21:51:30 +00001987 condition=None,
Aravind Vasudevanb6eaed22023-07-06 20:50:42 +00001988 protocol=self.protocol,
1989 git_dependencies_state=self.git_dependencies_state))
Anthony Politobb457342019-11-15 22:26:01 +00001990 if modified_files and self._options.delete_unversioned_trees:
1991 print('\nWARNING: \'%s\' is no longer part of this client.\n'
1992 'Despite running \'gclient sync -D\' no action was taken '
1993 'as there are modifications.\nIt is recommended you revert '
1994 'all changes or run \'gclient sync -D --force\' next '
1995 'time.' % entry_fixed)
1996 else:
1997 print('\nWARNING: \'%s\' is no longer part of this client.\n'
1998 'It is recommended that you manually remove it or use '
1999 '\'gclient sync -D\' next time.' % entry_fixed)
Edward Lemur5b1fa942018-10-04 23:22:09 +00002000 else:
2001 # Delete the entry
2002 print('\n________ deleting \'%s\' in \'%s\'' % (
2003 entry_fixed, self.root_dir))
2004 gclient_utils.rmtree(e_dir)
2005 # record the current list of entries for next time
2006 self._SaveEntries()
Josip Sokcevic1b8211f2022-09-30 17:46:53 +00002007 return removed_cipd_entries
Edward Lemur5b1fa942018-10-04 23:22:09 +00002008
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00002009 def RunOnDeps(self, command, args, ignore_requirements=False, progress=True):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002010 """Runs a command on each dependency in a client and its dependencies.
2011
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002012 Args:
2013 command: The command to use (e.g., 'status' or 'diff')
2014 args: list of str - extra arguments to add to the command line.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002015 """
Michael Mossd683d7c2018-06-15 05:05:17 +00002016 if not self.dependencies:
2017 raise gclient_utils.Error('No solution specified')
2018
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00002019 revision_overrides = {}
Edward Lesmesc621b212018-03-21 20:26:56 -04002020 patch_refs = {}
Edward Lemur6a4e31b2018-08-10 19:59:02 +00002021 target_branches = {}
Joanna Wanga84a16b2022-07-27 18:52:17 +00002022 skip_sync_revisions = {}
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00002023 # It's unnecessary to check for revision overrides for 'recurse'.
2024 # Save a few seconds by not calling _EnforceRevisions() in that case.
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +02002025 if command not in ('diff', 'recurse', 'runhooks', 'status', 'revert',
2026 'validate'):
szager@chromium.org5273b8a2014-08-21 15:10:10 +00002027 self._CheckConfig()
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00002028 revision_overrides = self._EnforceRevisions()
Edward Lesmesc621b212018-03-21 20:26:56 -04002029
2030 if command == 'update':
Edward Lemur6a4e31b2018-08-10 19:59:02 +00002031 patch_refs, target_branches = self._EnforcePatchRefsAndBranches()
Joanna Wanga84a16b2022-07-27 18:52:17 +00002032 if NO_SYNC_EXPERIMENT in self._options.experiments:
2033 skip_sync_revisions = self._EnforceSkipSyncRevisions(patch_refs)
Joanna Wang66286612022-06-30 19:59:13 +00002034
Joanna Wang01870792022-08-01 19:02:57 +00002035 # Store solutions' custom_vars on memory to compare in the next run.
2036 # All dependencies added later are inherited from the current
2037 # self.dependencies.
2038 custom_vars = {
2039 dep.name: dep.custom_vars
2040 for dep in self.dependencies if dep.custom_vars
2041 }
2042 if custom_vars:
2043 self._WriteFileContents(PREVIOUS_CUSTOM_VARS_FILE,
2044 json.dumps(custom_vars))
Joanna Wangf3edc502022-07-20 00:12:10 +00002045
Daniel Chenga21b5b32017-10-19 20:07:48 +00002046 # Disable progress for non-tty stdout.
Daniel Chenga0c5f082017-10-19 13:35:19 -07002047 should_show_progress = (
2048 setup_color.IS_TTY and not self._options.verbose and progress)
2049 pm = None
2050 if should_show_progress:
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00002051 if command in ('update', 'revert'):
2052 pm = Progress('Syncing projects', 1)
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +02002053 elif command in ('recurse', 'validate'):
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00002054 pm = Progress(' '.join(args), 1)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00002055 work_queue = gclient_utils.ExecutionQueue(
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00002056 self._options.jobs, pm, ignore_requirements=ignore_requirements,
2057 verbose=self._options.verbose)
maruel@chromium.org049bced2010-08-12 13:37:20 +00002058 for s in self.dependencies:
Michael Mossd683d7c2018-06-15 05:05:17 +00002059 if s.should_process:
2060 work_queue.enqueue(s)
Joanna Wanga84a16b2022-07-27 18:52:17 +00002061 work_queue.flush(revision_overrides,
2062 command,
2063 args,
2064 options=self._options,
2065 patch_refs=patch_refs,
2066 target_branches=target_branches,
Josip Sokcevicd47a9c22023-06-22 05:14:35 +00002067 skip_sync_revisions=skip_sync_revisions)
Edward Lesmesc621b212018-03-21 20:26:56 -04002068
szager@chromium.org4ad264b2014-05-20 04:43:47 +00002069 if revision_overrides:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002070 print('Please fix your script, having invalid --revision flags will soon '
Edward Lesmesc621b212018-03-21 20:26:56 -04002071 'be considered an error.', file=sys.stderr)
2072
Josip Sokcevicd47a9c22023-06-22 05:14:35 +00002073 if patch_refs:
Edward Lesmesc621b212018-03-21 20:26:56 -04002074 raise gclient_utils.Error(
2075 'The following --patch-ref flags were not used. Please fix it:\n%s' %
2076 ('\n'.join(
2077 patch_repo + '@' + patch_ref
Marc-Antoine Ruel8e57b4b2019-10-11 01:01:36 +00002078 for patch_repo, patch_ref in patch_refs.items())))
piman@chromium.org6f363722010-04-27 00:41:09 +00002079
Dirk Pranke9f20d022017-10-11 18:36:54 -07002080 # Once all the dependencies have been processed, it's now safe to write
Michael Moss848c86e2018-05-03 16:05:50 -07002081 # out the gn_args_file and run the hooks.
Josip Sokcevic1b8211f2022-09-30 17:46:53 +00002082 removed_cipd_entries = []
Dirk Pranke9f20d022017-10-11 18:36:54 -07002083 if command == 'update':
Ergün Erdoğmuş28190a22022-06-22 08:50:54 +00002084 for dependency in self.dependencies:
2085 gn_args_dep = dependency
2086 if gn_args_dep._gn_args_from:
2087 deps_map = {dep.name: dep for dep in gn_args_dep.dependencies}
2088 gn_args_dep = deps_map.get(gn_args_dep._gn_args_from)
2089 if gn_args_dep and gn_args_dep.HasGNArgsFile():
2090 gn_args_dep.WriteGNArgsFile()
Dirk Pranke9f20d022017-10-11 18:36:54 -07002091
Josip Sokcevic1b8211f2022-09-30 17:46:53 +00002092 removed_cipd_entries = self._RemoveUnversionedGitDirs()
Edward Lemur647e1e72018-09-19 18:15:29 +00002093
2094 # Sync CIPD dependencies once removed deps are deleted. In case a git
2095 # dependency was moved to CIPD, we want to remove the old git directory
2096 # first and then sync the CIPD dep.
2097 if self._cipd_root:
2098 self._cipd_root.run(command)
Josip Sokcevic1b8211f2022-09-30 17:46:53 +00002099 # It's possible that CIPD removed some entries that are now part of git
2100 # worktree. Try to checkout those directories
2101 if removed_cipd_entries:
2102 for cipd_entry in removed_cipd_entries:
2103 cwd = os.path.join(self._root_dir, cipd_entry.split(':')[0])
2104 cwd, tail = os.path.split(cwd)
2105 if cwd:
2106 try:
2107 gclient_scm.scm.GIT.Capture(['checkout', tail], cwd=cwd)
2108 except subprocess2.CalledProcessError:
2109 pass
Edward Lemur647e1e72018-09-19 18:15:29 +00002110
Edward Lemur5b1fa942018-10-04 23:22:09 +00002111 if not self._options.nohooks:
2112 if should_show_progress:
2113 pm = Progress('Running hooks', 1)
2114 self.RunHooksRecursively(self._options, pm)
2115
Joanna Wang01870792022-08-01 19:02:57 +00002116 self._WriteFileContents(PREVIOUS_SYNC_COMMITS_FILE,
2117 os.environ.get(PREVIOUS_SYNC_COMMITS, '{}'))
2118
maruel@chromium.org17cdf762010-05-28 17:30:52 +00002119 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002120
2121 def PrintRevInfo(self):
maruel@chromium.org54a07a22010-06-14 19:07:39 +00002122 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +00002123 raise gclient_utils.Error('No solution specified')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00002124 # Load all the settings.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00002125 work_queue = gclient_utils.ExecutionQueue(
2126 self._options.jobs, None, False, verbose=self._options.verbose)
maruel@chromium.org049bced2010-08-12 13:37:20 +00002127 for s in self.dependencies:
Michael Mossd683d7c2018-06-15 05:05:17 +00002128 if s.should_process:
2129 work_queue.enqueue(s)
Joanna Wanga84a16b2022-07-27 18:52:17 +00002130 work_queue.flush({},
2131 None, [],
2132 options=self._options,
2133 patch_refs=None,
2134 target_branches=None,
2135 skip_sync_revisions=None)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002136
Michael Mossd683d7c2018-06-15 05:05:17 +00002137 def ShouldPrintRevision(dep):
Edward Lesmesbb16e332018-03-30 17:54:51 -04002138 return (not self._options.filter
Edward Lemure7273d22018-05-10 19:13:51 -04002139 or dep.FuzzyMatchUrl(self._options.filter))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002140
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00002141 if self._options.snapshot:
Michael Mossd683d7c2018-06-15 05:05:17 +00002142 json_output = []
2143 # First level at .gclient
2144 for d in self.dependencies:
2145 entries = {}
2146 def GrabDeps(dep):
2147 """Recursively grab dependencies."""
Aravind Vasudevanc5f0cbb2022-01-24 23:56:57 +00002148 for rec_d in dep.dependencies:
2149 rec_d.PinToActualRevision()
2150 if ShouldPrintRevision(rec_d):
2151 entries[rec_d.name] = rec_d.url
2152 GrabDeps(rec_d)
2153
Michael Mossd683d7c2018-06-15 05:05:17 +00002154 GrabDeps(d)
2155 json_output.append({
2156 'name': d.name,
2157 'solution_url': d.url,
2158 'deps_file': d.deps_file,
2159 'managed': d.managed,
2160 'custom_deps': entries,
2161 })
2162 if self._options.output_json == '-':
2163 print(json.dumps(json_output, indent=2, separators=(',', ': ')))
2164 elif self._options.output_json:
2165 with open(self._options.output_json, 'w') as f:
2166 json.dump(json_output, f)
2167 else:
2168 # Print the snapshot configuration file
2169 print(self.DEFAULT_SNAPSHOT_FILE_TEXT % {
2170 'solution_list': pprint.pformat(json_output, indent=2),
2171 })
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +00002172 else:
Michael Mossd683d7c2018-06-15 05:05:17 +00002173 entries = {}
2174 for d in self.root.subtree(False):
2175 if self._options.actual:
2176 d.PinToActualRevision()
2177 if ShouldPrintRevision(d):
2178 entries[d.name] = d.url
2179 if self._options.output_json:
2180 json_output = {
2181 name: {
2182 'url': rev.split('@')[0] if rev else None,
2183 'rev': rev.split('@')[1] if rev and '@' in rev else None,
2184 }
Marc-Antoine Ruel8e57b4b2019-10-11 01:01:36 +00002185 for name, rev in entries.items()
Michael Mossd683d7c2018-06-15 05:05:17 +00002186 }
2187 if self._options.output_json == '-':
2188 print(json.dumps(json_output, indent=2, separators=(',', ': ')))
2189 else:
2190 with open(self._options.output_json, 'w') as f:
2191 json.dump(json_output, f)
2192 else:
2193 keys = sorted(entries.keys())
2194 for x in keys:
2195 print('%s: %s' % (x, entries[x]))
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +00002196 logging.info(str(self))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002197
Edward Lemure05f18d2018-06-08 17:36:53 +00002198 def ParseDepsFile(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00002199 """No DEPS to parse for a .gclient file."""
maruel@chromium.org049bced2010-08-12 13:37:20 +00002200 raise gclient_utils.Error('Internal error')
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00002201
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002202 def PrintLocationAndContents(self):
2203 # Print out the .gclient file. This is longer than if we just printed the
2204 # client dict, but more legible, and it might contain helpful comments.
2205 print('Loaded .gclient config in %s:\n%s' % (
2206 self.root_dir, self.config_content))
2207
John Budorickd3ba72b2018-03-20 12:27:42 -07002208 def GetCipdRoot(self):
2209 if not self._cipd_root:
2210 self._cipd_root = gclient_scm.CipdRoot(
2211 self.root_dir,
2212 # TODO(jbudorick): Support other service URLs as necessary.
2213 # Service URLs should be constant over the scope of a cipd
2214 # root, so a var per DEPS file specifying the service URL
2215 # should suffice.
2216 'https://chrome-infra-packages.appspot.com')
2217 return self._cipd_root
2218
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00002219 @property
maruel@chromium.org75a59272010-06-11 22:34:03 +00002220 def root_dir(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00002221 """Root directory of gclient checkout."""
maruel@chromium.org75a59272010-06-11 22:34:03 +00002222 return self._root_dir
2223
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00002224 @property
maruel@chromium.org271375b2010-06-23 19:17:38 +00002225 def enforced_os(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00002226 """What deps_os entries that are to be parsed."""
maruel@chromium.org271375b2010-06-23 19:17:38 +00002227 return self._enforced_os
2228
maruel@chromium.org68988972011-09-20 14:11:42 +00002229 @property
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +00002230 def target_os(self):
2231 return self._enforced_os
2232
Tom Andersonc31ae0b2018-02-06 14:48:56 -08002233 @property
2234 def target_cpu(self):
2235 return self._enforced_cpu
2236
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002237
John Budorick0f7b2002018-01-19 15:46:17 -08002238class CipdDependency(Dependency):
2239 """A Dependency object that represents a single CIPD package."""
2240
Michael Mossd683d7c2018-06-15 05:05:17 +00002241 def __init__(
2242 self, parent, name, dep_value, cipd_root,
2243 custom_vars, should_process, relative, condition):
Dan Le Febvref2da9062023-05-03 00:35:09 +00002244 package = dep_value['package']
John Budorick0f7b2002018-01-19 15:46:17 -08002245 version = dep_value['version']
2246 url = urlparse.urljoin(
2247 cipd_root.service_url, '%s@%s' % (package, version))
2248 super(CipdDependency, self).__init__(
Edward Lemure05f18d2018-06-08 17:36:53 +00002249 parent=parent,
2250 name=name + ':' + package,
2251 url=url,
2252 managed=None,
2253 custom_deps=None,
2254 custom_vars=custom_vars,
2255 custom_hooks=None,
2256 deps_file=None,
Michael Mossd683d7c2018-06-15 05:05:17 +00002257 should_process=should_process,
Edward Lemurfbb06aa2018-06-11 20:43:06 +00002258 should_recurse=False,
Edward Lemure05f18d2018-06-08 17:36:53 +00002259 relative=relative,
2260 condition=condition)
John Budorickd3ba72b2018-03-20 12:27:42 -07002261 self._cipd_package = None
John Budorick0f7b2002018-01-19 15:46:17 -08002262 self._cipd_root = cipd_root
John Budorick4099daa2018-06-21 19:22:10 +00002263 # CIPD wants /-separated paths, even on Windows.
2264 native_subdir_path = os.path.relpath(
Shenghua Zhang6f830312018-02-26 11:45:07 -08002265 os.path.join(self.root.root_dir, name), cipd_root.root_dir)
John Budorick4099daa2018-06-21 19:22:10 +00002266 self._cipd_subdir = posixpath.join(*native_subdir_path.split(os.sep))
John Budorickd3ba72b2018-03-20 12:27:42 -07002267 self._package_name = package
2268 self._package_version = version
2269
2270 #override
Josip Sokcevicd47a9c22023-06-22 05:14:35 +00002271 def run(self, revision_overrides, command, args, work_queue, options,
2272 patch_refs, target_branches, skip_sync_revisions):
John Budorickd3ba72b2018-03-20 12:27:42 -07002273 """Runs |command| then parse the DEPS file."""
2274 logging.info('CipdDependency(%s).run()' % self.name)
Michael Mossd683d7c2018-06-15 05:05:17 +00002275 if not self.should_process:
2276 return
John Budorickd3ba72b2018-03-20 12:27:42 -07002277 self._CreatePackageIfNecessary()
Joanna Wanga84a16b2022-07-27 18:52:17 +00002278 super(CipdDependency,
2279 self).run(revision_overrides, command, args, work_queue, options,
2280 patch_refs, target_branches, skip_sync_revisions)
John Budorickd3ba72b2018-03-20 12:27:42 -07002281
2282 def _CreatePackageIfNecessary(self):
2283 # We lazily create the CIPD package to make sure that only packages
2284 # that we want (as opposed to all packages defined in all DEPS files
2285 # we parse) get added to the root and subsequently ensured.
2286 if not self._cipd_package:
2287 self._cipd_package = self._cipd_root.add_package(
2288 self._cipd_subdir, self._package_name, self._package_version)
John Budorick0f7b2002018-01-19 15:46:17 -08002289
Edward Lemure05f18d2018-06-08 17:36:53 +00002290 def ParseDepsFile(self):
John Budorick0f7b2002018-01-19 15:46:17 -08002291 """CIPD dependencies are not currently allowed to have nested deps."""
2292 self.add_dependencies_and_close([], [])
2293
2294 #override
Shenghua Zhang6f830312018-02-26 11:45:07 -08002295 def verify_validity(self):
2296 """CIPD dependencies allow duplicate name for packages in same directory."""
2297 logging.info('Dependency(%s).verify_validity()' % self.name)
2298 return True
2299
2300 #override
Edward Lemurbabd0982018-05-11 13:32:37 -04002301 def GetScmName(self):
John Budorick0f7b2002018-01-19 15:46:17 -08002302 """Always 'cipd'."""
John Budorick0f7b2002018-01-19 15:46:17 -08002303 return 'cipd'
2304
Dan Le Febvreb0e8e7a2023-05-18 23:36:46 +00002305 def GetExpandedPackageName(self):
2306 """Return the CIPD package name with the variables evaluated."""
2307 package = self._cipd_root.expand_package_name(self._package_name)
2308 if package:
2309 return package
2310 return self._package_name
2311
John Budorick0f7b2002018-01-19 15:46:17 -08002312 #override
Edward Lemurbabd0982018-05-11 13:32:37 -04002313 def CreateSCM(self, out_cb=None):
John Budorick0f7b2002018-01-19 15:46:17 -08002314 """Create a Wrapper instance suitable for handling this CIPD dependency."""
John Budorickd3ba72b2018-03-20 12:27:42 -07002315 self._CreatePackageIfNecessary()
John Budorick0f7b2002018-01-19 15:46:17 -08002316 return gclient_scm.CipdWrapper(
Edward Lemurbabd0982018-05-11 13:32:37 -04002317 self.url, self.root.root_dir, self.name, self.outbuf, out_cb,
2318 root=self._cipd_root, package=self._cipd_package)
John Budorick0f7b2002018-01-19 15:46:17 -08002319
Joanna Wang9144b672023-02-24 23:36:17 +00002320 def hierarchy(self, include_url=False, graphviz=False):
2321 if graphviz:
2322 return '' # graphviz lines not implemented for cipd deps.
Edward Lemure4e15042018-06-28 18:07:00 +00002323 return self.parent.hierarchy(include_url) + ' -> ' + self._cipd_subdir
2324
John Budorick0f7b2002018-01-19 15:46:17 -08002325 def ToLines(self):
Joanna Wang9144b672023-02-24 23:36:17 +00002326 # () -> Sequence[str]
John Budorick0f7b2002018-01-19 15:46:17 -08002327 """Return a list of lines representing this in a DEPS file."""
John Budorickc35aba52018-06-28 20:57:03 +00002328 def escape_cipd_var(package):
2329 return package.replace('{', '{{').replace('}', '}}')
2330
John Budorick0f7b2002018-01-19 15:46:17 -08002331 s = []
John Budorickd3ba72b2018-03-20 12:27:42 -07002332 self._CreatePackageIfNecessary()
John Budorick0f7b2002018-01-19 15:46:17 -08002333 if self._cipd_package.authority_for_subdir:
2334 condition_part = ([' "condition": %r,' % self.condition]
2335 if self.condition else [])
2336 s.extend([
Michael Moss4e9b50a2018-05-23 22:35:06 -07002337 ' # %s' % self.hierarchy(include_url=False),
John Budorickd3ba72b2018-03-20 12:27:42 -07002338 ' "%s": {' % (self.name.split(':')[0],),
John Budorick0f7b2002018-01-19 15:46:17 -08002339 ' "packages": [',
2340 ])
John Budorick4099daa2018-06-21 19:22:10 +00002341 for p in sorted(
2342 self._cipd_root.packages(self._cipd_subdir),
Edward Lemur26a8b9f2019-08-15 20:46:44 +00002343 key=lambda x: x.name):
John Budorick0f7b2002018-01-19 15:46:17 -08002344 s.extend([
John Budorick64e33cb2018-02-20 09:40:30 -08002345 ' {',
John Budorickc35aba52018-06-28 20:57:03 +00002346 ' "package": "%s",' % escape_cipd_var(p.name),
John Budorick64e33cb2018-02-20 09:40:30 -08002347 ' "version": "%s",' % p.version,
2348 ' },',
John Budorick0f7b2002018-01-19 15:46:17 -08002349 ])
John Budorickd3ba72b2018-03-20 12:27:42 -07002350
John Budorick0f7b2002018-01-19 15:46:17 -08002351 s.extend([
2352 ' ],',
2353 ' "dep_type": "cipd",',
2354 ] + condition_part + [
2355 ' },',
2356 '',
2357 ])
2358 return s
2359
2360
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002361#### gclient commands.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002362
2363
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002364@subcommand.usage('[command] [args ...]')
Edward Lemur3298e7b2018-07-17 18:21:27 +00002365@metrics.collector.collect_metrics('gclient recurse')
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00002366def CMDrecurse(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002367 """Operates [command args ...] on all the dependencies.
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00002368
Arthur Milchior08cd5fe2022-07-28 20:38:47 +00002369 Change directory to each dependency's directory, and call [command
2370 args ...] there. Sets GCLIENT_DEP_PATH environment variable as the
2371 dep's relative location to root directory of the checkout.
2372
2373 Examples:
2374 * `gclient recurse --no-progress -j1 sh -c 'echo "$GCLIENT_DEP_PATH"'`
2375 print the relative path of each dependency.
2376 * `gclient recurse --no-progress -j1 sh -c "pwd"`
2377 print the absolute path of each dependency.
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00002378 """
2379 # Stop parsing at the first non-arg so that these go through to the command
2380 parser.disable_interspersed_args()
2381 parser.add_option('-s', '--scm', action='append', default=[],
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00002382 help='Choose scm types to operate upon.')
maruel@chromium.org288054d2012-03-05 00:43:07 +00002383 parser.add_option('-i', '--ignore', action='store_true',
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00002384 help='Ignore non-zero return codes from subcommands.')
2385 parser.add_option('--prepend-dir', action='store_true',
2386 help='Prepend relative dir for use with git <cmd> --null.')
2387 parser.add_option('--no-progress', action='store_true',
2388 help='Disable progress bar that shows sub-command updates')
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00002389 options, args = parser.parse_args(args)
maruel@chromium.org45e9f2d2010-10-18 13:33:46 +00002390 if not args:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002391 print('Need to supply a command!', file=sys.stderr)
maruel@chromium.org45e9f2d2010-10-18 13:33:46 +00002392 return 1
maruel@chromium.org78cba522010-10-18 13:32:05 +00002393 root_and_entries = gclient_utils.GetGClientRootAndEntries()
2394 if not root_and_entries:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002395 print(
maruel@chromium.org78cba522010-10-18 13:32:05 +00002396 'You need to run gclient sync at least once to use \'recurse\'.\n'
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002397 'This is because .gclient_entries needs to exist and be up to date.',
2398 file=sys.stderr)
maruel@chromium.org78cba522010-10-18 13:32:05 +00002399 return 1
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00002400
2401 # Normalize options.scm to a set()
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00002402 scm_set = set()
2403 for scm in options.scm:
2404 scm_set.update(scm.split(','))
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00002405 options.scm = scm_set
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00002406
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00002407 options.nohooks = True
2408 client = GClient.LoadCurrentConfig(options)
Marc-Antoine Ruele6e06412017-10-18 13:47:02 -04002409 if not client:
2410 raise gclient_utils.Error('client not configured; see \'gclient config\'')
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00002411 return client.RunOnDeps('recurse', args, ignore_requirements=True,
2412 progress=not options.no_progress)
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00002413
2414
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002415@subcommand.usage('[args ...]')
Edward Lemur3298e7b2018-07-17 18:21:27 +00002416@metrics.collector.collect_metrics('gclient fetch')
davidbarr@chromium.org12f944e2012-03-01 02:18:31 +00002417def CMDfetch(parser, args):
2418 """Fetches upstream commits for all modules.
2419
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002420 Completely git-specific. Simply runs 'git fetch [args ...]' for each module.
2421 """
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00002422 (options, args) = parser.parse_args(args)
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002423 return CMDrecurse(OptionParser(), [
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00002424 '--jobs=%d' % options.jobs, '--scm=git', 'git', 'fetch'] + args)
2425
2426
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002427class Flattener(object):
2428 """Flattens a gclient solution."""
2429
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02002430 def __init__(self, client, pin_all_deps=False):
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002431 """Constructor.
2432
2433 Arguments:
2434 client (GClient): client to flatten
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02002435 pin_all_deps (bool): whether to pin all deps, even if they're not pinned
2436 in DEPS
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002437 """
2438 self._client = client
2439
2440 self._deps_string = None
Joanna Wang9144b672023-02-24 23:36:17 +00002441 self._deps_graph_lines = None
Paweł Hajdan, Jrfeb01642017-09-12 15:50:46 +02002442 self._deps_files = set()
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002443
2444 self._allowed_hosts = set()
2445 self._deps = {}
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002446 self._hooks = []
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002447 self._pre_deps_hooks = []
Paweł Hajdan, Jrfb022012017-07-06 18:00:08 +02002448 self._vars = {}
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002449
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02002450 self._flatten(pin_all_deps=pin_all_deps)
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002451
2452 @property
2453 def deps_string(self):
2454 assert self._deps_string is not None
2455 return self._deps_string
2456
Paweł Hajdan, Jrfeb01642017-09-12 15:50:46 +02002457 @property
Joanna Wang9144b672023-02-24 23:36:17 +00002458 def deps_graph_lines(self):
2459 assert self._deps_graph_lines is not None
2460 return self._deps_graph_lines
2461
2462 @property
Paweł Hajdan, Jrfeb01642017-09-12 15:50:46 +02002463 def deps_files(self):
2464 return self._deps_files
2465
Paweł Hajdan, Jr5ec77132017-08-16 19:21:06 +02002466 def _pin_dep(self, dep):
2467 """Pins a dependency to specific full revision sha.
2468
2469 Arguments:
2470 dep (Dependency): dependency to process
2471 """
Michael Mossd683d7c2018-06-15 05:05:17 +00002472 if dep.url is None:
2473 return
2474
Paweł Hajdan, Jr5ec77132017-08-16 19:21:06 +02002475 # Make sure the revision is always fully specified (a hash),
2476 # as opposed to refs or tags which might change. Similarly,
2477 # shortened shas might become ambiguous; make sure to always
2478 # use full one for pinning.
Edward Lemure7273d22018-05-10 19:13:51 -04002479 revision = gclient_utils.SplitUrlRevision(dep.url)[1]
2480 if not revision or not gclient_utils.IsFullGitSha(revision):
2481 dep.PinToActualRevision()
Paweł Hajdan, Jr5ec77132017-08-16 19:21:06 +02002482
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02002483 def _flatten(self, pin_all_deps=False):
2484 """Runs the flattener. Saves resulting DEPS string.
2485
2486 Arguments:
2487 pin_all_deps (bool): whether to pin all deps, even if they're not pinned
2488 in DEPS
2489 """
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002490 for solution in self._client.dependencies:
Paweł Hajdan, Jr11eb7152017-08-10 12:50:11 +02002491 self._add_dep(solution)
Michael Mossd683d7c2018-06-15 05:05:17 +00002492 self._flatten_dep(solution)
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002493
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02002494 if pin_all_deps:
Marc-Antoine Ruel8e57b4b2019-10-11 01:01:36 +00002495 for dep in self._deps.values():
Paweł Hajdan, Jr5ec77132017-08-16 19:21:06 +02002496 self._pin_dep(dep)
Paweł Hajdan, Jr39300ba2017-08-11 16:52:38 +02002497
Paweł Hajdan, Jr76a9d042017-08-18 20:05:41 +02002498 def add_deps_file(dep):
Paweł Hajdan, Jr0870df22017-08-23 17:59:29 +02002499 # Only include DEPS files referenced by recursedeps.
Edward Lemurfbb06aa2018-06-11 20:43:06 +00002500 if not dep.should_recurse:
Paweł Hajdan, Jr0870df22017-08-23 17:59:29 +02002501 return
Paweł Hajdan, Jrfeb01642017-09-12 15:50:46 +02002502 deps_file = dep.deps_file
2503 deps_path = os.path.join(self._client.root_dir, dep.name, deps_file)
Paweł Hajdan, Jr76a9d042017-08-18 20:05:41 +02002504 if not os.path.exists(deps_path):
Paweł Hajdan, Jrfeb01642017-09-12 15:50:46 +02002505 # gclient has a fallback that if deps_file doesn't exist, it'll try
2506 # DEPS. Do the same here.
2507 deps_file = 'DEPS'
2508 deps_path = os.path.join(self._client.root_dir, dep.name, deps_file)
2509 if not os.path.exists(deps_path):
2510 return
Michael Mossd683d7c2018-06-15 05:05:17 +00002511 assert dep.url
Edward Lemure7273d22018-05-10 19:13:51 -04002512 self._deps_files.add((dep.url, deps_file, dep.hierarchy_data()))
Marc-Antoine Ruel8e57b4b2019-10-11 01:01:36 +00002513 for dep in self._deps.values():
Paweł Hajdan, Jr76a9d042017-08-18 20:05:41 +02002514 add_deps_file(dep)
Paweł Hajdan, Jr76a9d042017-08-18 20:05:41 +02002515
Michael Moss848c86e2018-05-03 16:05:50 -07002516 gn_args_dep = self._deps.get(self._client.dependencies[0]._gn_args_from,
2517 self._client.dependencies[0])
Joanna Wang9144b672023-02-24 23:36:17 +00002518
2519 self._deps_graph_lines = _DepsToDotGraphLines(self._deps)
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002520 self._deps_string = '\n'.join(
Michael Moss848c86e2018-05-03 16:05:50 -07002521 _GNSettingsToLines(gn_args_dep._gn_args_file, gn_args_dep._gn_args) +
Joanna Wang9144b672023-02-24 23:36:17 +00002522 _AllowedHostsToLines(self._allowed_hosts) + _DepsToLines(self._deps) +
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002523 _HooksToLines('hooks', self._hooks) +
2524 _HooksToLines('pre_deps_hooks', self._pre_deps_hooks) +
Joanna Wang9144b672023-02-24 23:36:17 +00002525 _VarsToLines(self._vars) + [
2526 '# %s, %s' % (url, deps_file)
2527 for url, deps_file, _ in sorted(self._deps_files)
2528 ] + ['']) # Ensure newline at end of file.
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002529
Paweł Hajdan, Jr11eb7152017-08-10 12:50:11 +02002530 def _add_dep(self, dep):
2531 """Helper to add a dependency to flattened DEPS.
2532
2533 Arguments:
2534 dep (Dependency): dependency to add
2535 """
2536 assert dep.name not in self._deps or self._deps.get(dep.name) == dep, (
2537 dep.name, self._deps.get(dep.name))
Michael Mossd683d7c2018-06-15 05:05:17 +00002538 if dep.url:
2539 self._deps[dep.name] = dep
Paweł Hajdan, Jr11eb7152017-08-10 12:50:11 +02002540
Edward Lemur16f4bad2018-05-16 16:53:49 -04002541 def _flatten_dep(self, dep):
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002542 """Visits a dependency in order to flatten it (see CMDflatten).
2543
2544 Arguments:
2545 dep (Dependency): dependency to process
2546 """
Edward Lemur16f4bad2018-05-16 16:53:49 -04002547 logging.debug('_flatten_dep(%s)', dep.name)
Paweł Hajdan, Jradae2a62017-08-18 16:49:57 +02002548
Edward Lemur16f4bad2018-05-16 16:53:49 -04002549 assert dep.deps_parsed, (
2550 "Attempted to flatten %s but it has not been processed." % dep.name)
Paweł Hajdan, Jrc69b32e2017-08-17 18:47:48 +02002551
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002552 self._allowed_hosts.update(dep.allowed_hosts)
2553
Michael Mossce9f17f2018-01-31 13:16:35 -08002554 # Only include vars explicitly listed in the DEPS files or gclient solution,
2555 # not automatic, local overrides (i.e. not all of dep.get_vars()).
Michael Moss4e9b50a2018-05-23 22:35:06 -07002556 hierarchy = dep.hierarchy(include_url=False)
Marc-Antoine Ruel8e57b4b2019-10-11 01:01:36 +00002557 for key, value in dep._vars.items():
Paweł Hajdan, Jrc9353602017-08-02 17:52:08 +02002558 # Make sure there are no conflicting variables. It is fine however
2559 # to use same variable name, as long as the value is consistent.
Takuto Ikuta575872e2019-02-21 15:20:07 +00002560 assert key not in self._vars or self._vars[key][1] == value, (
2561 "dep:%s key:%s value:%s != %s" % (
2562 dep.name, key, value, self._vars[key][1]))
Michael Mossce9f17f2018-01-31 13:16:35 -08002563 self._vars[key] = (hierarchy, value)
2564 # Override explicit custom variables.
Marc-Antoine Ruel8e57b4b2019-10-11 01:01:36 +00002565 for key, value in dep.custom_vars.items():
Michael Mossce9f17f2018-01-31 13:16:35 -08002566 # Do custom_vars that don't correspond to DEPS vars ever make sense? DEPS
2567 # conditionals shouldn't be using vars that aren't also defined in the
2568 # DEPS (presubmit actually disallows this), so any new custom_var must be
2569 # unused in the DEPS, so no need to add it to the flattened output either.
2570 if key not in self._vars:
2571 continue
2572 # Don't "override" existing vars if it's actually the same value.
Aravind Vasudevanc5f0cbb2022-01-24 23:56:57 +00002573 if self._vars[key][1] == value:
Michael Mossce9f17f2018-01-31 13:16:35 -08002574 continue
2575 # Anything else is overriding a default value from the DEPS.
2576 self._vars[key] = (hierarchy + ' [custom_var override]', value)
Paweł Hajdan, Jrfb022012017-07-06 18:00:08 +02002577
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002578 self._pre_deps_hooks.extend([(dep, hook) for hook in dep.pre_deps_hooks])
Edward Lemur16f4bad2018-05-16 16:53:49 -04002579 self._hooks.extend([(dep, hook) for hook in dep.deps_hooks])
Paweł Hajdan, Jradae2a62017-08-18 16:49:57 +02002580
Paweł Hajdan, Jrb0ad16e2017-08-03 15:33:21 +02002581 for sub_dep in dep.dependencies:
Edward Lemur16f4bad2018-05-16 16:53:49 -04002582 self._add_dep(sub_dep)
Paweł Hajdan, Jrb0ad16e2017-08-03 15:33:21 +02002583
Edward Lemurfbb06aa2018-06-11 20:43:06 +00002584 for d in dep.dependencies:
2585 if d.should_recurse:
2586 self._flatten_dep(d)
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002587
2588
Edward Lemur3298e7b2018-07-17 18:21:27 +00002589@metrics.collector.collect_metrics('gclient flatten')
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002590def CMDflatten(parser, args):
2591 """Flattens the solutions into a single DEPS file."""
2592 parser.add_option('--output-deps', help='Path to the output DEPS file')
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02002593 parser.add_option(
Paweł Hajdan, Jrfeb01642017-09-12 15:50:46 +02002594 '--output-deps-files',
2595 help=('Path to the output metadata about DEPS files referenced by '
2596 'recursedeps.'))
2597 parser.add_option(
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02002598 '--pin-all-deps', action='store_true',
2599 help=('Pin all deps, even if not pinned in DEPS. CAVEAT: only does so '
2600 'for checked out deps, NOT deps_os.'))
Joanna Wang9144b672023-02-24 23:36:17 +00002601 parser.add_option('--deps-graph-file',
2602 help='Provide a path for the output graph file')
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002603 options, args = parser.parse_args(args)
2604
2605 options.nohooks = True
Paweł Hajdan, Jr5ec77132017-08-16 19:21:06 +02002606 options.process_all_deps = True
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002607 client = GClient.LoadCurrentConfig(options)
Gavin Makf6b414c2021-01-12 19:10:41 +00002608 if not client:
2609 raise gclient_utils.Error('client not configured; see \'gclient config\'')
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002610
2611 # Only print progress if we're writing to a file. Otherwise, progress updates
2612 # could obscure intended output.
2613 code = client.RunOnDeps('flatten', args, progress=options.output_deps)
2614 if code != 0:
2615 return code
2616
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02002617 flattener = Flattener(client, pin_all_deps=options.pin_all_deps)
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002618
2619 if options.output_deps:
2620 with open(options.output_deps, 'w') as f:
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002621 f.write(flattener.deps_string)
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002622 else:
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002623 print(flattener.deps_string)
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002624
Joanna Wang9144b672023-02-24 23:36:17 +00002625 if options.deps_graph_file:
2626 with open(options.deps_graph_file, 'w') as f:
2627 f.write('\n'.join(flattener.deps_graph_lines))
2628
Michael Mossfe68c912018-03-22 19:19:35 -07002629 deps_files = [{'url': d[0], 'deps_file': d[1], 'hierarchy': d[2]}
Paweł Hajdan, Jrfeb01642017-09-12 15:50:46 +02002630 for d in sorted(flattener.deps_files)]
2631 if options.output_deps_files:
2632 with open(options.output_deps_files, 'w') as f:
2633 json.dump(deps_files, f)
2634
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002635 return 0
2636
2637
Paweł Hajdan, Jr3c2aa832017-06-07 20:22:16 +02002638def _GNSettingsToLines(gn_args_file, gn_args):
2639 s = []
2640 if gn_args_file:
2641 s.extend([
2642 'gclient_gn_args_file = "%s"' % gn_args_file,
2643 'gclient_gn_args = %r' % gn_args,
2644 ])
2645 return s
2646
2647
Paweł Hajdan, Jr6014b562017-06-30 17:43:42 +02002648def _AllowedHostsToLines(allowed_hosts):
2649 """Converts |allowed_hosts| set to list of lines for output."""
2650 if not allowed_hosts:
2651 return []
2652 s = ['allowed_hosts = [']
2653 for h in sorted(allowed_hosts):
2654 s.append(' "%s",' % h)
2655 s.extend([']', ''])
2656 return s
2657
2658
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002659def _DepsToLines(deps):
Joanna Wang9144b672023-02-24 23:36:17 +00002660 # type: (Mapping[str, Dependency]) -> Sequence[str]
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002661 """Converts |deps| dict to list of lines for output."""
Paweł Hajdan, Jr5b593352017-06-29 18:37:45 +02002662 if not deps:
2663 return []
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002664 s = ['deps = {']
Edward Lemuree7b9dd2019-07-20 01:29:08 +00002665 for _, dep in sorted(deps.items()):
John Budorick0f7b2002018-01-19 15:46:17 -08002666 s.extend(dep.ToLines())
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002667 s.extend(['}', ''])
2668 return s
2669
2670
Joanna Wang9144b672023-02-24 23:36:17 +00002671def _DepsToDotGraphLines(deps):
2672 # type: (Mapping[str, Dependency]) -> Sequence[str]
2673 """Converts |deps| dict to list of lines for dot graphs"""
2674 if not deps:
2675 return []
2676 graph_lines = ["digraph {\n\trankdir=\"LR\";"]
2677 for _, dep in sorted(deps.items()):
2678 line = dep.hierarchy(include_url=False, graphviz=True)
2679 if line:
2680 graph_lines.append("\t%s" % line)
2681 graph_lines.append("}")
2682 return graph_lines
2683
2684
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02002685def _DepsOsToLines(deps_os):
2686 """Converts |deps_os| dict to list of lines for output."""
Paweł Hajdan, Jr5b593352017-06-29 18:37:45 +02002687 if not deps_os:
2688 return []
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02002689 s = ['deps_os = {']
Edward Lemuree7b9dd2019-07-20 01:29:08 +00002690 for dep_os, os_deps in sorted(deps_os.items()):
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02002691 s.append(' "%s": {' % dep_os)
Edward Lemuree7b9dd2019-07-20 01:29:08 +00002692 for name, dep in sorted(os_deps.items()):
Paweł Hajdan, Jr78ce24e2017-10-03 17:09:13 +02002693 condition_part = ([' "condition": %r,' % dep.condition]
Paweł Hajdan, Jrad30de62017-06-26 18:51:58 +02002694 if dep.condition else [])
2695 s.extend([
Michael Moss4e9b50a2018-05-23 22:35:06 -07002696 ' # %s' % dep.hierarchy(include_url=False),
Paweł Hajdan, Jrad30de62017-06-26 18:51:58 +02002697 ' "%s": {' % (name,),
Edward Lemure05f18d2018-06-08 17:36:53 +00002698 ' "url": "%s",' % (dep.url,),
Paweł Hajdan, Jrad30de62017-06-26 18:51:58 +02002699 ] + condition_part + [
2700 ' },',
2701 '',
2702 ])
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02002703 s.extend([' },', ''])
2704 s.extend(['}', ''])
2705 return s
2706
2707
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002708def _HooksToLines(name, hooks):
2709 """Converts |hooks| list to list of lines for output."""
Paweł Hajdan, Jr5b593352017-06-29 18:37:45 +02002710 if not hooks:
2711 return []
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002712 s = ['%s = [' % name]
2713 for dep, hook in hooks:
2714 s.extend([
Michael Moss4e9b50a2018-05-23 22:35:06 -07002715 ' # %s' % dep.hierarchy(include_url=False),
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002716 ' {',
2717 ])
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +02002718 if hook.name is not None:
2719 s.append(' "name": "%s",' % hook.name)
2720 if hook.pattern is not None:
2721 s.append(' "pattern": "%s",' % hook.pattern)
Paweł Hajdan, Jrecf53fe2017-09-29 18:28:49 +02002722 if hook.condition is not None:
Paweł Hajdan, Jr78ce24e2017-10-03 17:09:13 +02002723 s.append(' "condition": %r,' % hook.condition)
Corentin Walleza68660d2018-09-10 17:33:24 +00002724 # Flattened hooks need to be written relative to the root gclient dir
2725 cwd = os.path.relpath(os.path.normpath(hook.effective_cwd))
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002726 s.extend(
Corentin Walleza68660d2018-09-10 17:33:24 +00002727 [' "cwd": "%s",' % cwd] +
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002728 [' "action": ['] +
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +02002729 [' "%s",' % arg for arg in hook.action] +
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002730 [' ]', ' },', '']
2731 )
2732 s.extend([']', ''])
2733 return s
2734
2735
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +02002736def _HooksOsToLines(hooks_os):
2737 """Converts |hooks| list to list of lines for output."""
Paweł Hajdan, Jr5b593352017-06-29 18:37:45 +02002738 if not hooks_os:
2739 return []
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +02002740 s = ['hooks_os = {']
Marc-Antoine Ruel8e57b4b2019-10-11 01:01:36 +00002741 for hook_os, os_hooks in hooks_os.items():
Michael Moss017bcf62017-06-28 15:26:38 -07002742 s.append(' "%s": [' % hook_os)
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +02002743 for dep, hook in os_hooks:
2744 s.extend([
Michael Moss4e9b50a2018-05-23 22:35:06 -07002745 ' # %s' % dep.hierarchy(include_url=False),
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +02002746 ' {',
2747 ])
2748 if hook.name is not None:
2749 s.append(' "name": "%s",' % hook.name)
2750 if hook.pattern is not None:
2751 s.append(' "pattern": "%s",' % hook.pattern)
Paweł Hajdan, Jrecf53fe2017-09-29 18:28:49 +02002752 if hook.condition is not None:
Paweł Hajdan, Jr78ce24e2017-10-03 17:09:13 +02002753 s.append(' "condition": %r,' % hook.condition)
Corentin Walleza68660d2018-09-10 17:33:24 +00002754 # Flattened hooks need to be written relative to the root gclient dir
2755 cwd = os.path.relpath(os.path.normpath(hook.effective_cwd))
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +02002756 s.extend(
Corentin Walleza68660d2018-09-10 17:33:24 +00002757 [' "cwd": "%s",' % cwd] +
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +02002758 [' "action": ['] +
2759 [' "%s",' % arg for arg in hook.action] +
2760 [' ]', ' },', '']
2761 )
Michael Moss017bcf62017-06-28 15:26:38 -07002762 s.extend([' ],', ''])
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +02002763 s.extend(['}', ''])
2764 return s
2765
2766
Paweł Hajdan, Jrfb022012017-07-06 18:00:08 +02002767def _VarsToLines(variables):
2768 """Converts |variables| dict to list of lines for output."""
2769 if not variables:
2770 return []
2771 s = ['vars = {']
Edward Lemuree7b9dd2019-07-20 01:29:08 +00002772 for key, tup in sorted(variables.items()):
Michael Mossce9f17f2018-01-31 13:16:35 -08002773 hierarchy, value = tup
Paweł Hajdan, Jrfb022012017-07-06 18:00:08 +02002774 s.extend([
Michael Mossce9f17f2018-01-31 13:16:35 -08002775 ' # %s' % hierarchy,
Paweł Hajdan, Jrfb022012017-07-06 18:00:08 +02002776 ' "%s": %r,' % (key, value),
2777 '',
2778 ])
2779 s.extend(['}', ''])
2780 return s
2781
2782
Edward Lemur3298e7b2018-07-17 18:21:27 +00002783@metrics.collector.collect_metrics('gclient grep')
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00002784def CMDgrep(parser, args):
2785 """Greps through git repos managed by gclient.
2786
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002787 Runs 'git grep [args...]' for each module.
2788 """
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00002789 # We can't use optparse because it will try to parse arguments sent
2790 # to git grep and throw an error. :-(
2791 if not args or re.match('(-h|--help)$', args[0]):
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002792 print(
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00002793 'Usage: gclient grep [-j <N>] git-grep-args...\n\n'
2794 'Example: "gclient grep -j10 -A2 RefCountedBase" runs\n"git grep '
2795 '-A2 RefCountedBase" on each of gclient\'s git\nrepos with up to '
2796 '10 jobs.\n\nBonus: page output by appending "|& less -FRSX" to the'
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002797 ' end of your query.',
2798 file=sys.stderr)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00002799 return 1
2800
2801 jobs_arg = ['--jobs=1']
2802 if re.match(r'(-j|--jobs=)\d+$', args[0]):
2803 jobs_arg, args = args[:1], args[1:]
2804 elif re.match(r'(-j|--jobs)$', args[0]):
2805 jobs_arg, args = args[:2], args[2:]
2806
2807 return CMDrecurse(
2808 parser,
2809 jobs_arg + ['--ignore', '--prepend-dir', '--no-progress', '--scm=git',
2810 'git', 'grep', '--null', '--color=Always'] + args)
davidbarr@chromium.org12f944e2012-03-01 02:18:31 +00002811
2812
Edward Lemur3298e7b2018-07-17 18:21:27 +00002813@metrics.collector.collect_metrics('gclient root')
stip@chromium.orga735da22015-04-29 23:18:20 +00002814def CMDroot(parser, args):
2815 """Outputs the solution root (or current dir if there isn't one)."""
2816 (options, args) = parser.parse_args(args)
2817 client = GClient.LoadCurrentConfig(options)
2818 if client:
2819 print(os.path.abspath(client.root_dir))
2820 else:
2821 print(os.path.abspath('.'))
2822
2823
agablea98a6cd2016-11-15 14:30:10 -08002824@subcommand.usage('[url]')
Edward Lemur3298e7b2018-07-17 18:21:27 +00002825@metrics.collector.collect_metrics('gclient config')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002826def CMDconfig(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002827 """Creates a .gclient file in the current directory.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00002828
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002829 This specifies the configuration for further commands. After update/sync,
2830 top-level DEPS files in each module are read to determine dependent
2831 modules to operate on as well. If optional [url] parameter is
2832 provided, then configuration is read from a specified Subversion server
2833 URL.
2834 """
szager@chromium.orge2e03202012-07-31 18:05:16 +00002835 # We do a little dance with the --gclientfile option. 'gclient config' is the
2836 # only command where it's acceptable to have both '--gclientfile' and '--spec'
2837 # arguments. So, we temporarily stash any --gclientfile parameter into
2838 # options.output_config_file until after the (gclientfile xor spec) error
2839 # check.
2840 parser.remove_option('--gclientfile')
2841 parser.add_option('--gclientfile', dest='output_config_file',
2842 help='Specify an alternate .gclient file')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002843 parser.add_option('--name',
2844 help='overrides the default name for the solution')
nsylvain@google.comefc80932011-05-31 21:27:56 +00002845 parser.add_option('--deps-file', default='DEPS',
David Benjamin105e11e2017-10-16 10:39:35 -04002846 help='overrides the default name for the DEPS file for the '
nsylvain@google.comefc80932011-05-31 21:27:56 +00002847 'main solutions and all sub-dependencies')
smutae7ea312016-07-18 11:59:41 -07002848 parser.add_option('--unmanaged', action='store_true', default=False,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00002849 help='overrides the default behavior to make it possible '
smutae7ea312016-07-18 11:59:41 -07002850 'to have the main solution untouched by gclient '
2851 '(gclient will check out unmanaged dependencies but '
2852 'will never sync them)')
Robert Iannuccia19649b2018-06-29 16:31:45 +00002853 parser.add_option('--cache-dir', default=UNSET_CACHE_DIR,
2854 help='Cache all git repos into this dir and do shared '
2855 'clones from the cache, instead of cloning directly '
2856 'from the remote. Pass "None" to disable cache, even '
2857 'if globally enabled due to $GIT_CACHE_PATH.')
Paweł Hajdan, Jr3ba2a7c2017-10-04 19:24:46 +02002858 parser.add_option('--custom-var', action='append', dest='custom_vars',
2859 default=[],
2860 help='overrides variables; key=value syntax')
szager@chromium.orge2e03202012-07-31 18:05:16 +00002861 parser.set_defaults(config_filename=None)
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002862 (options, args) = parser.parse_args(args)
szager@chromium.orge2e03202012-07-31 18:05:16 +00002863 if options.output_config_file:
2864 setattr(options, 'config_filename', getattr(options, 'output_config_file'))
maruel@chromium.org5fc2a332010-05-26 19:37:15 +00002865 if ((options.spec and args) or len(args) > 2 or
2866 (not options.spec and not args)):
2867 parser.error('Inconsistent arguments. Use either --spec or one or 2 args')
2868
Robert Iannuccia19649b2018-06-29 16:31:45 +00002869 if (options.cache_dir is not UNSET_CACHE_DIR
2870 and options.cache_dir.lower() == 'none'):
2871 options.cache_dir = None
2872
Paweł Hajdan, Jr3ba2a7c2017-10-04 19:24:46 +02002873 custom_vars = {}
2874 for arg in options.custom_vars:
2875 kv = arg.split('=', 1)
2876 if len(kv) != 2:
2877 parser.error('Invalid --custom-var argument: %r' % arg)
2878 custom_vars[kv[0]] = gclient_eval.EvaluateCondition(kv[1], {})
2879
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002880 client = GClient('.', options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002881 if options.spec:
2882 client.SetConfig(options.spec)
2883 else:
maruel@chromium.org1ab7ffc2009-06-03 17:21:37 +00002884 base_url = args[0].rstrip('/')
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00002885 if not options.name:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002886 name = base_url.split('/')[-1]
nsylvain@google.com12649ef2011-06-01 17:11:20 +00002887 if name.endswith('.git'):
2888 name = name[:-4]
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00002889 else:
2890 # specify an alternate relpath for the given URL.
2891 name = options.name
agable@chromium.orgf2214672015-10-27 21:02:48 +00002892 if not os.path.abspath(os.path.join(os.getcwd(), name)).startswith(
2893 os.getcwd()):
2894 parser.error('Do not pass a relative path for --name.')
2895 if any(x in ('..', '.', '/', '\\') for x in name.split(os.sep)):
2896 parser.error('Do not include relative path components in --name.')
2897
nsylvain@google.comefc80932011-05-31 21:27:56 +00002898 deps_file = options.deps_file
agablea98a6cd2016-11-15 14:30:10 -08002899 client.SetDefaultConfig(name, deps_file, base_url,
smutae7ea312016-07-18 11:59:41 -07002900 managed=not options.unmanaged,
Paweł Hajdan, Jr3ba2a7c2017-10-04 19:24:46 +02002901 cache_dir=options.cache_dir,
2902 custom_vars=custom_vars)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002903 client.SaveConfig()
maruel@chromium.org79692d62010-05-14 18:57:13 +00002904 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002905
2906
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002907@subcommand.epilog("""Example:
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002908 gclient pack > patch.txt
2909 generate simple patch for configured client and dependences
2910""")
Edward Lemur3298e7b2018-07-17 18:21:27 +00002911@metrics.collector.collect_metrics('gclient pack')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002912def CMDpack(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002913 """Generates a patch which can be applied at the root of the tree.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00002914
agabled437d762016-10-17 09:35:11 -07002915 Internally, runs 'git diff' on each checked out module and
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002916 dependencies, and performs minimal postprocessing of the output. The
2917 resulting patch is printed to stdout and can be applied to a freshly
2918 checked out tree via 'patch -p0 < patchfile'.
2919 """
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002920 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2921 help='override deps for the specified (comma-separated) '
2922 'platform(s); \'all\' will process all deps_os '
2923 'references')
haitao.feng@intel.com306080c2012-05-04 13:11:29 +00002924 parser.remove_option('--jobs')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002925 (options, args) = parser.parse_args(args)
iannucci@chromium.org50395ea2013-04-04 04:47:42 +00002926 # Force jobs to 1 so the stdout is not annotated with the thread ids
haitao.feng@intel.com306080c2012-05-04 13:11:29 +00002927 options.jobs = 1
kbr@google.comab318592009-09-04 00:54:55 +00002928 client = GClient.LoadCurrentConfig(options)
2929 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002930 raise gclient_utils.Error('client not configured; see \'gclient config\'')
kbr@google.comab318592009-09-04 00:54:55 +00002931 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002932 client.PrintLocationAndContents()
kbr@google.comab318592009-09-04 00:54:55 +00002933 return client.RunOnDeps('pack', args)
2934
2935
Edward Lemur3298e7b2018-07-17 18:21:27 +00002936@metrics.collector.collect_metrics('gclient status')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002937def CMDstatus(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002938 """Shows modification status for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002939 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2940 help='override deps for the specified (comma-separated) '
2941 'platform(s); \'all\' will process all deps_os '
2942 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002943 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002944 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002945 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002946 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002947 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002948 client.PrintLocationAndContents()
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002949 return client.RunOnDeps('status', args)
2950
2951
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002952@subcommand.epilog("""Examples:
maruel@chromium.org79692d62010-05-14 18:57:13 +00002953 gclient sync
2954 update files from SCM according to current configuration,
2955 *for modules which have changed since last update or sync*
2956 gclient sync --force
2957 update files from SCM according to current configuration, for
2958 all modules (useful for recovering files deleted from local copy)
Edward Lesmes3ffca4b2021-05-19 19:36:17 +00002959 gclient sync --revision src@GIT_COMMIT_OR_REF
2960 update src directory to GIT_COMMIT_OR_REF
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002961
2962JSON output format:
2963If the --output-json option is specified, the following document structure will
2964be emitted to the provided file. 'null' entries may occur for subprojects which
2965are present in the gclient solution, but were not processed (due to custom_deps,
2966os_deps, etc.)
2967
2968{
2969 "solutions" : {
2970 "<name>": { # <name> is the posix-normalized path to the solution.
agabled437d762016-10-17 09:35:11 -07002971 "revision": [<git id hex string>|null],
2972 "scm": ["git"|null],
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002973 }
2974 }
2975}
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002976""")
Edward Lemur3298e7b2018-07-17 18:21:27 +00002977@metrics.collector.collect_metrics('gclient sync')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002978def CMDsync(parser, args):
2979 """Checkout/update all modules."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002980 parser.add_option('-f', '--force', action='store_true',
2981 help='force update even for unchanged modules')
2982 parser.add_option('-n', '--nohooks', action='store_true',
2983 help='don\'t run hooks after the update is complete')
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00002984 parser.add_option('-p', '--noprehooks', action='store_true',
2985 help='don\'t run pre-DEPS hooks', default=False)
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002986 parser.add_option('-r', '--revision', action='append',
2987 dest='revisions', metavar='REV', default=[],
Edward Lesmes3ffca4b2021-05-19 19:36:17 +00002988 help='Enforces git ref/hash for the solutions with the '
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002989 'format src@rev. The src@ part is optional and can be '
Edward Lesmes53014652018-03-07 18:01:40 -05002990 'skipped. You can also specify URLs instead of paths '
2991 'and gclient will find the solution corresponding to '
2992 'the given URL. If a path is also specified, the URL '
2993 'takes precedence. -r can be used multiple times when '
2994 '.gclient has multiple solutions configured, and will '
Edward Lesmes3ffca4b2021-05-19 19:36:17 +00002995 'work even if the src@ part is skipped. Revision '
2996 'numbers (e.g. 31000 or r31000) are not supported.')
Edward Lesmesc621b212018-03-21 20:26:56 -04002997 parser.add_option('--patch-ref', action='append',
2998 dest='patch_refs', metavar='GERRIT_REF', default=[],
Edward Lemur6a4e31b2018-08-10 19:59:02 +00002999 help='Patches the given reference with the format '
Edward Lemur4c5c8ab2019-06-07 15:58:13 +00003000 'dep@target-ref:patch-ref. '
Edward Lemur6a4e31b2018-08-10 19:59:02 +00003001 'For |dep|, you can specify URLs as well as paths, '
3002 'with URLs taking preference. '
3003 '|patch-ref| will be applied to |dep|, rebased on top '
3004 'of what |dep| was synced to, and a soft reset will '
3005 'be done. Use --no-rebase-patch-ref and '
3006 '--no-reset-patch-ref to disable this behavior. '
3007 '|target-ref| is the target branch against which a '
3008 'patch was created, it is used to determine which '
3009 'commits from the |patch-ref| actually constitute a '
Edward Lemur4c5c8ab2019-06-07 15:58:13 +00003010 'patch.')
Ravi Mistryecda7822022-02-28 16:22:20 +00003011 parser.add_option('-t', '--download-topics', action='store_true',
3012 help='Downloads and patches locally changes from all open '
3013 'Gerrit CLs that have the same topic as the changes '
3014 'in the specified patch_refs. Only works if atleast '
3015 'one --patch-ref is specified.')
maruel@chromium.org794207e2013-03-08 15:29:43 +00003016 parser.add_option('--with_branch_heads', action='store_true',
3017 help='Clone git "branch_heads" refspecs in addition to '
3018 'the default refspecs. This adds about 1/2GB to a '
3019 'full checkout. (git only)')
szager@chromium.org8d3348f2014-08-19 22:49:16 +00003020 parser.add_option('--with_tags', action='store_true',
3021 help='Clone git tags in addition to the default refspecs.')
agable2697cd12016-06-28 10:23:53 -07003022 parser.add_option('-H', '--head', action='store_true',
agablea98a6cd2016-11-15 14:30:10 -08003023 help='DEPRECATED: only made sense with safesync urls.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00003024 parser.add_option('-D', '--delete_unversioned_trees', action='store_true',
steveblock@chromium.org98e69452012-02-16 16:36:43 +00003025 help='Deletes from the working copy any dependencies that '
3026 'have been removed since the last sync, as long as '
3027 'there are no local modifications. When used with '
3028 '--force, such dependencies are removed even if they '
3029 'have local modifications. When used with --reset, '
3030 'all untracked directories are removed from the '
tnagel@chromium.orga2aaa632014-02-28 21:47:27 +00003031 'working copy, excluding those which are explicitly '
steveblock@chromium.org98e69452012-02-16 16:36:43 +00003032 'ignored in the repository.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00003033 parser.add_option('-R', '--reset', action='store_true',
3034 help='resets any local changes before updating (git only)')
bauerb@chromium.org2aad1b22011-07-22 12:00:41 +00003035 parser.add_option('-M', '--merge', action='store_true',
3036 help='merge upstream changes instead of trying to '
3037 'fast-forward or rebase')
dnj@chromium.org5b23e872015-02-20 21:25:57 +00003038 parser.add_option('-A', '--auto_rebase', action='store_true',
3039 help='Automatically rebase repositories against local '
3040 'checkout during update (git only).')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00003041 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
3042 help='override deps for the specified (comma-separated) '
3043 'platform(s); \'all\' will process all deps_os '
3044 'references')
Paweł Hajdan, Jr5ec77132017-08-16 19:21:06 +02003045 parser.add_option('--process-all-deps', action='store_true',
3046 help='Check out all deps, even for different OS-es, '
3047 'or with conditions evaluating to false')
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +00003048 parser.add_option('--upstream', action='store_true',
3049 help='Make repo state match upstream branch.')
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00003050 parser.add_option('--output-json',
3051 help='Output a json document to this path containing '
3052 'summary information about the sync.')
primiano@chromium.org5439ea52014-08-06 17:18:18 +00003053 parser.add_option('--no-history', action='store_true',
3054 help='GIT ONLY - Reduces the size/time of the checkout at '
3055 'the cost of no history. Requires Git 1.9+')
hinoka@chromium.org46b87412014-05-15 00:42:05 +00003056 parser.add_option('--shallow', action='store_true',
3057 help='GIT ONLY - Do a shallow clone into the cache dir. '
3058 'Requires Git 1.9+')
e.hakkinen@samsung.come8bc1aa2015-04-08 08:00:37 +00003059 parser.add_option('--no_bootstrap', '--no-bootstrap',
3060 action='store_true',
3061 help='Don\'t bootstrap from Google Storage.')
Josip Sokcevic14a83ae2020-05-21 01:36:34 +00003062 parser.add_option('--ignore_locks',
3063 action='store_true',
3064 help='No longer used.')
3065 parser.add_option('--break_repo_locks',
3066 action='store_true',
3067 help='No longer used.')
Vadim Shtayura08049e22017-10-11 00:14:52 +00003068 parser.add_option('--lock_timeout', type='int', default=5000,
3069 help='GIT ONLY - Deadline (in seconds) to wait for git '
3070 'cache lock to become available. Default is %default.')
Edward Lesmesc621b212018-03-21 20:26:56 -04003071 parser.add_option('--no-rebase-patch-ref', action='store_false',
3072 dest='rebase_patch_ref', default=True,
3073 help='Bypass rebase of the patch ref after checkout.')
3074 parser.add_option('--no-reset-patch-ref', action='store_false',
3075 dest='reset_patch_ref', default=True,
3076 help='Bypass calling reset after patching the ref.')
Joanna Wanga84a16b2022-07-27 18:52:17 +00003077 parser.add_option('--experiment',
3078 action='append',
3079 dest='experiments',
3080 default=[],
3081 help='Which experiments should be enabled.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00003082 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00003083 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00003084
3085 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00003086 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00003087
Ravi Mistryecda7822022-02-28 16:22:20 +00003088 if options.download_topics and not options.rebase_patch_ref:
3089 raise gclient_utils.Error(
3090 'Warning: You cannot download topics and not rebase each patch ref')
3091
Josip Sokcevic14a83ae2020-05-21 01:36:34 +00003092 if options.ignore_locks:
3093 print('Warning: ignore_locks is no longer used. Please remove its usage.')
3094
3095 if options.break_repo_locks:
3096 print('Warning: break_repo_locks is no longer used. Please remove its '
3097 'usage.')
3098
smutae7ea312016-07-18 11:59:41 -07003099 if options.revisions and options.head:
3100 # TODO(maruel): Make it a parser.error if it doesn't break any builder.
3101 print('Warning: you cannot use both --head and --revision')
3102
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00003103 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00003104 client.PrintLocationAndContents()
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00003105 ret = client.RunOnDeps('update', args)
3106 if options.output_json:
3107 slns = {}
Michael Mossd683d7c2018-06-15 05:05:17 +00003108 for d in client.subtree(True):
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00003109 normed = d.name.replace('\\', '/').rstrip('/') + '/'
3110 slns[normed] = {
3111 'revision': d.got_revision,
3112 'scm': d.used_scm.name if d.used_scm else None,
Michael Mossd683d7c2018-06-15 05:05:17 +00003113 'url': str(d.url) if d.url else None,
Edward Lemur7ccf2f02018-06-26 20:41:56 +00003114 'was_processed': d.should_process,
Joanna Wanga84a16b2022-07-27 18:52:17 +00003115 'was_synced': d._should_sync,
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00003116 }
Edward Lemurca879322019-09-09 20:18:13 +00003117 with open(options.output_json, 'w') as f:
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00003118 json.dump({'solutions': slns}, f)
3119 return ret
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00003120
3121
maruel@chromium.org39c0b222013-08-17 16:57:01 +00003122CMDupdate = CMDsync
3123
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00003124
Edward Lemur3298e7b2018-07-17 18:21:27 +00003125@metrics.collector.collect_metrics('gclient validate')
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +02003126def CMDvalidate(parser, args):
3127 """Validates the .gclient and DEPS syntax."""
3128 options, args = parser.parse_args(args)
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +02003129 client = GClient.LoadCurrentConfig(options)
Gavin Makf6b414c2021-01-12 19:10:41 +00003130 if not client:
3131 raise gclient_utils.Error('client not configured; see \'gclient config\'')
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +02003132 rv = client.RunOnDeps('validate', args)
3133 if rv == 0:
3134 print('validate: SUCCESS')
3135 else:
3136 print('validate: FAILURE')
3137 return rv
3138
3139
Edward Lemur3298e7b2018-07-17 18:21:27 +00003140@metrics.collector.collect_metrics('gclient diff')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00003141def CMDdiff(parser, args):
3142 """Displays local diff for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00003143 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
3144 help='override deps for the specified (comma-separated) '
3145 'platform(s); \'all\' will process all deps_os '
3146 'references')
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 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00003150 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00003151 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00003152 client.PrintLocationAndContents()
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00003153 return client.RunOnDeps('diff', args)
3154
3155
Edward Lemur3298e7b2018-07-17 18:21:27 +00003156@metrics.collector.collect_metrics('gclient revert')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00003157def CMDrevert(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00003158 """Reverts all modifications in every dependencies.
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00003159
3160 That's the nuclear option to get back to a 'clean' state. It removes anything
agabled437d762016-10-17 09:35:11 -07003161 that shows up in git status."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00003162 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
3163 help='override deps for the specified (comma-separated) '
3164 'platform(s); \'all\' will process all deps_os '
3165 'references')
3166 parser.add_option('-n', '--nohooks', action='store_true',
3167 help='don\'t run hooks after the revert is complete')
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00003168 parser.add_option('-p', '--noprehooks', action='store_true',
3169 help='don\'t run pre-DEPS hooks', default=False)
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +00003170 parser.add_option('--upstream', action='store_true',
3171 help='Make repo state match upstream branch.')
Josip Sokcevic14a83ae2020-05-21 01:36:34 +00003172 parser.add_option('--break_repo_locks',
3173 action='store_true',
3174 help='No longer used.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00003175 (options, args) = parser.parse_args(args)
Josip Sokcevic14a83ae2020-05-21 01:36:34 +00003176 if options.break_repo_locks:
3177 print('Warning: break_repo_locks is no longer used. Please remove its ' +
3178 'usage.')
3179
maruel@chromium.org5ca27692010-05-26 19:32:41 +00003180 # --force is implied.
3181 options.force = True
steveblock@chromium.org98e69452012-02-16 16:36:43 +00003182 options.reset = False
3183 options.delete_unversioned_trees = False
agablec903d732016-07-26 09:07:24 -07003184 options.merge = False
maruel@chromium.org2806acc2009-05-15 12:33:34 +00003185 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00003186 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00003187 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00003188 return client.RunOnDeps('revert', args)
3189
3190
Edward Lemur3298e7b2018-07-17 18:21:27 +00003191@metrics.collector.collect_metrics('gclient runhooks')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00003192def CMDrunhooks(parser, args):
3193 """Runs hooks for files that have been modified in the local working copy."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00003194 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
3195 help='override deps for the specified (comma-separated) '
3196 'platform(s); \'all\' will process all deps_os '
3197 'references')
3198 parser.add_option('-f', '--force', action='store_true', default=True,
3199 help='Deprecated. No effect.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00003200 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00003201 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00003202 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00003203 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00003204 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00003205 client.PrintLocationAndContents()
maruel@chromium.org5df6a462009-08-28 18:52:26 +00003206 options.force = True
maruel@chromium.org5ca27692010-05-26 19:32:41 +00003207 options.nohooks = False
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00003208 return client.RunOnDeps('runhooks', args)
3209
3210
Edward Lemur3298e7b2018-07-17 18:21:27 +00003211@metrics.collector.collect_metrics('gclient revinfo')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00003212def CMDrevinfo(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00003213 """Outputs revision info mapping for the client and its dependencies.
maruel@chromium.org9eda4112010-06-11 18:56:10 +00003214
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00003215 This allows the capture of an overall 'revision' for the source tree that
maruel@chromium.org9eda4112010-06-11 18:56:10 +00003216 can be used to reproduce the same tree in the future. It is only useful for
agabled437d762016-10-17 09:35:11 -07003217 'unpinned dependencies', i.e. DEPS/deps references without a git hash.
3218 A git branch name isn't 'pinned' since the actual commit can change.
maruel@chromium.org9eda4112010-06-11 18:56:10 +00003219 """
3220 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
3221 help='override deps for the specified (comma-separated) '
3222 'platform(s); \'all\' will process all deps_os '
3223 'references')
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00003224 parser.add_option('-a', '--actual', action='store_true',
3225 help='gets the actual checked out revisions instead of the '
3226 'ones specified in the DEPS and .gclient files')
maruel@chromium.org9eda4112010-06-11 18:56:10 +00003227 parser.add_option('-s', '--snapshot', action='store_true',
3228 help='creates a snapshot .gclient file of the current '
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00003229 'version of all repositories to reproduce the tree, '
3230 'implies -a')
Edward Lesmesbb16e332018-03-30 17:54:51 -04003231 parser.add_option('--filter', action='append', dest='filter',
Edward Lesmesdaa76d22018-03-06 14:56:57 -05003232 help='Display revision information only for the specified '
Edward Lesmesbb16e332018-03-30 17:54:51 -04003233 'dependencies (filtered by URL or path).')
Edward Lesmesc2960242018-03-06 20:50:15 -05003234 parser.add_option('--output-json',
3235 help='Output a json document to this path containing '
3236 'information about the revisions.')
Joey Scarr8d3925b2018-07-15 23:36:25 +00003237 parser.add_option('--ignore-dep-type', choices=['git', 'cipd'],
3238 help='Specify to skip processing of a certain type of dep.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00003239 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00003240 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00003241 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00003242 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00003243 client.PrintRevInfo()
maruel@chromium.org79692d62010-05-14 18:57:13 +00003244 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00003245
3246
Edward Lemur3298e7b2018-07-17 18:21:27 +00003247@metrics.collector.collect_metrics('gclient getdep')
Edward Lesmes411041f2018-04-05 20:12:55 -04003248def CMDgetdep(parser, args):
Josip Sokcevic7b5e3d72023-06-13 00:28:23 +00003249 """Gets revision information and variable values from a DEPS file.
3250
3251 If key doesn't exist or is incorrectly declared, this script exits with exit
3252 code 2."""
Edward Lesmes411041f2018-04-05 20:12:55 -04003253 parser.add_option('--var', action='append',
3254 dest='vars', metavar='VAR', default=[],
3255 help='Gets the value of a given variable.')
3256 parser.add_option('-r', '--revision', action='append',
Edward Lemuraf3328f2018-11-19 14:11:46 +00003257 dest='getdep_revisions', metavar='DEP', default=[],
Edward Lesmes411041f2018-04-05 20:12:55 -04003258 help='Gets the revision/version for the given dependency. '
3259 'If it is a git dependency, dep must be a path. If it '
3260 'is a CIPD dependency, dep must be of the form '
3261 'path:package.')
3262 parser.add_option('--deps-file', default='DEPS',
3263 # TODO(ehmaldonado): Try to find the DEPS file pointed by
3264 # .gclient first.
3265 help='The DEPS file to be edited. Defaults to the DEPS '
3266 'file in the current directory.')
3267 (options, args) = parser.parse_args(args)
3268
3269 if not os.path.isfile(options.deps_file):
3270 raise gclient_utils.Error(
3271 'DEPS file %s does not exist.' % options.deps_file)
3272 with open(options.deps_file) as f:
3273 contents = f.read()
Edward Lemuraf3328f2018-11-19 14:11:46 +00003274 client = GClient.LoadCurrentConfig(options)
3275 if client is not None:
3276 builtin_vars = client.get_builtin_vars()
3277 else:
Edward Lemurca879322019-09-09 20:18:13 +00003278 logging.warning(
Edward Lemuraf3328f2018-11-19 14:11:46 +00003279 'Couldn\'t find a valid gclient config. Will attempt to parse the DEPS '
3280 'file without support for built-in variables.')
3281 builtin_vars = None
3282 local_scope = gclient_eval.Exec(contents, options.deps_file,
3283 builtin_vars=builtin_vars)
Edward Lesmes411041f2018-04-05 20:12:55 -04003284
3285 for var in options.vars:
3286 print(gclient_eval.GetVar(local_scope, var))
3287
Edward Lemuraf3328f2018-11-19 14:11:46 +00003288 for name in options.getdep_revisions:
Edward Lesmes411041f2018-04-05 20:12:55 -04003289 if ':' in name:
3290 name, _, package = name.partition(':')
3291 if not name or not package:
3292 parser.error(
3293 'Wrong CIPD format: %s:%s should be of the form path:pkg.'
3294 % (name, package))
3295 print(gclient_eval.GetCIPD(local_scope, name, package))
3296 else:
Josip Sokcevic7b5e3d72023-06-13 00:28:23 +00003297 try:
3298 print(gclient_eval.GetRevision(local_scope, name))
3299 except KeyError as e:
3300 print(repr(e), file=sys.stderr)
3301 sys.exit(2)
Edward Lesmes411041f2018-04-05 20:12:55 -04003302
3303
Edward Lemur3298e7b2018-07-17 18:21:27 +00003304@metrics.collector.collect_metrics('gclient setdep')
Edward Lesmes6f64a052018-03-20 17:35:49 -04003305def CMDsetdep(parser, args):
Edward Lesmes0ecf6d62018-04-05 18:28:55 -04003306 """Modifies dependency revisions and variable values in a DEPS file"""
Edward Lesmes6f64a052018-03-20 17:35:49 -04003307 parser.add_option('--var', action='append',
3308 dest='vars', metavar='VAR=VAL', default=[],
3309 help='Sets a variable to the given value with the format '
3310 'name=value.')
3311 parser.add_option('-r', '--revision', action='append',
Edward Lemuraf3328f2018-11-19 14:11:46 +00003312 dest='setdep_revisions', metavar='DEP@REV', default=[],
Edward Lesmes6f64a052018-03-20 17:35:49 -04003313 help='Sets the revision/version for the dependency with '
3314 'the format dep@rev. If it is a git dependency, dep '
3315 'must be a path and rev must be a git hash or '
3316 'reference (e.g. src/dep@deadbeef). If it is a CIPD '
3317 'dependency, dep must be of the form path:package and '
3318 'rev must be the package version '
3319 '(e.g. src/pkg:chromium/pkg@2.1-cr0).')
3320 parser.add_option('--deps-file', default='DEPS',
3321 # TODO(ehmaldonado): Try to find the DEPS file pointed by
3322 # .gclient first.
3323 help='The DEPS file to be edited. Defaults to the DEPS '
3324 'file in the current directory.')
3325 (options, args) = parser.parse_args(args)
Edward Lesmes0ecf6d62018-04-05 18:28:55 -04003326 if args:
3327 parser.error('Unused arguments: "%s"' % '" "'.join(args))
Edward Lesmesae6836e2018-11-19 15:27:20 +00003328 if not options.setdep_revisions and not options.vars:
Edward Lesmes0ecf6d62018-04-05 18:28:55 -04003329 parser.error(
3330 'You must specify at least one variable or revision to modify.')
Edward Lesmes6f64a052018-03-20 17:35:49 -04003331
Edward Lesmes6f64a052018-03-20 17:35:49 -04003332 if not os.path.isfile(options.deps_file):
3333 raise gclient_utils.Error(
3334 'DEPS file %s does not exist.' % options.deps_file)
3335 with open(options.deps_file) as f:
3336 contents = f.read()
Edward Lemuraf3328f2018-11-19 14:11:46 +00003337
3338 client = GClient.LoadCurrentConfig(options)
3339 if client is not None:
3340 builtin_vars = client.get_builtin_vars()
3341 else:
Edward Lemurca879322019-09-09 20:18:13 +00003342 logging.warning(
Edward Lemuraf3328f2018-11-19 14:11:46 +00003343 'Couldn\'t find a valid gclient config. Will attempt to parse the DEPS '
3344 'file without support for built-in variables.')
3345 builtin_vars = None
3346
3347 local_scope = gclient_eval.Exec(contents, options.deps_file,
3348 builtin_vars=builtin_vars)
Edward Lesmes6f64a052018-03-20 17:35:49 -04003349
Aravind Vasudevancb8023d2023-07-07 00:03:45 +00003350 # Create a set of all git submodules.
3351 submodule_status = subprocess2.check_output(['git', 'submodule',
3352 'status']).decode('utf-8')
3353 git_modules = {l.split()[1] for l in submodule_status.splitlines()}
3354
Edward Lesmes6f64a052018-03-20 17:35:49 -04003355 for var in options.vars:
3356 name, _, value = var.partition('=')
3357 if not name or not value:
Edward Lesmes0ecf6d62018-04-05 18:28:55 -04003358 parser.error(
Edward Lesmes6f64a052018-03-20 17:35:49 -04003359 'Wrong var format: %s should be of the form name=value.' % var)
Edward Lesmes3d993812018-04-02 12:52:49 -04003360 if name in local_scope['vars']:
3361 gclient_eval.SetVar(local_scope, name, value)
3362 else:
3363 gclient_eval.AddVar(local_scope, name, value)
Edward Lesmes6f64a052018-03-20 17:35:49 -04003364
Edward Lemuraf3328f2018-11-19 14:11:46 +00003365 for revision in options.setdep_revisions:
Edward Lesmes6f64a052018-03-20 17:35:49 -04003366 name, _, value = revision.partition('@')
3367 if not name or not value:
Edward Lesmes0ecf6d62018-04-05 18:28:55 -04003368 parser.error(
Edward Lesmes6f64a052018-03-20 17:35:49 -04003369 'Wrong dep format: %s should be of the form dep@rev.' % revision)
3370 if ':' in name:
3371 name, _, package = name.partition(':')
3372 if not name or not package:
Edward Lesmes0ecf6d62018-04-05 18:28:55 -04003373 parser.error(
Edward Lesmes6f64a052018-03-20 17:35:49 -04003374 'Wrong CIPD format: %s:%s should be of the form path:pkg@version.'
3375 % (name, package))
3376 gclient_eval.SetCIPD(local_scope, name, package, value)
3377 else:
Aravind Vasudevancb8023d2023-07-07 00:03:45 +00003378 # Update DEPS only when `git_dependencies` == DEPS or SYNC.
3379 # git_dependencies is defaulted to DEPS when not set.
3380 if 'git_dependencies' not in local_scope or local_scope[
3381 'git_dependencies'] in (gclient_eval.DEPS, gclient_eval.SYNC):
3382 gclient_eval.SetRevision(local_scope, name, value)
3383
3384 # Update git submodules when `git_dependencies` == SYNC or SUBMODULES.
3385 if 'git_dependencies' in local_scope and local_scope[
3386 'git_dependencies'] in (gclient_eval.SUBMODULES, gclient_eval.SYNC):
3387 # gclient setdep should update the revision, i.e., the gitlink only
3388 # when the submodule entry is already present within .gitmodules.
3389 if name not in git_modules:
3390 raise KeyError(
3391 'Could not find any dependency called %s in .gitmodules.' % name)
3392
3393 # Update the gitlink for the submodule.
3394 subprocess2.call([
3395 'git', 'update-index', '--add', '--cacheinfo',
3396 f'160000,{value},{name}'
3397 ])
Edward Lesmes6f64a052018-03-20 17:35:49 -04003398
John Emau7aa68242020-02-20 19:44:53 +00003399 with open(options.deps_file, 'wb') as f:
3400 f.write(gclient_eval.RenderDEPSFile(local_scope).encode('utf-8'))
Edward Lesmes6f64a052018-03-20 17:35:49 -04003401
3402
Edward Lemur3298e7b2018-07-17 18:21:27 +00003403@metrics.collector.collect_metrics('gclient verify')
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00003404def CMDverify(parser, args):
3405 """Verifies the DEPS file deps are only from allowed_hosts."""
3406 (options, args) = parser.parse_args(args)
3407 client = GClient.LoadCurrentConfig(options)
3408 if not client:
3409 raise gclient_utils.Error('client not configured; see \'gclient config\'')
3410 client.RunOnDeps(None, [])
3411 # Look at each first-level dependency of this gclient only.
3412 for dep in client.dependencies:
3413 bad_deps = dep.findDepsFromNotAllowedHosts()
3414 if not bad_deps:
3415 continue
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00003416 print("There are deps from not allowed hosts in file %s" % dep.deps_file)
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00003417 for bad_dep in bad_deps:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00003418 print("\t%s at %s" % (bad_dep.name, bad_dep.url))
3419 print("allowed_hosts:", ', '.join(dep.allowed_hosts))
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00003420 sys.stdout.flush()
3421 raise gclient_utils.Error(
3422 'dependencies from disallowed hosts; check your DEPS file.')
3423 return 0
3424
Edward Lemur32e3d1e2018-07-12 00:54:05 +00003425
3426@subcommand.epilog("""For more information on what metrics are we collecting and
Edward Lemur8a2e3312018-07-12 21:15:09 +00003427why, please read metrics.README.md or visit https://bit.ly/2ufRS4p""")
Edward Lemur3298e7b2018-07-17 18:21:27 +00003428@metrics.collector.collect_metrics('gclient metrics')
Edward Lemur32e3d1e2018-07-12 00:54:05 +00003429def CMDmetrics(parser, args):
3430 """Reports, and optionally modifies, the status of metric collection."""
3431 parser.add_option('--opt-in', action='store_true', dest='enable_metrics',
3432 help='Opt-in to metrics collection.',
3433 default=None)
3434 parser.add_option('--opt-out', action='store_false', dest='enable_metrics',
3435 help='Opt-out of metrics collection.')
3436 options, args = parser.parse_args(args)
3437 if args:
3438 parser.error('Unused arguments: "%s"' % '" "'.join(args))
3439 if not metrics.collector.config.is_googler:
3440 print("You're not a Googler. Metrics collection is disabled for you.")
3441 return 0
3442
3443 if options.enable_metrics is not None:
3444 metrics.collector.config.opted_in = options.enable_metrics
3445
3446 if metrics.collector.config.opted_in is None:
3447 print("You haven't opted in or out of metrics collection.")
3448 elif metrics.collector.config.opted_in:
3449 print("You have opted in. Thanks!")
3450 else:
3451 print("You have opted out. Please consider opting in.")
3452 return 0
3453
3454
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00003455class OptionParser(optparse.OptionParser):
szager@chromium.orge2e03202012-07-31 18:05:16 +00003456 gclientfile_default = os.environ.get('GCLIENT_FILE', '.gclient')
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00003457
3458 def __init__(self, **kwargs):
3459 optparse.OptionParser.__init__(
3460 self, version='%prog ' + __version__, **kwargs)
3461
Aravind Vasudevan3d760cc2023-03-30 20:36:14 +00003462 # Some arm boards have issues with parallel sync.
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00003463 if platform.machine().startswith('arm'):
Aravind Vasudevan3d760cc2023-03-30 20:36:14 +00003464 jobs = 1
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00003465 else:
3466 jobs = max(8, gclient_utils.NumLocalCpus())
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00003467
3468 self.add_option(
3469 '-j', '--jobs', default=jobs, type='int',
3470 help='Specify how many SCM commands can run in parallel; defaults to '
tnagel@chromium.orga2aaa632014-02-28 21:47:27 +00003471 '%default on this machine')
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00003472 self.add_option(
3473 '-v', '--verbose', action='count', default=0,
3474 help='Produces additional output for diagnostics. Can be used up to '
3475 'three times for more logging info.')
3476 self.add_option(
3477 '--gclientfile', dest='config_filename',
3478 help='Specify an alternate %s file' % self.gclientfile_default)
3479 self.add_option(
3480 '--spec',
3481 help='create a gclient file containing the provided string. Due to '
3482 'Cygwin/Python brokenness, it can\'t contain any newlines.')
3483 self.add_option(
3484 '--no-nag-max', default=False, action='store_true',
scottmg@chromium.orgf547c802013-09-27 17:55:26 +00003485 help='Ignored for backwards compatibility.')
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00003486
Edward Lemur3298e7b2018-07-17 18:21:27 +00003487 def parse_args(self, args=None, _values=None):
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00003488 """Integrates standard options processing."""
Edward Lemur3298e7b2018-07-17 18:21:27 +00003489 # Create an optparse.Values object that will store only the actual passed
3490 # options, without the defaults.
3491 actual_options = optparse.Values()
3492 _, args = optparse.OptionParser.parse_args(self, args, actual_options)
3493 # Create an optparse.Values object with the default options.
3494 options = optparse.Values(self.get_default_values().__dict__)
3495 # Update it with the options passed by the user.
3496 options._update_careful(actual_options.__dict__)
3497 # Store the options passed by the user in an _actual_options attribute.
3498 # We store only the keys, and not the values, since the values can contain
3499 # arbitrary information, which might be PII.
Edward Lemuree7b9dd2019-07-20 01:29:08 +00003500 metrics.collector.add('arguments', list(actual_options.__dict__))
Edward Lemur3298e7b2018-07-17 18:21:27 +00003501
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00003502 levels = [logging.ERROR, logging.WARNING, logging.INFO, logging.DEBUG]
3503 logging.basicConfig(
3504 level=levels[min(options.verbose, len(levels) - 1)],
maruel@chromium.org0895b752011-08-26 20:40:33 +00003505 format='%(module)s(%(lineno)d) %(funcName)s:%(message)s')
szager@chromium.orge2e03202012-07-31 18:05:16 +00003506 if options.config_filename and options.spec:
Quinten Yearsley925cedb2020-04-13 17:49:39 +00003507 self.error('Cannot specify both --gclientfile and --spec')
rdsmith@chromium.orgd9591f02014-02-05 19:28:20 +00003508 if (options.config_filename and
3509 options.config_filename != os.path.basename(options.config_filename)):
3510 self.error('--gclientfile target must be a filename, not a path')
szager@chromium.orge2e03202012-07-31 18:05:16 +00003511 if not options.config_filename:
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00003512 options.config_filename = self.gclientfile_default
maruel@chromium.org0895b752011-08-26 20:40:33 +00003513 options.entries_filename = options.config_filename + '_entries'
3514 if options.jobs < 1:
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00003515 self.error('--jobs must be 1 or higher')
maruel@chromium.org0895b752011-08-26 20:40:33 +00003516
3517 # These hacks need to die.
3518 if not hasattr(options, 'revisions'):
3519 # GClient.RunOnDeps expects it even if not applicable.
3520 options.revisions = []
Joanna Wanga84a16b2022-07-27 18:52:17 +00003521 if not hasattr(options, 'experiments'):
3522 options.experiments = []
smutae7ea312016-07-18 11:59:41 -07003523 if not hasattr(options, 'head'):
3524 options.head = None
maruel@chromium.org0895b752011-08-26 20:40:33 +00003525 if not hasattr(options, 'nohooks'):
3526 options.nohooks = True
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00003527 if not hasattr(options, 'noprehooks'):
3528 options.noprehooks = True
maruel@chromium.org0895b752011-08-26 20:40:33 +00003529 if not hasattr(options, 'deps_os'):
3530 options.deps_os = None
maruel@chromium.org0895b752011-08-26 20:40:33 +00003531 if not hasattr(options, 'force'):
3532 options.force = None
3533 return (options, args)
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00003534
maruel@chromium.org39c0b222013-08-17 16:57:01 +00003535
3536def disable_buffering():
3537 # Make stdout auto-flush so buildbot doesn't kill us during lengthy
3538 # operations. Python as a strong tendency to buffer sys.stdout.
3539 sys.stdout = gclient_utils.MakeFileAutoFlush(sys.stdout)
3540 # Make stdout annotated with the thread ids.
3541 sys.stdout = gclient_utils.MakeFileAnnotated(sys.stdout)
maruel@chromium.org0895b752011-08-26 20:40:33 +00003542
3543
Elly Fong-Jones7b294392019-04-18 18:32:10 +00003544def path_contains_tilde():
3545 for element in os.environ['PATH'].split(os.pathsep):
Henrique Ferreiro4ef32212019-04-29 23:32:31 +00003546 if element.startswith('~') and os.path.abspath(
3547 os.path.realpath(os.path.expanduser(element))) == DEPOT_TOOLS_DIR:
Elly Fong-Jones7b294392019-04-18 18:32:10 +00003548 return True
3549 return False
3550
3551
3552def can_run_gclient_and_helpers():
maruel@chromium.org82798cb2012-02-23 18:16:12 +00003553 if sys.hexversion < 0x02060000:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00003554 print(
maruel@chromium.org82798cb2012-02-23 18:16:12 +00003555 '\nYour python version %s is unsupported, please upgrade.\n' %
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00003556 sys.version.split(' ', 1)[0],
3557 file=sys.stderr)
Elly Fong-Jones7b294392019-04-18 18:32:10 +00003558 return False
bcwhite@chromium.org6683ab42013-02-11 16:13:47 +00003559 if not sys.executable:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00003560 print(
3561 '\nPython cannot find the location of it\'s own executable.\n',
3562 file=sys.stderr)
Elly Fong-Jones7b294392019-04-18 18:32:10 +00003563 return False
3564 if path_contains_tilde():
3565 print(
3566 '\nYour PATH contains a literal "~", which works in some shells ' +
3567 'but will break when python tries to run subprocesses. ' +
3568 'Replace the "~" with $HOME.\n' +
3569 'See https://crbug.com/952865.\n',
3570 file=sys.stderr)
3571 return False
3572 return True
3573
3574
3575def main(argv):
3576 """Doesn't parse the arguments here, just find the right subcommand to
3577 execute."""
3578 if not can_run_gclient_and_helpers():
bcwhite@chromium.org6683ab42013-02-11 16:13:47 +00003579 return 2
maruel@chromium.org39c0b222013-08-17 16:57:01 +00003580 fix_encoding.fix_encoding()
3581 disable_buffering()
iannucci@chromium.org596cd5c2016-04-04 21:34:39 +00003582 setup_color.init()
maruel@chromium.org39c0b222013-08-17 16:57:01 +00003583 dispatcher = subcommand.CommandDispatcher(__name__)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00003584 try:
maruel@chromium.org39c0b222013-08-17 16:57:01 +00003585 return dispatcher.execute(OptionParser(), argv)
xusydoc@chromium.org2fd6c3f2013-05-03 21:57:55 +00003586 except KeyboardInterrupt:
3587 gclient_utils.GClientChildren.KillAllRemainingChildren()
3588 raise
vapier@chromium.orga81a56e2015-11-11 07:56:13 +00003589 except (gclient_utils.Error, subprocess2.CalledProcessError) as e:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00003590 print('Error: %s' % str(e), file=sys.stderr)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00003591 return 1
borenet@google.com6a9b1682014-03-24 18:35:23 +00003592 finally:
3593 gclient_utils.PrintWarnings()
sbc@chromium.org013731e2015-02-26 18:28:43 +00003594 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00003595
3596
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00003597if '__main__' == __name__:
Edward Lemur6f812e12018-07-31 22:45:57 +00003598 with metrics.collector.print_notice_and_exit():
sbc@chromium.org013731e2015-02-26 18:28:43 +00003599 sys.exit(main(sys.argv[1:]))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00003600
3601# vim: ts=2:sw=2:tw=80:et: