blob: 814bf59922fc15730818e87b0b4212b8896cc672 [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,
Edward Lemur231f5ea2018-01-31 19:02:36 +0100359 relative, condition, condition_value, print_outbuf=False):
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
Edward Lemur231f5ea2018-01-31 19:02:36 +0100423 # Controls whether we want to print git's output when we first clone the
424 # dependency
425 self.print_outbuf = print_outbuf
426
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000427 if not self.name and self.parent:
428 raise gclient_utils.Error('Dependency without name')
429
John Budorick0f7b2002018-01-19 15:46:17 -0800430 def ToLines(self):
431 s = []
432 condition_part = ([' "condition": %r,' % self.condition]
433 if self.condition else [])
434 s.extend([
435 ' # %s' % self.hierarchy(include_url=False),
436 ' "%s": {' % (self.name,),
437 ' "url": "%s",' % (self.raw_url,),
438 ] + condition_part + [
439 ' },',
440 '',
441 ])
442 return s
443
444
445
maruel@chromium.org470b5432011-10-11 18:18:19 +0000446 @property
447 def requirements(self):
448 """Calculate the list of requirements."""
449 requirements = set()
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000450 # self.parent is implicitly a requirement. This will be recursive by
451 # definition.
452 if self.parent and self.parent.name:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000453 requirements.add(self.parent.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000454
455 # For a tree with at least 2 levels*, the leaf node needs to depend
456 # on the level higher up in an orderly way.
457 # This becomes messy for >2 depth as the DEPS file format is a dictionary,
458 # thus unsorted, while the .gclient format is a list thus sorted.
459 #
460 # * _recursion_limit is hard coded 2 and there is no hope to change this
461 # value.
462 #
463 # Interestingly enough, the following condition only works in the case we
464 # want: self is a 2nd level node. 3nd level node wouldn't need this since
465 # they already have their parent as a requirement.
maruel@chromium.org470b5432011-10-11 18:18:19 +0000466 if self.parent and self.parent.parent and not self.parent.parent.parent:
467 requirements |= set(i.name for i in self.root.dependencies if i.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000468
maruel@chromium.org470b5432011-10-11 18:18:19 +0000469 if self.name:
470 requirements |= set(
471 obj.name for obj in self.root.subtree(False)
472 if (obj is not self
473 and obj.name and
474 self.name.startswith(posixpath.join(obj.name, ''))))
475 requirements = tuple(sorted(requirements))
476 logging.info('Dependency(%s).requirements = %s' % (self.name, requirements))
477 return requirements
478
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000479 @property
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000480 def try_recursedeps(self):
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000481 """Returns False if recursion_override is ever specified."""
482 if self.recursion_override is not None:
483 return False
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000484 return self.parent.try_recursedeps
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000485
486 @property
487 def recursion_limit(self):
488 """Returns > 0 if this dependency is not too recursed to be processed."""
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000489 # We continue to support the absence of recursedeps until tools and DEPS
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000490 # using recursion_override are updated.
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000491 if self.try_recursedeps and self.parent.recursedeps != None:
492 if self.name in self.parent.recursedeps:
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000493 return 1
494
495 if self.recursion_override is not None:
496 return self.recursion_override
497 return max(self.parent.recursion_limit - 1, 0)
498
maruel@chromium.org470b5432011-10-11 18:18:19 +0000499 def verify_validity(self):
500 """Verifies that this Dependency is fine to add as a child of another one.
501
502 Returns True if this entry should be added, False if it is a duplicate of
503 another entry.
504 """
505 logging.info('Dependency(%s).verify_validity()' % self.name)
506 if self.name in [s.name for s in self.parent.dependencies]:
507 raise gclient_utils.Error(
508 'The same name "%s" appears multiple times in the deps section' %
509 self.name)
510 if not self.should_process:
511 # Return early, no need to set requirements.
512 return True
513
514 # This require a full tree traversal with locks.
515 siblings = [d for d in self.root.subtree(False) if d.name == self.name]
516 for sibling in siblings:
maruel@chromium.orgb848d5b2012-10-10 23:25:50 +0000517 self_url = self.LateOverride(self.url)
518 sibling_url = sibling.LateOverride(sibling.url)
519 # Allow to have only one to be None or ''.
520 if self_url != sibling_url and bool(self_url) == bool(sibling_url):
maruel@chromium.org470b5432011-10-11 18:18:19 +0000521 raise gclient_utils.Error(
maruel@chromium.orgb848d5b2012-10-10 23:25:50 +0000522 ('Dependency %s specified more than once:\n'
523 ' %s [%s]\n'
524 'vs\n'
525 ' %s [%s]') % (
526 self.name,
527 sibling.hierarchy(),
528 sibling_url,
529 self.hierarchy(),
530 self_url))
maruel@chromium.org470b5432011-10-11 18:18:19 +0000531 # In theory we could keep it as a shadow of the other one. In
532 # practice, simply ignore it.
533 logging.warn('Won\'t process duplicate dependency %s' % sibling)
534 return False
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000535 return True
maruel@chromium.org064186c2011-09-27 23:53:33 +0000536
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000537 def LateOverride(self, url):
Paweł Hajdan, Jr7e9303b2017-05-23 14:38:27 +0200538 """Resolves the parsed url from url."""
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000539 assert self.parsed_url == None or not self.should_process, self.parsed_url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000540 parsed_url = self.get_custom_deps(self.name, url)
541 if parsed_url != url:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000542 logging.info(
543 'Dependency(%s).LateOverride(%s) -> %s' %
544 (self.name, url, parsed_url))
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000545 return parsed_url
546
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000547 if isinstance(url, basestring):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000548 parsed_url = urlparse.urlparse(url)
scr@chromium.orgf1eccaf2014-04-11 15:51:33 +0000549 if (not parsed_url[0] and
550 not re.match(r'^\w+\@[\w\.-]+\:[\w\/]+', parsed_url[2])):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000551 # A relative url. Fetch the real base.
552 path = parsed_url[2]
553 if not path.startswith('/'):
554 raise gclient_utils.Error(
555 'relative DEPS entry \'%s\' must begin with a slash' % url)
556 # Create a scm just to query the full url.
557 parent_url = self.parent.parsed_url
John Budorick0f7b2002018-01-19 15:46:17 -0800558 scm = self.CreateSCM(
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000559 parent_url, self.root.root_dir, None, self.outbuf)
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000560 parsed_url = scm.FullUrlForRelativeUrl(url)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000561 else:
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000562 parsed_url = url
maruel@chromium.org470b5432011-10-11 18:18:19 +0000563 logging.info(
564 'Dependency(%s).LateOverride(%s) -> %s' %
565 (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000566 return parsed_url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000567
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000568 if url is None:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000569 logging.info(
570 'Dependency(%s).LateOverride(%s) -> %s' % (self.name, url, url))
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000571 return url
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000572
573 raise gclient_utils.Error('Unknown url type')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000574
bratell@opera.comed2b4fe2013-12-16 14:34:12 +0000575 @staticmethod
Paweł Hajdan, Jr5ec77132017-08-16 19:21:06 +0200576 def MergeWithOsDeps(deps, deps_os, target_os_list, process_all_deps):
bratell@opera.comed2b4fe2013-12-16 14:34:12 +0000577 """Returns a new "deps" structure that is the deps sent in updated
578 with information from deps_os (the deps_os section of the DEPS
579 file) that matches the list of target os."""
bratell@opera.comed2b4fe2013-12-16 14:34:12 +0000580 new_deps = deps.copy()
Paweł Hajdan, Jrfd0057e2017-06-21 14:20:21 +0200581 for dep_os, os_deps in deps_os.iteritems():
582 for key, value in os_deps.iteritems():
583 if value is None:
584 # Make this condition very visible, so it's not a silent failure.
585 # It's unclear how to support None override in deps_os.
586 logging.error('Ignoring %r:%r in %r deps_os', key, value, dep_os)
587 continue
588
589 # Normalize value to be a dict which contains |should_process| metadata.
590 if isinstance(value, basestring):
591 value = {'url': value}
592 assert isinstance(value, collections.Mapping), (key, value)
Paweł Hajdan, Jr5ec77132017-08-16 19:21:06 +0200593 value['should_process'] = dep_os in target_os_list or process_all_deps
Paweł Hajdan, Jrfd0057e2017-06-21 14:20:21 +0200594
595 # Handle collisions/overrides.
596 if key in new_deps and new_deps[key] != value:
597 # Normalize the existing new_deps entry.
598 if isinstance(new_deps[key], basestring):
599 new_deps[key] = {'url': new_deps[key]}
600 assert isinstance(new_deps[key],
601 collections.Mapping), (key, new_deps[key])
602
603 # It's OK if the "override" sets the key to the same value.
604 # This is mostly for legacy reasons to keep existing DEPS files
605 # working. Often mac/ios and unix/android will do this.
606 if value['url'] != new_deps[key]['url']:
607 raise gclient_utils.Error(
608 ('Value from deps_os (%r; %r: %r) conflicts with existing deps '
609 'entry (%r).') % (dep_os, key, value, new_deps[key]))
610
611 # We'd otherwise overwrite |should_process| metadata, but a dep should
612 # be processed if _any_ of its references call for that.
613 value['should_process'] = (
614 value['should_process'] or
615 new_deps[key].get('should_process', True))
616
617 new_deps[key] = value
618
bratell@opera.comed2b4fe2013-12-16 14:34:12 +0000619 return new_deps
620
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200621 def _postprocess_deps(self, deps, rel_prefix):
622 """Performs post-processing of deps compared to what's in the DEPS file."""
Paweł Hajdan, Jr4426eaf2017-06-13 12:25:47 +0200623 # Make sure the dict is mutable, e.g. in case it's frozen.
624 deps = dict(deps)
625
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200626 # If a line is in custom_deps, but not in the solution, we want to append
627 # this line to the solution.
628 for d in self.custom_deps:
629 if d not in deps:
630 deps[d] = self.custom_deps[d]
631
632 if rel_prefix:
633 logging.warning('use_relative_paths enabled.')
634 rel_deps = {}
635 for d, url in deps.items():
636 # normpath is required to allow DEPS to use .. in their
637 # dependency local path.
638 rel_deps[os.path.normpath(os.path.join(rel_prefix, d))] = url
639 logging.warning('Updating deps by prepending %s.', rel_prefix)
640 deps = rel_deps
641
642 return deps
643
644 def _deps_to_objects(self, deps, use_relative_paths):
645 """Convert a deps dict to a dict of Dependency objects."""
646 deps_to_add = []
John Budorick0f7b2002018-01-19 15:46:17 -0800647 cipd_root = None
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200648 for name, dep_value in deps.iteritems():
649 should_process = self.recursion_limit and self.should_process
650 deps_file = self.deps_file
651 if self.recursedeps is not None:
652 ent = self.recursedeps.get(name)
653 if ent is not None:
654 deps_file = ent['deps_file']
655 if dep_value is None:
656 continue
John Budorick0f7b2002018-01-19 15:46:17 -0800657
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200658 condition = None
659 condition_value = True
660 if isinstance(dep_value, basestring):
Paweł Hajdan, Jrfc6196b2017-07-27 13:15:25 +0200661 raw_url = dep_value
John Budorick0f7b2002018-01-19 15:46:17 -0800662 dep_type = None
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200663 else:
664 # This should be guaranteed by schema checking in gclient_eval.
665 assert isinstance(dep_value, collections.Mapping)
John Budorick0f7b2002018-01-19 15:46:17 -0800666 raw_url = dep_value.get('url')
Paweł Hajdan, Jrfd0057e2017-06-21 14:20:21 +0200667 # Take into account should_process metadata set by MergeWithOsDeps.
668 should_process = (should_process and
669 dep_value.get('should_process', True))
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200670 condition = dep_value.get('condition')
John Budorick0f7b2002018-01-19 15:46:17 -0800671 dep_type = dep_value.get('dep_type')
Paweł Hajdan, Jrfc6196b2017-07-27 13:15:25 +0200672
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200673 if condition:
Paweł Hajdan, Jrd3790252017-07-03 21:06:24 +0200674 condition_value = gclient_eval.EvaluateCondition(
675 condition, self.get_vars())
Paweł Hajdan, Jr5ec77132017-08-16 19:21:06 +0200676 if not self._get_option('process_all_deps', False):
677 should_process = should_process and condition_value
John Budorick0f7b2002018-01-19 15:46:17 -0800678
679 if dep_type == 'cipd':
680 if not cipd_root:
681 cipd_root = gclient_scm.CipdRoot(
682 os.path.join(self.root.root_dir, self.name),
683 # TODO(jbudorick): Support other service URLs as necessary.
684 # Service URLs should be constant over the scope of a cipd
685 # root, so a var per DEPS file specifying the service URL
686 # should suffice.
687 'https://chrome-infra-packages.appspot.com')
688 for package in dep_value.get('packages', []):
689 deps_to_add.append(
690 CipdDependency(
691 self, name, package, cipd_root,
692 self.custom_vars, should_process, use_relative_paths,
693 condition, condition_value))
694 elif dep_type == 'git':
695 url = raw_url.format(**self.get_vars())
696 deps_to_add.append(
697 GitDependency(
698 self, name, raw_url, url, None, None, self.custom_vars, None,
699 deps_file, should_process, use_relative_paths, condition,
700 condition_value))
701 else:
702 url = raw_url.format(**self.get_vars())
703 deps_to_add.append(
704 Dependency(
705 self, name, raw_url, url, None, None, self.custom_vars, None,
706 deps_file, should_process, use_relative_paths, condition,
707 condition_value))
708
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200709 deps_to_add.sort(key=lambda x: x.name)
710 return deps_to_add
711
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000712 def ParseDepsFile(self):
maruel@chromium.org271375b2010-06-23 19:17:38 +0000713 """Parses the DEPS file for this dependency."""
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000714 assert not self.deps_parsed
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000715 assert not self.dependencies
iannucci@chromium.org0c3f3052014-05-22 00:29:22 +0000716
717 deps_content = None
cmp@chromium.org76ce73c2014-07-02 00:13:18 +0000718
719 # First try to locate the configured deps file. If it's missing, fallback
720 # to DEPS.
721 deps_files = [self.deps_file]
722 if 'DEPS' not in deps_files:
723 deps_files.append('DEPS')
724 for deps_file in deps_files:
725 filepath = os.path.join(self.root.root_dir, self.name, deps_file)
726 if os.path.isfile(filepath):
727 logging.info(
728 'ParseDepsFile(%s): %s file found at %s', self.name, deps_file,
729 filepath)
730 break
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000731 logging.info(
cmp@chromium.org76ce73c2014-07-02 00:13:18 +0000732 'ParseDepsFile(%s): No %s file found at %s', self.name, deps_file,
733 filepath)
734
735 if os.path.isfile(filepath):
maruel@chromium.org46304292010-10-28 11:42:00 +0000736 deps_content = gclient_utils.FileRead(filepath)
cmp@chromium.org76ce73c2014-07-02 00:13:18 +0000737 logging.debug('ParseDepsFile(%s) read:\n%s', self.name, deps_content)
iannucci@chromium.org0c3f3052014-05-22 00:29:22 +0000738
739 local_scope = {}
740 if deps_content:
Paweł Hajdan, Jrf1587bf2017-06-20 21:19:07 +0200741 global_scope = {
Paweł Hajdan, Jrfc6196b2017-07-27 13:15:25 +0200742 'Var': lambda var_name: '{%s}' % var_name,
Paweł Hajdan, Jrf1587bf2017-06-20 21:19:07 +0200743 'deps_os': {},
744 }
maruel@chromium.org46304292010-10-28 11:42:00 +0000745 # Eval the content.
746 try:
Paweł Hajdan, Jrc485d5a2017-06-02 12:08:09 +0200747 if self._get_option('validate_syntax', False):
John Budorick0f7b2002018-01-19 15:46:17 -0800748 local_scope = gclient_eval.Exec(
749 deps_content, global_scope, local_scope, filepath)
Paweł Hajdan, Jrc485d5a2017-06-02 12:08:09 +0200750 else:
751 exec(deps_content, global_scope, local_scope)
vapier@chromium.orga81a56e2015-11-11 07:56:13 +0000752 except SyntaxError as e:
maruel@chromium.org46304292010-10-28 11:42:00 +0000753 gclient_utils.SyntaxErrorToError(filepath, e)
iannucci@chromium.org0c3f3052014-05-22 00:29:22 +0000754
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000755 if 'allowed_hosts' in local_scope:
756 try:
757 self._allowed_hosts = frozenset(local_scope.get('allowed_hosts'))
758 except TypeError: # raised if non-iterable
759 pass
760 if not self._allowed_hosts:
761 logging.warning("allowed_hosts is specified but empty %s",
762 self._allowed_hosts)
763 raise gclient_utils.Error(
764 'ParseDepsFile(%s): allowed_hosts must be absent '
765 'or a non-empty iterable' % self.name)
766
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +0200767 self._gn_args_file = local_scope.get('gclient_gn_args_file')
768 self._gn_args = local_scope.get('gclient_gn_args', [])
769
Paweł Hajdan, Jr1407d002017-08-01 20:01:01 +0200770 self._vars = local_scope.get('vars', {})
771 if self.parent:
772 for key, value in self.parent.get_vars().iteritems():
773 if key in self._vars:
774 self._vars[key] = value
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200775 # Since we heavily post-process things, freeze ones which should
776 # reflect original state of DEPS.
Paweł Hajdan, Jr1407d002017-08-01 20:01:01 +0200777 self._vars = gclient_utils.freeze(self._vars)
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200778
779 # If use_relative_paths is set in the DEPS file, regenerate
780 # the dictionary using paths relative to the directory containing
781 # the DEPS file. Also update recursedeps if use_relative_paths is
782 # enabled.
783 # If the deps file doesn't set use_relative_paths, but the parent did
784 # (and therefore set self.relative on this Dependency object), then we
785 # want to modify the deps and recursedeps by prepending the parent
786 # directory of this dependency.
787 use_relative_paths = local_scope.get('use_relative_paths', False)
788 rel_prefix = None
789 if use_relative_paths:
790 rel_prefix = self.name
791 elif self._relative:
792 rel_prefix = os.path.dirname(self.name)
793
Paweł Hajdan, Jrfc6196b2017-07-27 13:15:25 +0200794 deps = {}
795 for key, value in local_scope.get('deps', {}).iteritems():
796 deps[key.format(**self.get_vars())] = value
797
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200798 if 'recursion' in local_scope:
799 self.recursion_override = local_scope.get('recursion')
800 logging.warning(
801 'Setting %s recursion to %d.', self.name, self.recursion_limit)
802 self.recursedeps = None
803 if 'recursedeps' in local_scope:
804 self.recursedeps = {}
805 for ent in local_scope['recursedeps']:
806 if isinstance(ent, basestring):
807 self.recursedeps[ent] = {"deps_file": self.deps_file}
808 else: # (depname, depsfilename)
809 self.recursedeps[ent[0]] = {"deps_file": ent[1]}
810 logging.warning('Found recursedeps %r.', repr(self.recursedeps))
811
812 if rel_prefix:
813 logging.warning('Updating recursedeps by prepending %s.', rel_prefix)
814 rel_deps = {}
815 for depname, options in self.recursedeps.iteritems():
816 rel_deps[
817 os.path.normpath(os.path.join(rel_prefix, depname))] = options
818 self.recursedeps = rel_deps
819
820 # If present, save 'target_os' in the local_target_os property.
821 if 'target_os' in local_scope:
822 self.local_target_os = local_scope['target_os']
823 # load os specific dependencies if defined. these dependencies may
824 # override or extend the values defined by the 'deps' member.
825 target_os_list = self.target_os
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +0200826 if 'deps_os' in local_scope:
827 for dep_os, os_deps in local_scope['deps_os'].iteritems():
828 self._os_dependencies[dep_os] = self._deps_to_objects(
829 self._postprocess_deps(os_deps, rel_prefix), use_relative_paths)
Paweł Hajdan, Jr357415c2017-07-24 14:35:28 +0200830 if target_os_list and not self._get_option(
831 'do_not_merge_os_specific_entries', False):
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +0200832 deps = self.MergeWithOsDeps(
Paweł Hajdan, Jr5ec77132017-08-16 19:21:06 +0200833 deps, local_scope['deps_os'], target_os_list,
834 self._get_option('process_all_deps', False))
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200835
836 deps_to_add = self._deps_to_objects(
837 self._postprocess_deps(deps, rel_prefix), use_relative_paths)
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000838
839 # override named sets of hooks by the custom hooks
840 hooks_to_run = []
841 hook_names_to_suppress = [c.get('name', '') for c in self.custom_hooks]
842 for hook in local_scope.get('hooks', []):
843 if hook.get('name', '') not in hook_names_to_suppress:
844 hooks_to_run.append(hook)
Scott Grahamc4826742017-05-11 16:59:23 -0700845 if 'hooks_os' in local_scope and target_os_list:
846 hooks_os = local_scope['hooks_os']
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +0200847
848 # Keep original contents of hooks_os for flatten.
849 for hook_os, os_hooks in hooks_os.iteritems():
850 self._os_deps_hooks[hook_os] = [
Daniel Chenga0c5f082017-10-19 13:35:19 -0700851 Hook.from_dict(hook, variables=self.get_vars(), verbose=True)
Paweł Hajdan, Jrd3790252017-07-03 21:06:24 +0200852 for hook in os_hooks]
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +0200853
Scott Grahamc4826742017-05-11 16:59:23 -0700854 # Specifically append these to ensure that hooks_os run after hooks.
Paweł Hajdan, Jr357415c2017-07-24 14:35:28 +0200855 if not self._get_option('do_not_merge_os_specific_entries', False):
856 for the_target_os in target_os_list:
857 the_target_os_hooks = hooks_os.get(the_target_os, [])
858 hooks_to_run.extend(the_target_os_hooks)
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000859
860 # add the replacements and any additions
861 for hook in self.custom_hooks:
862 if 'action' in hook:
863 hooks_to_run.append(hook)
864
Dirk Prankeda3a29e2017-02-27 15:29:36 -0800865 if self.recursion_limit:
Paweł Hajdan, Jr032d5452017-06-22 20:43:53 +0200866 self._pre_deps_hooks = [
Daniel Chenga0c5f082017-10-19 13:35:19 -0700867 Hook.from_dict(hook, variables=self.get_vars(), verbose=True)
868 for hook in local_scope.get('pre_deps_hooks', [])
869 ]
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000870
Paweł Hajdan, Jr357415c2017-07-24 14:35:28 +0200871 self.add_dependencies_and_close(deps_to_add, hooks_to_run)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000872 logging.info('ParseDepsFile(%s) done' % self.name)
873
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +0200874 def _get_option(self, attr, default):
875 obj = self
876 while not hasattr(obj, '_options'):
877 obj = obj.parent
878 return getattr(obj._options, attr, default)
879
Paweł Hajdan, Jr357415c2017-07-24 14:35:28 +0200880 def add_dependencies_and_close(self, deps_to_add, hooks):
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000881 """Adds the dependencies, hooks and mark the parsing as done."""
maruel@chromium.orgb9be0652011-10-14 18:05:40 +0000882 for dep in deps_to_add:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000883 if dep.verify_validity():
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000884 self.add_dependency(dep)
Daniel Chenga0c5f082017-10-19 13:35:19 -0700885 self._mark_as_parsed([
886 Hook.from_dict(
887 h, variables=self.get_vars(), verbose=self.root._options.verbose)
888 for h in hooks
889 ])
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000890
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000891 def findDepsFromNotAllowedHosts(self):
892 """Returns a list of depenecies from not allowed hosts.
893
894 If allowed_hosts is not set, allows all hosts and returns empty list.
895 """
896 if not self._allowed_hosts:
897 return []
898 bad_deps = []
899 for dep in self._dependencies:
szager@chromium.orgbd772dd2014-11-05 18:43:08 +0000900 # Don't enforce this for custom_deps.
901 if dep.name in self._custom_deps:
902 continue
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000903 if isinstance(dep.url, basestring):
904 parsed_url = urlparse.urlparse(dep.url)
905 if parsed_url.netloc and parsed_url.netloc not in self._allowed_hosts:
906 bad_deps.append(dep)
907 return bad_deps
908
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +0000909 # Arguments number differs from overridden method
Quinten Yearsleyb2cc4a92016-12-15 13:53:26 -0800910 # pylint: disable=arguments-differ
maruel@chromium.org3742c842010-09-09 19:27:14 +0000911 def run(self, revision_overrides, command, args, work_queue, options):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000912 """Runs |command| then parse the DEPS file."""
maruel@chromium.org470b5432011-10-11 18:18:19 +0000913 logging.info('Dependency(%s).run()' % self.name)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000914 assert self._file_list == []
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000915 if not self.should_process:
916 return
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000917 # When running runhooks, there's no need to consult the SCM.
918 # All known hooks are expected to run unconditionally regardless of working
919 # copy state, so skip the SCM status check.
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +0200920 run_scm = command not in (
921 'flatten', 'runhooks', 'recurse', 'validate', None)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000922 parsed_url = self.LateOverride(self.url)
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000923 file_list = [] if not options.nohooks else None
szager@chromium.org3a3608d2014-10-22 21:13:52 +0000924 revision_override = revision_overrides.pop(self.name, None)
Dave Tubbda9712017-06-01 15:10:53 -0700925 if not revision_override and parsed_url:
926 revision_override = revision_overrides.get(parsed_url.split('@')[0], None)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000927 if run_scm and parsed_url:
agabled437d762016-10-17 09:35:11 -0700928 # Create a shallow copy to mutate revision.
929 options = copy.copy(options)
930 options.revision = revision_override
931 self._used_revision = options.revision
John Budorick0f7b2002018-01-19 15:46:17 -0800932 self._used_scm = self.CreateSCM(
agabled437d762016-10-17 09:35:11 -0700933 parsed_url, self.root.root_dir, self.name, self.outbuf,
934 out_cb=work_queue.out_cb)
935 self._got_revision = self._used_scm.RunCommand(command, options, args,
936 file_list)
937 if file_list:
938 file_list = [os.path.join(self.name, f.strip()) for f in file_list]
maruel@chromium.org68988972011-09-20 14:11:42 +0000939
940 # TODO(phajdan.jr): We should know exactly when the paths are absolute.
941 # Convert all absolute paths to relative.
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000942 for i in range(len(file_list or [])):
maruel@chromium.org68988972011-09-20 14:11:42 +0000943 # It depends on the command being executed (like runhooks vs sync).
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000944 if not os.path.isabs(file_list[i]):
maruel@chromium.org68988972011-09-20 14:11:42 +0000945 continue
946 prefix = os.path.commonprefix(
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000947 [self.root.root_dir.lower(), file_list[i].lower()])
948 file_list[i] = file_list[i][len(prefix):]
maruel@chromium.org68988972011-09-20 14:11:42 +0000949 # Strip any leading path separators.
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000950 while file_list[i].startswith(('\\', '/')):
951 file_list[i] = file_list[i][1:]
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000952
953 # Always parse the DEPS file.
954 self.ParseDepsFile()
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000955 self._run_is_done(file_list or [], parsed_url)
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000956 if command in ('update', 'revert') and not options.noprehooks:
957 self.RunPreDepsHooks()
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000958
959 if self.recursion_limit:
960 # Parse the dependencies of this dependency.
961 for s in self.dependencies:
Paweł Hajdan, Jr4baaa112017-07-04 19:09:32 +0200962 if s.should_process:
963 work_queue.enqueue(s)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000964
965 if command == 'recurse':
agabled437d762016-10-17 09:35:11 -0700966 # Skip file only checkout.
John Budorick0f7b2002018-01-19 15:46:17 -0800967 scm = self.GetScmName(parsed_url)
agabled437d762016-10-17 09:35:11 -0700968 if not options.scm or scm in options.scm:
969 cwd = os.path.normpath(os.path.join(self.root.root_dir, self.name))
970 # Pass in the SCM type as an env variable. Make sure we don't put
971 # unicode strings in the environment.
972 env = os.environ.copy()
973 if scm:
974 env['GCLIENT_SCM'] = str(scm)
975 if parsed_url:
976 env['GCLIENT_URL'] = str(parsed_url)
977 env['GCLIENT_DEP_PATH'] = str(self.name)
978 if options.prepend_dir and scm == 'git':
979 print_stdout = False
980 def filter_fn(line):
981 """Git-specific path marshaling. It is optimized for git-grep."""
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000982
agabled437d762016-10-17 09:35:11 -0700983 def mod_path(git_pathspec):
984 match = re.match('^(\\S+?:)?([^\0]+)$', git_pathspec)
985 modified_path = os.path.join(self.name, match.group(2))
986 branch = match.group(1) or ''
987 return '%s%s' % (branch, modified_path)
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000988
agabled437d762016-10-17 09:35:11 -0700989 match = re.match('^Binary file ([^\0]+) matches$', line)
990 if match:
991 print('Binary file %s matches\n' % mod_path(match.group(1)))
992 return
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000993
agabled437d762016-10-17 09:35:11 -0700994 items = line.split('\0')
995 if len(items) == 2 and items[1]:
996 print('%s : %s' % (mod_path(items[0]), items[1]))
997 elif len(items) >= 2:
998 # Multiple null bytes or a single trailing null byte indicate
999 # git is likely displaying filenames only (such as with -l)
1000 print('\n'.join(mod_path(path) for path in items if path))
1001 else:
1002 print(line)
1003 else:
1004 print_stdout = True
1005 filter_fn = None
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001006
agabled437d762016-10-17 09:35:11 -07001007 if parsed_url is None:
1008 print('Skipped omitted dependency %s' % cwd, file=sys.stderr)
1009 elif os.path.isdir(cwd):
1010 try:
1011 gclient_utils.CheckCallAndFilter(
1012 args, cwd=cwd, env=env, print_stdout=print_stdout,
1013 filter_fn=filter_fn,
1014 )
1015 except subprocess2.CalledProcessError:
1016 if not options.ignore:
1017 raise
1018 else:
1019 print('Skipped missing %s' % cwd, file=sys.stderr)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001020
John Budorick0f7b2002018-01-19 15:46:17 -08001021 def GetScmName(self, url):
1022 """Get the name of the SCM for the given URL.
1023
1024 While we currently support both git and cipd as SCM implementations,
1025 this currently cannot return 'cipd', regardless of the URL, as CIPD
1026 has no canonical URL format. If you want to use CIPD as an SCM, you
1027 must currently do so by explicitly using a CipdDependency.
1028 """
1029 if not url:
1030 return None
1031 url, _ = gclient_utils.SplitUrlRevision(url)
1032 if url.endswith('.git'):
1033 return 'git'
1034 protocol = url.split('://')[0]
1035 if protocol in (
1036 'file', 'git', 'git+http', 'git+https', 'http', 'https', 'ssh', 'sso'):
1037 return 'git'
1038 return None
1039
1040 def CreateSCM(self, url, root_dir=None, relpath=None, out_fh=None,
1041 out_cb=None):
1042 SCM_MAP = {
1043 'cipd': gclient_scm.CipdWrapper,
1044 'git': gclient_scm.GitWrapper,
1045 }
1046
1047 scm_name = self.GetScmName(url)
1048 if not scm_name in SCM_MAP:
1049 raise gclient_utils.Error('No SCM found for url %s' % url)
1050 scm_class = SCM_MAP[scm_name]
1051 if not scm_class.BinaryExists():
1052 raise gclient_utils.Error('%s command not found' % scm_name)
Edward Lemur231f5ea2018-01-31 19:02:36 +01001053 return scm_class(url, root_dir, relpath, out_fh, out_cb, self.print_outbuf)
John Budorick0f7b2002018-01-19 15:46:17 -08001054
Dirk Pranke9f20d022017-10-11 18:36:54 -07001055 def HasGNArgsFile(self):
1056 return self._gn_args_file is not None
1057
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +02001058 def WriteGNArgsFile(self):
1059 lines = ['# Generated from %r' % self.deps_file]
Paweł Hajdan, Jrb495bf52017-09-25 19:33:50 +02001060 variables = self.get_vars()
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +02001061 for arg in self._gn_args:
Paweł Hajdan, Jre0214742017-09-28 12:21:01 +02001062 value = variables[arg]
1063 if isinstance(value, basestring):
1064 value = gclient_eval.EvaluateCondition(value, variables)
Paweł Hajdan, Jrb495bf52017-09-25 19:33:50 +02001065 lines.append('%s = %s' % (arg, ToGNString(value)))
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +02001066 with open(os.path.join(self.root.root_dir, self._gn_args_file), 'w') as f:
1067 f.write('\n'.join(lines))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001068
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001069 @gclient_utils.lockedmethod
1070 def _run_is_done(self, file_list, parsed_url):
1071 # Both these are kept for hooks that are run as a separate tree traversal.
1072 self._file_list = file_list
1073 self._parsed_url = parsed_url
1074 self._processed = True
1075
szager@google.comb9a78d32012-03-13 18:46:21 +00001076 def GetHooks(self, options):
1077 """Evaluates all hooks, and return them in a flat list.
1078
1079 RunOnDeps() must have been called before to load the DEPS.
1080 """
1081 result = []
maruel@chromium.org68988972011-09-20 14:11:42 +00001082 if not self.should_process or not self.recursion_limit:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001083 # Don't run the hook when it is above recursion_limit.
szager@google.comb9a78d32012-03-13 18:46:21 +00001084 return result
maruel@chromium.orgdc7445d2010-07-09 21:05:29 +00001085 # If "--force" was specified, run all hooks regardless of what files have
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001086 # changed.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001087 if self.deps_hooks:
agabled437d762016-10-17 09:35:11 -07001088 # TODO(maruel): If the user is using git, then we don't know
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001089 # what files have changed so we always run all hooks. It'd be nice to fix
1090 # that.
1091 if (options.force or
John Budorick0f7b2002018-01-19 15:46:17 -08001092 self.GetScmName(self.parsed_url) in ('git', None) or
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001093 os.path.isdir(os.path.join(self.root.root_dir, self.name, '.git'))):
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +02001094 result.extend(self.deps_hooks)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001095 else:
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +02001096 for hook in self.deps_hooks:
1097 if hook.matches(self.file_list_and_children):
1098 result.append(hook)
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001099 for s in self.dependencies:
szager@google.comb9a78d32012-03-13 18:46:21 +00001100 result.extend(s.GetHooks(options))
1101 return result
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001102
Dirk Pranke9f20d022017-10-11 18:36:54 -07001103 def WriteGNArgsFilesRecursively(self, dependencies):
1104 for dep in dependencies:
1105 if dep.HasGNArgsFile():
1106 dep.WriteGNArgsFile()
1107 self.WriteGNArgsFilesRecursively(dep.dependencies)
1108
Daniel Chenga0c5f082017-10-19 13:35:19 -07001109 def RunHooksRecursively(self, options, progress):
szager@google.comb9a78d32012-03-13 18:46:21 +00001110 assert self.hooks_ran == False
maruel@chromium.org064186c2011-09-27 23:53:33 +00001111 self._hooks_ran = True
Daniel Chenga0c5f082017-10-19 13:35:19 -07001112 hooks = self.GetHooks(options)
1113 if progress:
1114 progress._total = len(hooks)
1115 for hook in hooks:
Daniel Chenga0c5f082017-10-19 13:35:19 -07001116 if progress:
1117 progress.update(extra=hook.name or '')
Daniel Cheng93c5d602017-10-20 11:40:17 -07001118 hook.run(self.root.root_dir)
Daniel Chenga0c5f082017-10-19 13:35:19 -07001119 if progress:
1120 progress.end()
maruel@chromium.orgeaf61062010-07-07 18:42:39 +00001121
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001122 def RunPreDepsHooks(self):
1123 assert self.processed
1124 assert self.deps_parsed
1125 assert not self.pre_deps_hooks_ran
1126 assert not self.hooks_ran
1127 for s in self.dependencies:
1128 assert not s.processed
1129 self._pre_deps_hooks_ran = True
1130 for hook in self.pre_deps_hooks:
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +02001131 hook.run(self.root.root_dir)
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001132
maruel@chromium.org0d812442010-08-10 12:41:08 +00001133 def subtree(self, include_all):
maruel@chromium.orgad3287e2011-10-03 19:15:10 +00001134 """Breadth first recursion excluding root node."""
maruel@chromium.orgf13a4182011-09-22 00:26:15 +00001135 dependencies = self.dependencies
1136 for d in dependencies:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001137 if d.should_process or include_all:
maruel@chromium.orgad3287e2011-10-03 19:15:10 +00001138 yield d
maruel@chromium.orgf13a4182011-09-22 00:26:15 +00001139 for d in dependencies:
maruel@chromium.orgad3287e2011-10-03 19:15:10 +00001140 for i in d.subtree(include_all):
1141 yield i
1142
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001143 @gclient_utils.lockedmethod
1144 def add_dependency(self, new_dep):
1145 self._dependencies.append(new_dep)
1146
1147 @gclient_utils.lockedmethod
Paweł Hajdan, Jr357415c2017-07-24 14:35:28 +02001148 def _mark_as_parsed(self, new_hooks):
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001149 self._deps_hooks.extend(new_hooks)
1150 self._deps_parsed = True
1151
maruel@chromium.org68988972011-09-20 14:11:42 +00001152 @property
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001153 @gclient_utils.lockedmethod
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +00001154 def dependencies(self):
1155 return tuple(self._dependencies)
1156
1157 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001158 @gclient_utils.lockedmethod
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02001159 def os_dependencies(self):
1160 return dict(self._os_dependencies)
1161
1162 @property
1163 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001164 def deps_hooks(self):
1165 return tuple(self._deps_hooks)
1166
1167 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001168 @gclient_utils.lockedmethod
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +02001169 def os_deps_hooks(self):
1170 return dict(self._os_deps_hooks)
1171
1172 @property
1173 @gclient_utils.lockedmethod
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001174 def pre_deps_hooks(self):
1175 return tuple(self._pre_deps_hooks)
1176
1177 @property
1178 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001179 def parsed_url(self):
1180 return self._parsed_url
1181
1182 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001183 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001184 def deps_parsed(self):
maruel@chromium.org3223edd2011-10-10 23:17:39 +00001185 """This is purely for debugging purposes. It's not used anywhere."""
maruel@chromium.org064186c2011-09-27 23:53:33 +00001186 return self._deps_parsed
1187
1188 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001189 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001190 def processed(self):
1191 return self._processed
1192
1193 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001194 @gclient_utils.lockedmethod
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001195 def pre_deps_hooks_ran(self):
1196 return self._pre_deps_hooks_ran
1197
1198 @property
1199 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001200 def hooks_ran(self):
1201 return self._hooks_ran
1202
1203 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001204 @gclient_utils.lockedmethod
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00001205 def allowed_hosts(self):
1206 return self._allowed_hosts
1207
1208 @property
1209 @gclient_utils.lockedmethod
maruel@chromium.org861fd0f2010-07-23 03:05:05 +00001210 def file_list(self):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001211 return tuple(self._file_list)
1212
1213 @property
kustermann@google.coma692e8f2013-04-18 08:32:04 +00001214 def used_scm(self):
1215 """SCMWrapper instance for this dependency or None if not processed yet."""
1216 return self._used_scm
1217
1218 @property
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00001219 @gclient_utils.lockedmethod
1220 def got_revision(self):
1221 return self._got_revision
1222
1223 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001224 def file_list_and_children(self):
1225 result = list(self.file_list)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +00001226 for d in self.dependencies:
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001227 result.extend(d.file_list_and_children)
maruel@chromium.org68988972011-09-20 14:11:42 +00001228 return tuple(result)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +00001229
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001230 def __str__(self):
1231 out = []
agablea98a6cd2016-11-15 14:30:10 -08001232 for i in ('name', 'url', 'parsed_url', 'custom_deps',
maruel@chromium.org3c74bc92011-09-15 19:17:21 +00001233 'custom_vars', 'deps_hooks', 'file_list', 'should_process',
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00001234 'processed', 'hooks_ran', 'deps_parsed', 'requirements',
1235 'allowed_hosts'):
maruel@chromium.org3c74bc92011-09-15 19:17:21 +00001236 # First try the native property if it exists.
1237 if hasattr(self, '_' + i):
1238 value = getattr(self, '_' + i, False)
1239 else:
1240 value = getattr(self, i, False)
1241 if value:
1242 out.append('%s: %s' % (i, value))
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001243
1244 for d in self.dependencies:
1245 out.extend([' ' + x for x in str(d).splitlines()])
1246 out.append('')
1247 return '\n'.join(out)
1248
1249 def __repr__(self):
1250 return '%s: %s' % (self.name, self.url)
1251
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001252 def hierarchy(self, include_url=True):
maruel@chromium.orgbc2d2f92010-07-22 21:26:48 +00001253 """Returns a human-readable hierarchical reference to a Dependency."""
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001254 def format_name(d):
1255 if include_url:
1256 return '%s(%s)' % (d.name, d.url)
1257 return d.name
1258 out = format_name(self)
maruel@chromium.orgbffb9042010-07-22 20:59:36 +00001259 i = self.parent
1260 while i and i.name:
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001261 out = '%s -> %s' % (format_name(i), out)
maruel@chromium.orgbffb9042010-07-22 20:59:36 +00001262 i = i.parent
1263 return out
1264
Paweł Hajdan, Jrd3790252017-07-03 21:06:24 +02001265 def get_vars(self):
1266 """Returns a dictionary of effective variable values
1267 (DEPS file contents with applied custom_vars overrides)."""
Paweł Hajdan, Jra3b67ae2017-08-30 15:18:21 +02001268 # Provide some built-in variables.
1269 result = {
Paweł Hajdan, Jrd325eb32017-10-03 17:43:37 +02001270 'checkout_android': 'android' in self.target_os,
Benjamin Pastene6fe29412018-01-23 15:35:58 -08001271 'checkout_chromeos': 'chromeos' in self.target_os,
Paweł Hajdan, Jrd325eb32017-10-03 17:43:37 +02001272 'checkout_fuchsia': 'fuchsia' in self.target_os,
1273 'checkout_ios': 'ios' in self.target_os,
1274 'checkout_linux': 'unix' in self.target_os,
1275 'checkout_mac': 'mac' in self.target_os,
1276 'checkout_win': 'win' in self.target_os,
1277 'host_os': _detect_host_os(),
Paweł Hajdan, Jra3b67ae2017-08-30 15:18:21 +02001278 }
1279 # Variables defined in DEPS file override built-in ones.
1280 result.update(self._vars)
Paweł Hajdan, Jrd3790252017-07-03 21:06:24 +02001281 result.update(self.custom_vars or {})
1282 return result
1283
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001284
Paweł Hajdan, Jra3b67ae2017-08-30 15:18:21 +02001285_PLATFORM_MAPPING = {
1286 'cygwin': 'win',
1287 'darwin': 'mac',
1288 'linux2': 'linux',
1289 'win32': 'win',
Jaideep Bajwad05f3582017-09-11 12:31:48 -04001290 'aix6': 'aix',
Paweł Hajdan, Jra3b67ae2017-08-30 15:18:21 +02001291}
1292
1293
1294def _detect_host_os():
1295 return _PLATFORM_MAPPING[sys.platform]
1296
1297
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001298class GClient(Dependency):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001299 """Object that represent a gclient checkout. A tree of Dependency(), one per
1300 solution or DEPS entry."""
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001301
1302 DEPS_OS_CHOICES = {
Jaideep Bajwad05f3582017-09-11 12:31:48 -04001303 "aix6": "unix",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001304 "win32": "win",
1305 "win": "win",
1306 "cygwin": "win",
1307 "darwin": "mac",
1308 "mac": "mac",
1309 "unix": "unix",
1310 "linux": "unix",
1311 "linux2": "unix",
maruel@chromium.org244e3442011-06-12 15:20:55 +00001312 "linux3": "unix",
szager@chromium.orgf8c95cd2012-06-01 22:26:52 +00001313 "android": "android",
Michael Mossc54fa812017-08-17 11:27:58 -07001314 "ios": "ios",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001315 }
1316
1317 DEFAULT_CLIENT_FILE_TEXT = ("""\
1318solutions = [
smutae7ea312016-07-18 11:59:41 -07001319 { "name" : "%(solution_name)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001320 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +00001321 "deps_file" : "%(deps_file)s",
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001322 "managed" : %(managed)s,
smutae7ea312016-07-18 11:59:41 -07001323 "custom_deps" : {
1324 },
Paweł Hajdan, Jr3ba2a7c2017-10-04 19:24:46 +02001325 "custom_vars": %(custom_vars)r,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001326 },
1327]
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001328cache_dir = %(cache_dir)r
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001329""")
1330
1331 DEFAULT_SNAPSHOT_SOLUTION_TEXT = ("""\
smutae7ea312016-07-18 11:59:41 -07001332 { "name" : "%(solution_name)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001333 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +00001334 "deps_file" : "%(deps_file)s",
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001335 "managed" : %(managed)s,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001336 "custom_deps" : {
smutae7ea312016-07-18 11:59:41 -07001337%(solution_deps)s },
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001338 },
1339""")
1340
1341 DEFAULT_SNAPSHOT_FILE_TEXT = ("""\
1342# Snapshot generated with gclient revinfo --snapshot
1343solutions = [
maruel@chromium.org73e21142010-07-05 13:32:01 +00001344%(solution_list)s]
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001345""")
1346
1347 def __init__(self, root_dir, options):
maruel@chromium.org0d812442010-08-10 12:41:08 +00001348 # Do not change previous behavior. Only solution level and immediate DEPS
1349 # are processed.
1350 self._recursion_limit = 2
Paweł Hajdan, Jrfc6196b2017-07-27 13:15:25 +02001351 Dependency.__init__(self, None, None, None, None, True, None, None, None,
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +02001352 'unused', True, None, None, True)
maruel@chromium.org0d425922010-06-21 19:22:24 +00001353 self._options = options
maruel@chromium.org271375b2010-06-23 19:17:38 +00001354 if options.deps_os:
1355 enforced_os = options.deps_os.split(',')
1356 else:
1357 enforced_os = [self.DEPS_OS_CHOICES.get(sys.platform, 'unix')]
1358 if 'all' in enforced_os:
1359 enforced_os = self.DEPS_OS_CHOICES.itervalues()
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001360 self._enforced_os = tuple(set(enforced_os))
maruel@chromium.org271375b2010-06-23 19:17:38 +00001361 self._root_dir = root_dir
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001362 self.config_content = None
1363
borenet@google.com88d10082014-03-21 17:24:48 +00001364 def _CheckConfig(self):
1365 """Verify that the config matches the state of the existing checked-out
1366 solutions."""
1367 for dep in self.dependencies:
1368 if dep.managed and dep.url:
John Budorick0f7b2002018-01-19 15:46:17 -08001369 scm = self.CreateSCM(
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001370 dep.url, self.root_dir, dep.name, self.outbuf)
smut@google.comd33eab32014-07-07 19:35:18 +00001371 actual_url = scm.GetActualRemoteURL(self._options)
borenet@google.com4e9be262014-04-08 19:40:30 +00001372 if actual_url and not scm.DoesRemoteURLMatch(self._options):
levarum@chromium.org27a6f9a2016-05-28 00:21:49 +00001373 mirror = scm.GetCacheMirror()
1374 if mirror:
1375 mirror_string = '%s (exists=%s)' % (mirror.mirror_path,
1376 mirror.exists())
1377 else:
1378 mirror_string = 'not used'
borenet@google.com0a427372014-04-02 19:12:13 +00001379 raise gclient_utils.Error('''
borenet@google.com88d10082014-03-21 17:24:48 +00001380Your .gclient file seems to be broken. The requested URL is different from what
borenet@google.com0a427372014-04-02 19:12:13 +00001381is actually checked out in %(checkout_path)s.
borenet@google.com88d10082014-03-21 17:24:48 +00001382
borenet@google.com97882362014-04-07 20:06:02 +00001383The .gclient file contains:
levarum@chromium.org27a6f9a2016-05-28 00:21:49 +00001384URL: %(expected_url)s (%(expected_scm)s)
1385Cache mirror: %(mirror_string)s
borenet@google.com97882362014-04-07 20:06:02 +00001386
1387The local checkout in %(checkout_path)s reports:
1388%(actual_url)s (%(actual_scm)s)
borenet@google.com88d10082014-03-21 17:24:48 +00001389
1390You should ensure that the URL listed in .gclient is correct and either change
agabled437d762016-10-17 09:35:11 -07001391it or fix the checkout.
borenet@google.com88d10082014-03-21 17:24:48 +00001392''' % {'checkout_path': os.path.join(self.root_dir, dep.name),
1393 'expected_url': dep.url,
John Budorick0f7b2002018-01-19 15:46:17 -08001394 'expected_scm': self.GetScmName(dep.url),
levarum@chromium.org27a6f9a2016-05-28 00:21:49 +00001395 'mirror_string' : mirror_string,
borenet@google.com88d10082014-03-21 17:24:48 +00001396 'actual_url': actual_url,
John Budorick0f7b2002018-01-19 15:46:17 -08001397 'actual_scm': self.GetScmName(actual_url)})
borenet@google.com88d10082014-03-21 17:24:48 +00001398
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001399 def SetConfig(self, content):
maruel@chromium.orgf13a4182011-09-22 00:26:15 +00001400 assert not self.dependencies
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001401 config_dict = {}
1402 self.config_content = content
1403 try:
1404 exec(content, config_dict)
vapier@chromium.orga81a56e2015-11-11 07:56:13 +00001405 except SyntaxError as e:
maruel@chromium.org5990f9d2010-07-07 18:02:58 +00001406 gclient_utils.SyntaxErrorToError('.gclient', e)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001407
peter@chromium.org1efccc82012-04-27 16:34:38 +00001408 # Append any target OS that is not already being enforced to the tuple.
1409 target_os = config_dict.get('target_os', [])
stuartmorgan@chromium.org18a4f6a2012-11-02 13:37:24 +00001410 if config_dict.get('target_os_only', False):
1411 self._enforced_os = tuple(set(target_os))
1412 else:
1413 self._enforced_os = tuple(set(self._enforced_os).union(target_os))
1414
Aleksandr Derbenev9e8fb0e2017-08-01 20:18:31 +03001415 cache_dir = config_dict.get('cache_dir', self._options.cache_dir)
dyen@chromium.orgd915cca2014-08-07 21:41:37 +00001416 if cache_dir:
1417 cache_dir = os.path.join(self.root_dir, cache_dir)
1418 cache_dir = os.path.abspath(cache_dir)
Andrii Shyshkalov77ce4bd2017-11-27 12:38:18 -08001419
dyen@chromium.orgd915cca2014-08-07 21:41:37 +00001420 gclient_scm.GitWrapper.cache_dir = cache_dir
1421 git_cache.Mirror.SetCachePath(cache_dir)
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001422
stuartmorgan@chromium.org18a4f6a2012-11-02 13:37:24 +00001423 if not target_os and config_dict.get('target_os_only', False):
1424 raise gclient_utils.Error('Can\'t use target_os_only if target_os is '
1425 'not specified')
peter@chromium.org1efccc82012-04-27 16:34:38 +00001426
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001427 deps_to_add = []
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001428 for s in config_dict.get('solutions', []):
maruel@chromium.org81843b82010-06-28 16:49:26 +00001429 try:
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001430 deps_to_add.append(Dependency(
Paweł Hajdan, Jrfc6196b2017-07-27 13:15:25 +02001431 self, s['name'], s['url'], s['url'],
smutae7ea312016-07-18 11:59:41 -07001432 s.get('managed', True),
maruel@chromium.org81843b82010-06-28 16:49:26 +00001433 s.get('custom_deps', {}),
maruel@chromium.org0d812442010-08-10 12:41:08 +00001434 s.get('custom_vars', {}),
petermayo@chromium.orge79161a2013-07-09 14:40:37 +00001435 s.get('custom_hooks', []),
nsylvain@google.comefc80932011-05-31 21:27:56 +00001436 s.get('deps_file', 'DEPS'),
agabledce6ddc2016-09-08 10:02:16 -07001437 True,
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +02001438 None,
1439 None,
Edward Lemur231f5ea2018-01-31 19:02:36 +01001440 True,
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +02001441 True))
maruel@chromium.org81843b82010-06-28 16:49:26 +00001442 except KeyError:
1443 raise gclient_utils.Error('Invalid .gclient file. Solution is '
1444 'incomplete: %s' % s)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001445 self.add_dependencies_and_close(deps_to_add, config_dict.get('hooks', []))
1446 logging.info('SetConfig() done')
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001447
1448 def SaveConfig(self):
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001449 gclient_utils.FileWrite(os.path.join(self.root_dir,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001450 self._options.config_filename),
1451 self.config_content)
1452
1453 @staticmethod
1454 def LoadCurrentConfig(options):
1455 """Searches for and loads a .gclient file relative to the current working
1456 dir. Returns a GClient object."""
szager@chromium.orge2e03202012-07-31 18:05:16 +00001457 if options.spec:
1458 client = GClient('.', options)
1459 client.SetConfig(options.spec)
1460 else:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00001461 if options.verbose:
1462 print('Looking for %s starting from %s\n' % (
1463 options.config_filename, os.getcwd()))
szager@chromium.orge2e03202012-07-31 18:05:16 +00001464 path = gclient_utils.FindGclientRoot(os.getcwd(), options.config_filename)
1465 if not path:
Michael Achenbachb3ce73d2017-10-11 16:41:27 +02001466 if options.verbose:
1467 print('Couldn\'t find configuration file.')
szager@chromium.orge2e03202012-07-31 18:05:16 +00001468 return None
1469 client = GClient(path, options)
1470 client.SetConfig(gclient_utils.FileRead(
1471 os.path.join(path, options.config_filename)))
maruel@chromium.org69392e72011-10-13 22:09:00 +00001472
1473 if (options.revisions and
1474 len(client.dependencies) > 1 and
1475 any('@' not in r for r in options.revisions)):
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001476 print(
1477 ('You must specify the full solution name like --revision %s@%s\n'
1478 'when you have multiple solutions setup in your .gclient file.\n'
1479 'Other solutions present are: %s.') % (
maruel@chromium.org69392e72011-10-13 22:09:00 +00001480 client.dependencies[0].name,
1481 options.revisions[0],
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001482 ', '.join(s.name for s in client.dependencies[1:])),
1483 file=sys.stderr)
maruel@chromium.org15804092010-09-02 17:07:37 +00001484 return client
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001485
nsylvain@google.comefc80932011-05-31 21:27:56 +00001486 def SetDefaultConfig(self, solution_name, deps_file, solution_url,
Paweł Hajdan, Jr3ba2a7c2017-10-04 19:24:46 +02001487 managed=True, cache_dir=None, custom_vars=None):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001488 self.SetConfig(self.DEFAULT_CLIENT_FILE_TEXT % {
1489 'solution_name': solution_name,
1490 'solution_url': solution_url,
nsylvain@google.comefc80932011-05-31 21:27:56 +00001491 'deps_file': deps_file,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001492 'managed': managed,
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001493 'cache_dir': cache_dir,
Paweł Hajdan, Jr3ba2a7c2017-10-04 19:24:46 +02001494 'custom_vars': custom_vars or {},
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001495 })
1496
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001497 def _SaveEntries(self):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001498 """Creates a .gclient_entries file to record the list of unique checkouts.
1499
1500 The .gclient_entries file lives in the same directory as .gclient.
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001501 """
1502 # Sometimes pprint.pformat will use {', sometimes it'll use { ' ... It
1503 # makes testing a bit too fun.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001504 result = 'entries = {\n'
maruel@chromium.org68988972011-09-20 14:11:42 +00001505 for entry in self.root.subtree(False):
agabled437d762016-10-17 09:35:11 -07001506 result += ' %s: %s,\n' % (pprint.pformat(entry.name),
1507 pprint.pformat(entry.parsed_url))
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001508 result += '}\n'
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001509 file_path = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org1333cb32011-10-04 23:40:16 +00001510 logging.debug(result)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001511 gclient_utils.FileWrite(file_path, result)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001512
1513 def _ReadEntries(self):
1514 """Read the .gclient_entries file for the given client.
1515
1516 Returns:
1517 A sequence of solution names, which will be empty if there is the
1518 entries file hasn't been created yet.
1519 """
1520 scope = {}
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001521 filename = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001522 if not os.path.exists(filename):
maruel@chromium.org73e21142010-07-05 13:32:01 +00001523 return {}
maruel@chromium.org5990f9d2010-07-07 18:02:58 +00001524 try:
1525 exec(gclient_utils.FileRead(filename), scope)
vapier@chromium.orga81a56e2015-11-11 07:56:13 +00001526 except SyntaxError as e:
maruel@chromium.org5990f9d2010-07-07 18:02:58 +00001527 gclient_utils.SyntaxErrorToError(filename, e)
Aaron Gable3721ee92017-04-03 14:53:14 -07001528 return scope.get('entries', {})
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001529
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001530 def _EnforceRevisions(self):
maruel@chromium.org918a9ae2010-05-28 15:50:30 +00001531 """Checks for revision overrides."""
1532 revision_overrides = {}
smutae7ea312016-07-18 11:59:41 -07001533 if self._options.head:
1534 return revision_overrides
joi@chromium.org792ea882010-11-10 02:37:27 +00001535 if not self._options.revisions:
1536 for s in self.dependencies:
smutae7ea312016-07-18 11:59:41 -07001537 if not s.managed:
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001538 self._options.revisions.append('%s@unmanaged' % s.name)
maruel@chromium.org307d1792010-05-31 20:03:13 +00001539 if not self._options.revisions:
1540 return revision_overrides
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001541 solutions_names = [s.name for s in self.dependencies]
smutae7ea312016-07-18 11:59:41 -07001542 index = 0
1543 for revision in self._options.revisions:
1544 if not '@' in revision:
maruel@chromium.org307d1792010-05-31 20:03:13 +00001545 # Support for --revision 123
smutae7ea312016-07-18 11:59:41 -07001546 revision = '%s@%s' % (solutions_names[index], revision)
1547 name, rev = revision.split('@', 1)
szager@chromium.org4ad264b2014-05-20 04:43:47 +00001548 revision_overrides[name] = rev
smutae7ea312016-07-18 11:59:41 -07001549 index += 1
maruel@chromium.org918a9ae2010-05-28 15:50:30 +00001550 return revision_overrides
1551
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001552 def RunOnDeps(self, command, args, ignore_requirements=False, progress=True):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001553 """Runs a command on each dependency in a client and its dependencies.
1554
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001555 Args:
1556 command: The command to use (e.g., 'status' or 'diff')
1557 args: list of str - extra arguments to add to the command line.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001558 """
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001559 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +00001560 raise gclient_utils.Error('No solution specified')
borenet@google.com0a427372014-04-02 19:12:13 +00001561
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001562 revision_overrides = {}
1563 # It's unnecessary to check for revision overrides for 'recurse'.
1564 # Save a few seconds by not calling _EnforceRevisions() in that case.
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +02001565 if command not in ('diff', 'recurse', 'runhooks', 'status', 'revert',
1566 'validate'):
szager@chromium.org5273b8a2014-08-21 15:10:10 +00001567 self._CheckConfig()
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001568 revision_overrides = self._EnforceRevisions()
Daniel Chenga21b5b32017-10-19 20:07:48 +00001569 # Disable progress for non-tty stdout.
Daniel Chenga0c5f082017-10-19 13:35:19 -07001570 should_show_progress = (
1571 setup_color.IS_TTY and not self._options.verbose and progress)
1572 pm = None
1573 if should_show_progress:
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001574 if command in ('update', 'revert'):
1575 pm = Progress('Syncing projects', 1)
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +02001576 elif command in ('recurse', 'validate'):
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001577 pm = Progress(' '.join(args), 1)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001578 work_queue = gclient_utils.ExecutionQueue(
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001579 self._options.jobs, pm, ignore_requirements=ignore_requirements,
1580 verbose=self._options.verbose)
maruel@chromium.org049bced2010-08-12 13:37:20 +00001581 for s in self.dependencies:
Paweł Hajdan, Jr4baaa112017-07-04 19:09:32 +02001582 if s.should_process:
1583 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +00001584 work_queue.flush(revision_overrides, command, args, options=self._options)
szager@chromium.org4ad264b2014-05-20 04:43:47 +00001585 if revision_overrides:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001586 print('Please fix your script, having invalid --revision flags will soon '
1587 'considered an error.', file=sys.stderr)
piman@chromium.org6f363722010-04-27 00:41:09 +00001588
Dirk Pranke9f20d022017-10-11 18:36:54 -07001589 # Once all the dependencies have been processed, it's now safe to write
1590 # out any gn_args_files and run the hooks.
1591 if command == 'update':
1592 self.WriteGNArgsFilesRecursively(self.dependencies)
1593
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001594 if not self._options.nohooks:
Daniel Chenga0c5f082017-10-19 13:35:19 -07001595 if should_show_progress:
1596 pm = Progress('Running hooks', 1)
1597 self.RunHooksRecursively(self._options, pm)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001598
1599 if command == 'update':
ajwong@chromium.orgcdcee802009-06-23 15:30:42 +00001600 # Notify the user if there is an orphaned entry in their working copy.
1601 # Only delete the directory if there are no changes in it, and
1602 # delete_unversioned_trees is set to true.
maruel@chromium.org68988972011-09-20 14:11:42 +00001603 entries = [i.name for i in self.root.subtree(False) if i.url]
xusydoc@chromium.org885a9602013-05-31 09:54:40 +00001604 full_entries = [os.path.join(self.root_dir, e.replace('/', os.path.sep))
1605 for e in entries]
1606
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001607 for entry, prev_url in self._ReadEntries().iteritems():
maruel@chromium.org04dd7de2010-10-14 13:25:49 +00001608 if not prev_url:
1609 # entry must have been overridden via .gclient custom_deps
1610 continue
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001611 # Fix path separator on Windows.
1612 entry_fixed = entry.replace('/', os.path.sep)
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001613 e_dir = os.path.join(self.root_dir, entry_fixed)
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001614 # Use entry and not entry_fixed there.
jochen@chromium.orga78e5532013-03-11 13:33:03 +00001615 if (entry not in entries and
1616 (not any(path.startswith(entry + '/') for path in entries)) and
jochen@chromium.orgcc475722013-03-11 13:07:40 +00001617 os.path.exists(e_dir)):
primiano@chromium.org1c127382015-02-17 11:15:40 +00001618 # The entry has been removed from DEPS.
John Budorick0f7b2002018-01-19 15:46:17 -08001619 scm = self.CreateSCM(
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001620 prev_url, self.root_dir, entry_fixed, self.outbuf)
xusydoc@chromium.org885a9602013-05-31 09:54:40 +00001621
1622 # Check to see if this directory is now part of a higher-up checkout.
borenet@google.com359bb642014-05-13 17:28:19 +00001623 scm_root = None
agabled437d762016-10-17 09:35:11 -07001624 try:
1625 scm_root = gclient_scm.scm.GIT.GetCheckoutRoot(scm.checkout_path)
1626 except subprocess2.CalledProcessError:
1627 pass
1628 if not scm_root:
borenet@google.com359bb642014-05-13 17:28:19 +00001629 logging.warning('Could not find checkout root for %s. Unable to '
1630 'determine whether it is part of a higher-level '
1631 'checkout, so not removing.' % entry)
1632 continue
primiano@chromium.org1c127382015-02-17 11:15:40 +00001633
1634 # This is to handle the case of third_party/WebKit migrating from
1635 # being a DEPS entry to being part of the main project.
1636 # If the subproject is a Git project, we need to remove its .git
1637 # folder. Otherwise git operations on that folder will have different
1638 # effects depending on the current working directory.
agabled437d762016-10-17 09:35:11 -07001639 if os.path.abspath(scm_root) == os.path.abspath(e_dir):
primiano@chromium.org1c127382015-02-17 11:15:40 +00001640 e_par_dir = os.path.join(e_dir, os.pardir)
agabled437d762016-10-17 09:35:11 -07001641 if gclient_scm.scm.GIT.IsInsideWorkTree(e_par_dir):
1642 par_scm_root = gclient_scm.scm.GIT.GetCheckoutRoot(e_par_dir)
primiano@chromium.org1c127382015-02-17 11:15:40 +00001643 # rel_e_dir : relative path of entry w.r.t. its parent repo.
1644 rel_e_dir = os.path.relpath(e_dir, par_scm_root)
agabled437d762016-10-17 09:35:11 -07001645 if gclient_scm.scm.GIT.IsDirectoryVersioned(
1646 par_scm_root, rel_e_dir):
primiano@chromium.org1c127382015-02-17 11:15:40 +00001647 save_dir = scm.GetGitBackupDirPath()
1648 # Remove any eventual stale backup dir for the same project.
1649 if os.path.exists(save_dir):
1650 gclient_utils.rmtree(save_dir)
1651 os.rename(os.path.join(e_dir, '.git'), save_dir)
1652 # When switching between the two states (entry/ is a subproject
1653 # -> entry/ is part of the outer project), it is very likely
1654 # that some files are changed in the checkout, unless we are
1655 # jumping *exactly* across the commit which changed just DEPS.
1656 # In such case we want to cleanup any eventual stale files
1657 # (coming from the old subproject) in order to end up with a
1658 # clean checkout.
agabled437d762016-10-17 09:35:11 -07001659 gclient_scm.scm.GIT.CleanupDir(par_scm_root, rel_e_dir)
primiano@chromium.org1c127382015-02-17 11:15:40 +00001660 assert not os.path.exists(os.path.join(e_dir, '.git'))
1661 print(('\nWARNING: \'%s\' has been moved from DEPS to a higher '
1662 'level checkout. The git folder containing all the local'
1663 ' branches has been saved to %s.\n'
1664 'If you don\'t care about its state you can safely '
1665 'remove that folder to free up space.') %
1666 (entry, save_dir))
1667 continue
1668
borenet@google.com359bb642014-05-13 17:28:19 +00001669 if scm_root in full_entries:
primiano@chromium.org1c127382015-02-17 11:15:40 +00001670 logging.info('%s is part of a higher level checkout, not removing',
1671 scm.GetCheckoutRoot())
xusydoc@chromium.org885a9602013-05-31 09:54:40 +00001672 continue
1673
1674 file_list = []
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001675 scm.status(self._options, [], file_list)
1676 modified_files = file_list != []
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001677 if (not self._options.delete_unversioned_trees or
1678 (modified_files and not self._options.force)):
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001679 # There are modified files in this entry. Keep warning until
1680 # removed.
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001681 print(('\nWARNING: \'%s\' is no longer part of this client. '
1682 'It is recommended that you manually remove it.\n') %
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001683 entry_fixed)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001684 else:
1685 # Delete the entry
maruel@chromium.org73e21142010-07-05 13:32:01 +00001686 print('\n________ deleting \'%s\' in \'%s\'' % (
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001687 entry_fixed, self.root_dir))
digit@chromium.orgdc112ac2013-04-24 13:00:19 +00001688 gclient_utils.rmtree(e_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001689 # record the current list of entries for next time
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001690 self._SaveEntries()
maruel@chromium.org17cdf762010-05-28 17:30:52 +00001691 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001692
1693 def PrintRevInfo(self):
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001694 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +00001695 raise gclient_utils.Error('No solution specified')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001696 # Load all the settings.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001697 work_queue = gclient_utils.ExecutionQueue(
1698 self._options.jobs, None, False, verbose=self._options.verbose)
maruel@chromium.org049bced2010-08-12 13:37:20 +00001699 for s in self.dependencies:
Paweł Hajdan, Jr4baaa112017-07-04 19:09:32 +02001700 if s.should_process:
1701 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +00001702 work_queue.flush({}, None, [], options=self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001703
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001704 def GetURLAndRev(dep):
1705 """Returns the revision-qualified SCM url for a Dependency."""
1706 if dep.parsed_url is None:
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001707 return None
agabled437d762016-10-17 09:35:11 -07001708 url, _ = gclient_utils.SplitUrlRevision(dep.parsed_url)
John Budorick0f7b2002018-01-19 15:46:17 -08001709 scm = dep.CreateSCM(
agabled437d762016-10-17 09:35:11 -07001710 dep.parsed_url, self.root_dir, dep.name, self.outbuf)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001711 if not os.path.isdir(scm.checkout_path):
1712 return None
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001713 return '%s@%s' % (url, scm.revinfo(self._options, [], None))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001714
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001715 if self._options.snapshot:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001716 new_gclient = ''
1717 # First level at .gclient
1718 for d in self.dependencies:
1719 entries = {}
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001720 def GrabDeps(dep):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001721 """Recursively grab dependencies."""
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001722 for d in dep.dependencies:
1723 entries[d.name] = GetURLAndRev(d)
1724 GrabDeps(d)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001725 GrabDeps(d)
1726 custom_deps = []
1727 for k in sorted(entries.keys()):
1728 if entries[k]:
1729 # Quotes aren't escaped...
1730 custom_deps.append(' \"%s\": \'%s\',\n' % (k, entries[k]))
1731 else:
1732 custom_deps.append(' \"%s\": None,\n' % k)
1733 new_gclient += self.DEFAULT_SNAPSHOT_SOLUTION_TEXT % {
1734 'solution_name': d.name,
1735 'solution_url': d.url,
nsylvain@google.comefc80932011-05-31 21:27:56 +00001736 'deps_file': d.deps_file,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001737 'managed': d.managed,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001738 'solution_deps': ''.join(custom_deps),
1739 }
1740 # Print the snapshot configuration file
1741 print(self.DEFAULT_SNAPSHOT_FILE_TEXT % {'solution_list': new_gclient})
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +00001742 else:
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001743 entries = {}
maruel@chromium.org68988972011-09-20 14:11:42 +00001744 for d in self.root.subtree(False):
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001745 if self._options.actual:
1746 entries[d.name] = GetURLAndRev(d)
1747 else:
1748 entries[d.name] = d.parsed_url
1749 keys = sorted(entries.keys())
1750 for x in keys:
maruel@chromium.orgce464892010-08-12 17:12:18 +00001751 print('%s: %s' % (x, entries[x]))
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +00001752 logging.info(str(self))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001753
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001754 def ParseDepsFile(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001755 """No DEPS to parse for a .gclient file."""
maruel@chromium.org049bced2010-08-12 13:37:20 +00001756 raise gclient_utils.Error('Internal error')
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001757
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00001758 def PrintLocationAndContents(self):
1759 # Print out the .gclient file. This is longer than if we just printed the
1760 # client dict, but more legible, and it might contain helpful comments.
1761 print('Loaded .gclient config in %s:\n%s' % (
1762 self.root_dir, self.config_content))
1763
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001764 @property
maruel@chromium.org75a59272010-06-11 22:34:03 +00001765 def root_dir(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001766 """Root directory of gclient checkout."""
maruel@chromium.org75a59272010-06-11 22:34:03 +00001767 return self._root_dir
1768
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001769 @property
maruel@chromium.org271375b2010-06-23 19:17:38 +00001770 def enforced_os(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001771 """What deps_os entries that are to be parsed."""
maruel@chromium.org271375b2010-06-23 19:17:38 +00001772 return self._enforced_os
1773
maruel@chromium.org68988972011-09-20 14:11:42 +00001774 @property
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001775 def recursion_limit(self):
1776 """How recursive can each dependencies in DEPS file can load DEPS file."""
1777 return self._recursion_limit
1778
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +00001779 @property
cmp@chromium.orgc401ad12014-07-02 23:20:08 +00001780 def try_recursedeps(self):
1781 """Whether to attempt using recursedeps-style recursion processing."""
cmp@chromium.orge84ac912014-06-30 23:14:35 +00001782 return True
1783
1784 @property
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +00001785 def target_os(self):
1786 return self._enforced_os
1787
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001788
John Budorick0f7b2002018-01-19 15:46:17 -08001789class GitDependency(Dependency):
1790 """A Dependency object that represents a single git checkout."""
1791
1792 #override
1793 def GetScmName(self, url):
1794 """Always 'git'."""
1795 del url
1796 return 'git'
1797
1798 #override
1799 def CreateSCM(self, url, root_dir=None, relpath=None, out_fh=None,
1800 out_cb=None):
1801 """Create a Wrapper instance suitable for handling this git dependency."""
1802 return gclient_scm.GitWrapper(url, root_dir, relpath, out_fh, out_cb)
1803
1804
1805class CipdDependency(Dependency):
1806 """A Dependency object that represents a single CIPD package."""
1807
1808 def __init__(
1809 self, parent, name, dep_value, cipd_root,
1810 custom_vars, should_process, relative, condition, condition_value):
1811 package = dep_value['package']
1812 version = dep_value['version']
1813 url = urlparse.urljoin(
1814 cipd_root.service_url, '%s@%s' % (package, version))
1815 super(CipdDependency, self).__init__(
1816 parent, name, url, url, None, None, custom_vars,
1817 None, None, should_process, relative, condition, condition_value)
1818 if relative:
1819 # TODO(jbudorick): Implement relative if necessary.
1820 raise gclient_utils.Error(
1821 'Relative CIPD dependencies are not currently supported.')
1822 self._cipd_root = cipd_root
1823
1824 self._cipd_subdir = os.path.relpath(
1825 os.path.join(self.root.root_dir, self.name), cipd_root.root_dir)
1826 self._cipd_package = self._cipd_root.add_package(
1827 self._cipd_subdir, package, version)
1828
1829 def ParseDepsFile(self):
1830 """CIPD dependencies are not currently allowed to have nested deps."""
1831 self.add_dependencies_and_close([], [])
1832
1833 #override
1834 def GetScmName(self, url):
1835 """Always 'cipd'."""
1836 del url
1837 return 'cipd'
1838
1839 #override
1840 def CreateSCM(self, url, root_dir=None, relpath=None, out_fh=None,
1841 out_cb=None):
1842 """Create a Wrapper instance suitable for handling this CIPD dependency."""
1843 return gclient_scm.CipdWrapper(
1844 url, root_dir, relpath, out_fh, out_cb,
1845 root=self._cipd_root,
1846 package=self._cipd_package)
1847
1848 def ToLines(self):
1849 """Return a list of lines representing this in a DEPS file."""
1850 s = []
1851 if self._cipd_package.authority_for_subdir:
1852 condition_part = ([' "condition": %r,' % self.condition]
1853 if self.condition else [])
1854 s.extend([
1855 ' # %s' % self.hierarchy(include_url=False),
1856 ' "%s": {' % (self.name,),
1857 ' "packages": [',
1858 ])
1859 for p in self._cipd_root.packages(self._cipd_subdir):
1860 s.extend([
1861 ' "package": "%s",' % p.name,
1862 ' "version": "%s",' % p.version,
1863 ])
1864 s.extend([
1865 ' ],',
1866 ' "dep_type": "cipd",',
1867 ] + condition_part + [
1868 ' },',
1869 '',
1870 ])
1871 return s
1872
1873
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001874#### gclient commands.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001875
1876
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001877@subcommand.usage('[command] [args ...]')
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001878def CMDrecurse(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001879 """Operates [command args ...] on all the dependencies.
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001880
1881 Runs a shell command on all entries.
qyearsley12fa6ff2016-08-24 09:18:40 -07001882 Sets GCLIENT_DEP_PATH environment variable as the dep's relative location to
ilevy@chromium.org37116242012-11-28 01:32:48 +00001883 root directory of the checkout.
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001884 """
1885 # Stop parsing at the first non-arg so that these go through to the command
1886 parser.disable_interspersed_args()
1887 parser.add_option('-s', '--scm', action='append', default=[],
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001888 help='Choose scm types to operate upon.')
maruel@chromium.org288054d2012-03-05 00:43:07 +00001889 parser.add_option('-i', '--ignore', action='store_true',
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001890 help='Ignore non-zero return codes from subcommands.')
1891 parser.add_option('--prepend-dir', action='store_true',
1892 help='Prepend relative dir for use with git <cmd> --null.')
1893 parser.add_option('--no-progress', action='store_true',
1894 help='Disable progress bar that shows sub-command updates')
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001895 options, args = parser.parse_args(args)
maruel@chromium.org45e9f2d2010-10-18 13:33:46 +00001896 if not args:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001897 print('Need to supply a command!', file=sys.stderr)
maruel@chromium.org45e9f2d2010-10-18 13:33:46 +00001898 return 1
maruel@chromium.org78cba522010-10-18 13:32:05 +00001899 root_and_entries = gclient_utils.GetGClientRootAndEntries()
1900 if not root_and_entries:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001901 print(
maruel@chromium.org78cba522010-10-18 13:32:05 +00001902 'You need to run gclient sync at least once to use \'recurse\'.\n'
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001903 'This is because .gclient_entries needs to exist and be up to date.',
1904 file=sys.stderr)
maruel@chromium.org78cba522010-10-18 13:32:05 +00001905 return 1
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001906
1907 # Normalize options.scm to a set()
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001908 scm_set = set()
1909 for scm in options.scm:
1910 scm_set.update(scm.split(','))
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001911 options.scm = scm_set
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001912
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001913 options.nohooks = True
1914 client = GClient.LoadCurrentConfig(options)
Marc-Antoine Ruele6e06412017-10-18 13:47:02 -04001915 if not client:
1916 raise gclient_utils.Error('client not configured; see \'gclient config\'')
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001917 return client.RunOnDeps('recurse', args, ignore_requirements=True,
1918 progress=not options.no_progress)
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001919
1920
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001921@subcommand.usage('[args ...]')
davidbarr@chromium.org12f944e2012-03-01 02:18:31 +00001922def CMDfetch(parser, args):
1923 """Fetches upstream commits for all modules.
1924
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001925 Completely git-specific. Simply runs 'git fetch [args ...]' for each module.
1926 """
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001927 (options, args) = parser.parse_args(args)
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00001928 return CMDrecurse(OptionParser(), [
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001929 '--jobs=%d' % options.jobs, '--scm=git', 'git', 'fetch'] + args)
1930
1931
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02001932class Flattener(object):
1933 """Flattens a gclient solution."""
1934
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02001935 def __init__(self, client, pin_all_deps=False):
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02001936 """Constructor.
1937
1938 Arguments:
1939 client (GClient): client to flatten
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02001940 pin_all_deps (bool): whether to pin all deps, even if they're not pinned
1941 in DEPS
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02001942 """
1943 self._client = client
1944
1945 self._deps_string = None
Paweł Hajdan, Jrfeb01642017-09-12 15:50:46 +02001946 self._deps_files = set()
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02001947
1948 self._allowed_hosts = set()
1949 self._deps = {}
1950 self._deps_os = {}
1951 self._hooks = []
1952 self._hooks_os = {}
1953 self._pre_deps_hooks = []
Paweł Hajdan, Jrfb022012017-07-06 18:00:08 +02001954 self._vars = {}
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02001955
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02001956 self._flatten(pin_all_deps=pin_all_deps)
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02001957
1958 @property
1959 def deps_string(self):
1960 assert self._deps_string is not None
1961 return self._deps_string
1962
Paweł Hajdan, Jrfeb01642017-09-12 15:50:46 +02001963 @property
1964 def deps_files(self):
1965 return self._deps_files
1966
Paweł Hajdan, Jr5ec77132017-08-16 19:21:06 +02001967 def _pin_dep(self, dep):
1968 """Pins a dependency to specific full revision sha.
1969
1970 Arguments:
1971 dep (Dependency): dependency to process
1972 """
1973 if dep.parsed_url is None:
1974 return
1975
1976 # Make sure the revision is always fully specified (a hash),
1977 # as opposed to refs or tags which might change. Similarly,
1978 # shortened shas might become ambiguous; make sure to always
1979 # use full one for pinning.
1980 url, revision = gclient_utils.SplitUrlRevision(dep.parsed_url)
1981 if revision and gclient_utils.IsFullGitSha(revision):
1982 return
1983
John Budorick0f7b2002018-01-19 15:46:17 -08001984 scm = dep.CreateSCM(
Paweł Hajdan, Jr5ec77132017-08-16 19:21:06 +02001985 dep.parsed_url, self._client.root_dir, dep.name, dep.outbuf)
1986 revinfo = scm.revinfo(self._client._options, [], None)
1987
1988 dep._parsed_url = dep._url = '%s@%s' % (url, revinfo)
1989 raw_url, _ = gclient_utils.SplitUrlRevision(dep._raw_url)
1990 dep._raw_url = '%s@%s' % (raw_url, revinfo)
1991
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02001992 def _flatten(self, pin_all_deps=False):
1993 """Runs the flattener. Saves resulting DEPS string.
1994
1995 Arguments:
1996 pin_all_deps (bool): whether to pin all deps, even if they're not pinned
1997 in DEPS
1998 """
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02001999 for solution in self._client.dependencies:
Paweł Hajdan, Jr11eb7152017-08-10 12:50:11 +02002000 self._add_dep(solution)
Paweł Hajdan, Jrb0ad16e2017-08-03 15:33:21 +02002001 self._flatten_dep(solution)
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002002
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02002003 if pin_all_deps:
2004 for dep in self._deps.itervalues():
Paweł Hajdan, Jr5ec77132017-08-16 19:21:06 +02002005 self._pin_dep(dep)
Paweł Hajdan, Jr39300ba2017-08-11 16:52:38 +02002006
Paweł Hajdan, Jr5ec77132017-08-16 19:21:06 +02002007 for os_deps in self._deps_os.itervalues():
2008 for dep in os_deps.itervalues():
Paweł Hajdan, Jr5ec77132017-08-16 19:21:06 +02002009 self._pin_dep(dep)
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02002010
Paweł Hajdan, Jr76a9d042017-08-18 20:05:41 +02002011 def add_deps_file(dep):
Paweł Hajdan, Jr0870df22017-08-23 17:59:29 +02002012 # Only include DEPS files referenced by recursedeps.
2013 if not (dep.parent is None or
2014 (dep.name in (dep.parent.recursedeps or {}))):
2015 return
Paweł Hajdan, Jrfeb01642017-09-12 15:50:46 +02002016 deps_file = dep.deps_file
2017 deps_path = os.path.join(self._client.root_dir, dep.name, deps_file)
Paweł Hajdan, Jr76a9d042017-08-18 20:05:41 +02002018 if not os.path.exists(deps_path):
Paweł Hajdan, Jrfeb01642017-09-12 15:50:46 +02002019 # gclient has a fallback that if deps_file doesn't exist, it'll try
2020 # DEPS. Do the same here.
2021 deps_file = 'DEPS'
2022 deps_path = os.path.join(self._client.root_dir, dep.name, deps_file)
2023 if not os.path.exists(deps_path):
2024 return
Paweł Hajdan, Jr76a9d042017-08-18 20:05:41 +02002025 assert dep.parsed_url
Paweł Hajdan, Jrfeb01642017-09-12 15:50:46 +02002026 self._deps_files.add((dep.parsed_url, deps_file))
Paweł Hajdan, Jr76a9d042017-08-18 20:05:41 +02002027 for dep in self._deps.itervalues():
2028 add_deps_file(dep)
2029 for os_deps in self._deps_os.itervalues():
2030 for dep in os_deps.itervalues():
2031 add_deps_file(dep)
2032
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002033 self._deps_string = '\n'.join(
2034 _GNSettingsToLines(
2035 self._client.dependencies[0]._gn_args_file,
2036 self._client.dependencies[0]._gn_args) +
2037 _AllowedHostsToLines(self._allowed_hosts) +
2038 _DepsToLines(self._deps) +
2039 _DepsOsToLines(self._deps_os) +
2040 _HooksToLines('hooks', self._hooks) +
2041 _HooksToLines('pre_deps_hooks', self._pre_deps_hooks) +
2042 _HooksOsToLines(self._hooks_os) +
Paweł Hajdan, Jrfb022012017-07-06 18:00:08 +02002043 _VarsToLines(self._vars) +
Paweł Hajdan, Jr76a9d042017-08-18 20:05:41 +02002044 ['# %s, %s' % (url, deps_file)
Paweł Hajdan, Jrfeb01642017-09-12 15:50:46 +02002045 for url, deps_file in sorted(self._deps_files)] +
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002046 ['']) # Ensure newline at end of file.
2047
Paweł Hajdan, Jr11eb7152017-08-10 12:50:11 +02002048 def _add_dep(self, dep):
2049 """Helper to add a dependency to flattened DEPS.
2050
2051 Arguments:
2052 dep (Dependency): dependency to add
2053 """
2054 assert dep.name not in self._deps or self._deps.get(dep.name) == dep, (
2055 dep.name, self._deps.get(dep.name))
Paweł Hajdan, Jr9a289022017-08-10 16:04:24 +02002056 if dep.url:
2057 self._deps[dep.name] = dep
Paweł Hajdan, Jr11eb7152017-08-10 12:50:11 +02002058
Paweł Hajdan, Jradae2a62017-08-18 16:49:57 +02002059 def _add_os_dep(self, os_dep, dep_os):
2060 """Helper to add an OS-specific dependency to flattened DEPS.
2061
2062 Arguments:
2063 os_dep (Dependency): dependency to add
2064 dep_os (str): name of the OS
2065 """
2066 assert (
2067 os_dep.name not in self._deps_os.get(dep_os, {}) or
2068 self._deps_os.get(dep_os, {}).get(os_dep.name) == os_dep), (
2069 os_dep.name, self._deps_os.get(dep_os, {}).get(os_dep.name))
2070 if os_dep.url:
Paweł Hajdan, Jr76a9d042017-08-18 20:05:41 +02002071 # OS-specific deps need to have their full URL resolved manually.
2072 assert not os_dep.parsed_url, (os_dep, os_dep.parsed_url)
2073 os_dep._parsed_url = os_dep.LateOverride(os_dep.url)
2074
Paweł Hajdan, Jradae2a62017-08-18 16:49:57 +02002075 self._deps_os.setdefault(dep_os, {})[os_dep.name] = os_dep
2076
2077 def _flatten_dep(self, dep, dep_os=None):
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002078 """Visits a dependency in order to flatten it (see CMDflatten).
2079
2080 Arguments:
2081 dep (Dependency): dependency to process
Paweł Hajdan, Jradae2a62017-08-18 16:49:57 +02002082 dep_os (str or None): name of the OS |dep| is specific to
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002083 """
Paweł Hajdan, Jradae2a62017-08-18 16:49:57 +02002084 logging.debug('_flatten_dep(%s, %s)', dep.name, dep_os)
2085
Paweł Hajdan, Jrc69b32e2017-08-17 18:47:48 +02002086 if not dep.deps_parsed:
2087 dep.ParseDepsFile()
2088
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002089 self._allowed_hosts.update(dep.allowed_hosts)
2090
Paweł Hajdan, Jra3b67ae2017-08-30 15:18:21 +02002091 # Only include vars listed in the DEPS files, not possible local overrides.
2092 for key, value in dep._vars.iteritems():
Paweł Hajdan, Jrc9353602017-08-02 17:52:08 +02002093 # Make sure there are no conflicting variables. It is fine however
2094 # to use same variable name, as long as the value is consistent.
2095 assert key not in self._vars or self._vars[key][1] == value
Paweł Hajdan, Jrfb022012017-07-06 18:00:08 +02002096 self._vars[key] = (dep, value)
2097
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002098 self._pre_deps_hooks.extend([(dep, hook) for hook in dep.pre_deps_hooks])
2099
Paweł Hajdan, Jradae2a62017-08-18 16:49:57 +02002100 if dep_os:
2101 if dep.deps_hooks:
2102 self._hooks_os.setdefault(dep_os, []).extend(
2103 [(dep, hook) for hook in dep.deps_hooks])
2104 else:
2105 self._hooks.extend([(dep, hook) for hook in dep.deps_hooks])
2106
Paweł Hajdan, Jrb0ad16e2017-08-03 15:33:21 +02002107 for sub_dep in dep.dependencies:
Paweł Hajdan, Jradae2a62017-08-18 16:49:57 +02002108 if dep_os:
2109 self._add_os_dep(sub_dep, dep_os)
2110 else:
2111 self._add_dep(sub_dep)
Paweł Hajdan, Jrb0ad16e2017-08-03 15:33:21 +02002112
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002113 for hook_os, os_hooks in dep.os_deps_hooks.iteritems():
2114 self._hooks_os.setdefault(hook_os, []).extend(
2115 [(dep, hook) for hook in os_hooks])
2116
Paweł Hajdan, Jradae2a62017-08-18 16:49:57 +02002117 for sub_dep_os, os_deps in dep.os_dependencies.iteritems():
Paweł Hajdan, Jre2deb1e2017-08-09 17:29:21 +02002118 for os_dep in os_deps:
Paweł Hajdan, Jradae2a62017-08-18 16:49:57 +02002119 self._add_os_dep(os_dep, sub_dep_os)
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002120
Paweł Hajdan, Jradae2a62017-08-18 16:49:57 +02002121 # Process recursedeps. |deps_by_name| is a map where keys are dependency
2122 # names, and values are maps of OS names to |Dependency| instances.
2123 # |None| in place of OS name means the dependency is not OS-specific.
2124 deps_by_name = dict((d.name, {None: d}) for d in dep.dependencies)
2125 for sub_dep_os, os_deps in dep.os_dependencies.iteritems():
Paweł Hajdan, Jrc9353602017-08-02 17:52:08 +02002126 for os_dep in os_deps:
Paweł Hajdan, Jradae2a62017-08-18 16:49:57 +02002127 assert sub_dep_os not in deps_by_name.get(os_dep.name, {}), (
2128 os_dep.name, sub_dep_os)
2129 deps_by_name.setdefault(os_dep.name, {})[sub_dep_os] = os_dep
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002130 for recurse_dep_name in (dep.recursedeps or []):
Paweł Hajdan, Jradae2a62017-08-18 16:49:57 +02002131 dep_info = deps_by_name[recurse_dep_name]
2132 for sub_dep_os, os_dep in dep_info.iteritems():
2133 self._flatten_dep(os_dep, dep_os=(sub_dep_os or dep_os))
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002134
2135
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002136def CMDflatten(parser, args):
2137 """Flattens the solutions into a single DEPS file."""
2138 parser.add_option('--output-deps', help='Path to the output DEPS file')
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02002139 parser.add_option(
Paweł Hajdan, Jrfeb01642017-09-12 15:50:46 +02002140 '--output-deps-files',
2141 help=('Path to the output metadata about DEPS files referenced by '
2142 'recursedeps.'))
2143 parser.add_option(
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02002144 '--pin-all-deps', action='store_true',
2145 help=('Pin all deps, even if not pinned in DEPS. CAVEAT: only does so '
2146 'for checked out deps, NOT deps_os.'))
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002147 options, args = parser.parse_args(args)
2148
Paweł Hajdan, Jr357415c2017-07-24 14:35:28 +02002149 options.do_not_merge_os_specific_entries = True
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002150 options.nohooks = True
Paweł Hajdan, Jr5ec77132017-08-16 19:21:06 +02002151 options.process_all_deps = True
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002152 client = GClient.LoadCurrentConfig(options)
2153
2154 # Only print progress if we're writing to a file. Otherwise, progress updates
2155 # could obscure intended output.
2156 code = client.RunOnDeps('flatten', args, progress=options.output_deps)
2157 if code != 0:
2158 return code
2159
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02002160 flattener = Flattener(client, pin_all_deps=options.pin_all_deps)
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002161
2162 if options.output_deps:
2163 with open(options.output_deps, 'w') as f:
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002164 f.write(flattener.deps_string)
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002165 else:
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002166 print(flattener.deps_string)
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002167
Paweł Hajdan, Jrfeb01642017-09-12 15:50:46 +02002168 deps_files = [{'url': d[0], 'deps_file': d[1]}
2169 for d in sorted(flattener.deps_files)]
2170 if options.output_deps_files:
2171 with open(options.output_deps_files, 'w') as f:
2172 json.dump(deps_files, f)
2173
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002174 return 0
2175
2176
Paweł Hajdan, Jr3c2aa832017-06-07 20:22:16 +02002177def _GNSettingsToLines(gn_args_file, gn_args):
2178 s = []
2179 if gn_args_file:
2180 s.extend([
2181 'gclient_gn_args_file = "%s"' % gn_args_file,
2182 'gclient_gn_args = %r' % gn_args,
2183 ])
2184 return s
2185
2186
Paweł Hajdan, Jr6014b562017-06-30 17:43:42 +02002187def _AllowedHostsToLines(allowed_hosts):
2188 """Converts |allowed_hosts| set to list of lines for output."""
2189 if not allowed_hosts:
2190 return []
2191 s = ['allowed_hosts = [']
2192 for h in sorted(allowed_hosts):
2193 s.append(' "%s",' % h)
2194 s.extend([']', ''])
2195 return s
2196
2197
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002198def _DepsToLines(deps):
2199 """Converts |deps| dict to list of lines for output."""
Paweł Hajdan, Jr5b593352017-06-29 18:37:45 +02002200 if not deps:
2201 return []
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002202 s = ['deps = {']
John Budorick0f7b2002018-01-19 15:46:17 -08002203 for _, dep in sorted(deps.iteritems()):
2204 s.extend(dep.ToLines())
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002205 s.extend(['}', ''])
2206 return s
2207
2208
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02002209def _DepsOsToLines(deps_os):
2210 """Converts |deps_os| dict to list of lines for output."""
Paweł Hajdan, Jr5b593352017-06-29 18:37:45 +02002211 if not deps_os:
2212 return []
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02002213 s = ['deps_os = {']
2214 for dep_os, os_deps in sorted(deps_os.iteritems()):
2215 s.append(' "%s": {' % dep_os)
Paweł Hajdan, Jrad30de62017-06-26 18:51:58 +02002216 for name, dep in sorted(os_deps.iteritems()):
Paweł Hajdan, Jr78ce24e2017-10-03 17:09:13 +02002217 condition_part = ([' "condition": %r,' % dep.condition]
Paweł Hajdan, Jrad30de62017-06-26 18:51:58 +02002218 if dep.condition else [])
2219 s.extend([
2220 ' # %s' % dep.hierarchy(include_url=False),
2221 ' "%s": {' % (name,),
Paweł Hajdan, Jrde86ab32017-08-10 13:55:16 +02002222 ' "url": "%s",' % (dep.raw_url,),
Paweł Hajdan, Jrad30de62017-06-26 18:51:58 +02002223 ] + condition_part + [
2224 ' },',
2225 '',
2226 ])
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02002227 s.extend([' },', ''])
2228 s.extend(['}', ''])
2229 return s
2230
2231
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002232def _HooksToLines(name, hooks):
2233 """Converts |hooks| list to list of lines for output."""
Paweł Hajdan, Jr5b593352017-06-29 18:37:45 +02002234 if not hooks:
2235 return []
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002236 s = ['%s = [' % name]
2237 for dep, hook in hooks:
2238 s.extend([
2239 ' # %s' % dep.hierarchy(include_url=False),
2240 ' {',
2241 ])
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +02002242 if hook.name is not None:
2243 s.append(' "name": "%s",' % hook.name)
2244 if hook.pattern is not None:
2245 s.append(' "pattern": "%s",' % hook.pattern)
Paweł Hajdan, Jrecf53fe2017-09-29 18:28:49 +02002246 if hook.condition is not None:
Paweł Hajdan, Jr78ce24e2017-10-03 17:09:13 +02002247 s.append(' "condition": %r,' % hook.condition)
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002248 s.extend(
Paweł Hajdan, Jrc9364392017-06-14 17:11:56 +02002249 # Hooks run in the parent directory of their dep.
Paweł Hajdan, Jrad30de62017-06-26 18:51:58 +02002250 [' "cwd": "%s",' % os.path.normpath(os.path.dirname(dep.name))] +
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002251 [' "action": ['] +
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +02002252 [' "%s",' % arg for arg in hook.action] +
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002253 [' ]', ' },', '']
2254 )
2255 s.extend([']', ''])
2256 return s
2257
2258
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +02002259def _HooksOsToLines(hooks_os):
2260 """Converts |hooks| list to list of lines for output."""
Paweł Hajdan, Jr5b593352017-06-29 18:37:45 +02002261 if not hooks_os:
2262 return []
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +02002263 s = ['hooks_os = {']
2264 for hook_os, os_hooks in hooks_os.iteritems():
Michael Moss017bcf62017-06-28 15:26:38 -07002265 s.append(' "%s": [' % hook_os)
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +02002266 for dep, hook in os_hooks:
2267 s.extend([
2268 ' # %s' % dep.hierarchy(include_url=False),
2269 ' {',
2270 ])
2271 if hook.name is not None:
2272 s.append(' "name": "%s",' % hook.name)
2273 if hook.pattern is not None:
2274 s.append(' "pattern": "%s",' % hook.pattern)
Paweł Hajdan, Jrecf53fe2017-09-29 18:28:49 +02002275 if hook.condition is not None:
Paweł Hajdan, Jr78ce24e2017-10-03 17:09:13 +02002276 s.append(' "condition": %r,' % hook.condition)
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +02002277 s.extend(
2278 # Hooks run in the parent directory of their dep.
2279 [' "cwd": "%s",' % os.path.normpath(os.path.dirname(dep.name))] +
2280 [' "action": ['] +
2281 [' "%s",' % arg for arg in hook.action] +
2282 [' ]', ' },', '']
2283 )
Michael Moss017bcf62017-06-28 15:26:38 -07002284 s.extend([' ],', ''])
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +02002285 s.extend(['}', ''])
2286 return s
2287
2288
Paweł Hajdan, Jrfb022012017-07-06 18:00:08 +02002289def _VarsToLines(variables):
2290 """Converts |variables| dict to list of lines for output."""
2291 if not variables:
2292 return []
2293 s = ['vars = {']
2294 for key, tup in sorted(variables.iteritems()):
2295 dep, value = tup
2296 s.extend([
2297 ' # %s' % dep.hierarchy(include_url=False),
2298 ' "%s": %r,' % (key, value),
2299 '',
2300 ])
2301 s.extend(['}', ''])
2302 return s
2303
2304
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00002305def CMDgrep(parser, args):
2306 """Greps through git repos managed by gclient.
2307
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002308 Runs 'git grep [args...]' for each module.
2309 """
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00002310 # We can't use optparse because it will try to parse arguments sent
2311 # to git grep and throw an error. :-(
2312 if not args or re.match('(-h|--help)$', args[0]):
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002313 print(
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00002314 'Usage: gclient grep [-j <N>] git-grep-args...\n\n'
2315 'Example: "gclient grep -j10 -A2 RefCountedBase" runs\n"git grep '
2316 '-A2 RefCountedBase" on each of gclient\'s git\nrepos with up to '
2317 '10 jobs.\n\nBonus: page output by appending "|& less -FRSX" to the'
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002318 ' end of your query.',
2319 file=sys.stderr)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00002320 return 1
2321
2322 jobs_arg = ['--jobs=1']
2323 if re.match(r'(-j|--jobs=)\d+$', args[0]):
2324 jobs_arg, args = args[:1], args[1:]
2325 elif re.match(r'(-j|--jobs)$', args[0]):
2326 jobs_arg, args = args[:2], args[2:]
2327
2328 return CMDrecurse(
2329 parser,
2330 jobs_arg + ['--ignore', '--prepend-dir', '--no-progress', '--scm=git',
2331 'git', 'grep', '--null', '--color=Always'] + args)
davidbarr@chromium.org12f944e2012-03-01 02:18:31 +00002332
2333
stip@chromium.orga735da22015-04-29 23:18:20 +00002334def CMDroot(parser, args):
2335 """Outputs the solution root (or current dir if there isn't one)."""
2336 (options, args) = parser.parse_args(args)
2337 client = GClient.LoadCurrentConfig(options)
2338 if client:
2339 print(os.path.abspath(client.root_dir))
2340 else:
2341 print(os.path.abspath('.'))
2342
2343
agablea98a6cd2016-11-15 14:30:10 -08002344@subcommand.usage('[url]')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002345def CMDconfig(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002346 """Creates a .gclient file in the current directory.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00002347
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002348 This specifies the configuration for further commands. After update/sync,
2349 top-level DEPS files in each module are read to determine dependent
2350 modules to operate on as well. If optional [url] parameter is
2351 provided, then configuration is read from a specified Subversion server
2352 URL.
2353 """
szager@chromium.orge2e03202012-07-31 18:05:16 +00002354 # We do a little dance with the --gclientfile option. 'gclient config' is the
2355 # only command where it's acceptable to have both '--gclientfile' and '--spec'
2356 # arguments. So, we temporarily stash any --gclientfile parameter into
2357 # options.output_config_file until after the (gclientfile xor spec) error
2358 # check.
2359 parser.remove_option('--gclientfile')
2360 parser.add_option('--gclientfile', dest='output_config_file',
2361 help='Specify an alternate .gclient file')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002362 parser.add_option('--name',
2363 help='overrides the default name for the solution')
nsylvain@google.comefc80932011-05-31 21:27:56 +00002364 parser.add_option('--deps-file', default='DEPS',
David Benjamin105e11e2017-10-16 10:39:35 -04002365 help='overrides the default name for the DEPS file for the '
nsylvain@google.comefc80932011-05-31 21:27:56 +00002366 'main solutions and all sub-dependencies')
smutae7ea312016-07-18 11:59:41 -07002367 parser.add_option('--unmanaged', action='store_true', default=False,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00002368 help='overrides the default behavior to make it possible '
smutae7ea312016-07-18 11:59:41 -07002369 'to have the main solution untouched by gclient '
2370 '(gclient will check out unmanaged dependencies but '
2371 'will never sync them)')
Paweł Hajdan, Jr3ba2a7c2017-10-04 19:24:46 +02002372 parser.add_option('--custom-var', action='append', dest='custom_vars',
2373 default=[],
2374 help='overrides variables; key=value syntax')
szager@chromium.orge2e03202012-07-31 18:05:16 +00002375 parser.set_defaults(config_filename=None)
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002376 (options, args) = parser.parse_args(args)
szager@chromium.orge2e03202012-07-31 18:05:16 +00002377 if options.output_config_file:
2378 setattr(options, 'config_filename', getattr(options, 'output_config_file'))
maruel@chromium.org5fc2a332010-05-26 19:37:15 +00002379 if ((options.spec and args) or len(args) > 2 or
2380 (not options.spec and not args)):
2381 parser.error('Inconsistent arguments. Use either --spec or one or 2 args')
2382
Paweł Hajdan, Jr3ba2a7c2017-10-04 19:24:46 +02002383 custom_vars = {}
2384 for arg in options.custom_vars:
2385 kv = arg.split('=', 1)
2386 if len(kv) != 2:
2387 parser.error('Invalid --custom-var argument: %r' % arg)
2388 custom_vars[kv[0]] = gclient_eval.EvaluateCondition(kv[1], {})
2389
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002390 client = GClient('.', options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002391 if options.spec:
2392 client.SetConfig(options.spec)
2393 else:
maruel@chromium.org1ab7ffc2009-06-03 17:21:37 +00002394 base_url = args[0].rstrip('/')
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00002395 if not options.name:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002396 name = base_url.split('/')[-1]
nsylvain@google.com12649ef2011-06-01 17:11:20 +00002397 if name.endswith('.git'):
2398 name = name[:-4]
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00002399 else:
2400 # specify an alternate relpath for the given URL.
2401 name = options.name
agable@chromium.orgf2214672015-10-27 21:02:48 +00002402 if not os.path.abspath(os.path.join(os.getcwd(), name)).startswith(
2403 os.getcwd()):
2404 parser.error('Do not pass a relative path for --name.')
2405 if any(x in ('..', '.', '/', '\\') for x in name.split(os.sep)):
2406 parser.error('Do not include relative path components in --name.')
2407
nsylvain@google.comefc80932011-05-31 21:27:56 +00002408 deps_file = options.deps_file
agablea98a6cd2016-11-15 14:30:10 -08002409 client.SetDefaultConfig(name, deps_file, base_url,
smutae7ea312016-07-18 11:59:41 -07002410 managed=not options.unmanaged,
Paweł Hajdan, Jr3ba2a7c2017-10-04 19:24:46 +02002411 cache_dir=options.cache_dir,
2412 custom_vars=custom_vars)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002413 client.SaveConfig()
maruel@chromium.org79692d62010-05-14 18:57:13 +00002414 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002415
2416
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002417@subcommand.epilog("""Example:
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002418 gclient pack > patch.txt
2419 generate simple patch for configured client and dependences
2420""")
2421def CMDpack(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002422 """Generates a patch which can be applied at the root of the tree.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00002423
agabled437d762016-10-17 09:35:11 -07002424 Internally, runs 'git diff' on each checked out module and
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002425 dependencies, and performs minimal postprocessing of the output. The
2426 resulting patch is printed to stdout and can be applied to a freshly
2427 checked out tree via 'patch -p0 < patchfile'.
2428 """
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002429 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2430 help='override deps for the specified (comma-separated) '
2431 'platform(s); \'all\' will process all deps_os '
2432 'references')
haitao.feng@intel.com306080c2012-05-04 13:11:29 +00002433 parser.remove_option('--jobs')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002434 (options, args) = parser.parse_args(args)
iannucci@chromium.org50395ea2013-04-04 04:47:42 +00002435 # Force jobs to 1 so the stdout is not annotated with the thread ids
haitao.feng@intel.com306080c2012-05-04 13:11:29 +00002436 options.jobs = 1
kbr@google.comab318592009-09-04 00:54:55 +00002437 client = GClient.LoadCurrentConfig(options)
2438 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002439 raise gclient_utils.Error('client not configured; see \'gclient config\'')
kbr@google.comab318592009-09-04 00:54:55 +00002440 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002441 client.PrintLocationAndContents()
kbr@google.comab318592009-09-04 00:54:55 +00002442 return client.RunOnDeps('pack', args)
2443
2444
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002445def CMDstatus(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002446 """Shows modification status for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002447 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2448 help='override deps for the specified (comma-separated) '
2449 'platform(s); \'all\' will process all deps_os '
2450 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002451 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002452 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002453 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002454 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002455 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002456 client.PrintLocationAndContents()
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002457 return client.RunOnDeps('status', args)
2458
2459
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002460@subcommand.epilog("""Examples:
maruel@chromium.org79692d62010-05-14 18:57:13 +00002461 gclient sync
2462 update files from SCM according to current configuration,
2463 *for modules which have changed since last update or sync*
2464 gclient sync --force
2465 update files from SCM according to current configuration, for
2466 all modules (useful for recovering files deleted from local copy)
2467 gclient sync --revision src@31000
2468 update src directory to r31000
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002469
2470JSON output format:
2471If the --output-json option is specified, the following document structure will
2472be emitted to the provided file. 'null' entries may occur for subprojects which
2473are present in the gclient solution, but were not processed (due to custom_deps,
2474os_deps, etc.)
2475
2476{
2477 "solutions" : {
2478 "<name>": { # <name> is the posix-normalized path to the solution.
agabled437d762016-10-17 09:35:11 -07002479 "revision": [<git id hex string>|null],
2480 "scm": ["git"|null],
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002481 }
2482 }
2483}
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002484""")
2485def CMDsync(parser, args):
2486 """Checkout/update all modules."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002487 parser.add_option('-f', '--force', action='store_true',
2488 help='force update even for unchanged modules')
2489 parser.add_option('-n', '--nohooks', action='store_true',
2490 help='don\'t run hooks after the update is complete')
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00002491 parser.add_option('-p', '--noprehooks', action='store_true',
2492 help='don\'t run pre-DEPS hooks', default=False)
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002493 parser.add_option('-r', '--revision', action='append',
2494 dest='revisions', metavar='REV', default=[],
2495 help='Enforces revision/hash for the solutions with the '
2496 'format src@rev. The src@ part is optional and can be '
2497 'skipped. -r can be used multiple times when .gclient '
2498 'has multiple solutions configured and will work even '
agablea98a6cd2016-11-15 14:30:10 -08002499 'if the src@ part is skipped.')
maruel@chromium.org794207e2013-03-08 15:29:43 +00002500 parser.add_option('--with_branch_heads', action='store_true',
2501 help='Clone git "branch_heads" refspecs in addition to '
2502 'the default refspecs. This adds about 1/2GB to a '
2503 'full checkout. (git only)')
szager@chromium.org8d3348f2014-08-19 22:49:16 +00002504 parser.add_option('--with_tags', action='store_true',
2505 help='Clone git tags in addition to the default refspecs.')
agable2697cd12016-06-28 10:23:53 -07002506 parser.add_option('-H', '--head', action='store_true',
agablea98a6cd2016-11-15 14:30:10 -08002507 help='DEPRECATED: only made sense with safesync urls.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002508 parser.add_option('-D', '--delete_unversioned_trees', action='store_true',
steveblock@chromium.org98e69452012-02-16 16:36:43 +00002509 help='Deletes from the working copy any dependencies that '
2510 'have been removed since the last sync, as long as '
2511 'there are no local modifications. When used with '
2512 '--force, such dependencies are removed even if they '
2513 'have local modifications. When used with --reset, '
2514 'all untracked directories are removed from the '
tnagel@chromium.orga2aaa632014-02-28 21:47:27 +00002515 'working copy, excluding those which are explicitly '
steveblock@chromium.org98e69452012-02-16 16:36:43 +00002516 'ignored in the repository.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002517 parser.add_option('-R', '--reset', action='store_true',
2518 help='resets any local changes before updating (git only)')
bauerb@chromium.org2aad1b22011-07-22 12:00:41 +00002519 parser.add_option('-M', '--merge', action='store_true',
2520 help='merge upstream changes instead of trying to '
2521 'fast-forward or rebase')
dnj@chromium.org5b23e872015-02-20 21:25:57 +00002522 parser.add_option('-A', '--auto_rebase', action='store_true',
2523 help='Automatically rebase repositories against local '
2524 'checkout during update (git only).')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002525 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2526 help='override deps for the specified (comma-separated) '
2527 'platform(s); \'all\' will process all deps_os '
2528 'references')
Paweł Hajdan, Jr357415c2017-07-24 14:35:28 +02002529 # TODO(phajdan.jr): use argparse.SUPPRESS to hide internal flags.
2530 parser.add_option('--do-not-merge-os-specific-entries', action='store_true',
2531 help='INTERNAL ONLY - disables merging of deps_os and '
2532 'hooks_os to dependencies and hooks')
Paweł Hajdan, Jr5ec77132017-08-16 19:21:06 +02002533 parser.add_option('--process-all-deps', action='store_true',
2534 help='Check out all deps, even for different OS-es, '
2535 'or with conditions evaluating to false')
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +00002536 parser.add_option('--upstream', action='store_true',
2537 help='Make repo state match upstream branch.')
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002538 parser.add_option('--output-json',
2539 help='Output a json document to this path containing '
2540 'summary information about the sync.')
primiano@chromium.org5439ea52014-08-06 17:18:18 +00002541 parser.add_option('--no-history', action='store_true',
2542 help='GIT ONLY - Reduces the size/time of the checkout at '
2543 'the cost of no history. Requires Git 1.9+')
hinoka@chromium.org46b87412014-05-15 00:42:05 +00002544 parser.add_option('--shallow', action='store_true',
2545 help='GIT ONLY - Do a shallow clone into the cache dir. '
2546 'Requires Git 1.9+')
e.hakkinen@samsung.come8bc1aa2015-04-08 08:00:37 +00002547 parser.add_option('--no_bootstrap', '--no-bootstrap',
2548 action='store_true',
2549 help='Don\'t bootstrap from Google Storage.')
Vadim Shtayura08049e22017-10-11 00:14:52 +00002550 parser.add_option('--ignore_locks', action='store_true',
2551 help='GIT ONLY - Ignore cache locks.')
iannucci@chromium.org30a07982016-04-07 21:35:19 +00002552 parser.add_option('--break_repo_locks', action='store_true',
2553 help='GIT ONLY - Forcibly remove repo locks (e.g. '
2554 'index.lock). This should only be used if you know for '
2555 'certain that this invocation of gclient is the only '
2556 'thing operating on the git repos (e.g. on a bot).')
Vadim Shtayura08049e22017-10-11 00:14:52 +00002557 parser.add_option('--lock_timeout', type='int', default=5000,
2558 help='GIT ONLY - Deadline (in seconds) to wait for git '
2559 'cache lock to become available. Default is %default.')
agabled437d762016-10-17 09:35:11 -07002560 # TODO(agable): Remove these when the oldest CrOS release milestone is M56.
2561 parser.add_option('-t', '--transitive', action='store_true',
2562 help='DEPRECATED: This is a no-op.')
sdefresne69b1be12016-10-18 05:48:02 -07002563 parser.add_option('-m', '--manually_grab_svn_rev', action='store_true',
agabled437d762016-10-17 09:35:11 -07002564 help='DEPRECATED: This is a no-op.')
Paweł Hajdan, Jr7c7b5592017-05-23 15:06:05 +02002565 # TODO(phajdan.jr): Remove validation options once default (crbug/570091).
Paweł Hajdan, Jr694773d2017-05-29 16:06:23 +02002566 parser.add_option('--validate-syntax', action='store_true', default=True,
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +02002567 help='Validate the .gclient and DEPS syntax')
Paweł Hajdan, Jr7c7b5592017-05-23 15:06:05 +02002568 parser.add_option('--disable-syntax-validation', action='store_false',
2569 dest='validate_syntax',
2570 help='Disable validation of .gclient and DEPS syntax.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002571 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002572 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002573
2574 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002575 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002576
smutae7ea312016-07-18 11:59:41 -07002577 if options.revisions and options.head:
2578 # TODO(maruel): Make it a parser.error if it doesn't break any builder.
2579 print('Warning: you cannot use both --head and --revision')
2580
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002581 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002582 client.PrintLocationAndContents()
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002583 ret = client.RunOnDeps('update', args)
2584 if options.output_json:
2585 slns = {}
2586 for d in client.subtree(True):
2587 normed = d.name.replace('\\', '/').rstrip('/') + '/'
2588 slns[normed] = {
2589 'revision': d.got_revision,
2590 'scm': d.used_scm.name if d.used_scm else None,
hinoka@chromium.org17db9052014-05-10 01:11:29 +00002591 'url': str(d.url) if d.url else None,
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002592 }
2593 with open(options.output_json, 'wb') as f:
2594 json.dump({'solutions': slns}, f)
2595 return ret
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002596
2597
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002598CMDupdate = CMDsync
2599
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002600
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +02002601def CMDvalidate(parser, args):
2602 """Validates the .gclient and DEPS syntax."""
2603 options, args = parser.parse_args(args)
2604 options.validate_syntax = True
2605 client = GClient.LoadCurrentConfig(options)
2606 rv = client.RunOnDeps('validate', args)
2607 if rv == 0:
2608 print('validate: SUCCESS')
2609 else:
2610 print('validate: FAILURE')
2611 return rv
2612
2613
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002614def CMDdiff(parser, args):
2615 """Displays local diff for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002616 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2617 help='override deps for the specified (comma-separated) '
2618 'platform(s); \'all\' will process all deps_os '
2619 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002620 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002621 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002622 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002623 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002624 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002625 client.PrintLocationAndContents()
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002626 return client.RunOnDeps('diff', args)
2627
2628
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002629def CMDrevert(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002630 """Reverts all modifications in every dependencies.
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00002631
2632 That's the nuclear option to get back to a 'clean' state. It removes anything
agabled437d762016-10-17 09:35:11 -07002633 that shows up in git status."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002634 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2635 help='override deps for the specified (comma-separated) '
2636 'platform(s); \'all\' will process all deps_os '
2637 'references')
2638 parser.add_option('-n', '--nohooks', action='store_true',
2639 help='don\'t run hooks after the revert is complete')
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00002640 parser.add_option('-p', '--noprehooks', action='store_true',
2641 help='don\'t run pre-DEPS hooks', default=False)
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +00002642 parser.add_option('--upstream', action='store_true',
2643 help='Make repo state match upstream branch.')
iannucci@chromium.orgbf525dc2016-04-07 22:00:28 +00002644 parser.add_option('--break_repo_locks', action='store_true',
2645 help='GIT ONLY - Forcibly remove repo locks (e.g. '
2646 'index.lock). This should only be used if you know for '
2647 'certain that this invocation of gclient is the only '
2648 'thing operating on the git repos (e.g. on a bot).')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002649 (options, args) = parser.parse_args(args)
2650 # --force is implied.
2651 options.force = True
steveblock@chromium.org98e69452012-02-16 16:36:43 +00002652 options.reset = False
2653 options.delete_unversioned_trees = False
agablec903d732016-07-26 09:07:24 -07002654 options.merge = False
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002655 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002656 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002657 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002658 return client.RunOnDeps('revert', args)
2659
2660
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002661def CMDrunhooks(parser, args):
2662 """Runs hooks for files that have been modified in the local working copy."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002663 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2664 help='override deps for the specified (comma-separated) '
2665 'platform(s); \'all\' will process all deps_os '
2666 'references')
2667 parser.add_option('-f', '--force', action='store_true', default=True,
2668 help='Deprecated. No effect.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002669 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002670 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002671 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002672 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002673 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002674 client.PrintLocationAndContents()
maruel@chromium.org5df6a462009-08-28 18:52:26 +00002675 options.force = True
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002676 options.nohooks = False
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002677 return client.RunOnDeps('runhooks', args)
2678
2679
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002680def CMDrevinfo(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002681 """Outputs revision info mapping for the client and its dependencies.
maruel@chromium.org9eda4112010-06-11 18:56:10 +00002682
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002683 This allows the capture of an overall 'revision' for the source tree that
maruel@chromium.org9eda4112010-06-11 18:56:10 +00002684 can be used to reproduce the same tree in the future. It is only useful for
agabled437d762016-10-17 09:35:11 -07002685 'unpinned dependencies', i.e. DEPS/deps references without a git hash.
2686 A git branch name isn't 'pinned' since the actual commit can change.
maruel@chromium.org9eda4112010-06-11 18:56:10 +00002687 """
2688 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2689 help='override deps for the specified (comma-separated) '
2690 'platform(s); \'all\' will process all deps_os '
2691 'references')
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00002692 parser.add_option('-a', '--actual', action='store_true',
2693 help='gets the actual checked out revisions instead of the '
2694 'ones specified in the DEPS and .gclient files')
maruel@chromium.org9eda4112010-06-11 18:56:10 +00002695 parser.add_option('-s', '--snapshot', action='store_true',
2696 help='creates a snapshot .gclient file of the current '
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00002697 'version of all repositories to reproduce the tree, '
2698 'implies -a')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002699 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002700 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002701 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002702 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002703 client.PrintRevInfo()
maruel@chromium.org79692d62010-05-14 18:57:13 +00002704 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002705
2706
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00002707def CMDverify(parser, args):
2708 """Verifies the DEPS file deps are only from allowed_hosts."""
2709 (options, args) = parser.parse_args(args)
2710 client = GClient.LoadCurrentConfig(options)
2711 if not client:
2712 raise gclient_utils.Error('client not configured; see \'gclient config\'')
2713 client.RunOnDeps(None, [])
2714 # Look at each first-level dependency of this gclient only.
2715 for dep in client.dependencies:
2716 bad_deps = dep.findDepsFromNotAllowedHosts()
2717 if not bad_deps:
2718 continue
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002719 print("There are deps from not allowed hosts in file %s" % dep.deps_file)
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00002720 for bad_dep in bad_deps:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002721 print("\t%s at %s" % (bad_dep.name, bad_dep.url))
2722 print("allowed_hosts:", ', '.join(dep.allowed_hosts))
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00002723 sys.stdout.flush()
2724 raise gclient_utils.Error(
2725 'dependencies from disallowed hosts; check your DEPS file.')
2726 return 0
2727
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002728class OptionParser(optparse.OptionParser):
szager@chromium.orge2e03202012-07-31 18:05:16 +00002729 gclientfile_default = os.environ.get('GCLIENT_FILE', '.gclient')
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002730
2731 def __init__(self, **kwargs):
2732 optparse.OptionParser.__init__(
2733 self, version='%prog ' + __version__, **kwargs)
2734
2735 # Some arm boards have issues with parallel sync.
2736 if platform.machine().startswith('arm'):
2737 jobs = 1
2738 else:
2739 jobs = max(8, gclient_utils.NumLocalCpus())
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002740
2741 self.add_option(
2742 '-j', '--jobs', default=jobs, type='int',
2743 help='Specify how many SCM commands can run in parallel; defaults to '
tnagel@chromium.orga2aaa632014-02-28 21:47:27 +00002744 '%default on this machine')
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002745 self.add_option(
2746 '-v', '--verbose', action='count', default=0,
2747 help='Produces additional output for diagnostics. Can be used up to '
2748 'three times for more logging info.')
2749 self.add_option(
2750 '--gclientfile', dest='config_filename',
2751 help='Specify an alternate %s file' % self.gclientfile_default)
2752 self.add_option(
2753 '--spec',
2754 help='create a gclient file containing the provided string. Due to '
2755 'Cygwin/Python brokenness, it can\'t contain any newlines.')
2756 self.add_option(
Aleksandr Derbenev9e8fb0e2017-08-01 20:18:31 +03002757 '--cache-dir',
2758 help='(git only) Cache all git repos into this dir and do '
2759 'shared clones from the cache, instead of cloning '
2760 'directly from the remote. (experimental)',
2761 default=os.environ.get('GCLIENT_CACHE_DIR'))
2762 self.add_option(
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002763 '--no-nag-max', default=False, action='store_true',
scottmg@chromium.orgf547c802013-09-27 17:55:26 +00002764 help='Ignored for backwards compatibility.')
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002765
2766 def parse_args(self, args=None, values=None):
2767 """Integrates standard options processing."""
2768 options, args = optparse.OptionParser.parse_args(self, args, values)
2769 levels = [logging.ERROR, logging.WARNING, logging.INFO, logging.DEBUG]
2770 logging.basicConfig(
2771 level=levels[min(options.verbose, len(levels) - 1)],
maruel@chromium.org0895b752011-08-26 20:40:33 +00002772 format='%(module)s(%(lineno)d) %(funcName)s:%(message)s')
szager@chromium.orge2e03202012-07-31 18:05:16 +00002773 if options.config_filename and options.spec:
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002774 self.error('Cannot specifiy both --gclientfile and --spec')
rdsmith@chromium.orgd9591f02014-02-05 19:28:20 +00002775 if (options.config_filename and
2776 options.config_filename != os.path.basename(options.config_filename)):
2777 self.error('--gclientfile target must be a filename, not a path')
szager@chromium.orge2e03202012-07-31 18:05:16 +00002778 if not options.config_filename:
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002779 options.config_filename = self.gclientfile_default
maruel@chromium.org0895b752011-08-26 20:40:33 +00002780 options.entries_filename = options.config_filename + '_entries'
2781 if options.jobs < 1:
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002782 self.error('--jobs must be 1 or higher')
maruel@chromium.org0895b752011-08-26 20:40:33 +00002783
2784 # These hacks need to die.
2785 if not hasattr(options, 'revisions'):
2786 # GClient.RunOnDeps expects it even if not applicable.
2787 options.revisions = []
smutae7ea312016-07-18 11:59:41 -07002788 if not hasattr(options, 'head'):
2789 options.head = None
maruel@chromium.org0895b752011-08-26 20:40:33 +00002790 if not hasattr(options, 'nohooks'):
2791 options.nohooks = True
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00002792 if not hasattr(options, 'noprehooks'):
2793 options.noprehooks = True
maruel@chromium.org0895b752011-08-26 20:40:33 +00002794 if not hasattr(options, 'deps_os'):
2795 options.deps_os = None
maruel@chromium.org0895b752011-08-26 20:40:33 +00002796 if not hasattr(options, 'force'):
2797 options.force = None
2798 return (options, args)
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002799
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002800
2801def disable_buffering():
2802 # Make stdout auto-flush so buildbot doesn't kill us during lengthy
2803 # operations. Python as a strong tendency to buffer sys.stdout.
2804 sys.stdout = gclient_utils.MakeFileAutoFlush(sys.stdout)
2805 # Make stdout annotated with the thread ids.
2806 sys.stdout = gclient_utils.MakeFileAnnotated(sys.stdout)
maruel@chromium.org0895b752011-08-26 20:40:33 +00002807
2808
sbc@chromium.org013731e2015-02-26 18:28:43 +00002809def main(argv):
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002810 """Doesn't parse the arguments here, just find the right subcommand to
2811 execute."""
maruel@chromium.org82798cb2012-02-23 18:16:12 +00002812 if sys.hexversion < 0x02060000:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002813 print(
maruel@chromium.org82798cb2012-02-23 18:16:12 +00002814 '\nYour python version %s is unsupported, please upgrade.\n' %
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002815 sys.version.split(' ', 1)[0],
2816 file=sys.stderr)
maruel@chromium.org82798cb2012-02-23 18:16:12 +00002817 return 2
bcwhite@chromium.org6683ab42013-02-11 16:13:47 +00002818 if not sys.executable:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002819 print(
2820 '\nPython cannot find the location of it\'s own executable.\n',
2821 file=sys.stderr)
bcwhite@chromium.org6683ab42013-02-11 16:13:47 +00002822 return 2
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002823 fix_encoding.fix_encoding()
2824 disable_buffering()
iannucci@chromium.org596cd5c2016-04-04 21:34:39 +00002825 setup_color.init()
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002826 dispatcher = subcommand.CommandDispatcher(__name__)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00002827 try:
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002828 return dispatcher.execute(OptionParser(), argv)
xusydoc@chromium.org2fd6c3f2013-05-03 21:57:55 +00002829 except KeyboardInterrupt:
2830 gclient_utils.GClientChildren.KillAllRemainingChildren()
2831 raise
vapier@chromium.orga81a56e2015-11-11 07:56:13 +00002832 except (gclient_utils.Error, subprocess2.CalledProcessError) as e:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002833 print('Error: %s' % str(e), file=sys.stderr)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00002834 return 1
borenet@google.com6a9b1682014-03-24 18:35:23 +00002835 finally:
2836 gclient_utils.PrintWarnings()
sbc@chromium.org013731e2015-02-26 18:28:43 +00002837 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002838
2839
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00002840if '__main__' == __name__:
sbc@chromium.org013731e2015-02-26 18:28:43 +00002841 try:
2842 sys.exit(main(sys.argv[1:]))
2843 except KeyboardInterrupt:
2844 sys.stderr.write('interrupted\n')
2845 sys.exit(1)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002846
2847# vim: ts=2:sw=2:tw=80:et: