blob: ed61ff8bf1ff90dbbb88afefe2f7c39214573db9 [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
Michael Mossce9f17f2018-01-31 13:16:35 -08002091 # Only include vars explicitly listed in the DEPS files or gclient solution,
2092 # not automatic, local overrides (i.e. not all of dep.get_vars()).
2093 hierarchy = dep.hierarchy(include_url=False)
Paweł Hajdan, Jra3b67ae2017-08-30 15:18:21 +02002094 for key, value in dep._vars.iteritems():
Paweł Hajdan, Jrc9353602017-08-02 17:52:08 +02002095 # Make sure there are no conflicting variables. It is fine however
2096 # to use same variable name, as long as the value is consistent.
2097 assert key not in self._vars or self._vars[key][1] == value
Michael Mossce9f17f2018-01-31 13:16:35 -08002098 self._vars[key] = (hierarchy, value)
2099 # Override explicit custom variables.
2100 for key, value in dep.custom_vars.iteritems():
2101 # Do custom_vars that don't correspond to DEPS vars ever make sense? DEPS
2102 # conditionals shouldn't be using vars that aren't also defined in the
2103 # DEPS (presubmit actually disallows this), so any new custom_var must be
2104 # unused in the DEPS, so no need to add it to the flattened output either.
2105 if key not in self._vars:
2106 continue
2107 # Don't "override" existing vars if it's actually the same value.
2108 elif self._vars[key][1] == value:
2109 continue
2110 # Anything else is overriding a default value from the DEPS.
2111 self._vars[key] = (hierarchy + ' [custom_var override]', value)
Paweł Hajdan, Jrfb022012017-07-06 18:00:08 +02002112
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002113 self._pre_deps_hooks.extend([(dep, hook) for hook in dep.pre_deps_hooks])
2114
Paweł Hajdan, Jradae2a62017-08-18 16:49:57 +02002115 if dep_os:
2116 if dep.deps_hooks:
2117 self._hooks_os.setdefault(dep_os, []).extend(
2118 [(dep, hook) for hook in dep.deps_hooks])
2119 else:
2120 self._hooks.extend([(dep, hook) for hook in dep.deps_hooks])
2121
Paweł Hajdan, Jrb0ad16e2017-08-03 15:33:21 +02002122 for sub_dep in dep.dependencies:
Paweł Hajdan, Jradae2a62017-08-18 16:49:57 +02002123 if dep_os:
2124 self._add_os_dep(sub_dep, dep_os)
2125 else:
2126 self._add_dep(sub_dep)
Paweł Hajdan, Jrb0ad16e2017-08-03 15:33:21 +02002127
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002128 for hook_os, os_hooks in dep.os_deps_hooks.iteritems():
2129 self._hooks_os.setdefault(hook_os, []).extend(
2130 [(dep, hook) for hook in os_hooks])
2131
Paweł Hajdan, Jradae2a62017-08-18 16:49:57 +02002132 for sub_dep_os, os_deps in dep.os_dependencies.iteritems():
Paweł Hajdan, Jre2deb1e2017-08-09 17:29:21 +02002133 for os_dep in os_deps:
Paweł Hajdan, Jradae2a62017-08-18 16:49:57 +02002134 self._add_os_dep(os_dep, sub_dep_os)
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002135
Paweł Hajdan, Jradae2a62017-08-18 16:49:57 +02002136 # Process recursedeps. |deps_by_name| is a map where keys are dependency
2137 # names, and values are maps of OS names to |Dependency| instances.
2138 # |None| in place of OS name means the dependency is not OS-specific.
2139 deps_by_name = dict((d.name, {None: d}) for d in dep.dependencies)
2140 for sub_dep_os, os_deps in dep.os_dependencies.iteritems():
Paweł Hajdan, Jrc9353602017-08-02 17:52:08 +02002141 for os_dep in os_deps:
Paweł Hajdan, Jradae2a62017-08-18 16:49:57 +02002142 assert sub_dep_os not in deps_by_name.get(os_dep.name, {}), (
2143 os_dep.name, sub_dep_os)
2144 deps_by_name.setdefault(os_dep.name, {})[sub_dep_os] = os_dep
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002145 for recurse_dep_name in (dep.recursedeps or []):
Paweł Hajdan, Jradae2a62017-08-18 16:49:57 +02002146 dep_info = deps_by_name[recurse_dep_name]
2147 for sub_dep_os, os_dep in dep_info.iteritems():
2148 self._flatten_dep(os_dep, dep_os=(sub_dep_os or dep_os))
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002149
2150
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002151def CMDflatten(parser, args):
2152 """Flattens the solutions into a single DEPS file."""
2153 parser.add_option('--output-deps', help='Path to the output DEPS file')
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02002154 parser.add_option(
Paweł Hajdan, Jrfeb01642017-09-12 15:50:46 +02002155 '--output-deps-files',
2156 help=('Path to the output metadata about DEPS files referenced by '
2157 'recursedeps.'))
2158 parser.add_option(
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02002159 '--pin-all-deps', action='store_true',
2160 help=('Pin all deps, even if not pinned in DEPS. CAVEAT: only does so '
2161 'for checked out deps, NOT deps_os.'))
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002162 options, args = parser.parse_args(args)
2163
Paweł Hajdan, Jr357415c2017-07-24 14:35:28 +02002164 options.do_not_merge_os_specific_entries = True
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002165 options.nohooks = True
Paweł Hajdan, Jr5ec77132017-08-16 19:21:06 +02002166 options.process_all_deps = True
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002167 client = GClient.LoadCurrentConfig(options)
2168
2169 # Only print progress if we're writing to a file. Otherwise, progress updates
2170 # could obscure intended output.
2171 code = client.RunOnDeps('flatten', args, progress=options.output_deps)
2172 if code != 0:
2173 return code
2174
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02002175 flattener = Flattener(client, pin_all_deps=options.pin_all_deps)
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002176
2177 if options.output_deps:
2178 with open(options.output_deps, 'w') as f:
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002179 f.write(flattener.deps_string)
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002180 else:
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002181 print(flattener.deps_string)
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002182
Paweł Hajdan, Jrfeb01642017-09-12 15:50:46 +02002183 deps_files = [{'url': d[0], 'deps_file': d[1]}
2184 for d in sorted(flattener.deps_files)]
2185 if options.output_deps_files:
2186 with open(options.output_deps_files, 'w') as f:
2187 json.dump(deps_files, f)
2188
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002189 return 0
2190
2191
Paweł Hajdan, Jr3c2aa832017-06-07 20:22:16 +02002192def _GNSettingsToLines(gn_args_file, gn_args):
2193 s = []
2194 if gn_args_file:
2195 s.extend([
2196 'gclient_gn_args_file = "%s"' % gn_args_file,
2197 'gclient_gn_args = %r' % gn_args,
2198 ])
2199 return s
2200
2201
Paweł Hajdan, Jr6014b562017-06-30 17:43:42 +02002202def _AllowedHostsToLines(allowed_hosts):
2203 """Converts |allowed_hosts| set to list of lines for output."""
2204 if not allowed_hosts:
2205 return []
2206 s = ['allowed_hosts = [']
2207 for h in sorted(allowed_hosts):
2208 s.append(' "%s",' % h)
2209 s.extend([']', ''])
2210 return s
2211
2212
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002213def _DepsToLines(deps):
2214 """Converts |deps| dict to list of lines for output."""
Paweł Hajdan, Jr5b593352017-06-29 18:37:45 +02002215 if not deps:
2216 return []
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002217 s = ['deps = {']
John Budorick0f7b2002018-01-19 15:46:17 -08002218 for _, dep in sorted(deps.iteritems()):
2219 s.extend(dep.ToLines())
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002220 s.extend(['}', ''])
2221 return s
2222
2223
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02002224def _DepsOsToLines(deps_os):
2225 """Converts |deps_os| dict to list of lines for output."""
Paweł Hajdan, Jr5b593352017-06-29 18:37:45 +02002226 if not deps_os:
2227 return []
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02002228 s = ['deps_os = {']
2229 for dep_os, os_deps in sorted(deps_os.iteritems()):
2230 s.append(' "%s": {' % dep_os)
Paweł Hajdan, Jrad30de62017-06-26 18:51:58 +02002231 for name, dep in sorted(os_deps.iteritems()):
Paweł Hajdan, Jr78ce24e2017-10-03 17:09:13 +02002232 condition_part = ([' "condition": %r,' % dep.condition]
Paweł Hajdan, Jrad30de62017-06-26 18:51:58 +02002233 if dep.condition else [])
2234 s.extend([
2235 ' # %s' % dep.hierarchy(include_url=False),
2236 ' "%s": {' % (name,),
Paweł Hajdan, Jrde86ab32017-08-10 13:55:16 +02002237 ' "url": "%s",' % (dep.raw_url,),
Paweł Hajdan, Jrad30de62017-06-26 18:51:58 +02002238 ] + condition_part + [
2239 ' },',
2240 '',
2241 ])
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02002242 s.extend([' },', ''])
2243 s.extend(['}', ''])
2244 return s
2245
2246
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002247def _HooksToLines(name, hooks):
2248 """Converts |hooks| list to list of lines for output."""
Paweł Hajdan, Jr5b593352017-06-29 18:37:45 +02002249 if not hooks:
2250 return []
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002251 s = ['%s = [' % name]
2252 for dep, hook in hooks:
2253 s.extend([
2254 ' # %s' % dep.hierarchy(include_url=False),
2255 ' {',
2256 ])
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +02002257 if hook.name is not None:
2258 s.append(' "name": "%s",' % hook.name)
2259 if hook.pattern is not None:
2260 s.append(' "pattern": "%s",' % hook.pattern)
Paweł Hajdan, Jrecf53fe2017-09-29 18:28:49 +02002261 if hook.condition is not None:
Paweł Hajdan, Jr78ce24e2017-10-03 17:09:13 +02002262 s.append(' "condition": %r,' % hook.condition)
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002263 s.extend(
Paweł Hajdan, Jrc9364392017-06-14 17:11:56 +02002264 # Hooks run in the parent directory of their dep.
Paweł Hajdan, Jrad30de62017-06-26 18:51:58 +02002265 [' "cwd": "%s",' % os.path.normpath(os.path.dirname(dep.name))] +
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002266 [' "action": ['] +
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +02002267 [' "%s",' % arg for arg in hook.action] +
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002268 [' ]', ' },', '']
2269 )
2270 s.extend([']', ''])
2271 return s
2272
2273
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +02002274def _HooksOsToLines(hooks_os):
2275 """Converts |hooks| list to list of lines for output."""
Paweł Hajdan, Jr5b593352017-06-29 18:37:45 +02002276 if not hooks_os:
2277 return []
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +02002278 s = ['hooks_os = {']
2279 for hook_os, os_hooks in hooks_os.iteritems():
Michael Moss017bcf62017-06-28 15:26:38 -07002280 s.append(' "%s": [' % hook_os)
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +02002281 for dep, hook in os_hooks:
2282 s.extend([
2283 ' # %s' % dep.hierarchy(include_url=False),
2284 ' {',
2285 ])
2286 if hook.name is not None:
2287 s.append(' "name": "%s",' % hook.name)
2288 if hook.pattern is not None:
2289 s.append(' "pattern": "%s",' % hook.pattern)
Paweł Hajdan, Jrecf53fe2017-09-29 18:28:49 +02002290 if hook.condition is not None:
Paweł Hajdan, Jr78ce24e2017-10-03 17:09:13 +02002291 s.append(' "condition": %r,' % hook.condition)
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +02002292 s.extend(
2293 # Hooks run in the parent directory of their dep.
2294 [' "cwd": "%s",' % os.path.normpath(os.path.dirname(dep.name))] +
2295 [' "action": ['] +
2296 [' "%s",' % arg for arg in hook.action] +
2297 [' ]', ' },', '']
2298 )
Michael Moss017bcf62017-06-28 15:26:38 -07002299 s.extend([' ],', ''])
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +02002300 s.extend(['}', ''])
2301 return s
2302
2303
Paweł Hajdan, Jrfb022012017-07-06 18:00:08 +02002304def _VarsToLines(variables):
2305 """Converts |variables| dict to list of lines for output."""
2306 if not variables:
2307 return []
2308 s = ['vars = {']
2309 for key, tup in sorted(variables.iteritems()):
Michael Mossce9f17f2018-01-31 13:16:35 -08002310 hierarchy, value = tup
Paweł Hajdan, Jrfb022012017-07-06 18:00:08 +02002311 s.extend([
Michael Mossce9f17f2018-01-31 13:16:35 -08002312 ' # %s' % hierarchy,
Paweł Hajdan, Jrfb022012017-07-06 18:00:08 +02002313 ' "%s": %r,' % (key, value),
2314 '',
2315 ])
2316 s.extend(['}', ''])
2317 return s
2318
2319
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00002320def CMDgrep(parser, args):
2321 """Greps through git repos managed by gclient.
2322
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002323 Runs 'git grep [args...]' for each module.
2324 """
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00002325 # We can't use optparse because it will try to parse arguments sent
2326 # to git grep and throw an error. :-(
2327 if not args or re.match('(-h|--help)$', args[0]):
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002328 print(
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00002329 'Usage: gclient grep [-j <N>] git-grep-args...\n\n'
2330 'Example: "gclient grep -j10 -A2 RefCountedBase" runs\n"git grep '
2331 '-A2 RefCountedBase" on each of gclient\'s git\nrepos with up to '
2332 '10 jobs.\n\nBonus: page output by appending "|& less -FRSX" to the'
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002333 ' end of your query.',
2334 file=sys.stderr)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00002335 return 1
2336
2337 jobs_arg = ['--jobs=1']
2338 if re.match(r'(-j|--jobs=)\d+$', args[0]):
2339 jobs_arg, args = args[:1], args[1:]
2340 elif re.match(r'(-j|--jobs)$', args[0]):
2341 jobs_arg, args = args[:2], args[2:]
2342
2343 return CMDrecurse(
2344 parser,
2345 jobs_arg + ['--ignore', '--prepend-dir', '--no-progress', '--scm=git',
2346 'git', 'grep', '--null', '--color=Always'] + args)
davidbarr@chromium.org12f944e2012-03-01 02:18:31 +00002347
2348
stip@chromium.orga735da22015-04-29 23:18:20 +00002349def CMDroot(parser, args):
2350 """Outputs the solution root (or current dir if there isn't one)."""
2351 (options, args) = parser.parse_args(args)
2352 client = GClient.LoadCurrentConfig(options)
2353 if client:
2354 print(os.path.abspath(client.root_dir))
2355 else:
2356 print(os.path.abspath('.'))
2357
2358
agablea98a6cd2016-11-15 14:30:10 -08002359@subcommand.usage('[url]')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002360def CMDconfig(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002361 """Creates a .gclient file in the current directory.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00002362
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002363 This specifies the configuration for further commands. After update/sync,
2364 top-level DEPS files in each module are read to determine dependent
2365 modules to operate on as well. If optional [url] parameter is
2366 provided, then configuration is read from a specified Subversion server
2367 URL.
2368 """
szager@chromium.orge2e03202012-07-31 18:05:16 +00002369 # We do a little dance with the --gclientfile option. 'gclient config' is the
2370 # only command where it's acceptable to have both '--gclientfile' and '--spec'
2371 # arguments. So, we temporarily stash any --gclientfile parameter into
2372 # options.output_config_file until after the (gclientfile xor spec) error
2373 # check.
2374 parser.remove_option('--gclientfile')
2375 parser.add_option('--gclientfile', dest='output_config_file',
2376 help='Specify an alternate .gclient file')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002377 parser.add_option('--name',
2378 help='overrides the default name for the solution')
nsylvain@google.comefc80932011-05-31 21:27:56 +00002379 parser.add_option('--deps-file', default='DEPS',
David Benjamin105e11e2017-10-16 10:39:35 -04002380 help='overrides the default name for the DEPS file for the '
nsylvain@google.comefc80932011-05-31 21:27:56 +00002381 'main solutions and all sub-dependencies')
smutae7ea312016-07-18 11:59:41 -07002382 parser.add_option('--unmanaged', action='store_true', default=False,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00002383 help='overrides the default behavior to make it possible '
smutae7ea312016-07-18 11:59:41 -07002384 'to have the main solution untouched by gclient '
2385 '(gclient will check out unmanaged dependencies but '
2386 'will never sync them)')
Paweł Hajdan, Jr3ba2a7c2017-10-04 19:24:46 +02002387 parser.add_option('--custom-var', action='append', dest='custom_vars',
2388 default=[],
2389 help='overrides variables; key=value syntax')
szager@chromium.orge2e03202012-07-31 18:05:16 +00002390 parser.set_defaults(config_filename=None)
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002391 (options, args) = parser.parse_args(args)
szager@chromium.orge2e03202012-07-31 18:05:16 +00002392 if options.output_config_file:
2393 setattr(options, 'config_filename', getattr(options, 'output_config_file'))
maruel@chromium.org5fc2a332010-05-26 19:37:15 +00002394 if ((options.spec and args) or len(args) > 2 or
2395 (not options.spec and not args)):
2396 parser.error('Inconsistent arguments. Use either --spec or one or 2 args')
2397
Paweł Hajdan, Jr3ba2a7c2017-10-04 19:24:46 +02002398 custom_vars = {}
2399 for arg in options.custom_vars:
2400 kv = arg.split('=', 1)
2401 if len(kv) != 2:
2402 parser.error('Invalid --custom-var argument: %r' % arg)
2403 custom_vars[kv[0]] = gclient_eval.EvaluateCondition(kv[1], {})
2404
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002405 client = GClient('.', options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002406 if options.spec:
2407 client.SetConfig(options.spec)
2408 else:
maruel@chromium.org1ab7ffc2009-06-03 17:21:37 +00002409 base_url = args[0].rstrip('/')
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00002410 if not options.name:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002411 name = base_url.split('/')[-1]
nsylvain@google.com12649ef2011-06-01 17:11:20 +00002412 if name.endswith('.git'):
2413 name = name[:-4]
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00002414 else:
2415 # specify an alternate relpath for the given URL.
2416 name = options.name
agable@chromium.orgf2214672015-10-27 21:02:48 +00002417 if not os.path.abspath(os.path.join(os.getcwd(), name)).startswith(
2418 os.getcwd()):
2419 parser.error('Do not pass a relative path for --name.')
2420 if any(x in ('..', '.', '/', '\\') for x in name.split(os.sep)):
2421 parser.error('Do not include relative path components in --name.')
2422
nsylvain@google.comefc80932011-05-31 21:27:56 +00002423 deps_file = options.deps_file
agablea98a6cd2016-11-15 14:30:10 -08002424 client.SetDefaultConfig(name, deps_file, base_url,
smutae7ea312016-07-18 11:59:41 -07002425 managed=not options.unmanaged,
Paweł Hajdan, Jr3ba2a7c2017-10-04 19:24:46 +02002426 cache_dir=options.cache_dir,
2427 custom_vars=custom_vars)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002428 client.SaveConfig()
maruel@chromium.org79692d62010-05-14 18:57:13 +00002429 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002430
2431
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002432@subcommand.epilog("""Example:
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002433 gclient pack > patch.txt
2434 generate simple patch for configured client and dependences
2435""")
2436def CMDpack(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002437 """Generates a patch which can be applied at the root of the tree.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00002438
agabled437d762016-10-17 09:35:11 -07002439 Internally, runs 'git diff' on each checked out module and
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002440 dependencies, and performs minimal postprocessing of the output. The
2441 resulting patch is printed to stdout and can be applied to a freshly
2442 checked out tree via 'patch -p0 < patchfile'.
2443 """
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002444 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2445 help='override deps for the specified (comma-separated) '
2446 'platform(s); \'all\' will process all deps_os '
2447 'references')
haitao.feng@intel.com306080c2012-05-04 13:11:29 +00002448 parser.remove_option('--jobs')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002449 (options, args) = parser.parse_args(args)
iannucci@chromium.org50395ea2013-04-04 04:47:42 +00002450 # Force jobs to 1 so the stdout is not annotated with the thread ids
haitao.feng@intel.com306080c2012-05-04 13:11:29 +00002451 options.jobs = 1
kbr@google.comab318592009-09-04 00:54:55 +00002452 client = GClient.LoadCurrentConfig(options)
2453 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002454 raise gclient_utils.Error('client not configured; see \'gclient config\'')
kbr@google.comab318592009-09-04 00:54:55 +00002455 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002456 client.PrintLocationAndContents()
kbr@google.comab318592009-09-04 00:54:55 +00002457 return client.RunOnDeps('pack', args)
2458
2459
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002460def CMDstatus(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002461 """Shows modification status for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002462 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2463 help='override deps for the specified (comma-separated) '
2464 'platform(s); \'all\' will process all deps_os '
2465 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002466 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002467 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002468 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002469 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002470 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002471 client.PrintLocationAndContents()
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002472 return client.RunOnDeps('status', args)
2473
2474
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002475@subcommand.epilog("""Examples:
maruel@chromium.org79692d62010-05-14 18:57:13 +00002476 gclient sync
2477 update files from SCM according to current configuration,
2478 *for modules which have changed since last update or sync*
2479 gclient sync --force
2480 update files from SCM according to current configuration, for
2481 all modules (useful for recovering files deleted from local copy)
2482 gclient sync --revision src@31000
2483 update src directory to r31000
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002484
2485JSON output format:
2486If the --output-json option is specified, the following document structure will
2487be emitted to the provided file. 'null' entries may occur for subprojects which
2488are present in the gclient solution, but were not processed (due to custom_deps,
2489os_deps, etc.)
2490
2491{
2492 "solutions" : {
2493 "<name>": { # <name> is the posix-normalized path to the solution.
agabled437d762016-10-17 09:35:11 -07002494 "revision": [<git id hex string>|null],
2495 "scm": ["git"|null],
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002496 }
2497 }
2498}
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002499""")
2500def CMDsync(parser, args):
2501 """Checkout/update all modules."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002502 parser.add_option('-f', '--force', action='store_true',
2503 help='force update even for unchanged modules')
2504 parser.add_option('-n', '--nohooks', action='store_true',
2505 help='don\'t run hooks after the update is complete')
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00002506 parser.add_option('-p', '--noprehooks', action='store_true',
2507 help='don\'t run pre-DEPS hooks', default=False)
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002508 parser.add_option('-r', '--revision', action='append',
2509 dest='revisions', metavar='REV', default=[],
2510 help='Enforces revision/hash for the solutions with the '
2511 'format src@rev. The src@ part is optional and can be '
2512 'skipped. -r can be used multiple times when .gclient '
2513 'has multiple solutions configured and will work even '
agablea98a6cd2016-11-15 14:30:10 -08002514 'if the src@ part is skipped.')
maruel@chromium.org794207e2013-03-08 15:29:43 +00002515 parser.add_option('--with_branch_heads', action='store_true',
2516 help='Clone git "branch_heads" refspecs in addition to '
2517 'the default refspecs. This adds about 1/2GB to a '
2518 'full checkout. (git only)')
szager@chromium.org8d3348f2014-08-19 22:49:16 +00002519 parser.add_option('--with_tags', action='store_true',
2520 help='Clone git tags in addition to the default refspecs.')
agable2697cd12016-06-28 10:23:53 -07002521 parser.add_option('-H', '--head', action='store_true',
agablea98a6cd2016-11-15 14:30:10 -08002522 help='DEPRECATED: only made sense with safesync urls.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002523 parser.add_option('-D', '--delete_unversioned_trees', action='store_true',
steveblock@chromium.org98e69452012-02-16 16:36:43 +00002524 help='Deletes from the working copy any dependencies that '
2525 'have been removed since the last sync, as long as '
2526 'there are no local modifications. When used with '
2527 '--force, such dependencies are removed even if they '
2528 'have local modifications. When used with --reset, '
2529 'all untracked directories are removed from the '
tnagel@chromium.orga2aaa632014-02-28 21:47:27 +00002530 'working copy, excluding those which are explicitly '
steveblock@chromium.org98e69452012-02-16 16:36:43 +00002531 'ignored in the repository.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002532 parser.add_option('-R', '--reset', action='store_true',
2533 help='resets any local changes before updating (git only)')
bauerb@chromium.org2aad1b22011-07-22 12:00:41 +00002534 parser.add_option('-M', '--merge', action='store_true',
2535 help='merge upstream changes instead of trying to '
2536 'fast-forward or rebase')
dnj@chromium.org5b23e872015-02-20 21:25:57 +00002537 parser.add_option('-A', '--auto_rebase', action='store_true',
2538 help='Automatically rebase repositories against local '
2539 'checkout during update (git only).')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002540 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2541 help='override deps for the specified (comma-separated) '
2542 'platform(s); \'all\' will process all deps_os '
2543 'references')
Paweł Hajdan, Jr357415c2017-07-24 14:35:28 +02002544 # TODO(phajdan.jr): use argparse.SUPPRESS to hide internal flags.
2545 parser.add_option('--do-not-merge-os-specific-entries', action='store_true',
2546 help='INTERNAL ONLY - disables merging of deps_os and '
2547 'hooks_os to dependencies and hooks')
Paweł Hajdan, Jr5ec77132017-08-16 19:21:06 +02002548 parser.add_option('--process-all-deps', action='store_true',
2549 help='Check out all deps, even for different OS-es, '
2550 'or with conditions evaluating to false')
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +00002551 parser.add_option('--upstream', action='store_true',
2552 help='Make repo state match upstream branch.')
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002553 parser.add_option('--output-json',
2554 help='Output a json document to this path containing '
2555 'summary information about the sync.')
primiano@chromium.org5439ea52014-08-06 17:18:18 +00002556 parser.add_option('--no-history', action='store_true',
2557 help='GIT ONLY - Reduces the size/time of the checkout at '
2558 'the cost of no history. Requires Git 1.9+')
hinoka@chromium.org46b87412014-05-15 00:42:05 +00002559 parser.add_option('--shallow', action='store_true',
2560 help='GIT ONLY - Do a shallow clone into the cache dir. '
2561 'Requires Git 1.9+')
e.hakkinen@samsung.come8bc1aa2015-04-08 08:00:37 +00002562 parser.add_option('--no_bootstrap', '--no-bootstrap',
2563 action='store_true',
2564 help='Don\'t bootstrap from Google Storage.')
Vadim Shtayura08049e22017-10-11 00:14:52 +00002565 parser.add_option('--ignore_locks', action='store_true',
2566 help='GIT ONLY - Ignore cache locks.')
iannucci@chromium.org30a07982016-04-07 21:35:19 +00002567 parser.add_option('--break_repo_locks', action='store_true',
2568 help='GIT ONLY - Forcibly remove repo locks (e.g. '
2569 'index.lock). This should only be used if you know for '
2570 'certain that this invocation of gclient is the only '
2571 'thing operating on the git repos (e.g. on a bot).')
Vadim Shtayura08049e22017-10-11 00:14:52 +00002572 parser.add_option('--lock_timeout', type='int', default=5000,
2573 help='GIT ONLY - Deadline (in seconds) to wait for git '
2574 'cache lock to become available. Default is %default.')
agabled437d762016-10-17 09:35:11 -07002575 # TODO(agable): Remove these when the oldest CrOS release milestone is M56.
2576 parser.add_option('-t', '--transitive', action='store_true',
2577 help='DEPRECATED: This is a no-op.')
sdefresne69b1be12016-10-18 05:48:02 -07002578 parser.add_option('-m', '--manually_grab_svn_rev', action='store_true',
agabled437d762016-10-17 09:35:11 -07002579 help='DEPRECATED: This is a no-op.')
Paweł Hajdan, Jr7c7b5592017-05-23 15:06:05 +02002580 # TODO(phajdan.jr): Remove validation options once default (crbug/570091).
Paweł Hajdan, Jr694773d2017-05-29 16:06:23 +02002581 parser.add_option('--validate-syntax', action='store_true', default=True,
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +02002582 help='Validate the .gclient and DEPS syntax')
Paweł Hajdan, Jr7c7b5592017-05-23 15:06:05 +02002583 parser.add_option('--disable-syntax-validation', action='store_false',
2584 dest='validate_syntax',
2585 help='Disable validation of .gclient and DEPS syntax.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002586 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002587 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002588
2589 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002590 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002591
smutae7ea312016-07-18 11:59:41 -07002592 if options.revisions and options.head:
2593 # TODO(maruel): Make it a parser.error if it doesn't break any builder.
2594 print('Warning: you cannot use both --head and --revision')
2595
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002596 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002597 client.PrintLocationAndContents()
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002598 ret = client.RunOnDeps('update', args)
2599 if options.output_json:
2600 slns = {}
2601 for d in client.subtree(True):
2602 normed = d.name.replace('\\', '/').rstrip('/') + '/'
2603 slns[normed] = {
2604 'revision': d.got_revision,
2605 'scm': d.used_scm.name if d.used_scm else None,
hinoka@chromium.org17db9052014-05-10 01:11:29 +00002606 'url': str(d.url) if d.url else None,
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002607 }
2608 with open(options.output_json, 'wb') as f:
2609 json.dump({'solutions': slns}, f)
2610 return ret
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002611
2612
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002613CMDupdate = CMDsync
2614
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002615
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +02002616def CMDvalidate(parser, args):
2617 """Validates the .gclient and DEPS syntax."""
2618 options, args = parser.parse_args(args)
2619 options.validate_syntax = True
2620 client = GClient.LoadCurrentConfig(options)
2621 rv = client.RunOnDeps('validate', args)
2622 if rv == 0:
2623 print('validate: SUCCESS')
2624 else:
2625 print('validate: FAILURE')
2626 return rv
2627
2628
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002629def CMDdiff(parser, args):
2630 """Displays local diff for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002631 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2632 help='override deps for the specified (comma-separated) '
2633 'platform(s); \'all\' will process all deps_os '
2634 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002635 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002636 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002637 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002638 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002639 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002640 client.PrintLocationAndContents()
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002641 return client.RunOnDeps('diff', args)
2642
2643
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002644def CMDrevert(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002645 """Reverts all modifications in every dependencies.
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00002646
2647 That's the nuclear option to get back to a 'clean' state. It removes anything
agabled437d762016-10-17 09:35:11 -07002648 that shows up in git status."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002649 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2650 help='override deps for the specified (comma-separated) '
2651 'platform(s); \'all\' will process all deps_os '
2652 'references')
2653 parser.add_option('-n', '--nohooks', action='store_true',
2654 help='don\'t run hooks after the revert is complete')
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00002655 parser.add_option('-p', '--noprehooks', action='store_true',
2656 help='don\'t run pre-DEPS hooks', default=False)
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +00002657 parser.add_option('--upstream', action='store_true',
2658 help='Make repo state match upstream branch.')
iannucci@chromium.orgbf525dc2016-04-07 22:00:28 +00002659 parser.add_option('--break_repo_locks', action='store_true',
2660 help='GIT ONLY - Forcibly remove repo locks (e.g. '
2661 'index.lock). This should only be used if you know for '
2662 'certain that this invocation of gclient is the only '
2663 'thing operating on the git repos (e.g. on a bot).')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002664 (options, args) = parser.parse_args(args)
2665 # --force is implied.
2666 options.force = True
steveblock@chromium.org98e69452012-02-16 16:36:43 +00002667 options.reset = False
2668 options.delete_unversioned_trees = False
agablec903d732016-07-26 09:07:24 -07002669 options.merge = False
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 return client.RunOnDeps('revert', args)
2674
2675
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002676def CMDrunhooks(parser, args):
2677 """Runs hooks for files that have been modified in the local working copy."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002678 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2679 help='override deps for the specified (comma-separated) '
2680 'platform(s); \'all\' will process all deps_os '
2681 'references')
2682 parser.add_option('-f', '--force', action='store_true', default=True,
2683 help='Deprecated. No effect.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002684 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002685 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002686 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002687 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002688 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002689 client.PrintLocationAndContents()
maruel@chromium.org5df6a462009-08-28 18:52:26 +00002690 options.force = True
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002691 options.nohooks = False
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002692 return client.RunOnDeps('runhooks', args)
2693
2694
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002695def CMDrevinfo(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002696 """Outputs revision info mapping for the client and its dependencies.
maruel@chromium.org9eda4112010-06-11 18:56:10 +00002697
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002698 This allows the capture of an overall 'revision' for the source tree that
maruel@chromium.org9eda4112010-06-11 18:56:10 +00002699 can be used to reproduce the same tree in the future. It is only useful for
agabled437d762016-10-17 09:35:11 -07002700 'unpinned dependencies', i.e. DEPS/deps references without a git hash.
2701 A git branch name isn't 'pinned' since the actual commit can change.
maruel@chromium.org9eda4112010-06-11 18:56:10 +00002702 """
2703 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2704 help='override deps for the specified (comma-separated) '
2705 'platform(s); \'all\' will process all deps_os '
2706 'references')
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00002707 parser.add_option('-a', '--actual', action='store_true',
2708 help='gets the actual checked out revisions instead of the '
2709 'ones specified in the DEPS and .gclient files')
maruel@chromium.org9eda4112010-06-11 18:56:10 +00002710 parser.add_option('-s', '--snapshot', action='store_true',
2711 help='creates a snapshot .gclient file of the current '
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00002712 'version of all repositories to reproduce the tree, '
2713 'implies -a')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002714 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002715 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002716 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002717 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002718 client.PrintRevInfo()
maruel@chromium.org79692d62010-05-14 18:57:13 +00002719 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002720
2721
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00002722def CMDverify(parser, args):
2723 """Verifies the DEPS file deps are only from allowed_hosts."""
2724 (options, args) = parser.parse_args(args)
2725 client = GClient.LoadCurrentConfig(options)
2726 if not client:
2727 raise gclient_utils.Error('client not configured; see \'gclient config\'')
2728 client.RunOnDeps(None, [])
2729 # Look at each first-level dependency of this gclient only.
2730 for dep in client.dependencies:
2731 bad_deps = dep.findDepsFromNotAllowedHosts()
2732 if not bad_deps:
2733 continue
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002734 print("There are deps from not allowed hosts in file %s" % dep.deps_file)
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00002735 for bad_dep in bad_deps:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002736 print("\t%s at %s" % (bad_dep.name, bad_dep.url))
2737 print("allowed_hosts:", ', '.join(dep.allowed_hosts))
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00002738 sys.stdout.flush()
2739 raise gclient_utils.Error(
2740 'dependencies from disallowed hosts; check your DEPS file.')
2741 return 0
2742
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002743class OptionParser(optparse.OptionParser):
szager@chromium.orge2e03202012-07-31 18:05:16 +00002744 gclientfile_default = os.environ.get('GCLIENT_FILE', '.gclient')
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002745
2746 def __init__(self, **kwargs):
2747 optparse.OptionParser.__init__(
2748 self, version='%prog ' + __version__, **kwargs)
2749
2750 # Some arm boards have issues with parallel sync.
2751 if platform.machine().startswith('arm'):
2752 jobs = 1
2753 else:
2754 jobs = max(8, gclient_utils.NumLocalCpus())
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002755
2756 self.add_option(
2757 '-j', '--jobs', default=jobs, type='int',
2758 help='Specify how many SCM commands can run in parallel; defaults to '
tnagel@chromium.orga2aaa632014-02-28 21:47:27 +00002759 '%default on this machine')
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002760 self.add_option(
2761 '-v', '--verbose', action='count', default=0,
2762 help='Produces additional output for diagnostics. Can be used up to '
2763 'three times for more logging info.')
2764 self.add_option(
2765 '--gclientfile', dest='config_filename',
2766 help='Specify an alternate %s file' % self.gclientfile_default)
2767 self.add_option(
2768 '--spec',
2769 help='create a gclient file containing the provided string. Due to '
2770 'Cygwin/Python brokenness, it can\'t contain any newlines.')
2771 self.add_option(
Aleksandr Derbenev9e8fb0e2017-08-01 20:18:31 +03002772 '--cache-dir',
2773 help='(git only) Cache all git repos into this dir and do '
2774 'shared clones from the cache, instead of cloning '
2775 'directly from the remote. (experimental)',
2776 default=os.environ.get('GCLIENT_CACHE_DIR'))
2777 self.add_option(
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002778 '--no-nag-max', default=False, action='store_true',
scottmg@chromium.orgf547c802013-09-27 17:55:26 +00002779 help='Ignored for backwards compatibility.')
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002780
2781 def parse_args(self, args=None, values=None):
2782 """Integrates standard options processing."""
2783 options, args = optparse.OptionParser.parse_args(self, args, values)
2784 levels = [logging.ERROR, logging.WARNING, logging.INFO, logging.DEBUG]
2785 logging.basicConfig(
2786 level=levels[min(options.verbose, len(levels) - 1)],
maruel@chromium.org0895b752011-08-26 20:40:33 +00002787 format='%(module)s(%(lineno)d) %(funcName)s:%(message)s')
szager@chromium.orge2e03202012-07-31 18:05:16 +00002788 if options.config_filename and options.spec:
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002789 self.error('Cannot specifiy both --gclientfile and --spec')
rdsmith@chromium.orgd9591f02014-02-05 19:28:20 +00002790 if (options.config_filename and
2791 options.config_filename != os.path.basename(options.config_filename)):
2792 self.error('--gclientfile target must be a filename, not a path')
szager@chromium.orge2e03202012-07-31 18:05:16 +00002793 if not options.config_filename:
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002794 options.config_filename = self.gclientfile_default
maruel@chromium.org0895b752011-08-26 20:40:33 +00002795 options.entries_filename = options.config_filename + '_entries'
2796 if options.jobs < 1:
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002797 self.error('--jobs must be 1 or higher')
maruel@chromium.org0895b752011-08-26 20:40:33 +00002798
2799 # These hacks need to die.
2800 if not hasattr(options, 'revisions'):
2801 # GClient.RunOnDeps expects it even if not applicable.
2802 options.revisions = []
smutae7ea312016-07-18 11:59:41 -07002803 if not hasattr(options, 'head'):
2804 options.head = None
maruel@chromium.org0895b752011-08-26 20:40:33 +00002805 if not hasattr(options, 'nohooks'):
2806 options.nohooks = True
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00002807 if not hasattr(options, 'noprehooks'):
2808 options.noprehooks = True
maruel@chromium.org0895b752011-08-26 20:40:33 +00002809 if not hasattr(options, 'deps_os'):
2810 options.deps_os = None
maruel@chromium.org0895b752011-08-26 20:40:33 +00002811 if not hasattr(options, 'force'):
2812 options.force = None
2813 return (options, args)
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002814
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002815
2816def disable_buffering():
2817 # Make stdout auto-flush so buildbot doesn't kill us during lengthy
2818 # operations. Python as a strong tendency to buffer sys.stdout.
2819 sys.stdout = gclient_utils.MakeFileAutoFlush(sys.stdout)
2820 # Make stdout annotated with the thread ids.
2821 sys.stdout = gclient_utils.MakeFileAnnotated(sys.stdout)
maruel@chromium.org0895b752011-08-26 20:40:33 +00002822
2823
sbc@chromium.org013731e2015-02-26 18:28:43 +00002824def main(argv):
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002825 """Doesn't parse the arguments here, just find the right subcommand to
2826 execute."""
maruel@chromium.org82798cb2012-02-23 18:16:12 +00002827 if sys.hexversion < 0x02060000:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002828 print(
maruel@chromium.org82798cb2012-02-23 18:16:12 +00002829 '\nYour python version %s is unsupported, please upgrade.\n' %
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002830 sys.version.split(' ', 1)[0],
2831 file=sys.stderr)
maruel@chromium.org82798cb2012-02-23 18:16:12 +00002832 return 2
bcwhite@chromium.org6683ab42013-02-11 16:13:47 +00002833 if not sys.executable:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002834 print(
2835 '\nPython cannot find the location of it\'s own executable.\n',
2836 file=sys.stderr)
bcwhite@chromium.org6683ab42013-02-11 16:13:47 +00002837 return 2
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002838 fix_encoding.fix_encoding()
2839 disable_buffering()
iannucci@chromium.org596cd5c2016-04-04 21:34:39 +00002840 setup_color.init()
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002841 dispatcher = subcommand.CommandDispatcher(__name__)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00002842 try:
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002843 return dispatcher.execute(OptionParser(), argv)
xusydoc@chromium.org2fd6c3f2013-05-03 21:57:55 +00002844 except KeyboardInterrupt:
2845 gclient_utils.GClientChildren.KillAllRemainingChildren()
2846 raise
vapier@chromium.orga81a56e2015-11-11 07:56:13 +00002847 except (gclient_utils.Error, subprocess2.CalledProcessError) as e:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002848 print('Error: %s' % str(e), file=sys.stderr)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00002849 return 1
borenet@google.com6a9b1682014-03-24 18:35:23 +00002850 finally:
2851 gclient_utils.PrintWarnings()
sbc@chromium.org013731e2015-02-26 18:28:43 +00002852 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002853
2854
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00002855if '__main__' == __name__:
sbc@chromium.org013731e2015-02-26 18:28:43 +00002856 try:
2857 sys.exit(main(sys.argv[1:]))
2858 except KeyboardInterrupt:
2859 sys.stderr.write('interrupted\n')
2860 sys.exit(1)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002861
2862# vim: ts=2:sw=2:tw=80:et: