blob: 741f16a3ec69962f56481dd45b4930d33a541850 [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
maruel@chromium.org39c0b222013-08-17 16:57:01 +000083__version__ = '0.7'
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000084
maruel@chromium.org9e5317a2010-08-13 20:35:11 +000085import copy
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +000086import json
maruel@chromium.org754960e2009-09-21 12:31:05 +000087import logging
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000088import optparse
89import os
bradnelson@google.com4949dab2012-04-19 16:41:07 +000090import platform
maruel@chromium.org621939b2010-08-10 20:12:00 +000091import posixpath
msb@chromium.org2e38de72009-09-28 17:04:47 +000092import pprint
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000093import re
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000094import sys
ilevy@chromium.orgc28d3772013-07-12 19:42:37 +000095import time
Gavin Mak65c49b12023-08-24 18:06:42 +000096import urllib.parse
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000097
Tom Andersonc31ae0b2018-02-06 14:48:56 -080098import detect_host_arch
maruel@chromium.org35625c72011-03-23 17:34:02 +000099import fix_encoding
Aravind Vasudevanb8164182023-08-25 21:49:12 +0000100import git_common
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +0200101import gclient_eval
Nico Weber09e0b382019-03-11 16:54:07 +0000102import gclient_paths
Gavin Mak65c49b12023-08-24 18:06:42 +0000103import gclient_scm
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000104import gclient_utils
szager@chromium.org848fd492014-04-09 19:06:44 +0000105import git_cache
Edward Lemur32e3d1e2018-07-12 00:54:05 +0000106import metrics
Edward Lemur40764b02018-07-20 18:50:29 +0000107import metrics_utils
Joanna Wange36c6bb2023-08-30 22:09:59 +0000108import scm as scm_git
Gavin Mak65c49b12023-08-24 18:06:42 +0000109import setup_color
maruel@chromium.org39c0b222013-08-17 16:57:01 +0000110import subcommand
maruel@chromium.org31cb48a2011-04-04 18:01:36 +0000111import subprocess2
Gavin Mak65c49b12023-08-24 18:06:42 +0000112from third_party.repo.progress import Progress
Aaron Gableac9b0f32019-04-18 17:38:37 +0000113
114
Henrique Ferreiro4ef32212019-04-29 23:32:31 +0000115DEPOT_TOOLS_DIR = os.path.dirname(os.path.abspath(os.path.realpath(__file__)))
116
Robert Iannuccia19649b2018-06-29 16:31:45 +0000117# Singleton object to represent an unset cache_dir (as opposed to a disabled
118# one, e.g. if a spec explicitly says `cache_dir = None`.)
119UNSET_CACHE_DIR = object()
120
Joanna Wang01870792022-08-01 19:02:57 +0000121PREVIOUS_CUSTOM_VARS_FILE = '.gclient_previous_custom_vars'
122PREVIOUS_SYNC_COMMITS_FILE = '.gclient_previous_sync_commits'
Robert Iannuccia19649b2018-06-29 16:31:45 +0000123
Joanna Wangf3edc502022-07-20 00:12:10 +0000124PREVIOUS_SYNC_COMMITS = 'GCLIENT_PREVIOUS_SYNC_COMMITS'
Joanna Wang66286612022-06-30 19:59:13 +0000125
Joanna Wanga84a16b2022-07-27 18:52:17 +0000126NO_SYNC_EXPERIMENT = 'no-sync'
127
Joanna Wang66286612022-06-30 19:59:13 +0000128
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +0200129class GNException(Exception):
130 pass
131
132
Aravind Vasudevaned935cf2023-08-24 23:52:20 +0000133def ToGNString(value):
134 """Returns a stringified GN equivalent of the Python value."""
Gavin Mak65c49b12023-08-24 18:06:42 +0000135 if isinstance(value, str):
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +0200136 if value.find('\n') >= 0:
137 raise GNException("Trying to print a string with a newline in it.")
138 return '"' + \
139 value.replace('\\', '\\\\').replace('"', '\\"').replace('$', '\\$') + \
140 '"'
141
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +0200142 if isinstance(value, bool):
143 if value:
144 return "true"
145 return "false"
146
147 # NOTE: some type handling removed compared to chromium/src copy.
148
149 raise GNException("Unsupported type when printing to GN.")
150
151
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200152class Hook(object):
153 """Descriptor of command ran before/after sync or on demand."""
154
Paweł Hajdan, Jr032d5452017-06-22 20:43:53 +0200155 def __init__(self, action, pattern=None, name=None, cwd=None, condition=None,
Corentin Walleza68660d2018-09-10 17:33:24 +0000156 variables=None, verbose=False, cwd_base=None):
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200157 """Constructor.
158
159 Arguments:
Gavin Mak65c49b12023-08-24 18:06:42 +0000160 action (list of str): argv of the command to run
161 pattern (str regex): noop with git; deprecated
162 name (str): optional name; no effect on operation
163 cwd (str): working directory to use
164 condition (str): condition when to run the hook
Paweł Hajdan, Jr032d5452017-06-22 20:43:53 +0200165 variables (dict): variables for evaluating the condition
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200166 """
167 self._action = gclient_utils.freeze(action)
168 self._pattern = pattern
169 self._name = name
Paweł Hajdan, Jrc9364392017-06-14 17:11:56 +0200170 self._cwd = cwd
Paweł Hajdan, Jr032d5452017-06-22 20:43:53 +0200171 self._condition = condition
172 self._variables = variables
Daniel Chenga0c5f082017-10-19 13:35:19 -0700173 self._verbose = verbose
Corentin Walleza68660d2018-09-10 17:33:24 +0000174 self._cwd_base = cwd_base
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200175
176 @staticmethod
Corentin Walleza68660d2018-09-10 17:33:24 +0000177 def from_dict(d, variables=None, verbose=False, conditions=None,
178 cwd_base=None):
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200179 """Creates a Hook instance from a dict like in the DEPS file."""
Michael Moss42d02c22018-02-05 10:32:24 -0800180 # Merge any local and inherited conditions.
Edward Lemur16f4bad2018-05-16 16:53:49 -0400181 gclient_eval.UpdateCondition(d, 'and', conditions)
Paweł Hajdan, Jr032d5452017-06-22 20:43:53 +0200182 return Hook(
183 d['action'],
184 d.get('pattern'),
185 d.get('name'),
186 d.get('cwd'),
Edward Lemur16f4bad2018-05-16 16:53:49 -0400187 d.get('condition'),
Daniel Chenga0c5f082017-10-19 13:35:19 -0700188 variables=variables,
189 # Always print the header if not printing to a TTY.
Corentin Walleza68660d2018-09-10 17:33:24 +0000190 verbose=verbose or not setup_color.IS_TTY,
191 cwd_base=cwd_base)
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200192
193 @property
194 def action(self):
195 return self._action
196
197 @property
198 def pattern(self):
199 return self._pattern
200
201 @property
202 def name(self):
203 return self._name
204
Paweł Hajdan, Jrecf53fe2017-09-29 18:28:49 +0200205 @property
206 def condition(self):
207 return self._condition
208
Corentin Walleza68660d2018-09-10 17:33:24 +0000209 @property
210 def effective_cwd(self):
211 cwd = self._cwd_base
212 if self._cwd:
213 cwd = os.path.join(cwd, self._cwd)
214 return cwd
215
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200216 def matches(self, file_list):
217 """Returns true if the pattern matches any of files in the list."""
218 if not self._pattern:
219 return True
220 pattern = re.compile(self._pattern)
221 return bool([f for f in file_list if pattern.search(f)])
222
Corentin Walleza68660d2018-09-10 17:33:24 +0000223 def run(self):
Paweł Hajdan, Jr032d5452017-06-22 20:43:53 +0200224 """Executes the hook's command (provided the condition is met)."""
225 if (self._condition and
226 not gclient_eval.EvaluateCondition(self._condition, self._variables)):
227 return
228
Aravind Vasudevanc5f0cbb2022-01-24 23:56:57 +0000229 cmd = list(self._action)
Paweł Hajdan, Jrfc6196b2017-07-27 13:15:25 +0200230
Josip Sokcevic9d18c9e2023-07-10 14:47:44 +0000231 if cmd[0] == 'vpython3' and _detect_host_os() == 'win':
Nodir Turakulov0ffcc872017-11-09 16:44:58 -0800232 cmd[0] += '.bat'
Paweł Hajdan, Jrc9364392017-06-14 17:11:56 +0200233
Edward Lesmes58542b72021-06-10 20:50:37 +0000234 exit_code = 2
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200235 try:
236 start_time = time.time()
Edward Lemur24146be2019-08-01 21:44:52 +0000237 gclient_utils.CheckCallAndFilter(
238 cmd, cwd=self.effective_cwd, print_stdout=True, show_header=True,
239 always_show_header=self._verbose)
Edward Lesmes58542b72021-06-10 20:50:37 +0000240 exit_code = 0
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200241 except (gclient_utils.Error, subprocess2.CalledProcessError) as e:
242 # Use a discrete exit status code of 2 to indicate that a hook action
243 # failed. Users of this script may wish to treat hook action failures
244 # differently from VC failures.
245 print('Error: %s' % str(e), file=sys.stderr)
Edward Lesmes58542b72021-06-10 20:50:37 +0000246 sys.exit(exit_code)
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200247 finally:
248 elapsed_time = time.time() - start_time
Edward Lesmes58542b72021-06-10 20:50:37 +0000249 metrics.collector.add_repeated('hooks', {
250 'action': gclient_utils.CommandToStr(cmd),
251 'name': self._name,
252 'cwd': os.path.relpath(
253 os.path.normpath(self.effective_cwd),
254 self._cwd_base),
255 'condition': self._condition,
256 'execution_time': elapsed_time,
257 'exit_code': exit_code,
258 })
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200259 if elapsed_time > 10:
260 print("Hook '%s' took %.2f secs" % (
261 gclient_utils.CommandToStr(cmd), elapsed_time))
262
263
Paweł Hajdan, Jrfc6196b2017-07-27 13:15:25 +0200264class DependencySettings(object):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000265 """Immutable configuration settings."""
266 def __init__(
Edward Lemure05f18d2018-06-08 17:36:53 +0000267 self, parent, url, managed, custom_deps, custom_vars,
Michael Mossd683d7c2018-06-15 05:05:17 +0000268 custom_hooks, deps_file, should_process, relative, condition):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000269 # These are not mutable:
270 self._parent = parent
mmoss@chromium.org8f93f792014-08-26 23:24:09 +0000271 self._deps_file = deps_file
maruel@chromium.org064186c2011-09-27 23:53:33 +0000272 self._url = url
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +0200273 # The condition as string (or None). Useful to keep e.g. for flatten.
274 self._condition = condition
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000275 # 'managed' determines whether or not this dependency is synced/updated by
Michael Mossd683d7c2018-06-15 05:05:17 +0000276 # gclient after gclient checks it out initially. The difference between
277 # 'managed' and 'should_process' is that the user specifies 'managed' via
278 # the --unmanaged command-line flag or a .gclient config, where
279 # 'should_process' is dynamically set by gclient if it goes over its
280 # recursion limit and controls gclient's behavior so it does not misbehave.
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000281 self._managed = managed
Michael Mossd683d7c2018-06-15 05:05:17 +0000282 self._should_process = should_process
agabledce6ddc2016-09-08 10:02:16 -0700283 # If this is a recursed-upon sub-dependency, and the parent has
284 # use_relative_paths set, then this dependency should check out its own
285 # dependencies relative to that parent's path for this, rather than
286 # relative to the .gclient file.
287 self._relative = relative
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +0000288 # This is a mutable value which has the list of 'target_os' OSes listed in
289 # the current deps file.
290 self.local_target_os = None
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000291
292 # These are only set in .gclient and not in DEPS files.
293 self._custom_vars = custom_vars or {}
294 self._custom_deps = custom_deps or {}
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000295 self._custom_hooks = custom_hooks or []
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000296
Michael Mossd683d7c2018-06-15 05:05:17 +0000297 # Post process the url to remove trailing slashes.
Gavin Mak65c49b12023-08-24 18:06:42 +0000298 if isinstance(self.url, str):
Michael Moss4e9b50a2018-05-23 22:35:06 -0700299 # urls are sometime incorrectly written as proto://host/path/@rev. Replace
300 # it to proto://host/path@rev.
301 self.set_url(self.url.replace('/@', '@'))
Michael Mossd683d7c2018-06-15 05:05:17 +0000302 elif not isinstance(self.url, (None.__class__)):
303 raise gclient_utils.Error(
304 ('dependency url must be either string or None, '
305 'instead of %s') % self.url.__class__.__name__)
Edward Lemure7273d22018-05-10 19:13:51 -0400306
mmoss@chromium.orgd0b272b2013-01-30 23:55:33 +0000307 # Make any deps_file path platform-appropriate.
John Budorick0f7b2002018-01-19 15:46:17 -0800308 if self._deps_file:
309 for sep in ['/', '\\']:
310 self._deps_file = self._deps_file.replace(sep, os.sep)
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000311
312 @property
313 def deps_file(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000314 return self._deps_file
315
316 @property
317 def managed(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000318 return self._managed
319
320 @property
321 def parent(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000322 return self._parent
323
324 @property
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000325 def root(self):
326 """Returns the root node, a GClient object."""
327 if not self.parent:
328 # This line is to signal pylint that it could be a GClient instance.
329 return self or GClient(None, None)
330 return self.parent.root
331
332 @property
Michael Mossd683d7c2018-06-15 05:05:17 +0000333 def should_process(self):
334 """True if this dependency should be processed, i.e. checked out."""
335 return self._should_process
336
337 @property
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000338 def custom_vars(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000339 return self._custom_vars.copy()
340
341 @property
342 def custom_deps(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000343 return self._custom_deps.copy()
344
maruel@chromium.org064186c2011-09-27 23:53:33 +0000345 @property
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000346 def custom_hooks(self):
347 return self._custom_hooks[:]
348
349 @property
maruel@chromium.org064186c2011-09-27 23:53:33 +0000350 def url(self):
Paweł Hajdan, Jrfc6196b2017-07-27 13:15:25 +0200351 """URL after variable expansion."""
maruel@chromium.org064186c2011-09-27 23:53:33 +0000352 return self._url
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000353
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000354 @property
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +0200355 def condition(self):
356 return self._condition
357
358 @property
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +0000359 def target_os(self):
360 if self.local_target_os is not None:
361 return tuple(set(self.local_target_os).union(self.parent.target_os))
Aravind Vasudevanc5f0cbb2022-01-24 23:56:57 +0000362
363 return self.parent.target_os
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +0000364
Tom Andersonc31ae0b2018-02-06 14:48:56 -0800365 @property
366 def target_cpu(self):
367 return self.parent.target_cpu
368
Edward Lemure7273d22018-05-10 19:13:51 -0400369 def set_url(self, url):
370 self._url = url
371
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000372 def get_custom_deps(self, name, url):
373 """Returns a custom deps if applicable."""
374 if self.parent:
375 url = self.parent.get_custom_deps(name, url)
376 # None is a valid return value to disable a dependency.
377 return self.custom_deps.get(name, url)
378
maruel@chromium.org064186c2011-09-27 23:53:33 +0000379
380class Dependency(gclient_utils.WorkItem, DependencySettings):
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000381 """Object that represents a dependency checkout."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000382
Aravind Vasudevanb6eaed22023-07-06 20:50:42 +0000383 def __init__(self,
384 parent,
385 name,
386 url,
387 managed,
388 custom_deps,
389 custom_vars,
390 custom_hooks,
391 deps_file,
392 should_process,
393 should_recurse,
394 relative,
395 condition,
396 protocol='https',
397 git_dependencies_state=gclient_eval.DEPS,
Aravind Vasudevan5965d3e2022-06-01 21:51:30 +0000398 print_outbuf=False):
maruel@chromium.org6ca8bf82011-09-19 23:04:30 +0000399 gclient_utils.WorkItem.__init__(self, name)
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000400 DependencySettings.__init__(
Michael Mossd683d7c2018-06-15 05:05:17 +0000401 self, parent, url, managed, custom_deps, custom_vars,
402 custom_hooks, deps_file, should_process, relative, condition)
maruel@chromium.org68988972011-09-20 14:11:42 +0000403
404 # This is in both .gclient and DEPS files:
maruel@chromium.org064186c2011-09-27 23:53:33 +0000405 self._deps_hooks = []
maruel@chromium.org68988972011-09-20 14:11:42 +0000406
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000407 self._pre_deps_hooks = []
408
maruel@chromium.org68988972011-09-20 14:11:42 +0000409 # Calculates properties:
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +0000410 self._dependencies = []
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +0200411 self._vars = {}
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200412
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000413 # A cache of the files affected by the current operation, necessary for
414 # hooks.
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000415 self._file_list = []
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000416 # List of host names from which dependencies are allowed.
417 # Default is an empty set, meaning unspecified in DEPS file, and hence all
Josip Sokcevic9c0dc302020-11-20 18:41:25 +0000418 # hosts will be allowed. Non-empty set means allowlist of hosts.
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000419 # allowed_hosts var is scoped to its DEPS file, and so it isn't recursive.
420 self._allowed_hosts = frozenset()
Michael Moss848c86e2018-05-03 16:05:50 -0700421 self._gn_args_from = None
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +0200422 # Spec for .gni output to write (if any).
423 self._gn_args_file = None
424 self._gn_args = []
maruel@chromium.org85c2a192010-07-22 21:14:43 +0000425 # If it is not set to True, the dependency wasn't processed for its child
426 # dependency, i.e. its DEPS wasn't read.
maruel@chromium.org064186c2011-09-27 23:53:33 +0000427 self._deps_parsed = False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000428 # This dependency has been processed, i.e. checked out
maruel@chromium.org064186c2011-09-27 23:53:33 +0000429 self._processed = False
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000430 # This dependency had its pre-DEPS hooks run
431 self._pre_deps_hooks_ran = False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000432 # This dependency had its hook run
maruel@chromium.org064186c2011-09-27 23:53:33 +0000433 self._hooks_ran = False
kustermann@google.coma692e8f2013-04-18 08:32:04 +0000434 # This is the scm used to checkout self.url. It may be used by dependencies
435 # to get the datetime of the revision we checked out.
436 self._used_scm = None
szager@chromium.org4ad264b2014-05-20 04:43:47 +0000437 self._used_revision = None
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +0000438 # The actual revision we ended up getting, or None if that information is
439 # unavailable
440 self._got_revision = None
Corentin Wallez271a78a2020-07-12 15:41:46 +0000441 # Whether this dependency should use relative paths.
442 self._use_relative_paths = False
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000443
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000444 # recursedeps is a mutable value that selectively overrides the default
Edward Lemurfbb06aa2018-06-11 20:43:06 +0000445 # 'no recursion' setting on a dep-by-dep basis.
iannucci@chromium.orgafa11ac2016-05-04 22:17:34 +0000446 #
Edward Lemurfbb06aa2018-06-11 20:43:06 +0000447 # It will be a dictionary of {deps_name: depfile_namee}
448 self.recursedeps = {}
449
450 # Whether we should process this dependency's DEPS file.
451 self._should_recurse = should_recurse
Edward Lemure7273d22018-05-10 19:13:51 -0400452
Joanna Wang18af7ef2022-07-01 16:51:00 +0000453 # Whether we should sync git/cipd dependencies and hooks from the
454 # DEPS file.
Joanna Wangf3edc502022-07-20 00:12:10 +0000455 # This is set based on skip_sync_revisions and must be done
Joanna Wang18af7ef2022-07-01 16:51:00 +0000456 # after the patch refs are applied.
457 # If this is False, we will still run custom_hooks and process
458 # custom_deps, if any.
459 self._should_sync = True
460
Michael Mossd683d7c2018-06-15 05:05:17 +0000461 self._OverrideUrl()
462 # This is inherited from WorkItem. We want the URL to be a resource.
Gavin Mak65c49b12023-08-24 18:06:42 +0000463 if self.url and isinstance(self.url, str):
Michael Moss4e9b50a2018-05-23 22:35:06 -0700464 # The url is usually given to gclient either as https://blah@123
465 # or just https://blah. The @123 portion is irrelevant.
466 self.resources.append(self.url.split('@')[0])
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000467
Edward Lemur231f5ea2018-01-31 19:02:36 +0100468 # Controls whether we want to print git's output when we first clone the
469 # dependency
470 self.print_outbuf = print_outbuf
471
Aravind Vasudevan5965d3e2022-06-01 21:51:30 +0000472 self.protocol = protocol
Aravind Vasudevanb6eaed22023-07-06 20:50:42 +0000473 self.git_dependencies_state = git_dependencies_state
Aravind Vasudevan5965d3e2022-06-01 21:51:30 +0000474
Michael Mossd683d7c2018-06-15 05:05:17 +0000475 if not self.name and self.parent:
476 raise gclient_utils.Error('Dependency without name')
477
478 def _OverrideUrl(self):
479 """Resolves the parsed url from the parent hierarchy."""
Aravind Vasudevanaae67252022-01-05 00:41:38 +0000480 parsed_url = self.get_custom_deps(
481 self._name.replace(os.sep, posixpath.sep) \
482 if self._name else self._name, self.url)
Michael Mossd683d7c2018-06-15 05:05:17 +0000483 if parsed_url != self.url:
484 logging.info('Dependency(%s)._OverrideUrl(%s) -> %s', self._name,
485 self.url, parsed_url)
486 self.set_url(parsed_url)
Edward Lemur1f392b82019-11-15 22:40:11 +0000487 return
Michael Mossd683d7c2018-06-15 05:05:17 +0000488
Edward Lemur1f392b82019-11-15 22:40:11 +0000489 if self.url is None:
Michael Mossd683d7c2018-06-15 05:05:17 +0000490 logging.info('Dependency(%s)._OverrideUrl(None) -> None', self._name)
Edward Lemur1f392b82019-11-15 22:40:11 +0000491 return
Michael Mossd683d7c2018-06-15 05:05:17 +0000492
Gavin Mak65c49b12023-08-24 18:06:42 +0000493 if not isinstance(self.url, str):
Michael Mossd683d7c2018-06-15 05:05:17 +0000494 raise gclient_utils.Error('Unknown url type')
495
Edward Lemur1f392b82019-11-15 22:40:11 +0000496 # self.url is a local path
497 path, at, rev = self.url.partition('@')
498 if os.path.isdir(path):
499 return
500
501 # self.url is a URL
Gavin Mak65c49b12023-08-24 18:06:42 +0000502 parsed_url = urllib.parse.urlparse(self.url)
Edward Lemur1f392b82019-11-15 22:40:11 +0000503 if parsed_url[0] or re.match(r'^\w+\@[\w\.-]+\:[\w\/]+', parsed_url[2]):
504 return
505
506 # self.url is relative to the parent's URL.
507 if not path.startswith('/'):
508 raise gclient_utils.Error(
509 'relative DEPS entry \'%s\' must begin with a slash' % self.url)
510
511 parent_url = self.parent.url
512 parent_path = self.parent.url.split('@')[0]
513 if os.path.isdir(parent_path):
514 # Parent's URL is a local path. Get parent's URL dirname and append
515 # self.url.
516 parent_path = os.path.dirname(parent_path)
517 parsed_url = parent_path + path.replace('/', os.sep) + at + rev
518 else:
519 # Parent's URL is a URL. Get parent's URL, strip from the last '/'
520 # (equivalent to unix dirname) and append self.url.
521 parsed_url = parent_url[:parent_url.rfind('/')] + self.url
522
523 logging.info('Dependency(%s)._OverrideUrl(%s) -> %s', self.name,
524 self.url, parsed_url)
525 self.set_url(parsed_url)
526
Edward Lemure7273d22018-05-10 19:13:51 -0400527 def PinToActualRevision(self):
Edward Lemure05f18d2018-06-08 17:36:53 +0000528 """Updates self.url to the revision checked out on disk."""
Michael Mossd683d7c2018-06-15 05:05:17 +0000529 if self.url is None:
530 return
Edward Lemure05f18d2018-06-08 17:36:53 +0000531 url = None
Edward Lemurbabd0982018-05-11 13:32:37 -0400532 scm = self.CreateSCM()
Dan Le Febvreb0e8e7a2023-05-18 23:36:46 +0000533 if scm.name == 'cipd':
534 revision = scm.revinfo(None, None, None)
535 package = self.GetExpandedPackageName()
536 url = '%s/p/%s/+/%s' % (scm.GetActualRemoteURL(None), package, revision)
537
Edward Lemure7273d22018-05-10 19:13:51 -0400538 if os.path.isdir(scm.checkout_path):
539 revision = scm.revinfo(None, None, None)
540 url = '%s@%s' % (gclient_utils.SplitUrlRevision(self.url)[0], revision)
Edward Lemure7273d22018-05-10 19:13:51 -0400541 self.set_url(url)
Edward Lemure7273d22018-05-10 19:13:51 -0400542
John Budorick0f7b2002018-01-19 15:46:17 -0800543 def ToLines(self):
Joanna Wang9144b672023-02-24 23:36:17 +0000544 # () -> Sequence[str]
545 """Returns strings representing the deps (info, graphviz line)"""
John Budorick0f7b2002018-01-19 15:46:17 -0800546 s = []
547 condition_part = ([' "condition": %r,' % self.condition]
548 if self.condition else [])
549 s.extend([
Michael Moss4e9b50a2018-05-23 22:35:06 -0700550 ' # %s' % self.hierarchy(include_url=False),
John Budorick0f7b2002018-01-19 15:46:17 -0800551 ' "%s": {' % (self.name,),
Edward Lemure05f18d2018-06-08 17:36:53 +0000552 ' "url": "%s",' % (self.url,),
John Budorick0f7b2002018-01-19 15:46:17 -0800553 ] + condition_part + [
554 ' },',
555 '',
556 ])
557 return s
558
maruel@chromium.org470b5432011-10-11 18:18:19 +0000559 @property
560 def requirements(self):
561 """Calculate the list of requirements."""
562 requirements = set()
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000563 # self.parent is implicitly a requirement. This will be recursive by
564 # definition.
565 if self.parent and self.parent.name:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000566 requirements.add(self.parent.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000567
568 # For a tree with at least 2 levels*, the leaf node needs to depend
569 # on the level higher up in an orderly way.
570 # This becomes messy for >2 depth as the DEPS file format is a dictionary,
571 # thus unsorted, while the .gclient format is a list thus sorted.
572 #
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000573 # Interestingly enough, the following condition only works in the case we
Quinten Yearsley925cedb2020-04-13 17:49:39 +0000574 # want: self is a 2nd level node. 3rd level node wouldn't need this since
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000575 # they already have their parent as a requirement.
maruel@chromium.org470b5432011-10-11 18:18:19 +0000576 if self.parent and self.parent.parent and not self.parent.parent.parent:
577 requirements |= set(i.name for i in self.root.dependencies if i.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000578
maruel@chromium.org470b5432011-10-11 18:18:19 +0000579 if self.name:
580 requirements |= set(
Michael Mossd683d7c2018-06-15 05:05:17 +0000581 obj.name for obj in self.root.subtree(False)
maruel@chromium.org470b5432011-10-11 18:18:19 +0000582 if (obj is not self
583 and obj.name and
584 self.name.startswith(posixpath.join(obj.name, ''))))
585 requirements = tuple(sorted(requirements))
586 logging.info('Dependency(%s).requirements = %s' % (self.name, requirements))
587 return requirements
588
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000589 @property
Edward Lemurfbb06aa2018-06-11 20:43:06 +0000590 def should_recurse(self):
591 return self._should_recurse
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000592
maruel@chromium.org470b5432011-10-11 18:18:19 +0000593 def verify_validity(self):
594 """Verifies that this Dependency is fine to add as a child of another one.
595
596 Returns True if this entry should be added, False if it is a duplicate of
597 another entry.
598 """
599 logging.info('Dependency(%s).verify_validity()' % self.name)
600 if self.name in [s.name for s in self.parent.dependencies]:
601 raise gclient_utils.Error(
602 'The same name "%s" appears multiple times in the deps section' %
603 self.name)
Michael Mossd683d7c2018-06-15 05:05:17 +0000604 if not self.should_process:
605 # Return early, no need to set requirements.
Edward Lemur7ccf2f02018-06-26 20:41:56 +0000606 return not any(d.name == self.name for d in self.root.subtree(True))
maruel@chromium.org470b5432011-10-11 18:18:19 +0000607
608 # This require a full tree traversal with locks.
Michael Mossd683d7c2018-06-15 05:05:17 +0000609 siblings = [d for d in self.root.subtree(False) if d.name == self.name]
maruel@chromium.org470b5432011-10-11 18:18:19 +0000610 for sibling in siblings:
Michael Mossd683d7c2018-06-15 05:05:17 +0000611 # Allow to have only one to be None or ''.
612 if self.url != sibling.url and bool(self.url) == bool(sibling.url):
maruel@chromium.org470b5432011-10-11 18:18:19 +0000613 raise gclient_utils.Error(
maruel@chromium.orgb848d5b2012-10-10 23:25:50 +0000614 ('Dependency %s specified more than once:\n'
615 ' %s [%s]\n'
616 'vs\n'
617 ' %s [%s]') % (
618 self.name,
619 sibling.hierarchy(),
Edward Lemure7273d22018-05-10 19:13:51 -0400620 sibling.url,
maruel@chromium.orgb848d5b2012-10-10 23:25:50 +0000621 self.hierarchy(),
Edward Lemure7273d22018-05-10 19:13:51 -0400622 self.url))
maruel@chromium.org470b5432011-10-11 18:18:19 +0000623 # In theory we could keep it as a shadow of the other one. In
624 # practice, simply ignore it.
John Budorickd94f8ea2020-03-27 15:55:24 +0000625 logging.warning("Won't process duplicate dependency %s" % sibling)
maruel@chromium.org470b5432011-10-11 18:18:19 +0000626 return False
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000627 return True
maruel@chromium.org064186c2011-09-27 23:53:33 +0000628
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200629 def _postprocess_deps(self, deps, rel_prefix):
Joanna Wang18af7ef2022-07-01 16:51:00 +0000630 # type: (Mapping[str, Mapping[str, str]], str) ->
631 # Mapping[str, Mapping[str, str]]
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200632 """Performs post-processing of deps compared to what's in the DEPS file."""
Joanna Wang18af7ef2022-07-01 16:51:00 +0000633 # If we don't need to sync, only process custom_deps, if any.
634 if not self._should_sync:
635 if not self.custom_deps:
636 return {}
Paweł Hajdan, Jr4426eaf2017-06-13 12:25:47 +0200637
Joanna Wang18af7ef2022-07-01 16:51:00 +0000638 processed_deps = {}
639 for dep_name, dep_info in self.custom_deps.items():
640 if dep_info and not dep_info.endswith('@unmanaged'):
641 if dep_name in deps:
642 # custom_deps that should override an existing deps gets applied
643 # in the Dependency itself with _OverrideUrl().
644 processed_deps[dep_name] = deps[dep_name]
645 else:
646 processed_deps[dep_name] = {'url': dep_info, 'dep_type': 'git'}
647 else:
648 processed_deps = dict(deps)
649
650 # If a line is in custom_deps, but not in the solution, we want to append
651 # this line to the solution.
652 for dep_name, dep_info in self.custom_deps.items():
Andrew Grievee8f9bdc2022-02-09 21:05:12 +0000653 # Don't add it to the solution for the values of "None" and "unmanaged"
654 # in order to force these kinds of custom_deps to act as revision
655 # overrides (via revision_overrides). Having them function as revision
656 # overrides allows them to be applied to recursive dependencies.
657 # https://crbug.com/1031185
Joanna Wang18af7ef2022-07-01 16:51:00 +0000658 if (dep_name not in processed_deps and dep_info
659 and not dep_info.endswith('@unmanaged')):
660 processed_deps[dep_name] = {'url': dep_info, 'dep_type': 'git'}
Edward Lemur16f4bad2018-05-16 16:53:49 -0400661
Michael Moss42d02c22018-02-05 10:32:24 -0800662 # Make child deps conditional on any parent conditions. This ensures that,
663 # when flattened, recursed entries have the correct restrictions, even if
664 # not explicitly set in the recursed DEPS file. For instance, if
665 # "src/ios_foo" is conditional on "checkout_ios=True", then anything
666 # recursively included by "src/ios_foo/DEPS" should also require
667 # "checkout_ios=True".
668 if self.condition:
Joanna Wang18af7ef2022-07-01 16:51:00 +0000669 for value in processed_deps.values():
Edward Lemur16f4bad2018-05-16 16:53:49 -0400670 gclient_eval.UpdateCondition(value, 'and', self.condition)
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200671
Joanna Wang18af7ef2022-07-01 16:51:00 +0000672 if not rel_prefix:
673 return processed_deps
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200674
Joanna Wang18af7ef2022-07-01 16:51:00 +0000675 logging.warning('use_relative_paths enabled.')
676 rel_deps = {}
677 for d, url in processed_deps.items():
678 # normpath is required to allow DEPS to use .. in their
679 # dependency local path.
sokcevic71b606e2023-03-16 23:28:36 +0000680 # We are following the same pattern when use_relative_paths = False,
681 # which uses slashes.
682 rel_deps[os.path.normpath(os.path.join(rel_prefix,
683 d)).replace(os.path.sep,
684 '/')] = url
Joanna Wang18af7ef2022-07-01 16:51:00 +0000685 logging.warning('Updating deps by prepending %s.', rel_prefix)
686 return rel_deps
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200687
688 def _deps_to_objects(self, deps, use_relative_paths):
Joanna Wang18af7ef2022-07-01 16:51:00 +0000689 # type: (Mapping[str, Mapping[str, str]], bool) -> Sequence[Dependency]
690 """Convert a deps dict to a list of Dependency objects."""
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200691 deps_to_add = []
Josip Sokcevic9cbe9a02021-12-01 17:25:16 +0000692 cached_conditions = {}
Marc-Antoine Ruel8e57b4b2019-10-11 01:01:36 +0000693 for name, dep_value in deps.items():
Michael Mossd683d7c2018-06-15 05:05:17 +0000694 should_process = self.should_process
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200695 if dep_value is None:
696 continue
John Budorick0f7b2002018-01-19 15:46:17 -0800697
Edward Lemur16f4bad2018-05-16 16:53:49 -0400698 condition = dep_value.get('condition')
Michael Mossd683d7c2018-06-15 05:05:17 +0000699 dep_type = dep_value.get('dep_type')
Paweł Hajdan, Jrfc6196b2017-07-27 13:15:25 +0200700
Josip Sokcevic9cbe9a02021-12-01 17:25:16 +0000701
Michael Mossd683d7c2018-06-15 05:05:17 +0000702 if condition and not self._get_option('process_all_deps', False):
Josip Sokcevic9cbe9a02021-12-01 17:25:16 +0000703 if condition not in cached_conditions:
704 cached_conditions[condition] = gclient_eval.EvaluateCondition(
705 condition, self.get_vars())
706 should_process = should_process and cached_conditions[condition]
John Budorick0f7b2002018-01-19 15:46:17 -0800707
Joey Scarr8d3925b2018-07-15 23:36:25 +0000708 # The following option is only set by the 'revinfo' command.
709 if self._get_option('ignore_dep_type', None) == dep_type:
710 continue
711
John Budorick0f7b2002018-01-19 15:46:17 -0800712 if dep_type == 'cipd':
John Budorickd3ba72b2018-03-20 12:27:42 -0700713 cipd_root = self.GetCipdRoot()
John Budorick0f7b2002018-01-19 15:46:17 -0800714 for package in dep_value.get('packages', []):
715 deps_to_add.append(
716 CipdDependency(
Edward Lemure05f18d2018-06-08 17:36:53 +0000717 parent=self,
718 name=name,
719 dep_value=package,
720 cipd_root=cipd_root,
721 custom_vars=self.custom_vars,
Michael Mossd683d7c2018-06-15 05:05:17 +0000722 should_process=should_process,
Edward Lemure05f18d2018-06-08 17:36:53 +0000723 relative=use_relative_paths,
724 condition=condition))
John Budorick0f7b2002018-01-19 15:46:17 -0800725 else:
Michael Mossd683d7c2018-06-15 05:05:17 +0000726 url = dep_value.get('url')
727 deps_to_add.append(
728 GitDependency(
729 parent=self,
730 name=name,
Aravind Vasudevan810598d2022-06-13 21:23:47 +0000731 # Update URL with scheme in protocol_override
Aravind Vasudevan5965d3e2022-06-01 21:51:30 +0000732 url=GitDependency.updateProtocol(url, self.protocol),
Edward Lemure4213702018-06-21 21:15:50 +0000733 managed=True,
Michael Mossd683d7c2018-06-15 05:05:17 +0000734 custom_deps=None,
735 custom_vars=self.custom_vars,
736 custom_hooks=None,
737 deps_file=self.recursedeps.get(name, self.deps_file),
738 should_process=should_process,
739 should_recurse=name in self.recursedeps,
740 relative=use_relative_paths,
Aravind Vasudevan5965d3e2022-06-01 21:51:30 +0000741 condition=condition,
Aravind Vasudevanb6eaed22023-07-06 20:50:42 +0000742 protocol=self.protocol,
743 git_dependencies_state=self.git_dependencies_state))
John Budorick0f7b2002018-01-19 15:46:17 -0800744
Joanna Wang18af7ef2022-07-01 16:51:00 +0000745 # TODO(crbug.com/1341285): Understand why we need this and remove
746 # it if we don't.
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200747 deps_to_add.sort(key=lambda x: x.name)
748 return deps_to_add
749
Edward Lemure05f18d2018-06-08 17:36:53 +0000750 def ParseDepsFile(self):
Joanna Wang18af7ef2022-07-01 16:51:00 +0000751 # type: () -> None
maruel@chromium.org271375b2010-06-23 19:17:38 +0000752 """Parses the DEPS file for this dependency."""
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000753 assert not self.deps_parsed
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000754 assert not self.dependencies
iannucci@chromium.org0c3f3052014-05-22 00:29:22 +0000755
756 deps_content = None
cmp@chromium.org76ce73c2014-07-02 00:13:18 +0000757
758 # First try to locate the configured deps file. If it's missing, fallback
759 # to DEPS.
760 deps_files = [self.deps_file]
761 if 'DEPS' not in deps_files:
762 deps_files.append('DEPS')
763 for deps_file in deps_files:
764 filepath = os.path.join(self.root.root_dir, self.name, deps_file)
765 if os.path.isfile(filepath):
766 logging.info(
767 'ParseDepsFile(%s): %s file found at %s', self.name, deps_file,
768 filepath)
769 break
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000770 logging.info(
cmp@chromium.org76ce73c2014-07-02 00:13:18 +0000771 'ParseDepsFile(%s): No %s file found at %s', self.name, deps_file,
772 filepath)
773
774 if os.path.isfile(filepath):
maruel@chromium.org46304292010-10-28 11:42:00 +0000775 deps_content = gclient_utils.FileRead(filepath)
cmp@chromium.org76ce73c2014-07-02 00:13:18 +0000776 logging.debug('ParseDepsFile(%s) read:\n%s', self.name, deps_content)
iannucci@chromium.org0c3f3052014-05-22 00:29:22 +0000777
778 local_scope = {}
779 if deps_content:
maruel@chromium.org46304292010-10-28 11:42:00 +0000780 try:
Edward Lesmes6c24d372018-03-28 12:52:29 -0400781 local_scope = gclient_eval.Parse(
Edward Lemur67cabcd2020-03-03 19:31:15 +0000782 deps_content, filepath, self.get_vars(), self.get_builtin_vars())
vapier@chromium.orga81a56e2015-11-11 07:56:13 +0000783 except SyntaxError as e:
maruel@chromium.org46304292010-10-28 11:42:00 +0000784 gclient_utils.SyntaxErrorToError(filepath, e)
iannucci@chromium.org0c3f3052014-05-22 00:29:22 +0000785
Aravind Vasudevanb6eaed22023-07-06 20:50:42 +0000786 if 'git_dependencies' in local_scope:
787 self.git_dependencies_state = local_scope['git_dependencies']
788
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000789 if 'allowed_hosts' in local_scope:
790 try:
791 self._allowed_hosts = frozenset(local_scope.get('allowed_hosts'))
792 except TypeError: # raised if non-iterable
793 pass
794 if not self._allowed_hosts:
795 logging.warning("allowed_hosts is specified but empty %s",
796 self._allowed_hosts)
797 raise gclient_utils.Error(
798 'ParseDepsFile(%s): allowed_hosts must be absent '
799 'or a non-empty iterable' % self.name)
800
Michael Moss848c86e2018-05-03 16:05:50 -0700801 self._gn_args_from = local_scope.get('gclient_gn_args_from')
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +0200802 self._gn_args_file = local_scope.get('gclient_gn_args_file')
803 self._gn_args = local_scope.get('gclient_gn_args', [])
Michael Moss848c86e2018-05-03 16:05:50 -0700804 # It doesn't make sense to set all of these, since setting gn_args_from to
805 # another DEPS will make gclient ignore any other local gn_args* settings.
806 assert not (self._gn_args_from and self._gn_args_file), \
807 'Only specify one of "gclient_gn_args_from" or ' \
808 '"gclient_gn_args_file + gclient_gn_args".'
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +0200809
Edward Lesmes0b899352018-03-19 21:59:55 +0000810 self._vars = local_scope.get('vars', {})
Paweł Hajdan, Jr1407d002017-08-01 20:01:01 +0200811 if self.parent:
Marc-Antoine Ruel8e57b4b2019-10-11 01:01:36 +0000812 for key, value in self.parent.get_vars().items():
Paweł Hajdan, Jr1407d002017-08-01 20:01:01 +0200813 if key in self._vars:
814 self._vars[key] = value
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200815 # Since we heavily post-process things, freeze ones which should
816 # reflect original state of DEPS.
Paweł Hajdan, Jr1407d002017-08-01 20:01:01 +0200817 self._vars = gclient_utils.freeze(self._vars)
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200818
819 # If use_relative_paths is set in the DEPS file, regenerate
820 # the dictionary using paths relative to the directory containing
821 # the DEPS file. Also update recursedeps if use_relative_paths is
822 # enabled.
823 # If the deps file doesn't set use_relative_paths, but the parent did
824 # (and therefore set self.relative on this Dependency object), then we
825 # want to modify the deps and recursedeps by prepending the parent
826 # directory of this dependency.
Corentin Wallez271a78a2020-07-12 15:41:46 +0000827 self._use_relative_paths = local_scope.get('use_relative_paths', False)
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200828 rel_prefix = None
Corentin Wallez271a78a2020-07-12 15:41:46 +0000829 if self._use_relative_paths:
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200830 rel_prefix = self.name
831 elif self._relative:
832 rel_prefix = os.path.dirname(self.name)
833
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200834 if 'recursion' in local_scope:
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200835 logging.warning(
Edward Lemurfbb06aa2018-06-11 20:43:06 +0000836 '%s: Ignoring recursion = %d.', self.name, local_scope['recursion'])
837
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200838 if 'recursedeps' in local_scope:
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200839 for ent in local_scope['recursedeps']:
Gavin Mak65c49b12023-08-24 18:06:42 +0000840 if isinstance(ent, str):
Edward Lemurfbb06aa2018-06-11 20:43:06 +0000841 self.recursedeps[ent] = self.deps_file
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200842 else: # (depname, depsfilename)
Edward Lemurfbb06aa2018-06-11 20:43:06 +0000843 self.recursedeps[ent[0]] = ent[1]
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200844 logging.warning('Found recursedeps %r.', repr(self.recursedeps))
845
846 if rel_prefix:
847 logging.warning('Updating recursedeps by prepending %s.', rel_prefix)
848 rel_deps = {}
Marc-Antoine Ruel8e57b4b2019-10-11 01:01:36 +0000849 for depname, options in self.recursedeps.items():
sokcevic71b606e2023-03-16 23:28:36 +0000850 rel_deps[os.path.normpath(os.path.join(rel_prefix, depname)).replace(
851 os.path.sep, '/')] = options
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200852 self.recursedeps = rel_deps
Michael Moss848c86e2018-05-03 16:05:50 -0700853 # To get gn_args from another DEPS, that DEPS must be recursed into.
854 if self._gn_args_from:
855 assert self.recursedeps and self._gn_args_from in self.recursedeps, \
856 'The "gclient_gn_args_from" value must be in recursedeps.'
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200857
858 # If present, save 'target_os' in the local_target_os property.
859 if 'target_os' in local_scope:
860 self.local_target_os = local_scope['target_os']
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200861
Edward Lemur16f4bad2018-05-16 16:53:49 -0400862 deps = local_scope.get('deps', {})
Aravind Vasudevanaf456fd2023-07-07 00:17:33 +0000863
Joanna Wang11b07522023-08-09 23:29:03 +0000864 # If dependencies are configured within git submodules, add them to deps.
Josip Sokcevice40f71c2023-08-29 17:35:35 +0000865 # We don't add for SYNC since we expect submodules to be in sync.
866 if self.git_dependencies_state == gclient_eval.SUBMODULES:
Joanna Wang978f43d2023-08-18 00:16:07 +0000867 deps.update(self.ParseGitSubmodules())
Aravind Vasudevanaf456fd2023-07-07 00:17:33 +0000868
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200869 deps_to_add = self._deps_to_objects(
Corentin Wallez271a78a2020-07-12 15:41:46 +0000870 self._postprocess_deps(deps, rel_prefix), self._use_relative_paths)
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000871
Corentin Walleza68660d2018-09-10 17:33:24 +0000872 # compute which working directory should be used for hooks
Michael Spang0e99b9b2020-08-12 13:34:48 +0000873 if local_scope.get('use_relative_hooks', False):
Joanna Wang4e6264c2022-06-30 19:10:43 +0000874 print('use_relative_hooks is deprecated, please remove it from '
875 '%s DEPS. (it was merged in use_relative_paths)' % self.name,
876 file=sys.stderr)
Michael Spang0e99b9b2020-08-12 13:34:48 +0000877
Corentin Walleza68660d2018-09-10 17:33:24 +0000878 hooks_cwd = self.root.root_dir
Corentin Wallez801c2022020-07-20 20:11:09 +0000879 if self._use_relative_paths:
Corentin Walleza68660d2018-09-10 17:33:24 +0000880 hooks_cwd = os.path.join(hooks_cwd, self.name)
Joanna Wang59e10112023-07-31 17:14:53 +0000881 elif self._relative:
882 hooks_cwd = os.path.join(hooks_cwd, os.path.dirname(self.name))
883 logging.warning('Using hook base working directory: %s.', hooks_cwd)
Corentin Walleza68660d2018-09-10 17:33:24 +0000884
Joanna Wang18af7ef2022-07-01 16:51:00 +0000885 # Only add all hooks if we should sync, otherwise just add custom hooks.
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000886 # override named sets of hooks by the custom hooks
887 hooks_to_run = []
Joanna Wang18af7ef2022-07-01 16:51:00 +0000888 if self._should_sync:
889 hook_names_to_suppress = [c.get('name', '') for c in self.custom_hooks]
890 for hook in local_scope.get('hooks', []):
891 if hook.get('name', '') not in hook_names_to_suppress:
892 hooks_to_run.append(hook)
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000893
894 # add the replacements and any additions
895 for hook in self.custom_hooks:
896 if 'action' in hook:
897 hooks_to_run.append(hook)
898
Joanna Wang18af7ef2022-07-01 16:51:00 +0000899 if self.should_recurse and deps_to_add:
Paweł Hajdan, Jr032d5452017-06-22 20:43:53 +0200900 self._pre_deps_hooks = [
Michael Moss42d02c22018-02-05 10:32:24 -0800901 Hook.from_dict(hook, variables=self.get_vars(), verbose=True,
Corentin Walleza68660d2018-09-10 17:33:24 +0000902 conditions=self.condition, cwd_base=hooks_cwd)
Daniel Chenga0c5f082017-10-19 13:35:19 -0700903 for hook in local_scope.get('pre_deps_hooks', [])
904 ]
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000905
Corentin Walleza68660d2018-09-10 17:33:24 +0000906 self.add_dependencies_and_close(deps_to_add, hooks_to_run,
907 hooks_cwd=hooks_cwd)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000908 logging.info('ParseDepsFile(%s) done' % self.name)
909
Aravind Vasudevanaf456fd2023-07-07 00:17:33 +0000910 def ParseGitSubmodules(self):
911 # type: () -> Mapping[str, str]
912 """
913 Parses git submodules and returns a dict of path to DEPS git url entries.
914
915 e.g {<path>: <url>@<commit_hash>}
916 """
917 cwd = os.path.join(self.root.root_dir, self.name)
918 filepath = os.path.join(cwd, '.gitmodules')
919 if not os.path.isfile(filepath):
920 logging.warning('ParseGitSubmodules(): No .gitmodules found at %s',
921 filepath)
922 return {}
923
Aravind Vasudevanaf456fd2023-07-07 00:17:33 +0000924 # Get .gitmodules fields
925 gitmodules_entries = subprocess2.check_output(
Aravind Vasudevanc91e2aa2023-08-10 23:42:11 +0000926 ['git', 'config', '--file', filepath, '-l']).decode('utf-8')
Aravind Vasudevanaf456fd2023-07-07 00:17:33 +0000927
928 gitmodules = {}
929 for entry in gitmodules_entries.splitlines():
930 key, value = entry.split('=', maxsplit=1)
931
932 # git config keys consist of section.name.key, e.g., submodule.foo.path
933 section, submodule_key = key.split('.', maxsplit=1)
934
935 # Only parse [submodule "foo"] sections from .gitmodules.
Aravind Vasudevan18c726d2023-08-09 02:39:28 +0000936 if section.strip() != 'submodule':
Aravind Vasudevanaf456fd2023-07-07 00:17:33 +0000937 continue
938
939 # The name of the submodule can contain '.', hence split from the back.
940 submodule, sub_key = submodule_key.rsplit('.', maxsplit=1)
941
942 if submodule not in gitmodules:
943 gitmodules[submodule] = {}
944
945 if sub_key in ('url', 'gclient-condition', 'path'):
946 gitmodules[submodule][sub_key] = value
947
Joanna Wang978f43d2023-08-18 00:16:07 +0000948 paths = [module['path'] for module in gitmodules.values()]
Joanna Wange36c6bb2023-08-30 22:09:59 +0000949 commit_hashes = scm_git.GIT.GetSubmoduleCommits(cwd, paths)
Joanna Wang978f43d2023-08-18 00:16:07 +0000950
Aravind Vasudevanaf456fd2023-07-07 00:17:33 +0000951 # Structure git submodules into a dict of DEPS git url entries.
952 submodules = {}
Aravind Vasudevan18c726d2023-08-09 02:39:28 +0000953 for module in gitmodules.values():
Josip Sokcevice7a7a902023-07-12 18:34:35 +0000954 if self._use_relative_paths:
955 path = module['path']
956 else:
957 path = f'{self.name}/{module["path"]}'
Joanna Wange460dac2023-08-11 19:28:45 +0000958 # TODO(crbug.com/1471685): Temporary hack. In case of applied patches
959 # where the changes are staged but not committed, any gitlinks from
960 # the patch are not returned by `git ls-tree`. The path won't be found
961 # in commit_hashes. Use a temporary '0000000' value that will be replaced
962 # with w/e is found in DEPS later.
Josip Sokcevice7a7a902023-07-12 18:34:35 +0000963 submodules[path] = {
Joanna Wange460dac2023-08-11 19:28:45 +0000964 'dep_type':
965 'git',
966 'url':
967 '{}@{}'.format(module['url'],
968 commit_hashes.get(module['path'], '0000000'))
Aravind Vasudevanaf456fd2023-07-07 00:17:33 +0000969 }
970
971 if 'gclient-condition' in module:
Josip Sokcevice7a7a902023-07-12 18:34:35 +0000972 submodules[path]['condition'] = module['gclient-condition']
Aravind Vasudevanaf456fd2023-07-07 00:17:33 +0000973
974 return submodules
975
Michael Mossd683d7c2018-06-15 05:05:17 +0000976 def _get_option(self, attr, default):
977 obj = self
978 while not hasattr(obj, '_options'):
979 obj = obj.parent
980 return getattr(obj._options, attr, default)
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +0200981
Corentin Walleza68660d2018-09-10 17:33:24 +0000982 def add_dependencies_and_close(self, deps_to_add, hooks, hooks_cwd=None):
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000983 """Adds the dependencies, hooks and mark the parsing as done."""
Corentin Walleza68660d2018-09-10 17:33:24 +0000984 if hooks_cwd == None:
985 hooks_cwd = self.root.root_dir
986
maruel@chromium.orgb9be0652011-10-14 18:05:40 +0000987 for dep in deps_to_add:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000988 if dep.verify_validity():
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000989 self.add_dependency(dep)
Daniel Chenga0c5f082017-10-19 13:35:19 -0700990 self._mark_as_parsed([
991 Hook.from_dict(
Michael Moss42d02c22018-02-05 10:32:24 -0800992 h, variables=self.get_vars(), verbose=self.root._options.verbose,
Corentin Walleza68660d2018-09-10 17:33:24 +0000993 conditions=self.condition, cwd_base=hooks_cwd)
Daniel Chenga0c5f082017-10-19 13:35:19 -0700994 for h in hooks
995 ])
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000996
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000997 def findDepsFromNotAllowedHosts(self):
Corentin Wallezaca984c2018-09-07 21:52:14 +0000998 """Returns a list of dependencies from not allowed hosts.
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000999
1000 If allowed_hosts is not set, allows all hosts and returns empty list.
1001 """
1002 if not self._allowed_hosts:
1003 return []
1004 bad_deps = []
1005 for dep in self._dependencies:
szager@chromium.orgbd772dd2014-11-05 18:43:08 +00001006 # Don't enforce this for custom_deps.
1007 if dep.name in self._custom_deps:
1008 continue
Gavin Mak65c49b12023-08-24 18:06:42 +00001009 if isinstance(dep.url, str):
1010 parsed_url = urllib.parse.urlparse(dep.url)
Michael Mossd683d7c2018-06-15 05:05:17 +00001011 if parsed_url.netloc and parsed_url.netloc not in self._allowed_hosts:
1012 bad_deps.append(dep)
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00001013 return bad_deps
1014
Edward Lemure7273d22018-05-10 19:13:51 -04001015 def FuzzyMatchUrl(self, candidates):
Joanna Wang66286612022-06-30 19:59:13 +00001016 # type: (Union[Mapping[str, str], Collection[str]]) -> Optional[str]
Edward Lesmesbb16e332018-03-30 17:54:51 -04001017 """Attempts to find this dependency in the list of candidates.
1018
Edward Lemure7273d22018-05-10 19:13:51 -04001019 It looks first for the URL of this dependency in the list of
Edward Lesmesbb16e332018-03-30 17:54:51 -04001020 candidates. If it doesn't succeed, and the URL ends in '.git', it will try
1021 looking for the URL minus '.git'. Finally it will try to look for the name
1022 of the dependency.
1023
1024 Args:
Edward Lesmesbb16e332018-03-30 17:54:51 -04001025 candidates: list, dict. The list of candidates in which to look for this
1026 dependency. It can contain URLs as above, or dependency names like
1027 "src/some/dep".
1028
1029 Returns:
1030 If this dependency is not found in the list of candidates, returns None.
1031 Otherwise, it returns under which name did we find this dependency:
1032 - Its parsed url: "https://example.com/src.git'
1033 - Its parsed url minus '.git': "https://example.com/src"
1034 - Its name: "src"
1035 """
Edward Lemure7273d22018-05-10 19:13:51 -04001036 if self.url:
1037 origin, _ = gclient_utils.SplitUrlRevision(self.url)
Joanna Wang66286612022-06-30 19:59:13 +00001038 match = gclient_utils.FuzzyMatchRepo(origin, candidates)
1039 if match:
1040 return match
Edward Lesmesbb16e332018-03-30 17:54:51 -04001041 if self.name in candidates:
1042 return self.name
1043 return None
1044
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +00001045 # Arguments number differs from overridden method
Quinten Yearsleyb2cc4a92016-12-15 13:53:26 -08001046 # pylint: disable=arguments-differ
Joanna Wang18af7ef2022-07-01 16:51:00 +00001047 def run(
1048 self,
1049 revision_overrides, # type: Mapping[str, str]
1050 command, # type: str
1051 args, # type: Sequence[str]
1052 work_queue, # type: ExecutionQueue
1053 options, # type: optparse.Values
1054 patch_refs, # type: Mapping[str, str]
Joanna Wanga84a16b2022-07-27 18:52:17 +00001055 target_branches, # type: Mapping[str, str]
1056 skip_sync_revisions, # type: Mapping[str, str]
Joanna Wang18af7ef2022-07-01 16:51:00 +00001057 ):
1058 # type: () -> None
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001059 """Runs |command| then parse the DEPS file."""
maruel@chromium.org470b5432011-10-11 18:18:19 +00001060 logging.info('Dependency(%s).run()' % self.name)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +00001061 assert self._file_list == []
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001062 # When running runhooks, there's no need to consult the SCM.
1063 # All known hooks are expected to run unconditionally regardless of working
1064 # copy state, so skip the SCM status check.
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001065 run_scm = command not in (
1066 'flatten', 'runhooks', 'recurse', 'validate', None)
iannucci@chromium.org396e1a62013-07-03 19:41:04 +00001067 file_list = [] if not options.nohooks else None
Edward Lesmesbb16e332018-03-30 17:54:51 -04001068 revision_override = revision_overrides.pop(
Edward Lemure7273d22018-05-10 19:13:51 -04001069 self.FuzzyMatchUrl(revision_overrides), None)
Edward Lemure4213702018-06-21 21:15:50 +00001070 if not revision_override and not self.managed:
1071 revision_override = 'unmanaged'
Michael Mossd683d7c2018-06-15 05:05:17 +00001072 if run_scm and self.url:
agabled437d762016-10-17 09:35:11 -07001073 # Create a shallow copy to mutate revision.
1074 options = copy.copy(options)
1075 options.revision = revision_override
1076 self._used_revision = options.revision
Edward Lemurbabd0982018-05-11 13:32:37 -04001077 self._used_scm = self.CreateSCM(out_cb=work_queue.out_cb)
Edward Lesmesc8f63d32021-06-02 23:51:53 +00001078 if command != 'update' or self.GetScmName() != 'git':
1079 self._got_revision = self._used_scm.RunCommand(command, options, args,
1080 file_list)
1081 else:
1082 try:
1083 start = time.time()
1084 sync_status = metrics_utils.SYNC_STATUS_FAILURE
1085 self._got_revision = self._used_scm.RunCommand(command, options, args,
1086 file_list)
1087 sync_status = metrics_utils.SYNC_STATUS_SUCCESS
1088 finally:
1089 url, revision = gclient_utils.SplitUrlRevision(self.url)
1090 metrics.collector.add_repeated('git_deps', {
1091 'path': self.name,
1092 'url': url,
1093 'revision': revision,
1094 'execution_time': time.time() - start,
1095 'sync_status': sync_status,
1096 })
Edward Lesmesc621b212018-03-21 20:26:56 -04001097
Joanna Wangf3edc502022-07-20 00:12:10 +00001098 if isinstance(self, GitDependency) and command == 'update':
1099 patch_repo = self.url.split('@')[0]
Josip Sokcevicd47a9c22023-06-22 05:14:35 +00001100 patch_ref = patch_refs.pop(self.FuzzyMatchUrl(patch_refs), None)
1101 target_branch = target_branches.pop(
1102 self.FuzzyMatchUrl(target_branches), None)
Joanna Wangf3edc502022-07-20 00:12:10 +00001103 if patch_ref:
1104 latest_commit = self._used_scm.apply_patch_ref(
1105 patch_repo, patch_ref, target_branch, options, file_list)
1106 else:
1107 latest_commit = self._used_scm.revinfo(None, None, None)
1108 existing_sync_commits = json.loads(
1109 os.environ.get(PREVIOUS_SYNC_COMMITS, '{}'))
1110 existing_sync_commits[self.name] = latest_commit
1111 os.environ[PREVIOUS_SYNC_COMMITS] = json.dumps(existing_sync_commits)
Edward Lesmesc621b212018-03-21 20:26:56 -04001112
agabled437d762016-10-17 09:35:11 -07001113 if file_list:
1114 file_list = [os.path.join(self.name, f.strip()) for f in file_list]
maruel@chromium.org68988972011-09-20 14:11:42 +00001115
1116 # TODO(phajdan.jr): We should know exactly when the paths are absolute.
1117 # Convert all absolute paths to relative.
iannucci@chromium.org396e1a62013-07-03 19:41:04 +00001118 for i in range(len(file_list or [])):
maruel@chromium.org68988972011-09-20 14:11:42 +00001119 # It depends on the command being executed (like runhooks vs sync).
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001120 if not os.path.isabs(file_list[i]):
maruel@chromium.org68988972011-09-20 14:11:42 +00001121 continue
1122 prefix = os.path.commonprefix(
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001123 [self.root.root_dir.lower(), file_list[i].lower()])
1124 file_list[i] = file_list[i][len(prefix):]
maruel@chromium.org68988972011-09-20 14:11:42 +00001125 # Strip any leading path separators.
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001126 while file_list[i].startswith(('\\', '/')):
1127 file_list[i] = file_list[i][1:]
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001128
Joanna Wanga84a16b2022-07-27 18:52:17 +00001129 # We must check for diffs AFTER any patch_refs have been applied.
1130 if skip_sync_revisions:
1131 skip_sync_rev = skip_sync_revisions.pop(
1132 self.FuzzyMatchUrl(skip_sync_revisions), None)
1133 self._should_sync = (skip_sync_rev is None
1134 or self._used_scm.check_diff(skip_sync_rev,
1135 files=['DEPS']))
1136 if not self._should_sync:
1137 logging.debug('Skipping sync for %s. No DEPS changes since last '
1138 'sync at %s' % (self.name, skip_sync_rev))
1139 else:
1140 logging.debug('DEPS changes detected for %s since last sync at '
1141 '%s. Not skipping deps sync' % (
1142 self.name, skip_sync_rev))
1143
Edward Lemurfbb06aa2018-06-11 20:43:06 +00001144 if self.should_recurse:
Edward Lemure05f18d2018-06-08 17:36:53 +00001145 self.ParseDepsFile()
Edward Lesmes5d6cde32018-04-12 18:32:46 -04001146
Edward Lemure7273d22018-05-10 19:13:51 -04001147 self._run_is_done(file_list or [])
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001148
Joanna Wanga84a16b2022-07-27 18:52:17 +00001149 # TODO(crbug.com/1339471): If should_recurse is false, ParseDepsFile never
1150 # gets called meaning we never fetch hooks and dependencies. So there's
1151 # no need to check should_recurse again here.
Edward Lemurfbb06aa2018-06-11 20:43:06 +00001152 if self.should_recurse:
Edward Lesmes5d6cde32018-04-12 18:32:46 -04001153 if command in ('update', 'revert') and not options.noprehooks:
1154 self.RunPreDepsHooks()
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001155 # Parse the dependencies of this dependency.
1156 for s in self.dependencies:
Michael Mossd683d7c2018-06-15 05:05:17 +00001157 if s.should_process:
1158 work_queue.enqueue(s)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001159
1160 if command == 'recurse':
agabled437d762016-10-17 09:35:11 -07001161 # Skip file only checkout.
Edward Lemurbabd0982018-05-11 13:32:37 -04001162 scm = self.GetScmName()
agabled437d762016-10-17 09:35:11 -07001163 if not options.scm or scm in options.scm:
1164 cwd = os.path.normpath(os.path.join(self.root.root_dir, self.name))
1165 # Pass in the SCM type as an env variable. Make sure we don't put
1166 # unicode strings in the environment.
1167 env = os.environ.copy()
Michael Mossd683d7c2018-06-15 05:05:17 +00001168 if scm:
1169 env['GCLIENT_SCM'] = str(scm)
1170 if self.url:
1171 env['GCLIENT_URL'] = str(self.url)
agabled437d762016-10-17 09:35:11 -07001172 env['GCLIENT_DEP_PATH'] = str(self.name)
1173 if options.prepend_dir and scm == 'git':
1174 print_stdout = False
1175 def filter_fn(line):
1176 """Git-specific path marshaling. It is optimized for git-grep."""
ilevy@chromium.org0233ac22012-11-28 20:27:02 +00001177
agabled437d762016-10-17 09:35:11 -07001178 def mod_path(git_pathspec):
1179 match = re.match('^(\\S+?:)?([^\0]+)$', git_pathspec)
1180 modified_path = os.path.join(self.name, match.group(2))
1181 branch = match.group(1) or ''
1182 return '%s%s' % (branch, modified_path)
ilevy@chromium.org0233ac22012-11-28 20:27:02 +00001183
agabled437d762016-10-17 09:35:11 -07001184 match = re.match('^Binary file ([^\0]+) matches$', line)
1185 if match:
1186 print('Binary file %s matches\n' % mod_path(match.group(1)))
1187 return
ilevy@chromium.org0233ac22012-11-28 20:27:02 +00001188
agabled437d762016-10-17 09:35:11 -07001189 items = line.split('\0')
1190 if len(items) == 2 and items[1]:
1191 print('%s : %s' % (mod_path(items[0]), items[1]))
1192 elif len(items) >= 2:
1193 # Multiple null bytes or a single trailing null byte indicate
1194 # git is likely displaying filenames only (such as with -l)
1195 print('\n'.join(mod_path(path) for path in items if path))
1196 else:
1197 print(line)
1198 else:
1199 print_stdout = True
1200 filter_fn = None
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001201
Michael Mossd683d7c2018-06-15 05:05:17 +00001202 if self.url is None:
1203 print('Skipped omitted dependency %s' % cwd, file=sys.stderr)
1204 elif os.path.isdir(cwd):
agabled437d762016-10-17 09:35:11 -07001205 try:
1206 gclient_utils.CheckCallAndFilter(
Ben Masonfbd2c632020-06-22 14:59:13 +00001207 args, cwd=cwd, env=env, print_stdout=print_stdout,
agabled437d762016-10-17 09:35:11 -07001208 filter_fn=filter_fn,
Ben Masonfbd2c632020-06-22 14:59:13 +00001209 )
agabled437d762016-10-17 09:35:11 -07001210 except subprocess2.CalledProcessError:
1211 if not options.ignore:
1212 raise
1213 else:
1214 print('Skipped missing %s' % cwd, file=sys.stderr)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001215
Edward Lemurbabd0982018-05-11 13:32:37 -04001216 def GetScmName(self):
Edward Lemurb61d3872018-05-09 18:42:47 -04001217 raise NotImplementedError()
John Budorick0f7b2002018-01-19 15:46:17 -08001218
Edward Lemurbabd0982018-05-11 13:32:37 -04001219 def CreateSCM(self, out_cb=None):
Edward Lemurb61d3872018-05-09 18:42:47 -04001220 raise NotImplementedError()
John Budorick0f7b2002018-01-19 15:46:17 -08001221
Dirk Pranke9f20d022017-10-11 18:36:54 -07001222 def HasGNArgsFile(self):
1223 return self._gn_args_file is not None
1224
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +02001225 def WriteGNArgsFile(self):
1226 lines = ['# Generated from %r' % self.deps_file]
Paweł Hajdan, Jrb495bf52017-09-25 19:33:50 +02001227 variables = self.get_vars()
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +02001228 for arg in self._gn_args:
Paweł Hajdan, Jre0214742017-09-28 12:21:01 +02001229 value = variables[arg]
Dirk Prankefdd2cd62020-06-30 23:30:47 +00001230 if isinstance(value, gclient_eval.ConstantString):
1231 value = value.value
Gavin Mak65c49b12023-08-24 18:06:42 +00001232 elif isinstance(value, str):
Paweł Hajdan, Jre0214742017-09-28 12:21:01 +02001233 value = gclient_eval.EvaluateCondition(value, variables)
Paweł Hajdan, Jrb495bf52017-09-25 19:33:50 +02001234 lines.append('%s = %s' % (arg, ToGNString(value)))
Corentin Wallez271a78a2020-07-12 15:41:46 +00001235
1236 # When use_relative_paths is set, gn_args_file is relative to this DEPS
1237 path_prefix = self.root.root_dir
1238 if self._use_relative_paths:
Lei Zhang67283c02020-07-13 21:38:44 +00001239 path_prefix = os.path.join(path_prefix, self.name)
Corentin Wallez271a78a2020-07-12 15:41:46 +00001240
1241 with open(os.path.join(path_prefix, self._gn_args_file), 'wb') as f:
Edward Lesmes05934952019-12-19 20:38:09 +00001242 f.write('\n'.join(lines).encode('utf-8', 'replace'))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001243
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001244 @gclient_utils.lockedmethod
Edward Lemure7273d22018-05-10 19:13:51 -04001245 def _run_is_done(self, file_list):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001246 # Both these are kept for hooks that are run as a separate tree traversal.
1247 self._file_list = file_list
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001248 self._processed = True
1249
szager@google.comb9a78d32012-03-13 18:46:21 +00001250 def GetHooks(self, options):
1251 """Evaluates all hooks, and return them in a flat list.
1252
1253 RunOnDeps() must have been called before to load the DEPS.
1254 """
1255 result = []
Michael Mossd683d7c2018-06-15 05:05:17 +00001256 if not self.should_process or not self.should_recurse:
1257 # Don't run the hook when it is above recursion_limit.
1258 return result
maruel@chromium.orgdc7445d2010-07-09 21:05:29 +00001259 # If "--force" was specified, run all hooks regardless of what files have
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001260 # changed.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001261 if self.deps_hooks:
agabled437d762016-10-17 09:35:11 -07001262 # TODO(maruel): If the user is using git, then we don't know
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001263 # what files have changed so we always run all hooks. It'd be nice to fix
1264 # that.
Edward Lemurbabd0982018-05-11 13:32:37 -04001265 result.extend(self.deps_hooks)
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001266 for s in self.dependencies:
szager@google.comb9a78d32012-03-13 18:46:21 +00001267 result.extend(s.GetHooks(options))
1268 return result
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001269
Daniel Chenga0c5f082017-10-19 13:35:19 -07001270 def RunHooksRecursively(self, options, progress):
szager@google.comb9a78d32012-03-13 18:46:21 +00001271 assert self.hooks_ran == False
maruel@chromium.org064186c2011-09-27 23:53:33 +00001272 self._hooks_ran = True
Daniel Chenga0c5f082017-10-19 13:35:19 -07001273 hooks = self.GetHooks(options)
1274 if progress:
1275 progress._total = len(hooks)
1276 for hook in hooks:
Daniel Chenga0c5f082017-10-19 13:35:19 -07001277 if progress:
1278 progress.update(extra=hook.name or '')
Corentin Walleza68660d2018-09-10 17:33:24 +00001279 hook.run()
Daniel Chenga0c5f082017-10-19 13:35:19 -07001280 if progress:
1281 progress.end()
maruel@chromium.orgeaf61062010-07-07 18:42:39 +00001282
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001283 def RunPreDepsHooks(self):
1284 assert self.processed
1285 assert self.deps_parsed
1286 assert not self.pre_deps_hooks_ran
1287 assert not self.hooks_ran
1288 for s in self.dependencies:
1289 assert not s.processed
1290 self._pre_deps_hooks_ran = True
1291 for hook in self.pre_deps_hooks:
Corentin Walleza68660d2018-09-10 17:33:24 +00001292 hook.run()
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001293
John Budorickd3ba72b2018-03-20 12:27:42 -07001294 def GetCipdRoot(self):
1295 if self.root is self:
1296 # Let's not infinitely recurse. If this is root and isn't an
1297 # instance of GClient, do nothing.
1298 return None
1299 return self.root.GetCipdRoot()
1300
Michael Mossd683d7c2018-06-15 05:05:17 +00001301 def subtree(self, include_all):
maruel@chromium.orgad3287e2011-10-03 19:15:10 +00001302 """Breadth first recursion excluding root node."""
maruel@chromium.orgf13a4182011-09-22 00:26:15 +00001303 dependencies = self.dependencies
1304 for d in dependencies:
Michael Mossd683d7c2018-06-15 05:05:17 +00001305 if d.should_process or include_all:
1306 yield d
maruel@chromium.orgf13a4182011-09-22 00:26:15 +00001307 for d in dependencies:
Michael Mossd683d7c2018-06-15 05:05:17 +00001308 for i in d.subtree(include_all):
maruel@chromium.orgad3287e2011-10-03 19:15:10 +00001309 yield i
1310
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001311 @gclient_utils.lockedmethod
1312 def add_dependency(self, new_dep):
1313 self._dependencies.append(new_dep)
1314
1315 @gclient_utils.lockedmethod
Paweł Hajdan, Jr357415c2017-07-24 14:35:28 +02001316 def _mark_as_parsed(self, new_hooks):
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001317 self._deps_hooks.extend(new_hooks)
1318 self._deps_parsed = True
1319
maruel@chromium.org68988972011-09-20 14:11:42 +00001320 @property
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001321 @gclient_utils.lockedmethod
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +00001322 def dependencies(self):
1323 return tuple(self._dependencies)
1324
1325 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001326 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001327 def deps_hooks(self):
1328 return tuple(self._deps_hooks)
1329
1330 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001331 @gclient_utils.lockedmethod
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001332 def pre_deps_hooks(self):
1333 return tuple(self._pre_deps_hooks)
1334
1335 @property
1336 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001337 def deps_parsed(self):
maruel@chromium.org3223edd2011-10-10 23:17:39 +00001338 """This is purely for debugging purposes. It's not used anywhere."""
maruel@chromium.org064186c2011-09-27 23:53:33 +00001339 return self._deps_parsed
1340
1341 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001342 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001343 def processed(self):
1344 return self._processed
1345
1346 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001347 @gclient_utils.lockedmethod
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001348 def pre_deps_hooks_ran(self):
1349 return self._pre_deps_hooks_ran
1350
1351 @property
1352 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001353 def hooks_ran(self):
1354 return self._hooks_ran
1355
1356 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001357 @gclient_utils.lockedmethod
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00001358 def allowed_hosts(self):
1359 return self._allowed_hosts
1360
1361 @property
1362 @gclient_utils.lockedmethod
maruel@chromium.org861fd0f2010-07-23 03:05:05 +00001363 def file_list(self):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001364 return tuple(self._file_list)
1365
1366 @property
kustermann@google.coma692e8f2013-04-18 08:32:04 +00001367 def used_scm(self):
1368 """SCMWrapper instance for this dependency or None if not processed yet."""
1369 return self._used_scm
1370
1371 @property
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00001372 @gclient_utils.lockedmethod
1373 def got_revision(self):
1374 return self._got_revision
1375
1376 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001377 def file_list_and_children(self):
1378 result = list(self.file_list)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +00001379 for d in self.dependencies:
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001380 result.extend(d.file_list_and_children)
maruel@chromium.org68988972011-09-20 14:11:42 +00001381 return tuple(result)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +00001382
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001383 def __str__(self):
1384 out = []
Edward Lemure7273d22018-05-10 19:13:51 -04001385 for i in ('name', 'url', 'custom_deps',
Michael Mossd683d7c2018-06-15 05:05:17 +00001386 'custom_vars', 'deps_hooks', 'file_list', 'should_process',
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00001387 'processed', 'hooks_ran', 'deps_parsed', 'requirements',
1388 'allowed_hosts'):
maruel@chromium.org3c74bc92011-09-15 19:17:21 +00001389 # First try the native property if it exists.
1390 if hasattr(self, '_' + i):
1391 value = getattr(self, '_' + i, False)
1392 else:
1393 value = getattr(self, i, False)
1394 if value:
1395 out.append('%s: %s' % (i, value))
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001396
1397 for d in self.dependencies:
1398 out.extend([' ' + x for x in str(d).splitlines()])
1399 out.append('')
1400 return '\n'.join(out)
1401
1402 def __repr__(self):
1403 return '%s: %s' % (self.name, self.url)
1404
Joanna Wang9144b672023-02-24 23:36:17 +00001405 def hierarchy(self, include_url=True, graphviz=False):
maruel@chromium.orgbc2d2f92010-07-22 21:26:48 +00001406 """Returns a human-readable hierarchical reference to a Dependency."""
Michael Moss4e9b50a2018-05-23 22:35:06 -07001407 def format_name(d):
1408 if include_url:
1409 return '%s(%s)' % (d.name, d.url)
Joanna Wang9144b672023-02-24 23:36:17 +00001410 return '"%s"' % d.name # quotes required for graph dot file.
1411
Michael Moss4e9b50a2018-05-23 22:35:06 -07001412 out = format_name(self)
maruel@chromium.orgbffb9042010-07-22 20:59:36 +00001413 i = self.parent
1414 while i and i.name:
Michael Moss4e9b50a2018-05-23 22:35:06 -07001415 out = '%s -> %s' % (format_name(i), out)
Joanna Wang9144b672023-02-24 23:36:17 +00001416 if graphviz:
1417 # for graphviz we just need each parent->child relationship listed once.
1418 return out
maruel@chromium.orgbffb9042010-07-22 20:59:36 +00001419 i = i.parent
1420 return out
1421
Michael Mossfe68c912018-03-22 19:19:35 -07001422 def hierarchy_data(self):
1423 """Returns a machine-readable hierarchical reference to a Dependency."""
1424 d = self
1425 out = []
1426 while d and d.name:
1427 out.insert(0, (d.name, d.url))
1428 d = d.parent
1429 return tuple(out)
1430
Edward Lemur8f8a50d2018-11-01 22:03:02 +00001431 def get_builtin_vars(self):
1432 return {
Paweł Hajdan, Jrd325eb32017-10-03 17:43:37 +02001433 'checkout_android': 'android' in self.target_os,
Benjamin Pastene6fe29412018-01-23 15:35:58 -08001434 'checkout_chromeos': 'chromeos' in self.target_os,
Paweł Hajdan, Jrd325eb32017-10-03 17:43:37 +02001435 'checkout_fuchsia': 'fuchsia' in self.target_os,
1436 'checkout_ios': 'ios' in self.target_os,
1437 'checkout_linux': 'unix' in self.target_os,
1438 'checkout_mac': 'mac' in self.target_os,
1439 'checkout_win': 'win' in self.target_os,
1440 'host_os': _detect_host_os(),
Robbie Iannucci3db32762023-07-05 19:02:44 +00001441
Tom Andersonc31ae0b2018-02-06 14:48:56 -08001442 'checkout_arm': 'arm' in self.target_cpu,
1443 'checkout_arm64': 'arm64' in self.target_cpu,
1444 'checkout_x86': 'x86' in self.target_cpu,
1445 'checkout_mips': 'mips' in self.target_cpu,
Wang Qing254538b2018-07-26 02:23:53 +00001446 'checkout_mips64': 'mips64' in self.target_cpu,
Tom Andersonc31ae0b2018-02-06 14:48:56 -08001447 'checkout_ppc': 'ppc' in self.target_cpu,
1448 'checkout_s390': 's390' in self.target_cpu,
1449 'checkout_x64': 'x64' in self.target_cpu,
1450 'host_cpu': detect_host_arch.HostArch(),
Paweł Hajdan, Jra3b67ae2017-08-30 15:18:21 +02001451 }
Edward Lemur8f8a50d2018-11-01 22:03:02 +00001452
1453 def get_vars(self):
1454 """Returns a dictionary of effective variable values
1455 (DEPS file contents with applied custom_vars overrides)."""
1456 # Variable precedence (last has highest):
Michael Mossda55cdc2018-04-06 18:37:19 -07001457 # - DEPS vars
1458 # - parents, from first to last
Edward Lemur8f8a50d2018-11-01 22:03:02 +00001459 # - built-in
Michael Mossda55cdc2018-04-06 18:37:19 -07001460 # - custom_vars overrides
Edward Lemur8f8a50d2018-11-01 22:03:02 +00001461 result = {}
Paweł Hajdan, Jra3b67ae2017-08-30 15:18:21 +02001462 result.update(self._vars)
Michael Mossda55cdc2018-04-06 18:37:19 -07001463 if self.parent:
Dirk Prankefdd2cd62020-06-30 23:30:47 +00001464 merge_vars(result, self.parent.get_vars())
Edward Lemur8f8a50d2018-11-01 22:03:02 +00001465 # Provide some built-in variables.
1466 result.update(self.get_builtin_vars())
Dirk Prankefdd2cd62020-06-30 23:30:47 +00001467 merge_vars(result, self.custom_vars)
1468
Paweł Hajdan, Jrd3790252017-07-03 21:06:24 +02001469 return result
1470
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001471
Paweł Hajdan, Jra3b67ae2017-08-30 15:18:21 +02001472_PLATFORM_MAPPING = {
1473 'cygwin': 'win',
1474 'darwin': 'mac',
1475 'linux2': 'linux',
Edward Lemuree7b9dd2019-07-20 01:29:08 +00001476 'linux': 'linux',
Paweł Hajdan, Jra3b67ae2017-08-30 15:18:21 +02001477 'win32': 'win',
Jaideep Bajwad05f3582017-09-11 12:31:48 -04001478 'aix6': 'aix',
Paweł Hajdan, Jra3b67ae2017-08-30 15:18:21 +02001479}
1480
1481
Dirk Prankefdd2cd62020-06-30 23:30:47 +00001482def merge_vars(result, new_vars):
1483 for k, v in new_vars.items():
1484 if k in result:
1485 if isinstance(result[k], gclient_eval.ConstantString):
1486 if isinstance(v, gclient_eval.ConstantString):
1487 result[k] = v
1488 else:
1489 result[k].value = v
1490 else:
1491 result[k] = v
1492 else:
1493 result[k] = v
1494
1495
Paweł Hajdan, Jra3b67ae2017-08-30 15:18:21 +02001496def _detect_host_os():
Jonas Termansenbf7eb522023-01-19 17:56:40 +00001497 if sys.platform in _PLATFORM_MAPPING:
1498 return _PLATFORM_MAPPING[sys.platform]
1499
1500 try:
1501 return os.uname().sysname.lower()
1502 except AttributeError:
1503 return sys.platform
Paweł Hajdan, Jra3b67ae2017-08-30 15:18:21 +02001504
1505
Edward Lemurb61d3872018-05-09 18:42:47 -04001506class GitDependency(Dependency):
1507 """A Dependency object that represents a single git checkout."""
1508
Aravind Vasudevan5965d3e2022-06-01 21:51:30 +00001509 @staticmethod
1510 def updateProtocol(url, protocol):
1511 """Updates given URL's protocol"""
1512 # only works on urls, skips local paths
1513 if not url or not protocol or not re.match('([a-z]+)://', url) or \
1514 re.match('file://', url):
1515 return url
1516
1517 return re.sub('^([a-z]+):', protocol + ':', url)
1518
Edward Lemurb61d3872018-05-09 18:42:47 -04001519 #override
Edward Lemurbabd0982018-05-11 13:32:37 -04001520 def GetScmName(self):
Edward Lemurb61d3872018-05-09 18:42:47 -04001521 """Always 'git'."""
Edward Lemurb61d3872018-05-09 18:42:47 -04001522 return 'git'
1523
1524 #override
Edward Lemurbabd0982018-05-11 13:32:37 -04001525 def CreateSCM(self, out_cb=None):
Edward Lemurb61d3872018-05-09 18:42:47 -04001526 """Create a Wrapper instance suitable for handling this git dependency."""
Edward Lemurbabd0982018-05-11 13:32:37 -04001527 return gclient_scm.GitWrapper(
1528 self.url, self.root.root_dir, self.name, self.outbuf, out_cb,
1529 print_outbuf=self.print_outbuf)
Edward Lemurb61d3872018-05-09 18:42:47 -04001530
1531
1532class GClient(GitDependency):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001533 """Object that represent a gclient checkout. A tree of Dependency(), one per
1534 solution or DEPS entry."""
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001535
1536 DEPS_OS_CHOICES = {
Jaideep Bajwad05f3582017-09-11 12:31:48 -04001537 "aix6": "unix",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001538 "win32": "win",
1539 "win": "win",
1540 "cygwin": "win",
1541 "darwin": "mac",
1542 "mac": "mac",
1543 "unix": "unix",
1544 "linux": "unix",
1545 "linux2": "unix",
maruel@chromium.org244e3442011-06-12 15:20:55 +00001546 "linux3": "unix",
szager@chromium.orgf8c95cd2012-06-01 22:26:52 +00001547 "android": "android",
Michael Mossc54fa812017-08-17 11:27:58 -07001548 "ios": "ios",
Sergiy Byelozyorov518bb682018-06-03 11:25:58 +02001549 "fuchsia": "fuchsia",
Michael Moss484d74f2019-02-06 01:55:43 +00001550 "chromeos": "chromeos",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001551 }
1552
1553 DEFAULT_CLIENT_FILE_TEXT = ("""\
1554solutions = [
Edward Lesmes05934952019-12-19 20:38:09 +00001555 { "name" : %(solution_name)r,
1556 "url" : %(solution_url)r,
1557 "deps_file" : %(deps_file)r,
1558 "managed" : %(managed)r,
smutae7ea312016-07-18 11:59:41 -07001559 "custom_deps" : {
1560 },
Paweł Hajdan, Jr3ba2a7c2017-10-04 19:24:46 +02001561 "custom_vars": %(custom_vars)r,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001562 },
1563]
Robert Iannuccia19649b2018-06-29 16:31:45 +00001564""")
1565
1566 DEFAULT_CLIENT_CACHE_DIR_TEXT = ("""\
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001567cache_dir = %(cache_dir)r
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001568""")
1569
Robert Iannuccia19649b2018-06-29 16:31:45 +00001570
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001571 DEFAULT_SNAPSHOT_FILE_TEXT = ("""\
1572# Snapshot generated with gclient revinfo --snapshot
Edward Lesmesc2960242018-03-06 20:50:15 -05001573solutions = %(solution_list)s
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001574""")
1575
1576 def __init__(self, root_dir, options):
maruel@chromium.org0d812442010-08-10 12:41:08 +00001577 # Do not change previous behavior. Only solution level and immediate DEPS
1578 # are processed.
1579 self._recursion_limit = 2
Edward Lemure05f18d2018-06-08 17:36:53 +00001580 super(GClient, self).__init__(
1581 parent=None,
1582 name=None,
Michael Mossd683d7c2018-06-15 05:05:17 +00001583 url=None,
Edward Lemure05f18d2018-06-08 17:36:53 +00001584 managed=True,
1585 custom_deps=None,
1586 custom_vars=None,
1587 custom_hooks=None,
1588 deps_file='unused',
Michael Mossd683d7c2018-06-15 05:05:17 +00001589 should_process=True,
Edward Lemurfbb06aa2018-06-11 20:43:06 +00001590 should_recurse=True,
Edward Lemure05f18d2018-06-08 17:36:53 +00001591 relative=None,
1592 condition=None,
1593 print_outbuf=True)
1594
maruel@chromium.org0d425922010-06-21 19:22:24 +00001595 self._options = options
maruel@chromium.org271375b2010-06-23 19:17:38 +00001596 if options.deps_os:
1597 enforced_os = options.deps_os.split(',')
1598 else:
1599 enforced_os = [self.DEPS_OS_CHOICES.get(sys.platform, 'unix')]
1600 if 'all' in enforced_os:
Edward Lemuree7b9dd2019-07-20 01:29:08 +00001601 enforced_os = self.DEPS_OS_CHOICES.values()
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001602 self._enforced_os = tuple(set(enforced_os))
Aravind Vasudevanc5f0cbb2022-01-24 23:56:57 +00001603 self._enforced_cpu = (detect_host_arch.HostArch(), )
maruel@chromium.org271375b2010-06-23 19:17:38 +00001604 self._root_dir = root_dir
John Budorickd3ba72b2018-03-20 12:27:42 -07001605 self._cipd_root = None
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001606 self.config_content = None
1607
borenet@google.com88d10082014-03-21 17:24:48 +00001608 def _CheckConfig(self):
1609 """Verify that the config matches the state of the existing checked-out
1610 solutions."""
1611 for dep in self.dependencies:
Michael Mossd683d7c2018-06-15 05:05:17 +00001612 if dep.managed and dep.url:
Edward Lemurbabd0982018-05-11 13:32:37 -04001613 scm = dep.CreateSCM()
smut@google.comd33eab32014-07-07 19:35:18 +00001614 actual_url = scm.GetActualRemoteURL(self._options)
borenet@google.com4e9be262014-04-08 19:40:30 +00001615 if actual_url and not scm.DoesRemoteURLMatch(self._options):
levarum@chromium.org27a6f9a2016-05-28 00:21:49 +00001616 mirror = scm.GetCacheMirror()
1617 if mirror:
1618 mirror_string = '%s (exists=%s)' % (mirror.mirror_path,
1619 mirror.exists())
1620 else:
1621 mirror_string = 'not used'
Raul Tambreb946b232019-03-26 14:48:46 +00001622 raise gclient_utils.Error(
1623 '''
borenet@google.com88d10082014-03-21 17:24:48 +00001624Your .gclient file seems to be broken. The requested URL is different from what
borenet@google.com0a427372014-04-02 19:12:13 +00001625is actually checked out in %(checkout_path)s.
borenet@google.com88d10082014-03-21 17:24:48 +00001626
borenet@google.com97882362014-04-07 20:06:02 +00001627The .gclient file contains:
levarum@chromium.org27a6f9a2016-05-28 00:21:49 +00001628URL: %(expected_url)s (%(expected_scm)s)
1629Cache mirror: %(mirror_string)s
borenet@google.com97882362014-04-07 20:06:02 +00001630
1631The local checkout in %(checkout_path)s reports:
1632%(actual_url)s (%(actual_scm)s)
borenet@google.com88d10082014-03-21 17:24:48 +00001633
1634You should ensure that the URL listed in .gclient is correct and either change
agabled437d762016-10-17 09:35:11 -07001635it or fix the checkout.
Josip Sokcevic14a83ae2020-05-21 01:36:34 +00001636''' % {
1637 'checkout_path': os.path.join(self.root_dir, dep.name),
1638 'expected_url': dep.url,
1639 'expected_scm': dep.GetScmName(),
1640 'mirror_string': mirror_string,
1641 'actual_url': actual_url,
1642 'actual_scm': dep.GetScmName()
1643 })
borenet@google.com88d10082014-03-21 17:24:48 +00001644
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001645 def SetConfig(self, content):
maruel@chromium.orgf13a4182011-09-22 00:26:15 +00001646 assert not self.dependencies
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001647 config_dict = {}
1648 self.config_content = content
1649 try:
1650 exec(content, config_dict)
vapier@chromium.orga81a56e2015-11-11 07:56:13 +00001651 except SyntaxError as e:
maruel@chromium.org5990f9d2010-07-07 18:02:58 +00001652 gclient_utils.SyntaxErrorToError('.gclient', e)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001653
peter@chromium.org1efccc82012-04-27 16:34:38 +00001654 # Append any target OS that is not already being enforced to the tuple.
1655 target_os = config_dict.get('target_os', [])
stuartmorgan@chromium.org18a4f6a2012-11-02 13:37:24 +00001656 if config_dict.get('target_os_only', False):
1657 self._enforced_os = tuple(set(target_os))
1658 else:
1659 self._enforced_os = tuple(set(self._enforced_os).union(target_os))
1660
Tom Andersonc31ae0b2018-02-06 14:48:56 -08001661 # Append any target CPU that is not already being enforced to the tuple.
1662 target_cpu = config_dict.get('target_cpu', [])
1663 if config_dict.get('target_cpu_only', False):
1664 self._enforced_cpu = tuple(set(target_cpu))
1665 else:
1666 self._enforced_cpu = tuple(set(self._enforced_cpu).union(target_cpu))
1667
Robert Iannuccia19649b2018-06-29 16:31:45 +00001668 cache_dir = config_dict.get('cache_dir', UNSET_CACHE_DIR)
1669 if cache_dir is not UNSET_CACHE_DIR:
1670 if cache_dir:
1671 cache_dir = os.path.join(self.root_dir, cache_dir)
1672 cache_dir = os.path.abspath(cache_dir)
Andrii Shyshkalov77ce4bd2017-11-27 12:38:18 -08001673
Robert Iannuccia19649b2018-06-29 16:31:45 +00001674 git_cache.Mirror.SetCachePath(cache_dir)
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001675
stuartmorgan@chromium.org18a4f6a2012-11-02 13:37:24 +00001676 if not target_os and config_dict.get('target_os_only', False):
1677 raise gclient_utils.Error('Can\'t use target_os_only if target_os is '
1678 'not specified')
peter@chromium.org1efccc82012-04-27 16:34:38 +00001679
Tom Andersonc31ae0b2018-02-06 14:48:56 -08001680 if not target_cpu and config_dict.get('target_cpu_only', False):
1681 raise gclient_utils.Error('Can\'t use target_cpu_only if target_cpu is '
1682 'not specified')
1683
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001684 deps_to_add = []
Michael Mossd683d7c2018-06-15 05:05:17 +00001685 for s in config_dict.get('solutions', []):
1686 try:
Aravind Vasudevanb6eaed22023-07-06 20:50:42 +00001687 deps_to_add.append(
1688 GitDependency(
1689 parent=self,
1690 name=s['name'],
1691 # Update URL with scheme in protocol_override
1692 url=GitDependency.updateProtocol(
1693 s['url'], s.get('protocol_override', None)),
1694 managed=s.get('managed', True),
1695 custom_deps=s.get('custom_deps', {}),
1696 custom_vars=s.get('custom_vars', {}),
1697 custom_hooks=s.get('custom_hooks', []),
1698 deps_file=s.get('deps_file', 'DEPS'),
1699 should_process=True,
1700 should_recurse=True,
1701 relative=None,
1702 condition=None,
1703 print_outbuf=True,
1704 # Pass protocol_override down the tree for child deps to use.
1705 protocol=s.get('protocol_override', None),
1706 git_dependencies_state=self.git_dependencies_state))
Michael Mossd683d7c2018-06-15 05:05:17 +00001707 except KeyError:
1708 raise gclient_utils.Error('Invalid .gclient file. Solution is '
1709 'incomplete: %s' % s)
Edward Lemur40764b02018-07-20 18:50:29 +00001710 metrics.collector.add(
1711 'project_urls',
1712 [
Edward Lemuraffd4102019-06-05 18:07:49 +00001713 dep.FuzzyMatchUrl(metrics_utils.KNOWN_PROJECT_URLS)
Edward Lemur40764b02018-07-20 18:50:29 +00001714 for dep in deps_to_add
1715 if dep.FuzzyMatchUrl(metrics_utils.KNOWN_PROJECT_URLS)
1716 ]
1717 )
1718
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001719 self.add_dependencies_and_close(deps_to_add, config_dict.get('hooks', []))
1720 logging.info('SetConfig() done')
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001721
1722 def SaveConfig(self):
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001723 gclient_utils.FileWrite(os.path.join(self.root_dir,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001724 self._options.config_filename),
1725 self.config_content)
1726
1727 @staticmethod
1728 def LoadCurrentConfig(options):
Joanna Wang66286612022-06-30 19:59:13 +00001729 # type: (optparse.Values) -> GClient
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001730 """Searches for and loads a .gclient file relative to the current working
Joanna Wang66286612022-06-30 19:59:13 +00001731 dir."""
szager@chromium.orge2e03202012-07-31 18:05:16 +00001732 if options.spec:
1733 client = GClient('.', options)
1734 client.SetConfig(options.spec)
1735 else:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00001736 if options.verbose:
1737 print('Looking for %s starting from %s\n' % (
1738 options.config_filename, os.getcwd()))
Nico Weber09e0b382019-03-11 16:54:07 +00001739 path = gclient_paths.FindGclientRoot(os.getcwd(), options.config_filename)
szager@chromium.orge2e03202012-07-31 18:05:16 +00001740 if not path:
Michael Achenbachb3ce73d2017-10-11 16:41:27 +02001741 if options.verbose:
1742 print('Couldn\'t find configuration file.')
szager@chromium.orge2e03202012-07-31 18:05:16 +00001743 return None
1744 client = GClient(path, options)
1745 client.SetConfig(gclient_utils.FileRead(
1746 os.path.join(path, options.config_filename)))
maruel@chromium.org69392e72011-10-13 22:09:00 +00001747
1748 if (options.revisions and
1749 len(client.dependencies) > 1 and
1750 any('@' not in r for r in options.revisions)):
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001751 print(
1752 ('You must specify the full solution name like --revision %s@%s\n'
1753 'when you have multiple solutions setup in your .gclient file.\n'
1754 'Other solutions present are: %s.') % (
maruel@chromium.org69392e72011-10-13 22:09:00 +00001755 client.dependencies[0].name,
1756 options.revisions[0],
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001757 ', '.join(s.name for s in client.dependencies[1:])),
1758 file=sys.stderr)
Joanna Wang66286612022-06-30 19:59:13 +00001759
maruel@chromium.org15804092010-09-02 17:07:37 +00001760 return client
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001761
nsylvain@google.comefc80932011-05-31 21:27:56 +00001762 def SetDefaultConfig(self, solution_name, deps_file, solution_url,
Robert Iannuccia19649b2018-06-29 16:31:45 +00001763 managed=True, cache_dir=UNSET_CACHE_DIR,
1764 custom_vars=None):
1765 text = self.DEFAULT_CLIENT_FILE_TEXT
1766 format_dict = {
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001767 'solution_name': solution_name,
1768 'solution_url': solution_url,
nsylvain@google.comefc80932011-05-31 21:27:56 +00001769 'deps_file': deps_file,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001770 'managed': managed,
Paweł Hajdan, Jr3ba2a7c2017-10-04 19:24:46 +02001771 'custom_vars': custom_vars or {},
Robert Iannuccia19649b2018-06-29 16:31:45 +00001772 }
1773
1774 if cache_dir is not UNSET_CACHE_DIR:
1775 text += self.DEFAULT_CLIENT_CACHE_DIR_TEXT
1776 format_dict['cache_dir'] = cache_dir
1777
1778 self.SetConfig(text % format_dict)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001779
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001780 def _SaveEntries(self):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001781 """Creates a .gclient_entries file to record the list of unique checkouts.
1782
1783 The .gclient_entries file lives in the same directory as .gclient.
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001784 """
1785 # Sometimes pprint.pformat will use {', sometimes it'll use { ' ... It
1786 # makes testing a bit too fun.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001787 result = 'entries = {\n'
Michael Mossd683d7c2018-06-15 05:05:17 +00001788 for entry in self.root.subtree(False):
agabled437d762016-10-17 09:35:11 -07001789 result += ' %s: %s,\n' % (pprint.pformat(entry.name),
Edward Lemure7273d22018-05-10 19:13:51 -04001790 pprint.pformat(entry.url))
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001791 result += '}\n'
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001792 file_path = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org1333cb32011-10-04 23:40:16 +00001793 logging.debug(result)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001794 gclient_utils.FileWrite(file_path, result)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001795
1796 def _ReadEntries(self):
1797 """Read the .gclient_entries file for the given client.
1798
1799 Returns:
1800 A sequence of solution names, which will be empty if there is the
1801 entries file hasn't been created yet.
1802 """
1803 scope = {}
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001804 filename = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001805 if not os.path.exists(filename):
maruel@chromium.org73e21142010-07-05 13:32:01 +00001806 return {}
maruel@chromium.org5990f9d2010-07-07 18:02:58 +00001807 try:
1808 exec(gclient_utils.FileRead(filename), scope)
vapier@chromium.orga81a56e2015-11-11 07:56:13 +00001809 except SyntaxError as e:
maruel@chromium.org5990f9d2010-07-07 18:02:58 +00001810 gclient_utils.SyntaxErrorToError(filename, e)
Aaron Gable3721ee92017-04-03 14:53:14 -07001811 return scope.get('entries', {})
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001812
Joanna Wang01870792022-08-01 19:02:57 +00001813 def _ExtractFileJsonContents(self, default_filename):
1814 # type: (str) -> Mapping[str,Any]
1815 f = os.path.join(self.root_dir, default_filename)
1816
1817 if not os.path.exists(f):
1818 logging.info('File %s does not exist.' % f)
1819 return {}
1820
1821 with open(f, 'r') as open_f:
1822 logging.info('Reading content from file %s' % f)
1823 content = open_f.read().rstrip()
1824 if content:
1825 return json.loads(content)
1826 return {}
1827
1828 def _WriteFileContents(self, default_filename, content):
1829 # type: (str, str) -> None
1830 f = os.path.join(self.root_dir, default_filename)
1831
1832 with open(f, 'w') as open_f:
1833 logging.info('Writing to file %s' % f)
1834 open_f.write(content)
1835
Joanna Wang66286612022-06-30 19:59:13 +00001836 def _EnforceSkipSyncRevisions(self, patch_refs):
1837 # type: (Mapping[str, str]) -> Mapping[str, str]
1838 """Checks for and enforces revisions for skipping deps syncing."""
Joanna Wang01870792022-08-01 19:02:57 +00001839 previous_sync_commits = self._ExtractFileJsonContents(
1840 PREVIOUS_SYNC_COMMITS_FILE)
Joanna Wangf3edc502022-07-20 00:12:10 +00001841
1842 if not previous_sync_commits:
Joanna Wang66286612022-06-30 19:59:13 +00001843 return {}
1844
1845 # Current `self.dependencies` only contain solutions. If a patch_ref is
1846 # not for a solution, then it is for a solution's dependency or recursed
Joanna Wangf3edc502022-07-20 00:12:10 +00001847 # dependency which we cannot support while skipping sync.
Joanna Wang66286612022-06-30 19:59:13 +00001848 if patch_refs:
1849 unclaimed_prs = []
1850 candidates = []
1851 for dep in self.dependencies:
1852 origin, _ = gclient_utils.SplitUrlRevision(dep.url)
1853 candidates.extend([origin, dep.name])
1854 for patch_repo in patch_refs:
1855 if not gclient_utils.FuzzyMatchRepo(patch_repo, candidates):
1856 unclaimed_prs.append(patch_repo)
1857 if unclaimed_prs:
1858 print(
Joanna Wangf3edc502022-07-20 00:12:10 +00001859 'We cannot skip syncs when there are --patch-refs flags for '
1860 'non-solution dependencies. To skip syncing, remove patch_refs '
1861 'for: \n%s' % '\n'.join(unclaimed_prs))
Joanna Wang66286612022-06-30 19:59:13 +00001862 return {}
1863
1864 # We cannot skip syncing if there are custom_vars that differ from the
1865 # previous run's custom_vars.
Joanna Wang01870792022-08-01 19:02:57 +00001866 previous_custom_vars = self._ExtractFileJsonContents(
1867 PREVIOUS_CUSTOM_VARS_FILE)
1868
Joanna Wang66286612022-06-30 19:59:13 +00001869 cvs_by_name = {s.name: s.custom_vars for s in self.dependencies}
Joanna Wangf3edc502022-07-20 00:12:10 +00001870
Joanna Wang66286612022-06-30 19:59:13 +00001871 skip_sync_revisions = {}
Joanna Wangf3edc502022-07-20 00:12:10 +00001872 for name, commit in previous_sync_commits.items():
Joanna Wang01870792022-08-01 19:02:57 +00001873 previous_vars = previous_custom_vars.get(name)
1874 if previous_vars == cvs_by_name.get(name) or (not previous_vars and
1875 not cvs_by_name.get(name)):
Joanna Wangf3edc502022-07-20 00:12:10 +00001876 skip_sync_revisions[name] = commit
Joanna Wang66286612022-06-30 19:59:13 +00001877 else:
Joanna Wangf3edc502022-07-20 00:12:10 +00001878 print('We cannot skip syncs when custom_vars for solutions have '
1879 'changed since the last sync run on this machine.\n'
1880 '\nRemoving skip_sync_revision for:\n'
Joanna Wang66286612022-06-30 19:59:13 +00001881 'solution: %s, current: %r, previous: %r.' %
1882 (name, cvs_by_name.get(name), previous_vars))
Joanna Wanga84a16b2022-07-27 18:52:17 +00001883 print('no-sync experiment enabled with %r' % skip_sync_revisions)
Joanna Wang66286612022-06-30 19:59:13 +00001884 return skip_sync_revisions
1885
1886 # TODO(crbug.com/1340695): Remove handling revisions without '@'.
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001887 def _EnforceRevisions(self):
maruel@chromium.org918a9ae2010-05-28 15:50:30 +00001888 """Checks for revision overrides."""
1889 revision_overrides = {}
smutae7ea312016-07-18 11:59:41 -07001890 if self._options.head:
1891 return revision_overrides
joi@chromium.org792ea882010-11-10 02:37:27 +00001892 if not self._options.revisions:
maruel@chromium.org307d1792010-05-31 20:03:13 +00001893 return revision_overrides
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001894 solutions_names = [s.name for s in self.dependencies]
Joanna Wanga84a16b2022-07-27 18:52:17 +00001895 for index, revision in enumerate(self._options.revisions):
smutae7ea312016-07-18 11:59:41 -07001896 if not '@' in revision:
maruel@chromium.org307d1792010-05-31 20:03:13 +00001897 # Support for --revision 123
smutae7ea312016-07-18 11:59:41 -07001898 revision = '%s@%s' % (solutions_names[index], revision)
1899 name, rev = revision.split('@', 1)
szager@chromium.org4ad264b2014-05-20 04:43:47 +00001900 revision_overrides[name] = rev
maruel@chromium.org918a9ae2010-05-28 15:50:30 +00001901 return revision_overrides
1902
Edward Lemur6a4e31b2018-08-10 19:59:02 +00001903 def _EnforcePatchRefsAndBranches(self):
Joanna Wang66286612022-06-30 19:59:13 +00001904 # type: () -> Tuple[Mapping[str, str], Mapping[str, str]]
Edward Lesmesc621b212018-03-21 20:26:56 -04001905 """Checks for patch refs."""
1906 patch_refs = {}
Edward Lemur6a4e31b2018-08-10 19:59:02 +00001907 target_branches = {}
Edward Lesmesc621b212018-03-21 20:26:56 -04001908 if not self._options.patch_refs:
Edward Lemur6a4e31b2018-08-10 19:59:02 +00001909 return patch_refs, target_branches
Edward Lesmesc621b212018-03-21 20:26:56 -04001910 for given_patch_ref in self._options.patch_refs:
1911 patch_repo, _, patch_ref = given_patch_ref.partition('@')
Edward Lemur4c5c8ab2019-06-07 15:58:13 +00001912 if not patch_repo or not patch_ref or ':' not in patch_ref:
Edward Lesmesc621b212018-03-21 20:26:56 -04001913 raise gclient_utils.Error(
1914 'Wrong revision format: %s should be of the form '
Edward Lemur4c5c8ab2019-06-07 15:58:13 +00001915 'patch_repo@target_branch:patch_ref.' % given_patch_ref)
1916 target_branch, _, patch_ref = patch_ref.partition(':')
1917 target_branches[patch_repo] = target_branch
Edward Lesmesc621b212018-03-21 20:26:56 -04001918 patch_refs[patch_repo] = patch_ref
Edward Lemur6a4e31b2018-08-10 19:59:02 +00001919 return patch_refs, target_branches
Edward Lesmesc621b212018-03-21 20:26:56 -04001920
Edward Lemur5b1fa942018-10-04 23:22:09 +00001921 def _RemoveUnversionedGitDirs(self):
1922 """Remove directories that are no longer part of the checkout.
1923
1924 Notify the user if there is an orphaned entry in their working copy.
1925 Only delete the directory if there are no changes in it, and
1926 delete_unversioned_trees is set to true.
Josip Sokcevic1b8211f2022-09-30 17:46:53 +00001927
1928 Returns CIPD packages that are no longer versioned.
Edward Lemur5b1fa942018-10-04 23:22:09 +00001929 """
1930
Joanna Wang01870792022-08-01 19:02:57 +00001931 entry_names_and_sync = [(i.name, i._should_sync)
1932 for i in self.root.subtree(False) if i.url]
1933 entries = []
1934 if entry_names_and_sync:
1935 entries, _ = zip(*entry_names_and_sync)
Edward Lemur5b1fa942018-10-04 23:22:09 +00001936 full_entries = [os.path.join(self.root_dir, e.replace('/', os.path.sep))
1937 for e in entries]
Joanna Wang01870792022-08-01 19:02:57 +00001938 no_sync_entries = [
1939 name for name, should_sync in entry_names_and_sync if not should_sync
1940 ]
Edward Lemur5b1fa942018-10-04 23:22:09 +00001941
Josip Sokcevic1b8211f2022-09-30 17:46:53 +00001942 removed_cipd_entries = []
Marc-Antoine Ruel8e57b4b2019-10-11 01:01:36 +00001943 for entry, prev_url in self._ReadEntries().items():
Edward Lemur5b1fa942018-10-04 23:22:09 +00001944 if not prev_url:
1945 # entry must have been overridden via .gclient custom_deps
1946 continue
Joanna Wang01870792022-08-01 19:02:57 +00001947 if any(entry.startswith(sln) for sln in no_sync_entries):
1948 # Dependencies of solutions that skipped syncing would not
1949 # show up in `entries`.
1950 continue
Josip Sokcevic1b8211f2022-09-30 17:46:53 +00001951 if (':' in entry):
1952 # This is a cipd package. Don't clean it up, but prepare for return
1953 if entry not in entries:
1954 removed_cipd_entries.append(entry)
1955 continue
Edward Lemur5b1fa942018-10-04 23:22:09 +00001956 # Fix path separator on Windows.
1957 entry_fixed = entry.replace('/', os.path.sep)
1958 e_dir = os.path.join(self.root_dir, entry_fixed)
1959 # Use entry and not entry_fixed there.
1960 if (entry not in entries and
1961 (not any(path.startswith(entry + '/') for path in entries)) and
1962 os.path.exists(e_dir)):
1963 # The entry has been removed from DEPS.
1964 scm = gclient_scm.GitWrapper(
1965 prev_url, self.root_dir, entry_fixed, self.outbuf)
1966
1967 # Check to see if this directory is now part of a higher-up checkout.
1968 scm_root = None
1969 try:
1970 scm_root = gclient_scm.scm.GIT.GetCheckoutRoot(scm.checkout_path)
1971 except subprocess2.CalledProcessError:
1972 pass
1973 if not scm_root:
1974 logging.warning('Could not find checkout root for %s. Unable to '
1975 'determine whether it is part of a higher-level '
1976 'checkout, so not removing.' % entry)
1977 continue
1978
1979 # This is to handle the case of third_party/WebKit migrating from
1980 # being a DEPS entry to being part of the main project.
1981 # If the subproject is a Git project, we need to remove its .git
1982 # folder. Otherwise git operations on that folder will have different
1983 # effects depending on the current working directory.
1984 if os.path.abspath(scm_root) == os.path.abspath(e_dir):
1985 e_par_dir = os.path.join(e_dir, os.pardir)
1986 if gclient_scm.scm.GIT.IsInsideWorkTree(e_par_dir):
1987 par_scm_root = gclient_scm.scm.GIT.GetCheckoutRoot(e_par_dir)
1988 # rel_e_dir : relative path of entry w.r.t. its parent repo.
1989 rel_e_dir = os.path.relpath(e_dir, par_scm_root)
1990 if gclient_scm.scm.GIT.IsDirectoryVersioned(
1991 par_scm_root, rel_e_dir):
1992 save_dir = scm.GetGitBackupDirPath()
1993 # Remove any eventual stale backup dir for the same project.
1994 if os.path.exists(save_dir):
1995 gclient_utils.rmtree(save_dir)
1996 os.rename(os.path.join(e_dir, '.git'), save_dir)
1997 # When switching between the two states (entry/ is a subproject
1998 # -> entry/ is part of the outer project), it is very likely
1999 # that some files are changed in the checkout, unless we are
2000 # jumping *exactly* across the commit which changed just DEPS.
2001 # In such case we want to cleanup any eventual stale files
2002 # (coming from the old subproject) in order to end up with a
2003 # clean checkout.
2004 gclient_scm.scm.GIT.CleanupDir(par_scm_root, rel_e_dir)
2005 assert not os.path.exists(os.path.join(e_dir, '.git'))
Raul Tambre80ee78e2019-05-06 22:41:05 +00002006 print('\nWARNING: \'%s\' has been moved from DEPS to a higher '
2007 'level checkout. The git folder containing all the local'
2008 ' branches has been saved to %s.\n'
2009 'If you don\'t care about its state you can safely '
2010 'remove that folder to free up space.' % (entry, save_dir))
Edward Lemur5b1fa942018-10-04 23:22:09 +00002011 continue
2012
2013 if scm_root in full_entries:
2014 logging.info('%s is part of a higher level checkout, not removing',
2015 scm.GetCheckoutRoot())
2016 continue
2017
2018 file_list = []
2019 scm.status(self._options, [], file_list)
2020 modified_files = file_list != []
2021 if (not self._options.delete_unversioned_trees or
2022 (modified_files and not self._options.force)):
2023 # There are modified files in this entry. Keep warning until
2024 # removed.
Henrique Ferreiroe72279d2019-04-17 12:01:50 +00002025 self.add_dependency(
2026 GitDependency(
2027 parent=self,
2028 name=entry,
Aravind Vasudevan810598d2022-06-13 21:23:47 +00002029 # Update URL with scheme in protocol_override
Aravind Vasudevan5965d3e2022-06-01 21:51:30 +00002030 url=GitDependency.updateProtocol(prev_url, self.protocol),
Henrique Ferreiroe72279d2019-04-17 12:01:50 +00002031 managed=False,
2032 custom_deps={},
2033 custom_vars={},
2034 custom_hooks=[],
2035 deps_file=None,
2036 should_process=True,
2037 should_recurse=False,
2038 relative=None,
Aravind Vasudevan5965d3e2022-06-01 21:51:30 +00002039 condition=None,
Aravind Vasudevanb6eaed22023-07-06 20:50:42 +00002040 protocol=self.protocol,
2041 git_dependencies_state=self.git_dependencies_state))
Anthony Politobb457342019-11-15 22:26:01 +00002042 if modified_files and self._options.delete_unversioned_trees:
2043 print('\nWARNING: \'%s\' is no longer part of this client.\n'
2044 'Despite running \'gclient sync -D\' no action was taken '
2045 'as there are modifications.\nIt is recommended you revert '
2046 'all changes or run \'gclient sync -D --force\' next '
2047 'time.' % entry_fixed)
2048 else:
2049 print('\nWARNING: \'%s\' is no longer part of this client.\n'
2050 'It is recommended that you manually remove it or use '
2051 '\'gclient sync -D\' next time.' % entry_fixed)
Edward Lemur5b1fa942018-10-04 23:22:09 +00002052 else:
2053 # Delete the entry
2054 print('\n________ deleting \'%s\' in \'%s\'' % (
2055 entry_fixed, self.root_dir))
2056 gclient_utils.rmtree(e_dir)
2057 # record the current list of entries for next time
2058 self._SaveEntries()
Josip Sokcevic1b8211f2022-09-30 17:46:53 +00002059 return removed_cipd_entries
Edward Lemur5b1fa942018-10-04 23:22:09 +00002060
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00002061 def RunOnDeps(self, command, args, ignore_requirements=False, progress=True):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002062 """Runs a command on each dependency in a client and its dependencies.
2063
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002064 Args:
2065 command: The command to use (e.g., 'status' or 'diff')
2066 args: list of str - extra arguments to add to the command line.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002067 """
Michael Mossd683d7c2018-06-15 05:05:17 +00002068 if not self.dependencies:
2069 raise gclient_utils.Error('No solution specified')
2070
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00002071 revision_overrides = {}
Edward Lesmesc621b212018-03-21 20:26:56 -04002072 patch_refs = {}
Edward Lemur6a4e31b2018-08-10 19:59:02 +00002073 target_branches = {}
Joanna Wanga84a16b2022-07-27 18:52:17 +00002074 skip_sync_revisions = {}
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00002075 # It's unnecessary to check for revision overrides for 'recurse'.
2076 # Save a few seconds by not calling _EnforceRevisions() in that case.
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +02002077 if command not in ('diff', 'recurse', 'runhooks', 'status', 'revert',
2078 'validate'):
szager@chromium.org5273b8a2014-08-21 15:10:10 +00002079 self._CheckConfig()
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00002080 revision_overrides = self._EnforceRevisions()
Edward Lesmesc621b212018-03-21 20:26:56 -04002081
2082 if command == 'update':
Edward Lemur6a4e31b2018-08-10 19:59:02 +00002083 patch_refs, target_branches = self._EnforcePatchRefsAndBranches()
Joanna Wanga84a16b2022-07-27 18:52:17 +00002084 if NO_SYNC_EXPERIMENT in self._options.experiments:
2085 skip_sync_revisions = self._EnforceSkipSyncRevisions(patch_refs)
Joanna Wang66286612022-06-30 19:59:13 +00002086
Joanna Wang01870792022-08-01 19:02:57 +00002087 # Store solutions' custom_vars on memory to compare in the next run.
2088 # All dependencies added later are inherited from the current
2089 # self.dependencies.
2090 custom_vars = {
2091 dep.name: dep.custom_vars
2092 for dep in self.dependencies if dep.custom_vars
2093 }
2094 if custom_vars:
2095 self._WriteFileContents(PREVIOUS_CUSTOM_VARS_FILE,
2096 json.dumps(custom_vars))
Joanna Wangf3edc502022-07-20 00:12:10 +00002097
Daniel Chenga21b5b32017-10-19 20:07:48 +00002098 # Disable progress for non-tty stdout.
Daniel Chenga0c5f082017-10-19 13:35:19 -07002099 should_show_progress = (
2100 setup_color.IS_TTY and not self._options.verbose and progress)
2101 pm = None
2102 if should_show_progress:
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00002103 if command in ('update', 'revert'):
2104 pm = Progress('Syncing projects', 1)
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +02002105 elif command in ('recurse', 'validate'):
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00002106 pm = Progress(' '.join(args), 1)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00002107 work_queue = gclient_utils.ExecutionQueue(
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00002108 self._options.jobs, pm, ignore_requirements=ignore_requirements,
2109 verbose=self._options.verbose)
maruel@chromium.org049bced2010-08-12 13:37:20 +00002110 for s in self.dependencies:
Michael Mossd683d7c2018-06-15 05:05:17 +00002111 if s.should_process:
2112 work_queue.enqueue(s)
Joanna Wanga84a16b2022-07-27 18:52:17 +00002113 work_queue.flush(revision_overrides,
2114 command,
2115 args,
2116 options=self._options,
2117 patch_refs=patch_refs,
2118 target_branches=target_branches,
Josip Sokcevicd47a9c22023-06-22 05:14:35 +00002119 skip_sync_revisions=skip_sync_revisions)
Edward Lesmesc621b212018-03-21 20:26:56 -04002120
szager@chromium.org4ad264b2014-05-20 04:43:47 +00002121 if revision_overrides:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002122 print('Please fix your script, having invalid --revision flags will soon '
Edward Lesmesc621b212018-03-21 20:26:56 -04002123 'be considered an error.', file=sys.stderr)
2124
Josip Sokcevicd47a9c22023-06-22 05:14:35 +00002125 if patch_refs:
Edward Lesmesc621b212018-03-21 20:26:56 -04002126 raise gclient_utils.Error(
2127 'The following --patch-ref flags were not used. Please fix it:\n%s' %
2128 ('\n'.join(
2129 patch_repo + '@' + patch_ref
Marc-Antoine Ruel8e57b4b2019-10-11 01:01:36 +00002130 for patch_repo, patch_ref in patch_refs.items())))
piman@chromium.org6f363722010-04-27 00:41:09 +00002131
Aravind Vasudevanb8164182023-08-25 21:49:12 +00002132 # TODO(crbug.com/1475405): Warn users if the project uses submodules and
2133 # they have fsmonitor enabled.
2134 if command == 'update':
2135 # Check if any of the root dependency have submodules.
2136 is_submoduled = any(
2137 map(
2138 lambda d: d.git_dependencies_state in
2139 (gclient_eval.SUBMODULES, gclient_eval.SYNC), self.dependencies))
2140 if is_submoduled:
2141 git_common.warn_submodule()
2142
Dirk Pranke9f20d022017-10-11 18:36:54 -07002143 # Once all the dependencies have been processed, it's now safe to write
Michael Moss848c86e2018-05-03 16:05:50 -07002144 # out the gn_args_file and run the hooks.
Josip Sokcevic1b8211f2022-09-30 17:46:53 +00002145 removed_cipd_entries = []
Dirk Pranke9f20d022017-10-11 18:36:54 -07002146 if command == 'update':
Ergün Erdoğmuş28190a22022-06-22 08:50:54 +00002147 for dependency in self.dependencies:
2148 gn_args_dep = dependency
2149 if gn_args_dep._gn_args_from:
2150 deps_map = {dep.name: dep for dep in gn_args_dep.dependencies}
2151 gn_args_dep = deps_map.get(gn_args_dep._gn_args_from)
2152 if gn_args_dep and gn_args_dep.HasGNArgsFile():
2153 gn_args_dep.WriteGNArgsFile()
Dirk Pranke9f20d022017-10-11 18:36:54 -07002154
Josip Sokcevic1b8211f2022-09-30 17:46:53 +00002155 removed_cipd_entries = self._RemoveUnversionedGitDirs()
Edward Lemur647e1e72018-09-19 18:15:29 +00002156
2157 # Sync CIPD dependencies once removed deps are deleted. In case a git
2158 # dependency was moved to CIPD, we want to remove the old git directory
2159 # first and then sync the CIPD dep.
2160 if self._cipd_root:
2161 self._cipd_root.run(command)
Josip Sokcevic1b8211f2022-09-30 17:46:53 +00002162 # It's possible that CIPD removed some entries that are now part of git
2163 # worktree. Try to checkout those directories
2164 if removed_cipd_entries:
2165 for cipd_entry in removed_cipd_entries:
2166 cwd = os.path.join(self._root_dir, cipd_entry.split(':')[0])
2167 cwd, tail = os.path.split(cwd)
2168 if cwd:
2169 try:
2170 gclient_scm.scm.GIT.Capture(['checkout', tail], cwd=cwd)
2171 except subprocess2.CalledProcessError:
2172 pass
Edward Lemur647e1e72018-09-19 18:15:29 +00002173
Edward Lemur5b1fa942018-10-04 23:22:09 +00002174 if not self._options.nohooks:
2175 if should_show_progress:
2176 pm = Progress('Running hooks', 1)
2177 self.RunHooksRecursively(self._options, pm)
2178
Joanna Wang01870792022-08-01 19:02:57 +00002179 self._WriteFileContents(PREVIOUS_SYNC_COMMITS_FILE,
2180 os.environ.get(PREVIOUS_SYNC_COMMITS, '{}'))
2181
maruel@chromium.org17cdf762010-05-28 17:30:52 +00002182 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002183
2184 def PrintRevInfo(self):
maruel@chromium.org54a07a22010-06-14 19:07:39 +00002185 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +00002186 raise gclient_utils.Error('No solution specified')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00002187 # Load all the settings.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00002188 work_queue = gclient_utils.ExecutionQueue(
2189 self._options.jobs, None, False, verbose=self._options.verbose)
maruel@chromium.org049bced2010-08-12 13:37:20 +00002190 for s in self.dependencies:
Michael Mossd683d7c2018-06-15 05:05:17 +00002191 if s.should_process:
2192 work_queue.enqueue(s)
Joanna Wanga84a16b2022-07-27 18:52:17 +00002193 work_queue.flush({},
2194 None, [],
2195 options=self._options,
2196 patch_refs=None,
2197 target_branches=None,
2198 skip_sync_revisions=None)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002199
Michael Mossd683d7c2018-06-15 05:05:17 +00002200 def ShouldPrintRevision(dep):
Edward Lesmesbb16e332018-03-30 17:54:51 -04002201 return (not self._options.filter
Edward Lemure7273d22018-05-10 19:13:51 -04002202 or dep.FuzzyMatchUrl(self._options.filter))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002203
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00002204 if self._options.snapshot:
Michael Mossd683d7c2018-06-15 05:05:17 +00002205 json_output = []
2206 # First level at .gclient
2207 for d in self.dependencies:
2208 entries = {}
2209 def GrabDeps(dep):
2210 """Recursively grab dependencies."""
Aravind Vasudevanc5f0cbb2022-01-24 23:56:57 +00002211 for rec_d in dep.dependencies:
2212 rec_d.PinToActualRevision()
2213 if ShouldPrintRevision(rec_d):
2214 entries[rec_d.name] = rec_d.url
2215 GrabDeps(rec_d)
2216
Michael Mossd683d7c2018-06-15 05:05:17 +00002217 GrabDeps(d)
2218 json_output.append({
2219 'name': d.name,
2220 'solution_url': d.url,
2221 'deps_file': d.deps_file,
2222 'managed': d.managed,
2223 'custom_deps': entries,
2224 })
2225 if self._options.output_json == '-':
2226 print(json.dumps(json_output, indent=2, separators=(',', ': ')))
2227 elif self._options.output_json:
2228 with open(self._options.output_json, 'w') as f:
2229 json.dump(json_output, f)
2230 else:
2231 # Print the snapshot configuration file
2232 print(self.DEFAULT_SNAPSHOT_FILE_TEXT % {
2233 'solution_list': pprint.pformat(json_output, indent=2),
2234 })
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +00002235 else:
Michael Mossd683d7c2018-06-15 05:05:17 +00002236 entries = {}
2237 for d in self.root.subtree(False):
2238 if self._options.actual:
2239 d.PinToActualRevision()
2240 if ShouldPrintRevision(d):
2241 entries[d.name] = d.url
2242 if self._options.output_json:
2243 json_output = {
2244 name: {
2245 'url': rev.split('@')[0] if rev else None,
2246 'rev': rev.split('@')[1] if rev and '@' in rev else None,
2247 }
Marc-Antoine Ruel8e57b4b2019-10-11 01:01:36 +00002248 for name, rev in entries.items()
Michael Mossd683d7c2018-06-15 05:05:17 +00002249 }
2250 if self._options.output_json == '-':
2251 print(json.dumps(json_output, indent=2, separators=(',', ': ')))
2252 else:
2253 with open(self._options.output_json, 'w') as f:
2254 json.dump(json_output, f)
2255 else:
2256 keys = sorted(entries.keys())
2257 for x in keys:
2258 print('%s: %s' % (x, entries[x]))
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +00002259 logging.info(str(self))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002260
Edward Lemure05f18d2018-06-08 17:36:53 +00002261 def ParseDepsFile(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00002262 """No DEPS to parse for a .gclient file."""
maruel@chromium.org049bced2010-08-12 13:37:20 +00002263 raise gclient_utils.Error('Internal error')
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00002264
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002265 def PrintLocationAndContents(self):
2266 # Print out the .gclient file. This is longer than if we just printed the
2267 # client dict, but more legible, and it might contain helpful comments.
2268 print('Loaded .gclient config in %s:\n%s' % (
2269 self.root_dir, self.config_content))
2270
John Budorickd3ba72b2018-03-20 12:27:42 -07002271 def GetCipdRoot(self):
2272 if not self._cipd_root:
2273 self._cipd_root = gclient_scm.CipdRoot(
2274 self.root_dir,
2275 # TODO(jbudorick): Support other service URLs as necessary.
2276 # Service URLs should be constant over the scope of a cipd
2277 # root, so a var per DEPS file specifying the service URL
2278 # should suffice.
2279 'https://chrome-infra-packages.appspot.com')
2280 return self._cipd_root
2281
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00002282 @property
maruel@chromium.org75a59272010-06-11 22:34:03 +00002283 def root_dir(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00002284 """Root directory of gclient checkout."""
maruel@chromium.org75a59272010-06-11 22:34:03 +00002285 return self._root_dir
2286
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00002287 @property
maruel@chromium.org271375b2010-06-23 19:17:38 +00002288 def enforced_os(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00002289 """What deps_os entries that are to be parsed."""
maruel@chromium.org271375b2010-06-23 19:17:38 +00002290 return self._enforced_os
2291
maruel@chromium.org68988972011-09-20 14:11:42 +00002292 @property
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +00002293 def target_os(self):
2294 return self._enforced_os
2295
Tom Andersonc31ae0b2018-02-06 14:48:56 -08002296 @property
2297 def target_cpu(self):
2298 return self._enforced_cpu
2299
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002300
John Budorick0f7b2002018-01-19 15:46:17 -08002301class CipdDependency(Dependency):
2302 """A Dependency object that represents a single CIPD package."""
2303
Michael Mossd683d7c2018-06-15 05:05:17 +00002304 def __init__(
2305 self, parent, name, dep_value, cipd_root,
2306 custom_vars, should_process, relative, condition):
Dan Le Febvref2da9062023-05-03 00:35:09 +00002307 package = dep_value['package']
John Budorick0f7b2002018-01-19 15:46:17 -08002308 version = dep_value['version']
Gavin Mak65c49b12023-08-24 18:06:42 +00002309 url = urllib.parse.urljoin(cipd_root.service_url,
2310 '%s@%s' % (package, version))
John Budorick0f7b2002018-01-19 15:46:17 -08002311 super(CipdDependency, self).__init__(
Edward Lemure05f18d2018-06-08 17:36:53 +00002312 parent=parent,
2313 name=name + ':' + package,
2314 url=url,
2315 managed=None,
2316 custom_deps=None,
2317 custom_vars=custom_vars,
2318 custom_hooks=None,
2319 deps_file=None,
Michael Mossd683d7c2018-06-15 05:05:17 +00002320 should_process=should_process,
Edward Lemurfbb06aa2018-06-11 20:43:06 +00002321 should_recurse=False,
Edward Lemure05f18d2018-06-08 17:36:53 +00002322 relative=relative,
2323 condition=condition)
John Budorickd3ba72b2018-03-20 12:27:42 -07002324 self._cipd_package = None
John Budorick0f7b2002018-01-19 15:46:17 -08002325 self._cipd_root = cipd_root
John Budorick4099daa2018-06-21 19:22:10 +00002326 # CIPD wants /-separated paths, even on Windows.
2327 native_subdir_path = os.path.relpath(
Shenghua Zhang6f830312018-02-26 11:45:07 -08002328 os.path.join(self.root.root_dir, name), cipd_root.root_dir)
John Budorick4099daa2018-06-21 19:22:10 +00002329 self._cipd_subdir = posixpath.join(*native_subdir_path.split(os.sep))
John Budorickd3ba72b2018-03-20 12:27:42 -07002330 self._package_name = package
2331 self._package_version = version
2332
2333 #override
Josip Sokcevicd47a9c22023-06-22 05:14:35 +00002334 def run(self, revision_overrides, command, args, work_queue, options,
2335 patch_refs, target_branches, skip_sync_revisions):
John Budorickd3ba72b2018-03-20 12:27:42 -07002336 """Runs |command| then parse the DEPS file."""
2337 logging.info('CipdDependency(%s).run()' % self.name)
Michael Mossd683d7c2018-06-15 05:05:17 +00002338 if not self.should_process:
2339 return
John Budorickd3ba72b2018-03-20 12:27:42 -07002340 self._CreatePackageIfNecessary()
Joanna Wanga84a16b2022-07-27 18:52:17 +00002341 super(CipdDependency,
2342 self).run(revision_overrides, command, args, work_queue, options,
2343 patch_refs, target_branches, skip_sync_revisions)
John Budorickd3ba72b2018-03-20 12:27:42 -07002344
2345 def _CreatePackageIfNecessary(self):
2346 # We lazily create the CIPD package to make sure that only packages
2347 # that we want (as opposed to all packages defined in all DEPS files
2348 # we parse) get added to the root and subsequently ensured.
2349 if not self._cipd_package:
2350 self._cipd_package = self._cipd_root.add_package(
2351 self._cipd_subdir, self._package_name, self._package_version)
John Budorick0f7b2002018-01-19 15:46:17 -08002352
Edward Lemure05f18d2018-06-08 17:36:53 +00002353 def ParseDepsFile(self):
John Budorick0f7b2002018-01-19 15:46:17 -08002354 """CIPD dependencies are not currently allowed to have nested deps."""
2355 self.add_dependencies_and_close([], [])
2356
2357 #override
Shenghua Zhang6f830312018-02-26 11:45:07 -08002358 def verify_validity(self):
2359 """CIPD dependencies allow duplicate name for packages in same directory."""
2360 logging.info('Dependency(%s).verify_validity()' % self.name)
2361 return True
2362
2363 #override
Edward Lemurbabd0982018-05-11 13:32:37 -04002364 def GetScmName(self):
John Budorick0f7b2002018-01-19 15:46:17 -08002365 """Always 'cipd'."""
John Budorick0f7b2002018-01-19 15:46:17 -08002366 return 'cipd'
2367
Dan Le Febvreb0e8e7a2023-05-18 23:36:46 +00002368 def GetExpandedPackageName(self):
2369 """Return the CIPD package name with the variables evaluated."""
2370 package = self._cipd_root.expand_package_name(self._package_name)
2371 if package:
2372 return package
2373 return self._package_name
2374
John Budorick0f7b2002018-01-19 15:46:17 -08002375 #override
Edward Lemurbabd0982018-05-11 13:32:37 -04002376 def CreateSCM(self, out_cb=None):
John Budorick0f7b2002018-01-19 15:46:17 -08002377 """Create a Wrapper instance suitable for handling this CIPD dependency."""
John Budorickd3ba72b2018-03-20 12:27:42 -07002378 self._CreatePackageIfNecessary()
John Budorick0f7b2002018-01-19 15:46:17 -08002379 return gclient_scm.CipdWrapper(
Edward Lemurbabd0982018-05-11 13:32:37 -04002380 self.url, self.root.root_dir, self.name, self.outbuf, out_cb,
2381 root=self._cipd_root, package=self._cipd_package)
John Budorick0f7b2002018-01-19 15:46:17 -08002382
Joanna Wang9144b672023-02-24 23:36:17 +00002383 def hierarchy(self, include_url=False, graphviz=False):
2384 if graphviz:
2385 return '' # graphviz lines not implemented for cipd deps.
Edward Lemure4e15042018-06-28 18:07:00 +00002386 return self.parent.hierarchy(include_url) + ' -> ' + self._cipd_subdir
2387
John Budorick0f7b2002018-01-19 15:46:17 -08002388 def ToLines(self):
Joanna Wang9144b672023-02-24 23:36:17 +00002389 # () -> Sequence[str]
John Budorick0f7b2002018-01-19 15:46:17 -08002390 """Return a list of lines representing this in a DEPS file."""
John Budorickc35aba52018-06-28 20:57:03 +00002391 def escape_cipd_var(package):
2392 return package.replace('{', '{{').replace('}', '}}')
2393
John Budorick0f7b2002018-01-19 15:46:17 -08002394 s = []
John Budorickd3ba72b2018-03-20 12:27:42 -07002395 self._CreatePackageIfNecessary()
John Budorick0f7b2002018-01-19 15:46:17 -08002396 if self._cipd_package.authority_for_subdir:
2397 condition_part = ([' "condition": %r,' % self.condition]
2398 if self.condition else [])
2399 s.extend([
Michael Moss4e9b50a2018-05-23 22:35:06 -07002400 ' # %s' % self.hierarchy(include_url=False),
John Budorickd3ba72b2018-03-20 12:27:42 -07002401 ' "%s": {' % (self.name.split(':')[0],),
John Budorick0f7b2002018-01-19 15:46:17 -08002402 ' "packages": [',
2403 ])
John Budorick4099daa2018-06-21 19:22:10 +00002404 for p in sorted(
2405 self._cipd_root.packages(self._cipd_subdir),
Edward Lemur26a8b9f2019-08-15 20:46:44 +00002406 key=lambda x: x.name):
John Budorick0f7b2002018-01-19 15:46:17 -08002407 s.extend([
John Budorick64e33cb2018-02-20 09:40:30 -08002408 ' {',
John Budorickc35aba52018-06-28 20:57:03 +00002409 ' "package": "%s",' % escape_cipd_var(p.name),
John Budorick64e33cb2018-02-20 09:40:30 -08002410 ' "version": "%s",' % p.version,
2411 ' },',
John Budorick0f7b2002018-01-19 15:46:17 -08002412 ])
John Budorickd3ba72b2018-03-20 12:27:42 -07002413
John Budorick0f7b2002018-01-19 15:46:17 -08002414 s.extend([
2415 ' ],',
2416 ' "dep_type": "cipd",',
2417 ] + condition_part + [
2418 ' },',
2419 '',
2420 ])
2421 return s
2422
2423
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002424#### gclient commands.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002425
2426
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002427@subcommand.usage('[command] [args ...]')
Edward Lemur3298e7b2018-07-17 18:21:27 +00002428@metrics.collector.collect_metrics('gclient recurse')
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00002429def CMDrecurse(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002430 """Operates [command args ...] on all the dependencies.
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00002431
Arthur Milchior08cd5fe2022-07-28 20:38:47 +00002432 Change directory to each dependency's directory, and call [command
2433 args ...] there. Sets GCLIENT_DEP_PATH environment variable as the
2434 dep's relative location to root directory of the checkout.
2435
2436 Examples:
2437 * `gclient recurse --no-progress -j1 sh -c 'echo "$GCLIENT_DEP_PATH"'`
2438 print the relative path of each dependency.
2439 * `gclient recurse --no-progress -j1 sh -c "pwd"`
2440 print the absolute path of each dependency.
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00002441 """
2442 # Stop parsing at the first non-arg so that these go through to the command
2443 parser.disable_interspersed_args()
2444 parser.add_option('-s', '--scm', action='append', default=[],
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00002445 help='Choose scm types to operate upon.')
maruel@chromium.org288054d2012-03-05 00:43:07 +00002446 parser.add_option('-i', '--ignore', action='store_true',
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00002447 help='Ignore non-zero return codes from subcommands.')
2448 parser.add_option('--prepend-dir', action='store_true',
2449 help='Prepend relative dir for use with git <cmd> --null.')
2450 parser.add_option('--no-progress', action='store_true',
2451 help='Disable progress bar that shows sub-command updates')
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00002452 options, args = parser.parse_args(args)
maruel@chromium.org45e9f2d2010-10-18 13:33:46 +00002453 if not args:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002454 print('Need to supply a command!', file=sys.stderr)
maruel@chromium.org45e9f2d2010-10-18 13:33:46 +00002455 return 1
maruel@chromium.org78cba522010-10-18 13:32:05 +00002456 root_and_entries = gclient_utils.GetGClientRootAndEntries()
2457 if not root_and_entries:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002458 print(
maruel@chromium.org78cba522010-10-18 13:32:05 +00002459 'You need to run gclient sync at least once to use \'recurse\'.\n'
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002460 'This is because .gclient_entries needs to exist and be up to date.',
2461 file=sys.stderr)
maruel@chromium.org78cba522010-10-18 13:32:05 +00002462 return 1
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00002463
2464 # Normalize options.scm to a set()
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00002465 scm_set = set()
2466 for scm in options.scm:
2467 scm_set.update(scm.split(','))
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00002468 options.scm = scm_set
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00002469
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00002470 options.nohooks = True
2471 client = GClient.LoadCurrentConfig(options)
Marc-Antoine Ruele6e06412017-10-18 13:47:02 -04002472 if not client:
2473 raise gclient_utils.Error('client not configured; see \'gclient config\'')
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00002474 return client.RunOnDeps('recurse', args, ignore_requirements=True,
2475 progress=not options.no_progress)
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00002476
2477
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002478@subcommand.usage('[args ...]')
Edward Lemur3298e7b2018-07-17 18:21:27 +00002479@metrics.collector.collect_metrics('gclient fetch')
davidbarr@chromium.org12f944e2012-03-01 02:18:31 +00002480def CMDfetch(parser, args):
2481 """Fetches upstream commits for all modules.
2482
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002483 Completely git-specific. Simply runs 'git fetch [args ...]' for each module.
2484 """
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00002485 (options, args) = parser.parse_args(args)
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002486 return CMDrecurse(OptionParser(), [
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00002487 '--jobs=%d' % options.jobs, '--scm=git', 'git', 'fetch'] + args)
2488
2489
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002490class Flattener(object):
2491 """Flattens a gclient solution."""
2492
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02002493 def __init__(self, client, pin_all_deps=False):
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002494 """Constructor.
2495
2496 Arguments:
2497 client (GClient): client to flatten
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02002498 pin_all_deps (bool): whether to pin all deps, even if they're not pinned
2499 in DEPS
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002500 """
2501 self._client = client
2502
2503 self._deps_string = None
Joanna Wang9144b672023-02-24 23:36:17 +00002504 self._deps_graph_lines = None
Paweł Hajdan, Jrfeb01642017-09-12 15:50:46 +02002505 self._deps_files = set()
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002506
2507 self._allowed_hosts = set()
2508 self._deps = {}
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002509 self._hooks = []
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002510 self._pre_deps_hooks = []
Paweł Hajdan, Jrfb022012017-07-06 18:00:08 +02002511 self._vars = {}
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002512
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02002513 self._flatten(pin_all_deps=pin_all_deps)
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002514
2515 @property
2516 def deps_string(self):
2517 assert self._deps_string is not None
2518 return self._deps_string
2519
Paweł Hajdan, Jrfeb01642017-09-12 15:50:46 +02002520 @property
Joanna Wang9144b672023-02-24 23:36:17 +00002521 def deps_graph_lines(self):
2522 assert self._deps_graph_lines is not None
2523 return self._deps_graph_lines
2524
2525 @property
Paweł Hajdan, Jrfeb01642017-09-12 15:50:46 +02002526 def deps_files(self):
2527 return self._deps_files
2528
Paweł Hajdan, Jr5ec77132017-08-16 19:21:06 +02002529 def _pin_dep(self, dep):
2530 """Pins a dependency to specific full revision sha.
2531
2532 Arguments:
2533 dep (Dependency): dependency to process
2534 """
Michael Mossd683d7c2018-06-15 05:05:17 +00002535 if dep.url is None:
2536 return
2537
Paweł Hajdan, Jr5ec77132017-08-16 19:21:06 +02002538 # Make sure the revision is always fully specified (a hash),
2539 # as opposed to refs or tags which might change. Similarly,
2540 # shortened shas might become ambiguous; make sure to always
2541 # use full one for pinning.
Edward Lemure7273d22018-05-10 19:13:51 -04002542 revision = gclient_utils.SplitUrlRevision(dep.url)[1]
2543 if not revision or not gclient_utils.IsFullGitSha(revision):
2544 dep.PinToActualRevision()
Paweł Hajdan, Jr5ec77132017-08-16 19:21:06 +02002545
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02002546 def _flatten(self, pin_all_deps=False):
2547 """Runs the flattener. Saves resulting DEPS string.
2548
2549 Arguments:
2550 pin_all_deps (bool): whether to pin all deps, even if they're not pinned
2551 in DEPS
2552 """
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002553 for solution in self._client.dependencies:
Paweł Hajdan, Jr11eb7152017-08-10 12:50:11 +02002554 self._add_dep(solution)
Michael Mossd683d7c2018-06-15 05:05:17 +00002555 self._flatten_dep(solution)
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002556
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02002557 if pin_all_deps:
Marc-Antoine Ruel8e57b4b2019-10-11 01:01:36 +00002558 for dep in self._deps.values():
Paweł Hajdan, Jr5ec77132017-08-16 19:21:06 +02002559 self._pin_dep(dep)
Paweł Hajdan, Jr39300ba2017-08-11 16:52:38 +02002560
Paweł Hajdan, Jr76a9d042017-08-18 20:05:41 +02002561 def add_deps_file(dep):
Paweł Hajdan, Jr0870df22017-08-23 17:59:29 +02002562 # Only include DEPS files referenced by recursedeps.
Edward Lemurfbb06aa2018-06-11 20:43:06 +00002563 if not dep.should_recurse:
Paweł Hajdan, Jr0870df22017-08-23 17:59:29 +02002564 return
Paweł Hajdan, Jrfeb01642017-09-12 15:50:46 +02002565 deps_file = dep.deps_file
2566 deps_path = os.path.join(self._client.root_dir, dep.name, deps_file)
Paweł Hajdan, Jr76a9d042017-08-18 20:05:41 +02002567 if not os.path.exists(deps_path):
Paweł Hajdan, Jrfeb01642017-09-12 15:50:46 +02002568 # gclient has a fallback that if deps_file doesn't exist, it'll try
2569 # DEPS. Do the same here.
2570 deps_file = 'DEPS'
2571 deps_path = os.path.join(self._client.root_dir, dep.name, deps_file)
2572 if not os.path.exists(deps_path):
2573 return
Michael Mossd683d7c2018-06-15 05:05:17 +00002574 assert dep.url
Edward Lemure7273d22018-05-10 19:13:51 -04002575 self._deps_files.add((dep.url, deps_file, dep.hierarchy_data()))
Marc-Antoine Ruel8e57b4b2019-10-11 01:01:36 +00002576 for dep in self._deps.values():
Paweł Hajdan, Jr76a9d042017-08-18 20:05:41 +02002577 add_deps_file(dep)
Paweł Hajdan, Jr76a9d042017-08-18 20:05:41 +02002578
Michael Moss848c86e2018-05-03 16:05:50 -07002579 gn_args_dep = self._deps.get(self._client.dependencies[0]._gn_args_from,
2580 self._client.dependencies[0])
Joanna Wang9144b672023-02-24 23:36:17 +00002581
2582 self._deps_graph_lines = _DepsToDotGraphLines(self._deps)
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002583 self._deps_string = '\n'.join(
Michael Moss848c86e2018-05-03 16:05:50 -07002584 _GNSettingsToLines(gn_args_dep._gn_args_file, gn_args_dep._gn_args) +
Joanna Wang9144b672023-02-24 23:36:17 +00002585 _AllowedHostsToLines(self._allowed_hosts) + _DepsToLines(self._deps) +
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002586 _HooksToLines('hooks', self._hooks) +
2587 _HooksToLines('pre_deps_hooks', self._pre_deps_hooks) +
Joanna Wang9144b672023-02-24 23:36:17 +00002588 _VarsToLines(self._vars) + [
2589 '# %s, %s' % (url, deps_file)
2590 for url, deps_file, _ in sorted(self._deps_files)
2591 ] + ['']) # Ensure newline at end of file.
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002592
Paweł Hajdan, Jr11eb7152017-08-10 12:50:11 +02002593 def _add_dep(self, dep):
2594 """Helper to add a dependency to flattened DEPS.
2595
2596 Arguments:
2597 dep (Dependency): dependency to add
2598 """
2599 assert dep.name not in self._deps or self._deps.get(dep.name) == dep, (
2600 dep.name, self._deps.get(dep.name))
Michael Mossd683d7c2018-06-15 05:05:17 +00002601 if dep.url:
2602 self._deps[dep.name] = dep
Paweł Hajdan, Jr11eb7152017-08-10 12:50:11 +02002603
Edward Lemur16f4bad2018-05-16 16:53:49 -04002604 def _flatten_dep(self, dep):
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002605 """Visits a dependency in order to flatten it (see CMDflatten).
2606
2607 Arguments:
2608 dep (Dependency): dependency to process
2609 """
Edward Lemur16f4bad2018-05-16 16:53:49 -04002610 logging.debug('_flatten_dep(%s)', dep.name)
Paweł Hajdan, Jradae2a62017-08-18 16:49:57 +02002611
Edward Lemur16f4bad2018-05-16 16:53:49 -04002612 assert dep.deps_parsed, (
2613 "Attempted to flatten %s but it has not been processed." % dep.name)
Paweł Hajdan, Jrc69b32e2017-08-17 18:47:48 +02002614
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002615 self._allowed_hosts.update(dep.allowed_hosts)
2616
Michael Mossce9f17f2018-01-31 13:16:35 -08002617 # Only include vars explicitly listed in the DEPS files or gclient solution,
2618 # not automatic, local overrides (i.e. not all of dep.get_vars()).
Michael Moss4e9b50a2018-05-23 22:35:06 -07002619 hierarchy = dep.hierarchy(include_url=False)
Marc-Antoine Ruel8e57b4b2019-10-11 01:01:36 +00002620 for key, value in dep._vars.items():
Paweł Hajdan, Jrc9353602017-08-02 17:52:08 +02002621 # Make sure there are no conflicting variables. It is fine however
2622 # to use same variable name, as long as the value is consistent.
Takuto Ikuta575872e2019-02-21 15:20:07 +00002623 assert key not in self._vars or self._vars[key][1] == value, (
2624 "dep:%s key:%s value:%s != %s" % (
2625 dep.name, key, value, self._vars[key][1]))
Michael Mossce9f17f2018-01-31 13:16:35 -08002626 self._vars[key] = (hierarchy, value)
2627 # Override explicit custom variables.
Marc-Antoine Ruel8e57b4b2019-10-11 01:01:36 +00002628 for key, value in dep.custom_vars.items():
Michael Mossce9f17f2018-01-31 13:16:35 -08002629 # Do custom_vars that don't correspond to DEPS vars ever make sense? DEPS
2630 # conditionals shouldn't be using vars that aren't also defined in the
2631 # DEPS (presubmit actually disallows this), so any new custom_var must be
2632 # unused in the DEPS, so no need to add it to the flattened output either.
2633 if key not in self._vars:
2634 continue
2635 # Don't "override" existing vars if it's actually the same value.
Aravind Vasudevanc5f0cbb2022-01-24 23:56:57 +00002636 if self._vars[key][1] == value:
Michael Mossce9f17f2018-01-31 13:16:35 -08002637 continue
2638 # Anything else is overriding a default value from the DEPS.
2639 self._vars[key] = (hierarchy + ' [custom_var override]', value)
Paweł Hajdan, Jrfb022012017-07-06 18:00:08 +02002640
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002641 self._pre_deps_hooks.extend([(dep, hook) for hook in dep.pre_deps_hooks])
Edward Lemur16f4bad2018-05-16 16:53:49 -04002642 self._hooks.extend([(dep, hook) for hook in dep.deps_hooks])
Paweł Hajdan, Jradae2a62017-08-18 16:49:57 +02002643
Paweł Hajdan, Jrb0ad16e2017-08-03 15:33:21 +02002644 for sub_dep in dep.dependencies:
Edward Lemur16f4bad2018-05-16 16:53:49 -04002645 self._add_dep(sub_dep)
Paweł Hajdan, Jrb0ad16e2017-08-03 15:33:21 +02002646
Edward Lemurfbb06aa2018-06-11 20:43:06 +00002647 for d in dep.dependencies:
2648 if d.should_recurse:
2649 self._flatten_dep(d)
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002650
2651
Joanna Wang3ab2f212023-08-09 01:25:15 +00002652@metrics.collector.collect_metrics('gclient gitmodules')
2653def CMDgitmodules(parser, args):
2654 """Adds or updates Git Submodules based on the contents of the DEPS file.
2655
2656 This command should be run in the root director of the repo.
2657 It will create or update the .gitmodules file and include
2658 `gclient-condition` values. Commits in gitlinks will also be updated.
2659 """
2660 parser.add_option('--output-gitmodules',
2661 help='name of the .gitmodules file to write to',
2662 default='.gitmodules')
2663 parser.add_option(
2664 '--deps-file',
2665 help=
2666 'name of the deps file to parse for git dependency paths and commits.',
2667 default='DEPS')
2668 parser.add_option(
2669 '--skip-dep',
2670 action="append",
2671 help='skip adding gitmodules for the git dependency at the given path',
2672 default=[])
2673 options, args = parser.parse_args(args)
2674
2675 deps_dir = os.path.dirname(os.path.abspath(options.deps_file))
2676 gclient_path = gclient_paths.FindGclientRoot(deps_dir)
2677 if not gclient_path:
2678 logging.error(
2679 '.gclient not found\n'
2680 'Make sure you are running this script from a gclient workspace.')
2681 sys.exit(1)
2682
2683 deps_content = gclient_utils.FileRead(options.deps_file)
2684 ls = gclient_eval.Parse(deps_content, options.deps_file, None, None)
2685
2686 prefix_length = 0
2687 if not 'use_relative_paths' in ls or ls['use_relative_paths'] != True:
2688 delta_path = os.path.relpath(deps_dir, os.path.abspath(gclient_path))
2689 if delta_path:
2690 prefix_length = len(delta_path.replace(os.path.sep, '/')) + 1
2691
Josip Sokcevicc9dae642023-08-21 16:41:05 +00002692 cache_info = []
Josip Sokcevic293aa652023-08-23 18:55:20 +00002693 with open(options.output_gitmodules, 'w', newline='') as f:
Joanna Wang3ab2f212023-08-09 01:25:15 +00002694 for path, dep in ls.get('deps').items():
2695 if path in options.skip_dep:
2696 continue
2697 if dep.get('dep_type') == 'cipd':
2698 continue
2699 try:
2700 url, commit = dep['url'].split('@', maxsplit=1)
2701 except ValueError:
2702 logging.error('error on %s; %s, not adding it', path, dep["url"])
2703 continue
2704 if prefix_length:
2705 path = path[prefix_length:]
2706
Josip Sokcevicc9dae642023-08-21 16:41:05 +00002707 cache_info += ['--cacheinfo', f'160000,{commit},{path}']
Joanna Wang3ab2f212023-08-09 01:25:15 +00002708 f.write(f'[submodule "{path}"]\n\tpath = {path}\n\turl = {url}\n')
2709 if 'condition' in dep:
2710 f.write(f'\tgclient-condition = {dep["condition"]}\n')
Josip Sokcevic293aa652023-08-23 18:55:20 +00002711 # Windows has limit how long, so let's chunk those calls.
2712 if len(cache_info) >= 100:
2713 subprocess2.call(['git', 'update-index', '--add'] + cache_info)
2714 cache_info = []
Josip Sokcevicc9dae642023-08-21 16:41:05 +00002715
2716 if cache_info:
2717 subprocess2.call(['git', 'update-index', '--add'] + cache_info)
Josip Sokcevic5561f8b2023-08-21 16:00:42 +00002718 subprocess2.call(['git', 'add', '.gitmodules'])
Josip Sokcevicde6bc662023-08-10 22:27:23 +00002719 print('.gitmodules and gitlinks updated. Please check git diff and '
2720 'commit changes.')
Joanna Wang3ab2f212023-08-09 01:25:15 +00002721
2722
Edward Lemur3298e7b2018-07-17 18:21:27 +00002723@metrics.collector.collect_metrics('gclient flatten')
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002724def CMDflatten(parser, args):
2725 """Flattens the solutions into a single DEPS file."""
2726 parser.add_option('--output-deps', help='Path to the output DEPS file')
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02002727 parser.add_option(
Paweł Hajdan, Jrfeb01642017-09-12 15:50:46 +02002728 '--output-deps-files',
2729 help=('Path to the output metadata about DEPS files referenced by '
2730 'recursedeps.'))
2731 parser.add_option(
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02002732 '--pin-all-deps', action='store_true',
2733 help=('Pin all deps, even if not pinned in DEPS. CAVEAT: only does so '
2734 'for checked out deps, NOT deps_os.'))
Joanna Wang9144b672023-02-24 23:36:17 +00002735 parser.add_option('--deps-graph-file',
2736 help='Provide a path for the output graph file')
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002737 options, args = parser.parse_args(args)
2738
2739 options.nohooks = True
Paweł Hajdan, Jr5ec77132017-08-16 19:21:06 +02002740 options.process_all_deps = True
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002741 client = GClient.LoadCurrentConfig(options)
Gavin Makf6b414c2021-01-12 19:10:41 +00002742 if not client:
2743 raise gclient_utils.Error('client not configured; see \'gclient config\'')
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002744
2745 # Only print progress if we're writing to a file. Otherwise, progress updates
2746 # could obscure intended output.
2747 code = client.RunOnDeps('flatten', args, progress=options.output_deps)
2748 if code != 0:
2749 return code
2750
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02002751 flattener = Flattener(client, pin_all_deps=options.pin_all_deps)
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002752
2753 if options.output_deps:
2754 with open(options.output_deps, 'w') as f:
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002755 f.write(flattener.deps_string)
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002756 else:
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002757 print(flattener.deps_string)
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002758
Joanna Wang9144b672023-02-24 23:36:17 +00002759 if options.deps_graph_file:
2760 with open(options.deps_graph_file, 'w') as f:
2761 f.write('\n'.join(flattener.deps_graph_lines))
2762
Michael Mossfe68c912018-03-22 19:19:35 -07002763 deps_files = [{'url': d[0], 'deps_file': d[1], 'hierarchy': d[2]}
Paweł Hajdan, Jrfeb01642017-09-12 15:50:46 +02002764 for d in sorted(flattener.deps_files)]
2765 if options.output_deps_files:
2766 with open(options.output_deps_files, 'w') as f:
2767 json.dump(deps_files, f)
2768
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002769 return 0
2770
2771
Paweł Hajdan, Jr3c2aa832017-06-07 20:22:16 +02002772def _GNSettingsToLines(gn_args_file, gn_args):
2773 s = []
2774 if gn_args_file:
2775 s.extend([
2776 'gclient_gn_args_file = "%s"' % gn_args_file,
2777 'gclient_gn_args = %r' % gn_args,
2778 ])
2779 return s
2780
2781
Paweł Hajdan, Jr6014b562017-06-30 17:43:42 +02002782def _AllowedHostsToLines(allowed_hosts):
2783 """Converts |allowed_hosts| set to list of lines for output."""
2784 if not allowed_hosts:
2785 return []
2786 s = ['allowed_hosts = [']
2787 for h in sorted(allowed_hosts):
2788 s.append(' "%s",' % h)
2789 s.extend([']', ''])
2790 return s
2791
2792
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002793def _DepsToLines(deps):
Joanna Wang9144b672023-02-24 23:36:17 +00002794 # type: (Mapping[str, Dependency]) -> Sequence[str]
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002795 """Converts |deps| dict to list of lines for output."""
Paweł Hajdan, Jr5b593352017-06-29 18:37:45 +02002796 if not deps:
2797 return []
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002798 s = ['deps = {']
Edward Lemuree7b9dd2019-07-20 01:29:08 +00002799 for _, dep in sorted(deps.items()):
John Budorick0f7b2002018-01-19 15:46:17 -08002800 s.extend(dep.ToLines())
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002801 s.extend(['}', ''])
2802 return s
2803
2804
Joanna Wang9144b672023-02-24 23:36:17 +00002805def _DepsToDotGraphLines(deps):
2806 # type: (Mapping[str, Dependency]) -> Sequence[str]
2807 """Converts |deps| dict to list of lines for dot graphs"""
2808 if not deps:
2809 return []
2810 graph_lines = ["digraph {\n\trankdir=\"LR\";"]
2811 for _, dep in sorted(deps.items()):
2812 line = dep.hierarchy(include_url=False, graphviz=True)
2813 if line:
2814 graph_lines.append("\t%s" % line)
2815 graph_lines.append("}")
2816 return graph_lines
2817
2818
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02002819def _DepsOsToLines(deps_os):
2820 """Converts |deps_os| dict to list of lines for output."""
Paweł Hajdan, Jr5b593352017-06-29 18:37:45 +02002821 if not deps_os:
2822 return []
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02002823 s = ['deps_os = {']
Edward Lemuree7b9dd2019-07-20 01:29:08 +00002824 for dep_os, os_deps in sorted(deps_os.items()):
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02002825 s.append(' "%s": {' % dep_os)
Edward Lemuree7b9dd2019-07-20 01:29:08 +00002826 for name, dep in sorted(os_deps.items()):
Paweł Hajdan, Jr78ce24e2017-10-03 17:09:13 +02002827 condition_part = ([' "condition": %r,' % dep.condition]
Paweł Hajdan, Jrad30de62017-06-26 18:51:58 +02002828 if dep.condition else [])
2829 s.extend([
Michael Moss4e9b50a2018-05-23 22:35:06 -07002830 ' # %s' % dep.hierarchy(include_url=False),
Paweł Hajdan, Jrad30de62017-06-26 18:51:58 +02002831 ' "%s": {' % (name,),
Edward Lemure05f18d2018-06-08 17:36:53 +00002832 ' "url": "%s",' % (dep.url,),
Paweł Hajdan, Jrad30de62017-06-26 18:51:58 +02002833 ] + condition_part + [
2834 ' },',
2835 '',
2836 ])
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02002837 s.extend([' },', ''])
2838 s.extend(['}', ''])
2839 return s
2840
2841
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002842def _HooksToLines(name, hooks):
2843 """Converts |hooks| list to list of lines for output."""
Paweł Hajdan, Jr5b593352017-06-29 18:37:45 +02002844 if not hooks:
2845 return []
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002846 s = ['%s = [' % name]
2847 for dep, hook in hooks:
2848 s.extend([
Michael Moss4e9b50a2018-05-23 22:35:06 -07002849 ' # %s' % dep.hierarchy(include_url=False),
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002850 ' {',
2851 ])
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +02002852 if hook.name is not None:
2853 s.append(' "name": "%s",' % hook.name)
2854 if hook.pattern is not None:
2855 s.append(' "pattern": "%s",' % hook.pattern)
Paweł Hajdan, Jrecf53fe2017-09-29 18:28:49 +02002856 if hook.condition is not None:
Paweł Hajdan, Jr78ce24e2017-10-03 17:09:13 +02002857 s.append(' "condition": %r,' % hook.condition)
Corentin Walleza68660d2018-09-10 17:33:24 +00002858 # Flattened hooks need to be written relative to the root gclient dir
2859 cwd = os.path.relpath(os.path.normpath(hook.effective_cwd))
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002860 s.extend(
Corentin Walleza68660d2018-09-10 17:33:24 +00002861 [' "cwd": "%s",' % cwd] +
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002862 [' "action": ['] +
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +02002863 [' "%s",' % arg for arg in hook.action] +
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002864 [' ]', ' },', '']
2865 )
2866 s.extend([']', ''])
2867 return s
2868
2869
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +02002870def _HooksOsToLines(hooks_os):
2871 """Converts |hooks| list to list of lines for output."""
Paweł Hajdan, Jr5b593352017-06-29 18:37:45 +02002872 if not hooks_os:
2873 return []
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +02002874 s = ['hooks_os = {']
Marc-Antoine Ruel8e57b4b2019-10-11 01:01:36 +00002875 for hook_os, os_hooks in hooks_os.items():
Michael Moss017bcf62017-06-28 15:26:38 -07002876 s.append(' "%s": [' % hook_os)
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +02002877 for dep, hook in os_hooks:
2878 s.extend([
Michael Moss4e9b50a2018-05-23 22:35:06 -07002879 ' # %s' % dep.hierarchy(include_url=False),
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +02002880 ' {',
2881 ])
2882 if hook.name is not None:
2883 s.append(' "name": "%s",' % hook.name)
2884 if hook.pattern is not None:
2885 s.append(' "pattern": "%s",' % hook.pattern)
Paweł Hajdan, Jrecf53fe2017-09-29 18:28:49 +02002886 if hook.condition is not None:
Paweł Hajdan, Jr78ce24e2017-10-03 17:09:13 +02002887 s.append(' "condition": %r,' % hook.condition)
Corentin Walleza68660d2018-09-10 17:33:24 +00002888 # Flattened hooks need to be written relative to the root gclient dir
2889 cwd = os.path.relpath(os.path.normpath(hook.effective_cwd))
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +02002890 s.extend(
Corentin Walleza68660d2018-09-10 17:33:24 +00002891 [' "cwd": "%s",' % cwd] +
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +02002892 [' "action": ['] +
2893 [' "%s",' % arg for arg in hook.action] +
2894 [' ]', ' },', '']
2895 )
Michael Moss017bcf62017-06-28 15:26:38 -07002896 s.extend([' ],', ''])
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +02002897 s.extend(['}', ''])
2898 return s
2899
2900
Paweł Hajdan, Jrfb022012017-07-06 18:00:08 +02002901def _VarsToLines(variables):
2902 """Converts |variables| dict to list of lines for output."""
2903 if not variables:
2904 return []
2905 s = ['vars = {']
Edward Lemuree7b9dd2019-07-20 01:29:08 +00002906 for key, tup in sorted(variables.items()):
Michael Mossce9f17f2018-01-31 13:16:35 -08002907 hierarchy, value = tup
Paweł Hajdan, Jrfb022012017-07-06 18:00:08 +02002908 s.extend([
Michael Mossce9f17f2018-01-31 13:16:35 -08002909 ' # %s' % hierarchy,
Paweł Hajdan, Jrfb022012017-07-06 18:00:08 +02002910 ' "%s": %r,' % (key, value),
2911 '',
2912 ])
2913 s.extend(['}', ''])
2914 return s
2915
2916
Edward Lemur3298e7b2018-07-17 18:21:27 +00002917@metrics.collector.collect_metrics('gclient grep')
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00002918def CMDgrep(parser, args):
2919 """Greps through git repos managed by gclient.
2920
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002921 Runs 'git grep [args...]' for each module.
2922 """
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00002923 # We can't use optparse because it will try to parse arguments sent
2924 # to git grep and throw an error. :-(
2925 if not args or re.match('(-h|--help)$', args[0]):
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002926 print(
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00002927 'Usage: gclient grep [-j <N>] git-grep-args...\n\n'
2928 'Example: "gclient grep -j10 -A2 RefCountedBase" runs\n"git grep '
2929 '-A2 RefCountedBase" on each of gclient\'s git\nrepos with up to '
2930 '10 jobs.\n\nBonus: page output by appending "|& less -FRSX" to the'
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002931 ' end of your query.',
2932 file=sys.stderr)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00002933 return 1
2934
2935 jobs_arg = ['--jobs=1']
2936 if re.match(r'(-j|--jobs=)\d+$', args[0]):
2937 jobs_arg, args = args[:1], args[1:]
2938 elif re.match(r'(-j|--jobs)$', args[0]):
2939 jobs_arg, args = args[:2], args[2:]
2940
2941 return CMDrecurse(
2942 parser,
2943 jobs_arg + ['--ignore', '--prepend-dir', '--no-progress', '--scm=git',
2944 'git', 'grep', '--null', '--color=Always'] + args)
davidbarr@chromium.org12f944e2012-03-01 02:18:31 +00002945
2946
Edward Lemur3298e7b2018-07-17 18:21:27 +00002947@metrics.collector.collect_metrics('gclient root')
stip@chromium.orga735da22015-04-29 23:18:20 +00002948def CMDroot(parser, args):
2949 """Outputs the solution root (or current dir if there isn't one)."""
2950 (options, args) = parser.parse_args(args)
2951 client = GClient.LoadCurrentConfig(options)
2952 if client:
2953 print(os.path.abspath(client.root_dir))
2954 else:
2955 print(os.path.abspath('.'))
2956
2957
agablea98a6cd2016-11-15 14:30:10 -08002958@subcommand.usage('[url]')
Edward Lemur3298e7b2018-07-17 18:21:27 +00002959@metrics.collector.collect_metrics('gclient config')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002960def CMDconfig(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002961 """Creates a .gclient file in the current directory.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00002962
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002963 This specifies the configuration for further commands. After update/sync,
2964 top-level DEPS files in each module are read to determine dependent
2965 modules to operate on as well. If optional [url] parameter is
2966 provided, then configuration is read from a specified Subversion server
2967 URL.
2968 """
szager@chromium.orge2e03202012-07-31 18:05:16 +00002969 # We do a little dance with the --gclientfile option. 'gclient config' is the
2970 # only command where it's acceptable to have both '--gclientfile' and '--spec'
2971 # arguments. So, we temporarily stash any --gclientfile parameter into
2972 # options.output_config_file until after the (gclientfile xor spec) error
2973 # check.
2974 parser.remove_option('--gclientfile')
2975 parser.add_option('--gclientfile', dest='output_config_file',
2976 help='Specify an alternate .gclient file')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002977 parser.add_option('--name',
2978 help='overrides the default name for the solution')
nsylvain@google.comefc80932011-05-31 21:27:56 +00002979 parser.add_option('--deps-file', default='DEPS',
David Benjamin105e11e2017-10-16 10:39:35 -04002980 help='overrides the default name for the DEPS file for the '
nsylvain@google.comefc80932011-05-31 21:27:56 +00002981 'main solutions and all sub-dependencies')
smutae7ea312016-07-18 11:59:41 -07002982 parser.add_option('--unmanaged', action='store_true', default=False,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00002983 help='overrides the default behavior to make it possible '
smutae7ea312016-07-18 11:59:41 -07002984 'to have the main solution untouched by gclient '
2985 '(gclient will check out unmanaged dependencies but '
2986 'will never sync them)')
Robert Iannuccia19649b2018-06-29 16:31:45 +00002987 parser.add_option('--cache-dir', default=UNSET_CACHE_DIR,
2988 help='Cache all git repos into this dir and do shared '
2989 'clones from the cache, instead of cloning directly '
2990 'from the remote. Pass "None" to disable cache, even '
2991 'if globally enabled due to $GIT_CACHE_PATH.')
Paweł Hajdan, Jr3ba2a7c2017-10-04 19:24:46 +02002992 parser.add_option('--custom-var', action='append', dest='custom_vars',
2993 default=[],
2994 help='overrides variables; key=value syntax')
szager@chromium.orge2e03202012-07-31 18:05:16 +00002995 parser.set_defaults(config_filename=None)
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002996 (options, args) = parser.parse_args(args)
szager@chromium.orge2e03202012-07-31 18:05:16 +00002997 if options.output_config_file:
2998 setattr(options, 'config_filename', getattr(options, 'output_config_file'))
maruel@chromium.org5fc2a332010-05-26 19:37:15 +00002999 if ((options.spec and args) or len(args) > 2 or
3000 (not options.spec and not args)):
3001 parser.error('Inconsistent arguments. Use either --spec or one or 2 args')
3002
Robert Iannuccia19649b2018-06-29 16:31:45 +00003003 if (options.cache_dir is not UNSET_CACHE_DIR
3004 and options.cache_dir.lower() == 'none'):
3005 options.cache_dir = None
3006
Paweł Hajdan, Jr3ba2a7c2017-10-04 19:24:46 +02003007 custom_vars = {}
3008 for arg in options.custom_vars:
3009 kv = arg.split('=', 1)
3010 if len(kv) != 2:
3011 parser.error('Invalid --custom-var argument: %r' % arg)
3012 custom_vars[kv[0]] = gclient_eval.EvaluateCondition(kv[1], {})
3013
maruel@chromium.org2806acc2009-05-15 12:33:34 +00003014 client = GClient('.', options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00003015 if options.spec:
3016 client.SetConfig(options.spec)
3017 else:
maruel@chromium.org1ab7ffc2009-06-03 17:21:37 +00003018 base_url = args[0].rstrip('/')
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00003019 if not options.name:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00003020 name = base_url.split('/')[-1]
nsylvain@google.com12649ef2011-06-01 17:11:20 +00003021 if name.endswith('.git'):
3022 name = name[:-4]
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00003023 else:
3024 # specify an alternate relpath for the given URL.
3025 name = options.name
agable@chromium.orgf2214672015-10-27 21:02:48 +00003026 if not os.path.abspath(os.path.join(os.getcwd(), name)).startswith(
3027 os.getcwd()):
3028 parser.error('Do not pass a relative path for --name.')
3029 if any(x in ('..', '.', '/', '\\') for x in name.split(os.sep)):
3030 parser.error('Do not include relative path components in --name.')
3031
nsylvain@google.comefc80932011-05-31 21:27:56 +00003032 deps_file = options.deps_file
agablea98a6cd2016-11-15 14:30:10 -08003033 client.SetDefaultConfig(name, deps_file, base_url,
smutae7ea312016-07-18 11:59:41 -07003034 managed=not options.unmanaged,
Paweł Hajdan, Jr3ba2a7c2017-10-04 19:24:46 +02003035 cache_dir=options.cache_dir,
3036 custom_vars=custom_vars)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00003037 client.SaveConfig()
maruel@chromium.org79692d62010-05-14 18:57:13 +00003038 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00003039
3040
maruel@chromium.org39c0b222013-08-17 16:57:01 +00003041@subcommand.epilog("""Example:
maruel@chromium.org5ca27692010-05-26 19:32:41 +00003042 gclient pack > patch.txt
3043 generate simple patch for configured client and dependences
3044""")
Edward Lemur3298e7b2018-07-17 18:21:27 +00003045@metrics.collector.collect_metrics('gclient pack')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00003046def CMDpack(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00003047 """Generates a patch which can be applied at the root of the tree.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00003048
agabled437d762016-10-17 09:35:11 -07003049 Internally, runs 'git diff' on each checked out module and
maruel@chromium.org39c0b222013-08-17 16:57:01 +00003050 dependencies, and performs minimal postprocessing of the output. The
3051 resulting patch is printed to stdout and can be applied to a freshly
3052 checked out tree via 'patch -p0 < patchfile'.
3053 """
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00003054 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
3055 help='override deps for the specified (comma-separated) '
3056 'platform(s); \'all\' will process all deps_os '
3057 'references')
haitao.feng@intel.com306080c2012-05-04 13:11:29 +00003058 parser.remove_option('--jobs')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00003059 (options, args) = parser.parse_args(args)
iannucci@chromium.org50395ea2013-04-04 04:47:42 +00003060 # Force jobs to 1 so the stdout is not annotated with the thread ids
haitao.feng@intel.com306080c2012-05-04 13:11:29 +00003061 options.jobs = 1
kbr@google.comab318592009-09-04 00:54:55 +00003062 client = GClient.LoadCurrentConfig(options)
3063 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00003064 raise gclient_utils.Error('client not configured; see \'gclient config\'')
kbr@google.comab318592009-09-04 00:54:55 +00003065 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00003066 client.PrintLocationAndContents()
kbr@google.comab318592009-09-04 00:54:55 +00003067 return client.RunOnDeps('pack', args)
3068
3069
Edward Lemur3298e7b2018-07-17 18:21:27 +00003070@metrics.collector.collect_metrics('gclient status')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00003071def CMDstatus(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00003072 """Shows modification status for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00003073 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
3074 help='override deps for the specified (comma-separated) '
3075 'platform(s); \'all\' will process all deps_os '
3076 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00003077 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00003078 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00003079 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00003080 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00003081 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00003082 client.PrintLocationAndContents()
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00003083 return client.RunOnDeps('status', args)
3084
3085
maruel@chromium.org39c0b222013-08-17 16:57:01 +00003086@subcommand.epilog("""Examples:
maruel@chromium.org79692d62010-05-14 18:57:13 +00003087 gclient sync
3088 update files from SCM according to current configuration,
3089 *for modules which have changed since last update or sync*
3090 gclient sync --force
3091 update files from SCM according to current configuration, for
3092 all modules (useful for recovering files deleted from local copy)
Edward Lesmes3ffca4b2021-05-19 19:36:17 +00003093 gclient sync --revision src@GIT_COMMIT_OR_REF
3094 update src directory to GIT_COMMIT_OR_REF
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00003095
3096JSON output format:
3097If the --output-json option is specified, the following document structure will
3098be emitted to the provided file. 'null' entries may occur for subprojects which
3099are present in the gclient solution, but were not processed (due to custom_deps,
3100os_deps, etc.)
3101
3102{
3103 "solutions" : {
3104 "<name>": { # <name> is the posix-normalized path to the solution.
agabled437d762016-10-17 09:35:11 -07003105 "revision": [<git id hex string>|null],
3106 "scm": ["git"|null],
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00003107 }
3108 }
3109}
maruel@chromium.org5ca27692010-05-26 19:32:41 +00003110""")
Edward Lemur3298e7b2018-07-17 18:21:27 +00003111@metrics.collector.collect_metrics('gclient sync')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00003112def CMDsync(parser, args):
3113 """Checkout/update all modules."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00003114 parser.add_option('-f', '--force', action='store_true',
3115 help='force update even for unchanged modules')
3116 parser.add_option('-n', '--nohooks', action='store_true',
3117 help='don\'t run hooks after the update is complete')
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00003118 parser.add_option('-p', '--noprehooks', action='store_true',
3119 help='don\'t run pre-DEPS hooks', default=False)
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00003120 parser.add_option('-r', '--revision', action='append',
3121 dest='revisions', metavar='REV', default=[],
Edward Lesmes3ffca4b2021-05-19 19:36:17 +00003122 help='Enforces git ref/hash for the solutions with the '
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00003123 'format src@rev. The src@ part is optional and can be '
Edward Lesmes53014652018-03-07 18:01:40 -05003124 'skipped. You can also specify URLs instead of paths '
3125 'and gclient will find the solution corresponding to '
3126 'the given URL. If a path is also specified, the URL '
3127 'takes precedence. -r can be used multiple times when '
3128 '.gclient has multiple solutions configured, and will '
Edward Lesmes3ffca4b2021-05-19 19:36:17 +00003129 'work even if the src@ part is skipped. Revision '
3130 'numbers (e.g. 31000 or r31000) are not supported.')
Edward Lesmesc621b212018-03-21 20:26:56 -04003131 parser.add_option('--patch-ref', action='append',
3132 dest='patch_refs', metavar='GERRIT_REF', default=[],
Edward Lemur6a4e31b2018-08-10 19:59:02 +00003133 help='Patches the given reference with the format '
Edward Lemur4c5c8ab2019-06-07 15:58:13 +00003134 'dep@target-ref:patch-ref. '
Edward Lemur6a4e31b2018-08-10 19:59:02 +00003135 'For |dep|, you can specify URLs as well as paths, '
3136 'with URLs taking preference. '
3137 '|patch-ref| will be applied to |dep|, rebased on top '
3138 'of what |dep| was synced to, and a soft reset will '
3139 'be done. Use --no-rebase-patch-ref and '
3140 '--no-reset-patch-ref to disable this behavior. '
3141 '|target-ref| is the target branch against which a '
3142 'patch was created, it is used to determine which '
3143 'commits from the |patch-ref| actually constitute a '
Edward Lemur4c5c8ab2019-06-07 15:58:13 +00003144 'patch.')
Ravi Mistryecda7822022-02-28 16:22:20 +00003145 parser.add_option('-t', '--download-topics', action='store_true',
3146 help='Downloads and patches locally changes from all open '
3147 'Gerrit CLs that have the same topic as the changes '
3148 'in the specified patch_refs. Only works if atleast '
3149 'one --patch-ref is specified.')
maruel@chromium.org794207e2013-03-08 15:29:43 +00003150 parser.add_option('--with_branch_heads', action='store_true',
3151 help='Clone git "branch_heads" refspecs in addition to '
3152 'the default refspecs. This adds about 1/2GB to a '
3153 'full checkout. (git only)')
szager@chromium.org8d3348f2014-08-19 22:49:16 +00003154 parser.add_option('--with_tags', action='store_true',
3155 help='Clone git tags in addition to the default refspecs.')
agable2697cd12016-06-28 10:23:53 -07003156 parser.add_option('-H', '--head', action='store_true',
agablea98a6cd2016-11-15 14:30:10 -08003157 help='DEPRECATED: only made sense with safesync urls.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00003158 parser.add_option('-D', '--delete_unversioned_trees', action='store_true',
steveblock@chromium.org98e69452012-02-16 16:36:43 +00003159 help='Deletes from the working copy any dependencies that '
3160 'have been removed since the last sync, as long as '
3161 'there are no local modifications. When used with '
3162 '--force, such dependencies are removed even if they '
3163 'have local modifications. When used with --reset, '
3164 'all untracked directories are removed from the '
tnagel@chromium.orga2aaa632014-02-28 21:47:27 +00003165 'working copy, excluding those which are explicitly '
steveblock@chromium.org98e69452012-02-16 16:36:43 +00003166 'ignored in the repository.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00003167 parser.add_option('-R', '--reset', action='store_true',
3168 help='resets any local changes before updating (git only)')
bauerb@chromium.org2aad1b22011-07-22 12:00:41 +00003169 parser.add_option('-M', '--merge', action='store_true',
3170 help='merge upstream changes instead of trying to '
3171 'fast-forward or rebase')
dnj@chromium.org5b23e872015-02-20 21:25:57 +00003172 parser.add_option('-A', '--auto_rebase', action='store_true',
3173 help='Automatically rebase repositories against local '
3174 'checkout during update (git only).')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00003175 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
3176 help='override deps for the specified (comma-separated) '
3177 'platform(s); \'all\' will process all deps_os '
3178 'references')
Paweł Hajdan, Jr5ec77132017-08-16 19:21:06 +02003179 parser.add_option('--process-all-deps', action='store_true',
3180 help='Check out all deps, even for different OS-es, '
3181 'or with conditions evaluating to false')
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +00003182 parser.add_option('--upstream', action='store_true',
3183 help='Make repo state match upstream branch.')
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00003184 parser.add_option('--output-json',
3185 help='Output a json document to this path containing '
3186 'summary information about the sync.')
primiano@chromium.org5439ea52014-08-06 17:18:18 +00003187 parser.add_option('--no-history', action='store_true',
3188 help='GIT ONLY - Reduces the size/time of the checkout at '
3189 'the cost of no history. Requires Git 1.9+')
hinoka@chromium.org46b87412014-05-15 00:42:05 +00003190 parser.add_option('--shallow', action='store_true',
3191 help='GIT ONLY - Do a shallow clone into the cache dir. '
3192 'Requires Git 1.9+')
e.hakkinen@samsung.come8bc1aa2015-04-08 08:00:37 +00003193 parser.add_option('--no_bootstrap', '--no-bootstrap',
3194 action='store_true',
3195 help='Don\'t bootstrap from Google Storage.')
Josip Sokcevic14a83ae2020-05-21 01:36:34 +00003196 parser.add_option('--ignore_locks',
3197 action='store_true',
3198 help='No longer used.')
3199 parser.add_option('--break_repo_locks',
3200 action='store_true',
3201 help='No longer used.')
Vadim Shtayura08049e22017-10-11 00:14:52 +00003202 parser.add_option('--lock_timeout', type='int', default=5000,
3203 help='GIT ONLY - Deadline (in seconds) to wait for git '
3204 'cache lock to become available. Default is %default.')
Edward Lesmesc621b212018-03-21 20:26:56 -04003205 parser.add_option('--no-rebase-patch-ref', action='store_false',
3206 dest='rebase_patch_ref', default=True,
3207 help='Bypass rebase of the patch ref after checkout.')
3208 parser.add_option('--no-reset-patch-ref', action='store_false',
3209 dest='reset_patch_ref', default=True,
3210 help='Bypass calling reset after patching the ref.')
Joanna Wanga84a16b2022-07-27 18:52:17 +00003211 parser.add_option('--experiment',
3212 action='append',
3213 dest='experiments',
3214 default=[],
3215 help='Which experiments should be enabled.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00003216 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00003217 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00003218
3219 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00003220 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00003221
Ravi Mistryecda7822022-02-28 16:22:20 +00003222 if options.download_topics and not options.rebase_patch_ref:
3223 raise gclient_utils.Error(
3224 'Warning: You cannot download topics and not rebase each patch ref')
3225
Josip Sokcevic14a83ae2020-05-21 01:36:34 +00003226 if options.ignore_locks:
3227 print('Warning: ignore_locks is no longer used. Please remove its usage.')
3228
3229 if options.break_repo_locks:
3230 print('Warning: break_repo_locks is no longer used. Please remove its '
3231 'usage.')
3232
smutae7ea312016-07-18 11:59:41 -07003233 if options.revisions and options.head:
3234 # TODO(maruel): Make it a parser.error if it doesn't break any builder.
3235 print('Warning: you cannot use both --head and --revision')
3236
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00003237 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00003238 client.PrintLocationAndContents()
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00003239 ret = client.RunOnDeps('update', args)
3240 if options.output_json:
3241 slns = {}
Michael Mossd683d7c2018-06-15 05:05:17 +00003242 for d in client.subtree(True):
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00003243 normed = d.name.replace('\\', '/').rstrip('/') + '/'
3244 slns[normed] = {
3245 'revision': d.got_revision,
3246 'scm': d.used_scm.name if d.used_scm else None,
Michael Mossd683d7c2018-06-15 05:05:17 +00003247 'url': str(d.url) if d.url else None,
Edward Lemur7ccf2f02018-06-26 20:41:56 +00003248 'was_processed': d.should_process,
Joanna Wanga84a16b2022-07-27 18:52:17 +00003249 'was_synced': d._should_sync,
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00003250 }
Edward Lemurca879322019-09-09 20:18:13 +00003251 with open(options.output_json, 'w') as f:
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00003252 json.dump({'solutions': slns}, f)
3253 return ret
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00003254
3255
maruel@chromium.org39c0b222013-08-17 16:57:01 +00003256CMDupdate = CMDsync
3257
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00003258
Edward Lemur3298e7b2018-07-17 18:21:27 +00003259@metrics.collector.collect_metrics('gclient validate')
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +02003260def CMDvalidate(parser, args):
3261 """Validates the .gclient and DEPS syntax."""
3262 options, args = parser.parse_args(args)
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +02003263 client = GClient.LoadCurrentConfig(options)
Gavin Makf6b414c2021-01-12 19:10:41 +00003264 if not client:
3265 raise gclient_utils.Error('client not configured; see \'gclient config\'')
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +02003266 rv = client.RunOnDeps('validate', args)
3267 if rv == 0:
3268 print('validate: SUCCESS')
3269 else:
3270 print('validate: FAILURE')
3271 return rv
3272
3273
Edward Lemur3298e7b2018-07-17 18:21:27 +00003274@metrics.collector.collect_metrics('gclient diff')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00003275def CMDdiff(parser, args):
3276 """Displays local diff for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00003277 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
3278 help='override deps for the specified (comma-separated) '
3279 'platform(s); \'all\' will process all deps_os '
3280 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00003281 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00003282 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00003283 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00003284 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00003285 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00003286 client.PrintLocationAndContents()
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00003287 return client.RunOnDeps('diff', args)
3288
3289
Edward Lemur3298e7b2018-07-17 18:21:27 +00003290@metrics.collector.collect_metrics('gclient revert')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00003291def CMDrevert(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00003292 """Reverts all modifications in every dependencies.
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00003293
3294 That's the nuclear option to get back to a 'clean' state. It removes anything
agabled437d762016-10-17 09:35:11 -07003295 that shows up in git status."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00003296 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
3297 help='override deps for the specified (comma-separated) '
3298 'platform(s); \'all\' will process all deps_os '
3299 'references')
3300 parser.add_option('-n', '--nohooks', action='store_true',
3301 help='don\'t run hooks after the revert is complete')
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00003302 parser.add_option('-p', '--noprehooks', action='store_true',
3303 help='don\'t run pre-DEPS hooks', default=False)
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +00003304 parser.add_option('--upstream', action='store_true',
3305 help='Make repo state match upstream branch.')
Josip Sokcevic14a83ae2020-05-21 01:36:34 +00003306 parser.add_option('--break_repo_locks',
3307 action='store_true',
3308 help='No longer used.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00003309 (options, args) = parser.parse_args(args)
Josip Sokcevic14a83ae2020-05-21 01:36:34 +00003310 if options.break_repo_locks:
3311 print('Warning: break_repo_locks is no longer used. Please remove its ' +
3312 'usage.')
3313
maruel@chromium.org5ca27692010-05-26 19:32:41 +00003314 # --force is implied.
3315 options.force = True
steveblock@chromium.org98e69452012-02-16 16:36:43 +00003316 options.reset = False
3317 options.delete_unversioned_trees = False
agablec903d732016-07-26 09:07:24 -07003318 options.merge = False
maruel@chromium.org2806acc2009-05-15 12:33:34 +00003319 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00003320 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00003321 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00003322 return client.RunOnDeps('revert', args)
3323
3324
Edward Lemur3298e7b2018-07-17 18:21:27 +00003325@metrics.collector.collect_metrics('gclient runhooks')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00003326def CMDrunhooks(parser, args):
3327 """Runs hooks for files that have been modified in the local working copy."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00003328 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
3329 help='override deps for the specified (comma-separated) '
3330 'platform(s); \'all\' will process all deps_os '
3331 'references')
3332 parser.add_option('-f', '--force', action='store_true', default=True,
3333 help='Deprecated. No effect.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00003334 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00003335 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00003336 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00003337 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00003338 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00003339 client.PrintLocationAndContents()
maruel@chromium.org5df6a462009-08-28 18:52:26 +00003340 options.force = True
maruel@chromium.org5ca27692010-05-26 19:32:41 +00003341 options.nohooks = False
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00003342 return client.RunOnDeps('runhooks', args)
3343
3344
Edward Lemur3298e7b2018-07-17 18:21:27 +00003345@metrics.collector.collect_metrics('gclient revinfo')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00003346def CMDrevinfo(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00003347 """Outputs revision info mapping for the client and its dependencies.
maruel@chromium.org9eda4112010-06-11 18:56:10 +00003348
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00003349 This allows the capture of an overall 'revision' for the source tree that
maruel@chromium.org9eda4112010-06-11 18:56:10 +00003350 can be used to reproduce the same tree in the future. It is only useful for
agabled437d762016-10-17 09:35:11 -07003351 'unpinned dependencies', i.e. DEPS/deps references without a git hash.
3352 A git branch name isn't 'pinned' since the actual commit can change.
maruel@chromium.org9eda4112010-06-11 18:56:10 +00003353 """
3354 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
3355 help='override deps for the specified (comma-separated) '
3356 'platform(s); \'all\' will process all deps_os '
3357 'references')
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00003358 parser.add_option('-a', '--actual', action='store_true',
3359 help='gets the actual checked out revisions instead of the '
3360 'ones specified in the DEPS and .gclient files')
maruel@chromium.org9eda4112010-06-11 18:56:10 +00003361 parser.add_option('-s', '--snapshot', action='store_true',
3362 help='creates a snapshot .gclient file of the current '
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00003363 'version of all repositories to reproduce the tree, '
3364 'implies -a')
Edward Lesmesbb16e332018-03-30 17:54:51 -04003365 parser.add_option('--filter', action='append', dest='filter',
Edward Lesmesdaa76d22018-03-06 14:56:57 -05003366 help='Display revision information only for the specified '
Edward Lesmesbb16e332018-03-30 17:54:51 -04003367 'dependencies (filtered by URL or path).')
Edward Lesmesc2960242018-03-06 20:50:15 -05003368 parser.add_option('--output-json',
3369 help='Output a json document to this path containing '
3370 'information about the revisions.')
Joey Scarr8d3925b2018-07-15 23:36:25 +00003371 parser.add_option('--ignore-dep-type', choices=['git', 'cipd'],
3372 help='Specify to skip processing of a certain type of dep.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00003373 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00003374 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00003375 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00003376 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00003377 client.PrintRevInfo()
maruel@chromium.org79692d62010-05-14 18:57:13 +00003378 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00003379
3380
Edward Lemur3298e7b2018-07-17 18:21:27 +00003381@metrics.collector.collect_metrics('gclient getdep')
Edward Lesmes411041f2018-04-05 20:12:55 -04003382def CMDgetdep(parser, args):
Josip Sokcevic7b5e3d72023-06-13 00:28:23 +00003383 """Gets revision information and variable values from a DEPS file.
3384
3385 If key doesn't exist or is incorrectly declared, this script exits with exit
3386 code 2."""
Edward Lesmes411041f2018-04-05 20:12:55 -04003387 parser.add_option('--var', action='append',
3388 dest='vars', metavar='VAR', default=[],
3389 help='Gets the value of a given variable.')
3390 parser.add_option('-r', '--revision', action='append',
Edward Lemuraf3328f2018-11-19 14:11:46 +00003391 dest='getdep_revisions', metavar='DEP', default=[],
Edward Lesmes411041f2018-04-05 20:12:55 -04003392 help='Gets the revision/version for the given dependency. '
3393 'If it is a git dependency, dep must be a path. If it '
3394 'is a CIPD dependency, dep must be of the form '
3395 'path:package.')
3396 parser.add_option('--deps-file', default='DEPS',
3397 # TODO(ehmaldonado): Try to find the DEPS file pointed by
3398 # .gclient first.
3399 help='The DEPS file to be edited. Defaults to the DEPS '
3400 'file in the current directory.')
3401 (options, args) = parser.parse_args(args)
3402
3403 if not os.path.isfile(options.deps_file):
3404 raise gclient_utils.Error(
3405 'DEPS file %s does not exist.' % options.deps_file)
3406 with open(options.deps_file) as f:
3407 contents = f.read()
Edward Lemuraf3328f2018-11-19 14:11:46 +00003408 client = GClient.LoadCurrentConfig(options)
3409 if client is not None:
3410 builtin_vars = client.get_builtin_vars()
3411 else:
Edward Lemurca879322019-09-09 20:18:13 +00003412 logging.warning(
Edward Lemuraf3328f2018-11-19 14:11:46 +00003413 'Couldn\'t find a valid gclient config. Will attempt to parse the DEPS '
3414 'file without support for built-in variables.')
3415 builtin_vars = None
3416 local_scope = gclient_eval.Exec(contents, options.deps_file,
3417 builtin_vars=builtin_vars)
Edward Lesmes411041f2018-04-05 20:12:55 -04003418
3419 for var in options.vars:
3420 print(gclient_eval.GetVar(local_scope, var))
3421
Joanna Wange36c6bb2023-08-30 22:09:59 +00003422 commits = {}
3423 if local_scope.get('git_dependencies'
3424 ) == gclient_eval.SUBMODULES and options.getdep_revisions:
3425 commits.update(
3426 scm_git.GIT.GetSubmoduleCommits(
3427 os.getcwd(),
3428 [path for path in options.getdep_revisions if ':' not in path]))
3429
Edward Lemuraf3328f2018-11-19 14:11:46 +00003430 for name in options.getdep_revisions:
Edward Lesmes411041f2018-04-05 20:12:55 -04003431 if ':' in name:
3432 name, _, package = name.partition(':')
3433 if not name or not package:
3434 parser.error(
3435 'Wrong CIPD format: %s:%s should be of the form path:pkg.'
3436 % (name, package))
3437 print(gclient_eval.GetCIPD(local_scope, name, package))
Joanna Wange36c6bb2023-08-30 22:09:59 +00003438 elif commits:
3439 print(commits[name])
Edward Lesmes411041f2018-04-05 20:12:55 -04003440 else:
Josip Sokcevic7b5e3d72023-06-13 00:28:23 +00003441 try:
3442 print(gclient_eval.GetRevision(local_scope, name))
3443 except KeyError as e:
3444 print(repr(e), file=sys.stderr)
3445 sys.exit(2)
Edward Lesmes411041f2018-04-05 20:12:55 -04003446
3447
Edward Lemur3298e7b2018-07-17 18:21:27 +00003448@metrics.collector.collect_metrics('gclient setdep')
Edward Lesmes6f64a052018-03-20 17:35:49 -04003449def CMDsetdep(parser, args):
Edward Lesmes0ecf6d62018-04-05 18:28:55 -04003450 """Modifies dependency revisions and variable values in a DEPS file"""
Edward Lesmes6f64a052018-03-20 17:35:49 -04003451 parser.add_option('--var', action='append',
3452 dest='vars', metavar='VAR=VAL', default=[],
3453 help='Sets a variable to the given value with the format '
3454 'name=value.')
3455 parser.add_option('-r', '--revision', action='append',
Edward Lemuraf3328f2018-11-19 14:11:46 +00003456 dest='setdep_revisions', metavar='DEP@REV', default=[],
Edward Lesmes6f64a052018-03-20 17:35:49 -04003457 help='Sets the revision/version for the dependency with '
3458 'the format dep@rev. If it is a git dependency, dep '
3459 'must be a path and rev must be a git hash or '
3460 'reference (e.g. src/dep@deadbeef). If it is a CIPD '
3461 'dependency, dep must be of the form path:package and '
3462 'rev must be the package version '
3463 '(e.g. src/pkg:chromium/pkg@2.1-cr0).')
3464 parser.add_option('--deps-file', default='DEPS',
3465 # TODO(ehmaldonado): Try to find the DEPS file pointed by
3466 # .gclient first.
3467 help='The DEPS file to be edited. Defaults to the DEPS '
3468 'file in the current directory.')
3469 (options, args) = parser.parse_args(args)
Edward Lesmes0ecf6d62018-04-05 18:28:55 -04003470 if args:
3471 parser.error('Unused arguments: "%s"' % '" "'.join(args))
Edward Lesmesae6836e2018-11-19 15:27:20 +00003472 if not options.setdep_revisions and not options.vars:
Edward Lesmes0ecf6d62018-04-05 18:28:55 -04003473 parser.error(
3474 'You must specify at least one variable or revision to modify.')
Edward Lesmes6f64a052018-03-20 17:35:49 -04003475
Edward Lesmes6f64a052018-03-20 17:35:49 -04003476 if not os.path.isfile(options.deps_file):
3477 raise gclient_utils.Error(
3478 'DEPS file %s does not exist.' % options.deps_file)
3479 with open(options.deps_file) as f:
3480 contents = f.read()
Edward Lemuraf3328f2018-11-19 14:11:46 +00003481
3482 client = GClient.LoadCurrentConfig(options)
3483 if client is not None:
3484 builtin_vars = client.get_builtin_vars()
3485 else:
Edward Lemurca879322019-09-09 20:18:13 +00003486 logging.warning(
Edward Lemuraf3328f2018-11-19 14:11:46 +00003487 'Couldn\'t find a valid gclient config. Will attempt to parse the DEPS '
3488 'file without support for built-in variables.')
3489 builtin_vars = None
3490
3491 local_scope = gclient_eval.Exec(contents, options.deps_file,
3492 builtin_vars=builtin_vars)
Edward Lesmes6f64a052018-03-20 17:35:49 -04003493
Aravind Vasudevancb8023d2023-07-07 00:03:45 +00003494 # Create a set of all git submodules.
Aravind Vasudevanf44f2192023-08-12 01:05:50 +00003495 cwd = os.path.dirname(options.deps_file) or os.getcwd()
3496 git_modules = None
Aravind Vasudevan11d847f2023-07-07 19:35:12 +00003497 if 'git_dependencies' in local_scope and local_scope['git_dependencies'] in (
3498 gclient_eval.SUBMODULES, gclient_eval.SYNC):
Aravind Vasudevanf44f2192023-08-12 01:05:50 +00003499 try:
3500 submodule_status = subprocess2.check_output(
3501 ['git', 'submodule', 'status'], cwd=cwd).decode('utf-8')
3502 git_modules = {l.split()[1] for l in submodule_status.splitlines()}
3503 except subprocess2.CalledProcessError as e:
3504 print('Warning: gitlinks won\'t be updated: ', e)
Aravind Vasudevancb8023d2023-07-07 00:03:45 +00003505
Edward Lesmes6f64a052018-03-20 17:35:49 -04003506 for var in options.vars:
3507 name, _, value = var.partition('=')
3508 if not name or not value:
Edward Lesmes0ecf6d62018-04-05 18:28:55 -04003509 parser.error(
Edward Lesmes6f64a052018-03-20 17:35:49 -04003510 'Wrong var format: %s should be of the form name=value.' % var)
Edward Lesmes3d993812018-04-02 12:52:49 -04003511 if name in local_scope['vars']:
3512 gclient_eval.SetVar(local_scope, name, value)
3513 else:
3514 gclient_eval.AddVar(local_scope, name, value)
Edward Lesmes6f64a052018-03-20 17:35:49 -04003515
Edward Lemuraf3328f2018-11-19 14:11:46 +00003516 for revision in options.setdep_revisions:
Edward Lesmes6f64a052018-03-20 17:35:49 -04003517 name, _, value = revision.partition('@')
3518 if not name or not value:
Edward Lesmes0ecf6d62018-04-05 18:28:55 -04003519 parser.error(
Edward Lesmes6f64a052018-03-20 17:35:49 -04003520 'Wrong dep format: %s should be of the form dep@rev.' % revision)
3521 if ':' in name:
3522 name, _, package = name.partition(':')
3523 if not name or not package:
Edward Lesmes0ecf6d62018-04-05 18:28:55 -04003524 parser.error(
Edward Lesmes6f64a052018-03-20 17:35:49 -04003525 'Wrong CIPD format: %s:%s should be of the form path:pkg@version.'
3526 % (name, package))
3527 gclient_eval.SetCIPD(local_scope, name, package, value)
3528 else:
Aravind Vasudevancb8023d2023-07-07 00:03:45 +00003529 # Update DEPS only when `git_dependencies` == DEPS or SYNC.
3530 # git_dependencies is defaulted to DEPS when not set.
3531 if 'git_dependencies' not in local_scope or local_scope[
3532 'git_dependencies'] in (gclient_eval.DEPS, gclient_eval.SYNC):
3533 gclient_eval.SetRevision(local_scope, name, value)
3534
3535 # Update git submodules when `git_dependencies` == SYNC or SUBMODULES.
Aravind Vasudevanf44f2192023-08-12 01:05:50 +00003536 if git_modules and 'git_dependencies' in local_scope and local_scope[
Aravind Vasudevancb8023d2023-07-07 00:03:45 +00003537 'git_dependencies'] in (gclient_eval.SUBMODULES, gclient_eval.SYNC):
Josip Sokcevic5561f8b2023-08-21 16:00:42 +00003538 git_module_name = name
3539 if not 'use_relative_paths' in local_scope or \
3540 local_scope['use_relative_paths'] != True:
3541 deps_dir = os.path.dirname(os.path.abspath(options.deps_file))
3542 gclient_path = gclient_paths.FindGclientRoot(deps_dir)
3543 delta_path = None
3544 if gclient_path:
3545 delta_path = os.path.relpath(deps_dir,
3546 os.path.abspath(gclient_path))
3547 if delta_path:
3548 prefix_length = len(delta_path.replace(os.path.sep, '/')) + 1
3549 git_module_name = name[prefix_length:]
Aravind Vasudevancb8023d2023-07-07 00:03:45 +00003550 # gclient setdep should update the revision, i.e., the gitlink only
3551 # when the submodule entry is already present within .gitmodules.
Josip Sokcevic5561f8b2023-08-21 16:00:42 +00003552 if git_module_name not in git_modules:
Aravind Vasudevancb8023d2023-07-07 00:03:45 +00003553 raise KeyError(
Josip Sokcevic5561f8b2023-08-21 16:00:42 +00003554 f'Could not find any dependency called "{git_module_name}" in '
3555 f'.gitmodules.')
Aravind Vasudevancb8023d2023-07-07 00:03:45 +00003556
3557 # Update the gitlink for the submodule.
3558 subprocess2.call([
3559 'git', 'update-index', '--add', '--cacheinfo',
Josip Sokcevic5561f8b2023-08-21 16:00:42 +00003560 f'160000,{value},{git_module_name}'
Aravind Vasudevanf44f2192023-08-12 01:05:50 +00003561 ],
3562 cwd=cwd)
Edward Lesmes6f64a052018-03-20 17:35:49 -04003563
John Emau7aa68242020-02-20 19:44:53 +00003564 with open(options.deps_file, 'wb') as f:
3565 f.write(gclient_eval.RenderDEPSFile(local_scope).encode('utf-8'))
Edward Lesmes6f64a052018-03-20 17:35:49 -04003566
Josip Sokcevic5561f8b2023-08-21 16:00:42 +00003567 if git_modules:
3568 subprocess2.call(['git', 'add', options.deps_file], cwd=cwd)
3569 print('Changes have been staged. See changes with `git status`.\n'
3570 'Use `git commit -m "Manual roll"` to commit your changes. \n'
3571 'Run gclient sync to update your local dependency checkout.')
3572
Edward Lesmes6f64a052018-03-20 17:35:49 -04003573
Edward Lemur3298e7b2018-07-17 18:21:27 +00003574@metrics.collector.collect_metrics('gclient verify')
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00003575def CMDverify(parser, args):
3576 """Verifies the DEPS file deps are only from allowed_hosts."""
3577 (options, args) = parser.parse_args(args)
3578 client = GClient.LoadCurrentConfig(options)
3579 if not client:
3580 raise gclient_utils.Error('client not configured; see \'gclient config\'')
3581 client.RunOnDeps(None, [])
3582 # Look at each first-level dependency of this gclient only.
3583 for dep in client.dependencies:
3584 bad_deps = dep.findDepsFromNotAllowedHosts()
3585 if not bad_deps:
3586 continue
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00003587 print("There are deps from not allowed hosts in file %s" % dep.deps_file)
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00003588 for bad_dep in bad_deps:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00003589 print("\t%s at %s" % (bad_dep.name, bad_dep.url))
3590 print("allowed_hosts:", ', '.join(dep.allowed_hosts))
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00003591 sys.stdout.flush()
3592 raise gclient_utils.Error(
3593 'dependencies from disallowed hosts; check your DEPS file.')
3594 return 0
3595
Edward Lemur32e3d1e2018-07-12 00:54:05 +00003596
3597@subcommand.epilog("""For more information on what metrics are we collecting and
Edward Lemur8a2e3312018-07-12 21:15:09 +00003598why, please read metrics.README.md or visit https://bit.ly/2ufRS4p""")
Edward Lemur3298e7b2018-07-17 18:21:27 +00003599@metrics.collector.collect_metrics('gclient metrics')
Edward Lemur32e3d1e2018-07-12 00:54:05 +00003600def CMDmetrics(parser, args):
3601 """Reports, and optionally modifies, the status of metric collection."""
3602 parser.add_option('--opt-in', action='store_true', dest='enable_metrics',
3603 help='Opt-in to metrics collection.',
3604 default=None)
3605 parser.add_option('--opt-out', action='store_false', dest='enable_metrics',
3606 help='Opt-out of metrics collection.')
3607 options, args = parser.parse_args(args)
3608 if args:
3609 parser.error('Unused arguments: "%s"' % '" "'.join(args))
3610 if not metrics.collector.config.is_googler:
3611 print("You're not a Googler. Metrics collection is disabled for you.")
3612 return 0
3613
3614 if options.enable_metrics is not None:
3615 metrics.collector.config.opted_in = options.enable_metrics
3616
3617 if metrics.collector.config.opted_in is None:
3618 print("You haven't opted in or out of metrics collection.")
3619 elif metrics.collector.config.opted_in:
3620 print("You have opted in. Thanks!")
3621 else:
3622 print("You have opted out. Please consider opting in.")
3623 return 0
3624
3625
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00003626class OptionParser(optparse.OptionParser):
szager@chromium.orge2e03202012-07-31 18:05:16 +00003627 gclientfile_default = os.environ.get('GCLIENT_FILE', '.gclient')
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00003628
3629 def __init__(self, **kwargs):
3630 optparse.OptionParser.__init__(
3631 self, version='%prog ' + __version__, **kwargs)
3632
Aravind Vasudevan3d760cc2023-03-30 20:36:14 +00003633 # Some arm boards have issues with parallel sync.
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00003634 if platform.machine().startswith('arm'):
Aravind Vasudevan3d760cc2023-03-30 20:36:14 +00003635 jobs = 1
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00003636 else:
3637 jobs = max(8, gclient_utils.NumLocalCpus())
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00003638
3639 self.add_option(
3640 '-j', '--jobs', default=jobs, type='int',
3641 help='Specify how many SCM commands can run in parallel; defaults to '
tnagel@chromium.orga2aaa632014-02-28 21:47:27 +00003642 '%default on this machine')
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00003643 self.add_option(
3644 '-v', '--verbose', action='count', default=0,
3645 help='Produces additional output for diagnostics. Can be used up to '
3646 'three times for more logging info.')
3647 self.add_option(
3648 '--gclientfile', dest='config_filename',
3649 help='Specify an alternate %s file' % self.gclientfile_default)
3650 self.add_option(
3651 '--spec',
3652 help='create a gclient file containing the provided string. Due to '
3653 'Cygwin/Python brokenness, it can\'t contain any newlines.')
3654 self.add_option(
3655 '--no-nag-max', default=False, action='store_true',
scottmg@chromium.orgf547c802013-09-27 17:55:26 +00003656 help='Ignored for backwards compatibility.')
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00003657
Edward Lemur3298e7b2018-07-17 18:21:27 +00003658 def parse_args(self, args=None, _values=None):
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00003659 """Integrates standard options processing."""
Edward Lemur3298e7b2018-07-17 18:21:27 +00003660 # Create an optparse.Values object that will store only the actual passed
3661 # options, without the defaults.
3662 actual_options = optparse.Values()
3663 _, args = optparse.OptionParser.parse_args(self, args, actual_options)
3664 # Create an optparse.Values object with the default options.
3665 options = optparse.Values(self.get_default_values().__dict__)
3666 # Update it with the options passed by the user.
3667 options._update_careful(actual_options.__dict__)
3668 # Store the options passed by the user in an _actual_options attribute.
3669 # We store only the keys, and not the values, since the values can contain
3670 # arbitrary information, which might be PII.
Edward Lemuree7b9dd2019-07-20 01:29:08 +00003671 metrics.collector.add('arguments', list(actual_options.__dict__))
Edward Lemur3298e7b2018-07-17 18:21:27 +00003672
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00003673 levels = [logging.ERROR, logging.WARNING, logging.INFO, logging.DEBUG]
3674 logging.basicConfig(
3675 level=levels[min(options.verbose, len(levels) - 1)],
maruel@chromium.org0895b752011-08-26 20:40:33 +00003676 format='%(module)s(%(lineno)d) %(funcName)s:%(message)s')
szager@chromium.orge2e03202012-07-31 18:05:16 +00003677 if options.config_filename and options.spec:
Quinten Yearsley925cedb2020-04-13 17:49:39 +00003678 self.error('Cannot specify both --gclientfile and --spec')
rdsmith@chromium.orgd9591f02014-02-05 19:28:20 +00003679 if (options.config_filename and
3680 options.config_filename != os.path.basename(options.config_filename)):
3681 self.error('--gclientfile target must be a filename, not a path')
szager@chromium.orge2e03202012-07-31 18:05:16 +00003682 if not options.config_filename:
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00003683 options.config_filename = self.gclientfile_default
maruel@chromium.org0895b752011-08-26 20:40:33 +00003684 options.entries_filename = options.config_filename + '_entries'
3685 if options.jobs < 1:
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00003686 self.error('--jobs must be 1 or higher')
maruel@chromium.org0895b752011-08-26 20:40:33 +00003687
3688 # These hacks need to die.
3689 if not hasattr(options, 'revisions'):
3690 # GClient.RunOnDeps expects it even if not applicable.
3691 options.revisions = []
Joanna Wanga84a16b2022-07-27 18:52:17 +00003692 if not hasattr(options, 'experiments'):
3693 options.experiments = []
smutae7ea312016-07-18 11:59:41 -07003694 if not hasattr(options, 'head'):
3695 options.head = None
maruel@chromium.org0895b752011-08-26 20:40:33 +00003696 if not hasattr(options, 'nohooks'):
3697 options.nohooks = True
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00003698 if not hasattr(options, 'noprehooks'):
3699 options.noprehooks = True
maruel@chromium.org0895b752011-08-26 20:40:33 +00003700 if not hasattr(options, 'deps_os'):
3701 options.deps_os = None
maruel@chromium.org0895b752011-08-26 20:40:33 +00003702 if not hasattr(options, 'force'):
3703 options.force = None
3704 return (options, args)
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00003705
maruel@chromium.org39c0b222013-08-17 16:57:01 +00003706
3707def disable_buffering():
3708 # Make stdout auto-flush so buildbot doesn't kill us during lengthy
3709 # operations. Python as a strong tendency to buffer sys.stdout.
3710 sys.stdout = gclient_utils.MakeFileAutoFlush(sys.stdout)
3711 # Make stdout annotated with the thread ids.
3712 sys.stdout = gclient_utils.MakeFileAnnotated(sys.stdout)
maruel@chromium.org0895b752011-08-26 20:40:33 +00003713
3714
Elly Fong-Jones7b294392019-04-18 18:32:10 +00003715def path_contains_tilde():
3716 for element in os.environ['PATH'].split(os.pathsep):
Henrique Ferreiro4ef32212019-04-29 23:32:31 +00003717 if element.startswith('~') and os.path.abspath(
3718 os.path.realpath(os.path.expanduser(element))) == DEPOT_TOOLS_DIR:
Elly Fong-Jones7b294392019-04-18 18:32:10 +00003719 return True
3720 return False
3721
3722
3723def can_run_gclient_and_helpers():
maruel@chromium.org82798cb2012-02-23 18:16:12 +00003724 if sys.hexversion < 0x02060000:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00003725 print(
maruel@chromium.org82798cb2012-02-23 18:16:12 +00003726 '\nYour python version %s is unsupported, please upgrade.\n' %
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00003727 sys.version.split(' ', 1)[0],
3728 file=sys.stderr)
Elly Fong-Jones7b294392019-04-18 18:32:10 +00003729 return False
bcwhite@chromium.org6683ab42013-02-11 16:13:47 +00003730 if not sys.executable:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00003731 print(
3732 '\nPython cannot find the location of it\'s own executable.\n',
3733 file=sys.stderr)
Elly Fong-Jones7b294392019-04-18 18:32:10 +00003734 return False
3735 if path_contains_tilde():
3736 print(
3737 '\nYour PATH contains a literal "~", which works in some shells ' +
3738 'but will break when python tries to run subprocesses. ' +
3739 'Replace the "~" with $HOME.\n' +
3740 'See https://crbug.com/952865.\n',
3741 file=sys.stderr)
3742 return False
3743 return True
3744
3745
3746def main(argv):
3747 """Doesn't parse the arguments here, just find the right subcommand to
3748 execute."""
3749 if not can_run_gclient_and_helpers():
bcwhite@chromium.org6683ab42013-02-11 16:13:47 +00003750 return 2
maruel@chromium.org39c0b222013-08-17 16:57:01 +00003751 fix_encoding.fix_encoding()
3752 disable_buffering()
iannucci@chromium.org596cd5c2016-04-04 21:34:39 +00003753 setup_color.init()
maruel@chromium.org39c0b222013-08-17 16:57:01 +00003754 dispatcher = subcommand.CommandDispatcher(__name__)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00003755 try:
maruel@chromium.org39c0b222013-08-17 16:57:01 +00003756 return dispatcher.execute(OptionParser(), argv)
xusydoc@chromium.org2fd6c3f2013-05-03 21:57:55 +00003757 except KeyboardInterrupt:
3758 gclient_utils.GClientChildren.KillAllRemainingChildren()
3759 raise
vapier@chromium.orga81a56e2015-11-11 07:56:13 +00003760 except (gclient_utils.Error, subprocess2.CalledProcessError) as e:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00003761 print('Error: %s' % str(e), file=sys.stderr)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00003762 return 1
borenet@google.com6a9b1682014-03-24 18:35:23 +00003763 finally:
3764 gclient_utils.PrintWarnings()
sbc@chromium.org013731e2015-02-26 18:28:43 +00003765 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00003766
3767
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00003768if '__main__' == __name__:
Edward Lemur6f812e12018-07-31 22:45:57 +00003769 with metrics.collector.print_notice_and_exit():
sbc@chromium.org013731e2015-02-26 18:28:43 +00003770 sys.exit(main(sys.argv[1:]))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00003771
3772# vim: ts=2:sw=2:tw=80:et: