blob: 9fe9ffa72fd774a827b5eed4d1d091c3d05ed96b [file] [log] [blame]
iannucci@chromium.org405b87e2015-11-12 18:08:34 +00001#!/usr/bin/env python
thakis@chromium.org4f474b62012-01-18 01:31:29 +00002# Copyright (c) 2012 The Chromium Authors. All rights reserved.
maruel@chromium.orgba551772010-02-03 18:21:42 +00003# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00005
agabled437d762016-10-17 09:35:11 -07006"""Meta checkout dependency manager for Git."""
maruel@chromium.org39c0b222013-08-17 16:57:01 +00007# Files
8# .gclient : Current client configuration, written by 'config' command.
9# Format is a Python script defining 'solutions', a list whose
10# entries each are maps binding the strings "name" and "url"
11# to strings specifying the name and location of the client
12# module, as well as "custom_deps" to a map similar to the
13# deps section of the DEPS file below, as well as
14# "custom_hooks" to a list similar to the hooks sections of
15# the DEPS file below.
16# .gclient_entries : A cache constructed by 'update' command. Format is a
17# Python script defining 'entries', a list of the names
18# of all modules in the client
19# <module>/DEPS : Python script defining var 'deps' as a map from each
20# requisite submodule name to a URL where it can be found (via
21# one SCM)
22#
23# Hooks
24# .gclient and DEPS files may optionally contain a list named "hooks" to
25# allow custom actions to be performed based on files that have changed in the
26# working copy as a result of a "sync"/"update" or "revert" operation. This
27# can be prevented by using --nohooks (hooks run by default). Hooks can also
28# be forced to run with the "runhooks" operation. If "sync" is run with
29# --force, all known but not suppressed hooks will run regardless of the state
30# of the working copy.
31#
32# Each item in a "hooks" list is a dict, containing these two keys:
33# "pattern" The associated value is a string containing a regular
34# expression. When a file whose pathname matches the expression
35# is checked out, updated, or reverted, the hook's "action" will
36# run.
37# "action" A list describing a command to run along with its arguments, if
38# any. An action command will run at most one time per gclient
39# invocation, regardless of how many files matched the pattern.
40# The action is executed in the same directory as the .gclient
41# file. If the first item in the list is the string "python",
42# the current Python interpreter (sys.executable) will be used
43# to run the command. If the list contains string
44# "$matching_files" it will be removed from the list and the list
45# will be extended by the list of matching files.
46# "name" An optional string specifying the group to which a hook belongs
47# for overriding and organizing.
48#
49# Example:
50# hooks = [
51# { "pattern": "\\.(gif|jpe?g|pr0n|png)$",
52# "action": ["python", "image_indexer.py", "--all"]},
53# { "pattern": ".",
54# "name": "gyp",
55# "action": ["python", "src/build/gyp_chromium"]},
56# ]
57#
borenet@google.com2d1ee9e2013-10-15 08:13:16 +000058# Pre-DEPS Hooks
59# DEPS files may optionally contain a list named "pre_deps_hooks". These are
60# the same as normal hooks, except that they run before the DEPS are
61# processed. Pre-DEPS run with "sync" and "revert" unless the --noprehooks
62# flag is used.
rdsmith@chromium.orgd9591f02014-02-05 19:28:20 +000063#
maruel@chromium.org39c0b222013-08-17 16:57:01 +000064# Specifying a target OS
65# An optional key named "target_os" may be added to a gclient file to specify
66# one or more additional operating systems that should be considered when
Scott Grahamc4826742017-05-11 16:59:23 -070067# processing the deps_os/hooks_os dict of a DEPS file.
maruel@chromium.org39c0b222013-08-17 16:57:01 +000068#
69# Example:
70# target_os = [ "android" ]
71#
72# If the "target_os_only" key is also present and true, then *only* the
73# operating systems listed in "target_os" will be used.
74#
75# Example:
76# target_os = [ "ios" ]
77# target_os_only = True
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000078
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +000079from __future__ import print_function
80
maruel@chromium.org39c0b222013-08-17 16:57:01 +000081__version__ = '0.7'
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000082
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +020083import collections
maruel@chromium.org9e5317a2010-08-13 20:35:11 +000084import copy
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +000085import json
maruel@chromium.org754960e2009-09-21 12:31:05 +000086import logging
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000087import optparse
88import os
bradnelson@google.com4949dab2012-04-19 16:41:07 +000089import platform
maruel@chromium.org621939b2010-08-10 20:12:00 +000090import posixpath
msb@chromium.org2e38de72009-09-28 17:04:47 +000091import pprint
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000092import re
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000093import sys
ilevy@chromium.orgc28d3772013-07-12 19:42:37 +000094import time
bradnelson@google.com4949dab2012-04-19 16:41:07 +000095import urlparse
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000096
maruel@chromium.org35625c72011-03-23 17:34:02 +000097import fix_encoding
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +020098import gclient_eval
maruel@chromium.org5f3eee32009-09-17 00:34:30 +000099import gclient_scm
100import gclient_utils
szager@chromium.org848fd492014-04-09 19:06:44 +0000101import git_cache
nasser@codeaurora.org1f7a3d12010-02-04 15:11:50 +0000102from third_party.repo.progress import Progress
maruel@chromium.org39c0b222013-08-17 16:57:01 +0000103import subcommand
maruel@chromium.org31cb48a2011-04-04 18:01:36 +0000104import subprocess2
iannucci@chromium.org596cd5c2016-04-04 21:34:39 +0000105import setup_color
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000106
107
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +0200108class GNException(Exception):
109 pass
110
111
112def ToGNString(value, allow_dicts = True):
113 """Returns a stringified GN equivalent of the Python value.
114
115 allow_dicts indicates if this function will allow converting dictionaries
116 to GN scopes. This is only possible at the top level, you can't nest a
117 GN scope in a list, so this should be set to False for recursive calls."""
118 if isinstance(value, basestring):
119 if value.find('\n') >= 0:
120 raise GNException("Trying to print a string with a newline in it.")
121 return '"' + \
122 value.replace('\\', '\\\\').replace('"', '\\"').replace('$', '\\$') + \
123 '"'
124
125 if isinstance(value, unicode):
126 return ToGNString(value.encode('utf-8'))
127
128 if isinstance(value, bool):
129 if value:
130 return "true"
131 return "false"
132
133 # NOTE: some type handling removed compared to chromium/src copy.
134
135 raise GNException("Unsupported type when printing to GN.")
136
137
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200138class Hook(object):
139 """Descriptor of command ran before/after sync or on demand."""
140
Paweł Hajdan, Jr032d5452017-06-22 20:43:53 +0200141 def __init__(self, action, pattern=None, name=None, cwd=None, condition=None,
Daniel Chenga0c5f082017-10-19 13:35:19 -0700142 variables=None, verbose=False):
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200143 """Constructor.
144
145 Arguments:
146 action (list of basestring): argv of the command to run
147 pattern (basestring regex): noop with git; deprecated
148 name (basestring): optional name; no effect on operation
Paweł Hajdan, Jrc9364392017-06-14 17:11:56 +0200149 cwd (basestring): working directory to use
Paweł Hajdan, Jr032d5452017-06-22 20:43:53 +0200150 condition (basestring): condition when to run the hook
151 variables (dict): variables for evaluating the condition
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200152 """
153 self._action = gclient_utils.freeze(action)
154 self._pattern = pattern
155 self._name = name
Paweł Hajdan, Jrc9364392017-06-14 17:11:56 +0200156 self._cwd = cwd
Paweł Hajdan, Jr032d5452017-06-22 20:43:53 +0200157 self._condition = condition
158 self._variables = variables
Daniel Chenga0c5f082017-10-19 13:35:19 -0700159 self._verbose = verbose
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200160
161 @staticmethod
Michael Moss42d02c22018-02-05 10:32:24 -0800162 def from_dict(d, variables=None, verbose=False, conditions=None):
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200163 """Creates a Hook instance from a dict like in the DEPS file."""
Michael Moss42d02c22018-02-05 10:32:24 -0800164 # Merge any local and inherited conditions.
165 if conditions and d.get('condition'):
166 condition = '(%s) and (%s)' % (conditions, d['condition'])
167 else:
168 condition = conditions or d.get('condition')
Paweł Hajdan, Jr032d5452017-06-22 20:43:53 +0200169 return Hook(
170 d['action'],
171 d.get('pattern'),
172 d.get('name'),
173 d.get('cwd'),
Michael Moss42d02c22018-02-05 10:32:24 -0800174 condition,
Daniel Chenga0c5f082017-10-19 13:35:19 -0700175 variables=variables,
176 # Always print the header if not printing to a TTY.
177 verbose=verbose or not setup_color.IS_TTY)
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200178
179 @property
180 def action(self):
181 return self._action
182
183 @property
184 def pattern(self):
185 return self._pattern
186
187 @property
188 def name(self):
189 return self._name
190
Paweł Hajdan, Jrecf53fe2017-09-29 18:28:49 +0200191 @property
192 def condition(self):
193 return self._condition
194
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200195 def matches(self, file_list):
196 """Returns true if the pattern matches any of files in the list."""
197 if not self._pattern:
198 return True
199 pattern = re.compile(self._pattern)
200 return bool([f for f in file_list if pattern.search(f)])
201
202 def run(self, root):
Paweł Hajdan, Jr032d5452017-06-22 20:43:53 +0200203 """Executes the hook's command (provided the condition is met)."""
204 if (self._condition and
205 not gclient_eval.EvaluateCondition(self._condition, self._variables)):
206 return
207
Paweł Hajdan, Jrfc6196b2017-07-27 13:15:25 +0200208 cmd = [arg.format(**self._variables) for arg in self._action]
209
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200210 if cmd[0] == 'python':
211 # If the hook specified "python" as the first item, the action is a
212 # Python script. Run it by starting a new copy of the same
213 # interpreter.
214 cmd[0] = sys.executable
Nodir Turakulov0ffcc872017-11-09 16:44:58 -0800215 elif cmd[0] == 'vpython' and _detect_host_os() == 'win':
216 cmd[0] += '.bat'
Paweł Hajdan, Jrc9364392017-06-14 17:11:56 +0200217
218 cwd = root
219 if self._cwd:
220 cwd = os.path.join(cwd, self._cwd)
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200221 try:
222 start_time = time.time()
223 gclient_utils.CheckCallAndFilterAndHeader(
Daniel Chenga0c5f082017-10-19 13:35:19 -0700224 cmd, cwd=cwd, always=self._verbose)
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200225 except (gclient_utils.Error, subprocess2.CalledProcessError) as e:
226 # Use a discrete exit status code of 2 to indicate that a hook action
227 # failed. Users of this script may wish to treat hook action failures
228 # differently from VC failures.
229 print('Error: %s' % str(e), file=sys.stderr)
230 sys.exit(2)
231 finally:
232 elapsed_time = time.time() - start_time
233 if elapsed_time > 10:
234 print("Hook '%s' took %.2f secs" % (
235 gclient_utils.CommandToStr(cmd), elapsed_time))
236
237
Paweł Hajdan, Jrfc6196b2017-07-27 13:15:25 +0200238class DependencySettings(object):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000239 """Immutable configuration settings."""
240 def __init__(
Paweł Hajdan, Jrfc6196b2017-07-27 13:15:25 +0200241 self, parent, raw_url, url, managed, custom_deps, custom_vars,
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +0200242 custom_hooks, deps_file, should_process, relative,
243 condition, condition_value):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000244 # These are not mutable:
245 self._parent = parent
mmoss@chromium.org8f93f792014-08-26 23:24:09 +0000246 self._deps_file = deps_file
Paweł Hajdan, Jrfc6196b2017-07-27 13:15:25 +0200247 self._raw_url = raw_url
maruel@chromium.org064186c2011-09-27 23:53:33 +0000248 self._url = url
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +0200249 # The condition as string (or None). Useful to keep e.g. for flatten.
250 self._condition = condition
251 # Boolean value of the condition. If there's no condition, just True.
252 self._condition_value = condition_value
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000253 # 'managed' determines whether or not this dependency is synced/updated by
254 # gclient after gclient checks it out initially. The difference between
255 # 'managed' and 'should_process' is that the user specifies 'managed' via
smutae7ea312016-07-18 11:59:41 -0700256 # the --unmanaged command-line flag or a .gclient config, where
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000257 # 'should_process' is dynamically set by gclient if it goes over its
258 # recursion limit and controls gclient's behavior so it does not misbehave.
259 self._managed = managed
260 self._should_process = should_process
agabledce6ddc2016-09-08 10:02:16 -0700261 # If this is a recursed-upon sub-dependency, and the parent has
262 # use_relative_paths set, then this dependency should check out its own
263 # dependencies relative to that parent's path for this, rather than
264 # relative to the .gclient file.
265 self._relative = relative
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +0000266 # This is a mutable value which has the list of 'target_os' OSes listed in
267 # the current deps file.
268 self.local_target_os = None
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000269
270 # These are only set in .gclient and not in DEPS files.
271 self._custom_vars = custom_vars or {}
272 self._custom_deps = custom_deps or {}
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000273 self._custom_hooks = custom_hooks or []
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000274
maruel@chromium.org064186c2011-09-27 23:53:33 +0000275 # Post process the url to remove trailing slashes.
276 if isinstance(self._url, basestring):
277 # urls are sometime incorrectly written as proto://host/path/@rev. Replace
278 # it to proto://host/path@rev.
maruel@chromium.org064186c2011-09-27 23:53:33 +0000279 self._url = self._url.replace('/@', '@')
Paweł Hajdan, Jr7e9303b2017-05-23 14:38:27 +0200280 elif not isinstance(self._url, (None.__class__)):
maruel@chromium.org064186c2011-09-27 23:53:33 +0000281 raise gclient_utils.Error(
Paweł Hajdan, Jr7e9303b2017-05-23 14:38:27 +0200282 ('dependency url must be either string or None, '
283 'instead of %s') % self._url.__class__.__name__)
mmoss@chromium.orgd0b272b2013-01-30 23:55:33 +0000284 # Make any deps_file path platform-appropriate.
John Budorick0f7b2002018-01-19 15:46:17 -0800285 if self._deps_file:
286 for sep in ['/', '\\']:
287 self._deps_file = self._deps_file.replace(sep, os.sep)
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000288
289 @property
290 def deps_file(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000291 return self._deps_file
292
293 @property
294 def managed(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000295 return self._managed
296
297 @property
298 def parent(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000299 return self._parent
300
301 @property
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000302 def root(self):
303 """Returns the root node, a GClient object."""
304 if not self.parent:
305 # This line is to signal pylint that it could be a GClient instance.
306 return self or GClient(None, None)
307 return self.parent.root
308
309 @property
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000310 def should_process(self):
311 """True if this dependency should be processed, i.e. checked out."""
312 return self._should_process
313
314 @property
315 def custom_vars(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000316 return self._custom_vars.copy()
317
318 @property
319 def custom_deps(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000320 return self._custom_deps.copy()
321
maruel@chromium.org064186c2011-09-27 23:53:33 +0000322 @property
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000323 def custom_hooks(self):
324 return self._custom_hooks[:]
325
326 @property
Paweł Hajdan, Jrfc6196b2017-07-27 13:15:25 +0200327 def raw_url(self):
328 """URL before variable expansion."""
329 return self._raw_url
330
331 @property
maruel@chromium.org064186c2011-09-27 23:53:33 +0000332 def url(self):
Paweł Hajdan, Jrfc6196b2017-07-27 13:15:25 +0200333 """URL after variable expansion."""
maruel@chromium.org064186c2011-09-27 23:53:33 +0000334 return self._url
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000335
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000336 @property
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +0200337 def condition(self):
338 return self._condition
339
340 @property
341 def condition_value(self):
342 return self._condition_value
343
344 @property
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +0000345 def target_os(self):
346 if self.local_target_os is not None:
347 return tuple(set(self.local_target_os).union(self.parent.target_os))
348 else:
349 return self.parent.target_os
350
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000351 def get_custom_deps(self, name, url):
352 """Returns a custom deps if applicable."""
353 if self.parent:
354 url = self.parent.get_custom_deps(name, url)
355 # None is a valid return value to disable a dependency.
356 return self.custom_deps.get(name, url)
357
maruel@chromium.org064186c2011-09-27 23:53:33 +0000358
359class Dependency(gclient_utils.WorkItem, DependencySettings):
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000360 """Object that represents a dependency checkout."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000361
Paweł Hajdan, Jrfc6196b2017-07-27 13:15:25 +0200362 def __init__(self, parent, name, raw_url, url, managed, custom_deps,
agabledce6ddc2016-09-08 10:02:16 -0700363 custom_vars, custom_hooks, deps_file, should_process,
Edward Lemur231f5ea2018-01-31 19:02:36 +0100364 relative, condition, condition_value, print_outbuf=False):
maruel@chromium.org6ca8bf82011-09-19 23:04:30 +0000365 gclient_utils.WorkItem.__init__(self, name)
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000366 DependencySettings.__init__(
Paweł Hajdan, Jrfc6196b2017-07-27 13:15:25 +0200367 self, parent, raw_url, url, managed, custom_deps, custom_vars,
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +0200368 custom_hooks, deps_file, should_process, relative,
369 condition, condition_value)
maruel@chromium.org68988972011-09-20 14:11:42 +0000370
371 # This is in both .gclient and DEPS files:
maruel@chromium.org064186c2011-09-27 23:53:33 +0000372 self._deps_hooks = []
maruel@chromium.org68988972011-09-20 14:11:42 +0000373
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000374 self._pre_deps_hooks = []
375
maruel@chromium.org68988972011-09-20 14:11:42 +0000376 # Calculates properties:
maruel@chromium.org064186c2011-09-27 23:53:33 +0000377 self._parsed_url = None
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +0000378 self._dependencies = []
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +0200379 self._vars = {}
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +0200380 self._os_dependencies = {}
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +0200381 self._os_deps_hooks = {}
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200382
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000383 # A cache of the files affected by the current operation, necessary for
384 # hooks.
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000385 self._file_list = []
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000386 # List of host names from which dependencies are allowed.
387 # Default is an empty set, meaning unspecified in DEPS file, and hence all
388 # hosts will be allowed. Non-empty set means whitelist of hosts.
389 # allowed_hosts var is scoped to its DEPS file, and so it isn't recursive.
390 self._allowed_hosts = frozenset()
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +0200391 # Spec for .gni output to write (if any).
392 self._gn_args_file = None
393 self._gn_args = []
maruel@chromium.org85c2a192010-07-22 21:14:43 +0000394 # If it is not set to True, the dependency wasn't processed for its child
395 # dependency, i.e. its DEPS wasn't read.
maruel@chromium.org064186c2011-09-27 23:53:33 +0000396 self._deps_parsed = False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000397 # This dependency has been processed, i.e. checked out
maruel@chromium.org064186c2011-09-27 23:53:33 +0000398 self._processed = False
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000399 # This dependency had its pre-DEPS hooks run
400 self._pre_deps_hooks_ran = False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000401 # This dependency had its hook run
maruel@chromium.org064186c2011-09-27 23:53:33 +0000402 self._hooks_ran = False
kustermann@google.coma692e8f2013-04-18 08:32:04 +0000403 # This is the scm used to checkout self.url. It may be used by dependencies
404 # to get the datetime of the revision we checked out.
405 self._used_scm = None
szager@chromium.org4ad264b2014-05-20 04:43:47 +0000406 self._used_revision = None
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +0000407 # The actual revision we ended up getting, or None if that information is
408 # unavailable
409 self._got_revision = None
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000410
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000411 # This is a mutable value that overrides the normal recursion limit for this
412 # dependency. It is read from the actual DEPS file so cannot be set on
413 # class instantiation.
414 self.recursion_override = None
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000415 # recursedeps is a mutable value that selectively overrides the default
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000416 # 'no recursion' setting on a dep-by-dep basis. It will replace
417 # recursion_override.
iannucci@chromium.orgafa11ac2016-05-04 22:17:34 +0000418 #
419 # It will be a dictionary of {deps_name: {"deps_file": depfile_name}} or
420 # None.
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000421 self.recursedeps = None
hinoka885e5b12016-06-08 14:40:09 -0700422 # This is inherited from WorkItem. We want the URL to be a resource.
423 if url and isinstance(url, basestring):
424 # The url is usually given to gclient either as https://blah@123
qyearsley12fa6ff2016-08-24 09:18:40 -0700425 # or just https://blah. The @123 portion is irrelevant.
hinoka885e5b12016-06-08 14:40:09 -0700426 self.resources.append(url.split('@')[0])
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000427
Edward Lemur231f5ea2018-01-31 19:02:36 +0100428 # Controls whether we want to print git's output when we first clone the
429 # dependency
430 self.print_outbuf = print_outbuf
431
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000432 if not self.name and self.parent:
433 raise gclient_utils.Error('Dependency without name')
434
John Budorick0f7b2002018-01-19 15:46:17 -0800435 def ToLines(self):
436 s = []
437 condition_part = ([' "condition": %r,' % self.condition]
438 if self.condition else [])
439 s.extend([
440 ' # %s' % self.hierarchy(include_url=False),
441 ' "%s": {' % (self.name,),
442 ' "url": "%s",' % (self.raw_url,),
443 ] + condition_part + [
444 ' },',
445 '',
446 ])
447 return s
448
449
450
maruel@chromium.org470b5432011-10-11 18:18:19 +0000451 @property
452 def requirements(self):
453 """Calculate the list of requirements."""
454 requirements = set()
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000455 # self.parent is implicitly a requirement. This will be recursive by
456 # definition.
457 if self.parent and self.parent.name:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000458 requirements.add(self.parent.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000459
460 # For a tree with at least 2 levels*, the leaf node needs to depend
461 # on the level higher up in an orderly way.
462 # This becomes messy for >2 depth as the DEPS file format is a dictionary,
463 # thus unsorted, while the .gclient format is a list thus sorted.
464 #
465 # * _recursion_limit is hard coded 2 and there is no hope to change this
466 # value.
467 #
468 # Interestingly enough, the following condition only works in the case we
469 # want: self is a 2nd level node. 3nd level node wouldn't need this since
470 # they already have their parent as a requirement.
maruel@chromium.org470b5432011-10-11 18:18:19 +0000471 if self.parent and self.parent.parent and not self.parent.parent.parent:
472 requirements |= set(i.name for i in self.root.dependencies if i.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000473
maruel@chromium.org470b5432011-10-11 18:18:19 +0000474 if self.name:
475 requirements |= set(
476 obj.name for obj in self.root.subtree(False)
477 if (obj is not self
478 and obj.name and
479 self.name.startswith(posixpath.join(obj.name, ''))))
480 requirements = tuple(sorted(requirements))
481 logging.info('Dependency(%s).requirements = %s' % (self.name, requirements))
482 return requirements
483
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000484 @property
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000485 def try_recursedeps(self):
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000486 """Returns False if recursion_override is ever specified."""
487 if self.recursion_override is not None:
488 return False
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000489 return self.parent.try_recursedeps
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000490
491 @property
492 def recursion_limit(self):
493 """Returns > 0 if this dependency is not too recursed to be processed."""
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000494 # We continue to support the absence of recursedeps until tools and DEPS
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000495 # using recursion_override are updated.
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000496 if self.try_recursedeps and self.parent.recursedeps != None:
497 if self.name in self.parent.recursedeps:
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000498 return 1
499
500 if self.recursion_override is not None:
501 return self.recursion_override
502 return max(self.parent.recursion_limit - 1, 0)
503
maruel@chromium.org470b5432011-10-11 18:18:19 +0000504 def verify_validity(self):
505 """Verifies that this Dependency is fine to add as a child of another one.
506
507 Returns True if this entry should be added, False if it is a duplicate of
508 another entry.
509 """
510 logging.info('Dependency(%s).verify_validity()' % self.name)
511 if self.name in [s.name for s in self.parent.dependencies]:
512 raise gclient_utils.Error(
513 'The same name "%s" appears multiple times in the deps section' %
514 self.name)
515 if not self.should_process:
516 # Return early, no need to set requirements.
517 return True
518
519 # This require a full tree traversal with locks.
520 siblings = [d for d in self.root.subtree(False) if d.name == self.name]
521 for sibling in siblings:
maruel@chromium.orgb848d5b2012-10-10 23:25:50 +0000522 self_url = self.LateOverride(self.url)
523 sibling_url = sibling.LateOverride(sibling.url)
524 # Allow to have only one to be None or ''.
525 if self_url != sibling_url and bool(self_url) == bool(sibling_url):
maruel@chromium.org470b5432011-10-11 18:18:19 +0000526 raise gclient_utils.Error(
maruel@chromium.orgb848d5b2012-10-10 23:25:50 +0000527 ('Dependency %s specified more than once:\n'
528 ' %s [%s]\n'
529 'vs\n'
530 ' %s [%s]') % (
531 self.name,
532 sibling.hierarchy(),
533 sibling_url,
534 self.hierarchy(),
535 self_url))
maruel@chromium.org470b5432011-10-11 18:18:19 +0000536 # In theory we could keep it as a shadow of the other one. In
537 # practice, simply ignore it.
538 logging.warn('Won\'t process duplicate dependency %s' % sibling)
539 return False
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000540 return True
maruel@chromium.org064186c2011-09-27 23:53:33 +0000541
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000542 def LateOverride(self, url):
Paweł Hajdan, Jr7e9303b2017-05-23 14:38:27 +0200543 """Resolves the parsed url from url."""
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000544 assert self.parsed_url == None or not self.should_process, self.parsed_url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000545 parsed_url = self.get_custom_deps(self.name, url)
546 if parsed_url != url:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000547 logging.info(
548 'Dependency(%s).LateOverride(%s) -> %s' %
549 (self.name, url, parsed_url))
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000550 return parsed_url
551
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000552 if isinstance(url, basestring):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000553 parsed_url = urlparse.urlparse(url)
scr@chromium.orgf1eccaf2014-04-11 15:51:33 +0000554 if (not parsed_url[0] and
555 not re.match(r'^\w+\@[\w\.-]+\:[\w\/]+', parsed_url[2])):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000556 # A relative url. Fetch the real base.
557 path = parsed_url[2]
558 if not path.startswith('/'):
559 raise gclient_utils.Error(
560 'relative DEPS entry \'%s\' must begin with a slash' % url)
561 # Create a scm just to query the full url.
562 parent_url = self.parent.parsed_url
John Budorick0f7b2002018-01-19 15:46:17 -0800563 scm = self.CreateSCM(
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000564 parent_url, self.root.root_dir, None, self.outbuf)
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000565 parsed_url = scm.FullUrlForRelativeUrl(url)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000566 else:
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000567 parsed_url = url
maruel@chromium.org470b5432011-10-11 18:18:19 +0000568 logging.info(
569 'Dependency(%s).LateOverride(%s) -> %s' %
570 (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000571 return parsed_url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000572
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000573 if url is None:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000574 logging.info(
575 'Dependency(%s).LateOverride(%s) -> %s' % (self.name, url, url))
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000576 return url
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000577
578 raise gclient_utils.Error('Unknown url type')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000579
bratell@opera.comed2b4fe2013-12-16 14:34:12 +0000580 @staticmethod
Paweł Hajdan, Jr5ec77132017-08-16 19:21:06 +0200581 def MergeWithOsDeps(deps, deps_os, target_os_list, process_all_deps):
bratell@opera.comed2b4fe2013-12-16 14:34:12 +0000582 """Returns a new "deps" structure that is the deps sent in updated
583 with information from deps_os (the deps_os section of the DEPS
584 file) that matches the list of target os."""
bratell@opera.comed2b4fe2013-12-16 14:34:12 +0000585 new_deps = deps.copy()
Paweł Hajdan, Jrfd0057e2017-06-21 14:20:21 +0200586 for dep_os, os_deps in deps_os.iteritems():
587 for key, value in os_deps.iteritems():
588 if value is None:
589 # Make this condition very visible, so it's not a silent failure.
590 # It's unclear how to support None override in deps_os.
591 logging.error('Ignoring %r:%r in %r deps_os', key, value, dep_os)
592 continue
593
594 # Normalize value to be a dict which contains |should_process| metadata.
595 if isinstance(value, basestring):
596 value = {'url': value}
597 assert isinstance(value, collections.Mapping), (key, value)
Paweł Hajdan, Jr5ec77132017-08-16 19:21:06 +0200598 value['should_process'] = dep_os in target_os_list or process_all_deps
Paweł Hajdan, Jrfd0057e2017-06-21 14:20:21 +0200599
600 # Handle collisions/overrides.
601 if key in new_deps and new_deps[key] != value:
602 # Normalize the existing new_deps entry.
603 if isinstance(new_deps[key], basestring):
604 new_deps[key] = {'url': new_deps[key]}
605 assert isinstance(new_deps[key],
606 collections.Mapping), (key, new_deps[key])
607
608 # It's OK if the "override" sets the key to the same value.
609 # This is mostly for legacy reasons to keep existing DEPS files
610 # working. Often mac/ios and unix/android will do this.
611 if value['url'] != new_deps[key]['url']:
612 raise gclient_utils.Error(
613 ('Value from deps_os (%r; %r: %r) conflicts with existing deps '
614 'entry (%r).') % (dep_os, key, value, new_deps[key]))
615
616 # We'd otherwise overwrite |should_process| metadata, but a dep should
617 # be processed if _any_ of its references call for that.
618 value['should_process'] = (
619 value['should_process'] or
620 new_deps[key].get('should_process', True))
621
622 new_deps[key] = value
623
bratell@opera.comed2b4fe2013-12-16 14:34:12 +0000624 return new_deps
625
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200626 def _postprocess_deps(self, deps, rel_prefix):
627 """Performs post-processing of deps compared to what's in the DEPS file."""
Paweł Hajdan, Jr4426eaf2017-06-13 12:25:47 +0200628 # Make sure the dict is mutable, e.g. in case it's frozen.
629 deps = dict(deps)
630
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200631 # If a line is in custom_deps, but not in the solution, we want to append
632 # this line to the solution.
633 for d in self.custom_deps:
634 if d not in deps:
635 deps[d] = self.custom_deps[d]
Michael Moss42d02c22018-02-05 10:32:24 -0800636 # Make child deps conditional on any parent conditions. This ensures that,
637 # when flattened, recursed entries have the correct restrictions, even if
638 # not explicitly set in the recursed DEPS file. For instance, if
639 # "src/ios_foo" is conditional on "checkout_ios=True", then anything
640 # recursively included by "src/ios_foo/DEPS" should also require
641 # "checkout_ios=True".
642 if self.condition:
643 for dname, dval in deps.iteritems():
644 if isinstance(dval, basestring):
645 dval = {'url': dval}
646 deps[dname] = dval
647 else:
648 assert isinstance(dval, collections.Mapping)
649 if dval.get('condition'):
650 dval['condition'] = '(%s) and (%s)' % (
651 dval['condition'], self.condition)
652 else:
653 dval['condition'] = self.condition
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200654
655 if rel_prefix:
656 logging.warning('use_relative_paths enabled.')
657 rel_deps = {}
658 for d, url in deps.items():
659 # normpath is required to allow DEPS to use .. in their
660 # dependency local path.
661 rel_deps[os.path.normpath(os.path.join(rel_prefix, d))] = url
662 logging.warning('Updating deps by prepending %s.', rel_prefix)
663 deps = rel_deps
664
665 return deps
666
667 def _deps_to_objects(self, deps, use_relative_paths):
668 """Convert a deps dict to a dict of Dependency objects."""
669 deps_to_add = []
John Budorick0f7b2002018-01-19 15:46:17 -0800670 cipd_root = None
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200671 for name, dep_value in deps.iteritems():
672 should_process = self.recursion_limit and self.should_process
673 deps_file = self.deps_file
674 if self.recursedeps is not None:
675 ent = self.recursedeps.get(name)
676 if ent is not None:
677 deps_file = ent['deps_file']
678 if dep_value is None:
679 continue
John Budorick0f7b2002018-01-19 15:46:17 -0800680
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200681 condition = None
682 condition_value = True
683 if isinstance(dep_value, basestring):
Paweł Hajdan, Jrfc6196b2017-07-27 13:15:25 +0200684 raw_url = dep_value
John Budorick0f7b2002018-01-19 15:46:17 -0800685 dep_type = None
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200686 else:
687 # This should be guaranteed by schema checking in gclient_eval.
688 assert isinstance(dep_value, collections.Mapping)
John Budorick0f7b2002018-01-19 15:46:17 -0800689 raw_url = dep_value.get('url')
Paweł Hajdan, Jrfd0057e2017-06-21 14:20:21 +0200690 # Take into account should_process metadata set by MergeWithOsDeps.
691 should_process = (should_process and
692 dep_value.get('should_process', True))
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200693 condition = dep_value.get('condition')
John Budorick0f7b2002018-01-19 15:46:17 -0800694 dep_type = dep_value.get('dep_type')
Paweł Hajdan, Jrfc6196b2017-07-27 13:15:25 +0200695
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200696 if condition:
Paweł Hajdan, Jrd3790252017-07-03 21:06:24 +0200697 condition_value = gclient_eval.EvaluateCondition(
698 condition, self.get_vars())
Paweł Hajdan, Jr5ec77132017-08-16 19:21:06 +0200699 if not self._get_option('process_all_deps', False):
700 should_process = should_process and condition_value
John Budorick0f7b2002018-01-19 15:46:17 -0800701
702 if dep_type == 'cipd':
703 if not cipd_root:
704 cipd_root = gclient_scm.CipdRoot(
705 os.path.join(self.root.root_dir, self.name),
706 # TODO(jbudorick): Support other service URLs as necessary.
707 # Service URLs should be constant over the scope of a cipd
708 # root, so a var per DEPS file specifying the service URL
709 # should suffice.
710 'https://chrome-infra-packages.appspot.com')
711 for package in dep_value.get('packages', []):
712 deps_to_add.append(
713 CipdDependency(
714 self, name, package, cipd_root,
715 self.custom_vars, should_process, use_relative_paths,
716 condition, condition_value))
717 elif dep_type == 'git':
718 url = raw_url.format(**self.get_vars())
719 deps_to_add.append(
720 GitDependency(
721 self, name, raw_url, url, None, None, self.custom_vars, None,
722 deps_file, should_process, use_relative_paths, condition,
723 condition_value))
724 else:
725 url = raw_url.format(**self.get_vars())
726 deps_to_add.append(
727 Dependency(
728 self, name, raw_url, url, None, None, self.custom_vars, None,
729 deps_file, should_process, use_relative_paths, condition,
730 condition_value))
731
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200732 deps_to_add.sort(key=lambda x: x.name)
733 return deps_to_add
734
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000735 def ParseDepsFile(self):
maruel@chromium.org271375b2010-06-23 19:17:38 +0000736 """Parses the DEPS file for this dependency."""
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000737 assert not self.deps_parsed
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000738 assert not self.dependencies
iannucci@chromium.org0c3f3052014-05-22 00:29:22 +0000739
740 deps_content = None
cmp@chromium.org76ce73c2014-07-02 00:13:18 +0000741
742 # First try to locate the configured deps file. If it's missing, fallback
743 # to DEPS.
744 deps_files = [self.deps_file]
745 if 'DEPS' not in deps_files:
746 deps_files.append('DEPS')
747 for deps_file in deps_files:
748 filepath = os.path.join(self.root.root_dir, self.name, deps_file)
749 if os.path.isfile(filepath):
750 logging.info(
751 'ParseDepsFile(%s): %s file found at %s', self.name, deps_file,
752 filepath)
753 break
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000754 logging.info(
cmp@chromium.org76ce73c2014-07-02 00:13:18 +0000755 'ParseDepsFile(%s): No %s file found at %s', self.name, deps_file,
756 filepath)
757
758 if os.path.isfile(filepath):
maruel@chromium.org46304292010-10-28 11:42:00 +0000759 deps_content = gclient_utils.FileRead(filepath)
cmp@chromium.org76ce73c2014-07-02 00:13:18 +0000760 logging.debug('ParseDepsFile(%s) read:\n%s', self.name, deps_content)
iannucci@chromium.org0c3f3052014-05-22 00:29:22 +0000761
762 local_scope = {}
763 if deps_content:
Paweł Hajdan, Jrf1587bf2017-06-20 21:19:07 +0200764 global_scope = {
Paweł Hajdan, Jrfc6196b2017-07-27 13:15:25 +0200765 'Var': lambda var_name: '{%s}' % var_name,
Paweł Hajdan, Jrf1587bf2017-06-20 21:19:07 +0200766 'deps_os': {},
767 }
maruel@chromium.org46304292010-10-28 11:42:00 +0000768 # Eval the content.
769 try:
Paweł Hajdan, Jrc485d5a2017-06-02 12:08:09 +0200770 if self._get_option('validate_syntax', False):
John Budorick0f7b2002018-01-19 15:46:17 -0800771 local_scope = gclient_eval.Exec(
772 deps_content, global_scope, local_scope, filepath)
Paweł Hajdan, Jrc485d5a2017-06-02 12:08:09 +0200773 else:
774 exec(deps_content, global_scope, local_scope)
vapier@chromium.orga81a56e2015-11-11 07:56:13 +0000775 except SyntaxError as e:
maruel@chromium.org46304292010-10-28 11:42:00 +0000776 gclient_utils.SyntaxErrorToError(filepath, e)
iannucci@chromium.org0c3f3052014-05-22 00:29:22 +0000777
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000778 if 'allowed_hosts' in local_scope:
779 try:
780 self._allowed_hosts = frozenset(local_scope.get('allowed_hosts'))
781 except TypeError: # raised if non-iterable
782 pass
783 if not self._allowed_hosts:
784 logging.warning("allowed_hosts is specified but empty %s",
785 self._allowed_hosts)
786 raise gclient_utils.Error(
787 'ParseDepsFile(%s): allowed_hosts must be absent '
788 'or a non-empty iterable' % self.name)
789
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +0200790 self._gn_args_file = local_scope.get('gclient_gn_args_file')
791 self._gn_args = local_scope.get('gclient_gn_args', [])
792
Paweł Hajdan, Jr1407d002017-08-01 20:01:01 +0200793 self._vars = local_scope.get('vars', {})
794 if self.parent:
795 for key, value in self.parent.get_vars().iteritems():
796 if key in self._vars:
797 self._vars[key] = value
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200798 # Since we heavily post-process things, freeze ones which should
799 # reflect original state of DEPS.
Paweł Hajdan, Jr1407d002017-08-01 20:01:01 +0200800 self._vars = gclient_utils.freeze(self._vars)
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200801
802 # If use_relative_paths is set in the DEPS file, regenerate
803 # the dictionary using paths relative to the directory containing
804 # the DEPS file. Also update recursedeps if use_relative_paths is
805 # enabled.
806 # If the deps file doesn't set use_relative_paths, but the parent did
807 # (and therefore set self.relative on this Dependency object), then we
808 # want to modify the deps and recursedeps by prepending the parent
809 # directory of this dependency.
810 use_relative_paths = local_scope.get('use_relative_paths', False)
811 rel_prefix = None
812 if use_relative_paths:
813 rel_prefix = self.name
814 elif self._relative:
815 rel_prefix = os.path.dirname(self.name)
816
Paweł Hajdan, Jrfc6196b2017-07-27 13:15:25 +0200817 deps = {}
818 for key, value in local_scope.get('deps', {}).iteritems():
819 deps[key.format(**self.get_vars())] = value
820
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200821 if 'recursion' in local_scope:
822 self.recursion_override = local_scope.get('recursion')
823 logging.warning(
824 'Setting %s recursion to %d.', self.name, self.recursion_limit)
825 self.recursedeps = None
826 if 'recursedeps' in local_scope:
827 self.recursedeps = {}
828 for ent in local_scope['recursedeps']:
829 if isinstance(ent, basestring):
830 self.recursedeps[ent] = {"deps_file": self.deps_file}
831 else: # (depname, depsfilename)
832 self.recursedeps[ent[0]] = {"deps_file": ent[1]}
833 logging.warning('Found recursedeps %r.', repr(self.recursedeps))
834
835 if rel_prefix:
836 logging.warning('Updating recursedeps by prepending %s.', rel_prefix)
837 rel_deps = {}
838 for depname, options in self.recursedeps.iteritems():
839 rel_deps[
840 os.path.normpath(os.path.join(rel_prefix, depname))] = options
841 self.recursedeps = rel_deps
842
843 # If present, save 'target_os' in the local_target_os property.
844 if 'target_os' in local_scope:
845 self.local_target_os = local_scope['target_os']
846 # load os specific dependencies if defined. these dependencies may
847 # override or extend the values defined by the 'deps' member.
848 target_os_list = self.target_os
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +0200849 if 'deps_os' in local_scope:
850 for dep_os, os_deps in local_scope['deps_os'].iteritems():
851 self._os_dependencies[dep_os] = self._deps_to_objects(
852 self._postprocess_deps(os_deps, rel_prefix), use_relative_paths)
Paweł Hajdan, Jr357415c2017-07-24 14:35:28 +0200853 if target_os_list and not self._get_option(
854 'do_not_merge_os_specific_entries', False):
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +0200855 deps = self.MergeWithOsDeps(
Paweł Hajdan, Jr5ec77132017-08-16 19:21:06 +0200856 deps, local_scope['deps_os'], target_os_list,
857 self._get_option('process_all_deps', False))
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200858
859 deps_to_add = self._deps_to_objects(
860 self._postprocess_deps(deps, rel_prefix), use_relative_paths)
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000861
862 # override named sets of hooks by the custom hooks
863 hooks_to_run = []
864 hook_names_to_suppress = [c.get('name', '') for c in self.custom_hooks]
865 for hook in local_scope.get('hooks', []):
866 if hook.get('name', '') not in hook_names_to_suppress:
867 hooks_to_run.append(hook)
Scott Grahamc4826742017-05-11 16:59:23 -0700868 if 'hooks_os' in local_scope and target_os_list:
869 hooks_os = local_scope['hooks_os']
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +0200870
871 # Keep original contents of hooks_os for flatten.
872 for hook_os, os_hooks in hooks_os.iteritems():
873 self._os_deps_hooks[hook_os] = [
Michael Moss42d02c22018-02-05 10:32:24 -0800874 Hook.from_dict(hook, variables=self.get_vars(), verbose=True,
875 conditions=self.condition)
Paweł Hajdan, Jrd3790252017-07-03 21:06:24 +0200876 for hook in os_hooks]
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +0200877
Scott Grahamc4826742017-05-11 16:59:23 -0700878 # Specifically append these to ensure that hooks_os run after hooks.
Paweł Hajdan, Jr357415c2017-07-24 14:35:28 +0200879 if not self._get_option('do_not_merge_os_specific_entries', False):
880 for the_target_os in target_os_list:
881 the_target_os_hooks = hooks_os.get(the_target_os, [])
882 hooks_to_run.extend(the_target_os_hooks)
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000883
884 # add the replacements and any additions
885 for hook in self.custom_hooks:
886 if 'action' in hook:
887 hooks_to_run.append(hook)
888
Dirk Prankeda3a29e2017-02-27 15:29:36 -0800889 if self.recursion_limit:
Paweł Hajdan, Jr032d5452017-06-22 20:43:53 +0200890 self._pre_deps_hooks = [
Michael Moss42d02c22018-02-05 10:32:24 -0800891 Hook.from_dict(hook, variables=self.get_vars(), verbose=True,
892 conditions=self.condition)
Daniel Chenga0c5f082017-10-19 13:35:19 -0700893 for hook in local_scope.get('pre_deps_hooks', [])
894 ]
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000895
Paweł Hajdan, Jr357415c2017-07-24 14:35:28 +0200896 self.add_dependencies_and_close(deps_to_add, hooks_to_run)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000897 logging.info('ParseDepsFile(%s) done' % self.name)
898
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +0200899 def _get_option(self, attr, default):
900 obj = self
901 while not hasattr(obj, '_options'):
902 obj = obj.parent
903 return getattr(obj._options, attr, default)
904
Paweł Hajdan, Jr357415c2017-07-24 14:35:28 +0200905 def add_dependencies_and_close(self, deps_to_add, hooks):
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000906 """Adds the dependencies, hooks and mark the parsing as done."""
maruel@chromium.orgb9be0652011-10-14 18:05:40 +0000907 for dep in deps_to_add:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000908 if dep.verify_validity():
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000909 self.add_dependency(dep)
Daniel Chenga0c5f082017-10-19 13:35:19 -0700910 self._mark_as_parsed([
911 Hook.from_dict(
Michael Moss42d02c22018-02-05 10:32:24 -0800912 h, variables=self.get_vars(), verbose=self.root._options.verbose,
913 conditions=self.condition)
Daniel Chenga0c5f082017-10-19 13:35:19 -0700914 for h in hooks
915 ])
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000916
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000917 def findDepsFromNotAllowedHosts(self):
918 """Returns a list of depenecies from not allowed hosts.
919
920 If allowed_hosts is not set, allows all hosts and returns empty list.
921 """
922 if not self._allowed_hosts:
923 return []
924 bad_deps = []
925 for dep in self._dependencies:
szager@chromium.orgbd772dd2014-11-05 18:43:08 +0000926 # Don't enforce this for custom_deps.
927 if dep.name in self._custom_deps:
928 continue
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000929 if isinstance(dep.url, basestring):
930 parsed_url = urlparse.urlparse(dep.url)
931 if parsed_url.netloc and parsed_url.netloc not in self._allowed_hosts:
932 bad_deps.append(dep)
933 return bad_deps
934
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +0000935 # Arguments number differs from overridden method
Quinten Yearsleyb2cc4a92016-12-15 13:53:26 -0800936 # pylint: disable=arguments-differ
maruel@chromium.org3742c842010-09-09 19:27:14 +0000937 def run(self, revision_overrides, command, args, work_queue, options):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000938 """Runs |command| then parse the DEPS file."""
maruel@chromium.org470b5432011-10-11 18:18:19 +0000939 logging.info('Dependency(%s).run()' % self.name)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000940 assert self._file_list == []
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000941 if not self.should_process:
942 return
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000943 # When running runhooks, there's no need to consult the SCM.
944 # All known hooks are expected to run unconditionally regardless of working
945 # copy state, so skip the SCM status check.
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +0200946 run_scm = command not in (
947 'flatten', 'runhooks', 'recurse', 'validate', None)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000948 parsed_url = self.LateOverride(self.url)
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000949 file_list = [] if not options.nohooks else None
szager@chromium.org3a3608d2014-10-22 21:13:52 +0000950 revision_override = revision_overrides.pop(self.name, None)
Dave Tubbda9712017-06-01 15:10:53 -0700951 if not revision_override and parsed_url:
952 revision_override = revision_overrides.get(parsed_url.split('@')[0], None)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000953 if run_scm and parsed_url:
agabled437d762016-10-17 09:35:11 -0700954 # Create a shallow copy to mutate revision.
955 options = copy.copy(options)
956 options.revision = revision_override
957 self._used_revision = options.revision
John Budorick0f7b2002018-01-19 15:46:17 -0800958 self._used_scm = self.CreateSCM(
agabled437d762016-10-17 09:35:11 -0700959 parsed_url, self.root.root_dir, self.name, self.outbuf,
960 out_cb=work_queue.out_cb)
961 self._got_revision = self._used_scm.RunCommand(command, options, args,
962 file_list)
963 if file_list:
964 file_list = [os.path.join(self.name, f.strip()) for f in file_list]
maruel@chromium.org68988972011-09-20 14:11:42 +0000965
966 # TODO(phajdan.jr): We should know exactly when the paths are absolute.
967 # Convert all absolute paths to relative.
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000968 for i in range(len(file_list or [])):
maruel@chromium.org68988972011-09-20 14:11:42 +0000969 # It depends on the command being executed (like runhooks vs sync).
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000970 if not os.path.isabs(file_list[i]):
maruel@chromium.org68988972011-09-20 14:11:42 +0000971 continue
972 prefix = os.path.commonprefix(
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000973 [self.root.root_dir.lower(), file_list[i].lower()])
974 file_list[i] = file_list[i][len(prefix):]
maruel@chromium.org68988972011-09-20 14:11:42 +0000975 # Strip any leading path separators.
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000976 while file_list[i].startswith(('\\', '/')):
977 file_list[i] = file_list[i][1:]
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000978
979 # Always parse the DEPS file.
980 self.ParseDepsFile()
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000981 self._run_is_done(file_list or [], parsed_url)
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000982 if command in ('update', 'revert') and not options.noprehooks:
983 self.RunPreDepsHooks()
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000984
985 if self.recursion_limit:
986 # Parse the dependencies of this dependency.
987 for s in self.dependencies:
Paweł Hajdan, Jr4baaa112017-07-04 19:09:32 +0200988 if s.should_process:
989 work_queue.enqueue(s)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000990
991 if command == 'recurse':
agabled437d762016-10-17 09:35:11 -0700992 # Skip file only checkout.
John Budorick0f7b2002018-01-19 15:46:17 -0800993 scm = self.GetScmName(parsed_url)
agabled437d762016-10-17 09:35:11 -0700994 if not options.scm or scm in options.scm:
995 cwd = os.path.normpath(os.path.join(self.root.root_dir, self.name))
996 # Pass in the SCM type as an env variable. Make sure we don't put
997 # unicode strings in the environment.
998 env = os.environ.copy()
999 if scm:
1000 env['GCLIENT_SCM'] = str(scm)
1001 if parsed_url:
1002 env['GCLIENT_URL'] = str(parsed_url)
1003 env['GCLIENT_DEP_PATH'] = str(self.name)
1004 if options.prepend_dir and scm == 'git':
1005 print_stdout = False
1006 def filter_fn(line):
1007 """Git-specific path marshaling. It is optimized for git-grep."""
ilevy@chromium.org0233ac22012-11-28 20:27:02 +00001008
agabled437d762016-10-17 09:35:11 -07001009 def mod_path(git_pathspec):
1010 match = re.match('^(\\S+?:)?([^\0]+)$', git_pathspec)
1011 modified_path = os.path.join(self.name, match.group(2))
1012 branch = match.group(1) or ''
1013 return '%s%s' % (branch, modified_path)
ilevy@chromium.org0233ac22012-11-28 20:27:02 +00001014
agabled437d762016-10-17 09:35:11 -07001015 match = re.match('^Binary file ([^\0]+) matches$', line)
1016 if match:
1017 print('Binary file %s matches\n' % mod_path(match.group(1)))
1018 return
ilevy@chromium.org0233ac22012-11-28 20:27:02 +00001019
agabled437d762016-10-17 09:35:11 -07001020 items = line.split('\0')
1021 if len(items) == 2 and items[1]:
1022 print('%s : %s' % (mod_path(items[0]), items[1]))
1023 elif len(items) >= 2:
1024 # Multiple null bytes or a single trailing null byte indicate
1025 # git is likely displaying filenames only (such as with -l)
1026 print('\n'.join(mod_path(path) for path in items if path))
1027 else:
1028 print(line)
1029 else:
1030 print_stdout = True
1031 filter_fn = None
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001032
agabled437d762016-10-17 09:35:11 -07001033 if parsed_url is None:
1034 print('Skipped omitted dependency %s' % cwd, file=sys.stderr)
1035 elif os.path.isdir(cwd):
1036 try:
1037 gclient_utils.CheckCallAndFilter(
1038 args, cwd=cwd, env=env, print_stdout=print_stdout,
1039 filter_fn=filter_fn,
1040 )
1041 except subprocess2.CalledProcessError:
1042 if not options.ignore:
1043 raise
1044 else:
1045 print('Skipped missing %s' % cwd, file=sys.stderr)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001046
John Budorick0f7b2002018-01-19 15:46:17 -08001047 def GetScmName(self, url):
1048 """Get the name of the SCM for the given URL.
1049
1050 While we currently support both git and cipd as SCM implementations,
1051 this currently cannot return 'cipd', regardless of the URL, as CIPD
1052 has no canonical URL format. If you want to use CIPD as an SCM, you
1053 must currently do so by explicitly using a CipdDependency.
1054 """
1055 if not url:
1056 return None
1057 url, _ = gclient_utils.SplitUrlRevision(url)
1058 if url.endswith('.git'):
1059 return 'git'
1060 protocol = url.split('://')[0]
1061 if protocol in (
1062 'file', 'git', 'git+http', 'git+https', 'http', 'https', 'ssh', 'sso'):
1063 return 'git'
1064 return None
1065
1066 def CreateSCM(self, url, root_dir=None, relpath=None, out_fh=None,
1067 out_cb=None):
1068 SCM_MAP = {
1069 'cipd': gclient_scm.CipdWrapper,
1070 'git': gclient_scm.GitWrapper,
1071 }
1072
1073 scm_name = self.GetScmName(url)
1074 if not scm_name in SCM_MAP:
1075 raise gclient_utils.Error('No SCM found for url %s' % url)
1076 scm_class = SCM_MAP[scm_name]
1077 if not scm_class.BinaryExists():
1078 raise gclient_utils.Error('%s command not found' % scm_name)
Edward Lemur231f5ea2018-01-31 19:02:36 +01001079 return scm_class(url, root_dir, relpath, out_fh, out_cb, self.print_outbuf)
John Budorick0f7b2002018-01-19 15:46:17 -08001080
Dirk Pranke9f20d022017-10-11 18:36:54 -07001081 def HasGNArgsFile(self):
1082 return self._gn_args_file is not None
1083
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +02001084 def WriteGNArgsFile(self):
1085 lines = ['# Generated from %r' % self.deps_file]
Paweł Hajdan, Jrb495bf52017-09-25 19:33:50 +02001086 variables = self.get_vars()
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +02001087 for arg in self._gn_args:
Paweł Hajdan, Jre0214742017-09-28 12:21:01 +02001088 value = variables[arg]
1089 if isinstance(value, basestring):
1090 value = gclient_eval.EvaluateCondition(value, variables)
Paweł Hajdan, Jrb495bf52017-09-25 19:33:50 +02001091 lines.append('%s = %s' % (arg, ToGNString(value)))
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +02001092 with open(os.path.join(self.root.root_dir, self._gn_args_file), 'w') as f:
1093 f.write('\n'.join(lines))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001094
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001095 @gclient_utils.lockedmethod
1096 def _run_is_done(self, file_list, parsed_url):
1097 # Both these are kept for hooks that are run as a separate tree traversal.
1098 self._file_list = file_list
1099 self._parsed_url = parsed_url
1100 self._processed = True
1101
szager@google.comb9a78d32012-03-13 18:46:21 +00001102 def GetHooks(self, options):
1103 """Evaluates all hooks, and return them in a flat list.
1104
1105 RunOnDeps() must have been called before to load the DEPS.
1106 """
1107 result = []
maruel@chromium.org68988972011-09-20 14:11:42 +00001108 if not self.should_process or not self.recursion_limit:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001109 # Don't run the hook when it is above recursion_limit.
szager@google.comb9a78d32012-03-13 18:46:21 +00001110 return result
maruel@chromium.orgdc7445d2010-07-09 21:05:29 +00001111 # If "--force" was specified, run all hooks regardless of what files have
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001112 # changed.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001113 if self.deps_hooks:
agabled437d762016-10-17 09:35:11 -07001114 # TODO(maruel): If the user is using git, then we don't know
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001115 # what files have changed so we always run all hooks. It'd be nice to fix
1116 # that.
1117 if (options.force or
John Budorick0f7b2002018-01-19 15:46:17 -08001118 self.GetScmName(self.parsed_url) in ('git', None) or
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001119 os.path.isdir(os.path.join(self.root.root_dir, self.name, '.git'))):
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +02001120 result.extend(self.deps_hooks)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001121 else:
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +02001122 for hook in self.deps_hooks:
1123 if hook.matches(self.file_list_and_children):
1124 result.append(hook)
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001125 for s in self.dependencies:
szager@google.comb9a78d32012-03-13 18:46:21 +00001126 result.extend(s.GetHooks(options))
1127 return result
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001128
Dirk Pranke9f20d022017-10-11 18:36:54 -07001129 def WriteGNArgsFilesRecursively(self, dependencies):
1130 for dep in dependencies:
1131 if dep.HasGNArgsFile():
1132 dep.WriteGNArgsFile()
1133 self.WriteGNArgsFilesRecursively(dep.dependencies)
1134
Daniel Chenga0c5f082017-10-19 13:35:19 -07001135 def RunHooksRecursively(self, options, progress):
szager@google.comb9a78d32012-03-13 18:46:21 +00001136 assert self.hooks_ran == False
maruel@chromium.org064186c2011-09-27 23:53:33 +00001137 self._hooks_ran = True
Daniel Chenga0c5f082017-10-19 13:35:19 -07001138 hooks = self.GetHooks(options)
1139 if progress:
1140 progress._total = len(hooks)
1141 for hook in hooks:
Daniel Chenga0c5f082017-10-19 13:35:19 -07001142 if progress:
1143 progress.update(extra=hook.name or '')
Daniel Cheng93c5d602017-10-20 11:40:17 -07001144 hook.run(self.root.root_dir)
Daniel Chenga0c5f082017-10-19 13:35:19 -07001145 if progress:
1146 progress.end()
maruel@chromium.orgeaf61062010-07-07 18:42:39 +00001147
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001148 def RunPreDepsHooks(self):
1149 assert self.processed
1150 assert self.deps_parsed
1151 assert not self.pre_deps_hooks_ran
1152 assert not self.hooks_ran
1153 for s in self.dependencies:
1154 assert not s.processed
1155 self._pre_deps_hooks_ran = True
1156 for hook in self.pre_deps_hooks:
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +02001157 hook.run(self.root.root_dir)
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001158
maruel@chromium.org0d812442010-08-10 12:41:08 +00001159 def subtree(self, include_all):
maruel@chromium.orgad3287e2011-10-03 19:15:10 +00001160 """Breadth first recursion excluding root node."""
maruel@chromium.orgf13a4182011-09-22 00:26:15 +00001161 dependencies = self.dependencies
1162 for d in dependencies:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001163 if d.should_process or include_all:
maruel@chromium.orgad3287e2011-10-03 19:15:10 +00001164 yield d
maruel@chromium.orgf13a4182011-09-22 00:26:15 +00001165 for d in dependencies:
maruel@chromium.orgad3287e2011-10-03 19:15:10 +00001166 for i in d.subtree(include_all):
1167 yield i
1168
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001169 @gclient_utils.lockedmethod
1170 def add_dependency(self, new_dep):
1171 self._dependencies.append(new_dep)
1172
1173 @gclient_utils.lockedmethod
Paweł Hajdan, Jr357415c2017-07-24 14:35:28 +02001174 def _mark_as_parsed(self, new_hooks):
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001175 self._deps_hooks.extend(new_hooks)
1176 self._deps_parsed = True
1177
maruel@chromium.org68988972011-09-20 14:11:42 +00001178 @property
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001179 @gclient_utils.lockedmethod
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +00001180 def dependencies(self):
1181 return tuple(self._dependencies)
1182
1183 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001184 @gclient_utils.lockedmethod
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02001185 def os_dependencies(self):
1186 return dict(self._os_dependencies)
1187
1188 @property
1189 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001190 def deps_hooks(self):
1191 return tuple(self._deps_hooks)
1192
1193 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001194 @gclient_utils.lockedmethod
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +02001195 def os_deps_hooks(self):
1196 return dict(self._os_deps_hooks)
1197
1198 @property
1199 @gclient_utils.lockedmethod
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001200 def pre_deps_hooks(self):
1201 return tuple(self._pre_deps_hooks)
1202
1203 @property
1204 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001205 def parsed_url(self):
1206 return self._parsed_url
1207
1208 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001209 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001210 def deps_parsed(self):
maruel@chromium.org3223edd2011-10-10 23:17:39 +00001211 """This is purely for debugging purposes. It's not used anywhere."""
maruel@chromium.org064186c2011-09-27 23:53:33 +00001212 return self._deps_parsed
1213
1214 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001215 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001216 def processed(self):
1217 return self._processed
1218
1219 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001220 @gclient_utils.lockedmethod
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001221 def pre_deps_hooks_ran(self):
1222 return self._pre_deps_hooks_ran
1223
1224 @property
1225 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001226 def hooks_ran(self):
1227 return self._hooks_ran
1228
1229 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001230 @gclient_utils.lockedmethod
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00001231 def allowed_hosts(self):
1232 return self._allowed_hosts
1233
1234 @property
1235 @gclient_utils.lockedmethod
maruel@chromium.org861fd0f2010-07-23 03:05:05 +00001236 def file_list(self):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001237 return tuple(self._file_list)
1238
1239 @property
kustermann@google.coma692e8f2013-04-18 08:32:04 +00001240 def used_scm(self):
1241 """SCMWrapper instance for this dependency or None if not processed yet."""
1242 return self._used_scm
1243
1244 @property
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00001245 @gclient_utils.lockedmethod
1246 def got_revision(self):
1247 return self._got_revision
1248
1249 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001250 def file_list_and_children(self):
1251 result = list(self.file_list)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +00001252 for d in self.dependencies:
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001253 result.extend(d.file_list_and_children)
maruel@chromium.org68988972011-09-20 14:11:42 +00001254 return tuple(result)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +00001255
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001256 def __str__(self):
1257 out = []
agablea98a6cd2016-11-15 14:30:10 -08001258 for i in ('name', 'url', 'parsed_url', 'custom_deps',
maruel@chromium.org3c74bc92011-09-15 19:17:21 +00001259 'custom_vars', 'deps_hooks', 'file_list', 'should_process',
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00001260 'processed', 'hooks_ran', 'deps_parsed', 'requirements',
1261 'allowed_hosts'):
maruel@chromium.org3c74bc92011-09-15 19:17:21 +00001262 # First try the native property if it exists.
1263 if hasattr(self, '_' + i):
1264 value = getattr(self, '_' + i, False)
1265 else:
1266 value = getattr(self, i, False)
1267 if value:
1268 out.append('%s: %s' % (i, value))
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001269
1270 for d in self.dependencies:
1271 out.extend([' ' + x for x in str(d).splitlines()])
1272 out.append('')
1273 return '\n'.join(out)
1274
1275 def __repr__(self):
1276 return '%s: %s' % (self.name, self.url)
1277
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001278 def hierarchy(self, include_url=True):
maruel@chromium.orgbc2d2f92010-07-22 21:26:48 +00001279 """Returns a human-readable hierarchical reference to a Dependency."""
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001280 def format_name(d):
1281 if include_url:
1282 return '%s(%s)' % (d.name, d.url)
1283 return d.name
1284 out = format_name(self)
maruel@chromium.orgbffb9042010-07-22 20:59:36 +00001285 i = self.parent
1286 while i and i.name:
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001287 out = '%s -> %s' % (format_name(i), out)
maruel@chromium.orgbffb9042010-07-22 20:59:36 +00001288 i = i.parent
1289 return out
1290
Paweł Hajdan, Jrd3790252017-07-03 21:06:24 +02001291 def get_vars(self):
1292 """Returns a dictionary of effective variable values
1293 (DEPS file contents with applied custom_vars overrides)."""
Paweł Hajdan, Jra3b67ae2017-08-30 15:18:21 +02001294 # Provide some built-in variables.
1295 result = {
Paweł Hajdan, Jrd325eb32017-10-03 17:43:37 +02001296 'checkout_android': 'android' in self.target_os,
Benjamin Pastene6fe29412018-01-23 15:35:58 -08001297 'checkout_chromeos': 'chromeos' in self.target_os,
Paweł Hajdan, Jrd325eb32017-10-03 17:43:37 +02001298 'checkout_fuchsia': 'fuchsia' in self.target_os,
1299 'checkout_ios': 'ios' in self.target_os,
1300 'checkout_linux': 'unix' in self.target_os,
1301 'checkout_mac': 'mac' in self.target_os,
1302 'checkout_win': 'win' in self.target_os,
1303 'host_os': _detect_host_os(),
Paweł Hajdan, Jra3b67ae2017-08-30 15:18:21 +02001304 }
1305 # Variables defined in DEPS file override built-in ones.
1306 result.update(self._vars)
Paweł Hajdan, Jrd3790252017-07-03 21:06:24 +02001307 result.update(self.custom_vars or {})
1308 return result
1309
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001310
Paweł Hajdan, Jra3b67ae2017-08-30 15:18:21 +02001311_PLATFORM_MAPPING = {
1312 'cygwin': 'win',
1313 'darwin': 'mac',
1314 'linux2': 'linux',
1315 'win32': 'win',
Jaideep Bajwad05f3582017-09-11 12:31:48 -04001316 'aix6': 'aix',
Paweł Hajdan, Jra3b67ae2017-08-30 15:18:21 +02001317}
1318
1319
1320def _detect_host_os():
1321 return _PLATFORM_MAPPING[sys.platform]
1322
1323
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001324class GClient(Dependency):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001325 """Object that represent a gclient checkout. A tree of Dependency(), one per
1326 solution or DEPS entry."""
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001327
1328 DEPS_OS_CHOICES = {
Jaideep Bajwad05f3582017-09-11 12:31:48 -04001329 "aix6": "unix",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001330 "win32": "win",
1331 "win": "win",
1332 "cygwin": "win",
1333 "darwin": "mac",
1334 "mac": "mac",
1335 "unix": "unix",
1336 "linux": "unix",
1337 "linux2": "unix",
maruel@chromium.org244e3442011-06-12 15:20:55 +00001338 "linux3": "unix",
szager@chromium.orgf8c95cd2012-06-01 22:26:52 +00001339 "android": "android",
Michael Mossc54fa812017-08-17 11:27:58 -07001340 "ios": "ios",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001341 }
1342
1343 DEFAULT_CLIENT_FILE_TEXT = ("""\
1344solutions = [
smutae7ea312016-07-18 11:59:41 -07001345 { "name" : "%(solution_name)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001346 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +00001347 "deps_file" : "%(deps_file)s",
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001348 "managed" : %(managed)s,
smutae7ea312016-07-18 11:59:41 -07001349 "custom_deps" : {
1350 },
Paweł Hajdan, Jr3ba2a7c2017-10-04 19:24:46 +02001351 "custom_vars": %(custom_vars)r,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001352 },
1353]
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001354cache_dir = %(cache_dir)r
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001355""")
1356
1357 DEFAULT_SNAPSHOT_SOLUTION_TEXT = ("""\
smutae7ea312016-07-18 11:59:41 -07001358 { "name" : "%(solution_name)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001359 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +00001360 "deps_file" : "%(deps_file)s",
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001361 "managed" : %(managed)s,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001362 "custom_deps" : {
smutae7ea312016-07-18 11:59:41 -07001363%(solution_deps)s },
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001364 },
1365""")
1366
1367 DEFAULT_SNAPSHOT_FILE_TEXT = ("""\
1368# Snapshot generated with gclient revinfo --snapshot
1369solutions = [
maruel@chromium.org73e21142010-07-05 13:32:01 +00001370%(solution_list)s]
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001371""")
1372
1373 def __init__(self, root_dir, options):
maruel@chromium.org0d812442010-08-10 12:41:08 +00001374 # Do not change previous behavior. Only solution level and immediate DEPS
1375 # are processed.
1376 self._recursion_limit = 2
Paweł Hajdan, Jrfc6196b2017-07-27 13:15:25 +02001377 Dependency.__init__(self, None, None, None, None, True, None, None, None,
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +02001378 'unused', True, None, None, True)
maruel@chromium.org0d425922010-06-21 19:22:24 +00001379 self._options = options
maruel@chromium.org271375b2010-06-23 19:17:38 +00001380 if options.deps_os:
1381 enforced_os = options.deps_os.split(',')
1382 else:
1383 enforced_os = [self.DEPS_OS_CHOICES.get(sys.platform, 'unix')]
1384 if 'all' in enforced_os:
1385 enforced_os = self.DEPS_OS_CHOICES.itervalues()
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001386 self._enforced_os = tuple(set(enforced_os))
maruel@chromium.org271375b2010-06-23 19:17:38 +00001387 self._root_dir = root_dir
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001388 self.config_content = None
1389
borenet@google.com88d10082014-03-21 17:24:48 +00001390 def _CheckConfig(self):
1391 """Verify that the config matches the state of the existing checked-out
1392 solutions."""
1393 for dep in self.dependencies:
1394 if dep.managed and dep.url:
John Budorick0f7b2002018-01-19 15:46:17 -08001395 scm = self.CreateSCM(
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001396 dep.url, self.root_dir, dep.name, self.outbuf)
smut@google.comd33eab32014-07-07 19:35:18 +00001397 actual_url = scm.GetActualRemoteURL(self._options)
borenet@google.com4e9be262014-04-08 19:40:30 +00001398 if actual_url and not scm.DoesRemoteURLMatch(self._options):
levarum@chromium.org27a6f9a2016-05-28 00:21:49 +00001399 mirror = scm.GetCacheMirror()
1400 if mirror:
1401 mirror_string = '%s (exists=%s)' % (mirror.mirror_path,
1402 mirror.exists())
1403 else:
1404 mirror_string = 'not used'
borenet@google.com0a427372014-04-02 19:12:13 +00001405 raise gclient_utils.Error('''
borenet@google.com88d10082014-03-21 17:24:48 +00001406Your .gclient file seems to be broken. The requested URL is different from what
borenet@google.com0a427372014-04-02 19:12:13 +00001407is actually checked out in %(checkout_path)s.
borenet@google.com88d10082014-03-21 17:24:48 +00001408
borenet@google.com97882362014-04-07 20:06:02 +00001409The .gclient file contains:
levarum@chromium.org27a6f9a2016-05-28 00:21:49 +00001410URL: %(expected_url)s (%(expected_scm)s)
1411Cache mirror: %(mirror_string)s
borenet@google.com97882362014-04-07 20:06:02 +00001412
1413The local checkout in %(checkout_path)s reports:
1414%(actual_url)s (%(actual_scm)s)
borenet@google.com88d10082014-03-21 17:24:48 +00001415
1416You should ensure that the URL listed in .gclient is correct and either change
agabled437d762016-10-17 09:35:11 -07001417it or fix the checkout.
borenet@google.com88d10082014-03-21 17:24:48 +00001418''' % {'checkout_path': os.path.join(self.root_dir, dep.name),
1419 'expected_url': dep.url,
John Budorick0f7b2002018-01-19 15:46:17 -08001420 'expected_scm': self.GetScmName(dep.url),
levarum@chromium.org27a6f9a2016-05-28 00:21:49 +00001421 'mirror_string' : mirror_string,
borenet@google.com88d10082014-03-21 17:24:48 +00001422 'actual_url': actual_url,
John Budorick0f7b2002018-01-19 15:46:17 -08001423 'actual_scm': self.GetScmName(actual_url)})
borenet@google.com88d10082014-03-21 17:24:48 +00001424
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001425 def SetConfig(self, content):
maruel@chromium.orgf13a4182011-09-22 00:26:15 +00001426 assert not self.dependencies
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001427 config_dict = {}
1428 self.config_content = content
1429 try:
1430 exec(content, config_dict)
vapier@chromium.orga81a56e2015-11-11 07:56:13 +00001431 except SyntaxError as e:
maruel@chromium.org5990f9d2010-07-07 18:02:58 +00001432 gclient_utils.SyntaxErrorToError('.gclient', e)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001433
peter@chromium.org1efccc82012-04-27 16:34:38 +00001434 # Append any target OS that is not already being enforced to the tuple.
1435 target_os = config_dict.get('target_os', [])
stuartmorgan@chromium.org18a4f6a2012-11-02 13:37:24 +00001436 if config_dict.get('target_os_only', False):
1437 self._enforced_os = tuple(set(target_os))
1438 else:
1439 self._enforced_os = tuple(set(self._enforced_os).union(target_os))
1440
Aleksandr Derbenev9e8fb0e2017-08-01 20:18:31 +03001441 cache_dir = config_dict.get('cache_dir', self._options.cache_dir)
dyen@chromium.orgd915cca2014-08-07 21:41:37 +00001442 if cache_dir:
1443 cache_dir = os.path.join(self.root_dir, cache_dir)
1444 cache_dir = os.path.abspath(cache_dir)
Andrii Shyshkalov77ce4bd2017-11-27 12:38:18 -08001445
dyen@chromium.orgd915cca2014-08-07 21:41:37 +00001446 gclient_scm.GitWrapper.cache_dir = cache_dir
1447 git_cache.Mirror.SetCachePath(cache_dir)
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001448
stuartmorgan@chromium.org18a4f6a2012-11-02 13:37:24 +00001449 if not target_os and config_dict.get('target_os_only', False):
1450 raise gclient_utils.Error('Can\'t use target_os_only if target_os is '
1451 'not specified')
peter@chromium.org1efccc82012-04-27 16:34:38 +00001452
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001453 deps_to_add = []
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001454 for s in config_dict.get('solutions', []):
maruel@chromium.org81843b82010-06-28 16:49:26 +00001455 try:
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001456 deps_to_add.append(Dependency(
Paweł Hajdan, Jrfc6196b2017-07-27 13:15:25 +02001457 self, s['name'], s['url'], s['url'],
smutae7ea312016-07-18 11:59:41 -07001458 s.get('managed', True),
maruel@chromium.org81843b82010-06-28 16:49:26 +00001459 s.get('custom_deps', {}),
maruel@chromium.org0d812442010-08-10 12:41:08 +00001460 s.get('custom_vars', {}),
petermayo@chromium.orge79161a2013-07-09 14:40:37 +00001461 s.get('custom_hooks', []),
nsylvain@google.comefc80932011-05-31 21:27:56 +00001462 s.get('deps_file', 'DEPS'),
agabledce6ddc2016-09-08 10:02:16 -07001463 True,
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +02001464 None,
1465 None,
Edward Lemur231f5ea2018-01-31 19:02:36 +01001466 True,
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +02001467 True))
maruel@chromium.org81843b82010-06-28 16:49:26 +00001468 except KeyError:
1469 raise gclient_utils.Error('Invalid .gclient file. Solution is '
1470 'incomplete: %s' % s)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001471 self.add_dependencies_and_close(deps_to_add, config_dict.get('hooks', []))
1472 logging.info('SetConfig() done')
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001473
1474 def SaveConfig(self):
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001475 gclient_utils.FileWrite(os.path.join(self.root_dir,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001476 self._options.config_filename),
1477 self.config_content)
1478
1479 @staticmethod
1480 def LoadCurrentConfig(options):
1481 """Searches for and loads a .gclient file relative to the current working
1482 dir. Returns a GClient object."""
szager@chromium.orge2e03202012-07-31 18:05:16 +00001483 if options.spec:
1484 client = GClient('.', options)
1485 client.SetConfig(options.spec)
1486 else:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00001487 if options.verbose:
1488 print('Looking for %s starting from %s\n' % (
1489 options.config_filename, os.getcwd()))
szager@chromium.orge2e03202012-07-31 18:05:16 +00001490 path = gclient_utils.FindGclientRoot(os.getcwd(), options.config_filename)
1491 if not path:
Michael Achenbachb3ce73d2017-10-11 16:41:27 +02001492 if options.verbose:
1493 print('Couldn\'t find configuration file.')
szager@chromium.orge2e03202012-07-31 18:05:16 +00001494 return None
1495 client = GClient(path, options)
1496 client.SetConfig(gclient_utils.FileRead(
1497 os.path.join(path, options.config_filename)))
maruel@chromium.org69392e72011-10-13 22:09:00 +00001498
1499 if (options.revisions and
1500 len(client.dependencies) > 1 and
1501 any('@' not in r for r in options.revisions)):
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001502 print(
1503 ('You must specify the full solution name like --revision %s@%s\n'
1504 'when you have multiple solutions setup in your .gclient file.\n'
1505 'Other solutions present are: %s.') % (
maruel@chromium.org69392e72011-10-13 22:09:00 +00001506 client.dependencies[0].name,
1507 options.revisions[0],
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001508 ', '.join(s.name for s in client.dependencies[1:])),
1509 file=sys.stderr)
maruel@chromium.org15804092010-09-02 17:07:37 +00001510 return client
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001511
nsylvain@google.comefc80932011-05-31 21:27:56 +00001512 def SetDefaultConfig(self, solution_name, deps_file, solution_url,
Paweł Hajdan, Jr3ba2a7c2017-10-04 19:24:46 +02001513 managed=True, cache_dir=None, custom_vars=None):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001514 self.SetConfig(self.DEFAULT_CLIENT_FILE_TEXT % {
1515 'solution_name': solution_name,
1516 'solution_url': solution_url,
nsylvain@google.comefc80932011-05-31 21:27:56 +00001517 'deps_file': deps_file,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001518 'managed': managed,
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001519 'cache_dir': cache_dir,
Paweł Hajdan, Jr3ba2a7c2017-10-04 19:24:46 +02001520 'custom_vars': custom_vars or {},
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001521 })
1522
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001523 def _SaveEntries(self):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001524 """Creates a .gclient_entries file to record the list of unique checkouts.
1525
1526 The .gclient_entries file lives in the same directory as .gclient.
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001527 """
1528 # Sometimes pprint.pformat will use {', sometimes it'll use { ' ... It
1529 # makes testing a bit too fun.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001530 result = 'entries = {\n'
maruel@chromium.org68988972011-09-20 14:11:42 +00001531 for entry in self.root.subtree(False):
agabled437d762016-10-17 09:35:11 -07001532 result += ' %s: %s,\n' % (pprint.pformat(entry.name),
1533 pprint.pformat(entry.parsed_url))
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001534 result += '}\n'
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001535 file_path = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org1333cb32011-10-04 23:40:16 +00001536 logging.debug(result)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001537 gclient_utils.FileWrite(file_path, result)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001538
1539 def _ReadEntries(self):
1540 """Read the .gclient_entries file for the given client.
1541
1542 Returns:
1543 A sequence of solution names, which will be empty if there is the
1544 entries file hasn't been created yet.
1545 """
1546 scope = {}
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001547 filename = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001548 if not os.path.exists(filename):
maruel@chromium.org73e21142010-07-05 13:32:01 +00001549 return {}
maruel@chromium.org5990f9d2010-07-07 18:02:58 +00001550 try:
1551 exec(gclient_utils.FileRead(filename), scope)
vapier@chromium.orga81a56e2015-11-11 07:56:13 +00001552 except SyntaxError as e:
maruel@chromium.org5990f9d2010-07-07 18:02:58 +00001553 gclient_utils.SyntaxErrorToError(filename, e)
Aaron Gable3721ee92017-04-03 14:53:14 -07001554 return scope.get('entries', {})
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001555
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001556 def _EnforceRevisions(self):
maruel@chromium.org918a9ae2010-05-28 15:50:30 +00001557 """Checks for revision overrides."""
1558 revision_overrides = {}
smutae7ea312016-07-18 11:59:41 -07001559 if self._options.head:
1560 return revision_overrides
joi@chromium.org792ea882010-11-10 02:37:27 +00001561 if not self._options.revisions:
1562 for s in self.dependencies:
smutae7ea312016-07-18 11:59:41 -07001563 if not s.managed:
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001564 self._options.revisions.append('%s@unmanaged' % s.name)
maruel@chromium.org307d1792010-05-31 20:03:13 +00001565 if not self._options.revisions:
1566 return revision_overrides
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001567 solutions_names = [s.name for s in self.dependencies]
smutae7ea312016-07-18 11:59:41 -07001568 index = 0
1569 for revision in self._options.revisions:
1570 if not '@' in revision:
maruel@chromium.org307d1792010-05-31 20:03:13 +00001571 # Support for --revision 123
smutae7ea312016-07-18 11:59:41 -07001572 revision = '%s@%s' % (solutions_names[index], revision)
1573 name, rev = revision.split('@', 1)
szager@chromium.org4ad264b2014-05-20 04:43:47 +00001574 revision_overrides[name] = rev
smutae7ea312016-07-18 11:59:41 -07001575 index += 1
maruel@chromium.org918a9ae2010-05-28 15:50:30 +00001576 return revision_overrides
1577
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001578 def RunOnDeps(self, command, args, ignore_requirements=False, progress=True):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001579 """Runs a command on each dependency in a client and its dependencies.
1580
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001581 Args:
1582 command: The command to use (e.g., 'status' or 'diff')
1583 args: list of str - extra arguments to add to the command line.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001584 """
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001585 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +00001586 raise gclient_utils.Error('No solution specified')
borenet@google.com0a427372014-04-02 19:12:13 +00001587
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001588 revision_overrides = {}
1589 # It's unnecessary to check for revision overrides for 'recurse'.
1590 # Save a few seconds by not calling _EnforceRevisions() in that case.
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +02001591 if command not in ('diff', 'recurse', 'runhooks', 'status', 'revert',
1592 'validate'):
szager@chromium.org5273b8a2014-08-21 15:10:10 +00001593 self._CheckConfig()
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001594 revision_overrides = self._EnforceRevisions()
Daniel Chenga21b5b32017-10-19 20:07:48 +00001595 # Disable progress for non-tty stdout.
Daniel Chenga0c5f082017-10-19 13:35:19 -07001596 should_show_progress = (
1597 setup_color.IS_TTY and not self._options.verbose and progress)
1598 pm = None
1599 if should_show_progress:
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001600 if command in ('update', 'revert'):
1601 pm = Progress('Syncing projects', 1)
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +02001602 elif command in ('recurse', 'validate'):
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001603 pm = Progress(' '.join(args), 1)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001604 work_queue = gclient_utils.ExecutionQueue(
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001605 self._options.jobs, pm, ignore_requirements=ignore_requirements,
1606 verbose=self._options.verbose)
maruel@chromium.org049bced2010-08-12 13:37:20 +00001607 for s in self.dependencies:
Paweł Hajdan, Jr4baaa112017-07-04 19:09:32 +02001608 if s.should_process:
1609 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +00001610 work_queue.flush(revision_overrides, command, args, options=self._options)
szager@chromium.org4ad264b2014-05-20 04:43:47 +00001611 if revision_overrides:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001612 print('Please fix your script, having invalid --revision flags will soon '
1613 'considered an error.', file=sys.stderr)
piman@chromium.org6f363722010-04-27 00:41:09 +00001614
Dirk Pranke9f20d022017-10-11 18:36:54 -07001615 # Once all the dependencies have been processed, it's now safe to write
1616 # out any gn_args_files and run the hooks.
1617 if command == 'update':
1618 self.WriteGNArgsFilesRecursively(self.dependencies)
1619
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001620 if not self._options.nohooks:
Daniel Chenga0c5f082017-10-19 13:35:19 -07001621 if should_show_progress:
1622 pm = Progress('Running hooks', 1)
1623 self.RunHooksRecursively(self._options, pm)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001624
1625 if command == 'update':
ajwong@chromium.orgcdcee802009-06-23 15:30:42 +00001626 # Notify the user if there is an orphaned entry in their working copy.
1627 # Only delete the directory if there are no changes in it, and
1628 # delete_unversioned_trees is set to true.
maruel@chromium.org68988972011-09-20 14:11:42 +00001629 entries = [i.name for i in self.root.subtree(False) if i.url]
xusydoc@chromium.org885a9602013-05-31 09:54:40 +00001630 full_entries = [os.path.join(self.root_dir, e.replace('/', os.path.sep))
1631 for e in entries]
1632
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001633 for entry, prev_url in self._ReadEntries().iteritems():
maruel@chromium.org04dd7de2010-10-14 13:25:49 +00001634 if not prev_url:
1635 # entry must have been overridden via .gclient custom_deps
1636 continue
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001637 # Fix path separator on Windows.
1638 entry_fixed = entry.replace('/', os.path.sep)
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001639 e_dir = os.path.join(self.root_dir, entry_fixed)
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001640 # Use entry and not entry_fixed there.
jochen@chromium.orga78e5532013-03-11 13:33:03 +00001641 if (entry not in entries and
1642 (not any(path.startswith(entry + '/') for path in entries)) and
jochen@chromium.orgcc475722013-03-11 13:07:40 +00001643 os.path.exists(e_dir)):
primiano@chromium.org1c127382015-02-17 11:15:40 +00001644 # The entry has been removed from DEPS.
John Budorick0f7b2002018-01-19 15:46:17 -08001645 scm = self.CreateSCM(
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001646 prev_url, self.root_dir, entry_fixed, self.outbuf)
xusydoc@chromium.org885a9602013-05-31 09:54:40 +00001647
1648 # Check to see if this directory is now part of a higher-up checkout.
borenet@google.com359bb642014-05-13 17:28:19 +00001649 scm_root = None
agabled437d762016-10-17 09:35:11 -07001650 try:
1651 scm_root = gclient_scm.scm.GIT.GetCheckoutRoot(scm.checkout_path)
1652 except subprocess2.CalledProcessError:
1653 pass
1654 if not scm_root:
borenet@google.com359bb642014-05-13 17:28:19 +00001655 logging.warning('Could not find checkout root for %s. Unable to '
1656 'determine whether it is part of a higher-level '
1657 'checkout, so not removing.' % entry)
1658 continue
primiano@chromium.org1c127382015-02-17 11:15:40 +00001659
1660 # This is to handle the case of third_party/WebKit migrating from
1661 # being a DEPS entry to being part of the main project.
1662 # If the subproject is a Git project, we need to remove its .git
1663 # folder. Otherwise git operations on that folder will have different
1664 # effects depending on the current working directory.
agabled437d762016-10-17 09:35:11 -07001665 if os.path.abspath(scm_root) == os.path.abspath(e_dir):
primiano@chromium.org1c127382015-02-17 11:15:40 +00001666 e_par_dir = os.path.join(e_dir, os.pardir)
agabled437d762016-10-17 09:35:11 -07001667 if gclient_scm.scm.GIT.IsInsideWorkTree(e_par_dir):
1668 par_scm_root = gclient_scm.scm.GIT.GetCheckoutRoot(e_par_dir)
primiano@chromium.org1c127382015-02-17 11:15:40 +00001669 # rel_e_dir : relative path of entry w.r.t. its parent repo.
1670 rel_e_dir = os.path.relpath(e_dir, par_scm_root)
agabled437d762016-10-17 09:35:11 -07001671 if gclient_scm.scm.GIT.IsDirectoryVersioned(
1672 par_scm_root, rel_e_dir):
primiano@chromium.org1c127382015-02-17 11:15:40 +00001673 save_dir = scm.GetGitBackupDirPath()
1674 # Remove any eventual stale backup dir for the same project.
1675 if os.path.exists(save_dir):
1676 gclient_utils.rmtree(save_dir)
1677 os.rename(os.path.join(e_dir, '.git'), save_dir)
1678 # When switching between the two states (entry/ is a subproject
1679 # -> entry/ is part of the outer project), it is very likely
1680 # that some files are changed in the checkout, unless we are
1681 # jumping *exactly* across the commit which changed just DEPS.
1682 # In such case we want to cleanup any eventual stale files
1683 # (coming from the old subproject) in order to end up with a
1684 # clean checkout.
agabled437d762016-10-17 09:35:11 -07001685 gclient_scm.scm.GIT.CleanupDir(par_scm_root, rel_e_dir)
primiano@chromium.org1c127382015-02-17 11:15:40 +00001686 assert not os.path.exists(os.path.join(e_dir, '.git'))
1687 print(('\nWARNING: \'%s\' has been moved from DEPS to a higher '
1688 'level checkout. The git folder containing all the local'
1689 ' branches has been saved to %s.\n'
1690 'If you don\'t care about its state you can safely '
1691 'remove that folder to free up space.') %
1692 (entry, save_dir))
1693 continue
1694
borenet@google.com359bb642014-05-13 17:28:19 +00001695 if scm_root in full_entries:
primiano@chromium.org1c127382015-02-17 11:15:40 +00001696 logging.info('%s is part of a higher level checkout, not removing',
1697 scm.GetCheckoutRoot())
xusydoc@chromium.org885a9602013-05-31 09:54:40 +00001698 continue
1699
1700 file_list = []
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001701 scm.status(self._options, [], file_list)
1702 modified_files = file_list != []
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001703 if (not self._options.delete_unversioned_trees or
1704 (modified_files and not self._options.force)):
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001705 # There are modified files in this entry. Keep warning until
1706 # removed.
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001707 print(('\nWARNING: \'%s\' is no longer part of this client. '
1708 'It is recommended that you manually remove it.\n') %
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001709 entry_fixed)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001710 else:
1711 # Delete the entry
maruel@chromium.org73e21142010-07-05 13:32:01 +00001712 print('\n________ deleting \'%s\' in \'%s\'' % (
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001713 entry_fixed, self.root_dir))
digit@chromium.orgdc112ac2013-04-24 13:00:19 +00001714 gclient_utils.rmtree(e_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001715 # record the current list of entries for next time
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001716 self._SaveEntries()
maruel@chromium.org17cdf762010-05-28 17:30:52 +00001717 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001718
1719 def PrintRevInfo(self):
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001720 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +00001721 raise gclient_utils.Error('No solution specified')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001722 # Load all the settings.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001723 work_queue = gclient_utils.ExecutionQueue(
1724 self._options.jobs, None, False, verbose=self._options.verbose)
maruel@chromium.org049bced2010-08-12 13:37:20 +00001725 for s in self.dependencies:
Paweł Hajdan, Jr4baaa112017-07-04 19:09:32 +02001726 if s.should_process:
1727 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +00001728 work_queue.flush({}, None, [], options=self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001729
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001730 def GetURLAndRev(dep):
1731 """Returns the revision-qualified SCM url for a Dependency."""
1732 if dep.parsed_url is None:
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001733 return None
agabled437d762016-10-17 09:35:11 -07001734 url, _ = gclient_utils.SplitUrlRevision(dep.parsed_url)
John Budorick0f7b2002018-01-19 15:46:17 -08001735 scm = dep.CreateSCM(
agabled437d762016-10-17 09:35:11 -07001736 dep.parsed_url, self.root_dir, dep.name, self.outbuf)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001737 if not os.path.isdir(scm.checkout_path):
1738 return None
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001739 return '%s@%s' % (url, scm.revinfo(self._options, [], None))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001740
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001741 if self._options.snapshot:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001742 new_gclient = ''
1743 # First level at .gclient
1744 for d in self.dependencies:
1745 entries = {}
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001746 def GrabDeps(dep):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001747 """Recursively grab dependencies."""
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001748 for d in dep.dependencies:
1749 entries[d.name] = GetURLAndRev(d)
1750 GrabDeps(d)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001751 GrabDeps(d)
1752 custom_deps = []
1753 for k in sorted(entries.keys()):
1754 if entries[k]:
1755 # Quotes aren't escaped...
1756 custom_deps.append(' \"%s\": \'%s\',\n' % (k, entries[k]))
1757 else:
1758 custom_deps.append(' \"%s\": None,\n' % k)
1759 new_gclient += self.DEFAULT_SNAPSHOT_SOLUTION_TEXT % {
1760 'solution_name': d.name,
1761 'solution_url': d.url,
nsylvain@google.comefc80932011-05-31 21:27:56 +00001762 'deps_file': d.deps_file,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001763 'managed': d.managed,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001764 'solution_deps': ''.join(custom_deps),
1765 }
1766 # Print the snapshot configuration file
1767 print(self.DEFAULT_SNAPSHOT_FILE_TEXT % {'solution_list': new_gclient})
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +00001768 else:
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001769 entries = {}
maruel@chromium.org68988972011-09-20 14:11:42 +00001770 for d in self.root.subtree(False):
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001771 if self._options.actual:
1772 entries[d.name] = GetURLAndRev(d)
1773 else:
1774 entries[d.name] = d.parsed_url
1775 keys = sorted(entries.keys())
1776 for x in keys:
maruel@chromium.orgce464892010-08-12 17:12:18 +00001777 print('%s: %s' % (x, entries[x]))
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +00001778 logging.info(str(self))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001779
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001780 def ParseDepsFile(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001781 """No DEPS to parse for a .gclient file."""
maruel@chromium.org049bced2010-08-12 13:37:20 +00001782 raise gclient_utils.Error('Internal error')
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001783
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00001784 def PrintLocationAndContents(self):
1785 # Print out the .gclient file. This is longer than if we just printed the
1786 # client dict, but more legible, and it might contain helpful comments.
1787 print('Loaded .gclient config in %s:\n%s' % (
1788 self.root_dir, self.config_content))
1789
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001790 @property
maruel@chromium.org75a59272010-06-11 22:34:03 +00001791 def root_dir(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001792 """Root directory of gclient checkout."""
maruel@chromium.org75a59272010-06-11 22:34:03 +00001793 return self._root_dir
1794
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001795 @property
maruel@chromium.org271375b2010-06-23 19:17:38 +00001796 def enforced_os(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001797 """What deps_os entries that are to be parsed."""
maruel@chromium.org271375b2010-06-23 19:17:38 +00001798 return self._enforced_os
1799
maruel@chromium.org68988972011-09-20 14:11:42 +00001800 @property
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001801 def recursion_limit(self):
1802 """How recursive can each dependencies in DEPS file can load DEPS file."""
1803 return self._recursion_limit
1804
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +00001805 @property
cmp@chromium.orgc401ad12014-07-02 23:20:08 +00001806 def try_recursedeps(self):
1807 """Whether to attempt using recursedeps-style recursion processing."""
cmp@chromium.orge84ac912014-06-30 23:14:35 +00001808 return True
1809
1810 @property
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +00001811 def target_os(self):
1812 return self._enforced_os
1813
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001814
John Budorick0f7b2002018-01-19 15:46:17 -08001815class GitDependency(Dependency):
1816 """A Dependency object that represents a single git checkout."""
1817
1818 #override
1819 def GetScmName(self, url):
1820 """Always 'git'."""
1821 del url
1822 return 'git'
1823
1824 #override
1825 def CreateSCM(self, url, root_dir=None, relpath=None, out_fh=None,
1826 out_cb=None):
1827 """Create a Wrapper instance suitable for handling this git dependency."""
1828 return gclient_scm.GitWrapper(url, root_dir, relpath, out_fh, out_cb)
1829
1830
1831class CipdDependency(Dependency):
1832 """A Dependency object that represents a single CIPD package."""
1833
1834 def __init__(
1835 self, parent, name, dep_value, cipd_root,
1836 custom_vars, should_process, relative, condition, condition_value):
1837 package = dep_value['package']
1838 version = dep_value['version']
1839 url = urlparse.urljoin(
1840 cipd_root.service_url, '%s@%s' % (package, version))
1841 super(CipdDependency, self).__init__(
1842 parent, name, url, url, None, None, custom_vars,
1843 None, None, should_process, relative, condition, condition_value)
1844 if relative:
1845 # TODO(jbudorick): Implement relative if necessary.
1846 raise gclient_utils.Error(
1847 'Relative CIPD dependencies are not currently supported.')
1848 self._cipd_root = cipd_root
1849
1850 self._cipd_subdir = os.path.relpath(
1851 os.path.join(self.root.root_dir, self.name), cipd_root.root_dir)
1852 self._cipd_package = self._cipd_root.add_package(
1853 self._cipd_subdir, package, version)
1854
1855 def ParseDepsFile(self):
1856 """CIPD dependencies are not currently allowed to have nested deps."""
1857 self.add_dependencies_and_close([], [])
1858
1859 #override
1860 def GetScmName(self, url):
1861 """Always 'cipd'."""
1862 del url
1863 return 'cipd'
1864
1865 #override
1866 def CreateSCM(self, url, root_dir=None, relpath=None, out_fh=None,
1867 out_cb=None):
1868 """Create a Wrapper instance suitable for handling this CIPD dependency."""
1869 return gclient_scm.CipdWrapper(
1870 url, root_dir, relpath, out_fh, out_cb,
1871 root=self._cipd_root,
1872 package=self._cipd_package)
1873
1874 def ToLines(self):
1875 """Return a list of lines representing this in a DEPS file."""
1876 s = []
1877 if self._cipd_package.authority_for_subdir:
1878 condition_part = ([' "condition": %r,' % self.condition]
1879 if self.condition else [])
1880 s.extend([
1881 ' # %s' % self.hierarchy(include_url=False),
1882 ' "%s": {' % (self.name,),
1883 ' "packages": [',
1884 ])
1885 for p in self._cipd_root.packages(self._cipd_subdir):
1886 s.extend([
1887 ' "package": "%s",' % p.name,
1888 ' "version": "%s",' % p.version,
1889 ])
1890 s.extend([
1891 ' ],',
1892 ' "dep_type": "cipd",',
1893 ] + condition_part + [
1894 ' },',
1895 '',
1896 ])
1897 return s
1898
1899
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001900#### gclient commands.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001901
1902
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001903@subcommand.usage('[command] [args ...]')
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001904def CMDrecurse(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001905 """Operates [command args ...] on all the dependencies.
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001906
1907 Runs a shell command on all entries.
qyearsley12fa6ff2016-08-24 09:18:40 -07001908 Sets GCLIENT_DEP_PATH environment variable as the dep's relative location to
ilevy@chromium.org37116242012-11-28 01:32:48 +00001909 root directory of the checkout.
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001910 """
1911 # Stop parsing at the first non-arg so that these go through to the command
1912 parser.disable_interspersed_args()
1913 parser.add_option('-s', '--scm', action='append', default=[],
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001914 help='Choose scm types to operate upon.')
maruel@chromium.org288054d2012-03-05 00:43:07 +00001915 parser.add_option('-i', '--ignore', action='store_true',
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001916 help='Ignore non-zero return codes from subcommands.')
1917 parser.add_option('--prepend-dir', action='store_true',
1918 help='Prepend relative dir for use with git <cmd> --null.')
1919 parser.add_option('--no-progress', action='store_true',
1920 help='Disable progress bar that shows sub-command updates')
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001921 options, args = parser.parse_args(args)
maruel@chromium.org45e9f2d2010-10-18 13:33:46 +00001922 if not args:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001923 print('Need to supply a command!', file=sys.stderr)
maruel@chromium.org45e9f2d2010-10-18 13:33:46 +00001924 return 1
maruel@chromium.org78cba522010-10-18 13:32:05 +00001925 root_and_entries = gclient_utils.GetGClientRootAndEntries()
1926 if not root_and_entries:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001927 print(
maruel@chromium.org78cba522010-10-18 13:32:05 +00001928 'You need to run gclient sync at least once to use \'recurse\'.\n'
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001929 'This is because .gclient_entries needs to exist and be up to date.',
1930 file=sys.stderr)
maruel@chromium.org78cba522010-10-18 13:32:05 +00001931 return 1
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001932
1933 # Normalize options.scm to a set()
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001934 scm_set = set()
1935 for scm in options.scm:
1936 scm_set.update(scm.split(','))
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001937 options.scm = scm_set
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001938
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001939 options.nohooks = True
1940 client = GClient.LoadCurrentConfig(options)
Marc-Antoine Ruele6e06412017-10-18 13:47:02 -04001941 if not client:
1942 raise gclient_utils.Error('client not configured; see \'gclient config\'')
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001943 return client.RunOnDeps('recurse', args, ignore_requirements=True,
1944 progress=not options.no_progress)
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001945
1946
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001947@subcommand.usage('[args ...]')
davidbarr@chromium.org12f944e2012-03-01 02:18:31 +00001948def CMDfetch(parser, args):
1949 """Fetches upstream commits for all modules.
1950
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001951 Completely git-specific. Simply runs 'git fetch [args ...]' for each module.
1952 """
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001953 (options, args) = parser.parse_args(args)
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00001954 return CMDrecurse(OptionParser(), [
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001955 '--jobs=%d' % options.jobs, '--scm=git', 'git', 'fetch'] + args)
1956
1957
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02001958class Flattener(object):
1959 """Flattens a gclient solution."""
1960
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02001961 def __init__(self, client, pin_all_deps=False):
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02001962 """Constructor.
1963
1964 Arguments:
1965 client (GClient): client to flatten
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02001966 pin_all_deps (bool): whether to pin all deps, even if they're not pinned
1967 in DEPS
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02001968 """
1969 self._client = client
1970
1971 self._deps_string = None
Paweł Hajdan, Jrfeb01642017-09-12 15:50:46 +02001972 self._deps_files = set()
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02001973
1974 self._allowed_hosts = set()
1975 self._deps = {}
1976 self._deps_os = {}
1977 self._hooks = []
1978 self._hooks_os = {}
1979 self._pre_deps_hooks = []
Paweł Hajdan, Jrfb022012017-07-06 18:00:08 +02001980 self._vars = {}
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02001981
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02001982 self._flatten(pin_all_deps=pin_all_deps)
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02001983
1984 @property
1985 def deps_string(self):
1986 assert self._deps_string is not None
1987 return self._deps_string
1988
Paweł Hajdan, Jrfeb01642017-09-12 15:50:46 +02001989 @property
1990 def deps_files(self):
1991 return self._deps_files
1992
Paweł Hajdan, Jr5ec77132017-08-16 19:21:06 +02001993 def _pin_dep(self, dep):
1994 """Pins a dependency to specific full revision sha.
1995
1996 Arguments:
1997 dep (Dependency): dependency to process
1998 """
1999 if dep.parsed_url is None:
2000 return
2001
2002 # Make sure the revision is always fully specified (a hash),
2003 # as opposed to refs or tags which might change. Similarly,
2004 # shortened shas might become ambiguous; make sure to always
2005 # use full one for pinning.
2006 url, revision = gclient_utils.SplitUrlRevision(dep.parsed_url)
2007 if revision and gclient_utils.IsFullGitSha(revision):
2008 return
2009
John Budorick0f7b2002018-01-19 15:46:17 -08002010 scm = dep.CreateSCM(
Paweł Hajdan, Jr5ec77132017-08-16 19:21:06 +02002011 dep.parsed_url, self._client.root_dir, dep.name, dep.outbuf)
2012 revinfo = scm.revinfo(self._client._options, [], None)
2013
2014 dep._parsed_url = dep._url = '%s@%s' % (url, revinfo)
2015 raw_url, _ = gclient_utils.SplitUrlRevision(dep._raw_url)
2016 dep._raw_url = '%s@%s' % (raw_url, revinfo)
2017
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02002018 def _flatten(self, pin_all_deps=False):
2019 """Runs the flattener. Saves resulting DEPS string.
2020
2021 Arguments:
2022 pin_all_deps (bool): whether to pin all deps, even if they're not pinned
2023 in DEPS
2024 """
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002025 for solution in self._client.dependencies:
Paweł Hajdan, Jr11eb7152017-08-10 12:50:11 +02002026 self._add_dep(solution)
Paweł Hajdan, Jrb0ad16e2017-08-03 15:33:21 +02002027 self._flatten_dep(solution)
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002028
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02002029 if pin_all_deps:
2030 for dep in self._deps.itervalues():
Paweł Hajdan, Jr5ec77132017-08-16 19:21:06 +02002031 self._pin_dep(dep)
Paweł Hajdan, Jr39300ba2017-08-11 16:52:38 +02002032
Paweł Hajdan, Jr5ec77132017-08-16 19:21:06 +02002033 for os_deps in self._deps_os.itervalues():
2034 for dep in os_deps.itervalues():
Paweł Hajdan, Jr5ec77132017-08-16 19:21:06 +02002035 self._pin_dep(dep)
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02002036
Paweł Hajdan, Jr76a9d042017-08-18 20:05:41 +02002037 def add_deps_file(dep):
Paweł Hajdan, Jr0870df22017-08-23 17:59:29 +02002038 # Only include DEPS files referenced by recursedeps.
2039 if not (dep.parent is None or
2040 (dep.name in (dep.parent.recursedeps or {}))):
2041 return
Paweł Hajdan, Jrfeb01642017-09-12 15:50:46 +02002042 deps_file = dep.deps_file
2043 deps_path = os.path.join(self._client.root_dir, dep.name, deps_file)
Paweł Hajdan, Jr76a9d042017-08-18 20:05:41 +02002044 if not os.path.exists(deps_path):
Paweł Hajdan, Jrfeb01642017-09-12 15:50:46 +02002045 # gclient has a fallback that if deps_file doesn't exist, it'll try
2046 # DEPS. Do the same here.
2047 deps_file = 'DEPS'
2048 deps_path = os.path.join(self._client.root_dir, dep.name, deps_file)
2049 if not os.path.exists(deps_path):
2050 return
Paweł Hajdan, Jr76a9d042017-08-18 20:05:41 +02002051 assert dep.parsed_url
Paweł Hajdan, Jrfeb01642017-09-12 15:50:46 +02002052 self._deps_files.add((dep.parsed_url, deps_file))
Paweł Hajdan, Jr76a9d042017-08-18 20:05:41 +02002053 for dep in self._deps.itervalues():
2054 add_deps_file(dep)
2055 for os_deps in self._deps_os.itervalues():
2056 for dep in os_deps.itervalues():
2057 add_deps_file(dep)
2058
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002059 self._deps_string = '\n'.join(
2060 _GNSettingsToLines(
2061 self._client.dependencies[0]._gn_args_file,
2062 self._client.dependencies[0]._gn_args) +
2063 _AllowedHostsToLines(self._allowed_hosts) +
2064 _DepsToLines(self._deps) +
2065 _DepsOsToLines(self._deps_os) +
2066 _HooksToLines('hooks', self._hooks) +
2067 _HooksToLines('pre_deps_hooks', self._pre_deps_hooks) +
2068 _HooksOsToLines(self._hooks_os) +
Paweł Hajdan, Jrfb022012017-07-06 18:00:08 +02002069 _VarsToLines(self._vars) +
Paweł Hajdan, Jr76a9d042017-08-18 20:05:41 +02002070 ['# %s, %s' % (url, deps_file)
Paweł Hajdan, Jrfeb01642017-09-12 15:50:46 +02002071 for url, deps_file in sorted(self._deps_files)] +
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002072 ['']) # Ensure newline at end of file.
2073
Paweł Hajdan, Jr11eb7152017-08-10 12:50:11 +02002074 def _add_dep(self, dep):
2075 """Helper to add a dependency to flattened DEPS.
2076
2077 Arguments:
2078 dep (Dependency): dependency to add
2079 """
2080 assert dep.name not in self._deps or self._deps.get(dep.name) == dep, (
2081 dep.name, self._deps.get(dep.name))
Paweł Hajdan, Jr9a289022017-08-10 16:04:24 +02002082 if dep.url:
2083 self._deps[dep.name] = dep
Paweł Hajdan, Jr11eb7152017-08-10 12:50:11 +02002084
Paweł Hajdan, Jradae2a62017-08-18 16:49:57 +02002085 def _add_os_dep(self, os_dep, dep_os):
2086 """Helper to add an OS-specific dependency to flattened DEPS.
2087
2088 Arguments:
2089 os_dep (Dependency): dependency to add
2090 dep_os (str): name of the OS
2091 """
2092 assert (
2093 os_dep.name not in self._deps_os.get(dep_os, {}) or
2094 self._deps_os.get(dep_os, {}).get(os_dep.name) == os_dep), (
2095 os_dep.name, self._deps_os.get(dep_os, {}).get(os_dep.name))
2096 if os_dep.url:
Paweł Hajdan, Jr76a9d042017-08-18 20:05:41 +02002097 # OS-specific deps need to have their full URL resolved manually.
2098 assert not os_dep.parsed_url, (os_dep, os_dep.parsed_url)
2099 os_dep._parsed_url = os_dep.LateOverride(os_dep.url)
2100
Paweł Hajdan, Jradae2a62017-08-18 16:49:57 +02002101 self._deps_os.setdefault(dep_os, {})[os_dep.name] = os_dep
2102
2103 def _flatten_dep(self, dep, dep_os=None):
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002104 """Visits a dependency in order to flatten it (see CMDflatten).
2105
2106 Arguments:
2107 dep (Dependency): dependency to process
Paweł Hajdan, Jradae2a62017-08-18 16:49:57 +02002108 dep_os (str or None): name of the OS |dep| is specific to
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002109 """
Paweł Hajdan, Jradae2a62017-08-18 16:49:57 +02002110 logging.debug('_flatten_dep(%s, %s)', dep.name, dep_os)
2111
Paweł Hajdan, Jrc69b32e2017-08-17 18:47:48 +02002112 if not dep.deps_parsed:
2113 dep.ParseDepsFile()
2114
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002115 self._allowed_hosts.update(dep.allowed_hosts)
2116
Michael Mossce9f17f2018-01-31 13:16:35 -08002117 # Only include vars explicitly listed in the DEPS files or gclient solution,
2118 # not automatic, local overrides (i.e. not all of dep.get_vars()).
2119 hierarchy = dep.hierarchy(include_url=False)
Paweł Hajdan, Jra3b67ae2017-08-30 15:18:21 +02002120 for key, value in dep._vars.iteritems():
Paweł Hajdan, Jrc9353602017-08-02 17:52:08 +02002121 # Make sure there are no conflicting variables. It is fine however
2122 # to use same variable name, as long as the value is consistent.
2123 assert key not in self._vars or self._vars[key][1] == value
Michael Mossce9f17f2018-01-31 13:16:35 -08002124 self._vars[key] = (hierarchy, value)
2125 # Override explicit custom variables.
2126 for key, value in dep.custom_vars.iteritems():
2127 # Do custom_vars that don't correspond to DEPS vars ever make sense? DEPS
2128 # conditionals shouldn't be using vars that aren't also defined in the
2129 # DEPS (presubmit actually disallows this), so any new custom_var must be
2130 # unused in the DEPS, so no need to add it to the flattened output either.
2131 if key not in self._vars:
2132 continue
2133 # Don't "override" existing vars if it's actually the same value.
2134 elif self._vars[key][1] == value:
2135 continue
2136 # Anything else is overriding a default value from the DEPS.
2137 self._vars[key] = (hierarchy + ' [custom_var override]', value)
Paweł Hajdan, Jrfb022012017-07-06 18:00:08 +02002138
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002139 self._pre_deps_hooks.extend([(dep, hook) for hook in dep.pre_deps_hooks])
2140
Paweł Hajdan, Jradae2a62017-08-18 16:49:57 +02002141 if dep_os:
2142 if dep.deps_hooks:
2143 self._hooks_os.setdefault(dep_os, []).extend(
2144 [(dep, hook) for hook in dep.deps_hooks])
2145 else:
2146 self._hooks.extend([(dep, hook) for hook in dep.deps_hooks])
2147
Paweł Hajdan, Jrb0ad16e2017-08-03 15:33:21 +02002148 for sub_dep in dep.dependencies:
Paweł Hajdan, Jradae2a62017-08-18 16:49:57 +02002149 if dep_os:
2150 self._add_os_dep(sub_dep, dep_os)
2151 else:
2152 self._add_dep(sub_dep)
Paweł Hajdan, Jrb0ad16e2017-08-03 15:33:21 +02002153
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002154 for hook_os, os_hooks in dep.os_deps_hooks.iteritems():
2155 self._hooks_os.setdefault(hook_os, []).extend(
2156 [(dep, hook) for hook in os_hooks])
2157
Paweł Hajdan, Jradae2a62017-08-18 16:49:57 +02002158 for sub_dep_os, os_deps in dep.os_dependencies.iteritems():
Paweł Hajdan, Jre2deb1e2017-08-09 17:29:21 +02002159 for os_dep in os_deps:
Paweł Hajdan, Jradae2a62017-08-18 16:49:57 +02002160 self._add_os_dep(os_dep, sub_dep_os)
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002161
Paweł Hajdan, Jradae2a62017-08-18 16:49:57 +02002162 # Process recursedeps. |deps_by_name| is a map where keys are dependency
2163 # names, and values are maps of OS names to |Dependency| instances.
2164 # |None| in place of OS name means the dependency is not OS-specific.
2165 deps_by_name = dict((d.name, {None: d}) for d in dep.dependencies)
2166 for sub_dep_os, os_deps in dep.os_dependencies.iteritems():
Paweł Hajdan, Jrc9353602017-08-02 17:52:08 +02002167 for os_dep in os_deps:
Paweł Hajdan, Jradae2a62017-08-18 16:49:57 +02002168 assert sub_dep_os not in deps_by_name.get(os_dep.name, {}), (
2169 os_dep.name, sub_dep_os)
2170 deps_by_name.setdefault(os_dep.name, {})[sub_dep_os] = os_dep
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002171 for recurse_dep_name in (dep.recursedeps or []):
Paweł Hajdan, Jradae2a62017-08-18 16:49:57 +02002172 dep_info = deps_by_name[recurse_dep_name]
2173 for sub_dep_os, os_dep in dep_info.iteritems():
2174 self._flatten_dep(os_dep, dep_os=(sub_dep_os or dep_os))
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002175
2176
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002177def CMDflatten(parser, args):
2178 """Flattens the solutions into a single DEPS file."""
2179 parser.add_option('--output-deps', help='Path to the output DEPS file')
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02002180 parser.add_option(
Paweł Hajdan, Jrfeb01642017-09-12 15:50:46 +02002181 '--output-deps-files',
2182 help=('Path to the output metadata about DEPS files referenced by '
2183 'recursedeps.'))
2184 parser.add_option(
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02002185 '--pin-all-deps', action='store_true',
2186 help=('Pin all deps, even if not pinned in DEPS. CAVEAT: only does so '
2187 'for checked out deps, NOT deps_os.'))
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002188 options, args = parser.parse_args(args)
2189
Paweł Hajdan, Jr357415c2017-07-24 14:35:28 +02002190 options.do_not_merge_os_specific_entries = True
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002191 options.nohooks = True
Paweł Hajdan, Jr5ec77132017-08-16 19:21:06 +02002192 options.process_all_deps = True
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002193 client = GClient.LoadCurrentConfig(options)
2194
2195 # Only print progress if we're writing to a file. Otherwise, progress updates
2196 # could obscure intended output.
2197 code = client.RunOnDeps('flatten', args, progress=options.output_deps)
2198 if code != 0:
2199 return code
2200
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02002201 flattener = Flattener(client, pin_all_deps=options.pin_all_deps)
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002202
2203 if options.output_deps:
2204 with open(options.output_deps, 'w') as f:
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002205 f.write(flattener.deps_string)
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002206 else:
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002207 print(flattener.deps_string)
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002208
Paweł Hajdan, Jrfeb01642017-09-12 15:50:46 +02002209 deps_files = [{'url': d[0], 'deps_file': d[1]}
2210 for d in sorted(flattener.deps_files)]
2211 if options.output_deps_files:
2212 with open(options.output_deps_files, 'w') as f:
2213 json.dump(deps_files, f)
2214
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002215 return 0
2216
2217
Paweł Hajdan, Jr3c2aa832017-06-07 20:22:16 +02002218def _GNSettingsToLines(gn_args_file, gn_args):
2219 s = []
2220 if gn_args_file:
2221 s.extend([
2222 'gclient_gn_args_file = "%s"' % gn_args_file,
2223 'gclient_gn_args = %r' % gn_args,
2224 ])
2225 return s
2226
2227
Paweł Hajdan, Jr6014b562017-06-30 17:43:42 +02002228def _AllowedHostsToLines(allowed_hosts):
2229 """Converts |allowed_hosts| set to list of lines for output."""
2230 if not allowed_hosts:
2231 return []
2232 s = ['allowed_hosts = [']
2233 for h in sorted(allowed_hosts):
2234 s.append(' "%s",' % h)
2235 s.extend([']', ''])
2236 return s
2237
2238
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002239def _DepsToLines(deps):
2240 """Converts |deps| dict to list of lines for output."""
Paweł Hajdan, Jr5b593352017-06-29 18:37:45 +02002241 if not deps:
2242 return []
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002243 s = ['deps = {']
John Budorick0f7b2002018-01-19 15:46:17 -08002244 for _, dep in sorted(deps.iteritems()):
2245 s.extend(dep.ToLines())
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002246 s.extend(['}', ''])
2247 return s
2248
2249
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02002250def _DepsOsToLines(deps_os):
2251 """Converts |deps_os| dict to list of lines for output."""
Paweł Hajdan, Jr5b593352017-06-29 18:37:45 +02002252 if not deps_os:
2253 return []
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02002254 s = ['deps_os = {']
2255 for dep_os, os_deps in sorted(deps_os.iteritems()):
2256 s.append(' "%s": {' % dep_os)
Paweł Hajdan, Jrad30de62017-06-26 18:51:58 +02002257 for name, dep in sorted(os_deps.iteritems()):
Paweł Hajdan, Jr78ce24e2017-10-03 17:09:13 +02002258 condition_part = ([' "condition": %r,' % dep.condition]
Paweł Hajdan, Jrad30de62017-06-26 18:51:58 +02002259 if dep.condition else [])
2260 s.extend([
2261 ' # %s' % dep.hierarchy(include_url=False),
2262 ' "%s": {' % (name,),
Paweł Hajdan, Jrde86ab32017-08-10 13:55:16 +02002263 ' "url": "%s",' % (dep.raw_url,),
Paweł Hajdan, Jrad30de62017-06-26 18:51:58 +02002264 ] + condition_part + [
2265 ' },',
2266 '',
2267 ])
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02002268 s.extend([' },', ''])
2269 s.extend(['}', ''])
2270 return s
2271
2272
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002273def _HooksToLines(name, hooks):
2274 """Converts |hooks| list to list of lines for output."""
Paweł Hajdan, Jr5b593352017-06-29 18:37:45 +02002275 if not hooks:
2276 return []
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002277 s = ['%s = [' % name]
2278 for dep, hook in hooks:
2279 s.extend([
2280 ' # %s' % dep.hierarchy(include_url=False),
2281 ' {',
2282 ])
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +02002283 if hook.name is not None:
2284 s.append(' "name": "%s",' % hook.name)
2285 if hook.pattern is not None:
2286 s.append(' "pattern": "%s",' % hook.pattern)
Paweł Hajdan, Jrecf53fe2017-09-29 18:28:49 +02002287 if hook.condition is not None:
Paweł Hajdan, Jr78ce24e2017-10-03 17:09:13 +02002288 s.append(' "condition": %r,' % hook.condition)
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002289 s.extend(
Paweł Hajdan, Jrc9364392017-06-14 17:11:56 +02002290 # Hooks run in the parent directory of their dep.
Paweł Hajdan, Jrad30de62017-06-26 18:51:58 +02002291 [' "cwd": "%s",' % os.path.normpath(os.path.dirname(dep.name))] +
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002292 [' "action": ['] +
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +02002293 [' "%s",' % arg for arg in hook.action] +
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002294 [' ]', ' },', '']
2295 )
2296 s.extend([']', ''])
2297 return s
2298
2299
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +02002300def _HooksOsToLines(hooks_os):
2301 """Converts |hooks| list to list of lines for output."""
Paweł Hajdan, Jr5b593352017-06-29 18:37:45 +02002302 if not hooks_os:
2303 return []
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +02002304 s = ['hooks_os = {']
2305 for hook_os, os_hooks in hooks_os.iteritems():
Michael Moss017bcf62017-06-28 15:26:38 -07002306 s.append(' "%s": [' % hook_os)
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +02002307 for dep, hook in os_hooks:
2308 s.extend([
2309 ' # %s' % dep.hierarchy(include_url=False),
2310 ' {',
2311 ])
2312 if hook.name is not None:
2313 s.append(' "name": "%s",' % hook.name)
2314 if hook.pattern is not None:
2315 s.append(' "pattern": "%s",' % hook.pattern)
Paweł Hajdan, Jrecf53fe2017-09-29 18:28:49 +02002316 if hook.condition is not None:
Paweł Hajdan, Jr78ce24e2017-10-03 17:09:13 +02002317 s.append(' "condition": %r,' % hook.condition)
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +02002318 s.extend(
2319 # Hooks run in the parent directory of their dep.
2320 [' "cwd": "%s",' % os.path.normpath(os.path.dirname(dep.name))] +
2321 [' "action": ['] +
2322 [' "%s",' % arg for arg in hook.action] +
2323 [' ]', ' },', '']
2324 )
Michael Moss017bcf62017-06-28 15:26:38 -07002325 s.extend([' ],', ''])
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +02002326 s.extend(['}', ''])
2327 return s
2328
2329
Paweł Hajdan, Jrfb022012017-07-06 18:00:08 +02002330def _VarsToLines(variables):
2331 """Converts |variables| dict to list of lines for output."""
2332 if not variables:
2333 return []
2334 s = ['vars = {']
2335 for key, tup in sorted(variables.iteritems()):
Michael Mossce9f17f2018-01-31 13:16:35 -08002336 hierarchy, value = tup
Paweł Hajdan, Jrfb022012017-07-06 18:00:08 +02002337 s.extend([
Michael Mossce9f17f2018-01-31 13:16:35 -08002338 ' # %s' % hierarchy,
Paweł Hajdan, Jrfb022012017-07-06 18:00:08 +02002339 ' "%s": %r,' % (key, value),
2340 '',
2341 ])
2342 s.extend(['}', ''])
2343 return s
2344
2345
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00002346def CMDgrep(parser, args):
2347 """Greps through git repos managed by gclient.
2348
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002349 Runs 'git grep [args...]' for each module.
2350 """
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00002351 # We can't use optparse because it will try to parse arguments sent
2352 # to git grep and throw an error. :-(
2353 if not args or re.match('(-h|--help)$', args[0]):
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002354 print(
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00002355 'Usage: gclient grep [-j <N>] git-grep-args...\n\n'
2356 'Example: "gclient grep -j10 -A2 RefCountedBase" runs\n"git grep '
2357 '-A2 RefCountedBase" on each of gclient\'s git\nrepos with up to '
2358 '10 jobs.\n\nBonus: page output by appending "|& less -FRSX" to the'
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002359 ' end of your query.',
2360 file=sys.stderr)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00002361 return 1
2362
2363 jobs_arg = ['--jobs=1']
2364 if re.match(r'(-j|--jobs=)\d+$', args[0]):
2365 jobs_arg, args = args[:1], args[1:]
2366 elif re.match(r'(-j|--jobs)$', args[0]):
2367 jobs_arg, args = args[:2], args[2:]
2368
2369 return CMDrecurse(
2370 parser,
2371 jobs_arg + ['--ignore', '--prepend-dir', '--no-progress', '--scm=git',
2372 'git', 'grep', '--null', '--color=Always'] + args)
davidbarr@chromium.org12f944e2012-03-01 02:18:31 +00002373
2374
stip@chromium.orga735da22015-04-29 23:18:20 +00002375def CMDroot(parser, args):
2376 """Outputs the solution root (or current dir if there isn't one)."""
2377 (options, args) = parser.parse_args(args)
2378 client = GClient.LoadCurrentConfig(options)
2379 if client:
2380 print(os.path.abspath(client.root_dir))
2381 else:
2382 print(os.path.abspath('.'))
2383
2384
agablea98a6cd2016-11-15 14:30:10 -08002385@subcommand.usage('[url]')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002386def CMDconfig(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002387 """Creates a .gclient file in the current directory.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00002388
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002389 This specifies the configuration for further commands. After update/sync,
2390 top-level DEPS files in each module are read to determine dependent
2391 modules to operate on as well. If optional [url] parameter is
2392 provided, then configuration is read from a specified Subversion server
2393 URL.
2394 """
szager@chromium.orge2e03202012-07-31 18:05:16 +00002395 # We do a little dance with the --gclientfile option. 'gclient config' is the
2396 # only command where it's acceptable to have both '--gclientfile' and '--spec'
2397 # arguments. So, we temporarily stash any --gclientfile parameter into
2398 # options.output_config_file until after the (gclientfile xor spec) error
2399 # check.
2400 parser.remove_option('--gclientfile')
2401 parser.add_option('--gclientfile', dest='output_config_file',
2402 help='Specify an alternate .gclient file')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002403 parser.add_option('--name',
2404 help='overrides the default name for the solution')
nsylvain@google.comefc80932011-05-31 21:27:56 +00002405 parser.add_option('--deps-file', default='DEPS',
David Benjamin105e11e2017-10-16 10:39:35 -04002406 help='overrides the default name for the DEPS file for the '
nsylvain@google.comefc80932011-05-31 21:27:56 +00002407 'main solutions and all sub-dependencies')
smutae7ea312016-07-18 11:59:41 -07002408 parser.add_option('--unmanaged', action='store_true', default=False,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00002409 help='overrides the default behavior to make it possible '
smutae7ea312016-07-18 11:59:41 -07002410 'to have the main solution untouched by gclient '
2411 '(gclient will check out unmanaged dependencies but '
2412 'will never sync them)')
Paweł Hajdan, Jr3ba2a7c2017-10-04 19:24:46 +02002413 parser.add_option('--custom-var', action='append', dest='custom_vars',
2414 default=[],
2415 help='overrides variables; key=value syntax')
szager@chromium.orge2e03202012-07-31 18:05:16 +00002416 parser.set_defaults(config_filename=None)
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002417 (options, args) = parser.parse_args(args)
szager@chromium.orge2e03202012-07-31 18:05:16 +00002418 if options.output_config_file:
2419 setattr(options, 'config_filename', getattr(options, 'output_config_file'))
maruel@chromium.org5fc2a332010-05-26 19:37:15 +00002420 if ((options.spec and args) or len(args) > 2 or
2421 (not options.spec and not args)):
2422 parser.error('Inconsistent arguments. Use either --spec or one or 2 args')
2423
Paweł Hajdan, Jr3ba2a7c2017-10-04 19:24:46 +02002424 custom_vars = {}
2425 for arg in options.custom_vars:
2426 kv = arg.split('=', 1)
2427 if len(kv) != 2:
2428 parser.error('Invalid --custom-var argument: %r' % arg)
2429 custom_vars[kv[0]] = gclient_eval.EvaluateCondition(kv[1], {})
2430
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002431 client = GClient('.', options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002432 if options.spec:
2433 client.SetConfig(options.spec)
2434 else:
maruel@chromium.org1ab7ffc2009-06-03 17:21:37 +00002435 base_url = args[0].rstrip('/')
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00002436 if not options.name:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002437 name = base_url.split('/')[-1]
nsylvain@google.com12649ef2011-06-01 17:11:20 +00002438 if name.endswith('.git'):
2439 name = name[:-4]
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00002440 else:
2441 # specify an alternate relpath for the given URL.
2442 name = options.name
agable@chromium.orgf2214672015-10-27 21:02:48 +00002443 if not os.path.abspath(os.path.join(os.getcwd(), name)).startswith(
2444 os.getcwd()):
2445 parser.error('Do not pass a relative path for --name.')
2446 if any(x in ('..', '.', '/', '\\') for x in name.split(os.sep)):
2447 parser.error('Do not include relative path components in --name.')
2448
nsylvain@google.comefc80932011-05-31 21:27:56 +00002449 deps_file = options.deps_file
agablea98a6cd2016-11-15 14:30:10 -08002450 client.SetDefaultConfig(name, deps_file, base_url,
smutae7ea312016-07-18 11:59:41 -07002451 managed=not options.unmanaged,
Paweł Hajdan, Jr3ba2a7c2017-10-04 19:24:46 +02002452 cache_dir=options.cache_dir,
2453 custom_vars=custom_vars)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002454 client.SaveConfig()
maruel@chromium.org79692d62010-05-14 18:57:13 +00002455 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002456
2457
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002458@subcommand.epilog("""Example:
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002459 gclient pack > patch.txt
2460 generate simple patch for configured client and dependences
2461""")
2462def CMDpack(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002463 """Generates a patch which can be applied at the root of the tree.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00002464
agabled437d762016-10-17 09:35:11 -07002465 Internally, runs 'git diff' on each checked out module and
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002466 dependencies, and performs minimal postprocessing of the output. The
2467 resulting patch is printed to stdout and can be applied to a freshly
2468 checked out tree via 'patch -p0 < patchfile'.
2469 """
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002470 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2471 help='override deps for the specified (comma-separated) '
2472 'platform(s); \'all\' will process all deps_os '
2473 'references')
haitao.feng@intel.com306080c2012-05-04 13:11:29 +00002474 parser.remove_option('--jobs')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002475 (options, args) = parser.parse_args(args)
iannucci@chromium.org50395ea2013-04-04 04:47:42 +00002476 # Force jobs to 1 so the stdout is not annotated with the thread ids
haitao.feng@intel.com306080c2012-05-04 13:11:29 +00002477 options.jobs = 1
kbr@google.comab318592009-09-04 00:54:55 +00002478 client = GClient.LoadCurrentConfig(options)
2479 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002480 raise gclient_utils.Error('client not configured; see \'gclient config\'')
kbr@google.comab318592009-09-04 00:54:55 +00002481 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002482 client.PrintLocationAndContents()
kbr@google.comab318592009-09-04 00:54:55 +00002483 return client.RunOnDeps('pack', args)
2484
2485
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002486def CMDstatus(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002487 """Shows modification status for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002488 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2489 help='override deps for the specified (comma-separated) '
2490 'platform(s); \'all\' will process all deps_os '
2491 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002492 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002493 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002494 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002495 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002496 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002497 client.PrintLocationAndContents()
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002498 return client.RunOnDeps('status', args)
2499
2500
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002501@subcommand.epilog("""Examples:
maruel@chromium.org79692d62010-05-14 18:57:13 +00002502 gclient sync
2503 update files from SCM according to current configuration,
2504 *for modules which have changed since last update or sync*
2505 gclient sync --force
2506 update files from SCM according to current configuration, for
2507 all modules (useful for recovering files deleted from local copy)
2508 gclient sync --revision src@31000
2509 update src directory to r31000
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002510
2511JSON output format:
2512If the --output-json option is specified, the following document structure will
2513be emitted to the provided file. 'null' entries may occur for subprojects which
2514are present in the gclient solution, but were not processed (due to custom_deps,
2515os_deps, etc.)
2516
2517{
2518 "solutions" : {
2519 "<name>": { # <name> is the posix-normalized path to the solution.
agabled437d762016-10-17 09:35:11 -07002520 "revision": [<git id hex string>|null],
2521 "scm": ["git"|null],
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002522 }
2523 }
2524}
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002525""")
2526def CMDsync(parser, args):
2527 """Checkout/update all modules."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002528 parser.add_option('-f', '--force', action='store_true',
2529 help='force update even for unchanged modules')
2530 parser.add_option('-n', '--nohooks', action='store_true',
2531 help='don\'t run hooks after the update is complete')
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00002532 parser.add_option('-p', '--noprehooks', action='store_true',
2533 help='don\'t run pre-DEPS hooks', default=False)
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002534 parser.add_option('-r', '--revision', action='append',
2535 dest='revisions', metavar='REV', default=[],
2536 help='Enforces revision/hash for the solutions with the '
2537 'format src@rev. The src@ part is optional and can be '
2538 'skipped. -r can be used multiple times when .gclient '
2539 'has multiple solutions configured and will work even '
agablea98a6cd2016-11-15 14:30:10 -08002540 'if the src@ part is skipped.')
maruel@chromium.org794207e2013-03-08 15:29:43 +00002541 parser.add_option('--with_branch_heads', action='store_true',
2542 help='Clone git "branch_heads" refspecs in addition to '
2543 'the default refspecs. This adds about 1/2GB to a '
2544 'full checkout. (git only)')
szager@chromium.org8d3348f2014-08-19 22:49:16 +00002545 parser.add_option('--with_tags', action='store_true',
2546 help='Clone git tags in addition to the default refspecs.')
agable2697cd12016-06-28 10:23:53 -07002547 parser.add_option('-H', '--head', action='store_true',
agablea98a6cd2016-11-15 14:30:10 -08002548 help='DEPRECATED: only made sense with safesync urls.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002549 parser.add_option('-D', '--delete_unversioned_trees', action='store_true',
steveblock@chromium.org98e69452012-02-16 16:36:43 +00002550 help='Deletes from the working copy any dependencies that '
2551 'have been removed since the last sync, as long as '
2552 'there are no local modifications. When used with '
2553 '--force, such dependencies are removed even if they '
2554 'have local modifications. When used with --reset, '
2555 'all untracked directories are removed from the '
tnagel@chromium.orga2aaa632014-02-28 21:47:27 +00002556 'working copy, excluding those which are explicitly '
steveblock@chromium.org98e69452012-02-16 16:36:43 +00002557 'ignored in the repository.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002558 parser.add_option('-R', '--reset', action='store_true',
2559 help='resets any local changes before updating (git only)')
bauerb@chromium.org2aad1b22011-07-22 12:00:41 +00002560 parser.add_option('-M', '--merge', action='store_true',
2561 help='merge upstream changes instead of trying to '
2562 'fast-forward or rebase')
dnj@chromium.org5b23e872015-02-20 21:25:57 +00002563 parser.add_option('-A', '--auto_rebase', action='store_true',
2564 help='Automatically rebase repositories against local '
2565 'checkout during update (git only).')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002566 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2567 help='override deps for the specified (comma-separated) '
2568 'platform(s); \'all\' will process all deps_os '
2569 'references')
Paweł Hajdan, Jr357415c2017-07-24 14:35:28 +02002570 # TODO(phajdan.jr): use argparse.SUPPRESS to hide internal flags.
2571 parser.add_option('--do-not-merge-os-specific-entries', action='store_true',
2572 help='INTERNAL ONLY - disables merging of deps_os and '
2573 'hooks_os to dependencies and hooks')
Paweł Hajdan, Jr5ec77132017-08-16 19:21:06 +02002574 parser.add_option('--process-all-deps', action='store_true',
2575 help='Check out all deps, even for different OS-es, '
2576 'or with conditions evaluating to false')
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +00002577 parser.add_option('--upstream', action='store_true',
2578 help='Make repo state match upstream branch.')
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002579 parser.add_option('--output-json',
2580 help='Output a json document to this path containing '
2581 'summary information about the sync.')
primiano@chromium.org5439ea52014-08-06 17:18:18 +00002582 parser.add_option('--no-history', action='store_true',
2583 help='GIT ONLY - Reduces the size/time of the checkout at '
2584 'the cost of no history. Requires Git 1.9+')
hinoka@chromium.org46b87412014-05-15 00:42:05 +00002585 parser.add_option('--shallow', action='store_true',
2586 help='GIT ONLY - Do a shallow clone into the cache dir. '
2587 'Requires Git 1.9+')
e.hakkinen@samsung.come8bc1aa2015-04-08 08:00:37 +00002588 parser.add_option('--no_bootstrap', '--no-bootstrap',
2589 action='store_true',
2590 help='Don\'t bootstrap from Google Storage.')
Vadim Shtayura08049e22017-10-11 00:14:52 +00002591 parser.add_option('--ignore_locks', action='store_true',
2592 help='GIT ONLY - Ignore cache locks.')
iannucci@chromium.org30a07982016-04-07 21:35:19 +00002593 parser.add_option('--break_repo_locks', action='store_true',
2594 help='GIT ONLY - Forcibly remove repo locks (e.g. '
2595 'index.lock). This should only be used if you know for '
2596 'certain that this invocation of gclient is the only '
2597 'thing operating on the git repos (e.g. on a bot).')
Vadim Shtayura08049e22017-10-11 00:14:52 +00002598 parser.add_option('--lock_timeout', type='int', default=5000,
2599 help='GIT ONLY - Deadline (in seconds) to wait for git '
2600 'cache lock to become available. Default is %default.')
agabled437d762016-10-17 09:35:11 -07002601 # TODO(agable): Remove these when the oldest CrOS release milestone is M56.
2602 parser.add_option('-t', '--transitive', action='store_true',
2603 help='DEPRECATED: This is a no-op.')
sdefresne69b1be12016-10-18 05:48:02 -07002604 parser.add_option('-m', '--manually_grab_svn_rev', action='store_true',
agabled437d762016-10-17 09:35:11 -07002605 help='DEPRECATED: This is a no-op.')
Paweł Hajdan, Jr7c7b5592017-05-23 15:06:05 +02002606 # TODO(phajdan.jr): Remove validation options once default (crbug/570091).
Paweł Hajdan, Jr694773d2017-05-29 16:06:23 +02002607 parser.add_option('--validate-syntax', action='store_true', default=True,
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +02002608 help='Validate the .gclient and DEPS syntax')
Paweł Hajdan, Jr7c7b5592017-05-23 15:06:05 +02002609 parser.add_option('--disable-syntax-validation', action='store_false',
2610 dest='validate_syntax',
2611 help='Disable validation of .gclient and DEPS syntax.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002612 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002613 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002614
2615 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002616 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002617
smutae7ea312016-07-18 11:59:41 -07002618 if options.revisions and options.head:
2619 # TODO(maruel): Make it a parser.error if it doesn't break any builder.
2620 print('Warning: you cannot use both --head and --revision')
2621
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002622 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002623 client.PrintLocationAndContents()
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002624 ret = client.RunOnDeps('update', args)
2625 if options.output_json:
2626 slns = {}
2627 for d in client.subtree(True):
2628 normed = d.name.replace('\\', '/').rstrip('/') + '/'
2629 slns[normed] = {
2630 'revision': d.got_revision,
2631 'scm': d.used_scm.name if d.used_scm else None,
hinoka@chromium.org17db9052014-05-10 01:11:29 +00002632 'url': str(d.url) if d.url else None,
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002633 }
2634 with open(options.output_json, 'wb') as f:
2635 json.dump({'solutions': slns}, f)
2636 return ret
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002637
2638
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002639CMDupdate = CMDsync
2640
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002641
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +02002642def CMDvalidate(parser, args):
2643 """Validates the .gclient and DEPS syntax."""
2644 options, args = parser.parse_args(args)
2645 options.validate_syntax = True
2646 client = GClient.LoadCurrentConfig(options)
2647 rv = client.RunOnDeps('validate', args)
2648 if rv == 0:
2649 print('validate: SUCCESS')
2650 else:
2651 print('validate: FAILURE')
2652 return rv
2653
2654
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002655def CMDdiff(parser, args):
2656 """Displays local diff for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002657 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2658 help='override deps for the specified (comma-separated) '
2659 'platform(s); \'all\' will process all deps_os '
2660 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002661 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002662 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002663 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002664 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002665 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002666 client.PrintLocationAndContents()
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002667 return client.RunOnDeps('diff', args)
2668
2669
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002670def CMDrevert(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002671 """Reverts all modifications in every dependencies.
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00002672
2673 That's the nuclear option to get back to a 'clean' state. It removes anything
agabled437d762016-10-17 09:35:11 -07002674 that shows up in git status."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002675 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2676 help='override deps for the specified (comma-separated) '
2677 'platform(s); \'all\' will process all deps_os '
2678 'references')
2679 parser.add_option('-n', '--nohooks', action='store_true',
2680 help='don\'t run hooks after the revert is complete')
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00002681 parser.add_option('-p', '--noprehooks', action='store_true',
2682 help='don\'t run pre-DEPS hooks', default=False)
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +00002683 parser.add_option('--upstream', action='store_true',
2684 help='Make repo state match upstream branch.')
iannucci@chromium.orgbf525dc2016-04-07 22:00:28 +00002685 parser.add_option('--break_repo_locks', action='store_true',
2686 help='GIT ONLY - Forcibly remove repo locks (e.g. '
2687 'index.lock). This should only be used if you know for '
2688 'certain that this invocation of gclient is the only '
2689 'thing operating on the git repos (e.g. on a bot).')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002690 (options, args) = parser.parse_args(args)
2691 # --force is implied.
2692 options.force = True
steveblock@chromium.org98e69452012-02-16 16:36:43 +00002693 options.reset = False
2694 options.delete_unversioned_trees = False
agablec903d732016-07-26 09:07:24 -07002695 options.merge = False
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002696 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002697 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002698 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002699 return client.RunOnDeps('revert', args)
2700
2701
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002702def CMDrunhooks(parser, args):
2703 """Runs hooks for files that have been modified in the local working copy."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002704 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2705 help='override deps for the specified (comma-separated) '
2706 'platform(s); \'all\' will process all deps_os '
2707 'references')
2708 parser.add_option('-f', '--force', action='store_true', default=True,
2709 help='Deprecated. No effect.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002710 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002711 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002712 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002713 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002714 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002715 client.PrintLocationAndContents()
maruel@chromium.org5df6a462009-08-28 18:52:26 +00002716 options.force = True
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002717 options.nohooks = False
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002718 return client.RunOnDeps('runhooks', args)
2719
2720
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002721def CMDrevinfo(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002722 """Outputs revision info mapping for the client and its dependencies.
maruel@chromium.org9eda4112010-06-11 18:56:10 +00002723
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002724 This allows the capture of an overall 'revision' for the source tree that
maruel@chromium.org9eda4112010-06-11 18:56:10 +00002725 can be used to reproduce the same tree in the future. It is only useful for
agabled437d762016-10-17 09:35:11 -07002726 'unpinned dependencies', i.e. DEPS/deps references without a git hash.
2727 A git branch name isn't 'pinned' since the actual commit can change.
maruel@chromium.org9eda4112010-06-11 18:56:10 +00002728 """
2729 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2730 help='override deps for the specified (comma-separated) '
2731 'platform(s); \'all\' will process all deps_os '
2732 'references')
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00002733 parser.add_option('-a', '--actual', action='store_true',
2734 help='gets the actual checked out revisions instead of the '
2735 'ones specified in the DEPS and .gclient files')
maruel@chromium.org9eda4112010-06-11 18:56:10 +00002736 parser.add_option('-s', '--snapshot', action='store_true',
2737 help='creates a snapshot .gclient file of the current '
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00002738 'version of all repositories to reproduce the tree, '
2739 'implies -a')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002740 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002741 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002742 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002743 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002744 client.PrintRevInfo()
maruel@chromium.org79692d62010-05-14 18:57:13 +00002745 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002746
2747
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00002748def CMDverify(parser, args):
2749 """Verifies the DEPS file deps are only from allowed_hosts."""
2750 (options, args) = parser.parse_args(args)
2751 client = GClient.LoadCurrentConfig(options)
2752 if not client:
2753 raise gclient_utils.Error('client not configured; see \'gclient config\'')
2754 client.RunOnDeps(None, [])
2755 # Look at each first-level dependency of this gclient only.
2756 for dep in client.dependencies:
2757 bad_deps = dep.findDepsFromNotAllowedHosts()
2758 if not bad_deps:
2759 continue
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002760 print("There are deps from not allowed hosts in file %s" % dep.deps_file)
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00002761 for bad_dep in bad_deps:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002762 print("\t%s at %s" % (bad_dep.name, bad_dep.url))
2763 print("allowed_hosts:", ', '.join(dep.allowed_hosts))
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00002764 sys.stdout.flush()
2765 raise gclient_utils.Error(
2766 'dependencies from disallowed hosts; check your DEPS file.')
2767 return 0
2768
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002769class OptionParser(optparse.OptionParser):
szager@chromium.orge2e03202012-07-31 18:05:16 +00002770 gclientfile_default = os.environ.get('GCLIENT_FILE', '.gclient')
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002771
2772 def __init__(self, **kwargs):
2773 optparse.OptionParser.__init__(
2774 self, version='%prog ' + __version__, **kwargs)
2775
2776 # Some arm boards have issues with parallel sync.
2777 if platform.machine().startswith('arm'):
2778 jobs = 1
2779 else:
2780 jobs = max(8, gclient_utils.NumLocalCpus())
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002781
2782 self.add_option(
2783 '-j', '--jobs', default=jobs, type='int',
2784 help='Specify how many SCM commands can run in parallel; defaults to '
tnagel@chromium.orga2aaa632014-02-28 21:47:27 +00002785 '%default on this machine')
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002786 self.add_option(
2787 '-v', '--verbose', action='count', default=0,
2788 help='Produces additional output for diagnostics. Can be used up to '
2789 'three times for more logging info.')
2790 self.add_option(
2791 '--gclientfile', dest='config_filename',
2792 help='Specify an alternate %s file' % self.gclientfile_default)
2793 self.add_option(
2794 '--spec',
2795 help='create a gclient file containing the provided string. Due to '
2796 'Cygwin/Python brokenness, it can\'t contain any newlines.')
2797 self.add_option(
Aleksandr Derbenev9e8fb0e2017-08-01 20:18:31 +03002798 '--cache-dir',
2799 help='(git only) Cache all git repos into this dir and do '
2800 'shared clones from the cache, instead of cloning '
2801 'directly from the remote. (experimental)',
2802 default=os.environ.get('GCLIENT_CACHE_DIR'))
2803 self.add_option(
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002804 '--no-nag-max', default=False, action='store_true',
scottmg@chromium.orgf547c802013-09-27 17:55:26 +00002805 help='Ignored for backwards compatibility.')
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002806
2807 def parse_args(self, args=None, values=None):
2808 """Integrates standard options processing."""
2809 options, args = optparse.OptionParser.parse_args(self, args, values)
2810 levels = [logging.ERROR, logging.WARNING, logging.INFO, logging.DEBUG]
2811 logging.basicConfig(
2812 level=levels[min(options.verbose, len(levels) - 1)],
maruel@chromium.org0895b752011-08-26 20:40:33 +00002813 format='%(module)s(%(lineno)d) %(funcName)s:%(message)s')
szager@chromium.orge2e03202012-07-31 18:05:16 +00002814 if options.config_filename and options.spec:
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002815 self.error('Cannot specifiy both --gclientfile and --spec')
rdsmith@chromium.orgd9591f02014-02-05 19:28:20 +00002816 if (options.config_filename and
2817 options.config_filename != os.path.basename(options.config_filename)):
2818 self.error('--gclientfile target must be a filename, not a path')
szager@chromium.orge2e03202012-07-31 18:05:16 +00002819 if not options.config_filename:
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002820 options.config_filename = self.gclientfile_default
maruel@chromium.org0895b752011-08-26 20:40:33 +00002821 options.entries_filename = options.config_filename + '_entries'
2822 if options.jobs < 1:
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002823 self.error('--jobs must be 1 or higher')
maruel@chromium.org0895b752011-08-26 20:40:33 +00002824
2825 # These hacks need to die.
2826 if not hasattr(options, 'revisions'):
2827 # GClient.RunOnDeps expects it even if not applicable.
2828 options.revisions = []
smutae7ea312016-07-18 11:59:41 -07002829 if not hasattr(options, 'head'):
2830 options.head = None
maruel@chromium.org0895b752011-08-26 20:40:33 +00002831 if not hasattr(options, 'nohooks'):
2832 options.nohooks = True
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00002833 if not hasattr(options, 'noprehooks'):
2834 options.noprehooks = True
maruel@chromium.org0895b752011-08-26 20:40:33 +00002835 if not hasattr(options, 'deps_os'):
2836 options.deps_os = None
maruel@chromium.org0895b752011-08-26 20:40:33 +00002837 if not hasattr(options, 'force'):
2838 options.force = None
2839 return (options, args)
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002840
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002841
2842def disable_buffering():
2843 # Make stdout auto-flush so buildbot doesn't kill us during lengthy
2844 # operations. Python as a strong tendency to buffer sys.stdout.
2845 sys.stdout = gclient_utils.MakeFileAutoFlush(sys.stdout)
2846 # Make stdout annotated with the thread ids.
2847 sys.stdout = gclient_utils.MakeFileAnnotated(sys.stdout)
maruel@chromium.org0895b752011-08-26 20:40:33 +00002848
2849
sbc@chromium.org013731e2015-02-26 18:28:43 +00002850def main(argv):
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002851 """Doesn't parse the arguments here, just find the right subcommand to
2852 execute."""
maruel@chromium.org82798cb2012-02-23 18:16:12 +00002853 if sys.hexversion < 0x02060000:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002854 print(
maruel@chromium.org82798cb2012-02-23 18:16:12 +00002855 '\nYour python version %s is unsupported, please upgrade.\n' %
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002856 sys.version.split(' ', 1)[0],
2857 file=sys.stderr)
maruel@chromium.org82798cb2012-02-23 18:16:12 +00002858 return 2
bcwhite@chromium.org6683ab42013-02-11 16:13:47 +00002859 if not sys.executable:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002860 print(
2861 '\nPython cannot find the location of it\'s own executable.\n',
2862 file=sys.stderr)
bcwhite@chromium.org6683ab42013-02-11 16:13:47 +00002863 return 2
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002864 fix_encoding.fix_encoding()
2865 disable_buffering()
iannucci@chromium.org596cd5c2016-04-04 21:34:39 +00002866 setup_color.init()
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002867 dispatcher = subcommand.CommandDispatcher(__name__)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00002868 try:
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002869 return dispatcher.execute(OptionParser(), argv)
xusydoc@chromium.org2fd6c3f2013-05-03 21:57:55 +00002870 except KeyboardInterrupt:
2871 gclient_utils.GClientChildren.KillAllRemainingChildren()
2872 raise
vapier@chromium.orga81a56e2015-11-11 07:56:13 +00002873 except (gclient_utils.Error, subprocess2.CalledProcessError) as e:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002874 print('Error: %s' % str(e), file=sys.stderr)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00002875 return 1
borenet@google.com6a9b1682014-03-24 18:35:23 +00002876 finally:
2877 gclient_utils.PrintWarnings()
sbc@chromium.org013731e2015-02-26 18:28:43 +00002878 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002879
2880
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00002881if '__main__' == __name__:
sbc@chromium.org013731e2015-02-26 18:28:43 +00002882 try:
2883 sys.exit(main(sys.argv[1:]))
2884 except KeyboardInterrupt:
2885 sys.stderr.write('interrupted\n')
2886 sys.exit(1)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002887
2888# vim: ts=2:sw=2:tw=80:et: