blob: 96c2ab3df5d0217d4980671403271fb47d65766b [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
Daniel Chenga0c5f082017-10-19 13:35:19 -0700162 def from_dict(d, variables=None, verbose=False):
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200163 """Creates a Hook instance from a dict like in the DEPS file."""
Paweł Hajdan, Jr032d5452017-06-22 20:43:53 +0200164 return Hook(
165 d['action'],
166 d.get('pattern'),
167 d.get('name'),
168 d.get('cwd'),
169 d.get('condition'),
Daniel Chenga0c5f082017-10-19 13:35:19 -0700170 variables=variables,
171 # Always print the header if not printing to a TTY.
172 verbose=verbose or not setup_color.IS_TTY)
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200173
174 @property
175 def action(self):
176 return self._action
177
178 @property
179 def pattern(self):
180 return self._pattern
181
182 @property
183 def name(self):
184 return self._name
185
Paweł Hajdan, Jrecf53fe2017-09-29 18:28:49 +0200186 @property
187 def condition(self):
188 return self._condition
189
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200190 def matches(self, file_list):
191 """Returns true if the pattern matches any of files in the list."""
192 if not self._pattern:
193 return True
194 pattern = re.compile(self._pattern)
195 return bool([f for f in file_list if pattern.search(f)])
196
197 def run(self, root):
Paweł Hajdan, Jr032d5452017-06-22 20:43:53 +0200198 """Executes the hook's command (provided the condition is met)."""
199 if (self._condition and
200 not gclient_eval.EvaluateCondition(self._condition, self._variables)):
201 return
202
Paweł Hajdan, Jrfc6196b2017-07-27 13:15:25 +0200203 cmd = [arg.format(**self._variables) for arg in self._action]
204
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200205 if cmd[0] == 'python':
206 # If the hook specified "python" as the first item, the action is a
207 # Python script. Run it by starting a new copy of the same
208 # interpreter.
209 cmd[0] = sys.executable
Nodir Turakulov0ffcc872017-11-09 16:44:58 -0800210 elif cmd[0] == 'vpython' and _detect_host_os() == 'win':
211 cmd[0] += '.bat'
Paweł Hajdan, Jrc9364392017-06-14 17:11:56 +0200212
213 cwd = root
214 if self._cwd:
215 cwd = os.path.join(cwd, self._cwd)
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200216 try:
217 start_time = time.time()
218 gclient_utils.CheckCallAndFilterAndHeader(
Daniel Chenga0c5f082017-10-19 13:35:19 -0700219 cmd, cwd=cwd, always=self._verbose)
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200220 except (gclient_utils.Error, subprocess2.CalledProcessError) as e:
221 # Use a discrete exit status code of 2 to indicate that a hook action
222 # failed. Users of this script may wish to treat hook action failures
223 # differently from VC failures.
224 print('Error: %s' % str(e), file=sys.stderr)
225 sys.exit(2)
226 finally:
227 elapsed_time = time.time() - start_time
228 if elapsed_time > 10:
229 print("Hook '%s' took %.2f secs" % (
230 gclient_utils.CommandToStr(cmd), elapsed_time))
231
232
Paweł Hajdan, Jrfc6196b2017-07-27 13:15:25 +0200233class DependencySettings(object):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000234 """Immutable configuration settings."""
235 def __init__(
Paweł Hajdan, Jrfc6196b2017-07-27 13:15:25 +0200236 self, parent, raw_url, url, managed, custom_deps, custom_vars,
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +0200237 custom_hooks, deps_file, should_process, relative,
238 condition, condition_value):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000239 # These are not mutable:
240 self._parent = parent
mmoss@chromium.org8f93f792014-08-26 23:24:09 +0000241 self._deps_file = deps_file
Paweł Hajdan, Jrfc6196b2017-07-27 13:15:25 +0200242 self._raw_url = raw_url
maruel@chromium.org064186c2011-09-27 23:53:33 +0000243 self._url = url
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +0200244 # The condition as string (or None). Useful to keep e.g. for flatten.
245 self._condition = condition
246 # Boolean value of the condition. If there's no condition, just True.
247 self._condition_value = condition_value
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000248 # 'managed' determines whether or not this dependency is synced/updated by
249 # gclient after gclient checks it out initially. The difference between
250 # 'managed' and 'should_process' is that the user specifies 'managed' via
smutae7ea312016-07-18 11:59:41 -0700251 # the --unmanaged command-line flag or a .gclient config, where
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000252 # 'should_process' is dynamically set by gclient if it goes over its
253 # recursion limit and controls gclient's behavior so it does not misbehave.
254 self._managed = managed
255 self._should_process = should_process
agabledce6ddc2016-09-08 10:02:16 -0700256 # If this is a recursed-upon sub-dependency, and the parent has
257 # use_relative_paths set, then this dependency should check out its own
258 # dependencies relative to that parent's path for this, rather than
259 # relative to the .gclient file.
260 self._relative = relative
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +0000261 # This is a mutable value which has the list of 'target_os' OSes listed in
262 # the current deps file.
263 self.local_target_os = None
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000264
265 # These are only set in .gclient and not in DEPS files.
266 self._custom_vars = custom_vars or {}
267 self._custom_deps = custom_deps or {}
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000268 self._custom_hooks = custom_hooks or []
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000269
maruel@chromium.org064186c2011-09-27 23:53:33 +0000270 # Post process the url to remove trailing slashes.
271 if isinstance(self._url, basestring):
272 # urls are sometime incorrectly written as proto://host/path/@rev. Replace
273 # it to proto://host/path@rev.
maruel@chromium.org064186c2011-09-27 23:53:33 +0000274 self._url = self._url.replace('/@', '@')
Paweł Hajdan, Jr7e9303b2017-05-23 14:38:27 +0200275 elif not isinstance(self._url, (None.__class__)):
maruel@chromium.org064186c2011-09-27 23:53:33 +0000276 raise gclient_utils.Error(
Paweł Hajdan, Jr7e9303b2017-05-23 14:38:27 +0200277 ('dependency url must be either string or None, '
278 'instead of %s') % self._url.__class__.__name__)
mmoss@chromium.orgd0b272b2013-01-30 23:55:33 +0000279 # Make any deps_file path platform-appropriate.
John Budorick0f7b2002018-01-19 15:46:17 -0800280 if self._deps_file:
281 for sep in ['/', '\\']:
282 self._deps_file = self._deps_file.replace(sep, os.sep)
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000283
284 @property
285 def deps_file(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000286 return self._deps_file
287
288 @property
289 def managed(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000290 return self._managed
291
292 @property
293 def parent(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000294 return self._parent
295
296 @property
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000297 def root(self):
298 """Returns the root node, a GClient object."""
299 if not self.parent:
300 # This line is to signal pylint that it could be a GClient instance.
301 return self or GClient(None, None)
302 return self.parent.root
303
304 @property
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000305 def should_process(self):
306 """True if this dependency should be processed, i.e. checked out."""
307 return self._should_process
308
309 @property
310 def custom_vars(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000311 return self._custom_vars.copy()
312
313 @property
314 def custom_deps(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000315 return self._custom_deps.copy()
316
maruel@chromium.org064186c2011-09-27 23:53:33 +0000317 @property
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000318 def custom_hooks(self):
319 return self._custom_hooks[:]
320
321 @property
Paweł Hajdan, Jrfc6196b2017-07-27 13:15:25 +0200322 def raw_url(self):
323 """URL before variable expansion."""
324 return self._raw_url
325
326 @property
maruel@chromium.org064186c2011-09-27 23:53:33 +0000327 def url(self):
Paweł Hajdan, Jrfc6196b2017-07-27 13:15:25 +0200328 """URL after variable expansion."""
maruel@chromium.org064186c2011-09-27 23:53:33 +0000329 return self._url
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000330
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000331 @property
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +0200332 def condition(self):
333 return self._condition
334
335 @property
336 def condition_value(self):
337 return self._condition_value
338
339 @property
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +0000340 def target_os(self):
341 if self.local_target_os is not None:
342 return tuple(set(self.local_target_os).union(self.parent.target_os))
343 else:
344 return self.parent.target_os
345
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000346 def get_custom_deps(self, name, url):
347 """Returns a custom deps if applicable."""
348 if self.parent:
349 url = self.parent.get_custom_deps(name, url)
350 # None is a valid return value to disable a dependency.
351 return self.custom_deps.get(name, url)
352
maruel@chromium.org064186c2011-09-27 23:53:33 +0000353
354class Dependency(gclient_utils.WorkItem, DependencySettings):
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000355 """Object that represents a dependency checkout."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000356
Paweł Hajdan, Jrfc6196b2017-07-27 13:15:25 +0200357 def __init__(self, parent, name, raw_url, url, managed, custom_deps,
agabledce6ddc2016-09-08 10:02:16 -0700358 custom_vars, custom_hooks, deps_file, should_process,
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +0200359 relative, condition, condition_value):
maruel@chromium.org6ca8bf82011-09-19 23:04:30 +0000360 gclient_utils.WorkItem.__init__(self, name)
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000361 DependencySettings.__init__(
Paweł Hajdan, Jrfc6196b2017-07-27 13:15:25 +0200362 self, parent, raw_url, url, managed, custom_deps, custom_vars,
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +0200363 custom_hooks, deps_file, should_process, relative,
364 condition, condition_value)
maruel@chromium.org68988972011-09-20 14:11:42 +0000365
366 # This is in both .gclient and DEPS files:
maruel@chromium.org064186c2011-09-27 23:53:33 +0000367 self._deps_hooks = []
maruel@chromium.org68988972011-09-20 14:11:42 +0000368
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000369 self._pre_deps_hooks = []
370
maruel@chromium.org68988972011-09-20 14:11:42 +0000371 # Calculates properties:
maruel@chromium.org064186c2011-09-27 23:53:33 +0000372 self._parsed_url = None
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +0000373 self._dependencies = []
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +0200374 self._vars = {}
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +0200375 self._os_dependencies = {}
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +0200376 self._os_deps_hooks = {}
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200377
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000378 # A cache of the files affected by the current operation, necessary for
379 # hooks.
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000380 self._file_list = []
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000381 # List of host names from which dependencies are allowed.
382 # Default is an empty set, meaning unspecified in DEPS file, and hence all
383 # hosts will be allowed. Non-empty set means whitelist of hosts.
384 # allowed_hosts var is scoped to its DEPS file, and so it isn't recursive.
385 self._allowed_hosts = frozenset()
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +0200386 # Spec for .gni output to write (if any).
387 self._gn_args_file = None
388 self._gn_args = []
maruel@chromium.org85c2a192010-07-22 21:14:43 +0000389 # If it is not set to True, the dependency wasn't processed for its child
390 # dependency, i.e. its DEPS wasn't read.
maruel@chromium.org064186c2011-09-27 23:53:33 +0000391 self._deps_parsed = False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000392 # This dependency has been processed, i.e. checked out
maruel@chromium.org064186c2011-09-27 23:53:33 +0000393 self._processed = False
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000394 # This dependency had its pre-DEPS hooks run
395 self._pre_deps_hooks_ran = False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000396 # This dependency had its hook run
maruel@chromium.org064186c2011-09-27 23:53:33 +0000397 self._hooks_ran = False
kustermann@google.coma692e8f2013-04-18 08:32:04 +0000398 # This is the scm used to checkout self.url. It may be used by dependencies
399 # to get the datetime of the revision we checked out.
400 self._used_scm = None
szager@chromium.org4ad264b2014-05-20 04:43:47 +0000401 self._used_revision = None
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +0000402 # The actual revision we ended up getting, or None if that information is
403 # unavailable
404 self._got_revision = None
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000405
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000406 # This is a mutable value that overrides the normal recursion limit for this
407 # dependency. It is read from the actual DEPS file so cannot be set on
408 # class instantiation.
409 self.recursion_override = None
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000410 # recursedeps is a mutable value that selectively overrides the default
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000411 # 'no recursion' setting on a dep-by-dep basis. It will replace
412 # recursion_override.
iannucci@chromium.orgafa11ac2016-05-04 22:17:34 +0000413 #
414 # It will be a dictionary of {deps_name: {"deps_file": depfile_name}} or
415 # None.
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000416 self.recursedeps = None
hinoka885e5b12016-06-08 14:40:09 -0700417 # This is inherited from WorkItem. We want the URL to be a resource.
418 if url and isinstance(url, basestring):
419 # The url is usually given to gclient either as https://blah@123
qyearsley12fa6ff2016-08-24 09:18:40 -0700420 # or just https://blah. The @123 portion is irrelevant.
hinoka885e5b12016-06-08 14:40:09 -0700421 self.resources.append(url.split('@')[0])
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000422
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000423 if not self.name and self.parent:
424 raise gclient_utils.Error('Dependency without name')
425
John Budorick0f7b2002018-01-19 15:46:17 -0800426 def ToLines(self):
427 s = []
428 condition_part = ([' "condition": %r,' % self.condition]
429 if self.condition else [])
430 s.extend([
431 ' # %s' % self.hierarchy(include_url=False),
432 ' "%s": {' % (self.name,),
433 ' "url": "%s",' % (self.raw_url,),
434 ] + condition_part + [
435 ' },',
436 '',
437 ])
438 return s
439
440
441
maruel@chromium.org470b5432011-10-11 18:18:19 +0000442 @property
443 def requirements(self):
444 """Calculate the list of requirements."""
445 requirements = set()
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000446 # self.parent is implicitly a requirement. This will be recursive by
447 # definition.
448 if self.parent and self.parent.name:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000449 requirements.add(self.parent.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000450
451 # For a tree with at least 2 levels*, the leaf node needs to depend
452 # on the level higher up in an orderly way.
453 # This becomes messy for >2 depth as the DEPS file format is a dictionary,
454 # thus unsorted, while the .gclient format is a list thus sorted.
455 #
456 # * _recursion_limit is hard coded 2 and there is no hope to change this
457 # value.
458 #
459 # Interestingly enough, the following condition only works in the case we
460 # want: self is a 2nd level node. 3nd level node wouldn't need this since
461 # they already have their parent as a requirement.
maruel@chromium.org470b5432011-10-11 18:18:19 +0000462 if self.parent and self.parent.parent and not self.parent.parent.parent:
463 requirements |= set(i.name for i in self.root.dependencies if i.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000464
maruel@chromium.org470b5432011-10-11 18:18:19 +0000465 if self.name:
466 requirements |= set(
467 obj.name for obj in self.root.subtree(False)
468 if (obj is not self
469 and obj.name and
470 self.name.startswith(posixpath.join(obj.name, ''))))
471 requirements = tuple(sorted(requirements))
472 logging.info('Dependency(%s).requirements = %s' % (self.name, requirements))
473 return requirements
474
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000475 @property
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000476 def try_recursedeps(self):
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000477 """Returns False if recursion_override is ever specified."""
478 if self.recursion_override is not None:
479 return False
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000480 return self.parent.try_recursedeps
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000481
482 @property
483 def recursion_limit(self):
484 """Returns > 0 if this dependency is not too recursed to be processed."""
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000485 # We continue to support the absence of recursedeps until tools and DEPS
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000486 # using recursion_override are updated.
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000487 if self.try_recursedeps and self.parent.recursedeps != None:
488 if self.name in self.parent.recursedeps:
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000489 return 1
490
491 if self.recursion_override is not None:
492 return self.recursion_override
493 return max(self.parent.recursion_limit - 1, 0)
494
maruel@chromium.org470b5432011-10-11 18:18:19 +0000495 def verify_validity(self):
496 """Verifies that this Dependency is fine to add as a child of another one.
497
498 Returns True if this entry should be added, False if it is a duplicate of
499 another entry.
500 """
501 logging.info('Dependency(%s).verify_validity()' % self.name)
502 if self.name in [s.name for s in self.parent.dependencies]:
503 raise gclient_utils.Error(
504 'The same name "%s" appears multiple times in the deps section' %
505 self.name)
506 if not self.should_process:
507 # Return early, no need to set requirements.
508 return True
509
510 # This require a full tree traversal with locks.
511 siblings = [d for d in self.root.subtree(False) if d.name == self.name]
512 for sibling in siblings:
maruel@chromium.orgb848d5b2012-10-10 23:25:50 +0000513 self_url = self.LateOverride(self.url)
514 sibling_url = sibling.LateOverride(sibling.url)
515 # Allow to have only one to be None or ''.
516 if self_url != sibling_url and bool(self_url) == bool(sibling_url):
maruel@chromium.org470b5432011-10-11 18:18:19 +0000517 raise gclient_utils.Error(
maruel@chromium.orgb848d5b2012-10-10 23:25:50 +0000518 ('Dependency %s specified more than once:\n'
519 ' %s [%s]\n'
520 'vs\n'
521 ' %s [%s]') % (
522 self.name,
523 sibling.hierarchy(),
524 sibling_url,
525 self.hierarchy(),
526 self_url))
maruel@chromium.org470b5432011-10-11 18:18:19 +0000527 # In theory we could keep it as a shadow of the other one. In
528 # practice, simply ignore it.
529 logging.warn('Won\'t process duplicate dependency %s' % sibling)
530 return False
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000531 return True
maruel@chromium.org064186c2011-09-27 23:53:33 +0000532
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000533 def LateOverride(self, url):
Paweł Hajdan, Jr7e9303b2017-05-23 14:38:27 +0200534 """Resolves the parsed url from url."""
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000535 assert self.parsed_url == None or not self.should_process, self.parsed_url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000536 parsed_url = self.get_custom_deps(self.name, url)
537 if parsed_url != url:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000538 logging.info(
539 'Dependency(%s).LateOverride(%s) -> %s' %
540 (self.name, url, parsed_url))
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000541 return parsed_url
542
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000543 if isinstance(url, basestring):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000544 parsed_url = urlparse.urlparse(url)
scr@chromium.orgf1eccaf2014-04-11 15:51:33 +0000545 if (not parsed_url[0] and
546 not re.match(r'^\w+\@[\w\.-]+\:[\w\/]+', parsed_url[2])):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000547 # A relative url. Fetch the real base.
548 path = parsed_url[2]
549 if not path.startswith('/'):
550 raise gclient_utils.Error(
551 'relative DEPS entry \'%s\' must begin with a slash' % url)
552 # Create a scm just to query the full url.
553 parent_url = self.parent.parsed_url
John Budorick0f7b2002018-01-19 15:46:17 -0800554 scm = self.CreateSCM(
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000555 parent_url, self.root.root_dir, None, self.outbuf)
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000556 parsed_url = scm.FullUrlForRelativeUrl(url)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000557 else:
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000558 parsed_url = url
maruel@chromium.org470b5432011-10-11 18:18:19 +0000559 logging.info(
560 'Dependency(%s).LateOverride(%s) -> %s' %
561 (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000562 return parsed_url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000563
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000564 if url is None:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000565 logging.info(
566 'Dependency(%s).LateOverride(%s) -> %s' % (self.name, url, url))
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000567 return url
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000568
569 raise gclient_utils.Error('Unknown url type')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000570
bratell@opera.comed2b4fe2013-12-16 14:34:12 +0000571 @staticmethod
Paweł Hajdan, Jr5ec77132017-08-16 19:21:06 +0200572 def MergeWithOsDeps(deps, deps_os, target_os_list, process_all_deps):
bratell@opera.comed2b4fe2013-12-16 14:34:12 +0000573 """Returns a new "deps" structure that is the deps sent in updated
574 with information from deps_os (the deps_os section of the DEPS
575 file) that matches the list of target os."""
bratell@opera.comed2b4fe2013-12-16 14:34:12 +0000576 new_deps = deps.copy()
Paweł Hajdan, Jrfd0057e2017-06-21 14:20:21 +0200577 for dep_os, os_deps in deps_os.iteritems():
578 for key, value in os_deps.iteritems():
579 if value is None:
580 # Make this condition very visible, so it's not a silent failure.
581 # It's unclear how to support None override in deps_os.
582 logging.error('Ignoring %r:%r in %r deps_os', key, value, dep_os)
583 continue
584
585 # Normalize value to be a dict which contains |should_process| metadata.
586 if isinstance(value, basestring):
587 value = {'url': value}
588 assert isinstance(value, collections.Mapping), (key, value)
Paweł Hajdan, Jr5ec77132017-08-16 19:21:06 +0200589 value['should_process'] = dep_os in target_os_list or process_all_deps
Paweł Hajdan, Jrfd0057e2017-06-21 14:20:21 +0200590
591 # Handle collisions/overrides.
592 if key in new_deps and new_deps[key] != value:
593 # Normalize the existing new_deps entry.
594 if isinstance(new_deps[key], basestring):
595 new_deps[key] = {'url': new_deps[key]}
596 assert isinstance(new_deps[key],
597 collections.Mapping), (key, new_deps[key])
598
599 # It's OK if the "override" sets the key to the same value.
600 # This is mostly for legacy reasons to keep existing DEPS files
601 # working. Often mac/ios and unix/android will do this.
602 if value['url'] != new_deps[key]['url']:
603 raise gclient_utils.Error(
604 ('Value from deps_os (%r; %r: %r) conflicts with existing deps '
605 'entry (%r).') % (dep_os, key, value, new_deps[key]))
606
607 # We'd otherwise overwrite |should_process| metadata, but a dep should
608 # be processed if _any_ of its references call for that.
609 value['should_process'] = (
610 value['should_process'] or
611 new_deps[key].get('should_process', True))
612
613 new_deps[key] = value
614
bratell@opera.comed2b4fe2013-12-16 14:34:12 +0000615 return new_deps
616
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200617 def _postprocess_deps(self, deps, rel_prefix):
618 """Performs post-processing of deps compared to what's in the DEPS file."""
Paweł Hajdan, Jr4426eaf2017-06-13 12:25:47 +0200619 # Make sure the dict is mutable, e.g. in case it's frozen.
620 deps = dict(deps)
621
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200622 # If a line is in custom_deps, but not in the solution, we want to append
623 # this line to the solution.
624 for d in self.custom_deps:
625 if d not in deps:
626 deps[d] = self.custom_deps[d]
627
628 if rel_prefix:
629 logging.warning('use_relative_paths enabled.')
630 rel_deps = {}
631 for d, url in deps.items():
632 # normpath is required to allow DEPS to use .. in their
633 # dependency local path.
634 rel_deps[os.path.normpath(os.path.join(rel_prefix, d))] = url
635 logging.warning('Updating deps by prepending %s.', rel_prefix)
636 deps = rel_deps
637
638 return deps
639
640 def _deps_to_objects(self, deps, use_relative_paths):
641 """Convert a deps dict to a dict of Dependency objects."""
642 deps_to_add = []
John Budorick0f7b2002018-01-19 15:46:17 -0800643 cipd_root = None
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200644 for name, dep_value in deps.iteritems():
645 should_process = self.recursion_limit and self.should_process
646 deps_file = self.deps_file
647 if self.recursedeps is not None:
648 ent = self.recursedeps.get(name)
649 if ent is not None:
650 deps_file = ent['deps_file']
651 if dep_value is None:
652 continue
John Budorick0f7b2002018-01-19 15:46:17 -0800653
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200654 condition = None
655 condition_value = True
656 if isinstance(dep_value, basestring):
Paweł Hajdan, Jrfc6196b2017-07-27 13:15:25 +0200657 raw_url = dep_value
John Budorick0f7b2002018-01-19 15:46:17 -0800658 dep_type = None
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200659 else:
660 # This should be guaranteed by schema checking in gclient_eval.
661 assert isinstance(dep_value, collections.Mapping)
John Budorick0f7b2002018-01-19 15:46:17 -0800662 raw_url = dep_value.get('url')
Paweł Hajdan, Jrfd0057e2017-06-21 14:20:21 +0200663 # Take into account should_process metadata set by MergeWithOsDeps.
664 should_process = (should_process and
665 dep_value.get('should_process', True))
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200666 condition = dep_value.get('condition')
John Budorick0f7b2002018-01-19 15:46:17 -0800667 dep_type = dep_value.get('dep_type')
Paweł Hajdan, Jrfc6196b2017-07-27 13:15:25 +0200668
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200669 if condition:
Paweł Hajdan, Jrd3790252017-07-03 21:06:24 +0200670 condition_value = gclient_eval.EvaluateCondition(
671 condition, self.get_vars())
Paweł Hajdan, Jr5ec77132017-08-16 19:21:06 +0200672 if not self._get_option('process_all_deps', False):
673 should_process = should_process and condition_value
John Budorick0f7b2002018-01-19 15:46:17 -0800674
675 if dep_type == 'cipd':
676 if not cipd_root:
677 cipd_root = gclient_scm.CipdRoot(
678 os.path.join(self.root.root_dir, self.name),
679 # TODO(jbudorick): Support other service URLs as necessary.
680 # Service URLs should be constant over the scope of a cipd
681 # root, so a var per DEPS file specifying the service URL
682 # should suffice.
683 'https://chrome-infra-packages.appspot.com')
684 for package in dep_value.get('packages', []):
685 deps_to_add.append(
686 CipdDependency(
687 self, name, package, cipd_root,
688 self.custom_vars, should_process, use_relative_paths,
689 condition, condition_value))
690 elif dep_type == 'git':
691 url = raw_url.format(**self.get_vars())
692 deps_to_add.append(
693 GitDependency(
694 self, name, raw_url, url, None, None, self.custom_vars, None,
695 deps_file, should_process, use_relative_paths, condition,
696 condition_value))
697 else:
698 url = raw_url.format(**self.get_vars())
699 deps_to_add.append(
700 Dependency(
701 self, name, raw_url, url, None, None, self.custom_vars, None,
702 deps_file, should_process, use_relative_paths, condition,
703 condition_value))
704
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200705 deps_to_add.sort(key=lambda x: x.name)
706 return deps_to_add
707
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000708 def ParseDepsFile(self):
maruel@chromium.org271375b2010-06-23 19:17:38 +0000709 """Parses the DEPS file for this dependency."""
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000710 assert not self.deps_parsed
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000711 assert not self.dependencies
iannucci@chromium.org0c3f3052014-05-22 00:29:22 +0000712
713 deps_content = None
cmp@chromium.org76ce73c2014-07-02 00:13:18 +0000714
715 # First try to locate the configured deps file. If it's missing, fallback
716 # to DEPS.
717 deps_files = [self.deps_file]
718 if 'DEPS' not in deps_files:
719 deps_files.append('DEPS')
720 for deps_file in deps_files:
721 filepath = os.path.join(self.root.root_dir, self.name, deps_file)
722 if os.path.isfile(filepath):
723 logging.info(
724 'ParseDepsFile(%s): %s file found at %s', self.name, deps_file,
725 filepath)
726 break
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000727 logging.info(
cmp@chromium.org76ce73c2014-07-02 00:13:18 +0000728 'ParseDepsFile(%s): No %s file found at %s', self.name, deps_file,
729 filepath)
730
731 if os.path.isfile(filepath):
maruel@chromium.org46304292010-10-28 11:42:00 +0000732 deps_content = gclient_utils.FileRead(filepath)
cmp@chromium.org76ce73c2014-07-02 00:13:18 +0000733 logging.debug('ParseDepsFile(%s) read:\n%s', self.name, deps_content)
iannucci@chromium.org0c3f3052014-05-22 00:29:22 +0000734
735 local_scope = {}
736 if deps_content:
Paweł Hajdan, Jrf1587bf2017-06-20 21:19:07 +0200737 global_scope = {
Paweł Hajdan, Jrfc6196b2017-07-27 13:15:25 +0200738 'Var': lambda var_name: '{%s}' % var_name,
Paweł Hajdan, Jrf1587bf2017-06-20 21:19:07 +0200739 'deps_os': {},
740 }
maruel@chromium.org46304292010-10-28 11:42:00 +0000741 # Eval the content.
742 try:
Paweł Hajdan, Jrc485d5a2017-06-02 12:08:09 +0200743 if self._get_option('validate_syntax', False):
John Budorick0f7b2002018-01-19 15:46:17 -0800744 local_scope = gclient_eval.Exec(
745 deps_content, global_scope, local_scope, filepath)
Paweł Hajdan, Jrc485d5a2017-06-02 12:08:09 +0200746 else:
747 exec(deps_content, global_scope, local_scope)
vapier@chromium.orga81a56e2015-11-11 07:56:13 +0000748 except SyntaxError as e:
maruel@chromium.org46304292010-10-28 11:42:00 +0000749 gclient_utils.SyntaxErrorToError(filepath, e)
iannucci@chromium.org0c3f3052014-05-22 00:29:22 +0000750
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000751 if 'allowed_hosts' in local_scope:
752 try:
753 self._allowed_hosts = frozenset(local_scope.get('allowed_hosts'))
754 except TypeError: # raised if non-iterable
755 pass
756 if not self._allowed_hosts:
757 logging.warning("allowed_hosts is specified but empty %s",
758 self._allowed_hosts)
759 raise gclient_utils.Error(
760 'ParseDepsFile(%s): allowed_hosts must be absent '
761 'or a non-empty iterable' % self.name)
762
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +0200763 self._gn_args_file = local_scope.get('gclient_gn_args_file')
764 self._gn_args = local_scope.get('gclient_gn_args', [])
765
Paweł Hajdan, Jr1407d002017-08-01 20:01:01 +0200766 self._vars = local_scope.get('vars', {})
767 if self.parent:
768 for key, value in self.parent.get_vars().iteritems():
769 if key in self._vars:
770 self._vars[key] = value
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200771 # Since we heavily post-process things, freeze ones which should
772 # reflect original state of DEPS.
Paweł Hajdan, Jr1407d002017-08-01 20:01:01 +0200773 self._vars = gclient_utils.freeze(self._vars)
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200774
775 # If use_relative_paths is set in the DEPS file, regenerate
776 # the dictionary using paths relative to the directory containing
777 # the DEPS file. Also update recursedeps if use_relative_paths is
778 # enabled.
779 # If the deps file doesn't set use_relative_paths, but the parent did
780 # (and therefore set self.relative on this Dependency object), then we
781 # want to modify the deps and recursedeps by prepending the parent
782 # directory of this dependency.
783 use_relative_paths = local_scope.get('use_relative_paths', False)
784 rel_prefix = None
785 if use_relative_paths:
786 rel_prefix = self.name
787 elif self._relative:
788 rel_prefix = os.path.dirname(self.name)
789
Paweł Hajdan, Jrfc6196b2017-07-27 13:15:25 +0200790 deps = {}
791 for key, value in local_scope.get('deps', {}).iteritems():
792 deps[key.format(**self.get_vars())] = value
793
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200794 if 'recursion' in local_scope:
795 self.recursion_override = local_scope.get('recursion')
796 logging.warning(
797 'Setting %s recursion to %d.', self.name, self.recursion_limit)
798 self.recursedeps = None
799 if 'recursedeps' in local_scope:
800 self.recursedeps = {}
801 for ent in local_scope['recursedeps']:
802 if isinstance(ent, basestring):
803 self.recursedeps[ent] = {"deps_file": self.deps_file}
804 else: # (depname, depsfilename)
805 self.recursedeps[ent[0]] = {"deps_file": ent[1]}
806 logging.warning('Found recursedeps %r.', repr(self.recursedeps))
807
808 if rel_prefix:
809 logging.warning('Updating recursedeps by prepending %s.', rel_prefix)
810 rel_deps = {}
811 for depname, options in self.recursedeps.iteritems():
812 rel_deps[
813 os.path.normpath(os.path.join(rel_prefix, depname))] = options
814 self.recursedeps = rel_deps
815
816 # If present, save 'target_os' in the local_target_os property.
817 if 'target_os' in local_scope:
818 self.local_target_os = local_scope['target_os']
819 # load os specific dependencies if defined. these dependencies may
820 # override or extend the values defined by the 'deps' member.
821 target_os_list = self.target_os
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +0200822 if 'deps_os' in local_scope:
823 for dep_os, os_deps in local_scope['deps_os'].iteritems():
824 self._os_dependencies[dep_os] = self._deps_to_objects(
825 self._postprocess_deps(os_deps, rel_prefix), use_relative_paths)
Paweł Hajdan, Jr357415c2017-07-24 14:35:28 +0200826 if target_os_list and not self._get_option(
827 'do_not_merge_os_specific_entries', False):
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +0200828 deps = self.MergeWithOsDeps(
Paweł Hajdan, Jr5ec77132017-08-16 19:21:06 +0200829 deps, local_scope['deps_os'], target_os_list,
830 self._get_option('process_all_deps', False))
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200831
832 deps_to_add = self._deps_to_objects(
833 self._postprocess_deps(deps, rel_prefix), use_relative_paths)
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000834
835 # override named sets of hooks by the custom hooks
836 hooks_to_run = []
837 hook_names_to_suppress = [c.get('name', '') for c in self.custom_hooks]
838 for hook in local_scope.get('hooks', []):
839 if hook.get('name', '') not in hook_names_to_suppress:
840 hooks_to_run.append(hook)
Scott Grahamc4826742017-05-11 16:59:23 -0700841 if 'hooks_os' in local_scope and target_os_list:
842 hooks_os = local_scope['hooks_os']
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +0200843
844 # Keep original contents of hooks_os for flatten.
845 for hook_os, os_hooks in hooks_os.iteritems():
846 self._os_deps_hooks[hook_os] = [
Daniel Chenga0c5f082017-10-19 13:35:19 -0700847 Hook.from_dict(hook, variables=self.get_vars(), verbose=True)
Paweł Hajdan, Jrd3790252017-07-03 21:06:24 +0200848 for hook in os_hooks]
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +0200849
Scott Grahamc4826742017-05-11 16:59:23 -0700850 # Specifically append these to ensure that hooks_os run after hooks.
Paweł Hajdan, Jr357415c2017-07-24 14:35:28 +0200851 if not self._get_option('do_not_merge_os_specific_entries', False):
852 for the_target_os in target_os_list:
853 the_target_os_hooks = hooks_os.get(the_target_os, [])
854 hooks_to_run.extend(the_target_os_hooks)
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000855
856 # add the replacements and any additions
857 for hook in self.custom_hooks:
858 if 'action' in hook:
859 hooks_to_run.append(hook)
860
Dirk Prankeda3a29e2017-02-27 15:29:36 -0800861 if self.recursion_limit:
Paweł Hajdan, Jr032d5452017-06-22 20:43:53 +0200862 self._pre_deps_hooks = [
Daniel Chenga0c5f082017-10-19 13:35:19 -0700863 Hook.from_dict(hook, variables=self.get_vars(), verbose=True)
864 for hook in local_scope.get('pre_deps_hooks', [])
865 ]
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000866
Paweł Hajdan, Jr357415c2017-07-24 14:35:28 +0200867 self.add_dependencies_and_close(deps_to_add, hooks_to_run)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000868 logging.info('ParseDepsFile(%s) done' % self.name)
869
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +0200870 def _get_option(self, attr, default):
871 obj = self
872 while not hasattr(obj, '_options'):
873 obj = obj.parent
874 return getattr(obj._options, attr, default)
875
Paweł Hajdan, Jr357415c2017-07-24 14:35:28 +0200876 def add_dependencies_and_close(self, deps_to_add, hooks):
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000877 """Adds the dependencies, hooks and mark the parsing as done."""
maruel@chromium.orgb9be0652011-10-14 18:05:40 +0000878 for dep in deps_to_add:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000879 if dep.verify_validity():
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000880 self.add_dependency(dep)
Daniel Chenga0c5f082017-10-19 13:35:19 -0700881 self._mark_as_parsed([
882 Hook.from_dict(
883 h, variables=self.get_vars(), verbose=self.root._options.verbose)
884 for h in hooks
885 ])
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000886
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000887 def findDepsFromNotAllowedHosts(self):
888 """Returns a list of depenecies from not allowed hosts.
889
890 If allowed_hosts is not set, allows all hosts and returns empty list.
891 """
892 if not self._allowed_hosts:
893 return []
894 bad_deps = []
895 for dep in self._dependencies:
szager@chromium.orgbd772dd2014-11-05 18:43:08 +0000896 # Don't enforce this for custom_deps.
897 if dep.name in self._custom_deps:
898 continue
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000899 if isinstance(dep.url, basestring):
900 parsed_url = urlparse.urlparse(dep.url)
901 if parsed_url.netloc and parsed_url.netloc not in self._allowed_hosts:
902 bad_deps.append(dep)
903 return bad_deps
904
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +0000905 # Arguments number differs from overridden method
Quinten Yearsleyb2cc4a92016-12-15 13:53:26 -0800906 # pylint: disable=arguments-differ
maruel@chromium.org3742c842010-09-09 19:27:14 +0000907 def run(self, revision_overrides, command, args, work_queue, options):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000908 """Runs |command| then parse the DEPS file."""
maruel@chromium.org470b5432011-10-11 18:18:19 +0000909 logging.info('Dependency(%s).run()' % self.name)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000910 assert self._file_list == []
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000911 if not self.should_process:
912 return
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000913 # When running runhooks, there's no need to consult the SCM.
914 # All known hooks are expected to run unconditionally regardless of working
915 # copy state, so skip the SCM status check.
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +0200916 run_scm = command not in (
917 'flatten', 'runhooks', 'recurse', 'validate', None)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000918 parsed_url = self.LateOverride(self.url)
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000919 file_list = [] if not options.nohooks else None
szager@chromium.org3a3608d2014-10-22 21:13:52 +0000920 revision_override = revision_overrides.pop(self.name, None)
Dave Tubbda9712017-06-01 15:10:53 -0700921 if not revision_override and parsed_url:
922 revision_override = revision_overrides.get(parsed_url.split('@')[0], None)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000923 if run_scm and parsed_url:
agabled437d762016-10-17 09:35:11 -0700924 # Create a shallow copy to mutate revision.
925 options = copy.copy(options)
926 options.revision = revision_override
927 self._used_revision = options.revision
John Budorick0f7b2002018-01-19 15:46:17 -0800928 self._used_scm = self.CreateSCM(
agabled437d762016-10-17 09:35:11 -0700929 parsed_url, self.root.root_dir, self.name, self.outbuf,
930 out_cb=work_queue.out_cb)
931 self._got_revision = self._used_scm.RunCommand(command, options, args,
932 file_list)
933 if file_list:
934 file_list = [os.path.join(self.name, f.strip()) for f in file_list]
maruel@chromium.org68988972011-09-20 14:11:42 +0000935
936 # TODO(phajdan.jr): We should know exactly when the paths are absolute.
937 # Convert all absolute paths to relative.
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000938 for i in range(len(file_list or [])):
maruel@chromium.org68988972011-09-20 14:11:42 +0000939 # It depends on the command being executed (like runhooks vs sync).
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000940 if not os.path.isabs(file_list[i]):
maruel@chromium.org68988972011-09-20 14:11:42 +0000941 continue
942 prefix = os.path.commonprefix(
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000943 [self.root.root_dir.lower(), file_list[i].lower()])
944 file_list[i] = file_list[i][len(prefix):]
maruel@chromium.org68988972011-09-20 14:11:42 +0000945 # Strip any leading path separators.
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000946 while file_list[i].startswith(('\\', '/')):
947 file_list[i] = file_list[i][1:]
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000948
949 # Always parse the DEPS file.
950 self.ParseDepsFile()
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000951 self._run_is_done(file_list or [], parsed_url)
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000952 if command in ('update', 'revert') and not options.noprehooks:
953 self.RunPreDepsHooks()
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000954
955 if self.recursion_limit:
956 # Parse the dependencies of this dependency.
957 for s in self.dependencies:
Paweł Hajdan, Jr4baaa112017-07-04 19:09:32 +0200958 if s.should_process:
959 work_queue.enqueue(s)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000960
961 if command == 'recurse':
agabled437d762016-10-17 09:35:11 -0700962 # Skip file only checkout.
John Budorick0f7b2002018-01-19 15:46:17 -0800963 scm = self.GetScmName(parsed_url)
agabled437d762016-10-17 09:35:11 -0700964 if not options.scm or scm in options.scm:
965 cwd = os.path.normpath(os.path.join(self.root.root_dir, self.name))
966 # Pass in the SCM type as an env variable. Make sure we don't put
967 # unicode strings in the environment.
968 env = os.environ.copy()
969 if scm:
970 env['GCLIENT_SCM'] = str(scm)
971 if parsed_url:
972 env['GCLIENT_URL'] = str(parsed_url)
973 env['GCLIENT_DEP_PATH'] = str(self.name)
974 if options.prepend_dir and scm == 'git':
975 print_stdout = False
976 def filter_fn(line):
977 """Git-specific path marshaling. It is optimized for git-grep."""
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000978
agabled437d762016-10-17 09:35:11 -0700979 def mod_path(git_pathspec):
980 match = re.match('^(\\S+?:)?([^\0]+)$', git_pathspec)
981 modified_path = os.path.join(self.name, match.group(2))
982 branch = match.group(1) or ''
983 return '%s%s' % (branch, modified_path)
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000984
agabled437d762016-10-17 09:35:11 -0700985 match = re.match('^Binary file ([^\0]+) matches$', line)
986 if match:
987 print('Binary file %s matches\n' % mod_path(match.group(1)))
988 return
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000989
agabled437d762016-10-17 09:35:11 -0700990 items = line.split('\0')
991 if len(items) == 2 and items[1]:
992 print('%s : %s' % (mod_path(items[0]), items[1]))
993 elif len(items) >= 2:
994 # Multiple null bytes or a single trailing null byte indicate
995 # git is likely displaying filenames only (such as with -l)
996 print('\n'.join(mod_path(path) for path in items if path))
997 else:
998 print(line)
999 else:
1000 print_stdout = True
1001 filter_fn = None
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001002
agabled437d762016-10-17 09:35:11 -07001003 if parsed_url is None:
1004 print('Skipped omitted dependency %s' % cwd, file=sys.stderr)
1005 elif os.path.isdir(cwd):
1006 try:
1007 gclient_utils.CheckCallAndFilter(
1008 args, cwd=cwd, env=env, print_stdout=print_stdout,
1009 filter_fn=filter_fn,
1010 )
1011 except subprocess2.CalledProcessError:
1012 if not options.ignore:
1013 raise
1014 else:
1015 print('Skipped missing %s' % cwd, file=sys.stderr)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001016
John Budorick0f7b2002018-01-19 15:46:17 -08001017 def GetScmName(self, url):
1018 """Get the name of the SCM for the given URL.
1019
1020 While we currently support both git and cipd as SCM implementations,
1021 this currently cannot return 'cipd', regardless of the URL, as CIPD
1022 has no canonical URL format. If you want to use CIPD as an SCM, you
1023 must currently do so by explicitly using a CipdDependency.
1024 """
1025 if not url:
1026 return None
1027 url, _ = gclient_utils.SplitUrlRevision(url)
1028 if url.endswith('.git'):
1029 return 'git'
1030 protocol = url.split('://')[0]
1031 if protocol in (
1032 'file', 'git', 'git+http', 'git+https', 'http', 'https', 'ssh', 'sso'):
1033 return 'git'
1034 return None
1035
1036 def CreateSCM(self, url, root_dir=None, relpath=None, out_fh=None,
1037 out_cb=None):
1038 SCM_MAP = {
1039 'cipd': gclient_scm.CipdWrapper,
1040 'git': gclient_scm.GitWrapper,
1041 }
1042
1043 scm_name = self.GetScmName(url)
1044 if not scm_name in SCM_MAP:
1045 raise gclient_utils.Error('No SCM found for url %s' % url)
1046 scm_class = SCM_MAP[scm_name]
1047 if not scm_class.BinaryExists():
1048 raise gclient_utils.Error('%s command not found' % scm_name)
1049 return scm_class(url, root_dir, relpath, out_fh, out_cb)
1050
Dirk Pranke9f20d022017-10-11 18:36:54 -07001051 def HasGNArgsFile(self):
1052 return self._gn_args_file is not None
1053
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +02001054 def WriteGNArgsFile(self):
1055 lines = ['# Generated from %r' % self.deps_file]
Paweł Hajdan, Jrb495bf52017-09-25 19:33:50 +02001056 variables = self.get_vars()
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +02001057 for arg in self._gn_args:
Paweł Hajdan, Jre0214742017-09-28 12:21:01 +02001058 value = variables[arg]
1059 if isinstance(value, basestring):
1060 value = gclient_eval.EvaluateCondition(value, variables)
Paweł Hajdan, Jrb495bf52017-09-25 19:33:50 +02001061 lines.append('%s = %s' % (arg, ToGNString(value)))
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +02001062 with open(os.path.join(self.root.root_dir, self._gn_args_file), 'w') as f:
1063 f.write('\n'.join(lines))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001064
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001065 @gclient_utils.lockedmethod
1066 def _run_is_done(self, file_list, parsed_url):
1067 # Both these are kept for hooks that are run as a separate tree traversal.
1068 self._file_list = file_list
1069 self._parsed_url = parsed_url
1070 self._processed = True
1071
szager@google.comb9a78d32012-03-13 18:46:21 +00001072 def GetHooks(self, options):
1073 """Evaluates all hooks, and return them in a flat list.
1074
1075 RunOnDeps() must have been called before to load the DEPS.
1076 """
1077 result = []
maruel@chromium.org68988972011-09-20 14:11:42 +00001078 if not self.should_process or not self.recursion_limit:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001079 # Don't run the hook when it is above recursion_limit.
szager@google.comb9a78d32012-03-13 18:46:21 +00001080 return result
maruel@chromium.orgdc7445d2010-07-09 21:05:29 +00001081 # If "--force" was specified, run all hooks regardless of what files have
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001082 # changed.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001083 if self.deps_hooks:
agabled437d762016-10-17 09:35:11 -07001084 # TODO(maruel): If the user is using git, then we don't know
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001085 # what files have changed so we always run all hooks. It'd be nice to fix
1086 # that.
1087 if (options.force or
John Budorick0f7b2002018-01-19 15:46:17 -08001088 self.GetScmName(self.parsed_url) in ('git', None) or
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001089 os.path.isdir(os.path.join(self.root.root_dir, self.name, '.git'))):
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +02001090 result.extend(self.deps_hooks)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001091 else:
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +02001092 for hook in self.deps_hooks:
1093 if hook.matches(self.file_list_and_children):
1094 result.append(hook)
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001095 for s in self.dependencies:
szager@google.comb9a78d32012-03-13 18:46:21 +00001096 result.extend(s.GetHooks(options))
1097 return result
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001098
Dirk Pranke9f20d022017-10-11 18:36:54 -07001099 def WriteGNArgsFilesRecursively(self, dependencies):
1100 for dep in dependencies:
1101 if dep.HasGNArgsFile():
1102 dep.WriteGNArgsFile()
1103 self.WriteGNArgsFilesRecursively(dep.dependencies)
1104
Daniel Chenga0c5f082017-10-19 13:35:19 -07001105 def RunHooksRecursively(self, options, progress):
szager@google.comb9a78d32012-03-13 18:46:21 +00001106 assert self.hooks_ran == False
maruel@chromium.org064186c2011-09-27 23:53:33 +00001107 self._hooks_ran = True
Daniel Chenga0c5f082017-10-19 13:35:19 -07001108 hooks = self.GetHooks(options)
1109 if progress:
1110 progress._total = len(hooks)
1111 for hook in hooks:
Daniel Chenga0c5f082017-10-19 13:35:19 -07001112 if progress:
1113 progress.update(extra=hook.name or '')
Daniel Cheng93c5d602017-10-20 11:40:17 -07001114 hook.run(self.root.root_dir)
Daniel Chenga0c5f082017-10-19 13:35:19 -07001115 if progress:
1116 progress.end()
maruel@chromium.orgeaf61062010-07-07 18:42:39 +00001117
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001118 def RunPreDepsHooks(self):
1119 assert self.processed
1120 assert self.deps_parsed
1121 assert not self.pre_deps_hooks_ran
1122 assert not self.hooks_ran
1123 for s in self.dependencies:
1124 assert not s.processed
1125 self._pre_deps_hooks_ran = True
1126 for hook in self.pre_deps_hooks:
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +02001127 hook.run(self.root.root_dir)
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001128
maruel@chromium.org0d812442010-08-10 12:41:08 +00001129 def subtree(self, include_all):
maruel@chromium.orgad3287e2011-10-03 19:15:10 +00001130 """Breadth first recursion excluding root node."""
maruel@chromium.orgf13a4182011-09-22 00:26:15 +00001131 dependencies = self.dependencies
1132 for d in dependencies:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001133 if d.should_process or include_all:
maruel@chromium.orgad3287e2011-10-03 19:15:10 +00001134 yield d
maruel@chromium.orgf13a4182011-09-22 00:26:15 +00001135 for d in dependencies:
maruel@chromium.orgad3287e2011-10-03 19:15:10 +00001136 for i in d.subtree(include_all):
1137 yield i
1138
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001139 @gclient_utils.lockedmethod
1140 def add_dependency(self, new_dep):
1141 self._dependencies.append(new_dep)
1142
1143 @gclient_utils.lockedmethod
Paweł Hajdan, Jr357415c2017-07-24 14:35:28 +02001144 def _mark_as_parsed(self, new_hooks):
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001145 self._deps_hooks.extend(new_hooks)
1146 self._deps_parsed = True
1147
maruel@chromium.org68988972011-09-20 14:11:42 +00001148 @property
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001149 @gclient_utils.lockedmethod
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +00001150 def dependencies(self):
1151 return tuple(self._dependencies)
1152
1153 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001154 @gclient_utils.lockedmethod
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02001155 def os_dependencies(self):
1156 return dict(self._os_dependencies)
1157
1158 @property
1159 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001160 def deps_hooks(self):
1161 return tuple(self._deps_hooks)
1162
1163 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001164 @gclient_utils.lockedmethod
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +02001165 def os_deps_hooks(self):
1166 return dict(self._os_deps_hooks)
1167
1168 @property
1169 @gclient_utils.lockedmethod
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001170 def pre_deps_hooks(self):
1171 return tuple(self._pre_deps_hooks)
1172
1173 @property
1174 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001175 def parsed_url(self):
1176 return self._parsed_url
1177
1178 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001179 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001180 def deps_parsed(self):
maruel@chromium.org3223edd2011-10-10 23:17:39 +00001181 """This is purely for debugging purposes. It's not used anywhere."""
maruel@chromium.org064186c2011-09-27 23:53:33 +00001182 return self._deps_parsed
1183
1184 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001185 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001186 def processed(self):
1187 return self._processed
1188
1189 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001190 @gclient_utils.lockedmethod
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001191 def pre_deps_hooks_ran(self):
1192 return self._pre_deps_hooks_ran
1193
1194 @property
1195 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001196 def hooks_ran(self):
1197 return self._hooks_ran
1198
1199 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001200 @gclient_utils.lockedmethod
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00001201 def allowed_hosts(self):
1202 return self._allowed_hosts
1203
1204 @property
1205 @gclient_utils.lockedmethod
maruel@chromium.org861fd0f2010-07-23 03:05:05 +00001206 def file_list(self):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001207 return tuple(self._file_list)
1208
1209 @property
kustermann@google.coma692e8f2013-04-18 08:32:04 +00001210 def used_scm(self):
1211 """SCMWrapper instance for this dependency or None if not processed yet."""
1212 return self._used_scm
1213
1214 @property
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00001215 @gclient_utils.lockedmethod
1216 def got_revision(self):
1217 return self._got_revision
1218
1219 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001220 def file_list_and_children(self):
1221 result = list(self.file_list)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +00001222 for d in self.dependencies:
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001223 result.extend(d.file_list_and_children)
maruel@chromium.org68988972011-09-20 14:11:42 +00001224 return tuple(result)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +00001225
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001226 def __str__(self):
1227 out = []
agablea98a6cd2016-11-15 14:30:10 -08001228 for i in ('name', 'url', 'parsed_url', 'custom_deps',
maruel@chromium.org3c74bc92011-09-15 19:17:21 +00001229 'custom_vars', 'deps_hooks', 'file_list', 'should_process',
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00001230 'processed', 'hooks_ran', 'deps_parsed', 'requirements',
1231 'allowed_hosts'):
maruel@chromium.org3c74bc92011-09-15 19:17:21 +00001232 # First try the native property if it exists.
1233 if hasattr(self, '_' + i):
1234 value = getattr(self, '_' + i, False)
1235 else:
1236 value = getattr(self, i, False)
1237 if value:
1238 out.append('%s: %s' % (i, value))
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001239
1240 for d in self.dependencies:
1241 out.extend([' ' + x for x in str(d).splitlines()])
1242 out.append('')
1243 return '\n'.join(out)
1244
1245 def __repr__(self):
1246 return '%s: %s' % (self.name, self.url)
1247
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001248 def hierarchy(self, include_url=True):
maruel@chromium.orgbc2d2f92010-07-22 21:26:48 +00001249 """Returns a human-readable hierarchical reference to a Dependency."""
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001250 def format_name(d):
1251 if include_url:
1252 return '%s(%s)' % (d.name, d.url)
1253 return d.name
1254 out = format_name(self)
maruel@chromium.orgbffb9042010-07-22 20:59:36 +00001255 i = self.parent
1256 while i and i.name:
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001257 out = '%s -> %s' % (format_name(i), out)
maruel@chromium.orgbffb9042010-07-22 20:59:36 +00001258 i = i.parent
1259 return out
1260
Paweł Hajdan, Jrd3790252017-07-03 21:06:24 +02001261 def get_vars(self):
1262 """Returns a dictionary of effective variable values
1263 (DEPS file contents with applied custom_vars overrides)."""
Paweł Hajdan, Jra3b67ae2017-08-30 15:18:21 +02001264 # Provide some built-in variables.
1265 result = {
Paweł Hajdan, Jrd325eb32017-10-03 17:43:37 +02001266 'checkout_android': 'android' in self.target_os,
Benjamin Pastene6fe29412018-01-23 15:35:58 -08001267 'checkout_chromeos': 'chromeos' in self.target_os,
Paweł Hajdan, Jrd325eb32017-10-03 17:43:37 +02001268 'checkout_fuchsia': 'fuchsia' in self.target_os,
1269 'checkout_ios': 'ios' in self.target_os,
1270 'checkout_linux': 'unix' in self.target_os,
1271 'checkout_mac': 'mac' in self.target_os,
1272 'checkout_win': 'win' in self.target_os,
1273 'host_os': _detect_host_os(),
Paweł Hajdan, Jra3b67ae2017-08-30 15:18:21 +02001274 }
1275 # Variables defined in DEPS file override built-in ones.
1276 result.update(self._vars)
Paweł Hajdan, Jrd3790252017-07-03 21:06:24 +02001277 result.update(self.custom_vars or {})
1278 return result
1279
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001280
Paweł Hajdan, Jra3b67ae2017-08-30 15:18:21 +02001281_PLATFORM_MAPPING = {
1282 'cygwin': 'win',
1283 'darwin': 'mac',
1284 'linux2': 'linux',
1285 'win32': 'win',
Jaideep Bajwad05f3582017-09-11 12:31:48 -04001286 'aix6': 'aix',
Paweł Hajdan, Jra3b67ae2017-08-30 15:18:21 +02001287}
1288
1289
1290def _detect_host_os():
1291 return _PLATFORM_MAPPING[sys.platform]
1292
1293
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001294class GClient(Dependency):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001295 """Object that represent a gclient checkout. A tree of Dependency(), one per
1296 solution or DEPS entry."""
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001297
1298 DEPS_OS_CHOICES = {
Jaideep Bajwad05f3582017-09-11 12:31:48 -04001299 "aix6": "unix",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001300 "win32": "win",
1301 "win": "win",
1302 "cygwin": "win",
1303 "darwin": "mac",
1304 "mac": "mac",
1305 "unix": "unix",
1306 "linux": "unix",
1307 "linux2": "unix",
maruel@chromium.org244e3442011-06-12 15:20:55 +00001308 "linux3": "unix",
szager@chromium.orgf8c95cd2012-06-01 22:26:52 +00001309 "android": "android",
Michael Mossc54fa812017-08-17 11:27:58 -07001310 "ios": "ios",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001311 }
1312
1313 DEFAULT_CLIENT_FILE_TEXT = ("""\
1314solutions = [
smutae7ea312016-07-18 11:59:41 -07001315 { "name" : "%(solution_name)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001316 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +00001317 "deps_file" : "%(deps_file)s",
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001318 "managed" : %(managed)s,
smutae7ea312016-07-18 11:59:41 -07001319 "custom_deps" : {
1320 },
Paweł Hajdan, Jr3ba2a7c2017-10-04 19:24:46 +02001321 "custom_vars": %(custom_vars)r,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001322 },
1323]
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001324cache_dir = %(cache_dir)r
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001325""")
1326
1327 DEFAULT_SNAPSHOT_SOLUTION_TEXT = ("""\
smutae7ea312016-07-18 11:59:41 -07001328 { "name" : "%(solution_name)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001329 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +00001330 "deps_file" : "%(deps_file)s",
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001331 "managed" : %(managed)s,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001332 "custom_deps" : {
smutae7ea312016-07-18 11:59:41 -07001333%(solution_deps)s },
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001334 },
1335""")
1336
1337 DEFAULT_SNAPSHOT_FILE_TEXT = ("""\
1338# Snapshot generated with gclient revinfo --snapshot
1339solutions = [
maruel@chromium.org73e21142010-07-05 13:32:01 +00001340%(solution_list)s]
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001341""")
1342
1343 def __init__(self, root_dir, options):
maruel@chromium.org0d812442010-08-10 12:41:08 +00001344 # Do not change previous behavior. Only solution level and immediate DEPS
1345 # are processed.
1346 self._recursion_limit = 2
Paweł Hajdan, Jrfc6196b2017-07-27 13:15:25 +02001347 Dependency.__init__(self, None, None, None, None, True, None, None, None,
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +02001348 'unused', True, None, None, True)
maruel@chromium.org0d425922010-06-21 19:22:24 +00001349 self._options = options
maruel@chromium.org271375b2010-06-23 19:17:38 +00001350 if options.deps_os:
1351 enforced_os = options.deps_os.split(',')
1352 else:
1353 enforced_os = [self.DEPS_OS_CHOICES.get(sys.platform, 'unix')]
1354 if 'all' in enforced_os:
1355 enforced_os = self.DEPS_OS_CHOICES.itervalues()
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001356 self._enforced_os = tuple(set(enforced_os))
maruel@chromium.org271375b2010-06-23 19:17:38 +00001357 self._root_dir = root_dir
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001358 self.config_content = None
1359
borenet@google.com88d10082014-03-21 17:24:48 +00001360 def _CheckConfig(self):
1361 """Verify that the config matches the state of the existing checked-out
1362 solutions."""
1363 for dep in self.dependencies:
1364 if dep.managed and dep.url:
John Budorick0f7b2002018-01-19 15:46:17 -08001365 scm = self.CreateSCM(
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001366 dep.url, self.root_dir, dep.name, self.outbuf)
smut@google.comd33eab32014-07-07 19:35:18 +00001367 actual_url = scm.GetActualRemoteURL(self._options)
borenet@google.com4e9be262014-04-08 19:40:30 +00001368 if actual_url and not scm.DoesRemoteURLMatch(self._options):
levarum@chromium.org27a6f9a2016-05-28 00:21:49 +00001369 mirror = scm.GetCacheMirror()
1370 if mirror:
1371 mirror_string = '%s (exists=%s)' % (mirror.mirror_path,
1372 mirror.exists())
1373 else:
1374 mirror_string = 'not used'
borenet@google.com0a427372014-04-02 19:12:13 +00001375 raise gclient_utils.Error('''
borenet@google.com88d10082014-03-21 17:24:48 +00001376Your .gclient file seems to be broken. The requested URL is different from what
borenet@google.com0a427372014-04-02 19:12:13 +00001377is actually checked out in %(checkout_path)s.
borenet@google.com88d10082014-03-21 17:24:48 +00001378
borenet@google.com97882362014-04-07 20:06:02 +00001379The .gclient file contains:
levarum@chromium.org27a6f9a2016-05-28 00:21:49 +00001380URL: %(expected_url)s (%(expected_scm)s)
1381Cache mirror: %(mirror_string)s
borenet@google.com97882362014-04-07 20:06:02 +00001382
1383The local checkout in %(checkout_path)s reports:
1384%(actual_url)s (%(actual_scm)s)
borenet@google.com88d10082014-03-21 17:24:48 +00001385
1386You should ensure that the URL listed in .gclient is correct and either change
agabled437d762016-10-17 09:35:11 -07001387it or fix the checkout.
borenet@google.com88d10082014-03-21 17:24:48 +00001388''' % {'checkout_path': os.path.join(self.root_dir, dep.name),
1389 'expected_url': dep.url,
John Budorick0f7b2002018-01-19 15:46:17 -08001390 'expected_scm': self.GetScmName(dep.url),
levarum@chromium.org27a6f9a2016-05-28 00:21:49 +00001391 'mirror_string' : mirror_string,
borenet@google.com88d10082014-03-21 17:24:48 +00001392 'actual_url': actual_url,
John Budorick0f7b2002018-01-19 15:46:17 -08001393 'actual_scm': self.GetScmName(actual_url)})
borenet@google.com88d10082014-03-21 17:24:48 +00001394
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001395 def SetConfig(self, content):
maruel@chromium.orgf13a4182011-09-22 00:26:15 +00001396 assert not self.dependencies
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001397 config_dict = {}
1398 self.config_content = content
1399 try:
1400 exec(content, config_dict)
vapier@chromium.orga81a56e2015-11-11 07:56:13 +00001401 except SyntaxError as e:
maruel@chromium.org5990f9d2010-07-07 18:02:58 +00001402 gclient_utils.SyntaxErrorToError('.gclient', e)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001403
peter@chromium.org1efccc82012-04-27 16:34:38 +00001404 # Append any target OS that is not already being enforced to the tuple.
1405 target_os = config_dict.get('target_os', [])
stuartmorgan@chromium.org18a4f6a2012-11-02 13:37:24 +00001406 if config_dict.get('target_os_only', False):
1407 self._enforced_os = tuple(set(target_os))
1408 else:
1409 self._enforced_os = tuple(set(self._enforced_os).union(target_os))
1410
Aleksandr Derbenev9e8fb0e2017-08-01 20:18:31 +03001411 cache_dir = config_dict.get('cache_dir', self._options.cache_dir)
dyen@chromium.orgd915cca2014-08-07 21:41:37 +00001412 if cache_dir:
1413 cache_dir = os.path.join(self.root_dir, cache_dir)
1414 cache_dir = os.path.abspath(cache_dir)
Andrii Shyshkalov77ce4bd2017-11-27 12:38:18 -08001415
dyen@chromium.orgd915cca2014-08-07 21:41:37 +00001416 gclient_scm.GitWrapper.cache_dir = cache_dir
1417 git_cache.Mirror.SetCachePath(cache_dir)
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001418
stuartmorgan@chromium.org18a4f6a2012-11-02 13:37:24 +00001419 if not target_os and config_dict.get('target_os_only', False):
1420 raise gclient_utils.Error('Can\'t use target_os_only if target_os is '
1421 'not specified')
peter@chromium.org1efccc82012-04-27 16:34:38 +00001422
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001423 deps_to_add = []
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001424 for s in config_dict.get('solutions', []):
maruel@chromium.org81843b82010-06-28 16:49:26 +00001425 try:
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001426 deps_to_add.append(Dependency(
Paweł Hajdan, Jrfc6196b2017-07-27 13:15:25 +02001427 self, s['name'], s['url'], s['url'],
smutae7ea312016-07-18 11:59:41 -07001428 s.get('managed', True),
maruel@chromium.org81843b82010-06-28 16:49:26 +00001429 s.get('custom_deps', {}),
maruel@chromium.org0d812442010-08-10 12:41:08 +00001430 s.get('custom_vars', {}),
petermayo@chromium.orge79161a2013-07-09 14:40:37 +00001431 s.get('custom_hooks', []),
nsylvain@google.comefc80932011-05-31 21:27:56 +00001432 s.get('deps_file', 'DEPS'),
agabledce6ddc2016-09-08 10:02:16 -07001433 True,
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +02001434 None,
1435 None,
1436 True))
maruel@chromium.org81843b82010-06-28 16:49:26 +00001437 except KeyError:
1438 raise gclient_utils.Error('Invalid .gclient file. Solution is '
1439 'incomplete: %s' % s)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001440 self.add_dependencies_and_close(deps_to_add, config_dict.get('hooks', []))
1441 logging.info('SetConfig() done')
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001442
1443 def SaveConfig(self):
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001444 gclient_utils.FileWrite(os.path.join(self.root_dir,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001445 self._options.config_filename),
1446 self.config_content)
1447
1448 @staticmethod
1449 def LoadCurrentConfig(options):
1450 """Searches for and loads a .gclient file relative to the current working
1451 dir. Returns a GClient object."""
szager@chromium.orge2e03202012-07-31 18:05:16 +00001452 if options.spec:
1453 client = GClient('.', options)
1454 client.SetConfig(options.spec)
1455 else:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00001456 if options.verbose:
1457 print('Looking for %s starting from %s\n' % (
1458 options.config_filename, os.getcwd()))
szager@chromium.orge2e03202012-07-31 18:05:16 +00001459 path = gclient_utils.FindGclientRoot(os.getcwd(), options.config_filename)
1460 if not path:
Michael Achenbachb3ce73d2017-10-11 16:41:27 +02001461 if options.verbose:
1462 print('Couldn\'t find configuration file.')
szager@chromium.orge2e03202012-07-31 18:05:16 +00001463 return None
1464 client = GClient(path, options)
1465 client.SetConfig(gclient_utils.FileRead(
1466 os.path.join(path, options.config_filename)))
maruel@chromium.org69392e72011-10-13 22:09:00 +00001467
1468 if (options.revisions and
1469 len(client.dependencies) > 1 and
1470 any('@' not in r for r in options.revisions)):
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001471 print(
1472 ('You must specify the full solution name like --revision %s@%s\n'
1473 'when you have multiple solutions setup in your .gclient file.\n'
1474 'Other solutions present are: %s.') % (
maruel@chromium.org69392e72011-10-13 22:09:00 +00001475 client.dependencies[0].name,
1476 options.revisions[0],
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001477 ', '.join(s.name for s in client.dependencies[1:])),
1478 file=sys.stderr)
maruel@chromium.org15804092010-09-02 17:07:37 +00001479 return client
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001480
nsylvain@google.comefc80932011-05-31 21:27:56 +00001481 def SetDefaultConfig(self, solution_name, deps_file, solution_url,
Paweł Hajdan, Jr3ba2a7c2017-10-04 19:24:46 +02001482 managed=True, cache_dir=None, custom_vars=None):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001483 self.SetConfig(self.DEFAULT_CLIENT_FILE_TEXT % {
1484 'solution_name': solution_name,
1485 'solution_url': solution_url,
nsylvain@google.comefc80932011-05-31 21:27:56 +00001486 'deps_file': deps_file,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001487 'managed': managed,
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001488 'cache_dir': cache_dir,
Paweł Hajdan, Jr3ba2a7c2017-10-04 19:24:46 +02001489 'custom_vars': custom_vars or {},
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001490 })
1491
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001492 def _SaveEntries(self):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001493 """Creates a .gclient_entries file to record the list of unique checkouts.
1494
1495 The .gclient_entries file lives in the same directory as .gclient.
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001496 """
1497 # Sometimes pprint.pformat will use {', sometimes it'll use { ' ... It
1498 # makes testing a bit too fun.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001499 result = 'entries = {\n'
maruel@chromium.org68988972011-09-20 14:11:42 +00001500 for entry in self.root.subtree(False):
agabled437d762016-10-17 09:35:11 -07001501 result += ' %s: %s,\n' % (pprint.pformat(entry.name),
1502 pprint.pformat(entry.parsed_url))
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001503 result += '}\n'
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001504 file_path = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org1333cb32011-10-04 23:40:16 +00001505 logging.debug(result)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001506 gclient_utils.FileWrite(file_path, result)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001507
1508 def _ReadEntries(self):
1509 """Read the .gclient_entries file for the given client.
1510
1511 Returns:
1512 A sequence of solution names, which will be empty if there is the
1513 entries file hasn't been created yet.
1514 """
1515 scope = {}
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001516 filename = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001517 if not os.path.exists(filename):
maruel@chromium.org73e21142010-07-05 13:32:01 +00001518 return {}
maruel@chromium.org5990f9d2010-07-07 18:02:58 +00001519 try:
1520 exec(gclient_utils.FileRead(filename), scope)
vapier@chromium.orga81a56e2015-11-11 07:56:13 +00001521 except SyntaxError as e:
maruel@chromium.org5990f9d2010-07-07 18:02:58 +00001522 gclient_utils.SyntaxErrorToError(filename, e)
Aaron Gable3721ee92017-04-03 14:53:14 -07001523 return scope.get('entries', {})
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001524
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001525 def _EnforceRevisions(self):
maruel@chromium.org918a9ae2010-05-28 15:50:30 +00001526 """Checks for revision overrides."""
1527 revision_overrides = {}
smutae7ea312016-07-18 11:59:41 -07001528 if self._options.head:
1529 return revision_overrides
joi@chromium.org792ea882010-11-10 02:37:27 +00001530 if not self._options.revisions:
1531 for s in self.dependencies:
smutae7ea312016-07-18 11:59:41 -07001532 if not s.managed:
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001533 self._options.revisions.append('%s@unmanaged' % s.name)
maruel@chromium.org307d1792010-05-31 20:03:13 +00001534 if not self._options.revisions:
1535 return revision_overrides
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001536 solutions_names = [s.name for s in self.dependencies]
smutae7ea312016-07-18 11:59:41 -07001537 index = 0
1538 for revision in self._options.revisions:
1539 if not '@' in revision:
maruel@chromium.org307d1792010-05-31 20:03:13 +00001540 # Support for --revision 123
smutae7ea312016-07-18 11:59:41 -07001541 revision = '%s@%s' % (solutions_names[index], revision)
1542 name, rev = revision.split('@', 1)
szager@chromium.org4ad264b2014-05-20 04:43:47 +00001543 revision_overrides[name] = rev
smutae7ea312016-07-18 11:59:41 -07001544 index += 1
maruel@chromium.org918a9ae2010-05-28 15:50:30 +00001545 return revision_overrides
1546
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001547 def RunOnDeps(self, command, args, ignore_requirements=False, progress=True):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001548 """Runs a command on each dependency in a client and its dependencies.
1549
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001550 Args:
1551 command: The command to use (e.g., 'status' or 'diff')
1552 args: list of str - extra arguments to add to the command line.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001553 """
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001554 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +00001555 raise gclient_utils.Error('No solution specified')
borenet@google.com0a427372014-04-02 19:12:13 +00001556
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001557 revision_overrides = {}
1558 # It's unnecessary to check for revision overrides for 'recurse'.
1559 # Save a few seconds by not calling _EnforceRevisions() in that case.
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +02001560 if command not in ('diff', 'recurse', 'runhooks', 'status', 'revert',
1561 'validate'):
szager@chromium.org5273b8a2014-08-21 15:10:10 +00001562 self._CheckConfig()
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001563 revision_overrides = self._EnforceRevisions()
Daniel Chenga21b5b32017-10-19 20:07:48 +00001564 # Disable progress for non-tty stdout.
Daniel Chenga0c5f082017-10-19 13:35:19 -07001565 should_show_progress = (
1566 setup_color.IS_TTY and not self._options.verbose and progress)
1567 pm = None
1568 if should_show_progress:
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001569 if command in ('update', 'revert'):
1570 pm = Progress('Syncing projects', 1)
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +02001571 elif command in ('recurse', 'validate'):
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001572 pm = Progress(' '.join(args), 1)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001573 work_queue = gclient_utils.ExecutionQueue(
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001574 self._options.jobs, pm, ignore_requirements=ignore_requirements,
1575 verbose=self._options.verbose)
maruel@chromium.org049bced2010-08-12 13:37:20 +00001576 for s in self.dependencies:
Paweł Hajdan, Jr4baaa112017-07-04 19:09:32 +02001577 if s.should_process:
1578 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +00001579 work_queue.flush(revision_overrides, command, args, options=self._options)
szager@chromium.org4ad264b2014-05-20 04:43:47 +00001580 if revision_overrides:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001581 print('Please fix your script, having invalid --revision flags will soon '
1582 'considered an error.', file=sys.stderr)
piman@chromium.org6f363722010-04-27 00:41:09 +00001583
Dirk Pranke9f20d022017-10-11 18:36:54 -07001584 # Once all the dependencies have been processed, it's now safe to write
1585 # out any gn_args_files and run the hooks.
1586 if command == 'update':
1587 self.WriteGNArgsFilesRecursively(self.dependencies)
1588
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001589 if not self._options.nohooks:
Daniel Chenga0c5f082017-10-19 13:35:19 -07001590 if should_show_progress:
1591 pm = Progress('Running hooks', 1)
1592 self.RunHooksRecursively(self._options, pm)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001593
1594 if command == 'update':
ajwong@chromium.orgcdcee802009-06-23 15:30:42 +00001595 # Notify the user if there is an orphaned entry in their working copy.
1596 # Only delete the directory if there are no changes in it, and
1597 # delete_unversioned_trees is set to true.
maruel@chromium.org68988972011-09-20 14:11:42 +00001598 entries = [i.name for i in self.root.subtree(False) if i.url]
xusydoc@chromium.org885a9602013-05-31 09:54:40 +00001599 full_entries = [os.path.join(self.root_dir, e.replace('/', os.path.sep))
1600 for e in entries]
1601
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001602 for entry, prev_url in self._ReadEntries().iteritems():
maruel@chromium.org04dd7de2010-10-14 13:25:49 +00001603 if not prev_url:
1604 # entry must have been overridden via .gclient custom_deps
1605 continue
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001606 # Fix path separator on Windows.
1607 entry_fixed = entry.replace('/', os.path.sep)
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001608 e_dir = os.path.join(self.root_dir, entry_fixed)
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001609 # Use entry and not entry_fixed there.
jochen@chromium.orga78e5532013-03-11 13:33:03 +00001610 if (entry not in entries and
1611 (not any(path.startswith(entry + '/') for path in entries)) and
jochen@chromium.orgcc475722013-03-11 13:07:40 +00001612 os.path.exists(e_dir)):
primiano@chromium.org1c127382015-02-17 11:15:40 +00001613 # The entry has been removed from DEPS.
John Budorick0f7b2002018-01-19 15:46:17 -08001614 scm = self.CreateSCM(
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001615 prev_url, self.root_dir, entry_fixed, self.outbuf)
xusydoc@chromium.org885a9602013-05-31 09:54:40 +00001616
1617 # Check to see if this directory is now part of a higher-up checkout.
borenet@google.com359bb642014-05-13 17:28:19 +00001618 scm_root = None
agabled437d762016-10-17 09:35:11 -07001619 try:
1620 scm_root = gclient_scm.scm.GIT.GetCheckoutRoot(scm.checkout_path)
1621 except subprocess2.CalledProcessError:
1622 pass
1623 if not scm_root:
borenet@google.com359bb642014-05-13 17:28:19 +00001624 logging.warning('Could not find checkout root for %s. Unable to '
1625 'determine whether it is part of a higher-level '
1626 'checkout, so not removing.' % entry)
1627 continue
primiano@chromium.org1c127382015-02-17 11:15:40 +00001628
1629 # This is to handle the case of third_party/WebKit migrating from
1630 # being a DEPS entry to being part of the main project.
1631 # If the subproject is a Git project, we need to remove its .git
1632 # folder. Otherwise git operations on that folder will have different
1633 # effects depending on the current working directory.
agabled437d762016-10-17 09:35:11 -07001634 if os.path.abspath(scm_root) == os.path.abspath(e_dir):
primiano@chromium.org1c127382015-02-17 11:15:40 +00001635 e_par_dir = os.path.join(e_dir, os.pardir)
agabled437d762016-10-17 09:35:11 -07001636 if gclient_scm.scm.GIT.IsInsideWorkTree(e_par_dir):
1637 par_scm_root = gclient_scm.scm.GIT.GetCheckoutRoot(e_par_dir)
primiano@chromium.org1c127382015-02-17 11:15:40 +00001638 # rel_e_dir : relative path of entry w.r.t. its parent repo.
1639 rel_e_dir = os.path.relpath(e_dir, par_scm_root)
agabled437d762016-10-17 09:35:11 -07001640 if gclient_scm.scm.GIT.IsDirectoryVersioned(
1641 par_scm_root, rel_e_dir):
primiano@chromium.org1c127382015-02-17 11:15:40 +00001642 save_dir = scm.GetGitBackupDirPath()
1643 # Remove any eventual stale backup dir for the same project.
1644 if os.path.exists(save_dir):
1645 gclient_utils.rmtree(save_dir)
1646 os.rename(os.path.join(e_dir, '.git'), save_dir)
1647 # When switching between the two states (entry/ is a subproject
1648 # -> entry/ is part of the outer project), it is very likely
1649 # that some files are changed in the checkout, unless we are
1650 # jumping *exactly* across the commit which changed just DEPS.
1651 # In such case we want to cleanup any eventual stale files
1652 # (coming from the old subproject) in order to end up with a
1653 # clean checkout.
agabled437d762016-10-17 09:35:11 -07001654 gclient_scm.scm.GIT.CleanupDir(par_scm_root, rel_e_dir)
primiano@chromium.org1c127382015-02-17 11:15:40 +00001655 assert not os.path.exists(os.path.join(e_dir, '.git'))
1656 print(('\nWARNING: \'%s\' has been moved from DEPS to a higher '
1657 'level checkout. The git folder containing all the local'
1658 ' branches has been saved to %s.\n'
1659 'If you don\'t care about its state you can safely '
1660 'remove that folder to free up space.') %
1661 (entry, save_dir))
1662 continue
1663
borenet@google.com359bb642014-05-13 17:28:19 +00001664 if scm_root in full_entries:
primiano@chromium.org1c127382015-02-17 11:15:40 +00001665 logging.info('%s is part of a higher level checkout, not removing',
1666 scm.GetCheckoutRoot())
xusydoc@chromium.org885a9602013-05-31 09:54:40 +00001667 continue
1668
1669 file_list = []
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001670 scm.status(self._options, [], file_list)
1671 modified_files = file_list != []
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001672 if (not self._options.delete_unversioned_trees or
1673 (modified_files and not self._options.force)):
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001674 # There are modified files in this entry. Keep warning until
1675 # removed.
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001676 print(('\nWARNING: \'%s\' is no longer part of this client. '
1677 'It is recommended that you manually remove it.\n') %
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001678 entry_fixed)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001679 else:
1680 # Delete the entry
maruel@chromium.org73e21142010-07-05 13:32:01 +00001681 print('\n________ deleting \'%s\' in \'%s\'' % (
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001682 entry_fixed, self.root_dir))
digit@chromium.orgdc112ac2013-04-24 13:00:19 +00001683 gclient_utils.rmtree(e_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001684 # record the current list of entries for next time
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001685 self._SaveEntries()
maruel@chromium.org17cdf762010-05-28 17:30:52 +00001686 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001687
1688 def PrintRevInfo(self):
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001689 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +00001690 raise gclient_utils.Error('No solution specified')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001691 # Load all the settings.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001692 work_queue = gclient_utils.ExecutionQueue(
1693 self._options.jobs, None, False, verbose=self._options.verbose)
maruel@chromium.org049bced2010-08-12 13:37:20 +00001694 for s in self.dependencies:
Paweł Hajdan, Jr4baaa112017-07-04 19:09:32 +02001695 if s.should_process:
1696 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +00001697 work_queue.flush({}, None, [], options=self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001698
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001699 def GetURLAndRev(dep):
1700 """Returns the revision-qualified SCM url for a Dependency."""
1701 if dep.parsed_url is None:
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001702 return None
agabled437d762016-10-17 09:35:11 -07001703 url, _ = gclient_utils.SplitUrlRevision(dep.parsed_url)
John Budorick0f7b2002018-01-19 15:46:17 -08001704 scm = dep.CreateSCM(
agabled437d762016-10-17 09:35:11 -07001705 dep.parsed_url, self.root_dir, dep.name, self.outbuf)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001706 if not os.path.isdir(scm.checkout_path):
1707 return None
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001708 return '%s@%s' % (url, scm.revinfo(self._options, [], None))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001709
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001710 if self._options.snapshot:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001711 new_gclient = ''
1712 # First level at .gclient
1713 for d in self.dependencies:
1714 entries = {}
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001715 def GrabDeps(dep):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001716 """Recursively grab dependencies."""
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001717 for d in dep.dependencies:
1718 entries[d.name] = GetURLAndRev(d)
1719 GrabDeps(d)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001720 GrabDeps(d)
1721 custom_deps = []
1722 for k in sorted(entries.keys()):
1723 if entries[k]:
1724 # Quotes aren't escaped...
1725 custom_deps.append(' \"%s\": \'%s\',\n' % (k, entries[k]))
1726 else:
1727 custom_deps.append(' \"%s\": None,\n' % k)
1728 new_gclient += self.DEFAULT_SNAPSHOT_SOLUTION_TEXT % {
1729 'solution_name': d.name,
1730 'solution_url': d.url,
nsylvain@google.comefc80932011-05-31 21:27:56 +00001731 'deps_file': d.deps_file,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001732 'managed': d.managed,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001733 'solution_deps': ''.join(custom_deps),
1734 }
1735 # Print the snapshot configuration file
1736 print(self.DEFAULT_SNAPSHOT_FILE_TEXT % {'solution_list': new_gclient})
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +00001737 else:
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001738 entries = {}
maruel@chromium.org68988972011-09-20 14:11:42 +00001739 for d in self.root.subtree(False):
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001740 if self._options.actual:
1741 entries[d.name] = GetURLAndRev(d)
1742 else:
1743 entries[d.name] = d.parsed_url
1744 keys = sorted(entries.keys())
1745 for x in keys:
maruel@chromium.orgce464892010-08-12 17:12:18 +00001746 print('%s: %s' % (x, entries[x]))
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +00001747 logging.info(str(self))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001748
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001749 def ParseDepsFile(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001750 """No DEPS to parse for a .gclient file."""
maruel@chromium.org049bced2010-08-12 13:37:20 +00001751 raise gclient_utils.Error('Internal error')
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001752
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00001753 def PrintLocationAndContents(self):
1754 # Print out the .gclient file. This is longer than if we just printed the
1755 # client dict, but more legible, and it might contain helpful comments.
1756 print('Loaded .gclient config in %s:\n%s' % (
1757 self.root_dir, self.config_content))
1758
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001759 @property
maruel@chromium.org75a59272010-06-11 22:34:03 +00001760 def root_dir(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001761 """Root directory of gclient checkout."""
maruel@chromium.org75a59272010-06-11 22:34:03 +00001762 return self._root_dir
1763
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001764 @property
maruel@chromium.org271375b2010-06-23 19:17:38 +00001765 def enforced_os(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001766 """What deps_os entries that are to be parsed."""
maruel@chromium.org271375b2010-06-23 19:17:38 +00001767 return self._enforced_os
1768
maruel@chromium.org68988972011-09-20 14:11:42 +00001769 @property
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001770 def recursion_limit(self):
1771 """How recursive can each dependencies in DEPS file can load DEPS file."""
1772 return self._recursion_limit
1773
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +00001774 @property
cmp@chromium.orgc401ad12014-07-02 23:20:08 +00001775 def try_recursedeps(self):
1776 """Whether to attempt using recursedeps-style recursion processing."""
cmp@chromium.orge84ac912014-06-30 23:14:35 +00001777 return True
1778
1779 @property
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +00001780 def target_os(self):
1781 return self._enforced_os
1782
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001783
John Budorick0f7b2002018-01-19 15:46:17 -08001784class GitDependency(Dependency):
1785 """A Dependency object that represents a single git checkout."""
1786
1787 #override
1788 def GetScmName(self, url):
1789 """Always 'git'."""
1790 del url
1791 return 'git'
1792
1793 #override
1794 def CreateSCM(self, url, root_dir=None, relpath=None, out_fh=None,
1795 out_cb=None):
1796 """Create a Wrapper instance suitable for handling this git dependency."""
1797 return gclient_scm.GitWrapper(url, root_dir, relpath, out_fh, out_cb)
1798
1799
1800class CipdDependency(Dependency):
1801 """A Dependency object that represents a single CIPD package."""
1802
1803 def __init__(
1804 self, parent, name, dep_value, cipd_root,
1805 custom_vars, should_process, relative, condition, condition_value):
1806 package = dep_value['package']
1807 version = dep_value['version']
1808 url = urlparse.urljoin(
1809 cipd_root.service_url, '%s@%s' % (package, version))
1810 super(CipdDependency, self).__init__(
1811 parent, name, url, url, None, None, custom_vars,
1812 None, None, should_process, relative, condition, condition_value)
1813 if relative:
1814 # TODO(jbudorick): Implement relative if necessary.
1815 raise gclient_utils.Error(
1816 'Relative CIPD dependencies are not currently supported.')
1817 self._cipd_root = cipd_root
1818
1819 self._cipd_subdir = os.path.relpath(
1820 os.path.join(self.root.root_dir, self.name), cipd_root.root_dir)
1821 self._cipd_package = self._cipd_root.add_package(
1822 self._cipd_subdir, package, version)
1823
1824 def ParseDepsFile(self):
1825 """CIPD dependencies are not currently allowed to have nested deps."""
1826 self.add_dependencies_and_close([], [])
1827
1828 #override
1829 def GetScmName(self, url):
1830 """Always 'cipd'."""
1831 del url
1832 return 'cipd'
1833
1834 #override
1835 def CreateSCM(self, url, root_dir=None, relpath=None, out_fh=None,
1836 out_cb=None):
1837 """Create a Wrapper instance suitable for handling this CIPD dependency."""
1838 return gclient_scm.CipdWrapper(
1839 url, root_dir, relpath, out_fh, out_cb,
1840 root=self._cipd_root,
1841 package=self._cipd_package)
1842
1843 def ToLines(self):
1844 """Return a list of lines representing this in a DEPS file."""
1845 s = []
1846 if self._cipd_package.authority_for_subdir:
1847 condition_part = ([' "condition": %r,' % self.condition]
1848 if self.condition else [])
1849 s.extend([
1850 ' # %s' % self.hierarchy(include_url=False),
1851 ' "%s": {' % (self.name,),
1852 ' "packages": [',
1853 ])
1854 for p in self._cipd_root.packages(self._cipd_subdir):
1855 s.extend([
1856 ' "package": "%s",' % p.name,
1857 ' "version": "%s",' % p.version,
1858 ])
1859 s.extend([
1860 ' ],',
1861 ' "dep_type": "cipd",',
1862 ] + condition_part + [
1863 ' },',
1864 '',
1865 ])
1866 return s
1867
1868
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001869#### gclient commands.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001870
1871
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001872@subcommand.usage('[command] [args ...]')
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001873def CMDrecurse(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001874 """Operates [command args ...] on all the dependencies.
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001875
1876 Runs a shell command on all entries.
qyearsley12fa6ff2016-08-24 09:18:40 -07001877 Sets GCLIENT_DEP_PATH environment variable as the dep's relative location to
ilevy@chromium.org37116242012-11-28 01:32:48 +00001878 root directory of the checkout.
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001879 """
1880 # Stop parsing at the first non-arg so that these go through to the command
1881 parser.disable_interspersed_args()
1882 parser.add_option('-s', '--scm', action='append', default=[],
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001883 help='Choose scm types to operate upon.')
maruel@chromium.org288054d2012-03-05 00:43:07 +00001884 parser.add_option('-i', '--ignore', action='store_true',
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001885 help='Ignore non-zero return codes from subcommands.')
1886 parser.add_option('--prepend-dir', action='store_true',
1887 help='Prepend relative dir for use with git <cmd> --null.')
1888 parser.add_option('--no-progress', action='store_true',
1889 help='Disable progress bar that shows sub-command updates')
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001890 options, args = parser.parse_args(args)
maruel@chromium.org45e9f2d2010-10-18 13:33:46 +00001891 if not args:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001892 print('Need to supply a command!', file=sys.stderr)
maruel@chromium.org45e9f2d2010-10-18 13:33:46 +00001893 return 1
maruel@chromium.org78cba522010-10-18 13:32:05 +00001894 root_and_entries = gclient_utils.GetGClientRootAndEntries()
1895 if not root_and_entries:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001896 print(
maruel@chromium.org78cba522010-10-18 13:32:05 +00001897 'You need to run gclient sync at least once to use \'recurse\'.\n'
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001898 'This is because .gclient_entries needs to exist and be up to date.',
1899 file=sys.stderr)
maruel@chromium.org78cba522010-10-18 13:32:05 +00001900 return 1
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001901
1902 # Normalize options.scm to a set()
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001903 scm_set = set()
1904 for scm in options.scm:
1905 scm_set.update(scm.split(','))
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001906 options.scm = scm_set
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001907
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001908 options.nohooks = True
1909 client = GClient.LoadCurrentConfig(options)
Marc-Antoine Ruele6e06412017-10-18 13:47:02 -04001910 if not client:
1911 raise gclient_utils.Error('client not configured; see \'gclient config\'')
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001912 return client.RunOnDeps('recurse', args, ignore_requirements=True,
1913 progress=not options.no_progress)
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001914
1915
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001916@subcommand.usage('[args ...]')
davidbarr@chromium.org12f944e2012-03-01 02:18:31 +00001917def CMDfetch(parser, args):
1918 """Fetches upstream commits for all modules.
1919
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001920 Completely git-specific. Simply runs 'git fetch [args ...]' for each module.
1921 """
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001922 (options, args) = parser.parse_args(args)
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00001923 return CMDrecurse(OptionParser(), [
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001924 '--jobs=%d' % options.jobs, '--scm=git', 'git', 'fetch'] + args)
1925
1926
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02001927class Flattener(object):
1928 """Flattens a gclient solution."""
1929
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02001930 def __init__(self, client, pin_all_deps=False):
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02001931 """Constructor.
1932
1933 Arguments:
1934 client (GClient): client to flatten
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02001935 pin_all_deps (bool): whether to pin all deps, even if they're not pinned
1936 in DEPS
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02001937 """
1938 self._client = client
1939
1940 self._deps_string = None
Paweł Hajdan, Jrfeb01642017-09-12 15:50:46 +02001941 self._deps_files = set()
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02001942
1943 self._allowed_hosts = set()
1944 self._deps = {}
1945 self._deps_os = {}
1946 self._hooks = []
1947 self._hooks_os = {}
1948 self._pre_deps_hooks = []
Paweł Hajdan, Jrfb022012017-07-06 18:00:08 +02001949 self._vars = {}
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02001950
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02001951 self._flatten(pin_all_deps=pin_all_deps)
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02001952
1953 @property
1954 def deps_string(self):
1955 assert self._deps_string is not None
1956 return self._deps_string
1957
Paweł Hajdan, Jrfeb01642017-09-12 15:50:46 +02001958 @property
1959 def deps_files(self):
1960 return self._deps_files
1961
Paweł Hajdan, Jr5ec77132017-08-16 19:21:06 +02001962 def _pin_dep(self, dep):
1963 """Pins a dependency to specific full revision sha.
1964
1965 Arguments:
1966 dep (Dependency): dependency to process
1967 """
1968 if dep.parsed_url is None:
1969 return
1970
1971 # Make sure the revision is always fully specified (a hash),
1972 # as opposed to refs or tags which might change. Similarly,
1973 # shortened shas might become ambiguous; make sure to always
1974 # use full one for pinning.
1975 url, revision = gclient_utils.SplitUrlRevision(dep.parsed_url)
1976 if revision and gclient_utils.IsFullGitSha(revision):
1977 return
1978
John Budorick0f7b2002018-01-19 15:46:17 -08001979 scm = dep.CreateSCM(
Paweł Hajdan, Jr5ec77132017-08-16 19:21:06 +02001980 dep.parsed_url, self._client.root_dir, dep.name, dep.outbuf)
1981 revinfo = scm.revinfo(self._client._options, [], None)
1982
1983 dep._parsed_url = dep._url = '%s@%s' % (url, revinfo)
1984 raw_url, _ = gclient_utils.SplitUrlRevision(dep._raw_url)
1985 dep._raw_url = '%s@%s' % (raw_url, revinfo)
1986
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02001987 def _flatten(self, pin_all_deps=False):
1988 """Runs the flattener. Saves resulting DEPS string.
1989
1990 Arguments:
1991 pin_all_deps (bool): whether to pin all deps, even if they're not pinned
1992 in DEPS
1993 """
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02001994 for solution in self._client.dependencies:
Paweł Hajdan, Jr11eb7152017-08-10 12:50:11 +02001995 self._add_dep(solution)
Paweł Hajdan, Jrb0ad16e2017-08-03 15:33:21 +02001996 self._flatten_dep(solution)
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02001997
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02001998 if pin_all_deps:
1999 for dep in self._deps.itervalues():
Paweł Hajdan, Jr5ec77132017-08-16 19:21:06 +02002000 self._pin_dep(dep)
Paweł Hajdan, Jr39300ba2017-08-11 16:52:38 +02002001
Paweł Hajdan, Jr5ec77132017-08-16 19:21:06 +02002002 for os_deps in self._deps_os.itervalues():
2003 for dep in os_deps.itervalues():
Paweł Hajdan, Jr5ec77132017-08-16 19:21:06 +02002004 self._pin_dep(dep)
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02002005
Paweł Hajdan, Jr76a9d042017-08-18 20:05:41 +02002006 def add_deps_file(dep):
Paweł Hajdan, Jr0870df22017-08-23 17:59:29 +02002007 # Only include DEPS files referenced by recursedeps.
2008 if not (dep.parent is None or
2009 (dep.name in (dep.parent.recursedeps or {}))):
2010 return
Paweł Hajdan, Jrfeb01642017-09-12 15:50:46 +02002011 deps_file = dep.deps_file
2012 deps_path = os.path.join(self._client.root_dir, dep.name, deps_file)
Paweł Hajdan, Jr76a9d042017-08-18 20:05:41 +02002013 if not os.path.exists(deps_path):
Paweł Hajdan, Jrfeb01642017-09-12 15:50:46 +02002014 # gclient has a fallback that if deps_file doesn't exist, it'll try
2015 # DEPS. Do the same here.
2016 deps_file = 'DEPS'
2017 deps_path = os.path.join(self._client.root_dir, dep.name, deps_file)
2018 if not os.path.exists(deps_path):
2019 return
Paweł Hajdan, Jr76a9d042017-08-18 20:05:41 +02002020 assert dep.parsed_url
Paweł Hajdan, Jrfeb01642017-09-12 15:50:46 +02002021 self._deps_files.add((dep.parsed_url, deps_file))
Paweł Hajdan, Jr76a9d042017-08-18 20:05:41 +02002022 for dep in self._deps.itervalues():
2023 add_deps_file(dep)
2024 for os_deps in self._deps_os.itervalues():
2025 for dep in os_deps.itervalues():
2026 add_deps_file(dep)
2027
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002028 self._deps_string = '\n'.join(
2029 _GNSettingsToLines(
2030 self._client.dependencies[0]._gn_args_file,
2031 self._client.dependencies[0]._gn_args) +
2032 _AllowedHostsToLines(self._allowed_hosts) +
2033 _DepsToLines(self._deps) +
2034 _DepsOsToLines(self._deps_os) +
2035 _HooksToLines('hooks', self._hooks) +
2036 _HooksToLines('pre_deps_hooks', self._pre_deps_hooks) +
2037 _HooksOsToLines(self._hooks_os) +
Paweł Hajdan, Jrfb022012017-07-06 18:00:08 +02002038 _VarsToLines(self._vars) +
Paweł Hajdan, Jr76a9d042017-08-18 20:05:41 +02002039 ['# %s, %s' % (url, deps_file)
Paweł Hajdan, Jrfeb01642017-09-12 15:50:46 +02002040 for url, deps_file in sorted(self._deps_files)] +
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002041 ['']) # Ensure newline at end of file.
2042
Paweł Hajdan, Jr11eb7152017-08-10 12:50:11 +02002043 def _add_dep(self, dep):
2044 """Helper to add a dependency to flattened DEPS.
2045
2046 Arguments:
2047 dep (Dependency): dependency to add
2048 """
2049 assert dep.name not in self._deps or self._deps.get(dep.name) == dep, (
2050 dep.name, self._deps.get(dep.name))
Paweł Hajdan, Jr9a289022017-08-10 16:04:24 +02002051 if dep.url:
2052 self._deps[dep.name] = dep
Paweł Hajdan, Jr11eb7152017-08-10 12:50:11 +02002053
Paweł Hajdan, Jradae2a62017-08-18 16:49:57 +02002054 def _add_os_dep(self, os_dep, dep_os):
2055 """Helper to add an OS-specific dependency to flattened DEPS.
2056
2057 Arguments:
2058 os_dep (Dependency): dependency to add
2059 dep_os (str): name of the OS
2060 """
2061 assert (
2062 os_dep.name not in self._deps_os.get(dep_os, {}) or
2063 self._deps_os.get(dep_os, {}).get(os_dep.name) == os_dep), (
2064 os_dep.name, self._deps_os.get(dep_os, {}).get(os_dep.name))
2065 if os_dep.url:
Paweł Hajdan, Jr76a9d042017-08-18 20:05:41 +02002066 # OS-specific deps need to have their full URL resolved manually.
2067 assert not os_dep.parsed_url, (os_dep, os_dep.parsed_url)
2068 os_dep._parsed_url = os_dep.LateOverride(os_dep.url)
2069
Paweł Hajdan, Jradae2a62017-08-18 16:49:57 +02002070 self._deps_os.setdefault(dep_os, {})[os_dep.name] = os_dep
2071
2072 def _flatten_dep(self, dep, dep_os=None):
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002073 """Visits a dependency in order to flatten it (see CMDflatten).
2074
2075 Arguments:
2076 dep (Dependency): dependency to process
Paweł Hajdan, Jradae2a62017-08-18 16:49:57 +02002077 dep_os (str or None): name of the OS |dep| is specific to
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002078 """
Paweł Hajdan, Jradae2a62017-08-18 16:49:57 +02002079 logging.debug('_flatten_dep(%s, %s)', dep.name, dep_os)
2080
Paweł Hajdan, Jrc69b32e2017-08-17 18:47:48 +02002081 if not dep.deps_parsed:
2082 dep.ParseDepsFile()
2083
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002084 self._allowed_hosts.update(dep.allowed_hosts)
2085
Paweł Hajdan, Jra3b67ae2017-08-30 15:18:21 +02002086 # Only include vars listed in the DEPS files, not possible local overrides.
2087 for key, value in dep._vars.iteritems():
Paweł Hajdan, Jrc9353602017-08-02 17:52:08 +02002088 # Make sure there are no conflicting variables. It is fine however
2089 # to use same variable name, as long as the value is consistent.
2090 assert key not in self._vars or self._vars[key][1] == value
Paweł Hajdan, Jrfb022012017-07-06 18:00:08 +02002091 self._vars[key] = (dep, value)
2092
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002093 self._pre_deps_hooks.extend([(dep, hook) for hook in dep.pre_deps_hooks])
2094
Paweł Hajdan, Jradae2a62017-08-18 16:49:57 +02002095 if dep_os:
2096 if dep.deps_hooks:
2097 self._hooks_os.setdefault(dep_os, []).extend(
2098 [(dep, hook) for hook in dep.deps_hooks])
2099 else:
2100 self._hooks.extend([(dep, hook) for hook in dep.deps_hooks])
2101
Paweł Hajdan, Jrb0ad16e2017-08-03 15:33:21 +02002102 for sub_dep in dep.dependencies:
Paweł Hajdan, Jradae2a62017-08-18 16:49:57 +02002103 if dep_os:
2104 self._add_os_dep(sub_dep, dep_os)
2105 else:
2106 self._add_dep(sub_dep)
Paweł Hajdan, Jrb0ad16e2017-08-03 15:33:21 +02002107
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002108 for hook_os, os_hooks in dep.os_deps_hooks.iteritems():
2109 self._hooks_os.setdefault(hook_os, []).extend(
2110 [(dep, hook) for hook in os_hooks])
2111
Paweł Hajdan, Jradae2a62017-08-18 16:49:57 +02002112 for sub_dep_os, os_deps in dep.os_dependencies.iteritems():
Paweł Hajdan, Jre2deb1e2017-08-09 17:29:21 +02002113 for os_dep in os_deps:
Paweł Hajdan, Jradae2a62017-08-18 16:49:57 +02002114 self._add_os_dep(os_dep, sub_dep_os)
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002115
Paweł Hajdan, Jradae2a62017-08-18 16:49:57 +02002116 # Process recursedeps. |deps_by_name| is a map where keys are dependency
2117 # names, and values are maps of OS names to |Dependency| instances.
2118 # |None| in place of OS name means the dependency is not OS-specific.
2119 deps_by_name = dict((d.name, {None: d}) for d in dep.dependencies)
2120 for sub_dep_os, os_deps in dep.os_dependencies.iteritems():
Paweł Hajdan, Jrc9353602017-08-02 17:52:08 +02002121 for os_dep in os_deps:
Paweł Hajdan, Jradae2a62017-08-18 16:49:57 +02002122 assert sub_dep_os not in deps_by_name.get(os_dep.name, {}), (
2123 os_dep.name, sub_dep_os)
2124 deps_by_name.setdefault(os_dep.name, {})[sub_dep_os] = os_dep
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002125 for recurse_dep_name in (dep.recursedeps or []):
Paweł Hajdan, Jradae2a62017-08-18 16:49:57 +02002126 dep_info = deps_by_name[recurse_dep_name]
2127 for sub_dep_os, os_dep in dep_info.iteritems():
2128 self._flatten_dep(os_dep, dep_os=(sub_dep_os or dep_os))
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002129
2130
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002131def CMDflatten(parser, args):
2132 """Flattens the solutions into a single DEPS file."""
2133 parser.add_option('--output-deps', help='Path to the output DEPS file')
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02002134 parser.add_option(
Paweł Hajdan, Jrfeb01642017-09-12 15:50:46 +02002135 '--output-deps-files',
2136 help=('Path to the output metadata about DEPS files referenced by '
2137 'recursedeps.'))
2138 parser.add_option(
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02002139 '--pin-all-deps', action='store_true',
2140 help=('Pin all deps, even if not pinned in DEPS. CAVEAT: only does so '
2141 'for checked out deps, NOT deps_os.'))
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002142 options, args = parser.parse_args(args)
2143
Paweł Hajdan, Jr357415c2017-07-24 14:35:28 +02002144 options.do_not_merge_os_specific_entries = True
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002145 options.nohooks = True
Paweł Hajdan, Jr5ec77132017-08-16 19:21:06 +02002146 options.process_all_deps = True
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002147 client = GClient.LoadCurrentConfig(options)
2148
2149 # Only print progress if we're writing to a file. Otherwise, progress updates
2150 # could obscure intended output.
2151 code = client.RunOnDeps('flatten', args, progress=options.output_deps)
2152 if code != 0:
2153 return code
2154
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02002155 flattener = Flattener(client, pin_all_deps=options.pin_all_deps)
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002156
2157 if options.output_deps:
2158 with open(options.output_deps, 'w') as f:
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002159 f.write(flattener.deps_string)
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002160 else:
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002161 print(flattener.deps_string)
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002162
Paweł Hajdan, Jrfeb01642017-09-12 15:50:46 +02002163 deps_files = [{'url': d[0], 'deps_file': d[1]}
2164 for d in sorted(flattener.deps_files)]
2165 if options.output_deps_files:
2166 with open(options.output_deps_files, 'w') as f:
2167 json.dump(deps_files, f)
2168
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002169 return 0
2170
2171
Paweł Hajdan, Jr3c2aa832017-06-07 20:22:16 +02002172def _GNSettingsToLines(gn_args_file, gn_args):
2173 s = []
2174 if gn_args_file:
2175 s.extend([
2176 'gclient_gn_args_file = "%s"' % gn_args_file,
2177 'gclient_gn_args = %r' % gn_args,
2178 ])
2179 return s
2180
2181
Paweł Hajdan, Jr6014b562017-06-30 17:43:42 +02002182def _AllowedHostsToLines(allowed_hosts):
2183 """Converts |allowed_hosts| set to list of lines for output."""
2184 if not allowed_hosts:
2185 return []
2186 s = ['allowed_hosts = [']
2187 for h in sorted(allowed_hosts):
2188 s.append(' "%s",' % h)
2189 s.extend([']', ''])
2190 return s
2191
2192
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002193def _DepsToLines(deps):
2194 """Converts |deps| dict to list of lines for output."""
Paweł Hajdan, Jr5b593352017-06-29 18:37:45 +02002195 if not deps:
2196 return []
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002197 s = ['deps = {']
John Budorick0f7b2002018-01-19 15:46:17 -08002198 for _, dep in sorted(deps.iteritems()):
2199 s.extend(dep.ToLines())
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002200 s.extend(['}', ''])
2201 return s
2202
2203
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02002204def _DepsOsToLines(deps_os):
2205 """Converts |deps_os| dict to list of lines for output."""
Paweł Hajdan, Jr5b593352017-06-29 18:37:45 +02002206 if not deps_os:
2207 return []
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02002208 s = ['deps_os = {']
2209 for dep_os, os_deps in sorted(deps_os.iteritems()):
2210 s.append(' "%s": {' % dep_os)
Paweł Hajdan, Jrad30de62017-06-26 18:51:58 +02002211 for name, dep in sorted(os_deps.iteritems()):
Paweł Hajdan, Jr78ce24e2017-10-03 17:09:13 +02002212 condition_part = ([' "condition": %r,' % dep.condition]
Paweł Hajdan, Jrad30de62017-06-26 18:51:58 +02002213 if dep.condition else [])
2214 s.extend([
2215 ' # %s' % dep.hierarchy(include_url=False),
2216 ' "%s": {' % (name,),
Paweł Hajdan, Jrde86ab32017-08-10 13:55:16 +02002217 ' "url": "%s",' % (dep.raw_url,),
Paweł Hajdan, Jrad30de62017-06-26 18:51:58 +02002218 ] + condition_part + [
2219 ' },',
2220 '',
2221 ])
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02002222 s.extend([' },', ''])
2223 s.extend(['}', ''])
2224 return s
2225
2226
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002227def _HooksToLines(name, hooks):
2228 """Converts |hooks| list to list of lines for output."""
Paweł Hajdan, Jr5b593352017-06-29 18:37:45 +02002229 if not hooks:
2230 return []
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002231 s = ['%s = [' % name]
2232 for dep, hook in hooks:
2233 s.extend([
2234 ' # %s' % dep.hierarchy(include_url=False),
2235 ' {',
2236 ])
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +02002237 if hook.name is not None:
2238 s.append(' "name": "%s",' % hook.name)
2239 if hook.pattern is not None:
2240 s.append(' "pattern": "%s",' % hook.pattern)
Paweł Hajdan, Jrecf53fe2017-09-29 18:28:49 +02002241 if hook.condition is not None:
Paweł Hajdan, Jr78ce24e2017-10-03 17:09:13 +02002242 s.append(' "condition": %r,' % hook.condition)
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002243 s.extend(
Paweł Hajdan, Jrc9364392017-06-14 17:11:56 +02002244 # Hooks run in the parent directory of their dep.
Paweł Hajdan, Jrad30de62017-06-26 18:51:58 +02002245 [' "cwd": "%s",' % os.path.normpath(os.path.dirname(dep.name))] +
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002246 [' "action": ['] +
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +02002247 [' "%s",' % arg for arg in hook.action] +
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002248 [' ]', ' },', '']
2249 )
2250 s.extend([']', ''])
2251 return s
2252
2253
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +02002254def _HooksOsToLines(hooks_os):
2255 """Converts |hooks| list to list of lines for output."""
Paweł Hajdan, Jr5b593352017-06-29 18:37:45 +02002256 if not hooks_os:
2257 return []
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +02002258 s = ['hooks_os = {']
2259 for hook_os, os_hooks in hooks_os.iteritems():
Michael Moss017bcf62017-06-28 15:26:38 -07002260 s.append(' "%s": [' % hook_os)
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +02002261 for dep, hook in os_hooks:
2262 s.extend([
2263 ' # %s' % dep.hierarchy(include_url=False),
2264 ' {',
2265 ])
2266 if hook.name is not None:
2267 s.append(' "name": "%s",' % hook.name)
2268 if hook.pattern is not None:
2269 s.append(' "pattern": "%s",' % hook.pattern)
Paweł Hajdan, Jrecf53fe2017-09-29 18:28:49 +02002270 if hook.condition is not None:
Paweł Hajdan, Jr78ce24e2017-10-03 17:09:13 +02002271 s.append(' "condition": %r,' % hook.condition)
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +02002272 s.extend(
2273 # Hooks run in the parent directory of their dep.
2274 [' "cwd": "%s",' % os.path.normpath(os.path.dirname(dep.name))] +
2275 [' "action": ['] +
2276 [' "%s",' % arg for arg in hook.action] +
2277 [' ]', ' },', '']
2278 )
Michael Moss017bcf62017-06-28 15:26:38 -07002279 s.extend([' ],', ''])
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +02002280 s.extend(['}', ''])
2281 return s
2282
2283
Paweł Hajdan, Jrfb022012017-07-06 18:00:08 +02002284def _VarsToLines(variables):
2285 """Converts |variables| dict to list of lines for output."""
2286 if not variables:
2287 return []
2288 s = ['vars = {']
2289 for key, tup in sorted(variables.iteritems()):
2290 dep, value = tup
2291 s.extend([
2292 ' # %s' % dep.hierarchy(include_url=False),
2293 ' "%s": %r,' % (key, value),
2294 '',
2295 ])
2296 s.extend(['}', ''])
2297 return s
2298
2299
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00002300def CMDgrep(parser, args):
2301 """Greps through git repos managed by gclient.
2302
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002303 Runs 'git grep [args...]' for each module.
2304 """
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00002305 # We can't use optparse because it will try to parse arguments sent
2306 # to git grep and throw an error. :-(
2307 if not args or re.match('(-h|--help)$', args[0]):
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002308 print(
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00002309 'Usage: gclient grep [-j <N>] git-grep-args...\n\n'
2310 'Example: "gclient grep -j10 -A2 RefCountedBase" runs\n"git grep '
2311 '-A2 RefCountedBase" on each of gclient\'s git\nrepos with up to '
2312 '10 jobs.\n\nBonus: page output by appending "|& less -FRSX" to the'
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002313 ' end of your query.',
2314 file=sys.stderr)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00002315 return 1
2316
2317 jobs_arg = ['--jobs=1']
2318 if re.match(r'(-j|--jobs=)\d+$', args[0]):
2319 jobs_arg, args = args[:1], args[1:]
2320 elif re.match(r'(-j|--jobs)$', args[0]):
2321 jobs_arg, args = args[:2], args[2:]
2322
2323 return CMDrecurse(
2324 parser,
2325 jobs_arg + ['--ignore', '--prepend-dir', '--no-progress', '--scm=git',
2326 'git', 'grep', '--null', '--color=Always'] + args)
davidbarr@chromium.org12f944e2012-03-01 02:18:31 +00002327
2328
stip@chromium.orga735da22015-04-29 23:18:20 +00002329def CMDroot(parser, args):
2330 """Outputs the solution root (or current dir if there isn't one)."""
2331 (options, args) = parser.parse_args(args)
2332 client = GClient.LoadCurrentConfig(options)
2333 if client:
2334 print(os.path.abspath(client.root_dir))
2335 else:
2336 print(os.path.abspath('.'))
2337
2338
agablea98a6cd2016-11-15 14:30:10 -08002339@subcommand.usage('[url]')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002340def CMDconfig(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002341 """Creates a .gclient file in the current directory.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00002342
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002343 This specifies the configuration for further commands. After update/sync,
2344 top-level DEPS files in each module are read to determine dependent
2345 modules to operate on as well. If optional [url] parameter is
2346 provided, then configuration is read from a specified Subversion server
2347 URL.
2348 """
szager@chromium.orge2e03202012-07-31 18:05:16 +00002349 # We do a little dance with the --gclientfile option. 'gclient config' is the
2350 # only command where it's acceptable to have both '--gclientfile' and '--spec'
2351 # arguments. So, we temporarily stash any --gclientfile parameter into
2352 # options.output_config_file until after the (gclientfile xor spec) error
2353 # check.
2354 parser.remove_option('--gclientfile')
2355 parser.add_option('--gclientfile', dest='output_config_file',
2356 help='Specify an alternate .gclient file')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002357 parser.add_option('--name',
2358 help='overrides the default name for the solution')
nsylvain@google.comefc80932011-05-31 21:27:56 +00002359 parser.add_option('--deps-file', default='DEPS',
David Benjamin105e11e2017-10-16 10:39:35 -04002360 help='overrides the default name for the DEPS file for the '
nsylvain@google.comefc80932011-05-31 21:27:56 +00002361 'main solutions and all sub-dependencies')
smutae7ea312016-07-18 11:59:41 -07002362 parser.add_option('--unmanaged', action='store_true', default=False,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00002363 help='overrides the default behavior to make it possible '
smutae7ea312016-07-18 11:59:41 -07002364 'to have the main solution untouched by gclient '
2365 '(gclient will check out unmanaged dependencies but '
2366 'will never sync them)')
Paweł Hajdan, Jr3ba2a7c2017-10-04 19:24:46 +02002367 parser.add_option('--custom-var', action='append', dest='custom_vars',
2368 default=[],
2369 help='overrides variables; key=value syntax')
szager@chromium.orge2e03202012-07-31 18:05:16 +00002370 parser.set_defaults(config_filename=None)
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002371 (options, args) = parser.parse_args(args)
szager@chromium.orge2e03202012-07-31 18:05:16 +00002372 if options.output_config_file:
2373 setattr(options, 'config_filename', getattr(options, 'output_config_file'))
maruel@chromium.org5fc2a332010-05-26 19:37:15 +00002374 if ((options.spec and args) or len(args) > 2 or
2375 (not options.spec and not args)):
2376 parser.error('Inconsistent arguments. Use either --spec or one or 2 args')
2377
Paweł Hajdan, Jr3ba2a7c2017-10-04 19:24:46 +02002378 custom_vars = {}
2379 for arg in options.custom_vars:
2380 kv = arg.split('=', 1)
2381 if len(kv) != 2:
2382 parser.error('Invalid --custom-var argument: %r' % arg)
2383 custom_vars[kv[0]] = gclient_eval.EvaluateCondition(kv[1], {})
2384
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002385 client = GClient('.', options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002386 if options.spec:
2387 client.SetConfig(options.spec)
2388 else:
maruel@chromium.org1ab7ffc2009-06-03 17:21:37 +00002389 base_url = args[0].rstrip('/')
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00002390 if not options.name:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002391 name = base_url.split('/')[-1]
nsylvain@google.com12649ef2011-06-01 17:11:20 +00002392 if name.endswith('.git'):
2393 name = name[:-4]
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00002394 else:
2395 # specify an alternate relpath for the given URL.
2396 name = options.name
agable@chromium.orgf2214672015-10-27 21:02:48 +00002397 if not os.path.abspath(os.path.join(os.getcwd(), name)).startswith(
2398 os.getcwd()):
2399 parser.error('Do not pass a relative path for --name.')
2400 if any(x in ('..', '.', '/', '\\') for x in name.split(os.sep)):
2401 parser.error('Do not include relative path components in --name.')
2402
nsylvain@google.comefc80932011-05-31 21:27:56 +00002403 deps_file = options.deps_file
agablea98a6cd2016-11-15 14:30:10 -08002404 client.SetDefaultConfig(name, deps_file, base_url,
smutae7ea312016-07-18 11:59:41 -07002405 managed=not options.unmanaged,
Paweł Hajdan, Jr3ba2a7c2017-10-04 19:24:46 +02002406 cache_dir=options.cache_dir,
2407 custom_vars=custom_vars)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002408 client.SaveConfig()
maruel@chromium.org79692d62010-05-14 18:57:13 +00002409 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002410
2411
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002412@subcommand.epilog("""Example:
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002413 gclient pack > patch.txt
2414 generate simple patch for configured client and dependences
2415""")
2416def CMDpack(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002417 """Generates a patch which can be applied at the root of the tree.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00002418
agabled437d762016-10-17 09:35:11 -07002419 Internally, runs 'git diff' on each checked out module and
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002420 dependencies, and performs minimal postprocessing of the output. The
2421 resulting patch is printed to stdout and can be applied to a freshly
2422 checked out tree via 'patch -p0 < patchfile'.
2423 """
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002424 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2425 help='override deps for the specified (comma-separated) '
2426 'platform(s); \'all\' will process all deps_os '
2427 'references')
haitao.feng@intel.com306080c2012-05-04 13:11:29 +00002428 parser.remove_option('--jobs')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002429 (options, args) = parser.parse_args(args)
iannucci@chromium.org50395ea2013-04-04 04:47:42 +00002430 # Force jobs to 1 so the stdout is not annotated with the thread ids
haitao.feng@intel.com306080c2012-05-04 13:11:29 +00002431 options.jobs = 1
kbr@google.comab318592009-09-04 00:54:55 +00002432 client = GClient.LoadCurrentConfig(options)
2433 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002434 raise gclient_utils.Error('client not configured; see \'gclient config\'')
kbr@google.comab318592009-09-04 00:54:55 +00002435 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002436 client.PrintLocationAndContents()
kbr@google.comab318592009-09-04 00:54:55 +00002437 return client.RunOnDeps('pack', args)
2438
2439
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002440def CMDstatus(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002441 """Shows modification status for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002442 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2443 help='override deps for the specified (comma-separated) '
2444 'platform(s); \'all\' will process all deps_os '
2445 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002446 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002447 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002448 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002449 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002450 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002451 client.PrintLocationAndContents()
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002452 return client.RunOnDeps('status', args)
2453
2454
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002455@subcommand.epilog("""Examples:
maruel@chromium.org79692d62010-05-14 18:57:13 +00002456 gclient sync
2457 update files from SCM according to current configuration,
2458 *for modules which have changed since last update or sync*
2459 gclient sync --force
2460 update files from SCM according to current configuration, for
2461 all modules (useful for recovering files deleted from local copy)
2462 gclient sync --revision src@31000
2463 update src directory to r31000
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002464
2465JSON output format:
2466If the --output-json option is specified, the following document structure will
2467be emitted to the provided file. 'null' entries may occur for subprojects which
2468are present in the gclient solution, but were not processed (due to custom_deps,
2469os_deps, etc.)
2470
2471{
2472 "solutions" : {
2473 "<name>": { # <name> is the posix-normalized path to the solution.
agabled437d762016-10-17 09:35:11 -07002474 "revision": [<git id hex string>|null],
2475 "scm": ["git"|null],
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002476 }
2477 }
2478}
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002479""")
2480def CMDsync(parser, args):
2481 """Checkout/update all modules."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002482 parser.add_option('-f', '--force', action='store_true',
2483 help='force update even for unchanged modules')
2484 parser.add_option('-n', '--nohooks', action='store_true',
2485 help='don\'t run hooks after the update is complete')
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00002486 parser.add_option('-p', '--noprehooks', action='store_true',
2487 help='don\'t run pre-DEPS hooks', default=False)
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002488 parser.add_option('-r', '--revision', action='append',
2489 dest='revisions', metavar='REV', default=[],
2490 help='Enforces revision/hash for the solutions with the '
2491 'format src@rev. The src@ part is optional and can be '
2492 'skipped. -r can be used multiple times when .gclient '
2493 'has multiple solutions configured and will work even '
agablea98a6cd2016-11-15 14:30:10 -08002494 'if the src@ part is skipped.')
maruel@chromium.org794207e2013-03-08 15:29:43 +00002495 parser.add_option('--with_branch_heads', action='store_true',
2496 help='Clone git "branch_heads" refspecs in addition to '
2497 'the default refspecs. This adds about 1/2GB to a '
2498 'full checkout. (git only)')
szager@chromium.org8d3348f2014-08-19 22:49:16 +00002499 parser.add_option('--with_tags', action='store_true',
2500 help='Clone git tags in addition to the default refspecs.')
agable2697cd12016-06-28 10:23:53 -07002501 parser.add_option('-H', '--head', action='store_true',
agablea98a6cd2016-11-15 14:30:10 -08002502 help='DEPRECATED: only made sense with safesync urls.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002503 parser.add_option('-D', '--delete_unversioned_trees', action='store_true',
steveblock@chromium.org98e69452012-02-16 16:36:43 +00002504 help='Deletes from the working copy any dependencies that '
2505 'have been removed since the last sync, as long as '
2506 'there are no local modifications. When used with '
2507 '--force, such dependencies are removed even if they '
2508 'have local modifications. When used with --reset, '
2509 'all untracked directories are removed from the '
tnagel@chromium.orga2aaa632014-02-28 21:47:27 +00002510 'working copy, excluding those which are explicitly '
steveblock@chromium.org98e69452012-02-16 16:36:43 +00002511 'ignored in the repository.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002512 parser.add_option('-R', '--reset', action='store_true',
2513 help='resets any local changes before updating (git only)')
bauerb@chromium.org2aad1b22011-07-22 12:00:41 +00002514 parser.add_option('-M', '--merge', action='store_true',
2515 help='merge upstream changes instead of trying to '
2516 'fast-forward or rebase')
dnj@chromium.org5b23e872015-02-20 21:25:57 +00002517 parser.add_option('-A', '--auto_rebase', action='store_true',
2518 help='Automatically rebase repositories against local '
2519 'checkout during update (git only).')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002520 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2521 help='override deps for the specified (comma-separated) '
2522 'platform(s); \'all\' will process all deps_os '
2523 'references')
Paweł Hajdan, Jr357415c2017-07-24 14:35:28 +02002524 # TODO(phajdan.jr): use argparse.SUPPRESS to hide internal flags.
2525 parser.add_option('--do-not-merge-os-specific-entries', action='store_true',
2526 help='INTERNAL ONLY - disables merging of deps_os and '
2527 'hooks_os to dependencies and hooks')
Paweł Hajdan, Jr5ec77132017-08-16 19:21:06 +02002528 parser.add_option('--process-all-deps', action='store_true',
2529 help='Check out all deps, even for different OS-es, '
2530 'or with conditions evaluating to false')
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +00002531 parser.add_option('--upstream', action='store_true',
2532 help='Make repo state match upstream branch.')
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002533 parser.add_option('--output-json',
2534 help='Output a json document to this path containing '
2535 'summary information about the sync.')
primiano@chromium.org5439ea52014-08-06 17:18:18 +00002536 parser.add_option('--no-history', action='store_true',
2537 help='GIT ONLY - Reduces the size/time of the checkout at '
2538 'the cost of no history. Requires Git 1.9+')
hinoka@chromium.org46b87412014-05-15 00:42:05 +00002539 parser.add_option('--shallow', action='store_true',
2540 help='GIT ONLY - Do a shallow clone into the cache dir. '
2541 'Requires Git 1.9+')
e.hakkinen@samsung.come8bc1aa2015-04-08 08:00:37 +00002542 parser.add_option('--no_bootstrap', '--no-bootstrap',
2543 action='store_true',
2544 help='Don\'t bootstrap from Google Storage.')
Vadim Shtayura08049e22017-10-11 00:14:52 +00002545 parser.add_option('--ignore_locks', action='store_true',
2546 help='GIT ONLY - Ignore cache locks.')
iannucci@chromium.org30a07982016-04-07 21:35:19 +00002547 parser.add_option('--break_repo_locks', action='store_true',
2548 help='GIT ONLY - Forcibly remove repo locks (e.g. '
2549 'index.lock). This should only be used if you know for '
2550 'certain that this invocation of gclient is the only '
2551 'thing operating on the git repos (e.g. on a bot).')
Vadim Shtayura08049e22017-10-11 00:14:52 +00002552 parser.add_option('--lock_timeout', type='int', default=5000,
2553 help='GIT ONLY - Deadline (in seconds) to wait for git '
2554 'cache lock to become available. Default is %default.')
agabled437d762016-10-17 09:35:11 -07002555 # TODO(agable): Remove these when the oldest CrOS release milestone is M56.
2556 parser.add_option('-t', '--transitive', action='store_true',
2557 help='DEPRECATED: This is a no-op.')
sdefresne69b1be12016-10-18 05:48:02 -07002558 parser.add_option('-m', '--manually_grab_svn_rev', action='store_true',
agabled437d762016-10-17 09:35:11 -07002559 help='DEPRECATED: This is a no-op.')
Paweł Hajdan, Jr7c7b5592017-05-23 15:06:05 +02002560 # TODO(phajdan.jr): Remove validation options once default (crbug/570091).
Paweł Hajdan, Jr694773d2017-05-29 16:06:23 +02002561 parser.add_option('--validate-syntax', action='store_true', default=True,
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +02002562 help='Validate the .gclient and DEPS syntax')
Paweł Hajdan, Jr7c7b5592017-05-23 15:06:05 +02002563 parser.add_option('--disable-syntax-validation', action='store_false',
2564 dest='validate_syntax',
2565 help='Disable validation of .gclient and DEPS syntax.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002566 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002567 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002568
2569 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002570 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002571
smutae7ea312016-07-18 11:59:41 -07002572 if options.revisions and options.head:
2573 # TODO(maruel): Make it a parser.error if it doesn't break any builder.
2574 print('Warning: you cannot use both --head and --revision')
2575
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002576 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002577 client.PrintLocationAndContents()
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002578 ret = client.RunOnDeps('update', args)
2579 if options.output_json:
2580 slns = {}
2581 for d in client.subtree(True):
2582 normed = d.name.replace('\\', '/').rstrip('/') + '/'
2583 slns[normed] = {
2584 'revision': d.got_revision,
2585 'scm': d.used_scm.name if d.used_scm else None,
hinoka@chromium.org17db9052014-05-10 01:11:29 +00002586 'url': str(d.url) if d.url else None,
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002587 }
2588 with open(options.output_json, 'wb') as f:
2589 json.dump({'solutions': slns}, f)
2590 return ret
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002591
2592
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002593CMDupdate = CMDsync
2594
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002595
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +02002596def CMDvalidate(parser, args):
2597 """Validates the .gclient and DEPS syntax."""
2598 options, args = parser.parse_args(args)
2599 options.validate_syntax = True
2600 client = GClient.LoadCurrentConfig(options)
2601 rv = client.RunOnDeps('validate', args)
2602 if rv == 0:
2603 print('validate: SUCCESS')
2604 else:
2605 print('validate: FAILURE')
2606 return rv
2607
2608
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002609def CMDdiff(parser, args):
2610 """Displays local diff for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002611 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2612 help='override deps for the specified (comma-separated) '
2613 'platform(s); \'all\' will process all deps_os '
2614 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002615 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002616 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002617 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002618 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002619 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002620 client.PrintLocationAndContents()
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002621 return client.RunOnDeps('diff', args)
2622
2623
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002624def CMDrevert(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002625 """Reverts all modifications in every dependencies.
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00002626
2627 That's the nuclear option to get back to a 'clean' state. It removes anything
agabled437d762016-10-17 09:35:11 -07002628 that shows up in git status."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002629 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2630 help='override deps for the specified (comma-separated) '
2631 'platform(s); \'all\' will process all deps_os '
2632 'references')
2633 parser.add_option('-n', '--nohooks', action='store_true',
2634 help='don\'t run hooks after the revert is complete')
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00002635 parser.add_option('-p', '--noprehooks', action='store_true',
2636 help='don\'t run pre-DEPS hooks', default=False)
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +00002637 parser.add_option('--upstream', action='store_true',
2638 help='Make repo state match upstream branch.')
iannucci@chromium.orgbf525dc2016-04-07 22:00:28 +00002639 parser.add_option('--break_repo_locks', action='store_true',
2640 help='GIT ONLY - Forcibly remove repo locks (e.g. '
2641 'index.lock). This should only be used if you know for '
2642 'certain that this invocation of gclient is the only '
2643 'thing operating on the git repos (e.g. on a bot).')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002644 (options, args) = parser.parse_args(args)
2645 # --force is implied.
2646 options.force = True
steveblock@chromium.org98e69452012-02-16 16:36:43 +00002647 options.reset = False
2648 options.delete_unversioned_trees = False
agablec903d732016-07-26 09:07:24 -07002649 options.merge = False
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002650 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002651 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002652 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002653 return client.RunOnDeps('revert', args)
2654
2655
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002656def CMDrunhooks(parser, args):
2657 """Runs hooks for files that have been modified in the local working copy."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002658 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2659 help='override deps for the specified (comma-separated) '
2660 'platform(s); \'all\' will process all deps_os '
2661 'references')
2662 parser.add_option('-f', '--force', action='store_true', default=True,
2663 help='Deprecated. No effect.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002664 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002665 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002666 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002667 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002668 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002669 client.PrintLocationAndContents()
maruel@chromium.org5df6a462009-08-28 18:52:26 +00002670 options.force = True
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002671 options.nohooks = False
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002672 return client.RunOnDeps('runhooks', args)
2673
2674
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002675def CMDrevinfo(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002676 """Outputs revision info mapping for the client and its dependencies.
maruel@chromium.org9eda4112010-06-11 18:56:10 +00002677
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002678 This allows the capture of an overall 'revision' for the source tree that
maruel@chromium.org9eda4112010-06-11 18:56:10 +00002679 can be used to reproduce the same tree in the future. It is only useful for
agabled437d762016-10-17 09:35:11 -07002680 'unpinned dependencies', i.e. DEPS/deps references without a git hash.
2681 A git branch name isn't 'pinned' since the actual commit can change.
maruel@chromium.org9eda4112010-06-11 18:56:10 +00002682 """
2683 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2684 help='override deps for the specified (comma-separated) '
2685 'platform(s); \'all\' will process all deps_os '
2686 'references')
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00002687 parser.add_option('-a', '--actual', action='store_true',
2688 help='gets the actual checked out revisions instead of the '
2689 'ones specified in the DEPS and .gclient files')
maruel@chromium.org9eda4112010-06-11 18:56:10 +00002690 parser.add_option('-s', '--snapshot', action='store_true',
2691 help='creates a snapshot .gclient file of the current '
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00002692 'version of all repositories to reproduce the tree, '
2693 'implies -a')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002694 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002695 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002696 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002697 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002698 client.PrintRevInfo()
maruel@chromium.org79692d62010-05-14 18:57:13 +00002699 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002700
2701
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00002702def CMDverify(parser, args):
2703 """Verifies the DEPS file deps are only from allowed_hosts."""
2704 (options, args) = parser.parse_args(args)
2705 client = GClient.LoadCurrentConfig(options)
2706 if not client:
2707 raise gclient_utils.Error('client not configured; see \'gclient config\'')
2708 client.RunOnDeps(None, [])
2709 # Look at each first-level dependency of this gclient only.
2710 for dep in client.dependencies:
2711 bad_deps = dep.findDepsFromNotAllowedHosts()
2712 if not bad_deps:
2713 continue
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002714 print("There are deps from not allowed hosts in file %s" % dep.deps_file)
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00002715 for bad_dep in bad_deps:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002716 print("\t%s at %s" % (bad_dep.name, bad_dep.url))
2717 print("allowed_hosts:", ', '.join(dep.allowed_hosts))
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00002718 sys.stdout.flush()
2719 raise gclient_utils.Error(
2720 'dependencies from disallowed hosts; check your DEPS file.')
2721 return 0
2722
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002723class OptionParser(optparse.OptionParser):
szager@chromium.orge2e03202012-07-31 18:05:16 +00002724 gclientfile_default = os.environ.get('GCLIENT_FILE', '.gclient')
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002725
2726 def __init__(self, **kwargs):
2727 optparse.OptionParser.__init__(
2728 self, version='%prog ' + __version__, **kwargs)
2729
2730 # Some arm boards have issues with parallel sync.
2731 if platform.machine().startswith('arm'):
2732 jobs = 1
2733 else:
2734 jobs = max(8, gclient_utils.NumLocalCpus())
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002735
2736 self.add_option(
2737 '-j', '--jobs', default=jobs, type='int',
2738 help='Specify how many SCM commands can run in parallel; defaults to '
tnagel@chromium.orga2aaa632014-02-28 21:47:27 +00002739 '%default on this machine')
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002740 self.add_option(
2741 '-v', '--verbose', action='count', default=0,
2742 help='Produces additional output for diagnostics. Can be used up to '
2743 'three times for more logging info.')
2744 self.add_option(
2745 '--gclientfile', dest='config_filename',
2746 help='Specify an alternate %s file' % self.gclientfile_default)
2747 self.add_option(
2748 '--spec',
2749 help='create a gclient file containing the provided string. Due to '
2750 'Cygwin/Python brokenness, it can\'t contain any newlines.')
2751 self.add_option(
Aleksandr Derbenev9e8fb0e2017-08-01 20:18:31 +03002752 '--cache-dir',
2753 help='(git only) Cache all git repos into this dir and do '
2754 'shared clones from the cache, instead of cloning '
2755 'directly from the remote. (experimental)',
2756 default=os.environ.get('GCLIENT_CACHE_DIR'))
2757 self.add_option(
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002758 '--no-nag-max', default=False, action='store_true',
scottmg@chromium.orgf547c802013-09-27 17:55:26 +00002759 help='Ignored for backwards compatibility.')
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002760
2761 def parse_args(self, args=None, values=None):
2762 """Integrates standard options processing."""
2763 options, args = optparse.OptionParser.parse_args(self, args, values)
2764 levels = [logging.ERROR, logging.WARNING, logging.INFO, logging.DEBUG]
2765 logging.basicConfig(
2766 level=levels[min(options.verbose, len(levels) - 1)],
maruel@chromium.org0895b752011-08-26 20:40:33 +00002767 format='%(module)s(%(lineno)d) %(funcName)s:%(message)s')
szager@chromium.orge2e03202012-07-31 18:05:16 +00002768 if options.config_filename and options.spec:
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002769 self.error('Cannot specifiy both --gclientfile and --spec')
rdsmith@chromium.orgd9591f02014-02-05 19:28:20 +00002770 if (options.config_filename and
2771 options.config_filename != os.path.basename(options.config_filename)):
2772 self.error('--gclientfile target must be a filename, not a path')
szager@chromium.orge2e03202012-07-31 18:05:16 +00002773 if not options.config_filename:
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002774 options.config_filename = self.gclientfile_default
maruel@chromium.org0895b752011-08-26 20:40:33 +00002775 options.entries_filename = options.config_filename + '_entries'
2776 if options.jobs < 1:
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002777 self.error('--jobs must be 1 or higher')
maruel@chromium.org0895b752011-08-26 20:40:33 +00002778
2779 # These hacks need to die.
2780 if not hasattr(options, 'revisions'):
2781 # GClient.RunOnDeps expects it even if not applicable.
2782 options.revisions = []
smutae7ea312016-07-18 11:59:41 -07002783 if not hasattr(options, 'head'):
2784 options.head = None
maruel@chromium.org0895b752011-08-26 20:40:33 +00002785 if not hasattr(options, 'nohooks'):
2786 options.nohooks = True
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00002787 if not hasattr(options, 'noprehooks'):
2788 options.noprehooks = True
maruel@chromium.org0895b752011-08-26 20:40:33 +00002789 if not hasattr(options, 'deps_os'):
2790 options.deps_os = None
maruel@chromium.org0895b752011-08-26 20:40:33 +00002791 if not hasattr(options, 'force'):
2792 options.force = None
2793 return (options, args)
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002794
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002795
2796def disable_buffering():
2797 # Make stdout auto-flush so buildbot doesn't kill us during lengthy
2798 # operations. Python as a strong tendency to buffer sys.stdout.
2799 sys.stdout = gclient_utils.MakeFileAutoFlush(sys.stdout)
2800 # Make stdout annotated with the thread ids.
2801 sys.stdout = gclient_utils.MakeFileAnnotated(sys.stdout)
maruel@chromium.org0895b752011-08-26 20:40:33 +00002802
2803
sbc@chromium.org013731e2015-02-26 18:28:43 +00002804def main(argv):
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002805 """Doesn't parse the arguments here, just find the right subcommand to
2806 execute."""
maruel@chromium.org82798cb2012-02-23 18:16:12 +00002807 if sys.hexversion < 0x02060000:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002808 print(
maruel@chromium.org82798cb2012-02-23 18:16:12 +00002809 '\nYour python version %s is unsupported, please upgrade.\n' %
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002810 sys.version.split(' ', 1)[0],
2811 file=sys.stderr)
maruel@chromium.org82798cb2012-02-23 18:16:12 +00002812 return 2
bcwhite@chromium.org6683ab42013-02-11 16:13:47 +00002813 if not sys.executable:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002814 print(
2815 '\nPython cannot find the location of it\'s own executable.\n',
2816 file=sys.stderr)
bcwhite@chromium.org6683ab42013-02-11 16:13:47 +00002817 return 2
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002818 fix_encoding.fix_encoding()
2819 disable_buffering()
iannucci@chromium.org596cd5c2016-04-04 21:34:39 +00002820 setup_color.init()
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002821 dispatcher = subcommand.CommandDispatcher(__name__)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00002822 try:
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002823 return dispatcher.execute(OptionParser(), argv)
xusydoc@chromium.org2fd6c3f2013-05-03 21:57:55 +00002824 except KeyboardInterrupt:
2825 gclient_utils.GClientChildren.KillAllRemainingChildren()
2826 raise
vapier@chromium.orga81a56e2015-11-11 07:56:13 +00002827 except (gclient_utils.Error, subprocess2.CalledProcessError) as e:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002828 print('Error: %s' % str(e), file=sys.stderr)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00002829 return 1
borenet@google.com6a9b1682014-03-24 18:35:23 +00002830 finally:
2831 gclient_utils.PrintWarnings()
sbc@chromium.org013731e2015-02-26 18:28:43 +00002832 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002833
2834
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00002835if '__main__' == __name__:
sbc@chromium.org013731e2015-02-26 18:28:43 +00002836 try:
2837 sys.exit(main(sys.argv[1:]))
2838 except KeyboardInterrupt:
2839 sys.stderr.write('interrupted\n')
2840 sys.exit(1)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002841
2842# vim: ts=2:sw=2:tw=80:et: