blob: fd46eceee214d37695ced254e9e330a8059ac7c0 [file] [log] [blame]
iannucci@chromium.org405b87e2015-11-12 18:08:34 +00001#!/usr/bin/env python
thakis@chromium.org4f474b62012-01-18 01:31:29 +00002# Copyright (c) 2012 The Chromium Authors. All rights reserved.
maruel@chromium.orgba551772010-02-03 18:21:42 +00003# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00005
agabled437d762016-10-17 09:35:11 -07006"""Meta checkout dependency manager for Git."""
maruel@chromium.org39c0b222013-08-17 16:57:01 +00007# Files
8# .gclient : Current client configuration, written by 'config' command.
9# Format is a Python script defining 'solutions', a list whose
10# entries each are maps binding the strings "name" and "url"
11# to strings specifying the name and location of the client
12# module, as well as "custom_deps" to a map similar to the
13# deps section of the DEPS file below, as well as
14# "custom_hooks" to a list similar to the hooks sections of
15# the DEPS file below.
16# .gclient_entries : A cache constructed by 'update' command. Format is a
17# Python script defining 'entries', a list of the names
18# of all modules in the client
19# <module>/DEPS : Python script defining var 'deps' as a map from each
20# requisite submodule name to a URL where it can be found (via
21# one SCM)
22#
23# Hooks
24# .gclient and DEPS files may optionally contain a list named "hooks" to
25# allow custom actions to be performed based on files that have changed in the
26# working copy as a result of a "sync"/"update" or "revert" operation. This
27# can be prevented by using --nohooks (hooks run by default). Hooks can also
28# be forced to run with the "runhooks" operation. If "sync" is run with
29# --force, all known but not suppressed hooks will run regardless of the state
30# of the working copy.
31#
32# Each item in a "hooks" list is a dict, containing these two keys:
33# "pattern" The associated value is a string containing a regular
34# expression. When a file whose pathname matches the expression
35# is checked out, updated, or reverted, the hook's "action" will
36# run.
37# "action" A list describing a command to run along with its arguments, if
38# any. An action command will run at most one time per gclient
39# invocation, regardless of how many files matched the pattern.
40# The action is executed in the same directory as the .gclient
41# file. If the first item in the list is the string "python",
42# the current Python interpreter (sys.executable) will be used
43# to run the command. If the list contains string
44# "$matching_files" it will be removed from the list and the list
45# will be extended by the list of matching files.
46# "name" An optional string specifying the group to which a hook belongs
47# for overriding and organizing.
48#
49# Example:
50# hooks = [
51# { "pattern": "\\.(gif|jpe?g|pr0n|png)$",
52# "action": ["python", "image_indexer.py", "--all"]},
53# { "pattern": ".",
54# "name": "gyp",
55# "action": ["python", "src/build/gyp_chromium"]},
56# ]
57#
borenet@google.com2d1ee9e2013-10-15 08:13:16 +000058# Pre-DEPS Hooks
59# DEPS files may optionally contain a list named "pre_deps_hooks". These are
60# the same as normal hooks, except that they run before the DEPS are
61# processed. Pre-DEPS run with "sync" and "revert" unless the --noprehooks
62# flag is used.
rdsmith@chromium.orgd9591f02014-02-05 19:28:20 +000063#
maruel@chromium.org39c0b222013-08-17 16:57:01 +000064# Specifying a target OS
65# An optional key named "target_os" may be added to a gclient file to specify
66# one or more additional operating systems that should be considered when
Scott Grahamc4826742017-05-11 16:59:23 -070067# processing the deps_os/hooks_os dict of a DEPS file.
maruel@chromium.org39c0b222013-08-17 16:57:01 +000068#
69# Example:
70# target_os = [ "android" ]
71#
72# If the "target_os_only" key is also present and true, then *only* the
73# operating systems listed in "target_os" will be used.
74#
75# Example:
76# target_os = [ "ios" ]
77# target_os_only = True
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000078
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +000079from __future__ import print_function
80
maruel@chromium.org39c0b222013-08-17 16:57:01 +000081__version__ = '0.7'
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000082
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +020083import collections
maruel@chromium.org9e5317a2010-08-13 20:35:11 +000084import copy
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +000085import json
maruel@chromium.org754960e2009-09-21 12:31:05 +000086import logging
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000087import optparse
88import os
bradnelson@google.com4949dab2012-04-19 16:41:07 +000089import platform
maruel@chromium.org621939b2010-08-10 20:12:00 +000090import posixpath
msb@chromium.org2e38de72009-09-28 17:04:47 +000091import pprint
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000092import re
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000093import sys
ilevy@chromium.orgc28d3772013-07-12 19:42:37 +000094import time
bradnelson@google.com4949dab2012-04-19 16:41:07 +000095import urlparse
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000096
maruel@chromium.org35625c72011-03-23 17:34:02 +000097import fix_encoding
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +020098import gclient_eval
maruel@chromium.org5f3eee32009-09-17 00:34:30 +000099import gclient_scm
100import gclient_utils
szager@chromium.org848fd492014-04-09 19:06:44 +0000101import git_cache
nasser@codeaurora.org1f7a3d12010-02-04 15:11:50 +0000102from third_party.repo.progress import Progress
maruel@chromium.org39c0b222013-08-17 16:57:01 +0000103import subcommand
maruel@chromium.org31cb48a2011-04-04 18:01:36 +0000104import subprocess2
iannucci@chromium.org596cd5c2016-04-04 21:34:39 +0000105import setup_color
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000106
107
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +0200108class GNException(Exception):
109 pass
110
111
112def ToGNString(value, allow_dicts = True):
113 """Returns a stringified GN equivalent of the Python value.
114
115 allow_dicts indicates if this function will allow converting dictionaries
116 to GN scopes. This is only possible at the top level, you can't nest a
117 GN scope in a list, so this should be set to False for recursive calls."""
118 if isinstance(value, basestring):
119 if value.find('\n') >= 0:
120 raise GNException("Trying to print a string with a newline in it.")
121 return '"' + \
122 value.replace('\\', '\\\\').replace('"', '\\"').replace('$', '\\$') + \
123 '"'
124
125 if isinstance(value, unicode):
126 return ToGNString(value.encode('utf-8'))
127
128 if isinstance(value, bool):
129 if value:
130 return "true"
131 return "false"
132
133 # NOTE: some type handling removed compared to chromium/src copy.
134
135 raise GNException("Unsupported type when printing to GN.")
136
137
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200138class Hook(object):
139 """Descriptor of command ran before/after sync or on demand."""
140
Paweł Hajdan, Jr032d5452017-06-22 20:43:53 +0200141 def __init__(self, action, pattern=None, name=None, cwd=None, condition=None,
Daniel Chenga0c5f082017-10-19 13:35:19 -0700142 variables=None, verbose=False):
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200143 """Constructor.
144
145 Arguments:
146 action (list of basestring): argv of the command to run
147 pattern (basestring regex): noop with git; deprecated
148 name (basestring): optional name; no effect on operation
Paweł Hajdan, Jrc9364392017-06-14 17:11:56 +0200149 cwd (basestring): working directory to use
Paweł Hajdan, Jr032d5452017-06-22 20:43:53 +0200150 condition (basestring): condition when to run the hook
151 variables (dict): variables for evaluating the condition
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200152 """
153 self._action = gclient_utils.freeze(action)
154 self._pattern = pattern
155 self._name = name
Paweł Hajdan, Jrc9364392017-06-14 17:11:56 +0200156 self._cwd = cwd
Paweł Hajdan, Jr032d5452017-06-22 20:43:53 +0200157 self._condition = condition
158 self._variables = variables
Daniel Chenga0c5f082017-10-19 13:35:19 -0700159 self._verbose = verbose
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200160
161 @staticmethod
Daniel Chenga0c5f082017-10-19 13:35:19 -0700162 def from_dict(d, variables=None, verbose=False):
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200163 """Creates a Hook instance from a dict like in the DEPS file."""
Paweł Hajdan, Jr032d5452017-06-22 20:43:53 +0200164 return Hook(
165 d['action'],
166 d.get('pattern'),
167 d.get('name'),
168 d.get('cwd'),
169 d.get('condition'),
Daniel Chenga0c5f082017-10-19 13:35:19 -0700170 variables=variables,
171 # Always print the header if not printing to a TTY.
172 verbose=verbose or not setup_color.IS_TTY)
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200173
174 @property
175 def action(self):
176 return self._action
177
178 @property
179 def pattern(self):
180 return self._pattern
181
182 @property
183 def name(self):
184 return self._name
185
Paweł Hajdan, Jrecf53fe2017-09-29 18:28:49 +0200186 @property
187 def condition(self):
188 return self._condition
189
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200190 def matches(self, file_list):
191 """Returns true if the pattern matches any of files in the list."""
192 if not self._pattern:
193 return True
194 pattern = re.compile(self._pattern)
195 return bool([f for f in file_list if pattern.search(f)])
196
197 def run(self, root):
Paweł Hajdan, Jr032d5452017-06-22 20:43:53 +0200198 """Executes the hook's command (provided the condition is met)."""
199 if (self._condition and
200 not gclient_eval.EvaluateCondition(self._condition, self._variables)):
201 return
202
Paweł Hajdan, Jrfc6196b2017-07-27 13:15:25 +0200203 cmd = [arg.format(**self._variables) for arg in self._action]
204
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200205 if cmd[0] == 'python':
206 # If the hook specified "python" as the first item, the action is a
207 # Python script. Run it by starting a new copy of the same
208 # interpreter.
209 cmd[0] = sys.executable
Nodir Turakulov0ffcc872017-11-09 16:44:58 -0800210 elif cmd[0] == 'vpython' and _detect_host_os() == 'win':
211 cmd[0] += '.bat'
Paweł Hajdan, Jrc9364392017-06-14 17:11:56 +0200212
213 cwd = root
214 if self._cwd:
215 cwd = os.path.join(cwd, self._cwd)
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200216 try:
217 start_time = time.time()
218 gclient_utils.CheckCallAndFilterAndHeader(
Daniel Chenga0c5f082017-10-19 13:35:19 -0700219 cmd, cwd=cwd, always=self._verbose)
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200220 except (gclient_utils.Error, subprocess2.CalledProcessError) as e:
221 # Use a discrete exit status code of 2 to indicate that a hook action
222 # failed. Users of this script may wish to treat hook action failures
223 # differently from VC failures.
224 print('Error: %s' % str(e), file=sys.stderr)
225 sys.exit(2)
226 finally:
227 elapsed_time = time.time() - start_time
228 if elapsed_time > 10:
229 print("Hook '%s' took %.2f secs" % (
230 gclient_utils.CommandToStr(cmd), elapsed_time))
231
232
Paweł Hajdan, Jrfc6196b2017-07-27 13:15:25 +0200233class DependencySettings(object):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000234 """Immutable configuration settings."""
235 def __init__(
Paweł Hajdan, Jrfc6196b2017-07-27 13:15:25 +0200236 self, parent, raw_url, url, managed, custom_deps, custom_vars,
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +0200237 custom_hooks, deps_file, should_process, relative,
238 condition, condition_value):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000239 # These are not mutable:
240 self._parent = parent
mmoss@chromium.org8f93f792014-08-26 23:24:09 +0000241 self._deps_file = deps_file
Paweł Hajdan, Jrfc6196b2017-07-27 13:15:25 +0200242 self._raw_url = raw_url
maruel@chromium.org064186c2011-09-27 23:53:33 +0000243 self._url = url
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +0200244 # The condition as string (or None). Useful to keep e.g. for flatten.
245 self._condition = condition
246 # Boolean value of the condition. If there's no condition, just True.
247 self._condition_value = condition_value
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000248 # 'managed' determines whether or not this dependency is synced/updated by
249 # gclient after gclient checks it out initially. The difference between
250 # 'managed' and 'should_process' is that the user specifies 'managed' via
smutae7ea312016-07-18 11:59:41 -0700251 # the --unmanaged command-line flag or a .gclient config, where
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000252 # 'should_process' is dynamically set by gclient if it goes over its
253 # recursion limit and controls gclient's behavior so it does not misbehave.
254 self._managed = managed
255 self._should_process = should_process
agabledce6ddc2016-09-08 10:02:16 -0700256 # If this is a recursed-upon sub-dependency, and the parent has
257 # use_relative_paths set, then this dependency should check out its own
258 # dependencies relative to that parent's path for this, rather than
259 # relative to the .gclient file.
260 self._relative = relative
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +0000261 # This is a mutable value which has the list of 'target_os' OSes listed in
262 # the current deps file.
263 self.local_target_os = None
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000264
265 # These are only set in .gclient and not in DEPS files.
266 self._custom_vars = custom_vars or {}
267 self._custom_deps = custom_deps or {}
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000268 self._custom_hooks = custom_hooks or []
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000269
maruel@chromium.org064186c2011-09-27 23:53:33 +0000270 # Post process the url to remove trailing slashes.
271 if isinstance(self._url, basestring):
272 # urls are sometime incorrectly written as proto://host/path/@rev. Replace
273 # it to proto://host/path@rev.
maruel@chromium.org064186c2011-09-27 23:53:33 +0000274 self._url = self._url.replace('/@', '@')
Paweł Hajdan, Jr7e9303b2017-05-23 14:38:27 +0200275 elif not isinstance(self._url, (None.__class__)):
maruel@chromium.org064186c2011-09-27 23:53:33 +0000276 raise gclient_utils.Error(
Paweł Hajdan, Jr7e9303b2017-05-23 14:38:27 +0200277 ('dependency url must be either string or None, '
278 'instead of %s') % self._url.__class__.__name__)
mmoss@chromium.orgd0b272b2013-01-30 23:55:33 +0000279 # Make any deps_file path platform-appropriate.
John Budorick0f7b2002018-01-19 15:46:17 -0800280 if self._deps_file:
281 for sep in ['/', '\\']:
282 self._deps_file = self._deps_file.replace(sep, os.sep)
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000283
284 @property
285 def deps_file(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000286 return self._deps_file
287
288 @property
289 def managed(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000290 return self._managed
291
292 @property
293 def parent(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000294 return self._parent
295
296 @property
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000297 def root(self):
298 """Returns the root node, a GClient object."""
299 if not self.parent:
300 # This line is to signal pylint that it could be a GClient instance.
301 return self or GClient(None, None)
302 return self.parent.root
303
304 @property
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000305 def should_process(self):
306 """True if this dependency should be processed, i.e. checked out."""
307 return self._should_process
308
309 @property
310 def custom_vars(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000311 return self._custom_vars.copy()
312
313 @property
314 def custom_deps(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000315 return self._custom_deps.copy()
316
maruel@chromium.org064186c2011-09-27 23:53:33 +0000317 @property
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000318 def custom_hooks(self):
319 return self._custom_hooks[:]
320
321 @property
Paweł Hajdan, Jrfc6196b2017-07-27 13:15:25 +0200322 def raw_url(self):
323 """URL before variable expansion."""
324 return self._raw_url
325
326 @property
maruel@chromium.org064186c2011-09-27 23:53:33 +0000327 def url(self):
Paweł Hajdan, Jrfc6196b2017-07-27 13:15:25 +0200328 """URL after variable expansion."""
maruel@chromium.org064186c2011-09-27 23:53:33 +0000329 return self._url
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000330
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000331 @property
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +0200332 def condition(self):
333 return self._condition
334
335 @property
336 def condition_value(self):
337 return self._condition_value
338
339 @property
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +0000340 def target_os(self):
341 if self.local_target_os is not None:
342 return tuple(set(self.local_target_os).union(self.parent.target_os))
343 else:
344 return self.parent.target_os
345
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000346 def get_custom_deps(self, name, url):
347 """Returns a custom deps if applicable."""
348 if self.parent:
349 url = self.parent.get_custom_deps(name, url)
350 # None is a valid return value to disable a dependency.
351 return self.custom_deps.get(name, url)
352
maruel@chromium.org064186c2011-09-27 23:53:33 +0000353
354class Dependency(gclient_utils.WorkItem, DependencySettings):
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000355 """Object that represents a dependency checkout."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000356
Paweł Hajdan, Jrfc6196b2017-07-27 13:15:25 +0200357 def __init__(self, parent, name, raw_url, url, managed, custom_deps,
agabledce6ddc2016-09-08 10:02:16 -0700358 custom_vars, custom_hooks, deps_file, should_process,
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +0200359 relative, condition, condition_value):
maruel@chromium.org6ca8bf82011-09-19 23:04:30 +0000360 gclient_utils.WorkItem.__init__(self, name)
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000361 DependencySettings.__init__(
Paweł Hajdan, Jrfc6196b2017-07-27 13:15:25 +0200362 self, parent, raw_url, url, managed, custom_deps, custom_vars,
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +0200363 custom_hooks, deps_file, should_process, relative,
364 condition, condition_value)
maruel@chromium.org68988972011-09-20 14:11:42 +0000365
366 # This is in both .gclient and DEPS files:
maruel@chromium.org064186c2011-09-27 23:53:33 +0000367 self._deps_hooks = []
maruel@chromium.org68988972011-09-20 14:11:42 +0000368
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000369 self._pre_deps_hooks = []
370
maruel@chromium.org68988972011-09-20 14:11:42 +0000371 # Calculates properties:
maruel@chromium.org064186c2011-09-27 23:53:33 +0000372 self._parsed_url = None
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +0000373 self._dependencies = []
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +0200374 self._vars = {}
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +0200375 self._os_dependencies = {}
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +0200376 self._os_deps_hooks = {}
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200377
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000378 # A cache of the files affected by the current operation, necessary for
379 # hooks.
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000380 self._file_list = []
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000381 # List of host names from which dependencies are allowed.
382 # Default is an empty set, meaning unspecified in DEPS file, and hence all
383 # hosts will be allowed. Non-empty set means whitelist of hosts.
384 # allowed_hosts var is scoped to its DEPS file, and so it isn't recursive.
385 self._allowed_hosts = frozenset()
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +0200386 # Spec for .gni output to write (if any).
387 self._gn_args_file = None
388 self._gn_args = []
maruel@chromium.org85c2a192010-07-22 21:14:43 +0000389 # If it is not set to True, the dependency wasn't processed for its child
390 # dependency, i.e. its DEPS wasn't read.
maruel@chromium.org064186c2011-09-27 23:53:33 +0000391 self._deps_parsed = False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000392 # This dependency has been processed, i.e. checked out
maruel@chromium.org064186c2011-09-27 23:53:33 +0000393 self._processed = False
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000394 # This dependency had its pre-DEPS hooks run
395 self._pre_deps_hooks_ran = False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000396 # This dependency had its hook run
maruel@chromium.org064186c2011-09-27 23:53:33 +0000397 self._hooks_ran = False
kustermann@google.coma692e8f2013-04-18 08:32:04 +0000398 # This is the scm used to checkout self.url. It may be used by dependencies
399 # to get the datetime of the revision we checked out.
400 self._used_scm = None
szager@chromium.org4ad264b2014-05-20 04:43:47 +0000401 self._used_revision = None
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +0000402 # The actual revision we ended up getting, or None if that information is
403 # unavailable
404 self._got_revision = None
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000405
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000406 # This is a mutable value that overrides the normal recursion limit for this
407 # dependency. It is read from the actual DEPS file so cannot be set on
408 # class instantiation.
409 self.recursion_override = None
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000410 # recursedeps is a mutable value that selectively overrides the default
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000411 # 'no recursion' setting on a dep-by-dep basis. It will replace
412 # recursion_override.
iannucci@chromium.orgafa11ac2016-05-04 22:17:34 +0000413 #
414 # It will be a dictionary of {deps_name: {"deps_file": depfile_name}} or
415 # None.
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000416 self.recursedeps = None
hinoka885e5b12016-06-08 14:40:09 -0700417 # This is inherited from WorkItem. We want the URL to be a resource.
418 if url and isinstance(url, basestring):
419 # The url is usually given to gclient either as https://blah@123
qyearsley12fa6ff2016-08-24 09:18:40 -0700420 # or just https://blah. The @123 portion is irrelevant.
hinoka885e5b12016-06-08 14:40:09 -0700421 self.resources.append(url.split('@')[0])
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000422
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000423 if not self.name and self.parent:
424 raise gclient_utils.Error('Dependency without name')
425
John Budorick0f7b2002018-01-19 15:46:17 -0800426 def ToLines(self):
427 s = []
428 condition_part = ([' "condition": %r,' % self.condition]
429 if self.condition else [])
430 s.extend([
431 ' # %s' % self.hierarchy(include_url=False),
432 ' "%s": {' % (self.name,),
433 ' "url": "%s",' % (self.raw_url,),
434 ] + condition_part + [
435 ' },',
436 '',
437 ])
438 return s
439
440
441
maruel@chromium.org470b5432011-10-11 18:18:19 +0000442 @property
443 def requirements(self):
444 """Calculate the list of requirements."""
445 requirements = set()
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000446 # self.parent is implicitly a requirement. This will be recursive by
447 # definition.
448 if self.parent and self.parent.name:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000449 requirements.add(self.parent.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000450
451 # For a tree with at least 2 levels*, the leaf node needs to depend
452 # on the level higher up in an orderly way.
453 # This becomes messy for >2 depth as the DEPS file format is a dictionary,
454 # thus unsorted, while the .gclient format is a list thus sorted.
455 #
456 # * _recursion_limit is hard coded 2 and there is no hope to change this
457 # value.
458 #
459 # Interestingly enough, the following condition only works in the case we
460 # want: self is a 2nd level node. 3nd level node wouldn't need this since
461 # they already have their parent as a requirement.
maruel@chromium.org470b5432011-10-11 18:18:19 +0000462 if self.parent and self.parent.parent and not self.parent.parent.parent:
463 requirements |= set(i.name for i in self.root.dependencies if i.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000464
maruel@chromium.org470b5432011-10-11 18:18:19 +0000465 if self.name:
466 requirements |= set(
467 obj.name for obj in self.root.subtree(False)
468 if (obj is not self
469 and obj.name and
470 self.name.startswith(posixpath.join(obj.name, ''))))
471 requirements = tuple(sorted(requirements))
472 logging.info('Dependency(%s).requirements = %s' % (self.name, requirements))
473 return requirements
474
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000475 @property
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000476 def try_recursedeps(self):
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000477 """Returns False if recursion_override is ever specified."""
478 if self.recursion_override is not None:
479 return False
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000480 return self.parent.try_recursedeps
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000481
482 @property
483 def recursion_limit(self):
484 """Returns > 0 if this dependency is not too recursed to be processed."""
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000485 # We continue to support the absence of recursedeps until tools and DEPS
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000486 # using recursion_override are updated.
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000487 if self.try_recursedeps and self.parent.recursedeps != None:
488 if self.name in self.parent.recursedeps:
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000489 return 1
490
491 if self.recursion_override is not None:
492 return self.recursion_override
493 return max(self.parent.recursion_limit - 1, 0)
494
maruel@chromium.org470b5432011-10-11 18:18:19 +0000495 def verify_validity(self):
496 """Verifies that this Dependency is fine to add as a child of another one.
497
498 Returns True if this entry should be added, False if it is a duplicate of
499 another entry.
500 """
501 logging.info('Dependency(%s).verify_validity()' % self.name)
502 if self.name in [s.name for s in self.parent.dependencies]:
503 raise gclient_utils.Error(
504 'The same name "%s" appears multiple times in the deps section' %
505 self.name)
506 if not self.should_process:
507 # Return early, no need to set requirements.
508 return True
509
510 # This require a full tree traversal with locks.
511 siblings = [d for d in self.root.subtree(False) if d.name == self.name]
512 for sibling in siblings:
maruel@chromium.orgb848d5b2012-10-10 23:25:50 +0000513 self_url = self.LateOverride(self.url)
514 sibling_url = sibling.LateOverride(sibling.url)
515 # Allow to have only one to be None or ''.
516 if self_url != sibling_url and bool(self_url) == bool(sibling_url):
maruel@chromium.org470b5432011-10-11 18:18:19 +0000517 raise gclient_utils.Error(
maruel@chromium.orgb848d5b2012-10-10 23:25:50 +0000518 ('Dependency %s specified more than once:\n'
519 ' %s [%s]\n'
520 'vs\n'
521 ' %s [%s]') % (
522 self.name,
523 sibling.hierarchy(),
524 sibling_url,
525 self.hierarchy(),
526 self_url))
maruel@chromium.org470b5432011-10-11 18:18:19 +0000527 # In theory we could keep it as a shadow of the other one. In
528 # practice, simply ignore it.
529 logging.warn('Won\'t process duplicate dependency %s' % sibling)
530 return False
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000531 return True
maruel@chromium.org064186c2011-09-27 23:53:33 +0000532
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000533 def LateOverride(self, url):
Paweł Hajdan, Jr7e9303b2017-05-23 14:38:27 +0200534 """Resolves the parsed url from url."""
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000535 assert self.parsed_url == None or not self.should_process, self.parsed_url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000536 parsed_url = self.get_custom_deps(self.name, url)
537 if parsed_url != url:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000538 logging.info(
539 'Dependency(%s).LateOverride(%s) -> %s' %
540 (self.name, url, parsed_url))
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000541 return parsed_url
542
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000543 if isinstance(url, basestring):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000544 parsed_url = urlparse.urlparse(url)
scr@chromium.orgf1eccaf2014-04-11 15:51:33 +0000545 if (not parsed_url[0] and
546 not re.match(r'^\w+\@[\w\.-]+\:[\w\/]+', parsed_url[2])):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000547 # A relative url. Fetch the real base.
548 path = parsed_url[2]
549 if not path.startswith('/'):
550 raise gclient_utils.Error(
551 'relative DEPS entry \'%s\' must begin with a slash' % url)
552 # Create a scm just to query the full url.
553 parent_url = self.parent.parsed_url
John Budorick0f7b2002018-01-19 15:46:17 -0800554 scm = self.CreateSCM(
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000555 parent_url, self.root.root_dir, None, self.outbuf)
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000556 parsed_url = scm.FullUrlForRelativeUrl(url)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000557 else:
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000558 parsed_url = url
maruel@chromium.org470b5432011-10-11 18:18:19 +0000559 logging.info(
560 'Dependency(%s).LateOverride(%s) -> %s' %
561 (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000562 return parsed_url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000563
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000564 if url is None:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000565 logging.info(
566 'Dependency(%s).LateOverride(%s) -> %s' % (self.name, url, url))
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000567 return url
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000568
569 raise gclient_utils.Error('Unknown url type')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000570
bratell@opera.comed2b4fe2013-12-16 14:34:12 +0000571 @staticmethod
Paweł Hajdan, Jr5ec77132017-08-16 19:21:06 +0200572 def MergeWithOsDeps(deps, deps_os, target_os_list, process_all_deps):
bratell@opera.comed2b4fe2013-12-16 14:34:12 +0000573 """Returns a new "deps" structure that is the deps sent in updated
574 with information from deps_os (the deps_os section of the DEPS
575 file) that matches the list of target os."""
bratell@opera.comed2b4fe2013-12-16 14:34:12 +0000576 new_deps = deps.copy()
Paweł Hajdan, Jrfd0057e2017-06-21 14:20:21 +0200577 for dep_os, os_deps in deps_os.iteritems():
578 for key, value in os_deps.iteritems():
579 if value is None:
580 # Make this condition very visible, so it's not a silent failure.
581 # It's unclear how to support None override in deps_os.
582 logging.error('Ignoring %r:%r in %r deps_os', key, value, dep_os)
583 continue
584
585 # Normalize value to be a dict which contains |should_process| metadata.
586 if isinstance(value, basestring):
587 value = {'url': value}
588 assert isinstance(value, collections.Mapping), (key, value)
Paweł Hajdan, Jr5ec77132017-08-16 19:21:06 +0200589 value['should_process'] = dep_os in target_os_list or process_all_deps
Paweł Hajdan, Jrfd0057e2017-06-21 14:20:21 +0200590
591 # Handle collisions/overrides.
592 if key in new_deps and new_deps[key] != value:
593 # Normalize the existing new_deps entry.
594 if isinstance(new_deps[key], basestring):
595 new_deps[key] = {'url': new_deps[key]}
596 assert isinstance(new_deps[key],
597 collections.Mapping), (key, new_deps[key])
598
599 # It's OK if the "override" sets the key to the same value.
600 # This is mostly for legacy reasons to keep existing DEPS files
601 # working. Often mac/ios and unix/android will do this.
602 if value['url'] != new_deps[key]['url']:
603 raise gclient_utils.Error(
604 ('Value from deps_os (%r; %r: %r) conflicts with existing deps '
605 'entry (%r).') % (dep_os, key, value, new_deps[key]))
606
607 # We'd otherwise overwrite |should_process| metadata, but a dep should
608 # be processed if _any_ of its references call for that.
609 value['should_process'] = (
610 value['should_process'] or
611 new_deps[key].get('should_process', True))
612
613 new_deps[key] = value
614
bratell@opera.comed2b4fe2013-12-16 14:34:12 +0000615 return new_deps
616
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200617 def _postprocess_deps(self, deps, rel_prefix):
618 """Performs post-processing of deps compared to what's in the DEPS file."""
Paweł Hajdan, Jr4426eaf2017-06-13 12:25:47 +0200619 # Make sure the dict is mutable, e.g. in case it's frozen.
620 deps = dict(deps)
621
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200622 # If a line is in custom_deps, but not in the solution, we want to append
623 # this line to the solution.
624 for d in self.custom_deps:
625 if d not in deps:
626 deps[d] = self.custom_deps[d]
627
628 if rel_prefix:
629 logging.warning('use_relative_paths enabled.')
630 rel_deps = {}
631 for d, url in deps.items():
632 # normpath is required to allow DEPS to use .. in their
633 # dependency local path.
634 rel_deps[os.path.normpath(os.path.join(rel_prefix, d))] = url
635 logging.warning('Updating deps by prepending %s.', rel_prefix)
636 deps = rel_deps
637
638 return deps
639
640 def _deps_to_objects(self, deps, use_relative_paths):
641 """Convert a deps dict to a dict of Dependency objects."""
642 deps_to_add = []
John Budorick0f7b2002018-01-19 15:46:17 -0800643 cipd_root = None
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200644 for name, dep_value in deps.iteritems():
645 should_process = self.recursion_limit and self.should_process
646 deps_file = self.deps_file
647 if self.recursedeps is not None:
648 ent = self.recursedeps.get(name)
649 if ent is not None:
650 deps_file = ent['deps_file']
651 if dep_value is None:
652 continue
John Budorick0f7b2002018-01-19 15:46:17 -0800653
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200654 condition = None
655 condition_value = True
656 if isinstance(dep_value, basestring):
Paweł Hajdan, Jrfc6196b2017-07-27 13:15:25 +0200657 raw_url = dep_value
John Budorick0f7b2002018-01-19 15:46:17 -0800658 dep_type = None
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200659 else:
660 # This should be guaranteed by schema checking in gclient_eval.
661 assert isinstance(dep_value, collections.Mapping)
John Budorick0f7b2002018-01-19 15:46:17 -0800662 raw_url = dep_value.get('url')
Paweł Hajdan, Jrfd0057e2017-06-21 14:20:21 +0200663 # Take into account should_process metadata set by MergeWithOsDeps.
664 should_process = (should_process and
665 dep_value.get('should_process', True))
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200666 condition = dep_value.get('condition')
John Budorick0f7b2002018-01-19 15:46:17 -0800667 dep_type = dep_value.get('dep_type')
Paweł Hajdan, Jrfc6196b2017-07-27 13:15:25 +0200668
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200669 if condition:
Paweł Hajdan, Jrd3790252017-07-03 21:06:24 +0200670 condition_value = gclient_eval.EvaluateCondition(
671 condition, self.get_vars())
Paweł Hajdan, Jr5ec77132017-08-16 19:21:06 +0200672 if not self._get_option('process_all_deps', False):
673 should_process = should_process and condition_value
John Budorick0f7b2002018-01-19 15:46:17 -0800674
675 if dep_type == 'cipd':
676 if not cipd_root:
677 cipd_root = gclient_scm.CipdRoot(
678 os.path.join(self.root.root_dir, self.name),
679 # TODO(jbudorick): Support other service URLs as necessary.
680 # Service URLs should be constant over the scope of a cipd
681 # root, so a var per DEPS file specifying the service URL
682 # should suffice.
683 'https://chrome-infra-packages.appspot.com')
684 for package in dep_value.get('packages', []):
685 deps_to_add.append(
686 CipdDependency(
687 self, name, package, cipd_root,
688 self.custom_vars, should_process, use_relative_paths,
689 condition, condition_value))
690 elif dep_type == 'git':
691 url = raw_url.format(**self.get_vars())
692 deps_to_add.append(
693 GitDependency(
694 self, name, raw_url, url, None, None, self.custom_vars, None,
695 deps_file, should_process, use_relative_paths, condition,
696 condition_value))
697 else:
698 url = raw_url.format(**self.get_vars())
699 deps_to_add.append(
700 Dependency(
701 self, name, raw_url, url, None, None, self.custom_vars, None,
702 deps_file, should_process, use_relative_paths, condition,
703 condition_value))
704
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200705 deps_to_add.sort(key=lambda x: x.name)
706 return deps_to_add
707
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000708 def ParseDepsFile(self):
maruel@chromium.org271375b2010-06-23 19:17:38 +0000709 """Parses the DEPS file for this dependency."""
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000710 assert not self.deps_parsed
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000711 assert not self.dependencies
iannucci@chromium.org0c3f3052014-05-22 00:29:22 +0000712
713 deps_content = None
cmp@chromium.org76ce73c2014-07-02 00:13:18 +0000714
715 # First try to locate the configured deps file. If it's missing, fallback
716 # to DEPS.
717 deps_files = [self.deps_file]
718 if 'DEPS' not in deps_files:
719 deps_files.append('DEPS')
720 for deps_file in deps_files:
721 filepath = os.path.join(self.root.root_dir, self.name, deps_file)
722 if os.path.isfile(filepath):
723 logging.info(
724 'ParseDepsFile(%s): %s file found at %s', self.name, deps_file,
725 filepath)
726 break
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000727 logging.info(
cmp@chromium.org76ce73c2014-07-02 00:13:18 +0000728 'ParseDepsFile(%s): No %s file found at %s', self.name, deps_file,
729 filepath)
730
731 if os.path.isfile(filepath):
maruel@chromium.org46304292010-10-28 11:42:00 +0000732 deps_content = gclient_utils.FileRead(filepath)
cmp@chromium.org76ce73c2014-07-02 00:13:18 +0000733 logging.debug('ParseDepsFile(%s) read:\n%s', self.name, deps_content)
iannucci@chromium.org0c3f3052014-05-22 00:29:22 +0000734
735 local_scope = {}
736 if deps_content:
Paweł Hajdan, Jrf1587bf2017-06-20 21:19:07 +0200737 global_scope = {
Paweł Hajdan, Jrfc6196b2017-07-27 13:15:25 +0200738 'Var': lambda var_name: '{%s}' % var_name,
Paweł Hajdan, Jrf1587bf2017-06-20 21:19:07 +0200739 'deps_os': {},
740 }
maruel@chromium.org46304292010-10-28 11:42:00 +0000741 # Eval the content.
742 try:
Paweł Hajdan, Jrc485d5a2017-06-02 12:08:09 +0200743 if self._get_option('validate_syntax', False):
John Budorick0f7b2002018-01-19 15:46:17 -0800744 local_scope = gclient_eval.Exec(
745 deps_content, global_scope, local_scope, filepath)
Paweł Hajdan, Jrc485d5a2017-06-02 12:08:09 +0200746 else:
747 exec(deps_content, global_scope, local_scope)
vapier@chromium.orga81a56e2015-11-11 07:56:13 +0000748 except SyntaxError as e:
maruel@chromium.org46304292010-10-28 11:42:00 +0000749 gclient_utils.SyntaxErrorToError(filepath, e)
iannucci@chromium.org0c3f3052014-05-22 00:29:22 +0000750
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000751 if 'allowed_hosts' in local_scope:
752 try:
753 self._allowed_hosts = frozenset(local_scope.get('allowed_hosts'))
754 except TypeError: # raised if non-iterable
755 pass
756 if not self._allowed_hosts:
757 logging.warning("allowed_hosts is specified but empty %s",
758 self._allowed_hosts)
759 raise gclient_utils.Error(
760 'ParseDepsFile(%s): allowed_hosts must be absent '
761 'or a non-empty iterable' % self.name)
762
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +0200763 self._gn_args_file = local_scope.get('gclient_gn_args_file')
764 self._gn_args = local_scope.get('gclient_gn_args', [])
765
Paweł Hajdan, Jr1407d002017-08-01 20:01:01 +0200766 self._vars = local_scope.get('vars', {})
767 if self.parent:
768 for key, value in self.parent.get_vars().iteritems():
769 if key in self._vars:
770 self._vars[key] = value
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200771 # Since we heavily post-process things, freeze ones which should
772 # reflect original state of DEPS.
Paweł Hajdan, Jr1407d002017-08-01 20:01:01 +0200773 self._vars = gclient_utils.freeze(self._vars)
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200774
775 # If use_relative_paths is set in the DEPS file, regenerate
776 # the dictionary using paths relative to the directory containing
777 # the DEPS file. Also update recursedeps if use_relative_paths is
778 # enabled.
779 # If the deps file doesn't set use_relative_paths, but the parent did
780 # (and therefore set self.relative on this Dependency object), then we
781 # want to modify the deps and recursedeps by prepending the parent
782 # directory of this dependency.
783 use_relative_paths = local_scope.get('use_relative_paths', False)
784 rel_prefix = None
785 if use_relative_paths:
786 rel_prefix = self.name
787 elif self._relative:
788 rel_prefix = os.path.dirname(self.name)
789
Paweł Hajdan, Jrfc6196b2017-07-27 13:15:25 +0200790 deps = {}
791 for key, value in local_scope.get('deps', {}).iteritems():
792 deps[key.format(**self.get_vars())] = value
793
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200794 if 'recursion' in local_scope:
795 self.recursion_override = local_scope.get('recursion')
796 logging.warning(
797 'Setting %s recursion to %d.', self.name, self.recursion_limit)
798 self.recursedeps = None
799 if 'recursedeps' in local_scope:
800 self.recursedeps = {}
801 for ent in local_scope['recursedeps']:
802 if isinstance(ent, basestring):
803 self.recursedeps[ent] = {"deps_file": self.deps_file}
804 else: # (depname, depsfilename)
805 self.recursedeps[ent[0]] = {"deps_file": ent[1]}
806 logging.warning('Found recursedeps %r.', repr(self.recursedeps))
807
808 if rel_prefix:
809 logging.warning('Updating recursedeps by prepending %s.', rel_prefix)
810 rel_deps = {}
811 for depname, options in self.recursedeps.iteritems():
812 rel_deps[
813 os.path.normpath(os.path.join(rel_prefix, depname))] = options
814 self.recursedeps = rel_deps
815
816 # If present, save 'target_os' in the local_target_os property.
817 if 'target_os' in local_scope:
818 self.local_target_os = local_scope['target_os']
819 # load os specific dependencies if defined. these dependencies may
820 # override or extend the values defined by the 'deps' member.
821 target_os_list = self.target_os
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +0200822 if 'deps_os' in local_scope:
823 for dep_os, os_deps in local_scope['deps_os'].iteritems():
824 self._os_dependencies[dep_os] = self._deps_to_objects(
825 self._postprocess_deps(os_deps, rel_prefix), use_relative_paths)
Paweł Hajdan, Jr357415c2017-07-24 14:35:28 +0200826 if target_os_list and not self._get_option(
827 'do_not_merge_os_specific_entries', False):
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +0200828 deps = self.MergeWithOsDeps(
Paweł Hajdan, Jr5ec77132017-08-16 19:21:06 +0200829 deps, local_scope['deps_os'], target_os_list,
830 self._get_option('process_all_deps', False))
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200831
832 deps_to_add = self._deps_to_objects(
833 self._postprocess_deps(deps, rel_prefix), use_relative_paths)
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000834
835 # override named sets of hooks by the custom hooks
836 hooks_to_run = []
837 hook_names_to_suppress = [c.get('name', '') for c in self.custom_hooks]
838 for hook in local_scope.get('hooks', []):
839 if hook.get('name', '') not in hook_names_to_suppress:
840 hooks_to_run.append(hook)
Scott Grahamc4826742017-05-11 16:59:23 -0700841 if 'hooks_os' in local_scope and target_os_list:
842 hooks_os = local_scope['hooks_os']
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +0200843
844 # Keep original contents of hooks_os for flatten.
845 for hook_os, os_hooks in hooks_os.iteritems():
846 self._os_deps_hooks[hook_os] = [
Daniel Chenga0c5f082017-10-19 13:35:19 -0700847 Hook.from_dict(hook, variables=self.get_vars(), verbose=True)
Paweł Hajdan, Jrd3790252017-07-03 21:06:24 +0200848 for hook in os_hooks]
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +0200849
Scott Grahamc4826742017-05-11 16:59:23 -0700850 # Specifically append these to ensure that hooks_os run after hooks.
Paweł Hajdan, Jr357415c2017-07-24 14:35:28 +0200851 if not self._get_option('do_not_merge_os_specific_entries', False):
852 for the_target_os in target_os_list:
853 the_target_os_hooks = hooks_os.get(the_target_os, [])
854 hooks_to_run.extend(the_target_os_hooks)
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000855
856 # add the replacements and any additions
857 for hook in self.custom_hooks:
858 if 'action' in hook:
859 hooks_to_run.append(hook)
860
Dirk Prankeda3a29e2017-02-27 15:29:36 -0800861 if self.recursion_limit:
Paweł Hajdan, Jr032d5452017-06-22 20:43:53 +0200862 self._pre_deps_hooks = [
Daniel Chenga0c5f082017-10-19 13:35:19 -0700863 Hook.from_dict(hook, variables=self.get_vars(), verbose=True)
864 for hook in local_scope.get('pre_deps_hooks', [])
865 ]
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000866
Paweł Hajdan, Jr357415c2017-07-24 14:35:28 +0200867 self.add_dependencies_and_close(deps_to_add, hooks_to_run)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000868 logging.info('ParseDepsFile(%s) done' % self.name)
869
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +0200870 def _get_option(self, attr, default):
871 obj = self
872 while not hasattr(obj, '_options'):
873 obj = obj.parent
874 return getattr(obj._options, attr, default)
875
Paweł Hajdan, Jr357415c2017-07-24 14:35:28 +0200876 def add_dependencies_and_close(self, deps_to_add, hooks):
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000877 """Adds the dependencies, hooks and mark the parsing as done."""
maruel@chromium.orgb9be0652011-10-14 18:05:40 +0000878 for dep in deps_to_add:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000879 if dep.verify_validity():
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000880 self.add_dependency(dep)
Daniel Chenga0c5f082017-10-19 13:35:19 -0700881 self._mark_as_parsed([
882 Hook.from_dict(
883 h, variables=self.get_vars(), verbose=self.root._options.verbose)
884 for h in hooks
885 ])
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000886
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000887 def findDepsFromNotAllowedHosts(self):
888 """Returns a list of depenecies from not allowed hosts.
889
890 If allowed_hosts is not set, allows all hosts and returns empty list.
891 """
892 if not self._allowed_hosts:
893 return []
894 bad_deps = []
895 for dep in self._dependencies:
szager@chromium.orgbd772dd2014-11-05 18:43:08 +0000896 # Don't enforce this for custom_deps.
897 if dep.name in self._custom_deps:
898 continue
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000899 if isinstance(dep.url, basestring):
900 parsed_url = urlparse.urlparse(dep.url)
901 if parsed_url.netloc and parsed_url.netloc not in self._allowed_hosts:
902 bad_deps.append(dep)
903 return bad_deps
904
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +0000905 # Arguments number differs from overridden method
Quinten Yearsleyb2cc4a92016-12-15 13:53:26 -0800906 # pylint: disable=arguments-differ
maruel@chromium.org3742c842010-09-09 19:27:14 +0000907 def run(self, revision_overrides, command, args, work_queue, options):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000908 """Runs |command| then parse the DEPS file."""
maruel@chromium.org470b5432011-10-11 18:18:19 +0000909 logging.info('Dependency(%s).run()' % self.name)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000910 assert self._file_list == []
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000911 if not self.should_process:
912 return
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000913 # When running runhooks, there's no need to consult the SCM.
914 # All known hooks are expected to run unconditionally regardless of working
915 # copy state, so skip the SCM status check.
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +0200916 run_scm = command not in (
917 'flatten', 'runhooks', 'recurse', 'validate', None)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000918 parsed_url = self.LateOverride(self.url)
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000919 file_list = [] if not options.nohooks else None
szager@chromium.org3a3608d2014-10-22 21:13:52 +0000920 revision_override = revision_overrides.pop(self.name, None)
Dave Tubbda9712017-06-01 15:10:53 -0700921 if not revision_override and parsed_url:
922 revision_override = revision_overrides.get(parsed_url.split('@')[0], None)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000923 if run_scm and parsed_url:
agabled437d762016-10-17 09:35:11 -0700924 # Create a shallow copy to mutate revision.
925 options = copy.copy(options)
926 options.revision = revision_override
927 self._used_revision = options.revision
John Budorick0f7b2002018-01-19 15:46:17 -0800928 self._used_scm = self.CreateSCM(
agabled437d762016-10-17 09:35:11 -0700929 parsed_url, self.root.root_dir, self.name, self.outbuf,
930 out_cb=work_queue.out_cb)
931 self._got_revision = self._used_scm.RunCommand(command, options, args,
932 file_list)
933 if file_list:
934 file_list = [os.path.join(self.name, f.strip()) for f in file_list]
maruel@chromium.org68988972011-09-20 14:11:42 +0000935
936 # TODO(phajdan.jr): We should know exactly when the paths are absolute.
937 # Convert all absolute paths to relative.
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000938 for i in range(len(file_list or [])):
maruel@chromium.org68988972011-09-20 14:11:42 +0000939 # It depends on the command being executed (like runhooks vs sync).
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000940 if not os.path.isabs(file_list[i]):
maruel@chromium.org68988972011-09-20 14:11:42 +0000941 continue
942 prefix = os.path.commonprefix(
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000943 [self.root.root_dir.lower(), file_list[i].lower()])
944 file_list[i] = file_list[i][len(prefix):]
maruel@chromium.org68988972011-09-20 14:11:42 +0000945 # Strip any leading path separators.
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000946 while file_list[i].startswith(('\\', '/')):
947 file_list[i] = file_list[i][1:]
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000948
949 # Always parse the DEPS file.
950 self.ParseDepsFile()
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000951 self._run_is_done(file_list or [], parsed_url)
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000952 if command in ('update', 'revert') and not options.noprehooks:
953 self.RunPreDepsHooks()
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000954
955 if self.recursion_limit:
956 # Parse the dependencies of this dependency.
957 for s in self.dependencies:
Paweł Hajdan, Jr4baaa112017-07-04 19:09:32 +0200958 if s.should_process:
959 work_queue.enqueue(s)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000960
961 if command == 'recurse':
agabled437d762016-10-17 09:35:11 -0700962 # Skip file only checkout.
John Budorick0f7b2002018-01-19 15:46:17 -0800963 scm = self.GetScmName(parsed_url)
agabled437d762016-10-17 09:35:11 -0700964 if not options.scm or scm in options.scm:
965 cwd = os.path.normpath(os.path.join(self.root.root_dir, self.name))
966 # Pass in the SCM type as an env variable. Make sure we don't put
967 # unicode strings in the environment.
968 env = os.environ.copy()
969 if scm:
970 env['GCLIENT_SCM'] = str(scm)
971 if parsed_url:
972 env['GCLIENT_URL'] = str(parsed_url)
973 env['GCLIENT_DEP_PATH'] = str(self.name)
974 if options.prepend_dir and scm == 'git':
975 print_stdout = False
976 def filter_fn(line):
977 """Git-specific path marshaling. It is optimized for git-grep."""
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000978
agabled437d762016-10-17 09:35:11 -0700979 def mod_path(git_pathspec):
980 match = re.match('^(\\S+?:)?([^\0]+)$', git_pathspec)
981 modified_path = os.path.join(self.name, match.group(2))
982 branch = match.group(1) or ''
983 return '%s%s' % (branch, modified_path)
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000984
agabled437d762016-10-17 09:35:11 -0700985 match = re.match('^Binary file ([^\0]+) matches$', line)
986 if match:
987 print('Binary file %s matches\n' % mod_path(match.group(1)))
988 return
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000989
agabled437d762016-10-17 09:35:11 -0700990 items = line.split('\0')
991 if len(items) == 2 and items[1]:
992 print('%s : %s' % (mod_path(items[0]), items[1]))
993 elif len(items) >= 2:
994 # Multiple null bytes or a single trailing null byte indicate
995 # git is likely displaying filenames only (such as with -l)
996 print('\n'.join(mod_path(path) for path in items if path))
997 else:
998 print(line)
999 else:
1000 print_stdout = True
1001 filter_fn = None
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001002
agabled437d762016-10-17 09:35:11 -07001003 if parsed_url is None:
1004 print('Skipped omitted dependency %s' % cwd, file=sys.stderr)
1005 elif os.path.isdir(cwd):
1006 try:
1007 gclient_utils.CheckCallAndFilter(
1008 args, cwd=cwd, env=env, print_stdout=print_stdout,
1009 filter_fn=filter_fn,
1010 )
1011 except subprocess2.CalledProcessError:
1012 if not options.ignore:
1013 raise
1014 else:
1015 print('Skipped missing %s' % cwd, file=sys.stderr)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001016
John Budorick0f7b2002018-01-19 15:46:17 -08001017 def GetScmName(self, url):
1018 """Get the name of the SCM for the given URL.
1019
1020 While we currently support both git and cipd as SCM implementations,
1021 this currently cannot return 'cipd', regardless of the URL, as CIPD
1022 has no canonical URL format. If you want to use CIPD as an SCM, you
1023 must currently do so by explicitly using a CipdDependency.
1024 """
1025 if not url:
1026 return None
1027 url, _ = gclient_utils.SplitUrlRevision(url)
1028 if url.endswith('.git'):
1029 return 'git'
1030 protocol = url.split('://')[0]
1031 if protocol in (
1032 'file', 'git', 'git+http', 'git+https', 'http', 'https', 'ssh', 'sso'):
1033 return 'git'
1034 return None
1035
1036 def CreateSCM(self, url, root_dir=None, relpath=None, out_fh=None,
1037 out_cb=None):
1038 SCM_MAP = {
1039 'cipd': gclient_scm.CipdWrapper,
1040 'git': gclient_scm.GitWrapper,
1041 }
1042
1043 scm_name = self.GetScmName(url)
1044 if not scm_name in SCM_MAP:
1045 raise gclient_utils.Error('No SCM found for url %s' % url)
1046 scm_class = SCM_MAP[scm_name]
1047 if not scm_class.BinaryExists():
1048 raise gclient_utils.Error('%s command not found' % scm_name)
1049 return scm_class(url, root_dir, relpath, out_fh, out_cb)
1050
Dirk Pranke9f20d022017-10-11 18:36:54 -07001051 def HasGNArgsFile(self):
1052 return self._gn_args_file is not None
1053
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +02001054 def WriteGNArgsFile(self):
1055 lines = ['# Generated from %r' % self.deps_file]
Paweł Hajdan, Jrb495bf52017-09-25 19:33:50 +02001056 variables = self.get_vars()
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +02001057 for arg in self._gn_args:
Paweł Hajdan, Jre0214742017-09-28 12:21:01 +02001058 value = variables[arg]
1059 if isinstance(value, basestring):
1060 value = gclient_eval.EvaluateCondition(value, variables)
Paweł Hajdan, Jrb495bf52017-09-25 19:33:50 +02001061 lines.append('%s = %s' % (arg, ToGNString(value)))
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +02001062 with open(os.path.join(self.root.root_dir, self._gn_args_file), 'w') as f:
1063 f.write('\n'.join(lines))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001064
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001065 @gclient_utils.lockedmethod
1066 def _run_is_done(self, file_list, parsed_url):
1067 # Both these are kept for hooks that are run as a separate tree traversal.
1068 self._file_list = file_list
1069 self._parsed_url = parsed_url
1070 self._processed = True
1071
szager@google.comb9a78d32012-03-13 18:46:21 +00001072 def GetHooks(self, options):
1073 """Evaluates all hooks, and return them in a flat list.
1074
1075 RunOnDeps() must have been called before to load the DEPS.
1076 """
1077 result = []
maruel@chromium.org68988972011-09-20 14:11:42 +00001078 if not self.should_process or not self.recursion_limit:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001079 # Don't run the hook when it is above recursion_limit.
szager@google.comb9a78d32012-03-13 18:46:21 +00001080 return result
maruel@chromium.orgdc7445d2010-07-09 21:05:29 +00001081 # If "--force" was specified, run all hooks regardless of what files have
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001082 # changed.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001083 if self.deps_hooks:
agabled437d762016-10-17 09:35:11 -07001084 # TODO(maruel): If the user is using git, then we don't know
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001085 # what files have changed so we always run all hooks. It'd be nice to fix
1086 # that.
1087 if (options.force or
John Budorick0f7b2002018-01-19 15:46:17 -08001088 self.GetScmName(self.parsed_url) in ('git', None) or
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001089 os.path.isdir(os.path.join(self.root.root_dir, self.name, '.git'))):
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +02001090 result.extend(self.deps_hooks)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001091 else:
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +02001092 for hook in self.deps_hooks:
1093 if hook.matches(self.file_list_and_children):
1094 result.append(hook)
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001095 for s in self.dependencies:
szager@google.comb9a78d32012-03-13 18:46:21 +00001096 result.extend(s.GetHooks(options))
1097 return result
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001098
Dirk Pranke9f20d022017-10-11 18:36:54 -07001099 def WriteGNArgsFilesRecursively(self, dependencies):
1100 for dep in dependencies:
1101 if dep.HasGNArgsFile():
1102 dep.WriteGNArgsFile()
1103 self.WriteGNArgsFilesRecursively(dep.dependencies)
1104
Daniel Chenga0c5f082017-10-19 13:35:19 -07001105 def RunHooksRecursively(self, options, progress):
szager@google.comb9a78d32012-03-13 18:46:21 +00001106 assert self.hooks_ran == False
maruel@chromium.org064186c2011-09-27 23:53:33 +00001107 self._hooks_ran = True
Daniel Chenga0c5f082017-10-19 13:35:19 -07001108 hooks = self.GetHooks(options)
1109 if progress:
1110 progress._total = len(hooks)
1111 for hook in hooks:
Daniel Chenga0c5f082017-10-19 13:35:19 -07001112 if progress:
1113 progress.update(extra=hook.name or '')
Daniel Cheng93c5d602017-10-20 11:40:17 -07001114 hook.run(self.root.root_dir)
Daniel Chenga0c5f082017-10-19 13:35:19 -07001115 if progress:
1116 progress.end()
maruel@chromium.orgeaf61062010-07-07 18:42:39 +00001117
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001118 def RunPreDepsHooks(self):
1119 assert self.processed
1120 assert self.deps_parsed
1121 assert not self.pre_deps_hooks_ran
1122 assert not self.hooks_ran
1123 for s in self.dependencies:
1124 assert not s.processed
1125 self._pre_deps_hooks_ran = True
1126 for hook in self.pre_deps_hooks:
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +02001127 hook.run(self.root.root_dir)
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001128
maruel@chromium.org0d812442010-08-10 12:41:08 +00001129 def subtree(self, include_all):
maruel@chromium.orgad3287e2011-10-03 19:15:10 +00001130 """Breadth first recursion excluding root node."""
maruel@chromium.orgf13a4182011-09-22 00:26:15 +00001131 dependencies = self.dependencies
1132 for d in dependencies:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001133 if d.should_process or include_all:
maruel@chromium.orgad3287e2011-10-03 19:15:10 +00001134 yield d
maruel@chromium.orgf13a4182011-09-22 00:26:15 +00001135 for d in dependencies:
maruel@chromium.orgad3287e2011-10-03 19:15:10 +00001136 for i in d.subtree(include_all):
1137 yield i
1138
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001139 @gclient_utils.lockedmethod
1140 def add_dependency(self, new_dep):
1141 self._dependencies.append(new_dep)
1142
1143 @gclient_utils.lockedmethod
Paweł Hajdan, Jr357415c2017-07-24 14:35:28 +02001144 def _mark_as_parsed(self, new_hooks):
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001145 self._deps_hooks.extend(new_hooks)
1146 self._deps_parsed = True
1147
maruel@chromium.org68988972011-09-20 14:11:42 +00001148 @property
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001149 @gclient_utils.lockedmethod
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +00001150 def dependencies(self):
1151 return tuple(self._dependencies)
1152
1153 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001154 @gclient_utils.lockedmethod
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02001155 def os_dependencies(self):
1156 return dict(self._os_dependencies)
1157
1158 @property
1159 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001160 def deps_hooks(self):
1161 return tuple(self._deps_hooks)
1162
1163 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001164 @gclient_utils.lockedmethod
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +02001165 def os_deps_hooks(self):
1166 return dict(self._os_deps_hooks)
1167
1168 @property
1169 @gclient_utils.lockedmethod
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001170 def pre_deps_hooks(self):
1171 return tuple(self._pre_deps_hooks)
1172
1173 @property
1174 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001175 def parsed_url(self):
1176 return self._parsed_url
1177
1178 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001179 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001180 def deps_parsed(self):
maruel@chromium.org3223edd2011-10-10 23:17:39 +00001181 """This is purely for debugging purposes. It's not used anywhere."""
maruel@chromium.org064186c2011-09-27 23:53:33 +00001182 return self._deps_parsed
1183
1184 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001185 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001186 def processed(self):
1187 return self._processed
1188
1189 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001190 @gclient_utils.lockedmethod
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001191 def pre_deps_hooks_ran(self):
1192 return self._pre_deps_hooks_ran
1193
1194 @property
1195 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001196 def hooks_ran(self):
1197 return self._hooks_ran
1198
1199 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001200 @gclient_utils.lockedmethod
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00001201 def allowed_hosts(self):
1202 return self._allowed_hosts
1203
1204 @property
1205 @gclient_utils.lockedmethod
maruel@chromium.org861fd0f2010-07-23 03:05:05 +00001206 def file_list(self):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001207 return tuple(self._file_list)
1208
1209 @property
kustermann@google.coma692e8f2013-04-18 08:32:04 +00001210 def used_scm(self):
1211 """SCMWrapper instance for this dependency or None if not processed yet."""
1212 return self._used_scm
1213
1214 @property
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00001215 @gclient_utils.lockedmethod
1216 def got_revision(self):
1217 return self._got_revision
1218
1219 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001220 def file_list_and_children(self):
1221 result = list(self.file_list)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +00001222 for d in self.dependencies:
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001223 result.extend(d.file_list_and_children)
maruel@chromium.org68988972011-09-20 14:11:42 +00001224 return tuple(result)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +00001225
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001226 def __str__(self):
1227 out = []
agablea98a6cd2016-11-15 14:30:10 -08001228 for i in ('name', 'url', 'parsed_url', 'custom_deps',
maruel@chromium.org3c74bc92011-09-15 19:17:21 +00001229 'custom_vars', 'deps_hooks', 'file_list', 'should_process',
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00001230 'processed', 'hooks_ran', 'deps_parsed', 'requirements',
1231 'allowed_hosts'):
maruel@chromium.org3c74bc92011-09-15 19:17:21 +00001232 # First try the native property if it exists.
1233 if hasattr(self, '_' + i):
1234 value = getattr(self, '_' + i, False)
1235 else:
1236 value = getattr(self, i, False)
1237 if value:
1238 out.append('%s: %s' % (i, value))
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001239
1240 for d in self.dependencies:
1241 out.extend([' ' + x for x in str(d).splitlines()])
1242 out.append('')
1243 return '\n'.join(out)
1244
1245 def __repr__(self):
1246 return '%s: %s' % (self.name, self.url)
1247
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001248 def hierarchy(self, include_url=True):
maruel@chromium.orgbc2d2f92010-07-22 21:26:48 +00001249 """Returns a human-readable hierarchical reference to a Dependency."""
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001250 def format_name(d):
1251 if include_url:
1252 return '%s(%s)' % (d.name, d.url)
1253 return d.name
1254 out = format_name(self)
maruel@chromium.orgbffb9042010-07-22 20:59:36 +00001255 i = self.parent
1256 while i and i.name:
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001257 out = '%s -> %s' % (format_name(i), out)
maruel@chromium.orgbffb9042010-07-22 20:59:36 +00001258 i = i.parent
1259 return out
1260
Paweł Hajdan, Jrd3790252017-07-03 21:06:24 +02001261 def get_vars(self):
1262 """Returns a dictionary of effective variable values
1263 (DEPS file contents with applied custom_vars overrides)."""
Paweł Hajdan, Jra3b67ae2017-08-30 15:18:21 +02001264 # Provide some built-in variables.
1265 result = {
Paweł Hajdan, Jrd325eb32017-10-03 17:43:37 +02001266 'checkout_android': 'android' in self.target_os,
1267 'checkout_fuchsia': 'fuchsia' in self.target_os,
1268 'checkout_ios': 'ios' in self.target_os,
1269 'checkout_linux': 'unix' in self.target_os,
1270 'checkout_mac': 'mac' in self.target_os,
1271 'checkout_win': 'win' in self.target_os,
1272 'host_os': _detect_host_os(),
Paweł Hajdan, Jra3b67ae2017-08-30 15:18:21 +02001273 }
1274 # Variables defined in DEPS file override built-in ones.
1275 result.update(self._vars)
Paweł Hajdan, Jrd3790252017-07-03 21:06:24 +02001276 result.update(self.custom_vars or {})
1277 return result
1278
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001279
Paweł Hajdan, Jra3b67ae2017-08-30 15:18:21 +02001280_PLATFORM_MAPPING = {
1281 'cygwin': 'win',
1282 'darwin': 'mac',
1283 'linux2': 'linux',
1284 'win32': 'win',
Jaideep Bajwad05f3582017-09-11 12:31:48 -04001285 'aix6': 'aix',
Paweł Hajdan, Jra3b67ae2017-08-30 15:18:21 +02001286}
1287
1288
1289def _detect_host_os():
1290 return _PLATFORM_MAPPING[sys.platform]
1291
1292
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001293class GClient(Dependency):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001294 """Object that represent a gclient checkout. A tree of Dependency(), one per
1295 solution or DEPS entry."""
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001296
1297 DEPS_OS_CHOICES = {
Jaideep Bajwad05f3582017-09-11 12:31:48 -04001298 "aix6": "unix",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001299 "win32": "win",
1300 "win": "win",
1301 "cygwin": "win",
1302 "darwin": "mac",
1303 "mac": "mac",
1304 "unix": "unix",
1305 "linux": "unix",
1306 "linux2": "unix",
maruel@chromium.org244e3442011-06-12 15:20:55 +00001307 "linux3": "unix",
szager@chromium.orgf8c95cd2012-06-01 22:26:52 +00001308 "android": "android",
Michael Mossc54fa812017-08-17 11:27:58 -07001309 "ios": "ios",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001310 }
1311
1312 DEFAULT_CLIENT_FILE_TEXT = ("""\
1313solutions = [
smutae7ea312016-07-18 11:59:41 -07001314 { "name" : "%(solution_name)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001315 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +00001316 "deps_file" : "%(deps_file)s",
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001317 "managed" : %(managed)s,
smutae7ea312016-07-18 11:59:41 -07001318 "custom_deps" : {
1319 },
Paweł Hajdan, Jr3ba2a7c2017-10-04 19:24:46 +02001320 "custom_vars": %(custom_vars)r,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001321 },
1322]
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001323cache_dir = %(cache_dir)r
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001324""")
1325
1326 DEFAULT_SNAPSHOT_SOLUTION_TEXT = ("""\
smutae7ea312016-07-18 11:59:41 -07001327 { "name" : "%(solution_name)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001328 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +00001329 "deps_file" : "%(deps_file)s",
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001330 "managed" : %(managed)s,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001331 "custom_deps" : {
smutae7ea312016-07-18 11:59:41 -07001332%(solution_deps)s },
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001333 },
1334""")
1335
1336 DEFAULT_SNAPSHOT_FILE_TEXT = ("""\
1337# Snapshot generated with gclient revinfo --snapshot
1338solutions = [
maruel@chromium.org73e21142010-07-05 13:32:01 +00001339%(solution_list)s]
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001340""")
1341
1342 def __init__(self, root_dir, options):
maruel@chromium.org0d812442010-08-10 12:41:08 +00001343 # Do not change previous behavior. Only solution level and immediate DEPS
1344 # are processed.
1345 self._recursion_limit = 2
Paweł Hajdan, Jrfc6196b2017-07-27 13:15:25 +02001346 Dependency.__init__(self, None, None, None, None, True, None, None, None,
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +02001347 'unused', True, None, None, True)
maruel@chromium.org0d425922010-06-21 19:22:24 +00001348 self._options = options
maruel@chromium.org271375b2010-06-23 19:17:38 +00001349 if options.deps_os:
1350 enforced_os = options.deps_os.split(',')
1351 else:
1352 enforced_os = [self.DEPS_OS_CHOICES.get(sys.platform, 'unix')]
1353 if 'all' in enforced_os:
1354 enforced_os = self.DEPS_OS_CHOICES.itervalues()
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001355 self._enforced_os = tuple(set(enforced_os))
maruel@chromium.org271375b2010-06-23 19:17:38 +00001356 self._root_dir = root_dir
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001357 self.config_content = None
1358
borenet@google.com88d10082014-03-21 17:24:48 +00001359 def _CheckConfig(self):
1360 """Verify that the config matches the state of the existing checked-out
1361 solutions."""
1362 for dep in self.dependencies:
1363 if dep.managed and dep.url:
John Budorick0f7b2002018-01-19 15:46:17 -08001364 scm = self.CreateSCM(
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001365 dep.url, self.root_dir, dep.name, self.outbuf)
smut@google.comd33eab32014-07-07 19:35:18 +00001366 actual_url = scm.GetActualRemoteURL(self._options)
borenet@google.com4e9be262014-04-08 19:40:30 +00001367 if actual_url and not scm.DoesRemoteURLMatch(self._options):
levarum@chromium.org27a6f9a2016-05-28 00:21:49 +00001368 mirror = scm.GetCacheMirror()
1369 if mirror:
1370 mirror_string = '%s (exists=%s)' % (mirror.mirror_path,
1371 mirror.exists())
1372 else:
1373 mirror_string = 'not used'
borenet@google.com0a427372014-04-02 19:12:13 +00001374 raise gclient_utils.Error('''
borenet@google.com88d10082014-03-21 17:24:48 +00001375Your .gclient file seems to be broken. The requested URL is different from what
borenet@google.com0a427372014-04-02 19:12:13 +00001376is actually checked out in %(checkout_path)s.
borenet@google.com88d10082014-03-21 17:24:48 +00001377
borenet@google.com97882362014-04-07 20:06:02 +00001378The .gclient file contains:
levarum@chromium.org27a6f9a2016-05-28 00:21:49 +00001379URL: %(expected_url)s (%(expected_scm)s)
1380Cache mirror: %(mirror_string)s
borenet@google.com97882362014-04-07 20:06:02 +00001381
1382The local checkout in %(checkout_path)s reports:
1383%(actual_url)s (%(actual_scm)s)
borenet@google.com88d10082014-03-21 17:24:48 +00001384
1385You should ensure that the URL listed in .gclient is correct and either change
agabled437d762016-10-17 09:35:11 -07001386it or fix the checkout.
borenet@google.com88d10082014-03-21 17:24:48 +00001387''' % {'checkout_path': os.path.join(self.root_dir, dep.name),
1388 'expected_url': dep.url,
John Budorick0f7b2002018-01-19 15:46:17 -08001389 'expected_scm': self.GetScmName(dep.url),
levarum@chromium.org27a6f9a2016-05-28 00:21:49 +00001390 'mirror_string' : mirror_string,
borenet@google.com88d10082014-03-21 17:24:48 +00001391 'actual_url': actual_url,
John Budorick0f7b2002018-01-19 15:46:17 -08001392 'actual_scm': self.GetScmName(actual_url)})
borenet@google.com88d10082014-03-21 17:24:48 +00001393
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001394 def SetConfig(self, content):
maruel@chromium.orgf13a4182011-09-22 00:26:15 +00001395 assert not self.dependencies
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001396 config_dict = {}
1397 self.config_content = content
1398 try:
1399 exec(content, config_dict)
vapier@chromium.orga81a56e2015-11-11 07:56:13 +00001400 except SyntaxError as e:
maruel@chromium.org5990f9d2010-07-07 18:02:58 +00001401 gclient_utils.SyntaxErrorToError('.gclient', e)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001402
peter@chromium.org1efccc82012-04-27 16:34:38 +00001403 # Append any target OS that is not already being enforced to the tuple.
1404 target_os = config_dict.get('target_os', [])
stuartmorgan@chromium.org18a4f6a2012-11-02 13:37:24 +00001405 if config_dict.get('target_os_only', False):
1406 self._enforced_os = tuple(set(target_os))
1407 else:
1408 self._enforced_os = tuple(set(self._enforced_os).union(target_os))
1409
Aleksandr Derbenev9e8fb0e2017-08-01 20:18:31 +03001410 cache_dir = config_dict.get('cache_dir', self._options.cache_dir)
dyen@chromium.orgd915cca2014-08-07 21:41:37 +00001411 if cache_dir:
1412 cache_dir = os.path.join(self.root_dir, cache_dir)
1413 cache_dir = os.path.abspath(cache_dir)
Andrii Shyshkalov77ce4bd2017-11-27 12:38:18 -08001414
dyen@chromium.orgd915cca2014-08-07 21:41:37 +00001415 gclient_scm.GitWrapper.cache_dir = cache_dir
1416 git_cache.Mirror.SetCachePath(cache_dir)
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001417
stuartmorgan@chromium.org18a4f6a2012-11-02 13:37:24 +00001418 if not target_os and config_dict.get('target_os_only', False):
1419 raise gclient_utils.Error('Can\'t use target_os_only if target_os is '
1420 'not specified')
peter@chromium.org1efccc82012-04-27 16:34:38 +00001421
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001422 deps_to_add = []
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001423 for s in config_dict.get('solutions', []):
maruel@chromium.org81843b82010-06-28 16:49:26 +00001424 try:
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001425 deps_to_add.append(Dependency(
Paweł Hajdan, Jrfc6196b2017-07-27 13:15:25 +02001426 self, s['name'], s['url'], s['url'],
smutae7ea312016-07-18 11:59:41 -07001427 s.get('managed', True),
maruel@chromium.org81843b82010-06-28 16:49:26 +00001428 s.get('custom_deps', {}),
maruel@chromium.org0d812442010-08-10 12:41:08 +00001429 s.get('custom_vars', {}),
petermayo@chromium.orge79161a2013-07-09 14:40:37 +00001430 s.get('custom_hooks', []),
nsylvain@google.comefc80932011-05-31 21:27:56 +00001431 s.get('deps_file', 'DEPS'),
agabledce6ddc2016-09-08 10:02:16 -07001432 True,
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +02001433 None,
1434 None,
1435 True))
maruel@chromium.org81843b82010-06-28 16:49:26 +00001436 except KeyError:
1437 raise gclient_utils.Error('Invalid .gclient file. Solution is '
1438 'incomplete: %s' % s)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001439 self.add_dependencies_and_close(deps_to_add, config_dict.get('hooks', []))
1440 logging.info('SetConfig() done')
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001441
1442 def SaveConfig(self):
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001443 gclient_utils.FileWrite(os.path.join(self.root_dir,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001444 self._options.config_filename),
1445 self.config_content)
1446
1447 @staticmethod
1448 def LoadCurrentConfig(options):
1449 """Searches for and loads a .gclient file relative to the current working
1450 dir. Returns a GClient object."""
szager@chromium.orge2e03202012-07-31 18:05:16 +00001451 if options.spec:
1452 client = GClient('.', options)
1453 client.SetConfig(options.spec)
1454 else:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00001455 if options.verbose:
1456 print('Looking for %s starting from %s\n' % (
1457 options.config_filename, os.getcwd()))
szager@chromium.orge2e03202012-07-31 18:05:16 +00001458 path = gclient_utils.FindGclientRoot(os.getcwd(), options.config_filename)
1459 if not path:
Michael Achenbachb3ce73d2017-10-11 16:41:27 +02001460 if options.verbose:
1461 print('Couldn\'t find configuration file.')
szager@chromium.orge2e03202012-07-31 18:05:16 +00001462 return None
1463 client = GClient(path, options)
1464 client.SetConfig(gclient_utils.FileRead(
1465 os.path.join(path, options.config_filename)))
maruel@chromium.org69392e72011-10-13 22:09:00 +00001466
1467 if (options.revisions and
1468 len(client.dependencies) > 1 and
1469 any('@' not in r for r in options.revisions)):
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001470 print(
1471 ('You must specify the full solution name like --revision %s@%s\n'
1472 'when you have multiple solutions setup in your .gclient file.\n'
1473 'Other solutions present are: %s.') % (
maruel@chromium.org69392e72011-10-13 22:09:00 +00001474 client.dependencies[0].name,
1475 options.revisions[0],
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001476 ', '.join(s.name for s in client.dependencies[1:])),
1477 file=sys.stderr)
maruel@chromium.org15804092010-09-02 17:07:37 +00001478 return client
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001479
nsylvain@google.comefc80932011-05-31 21:27:56 +00001480 def SetDefaultConfig(self, solution_name, deps_file, solution_url,
Paweł Hajdan, Jr3ba2a7c2017-10-04 19:24:46 +02001481 managed=True, cache_dir=None, custom_vars=None):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001482 self.SetConfig(self.DEFAULT_CLIENT_FILE_TEXT % {
1483 'solution_name': solution_name,
1484 'solution_url': solution_url,
nsylvain@google.comefc80932011-05-31 21:27:56 +00001485 'deps_file': deps_file,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001486 'managed': managed,
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001487 'cache_dir': cache_dir,
Paweł Hajdan, Jr3ba2a7c2017-10-04 19:24:46 +02001488 'custom_vars': custom_vars or {},
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001489 })
1490
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001491 def _SaveEntries(self):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001492 """Creates a .gclient_entries file to record the list of unique checkouts.
1493
1494 The .gclient_entries file lives in the same directory as .gclient.
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001495 """
1496 # Sometimes pprint.pformat will use {', sometimes it'll use { ' ... It
1497 # makes testing a bit too fun.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001498 result = 'entries = {\n'
maruel@chromium.org68988972011-09-20 14:11:42 +00001499 for entry in self.root.subtree(False):
agabled437d762016-10-17 09:35:11 -07001500 result += ' %s: %s,\n' % (pprint.pformat(entry.name),
1501 pprint.pformat(entry.parsed_url))
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001502 result += '}\n'
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001503 file_path = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org1333cb32011-10-04 23:40:16 +00001504 logging.debug(result)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001505 gclient_utils.FileWrite(file_path, result)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001506
1507 def _ReadEntries(self):
1508 """Read the .gclient_entries file for the given client.
1509
1510 Returns:
1511 A sequence of solution names, which will be empty if there is the
1512 entries file hasn't been created yet.
1513 """
1514 scope = {}
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001515 filename = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001516 if not os.path.exists(filename):
maruel@chromium.org73e21142010-07-05 13:32:01 +00001517 return {}
maruel@chromium.org5990f9d2010-07-07 18:02:58 +00001518 try:
1519 exec(gclient_utils.FileRead(filename), scope)
vapier@chromium.orga81a56e2015-11-11 07:56:13 +00001520 except SyntaxError as e:
maruel@chromium.org5990f9d2010-07-07 18:02:58 +00001521 gclient_utils.SyntaxErrorToError(filename, e)
Aaron Gable3721ee92017-04-03 14:53:14 -07001522 return scope.get('entries', {})
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001523
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001524 def _EnforceRevisions(self):
maruel@chromium.org918a9ae2010-05-28 15:50:30 +00001525 """Checks for revision overrides."""
1526 revision_overrides = {}
smutae7ea312016-07-18 11:59:41 -07001527 if self._options.head:
1528 return revision_overrides
joi@chromium.org792ea882010-11-10 02:37:27 +00001529 if not self._options.revisions:
1530 for s in self.dependencies:
smutae7ea312016-07-18 11:59:41 -07001531 if not s.managed:
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001532 self._options.revisions.append('%s@unmanaged' % s.name)
maruel@chromium.org307d1792010-05-31 20:03:13 +00001533 if not self._options.revisions:
1534 return revision_overrides
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001535 solutions_names = [s.name for s in self.dependencies]
smutae7ea312016-07-18 11:59:41 -07001536 index = 0
1537 for revision in self._options.revisions:
1538 if not '@' in revision:
maruel@chromium.org307d1792010-05-31 20:03:13 +00001539 # Support for --revision 123
smutae7ea312016-07-18 11:59:41 -07001540 revision = '%s@%s' % (solutions_names[index], revision)
1541 name, rev = revision.split('@', 1)
szager@chromium.org4ad264b2014-05-20 04:43:47 +00001542 revision_overrides[name] = rev
smutae7ea312016-07-18 11:59:41 -07001543 index += 1
maruel@chromium.org918a9ae2010-05-28 15:50:30 +00001544 return revision_overrides
1545
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001546 def RunOnDeps(self, command, args, ignore_requirements=False, progress=True):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001547 """Runs a command on each dependency in a client and its dependencies.
1548
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001549 Args:
1550 command: The command to use (e.g., 'status' or 'diff')
1551 args: list of str - extra arguments to add to the command line.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001552 """
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001553 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +00001554 raise gclient_utils.Error('No solution specified')
borenet@google.com0a427372014-04-02 19:12:13 +00001555
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001556 revision_overrides = {}
1557 # It's unnecessary to check for revision overrides for 'recurse'.
1558 # Save a few seconds by not calling _EnforceRevisions() in that case.
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +02001559 if command not in ('diff', 'recurse', 'runhooks', 'status', 'revert',
1560 'validate'):
szager@chromium.org5273b8a2014-08-21 15:10:10 +00001561 self._CheckConfig()
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001562 revision_overrides = self._EnforceRevisions()
Daniel Chenga21b5b32017-10-19 20:07:48 +00001563 # Disable progress for non-tty stdout.
Daniel Chenga0c5f082017-10-19 13:35:19 -07001564 should_show_progress = (
1565 setup_color.IS_TTY and not self._options.verbose and progress)
1566 pm = None
1567 if should_show_progress:
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001568 if command in ('update', 'revert'):
1569 pm = Progress('Syncing projects', 1)
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +02001570 elif command in ('recurse', 'validate'):
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001571 pm = Progress(' '.join(args), 1)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001572 work_queue = gclient_utils.ExecutionQueue(
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001573 self._options.jobs, pm, ignore_requirements=ignore_requirements,
1574 verbose=self._options.verbose)
maruel@chromium.org049bced2010-08-12 13:37:20 +00001575 for s in self.dependencies:
Paweł Hajdan, Jr4baaa112017-07-04 19:09:32 +02001576 if s.should_process:
1577 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +00001578 work_queue.flush(revision_overrides, command, args, options=self._options)
szager@chromium.org4ad264b2014-05-20 04:43:47 +00001579 if revision_overrides:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001580 print('Please fix your script, having invalid --revision flags will soon '
1581 'considered an error.', file=sys.stderr)
piman@chromium.org6f363722010-04-27 00:41:09 +00001582
Dirk Pranke9f20d022017-10-11 18:36:54 -07001583 # Once all the dependencies have been processed, it's now safe to write
1584 # out any gn_args_files and run the hooks.
1585 if command == 'update':
1586 self.WriteGNArgsFilesRecursively(self.dependencies)
1587
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001588 if not self._options.nohooks:
Daniel Chenga0c5f082017-10-19 13:35:19 -07001589 if should_show_progress:
1590 pm = Progress('Running hooks', 1)
1591 self.RunHooksRecursively(self._options, pm)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001592
1593 if command == 'update':
ajwong@chromium.orgcdcee802009-06-23 15:30:42 +00001594 # Notify the user if there is an orphaned entry in their working copy.
1595 # Only delete the directory if there are no changes in it, and
1596 # delete_unversioned_trees is set to true.
maruel@chromium.org68988972011-09-20 14:11:42 +00001597 entries = [i.name for i in self.root.subtree(False) if i.url]
xusydoc@chromium.org885a9602013-05-31 09:54:40 +00001598 full_entries = [os.path.join(self.root_dir, e.replace('/', os.path.sep))
1599 for e in entries]
1600
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001601 for entry, prev_url in self._ReadEntries().iteritems():
maruel@chromium.org04dd7de2010-10-14 13:25:49 +00001602 if not prev_url:
1603 # entry must have been overridden via .gclient custom_deps
1604 continue
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001605 # Fix path separator on Windows.
1606 entry_fixed = entry.replace('/', os.path.sep)
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001607 e_dir = os.path.join(self.root_dir, entry_fixed)
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001608 # Use entry and not entry_fixed there.
jochen@chromium.orga78e5532013-03-11 13:33:03 +00001609 if (entry not in entries and
1610 (not any(path.startswith(entry + '/') for path in entries)) and
jochen@chromium.orgcc475722013-03-11 13:07:40 +00001611 os.path.exists(e_dir)):
primiano@chromium.org1c127382015-02-17 11:15:40 +00001612 # The entry has been removed from DEPS.
John Budorick0f7b2002018-01-19 15:46:17 -08001613 scm = self.CreateSCM(
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001614 prev_url, self.root_dir, entry_fixed, self.outbuf)
xusydoc@chromium.org885a9602013-05-31 09:54:40 +00001615
1616 # Check to see if this directory is now part of a higher-up checkout.
borenet@google.com359bb642014-05-13 17:28:19 +00001617 scm_root = None
agabled437d762016-10-17 09:35:11 -07001618 try:
1619 scm_root = gclient_scm.scm.GIT.GetCheckoutRoot(scm.checkout_path)
1620 except subprocess2.CalledProcessError:
1621 pass
1622 if not scm_root:
borenet@google.com359bb642014-05-13 17:28:19 +00001623 logging.warning('Could not find checkout root for %s. Unable to '
1624 'determine whether it is part of a higher-level '
1625 'checkout, so not removing.' % entry)
1626 continue
primiano@chromium.org1c127382015-02-17 11:15:40 +00001627
1628 # This is to handle the case of third_party/WebKit migrating from
1629 # being a DEPS entry to being part of the main project.
1630 # If the subproject is a Git project, we need to remove its .git
1631 # folder. Otherwise git operations on that folder will have different
1632 # effects depending on the current working directory.
agabled437d762016-10-17 09:35:11 -07001633 if os.path.abspath(scm_root) == os.path.abspath(e_dir):
primiano@chromium.org1c127382015-02-17 11:15:40 +00001634 e_par_dir = os.path.join(e_dir, os.pardir)
agabled437d762016-10-17 09:35:11 -07001635 if gclient_scm.scm.GIT.IsInsideWorkTree(e_par_dir):
1636 par_scm_root = gclient_scm.scm.GIT.GetCheckoutRoot(e_par_dir)
primiano@chromium.org1c127382015-02-17 11:15:40 +00001637 # rel_e_dir : relative path of entry w.r.t. its parent repo.
1638 rel_e_dir = os.path.relpath(e_dir, par_scm_root)
agabled437d762016-10-17 09:35:11 -07001639 if gclient_scm.scm.GIT.IsDirectoryVersioned(
1640 par_scm_root, rel_e_dir):
primiano@chromium.org1c127382015-02-17 11:15:40 +00001641 save_dir = scm.GetGitBackupDirPath()
1642 # Remove any eventual stale backup dir for the same project.
1643 if os.path.exists(save_dir):
1644 gclient_utils.rmtree(save_dir)
1645 os.rename(os.path.join(e_dir, '.git'), save_dir)
1646 # When switching between the two states (entry/ is a subproject
1647 # -> entry/ is part of the outer project), it is very likely
1648 # that some files are changed in the checkout, unless we are
1649 # jumping *exactly* across the commit which changed just DEPS.
1650 # In such case we want to cleanup any eventual stale files
1651 # (coming from the old subproject) in order to end up with a
1652 # clean checkout.
agabled437d762016-10-17 09:35:11 -07001653 gclient_scm.scm.GIT.CleanupDir(par_scm_root, rel_e_dir)
primiano@chromium.org1c127382015-02-17 11:15:40 +00001654 assert not os.path.exists(os.path.join(e_dir, '.git'))
1655 print(('\nWARNING: \'%s\' has been moved from DEPS to a higher '
1656 'level checkout. The git folder containing all the local'
1657 ' branches has been saved to %s.\n'
1658 'If you don\'t care about its state you can safely '
1659 'remove that folder to free up space.') %
1660 (entry, save_dir))
1661 continue
1662
borenet@google.com359bb642014-05-13 17:28:19 +00001663 if scm_root in full_entries:
primiano@chromium.org1c127382015-02-17 11:15:40 +00001664 logging.info('%s is part of a higher level checkout, not removing',
1665 scm.GetCheckoutRoot())
xusydoc@chromium.org885a9602013-05-31 09:54:40 +00001666 continue
1667
1668 file_list = []
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001669 scm.status(self._options, [], file_list)
1670 modified_files = file_list != []
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001671 if (not self._options.delete_unversioned_trees or
1672 (modified_files and not self._options.force)):
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001673 # There are modified files in this entry. Keep warning until
1674 # removed.
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001675 print(('\nWARNING: \'%s\' is no longer part of this client. '
1676 'It is recommended that you manually remove it.\n') %
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001677 entry_fixed)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001678 else:
1679 # Delete the entry
maruel@chromium.org73e21142010-07-05 13:32:01 +00001680 print('\n________ deleting \'%s\' in \'%s\'' % (
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001681 entry_fixed, self.root_dir))
digit@chromium.orgdc112ac2013-04-24 13:00:19 +00001682 gclient_utils.rmtree(e_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001683 # record the current list of entries for next time
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001684 self._SaveEntries()
maruel@chromium.org17cdf762010-05-28 17:30:52 +00001685 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001686
1687 def PrintRevInfo(self):
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001688 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +00001689 raise gclient_utils.Error('No solution specified')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001690 # Load all the settings.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001691 work_queue = gclient_utils.ExecutionQueue(
1692 self._options.jobs, None, False, verbose=self._options.verbose)
maruel@chromium.org049bced2010-08-12 13:37:20 +00001693 for s in self.dependencies:
Paweł Hajdan, Jr4baaa112017-07-04 19:09:32 +02001694 if s.should_process:
1695 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +00001696 work_queue.flush({}, None, [], options=self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001697
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001698 def GetURLAndRev(dep):
1699 """Returns the revision-qualified SCM url for a Dependency."""
1700 if dep.parsed_url is None:
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001701 return None
agabled437d762016-10-17 09:35:11 -07001702 url, _ = gclient_utils.SplitUrlRevision(dep.parsed_url)
John Budorick0f7b2002018-01-19 15:46:17 -08001703 scm = dep.CreateSCM(
agabled437d762016-10-17 09:35:11 -07001704 dep.parsed_url, self.root_dir, dep.name, self.outbuf)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001705 if not os.path.isdir(scm.checkout_path):
1706 return None
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001707 return '%s@%s' % (url, scm.revinfo(self._options, [], None))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001708
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001709 if self._options.snapshot:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001710 new_gclient = ''
1711 # First level at .gclient
1712 for d in self.dependencies:
1713 entries = {}
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001714 def GrabDeps(dep):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001715 """Recursively grab dependencies."""
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001716 for d in dep.dependencies:
1717 entries[d.name] = GetURLAndRev(d)
1718 GrabDeps(d)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001719 GrabDeps(d)
1720 custom_deps = []
1721 for k in sorted(entries.keys()):
1722 if entries[k]:
1723 # Quotes aren't escaped...
1724 custom_deps.append(' \"%s\": \'%s\',\n' % (k, entries[k]))
1725 else:
1726 custom_deps.append(' \"%s\": None,\n' % k)
1727 new_gclient += self.DEFAULT_SNAPSHOT_SOLUTION_TEXT % {
1728 'solution_name': d.name,
1729 'solution_url': d.url,
nsylvain@google.comefc80932011-05-31 21:27:56 +00001730 'deps_file': d.deps_file,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001731 'managed': d.managed,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001732 'solution_deps': ''.join(custom_deps),
1733 }
1734 # Print the snapshot configuration file
1735 print(self.DEFAULT_SNAPSHOT_FILE_TEXT % {'solution_list': new_gclient})
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +00001736 else:
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001737 entries = {}
maruel@chromium.org68988972011-09-20 14:11:42 +00001738 for d in self.root.subtree(False):
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001739 if self._options.actual:
1740 entries[d.name] = GetURLAndRev(d)
1741 else:
1742 entries[d.name] = d.parsed_url
1743 keys = sorted(entries.keys())
1744 for x in keys:
maruel@chromium.orgce464892010-08-12 17:12:18 +00001745 print('%s: %s' % (x, entries[x]))
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +00001746 logging.info(str(self))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001747
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001748 def ParseDepsFile(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001749 """No DEPS to parse for a .gclient file."""
maruel@chromium.org049bced2010-08-12 13:37:20 +00001750 raise gclient_utils.Error('Internal error')
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001751
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00001752 def PrintLocationAndContents(self):
1753 # Print out the .gclient file. This is longer than if we just printed the
1754 # client dict, but more legible, and it might contain helpful comments.
1755 print('Loaded .gclient config in %s:\n%s' % (
1756 self.root_dir, self.config_content))
1757
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001758 @property
maruel@chromium.org75a59272010-06-11 22:34:03 +00001759 def root_dir(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001760 """Root directory of gclient checkout."""
maruel@chromium.org75a59272010-06-11 22:34:03 +00001761 return self._root_dir
1762
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001763 @property
maruel@chromium.org271375b2010-06-23 19:17:38 +00001764 def enforced_os(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001765 """What deps_os entries that are to be parsed."""
maruel@chromium.org271375b2010-06-23 19:17:38 +00001766 return self._enforced_os
1767
maruel@chromium.org68988972011-09-20 14:11:42 +00001768 @property
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001769 def recursion_limit(self):
1770 """How recursive can each dependencies in DEPS file can load DEPS file."""
1771 return self._recursion_limit
1772
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +00001773 @property
cmp@chromium.orgc401ad12014-07-02 23:20:08 +00001774 def try_recursedeps(self):
1775 """Whether to attempt using recursedeps-style recursion processing."""
cmp@chromium.orge84ac912014-06-30 23:14:35 +00001776 return True
1777
1778 @property
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +00001779 def target_os(self):
1780 return self._enforced_os
1781
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001782
John Budorick0f7b2002018-01-19 15:46:17 -08001783class GitDependency(Dependency):
1784 """A Dependency object that represents a single git checkout."""
1785
1786 #override
1787 def GetScmName(self, url):
1788 """Always 'git'."""
1789 del url
1790 return 'git'
1791
1792 #override
1793 def CreateSCM(self, url, root_dir=None, relpath=None, out_fh=None,
1794 out_cb=None):
1795 """Create a Wrapper instance suitable for handling this git dependency."""
1796 return gclient_scm.GitWrapper(url, root_dir, relpath, out_fh, out_cb)
1797
1798
1799class CipdDependency(Dependency):
1800 """A Dependency object that represents a single CIPD package."""
1801
1802 def __init__(
1803 self, parent, name, dep_value, cipd_root,
1804 custom_vars, should_process, relative, condition, condition_value):
1805 package = dep_value['package']
1806 version = dep_value['version']
1807 url = urlparse.urljoin(
1808 cipd_root.service_url, '%s@%s' % (package, version))
1809 super(CipdDependency, self).__init__(
1810 parent, name, url, url, None, None, custom_vars,
1811 None, None, should_process, relative, condition, condition_value)
1812 if relative:
1813 # TODO(jbudorick): Implement relative if necessary.
1814 raise gclient_utils.Error(
1815 'Relative CIPD dependencies are not currently supported.')
1816 self._cipd_root = cipd_root
1817
1818 self._cipd_subdir = os.path.relpath(
1819 os.path.join(self.root.root_dir, self.name), cipd_root.root_dir)
1820 self._cipd_package = self._cipd_root.add_package(
1821 self._cipd_subdir, package, version)
1822
1823 def ParseDepsFile(self):
1824 """CIPD dependencies are not currently allowed to have nested deps."""
1825 self.add_dependencies_and_close([], [])
1826
1827 #override
1828 def GetScmName(self, url):
1829 """Always 'cipd'."""
1830 del url
1831 return 'cipd'
1832
1833 #override
1834 def CreateSCM(self, url, root_dir=None, relpath=None, out_fh=None,
1835 out_cb=None):
1836 """Create a Wrapper instance suitable for handling this CIPD dependency."""
1837 return gclient_scm.CipdWrapper(
1838 url, root_dir, relpath, out_fh, out_cb,
1839 root=self._cipd_root,
1840 package=self._cipd_package)
1841
1842 def ToLines(self):
1843 """Return a list of lines representing this in a DEPS file."""
1844 s = []
1845 if self._cipd_package.authority_for_subdir:
1846 condition_part = ([' "condition": %r,' % self.condition]
1847 if self.condition else [])
1848 s.extend([
1849 ' # %s' % self.hierarchy(include_url=False),
1850 ' "%s": {' % (self.name,),
1851 ' "packages": [',
1852 ])
1853 for p in self._cipd_root.packages(self._cipd_subdir):
1854 s.extend([
1855 ' "package": "%s",' % p.name,
1856 ' "version": "%s",' % p.version,
1857 ])
1858 s.extend([
1859 ' ],',
1860 ' "dep_type": "cipd",',
1861 ] + condition_part + [
1862 ' },',
1863 '',
1864 ])
1865 return s
1866
1867
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001868#### gclient commands.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001869
1870
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001871@subcommand.usage('[command] [args ...]')
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001872def CMDrecurse(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001873 """Operates [command args ...] on all the dependencies.
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001874
1875 Runs a shell command on all entries.
qyearsley12fa6ff2016-08-24 09:18:40 -07001876 Sets GCLIENT_DEP_PATH environment variable as the dep's relative location to
ilevy@chromium.org37116242012-11-28 01:32:48 +00001877 root directory of the checkout.
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001878 """
1879 # Stop parsing at the first non-arg so that these go through to the command
1880 parser.disable_interspersed_args()
1881 parser.add_option('-s', '--scm', action='append', default=[],
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001882 help='Choose scm types to operate upon.')
maruel@chromium.org288054d2012-03-05 00:43:07 +00001883 parser.add_option('-i', '--ignore', action='store_true',
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001884 help='Ignore non-zero return codes from subcommands.')
1885 parser.add_option('--prepend-dir', action='store_true',
1886 help='Prepend relative dir for use with git <cmd> --null.')
1887 parser.add_option('--no-progress', action='store_true',
1888 help='Disable progress bar that shows sub-command updates')
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001889 options, args = parser.parse_args(args)
maruel@chromium.org45e9f2d2010-10-18 13:33:46 +00001890 if not args:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001891 print('Need to supply a command!', file=sys.stderr)
maruel@chromium.org45e9f2d2010-10-18 13:33:46 +00001892 return 1
maruel@chromium.org78cba522010-10-18 13:32:05 +00001893 root_and_entries = gclient_utils.GetGClientRootAndEntries()
1894 if not root_and_entries:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001895 print(
maruel@chromium.org78cba522010-10-18 13:32:05 +00001896 'You need to run gclient sync at least once to use \'recurse\'.\n'
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001897 'This is because .gclient_entries needs to exist and be up to date.',
1898 file=sys.stderr)
maruel@chromium.org78cba522010-10-18 13:32:05 +00001899 return 1
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001900
1901 # Normalize options.scm to a set()
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001902 scm_set = set()
1903 for scm in options.scm:
1904 scm_set.update(scm.split(','))
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001905 options.scm = scm_set
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001906
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001907 options.nohooks = True
1908 client = GClient.LoadCurrentConfig(options)
Marc-Antoine Ruele6e06412017-10-18 13:47:02 -04001909 if not client:
1910 raise gclient_utils.Error('client not configured; see \'gclient config\'')
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001911 return client.RunOnDeps('recurse', args, ignore_requirements=True,
1912 progress=not options.no_progress)
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001913
1914
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001915@subcommand.usage('[args ...]')
davidbarr@chromium.org12f944e2012-03-01 02:18:31 +00001916def CMDfetch(parser, args):
1917 """Fetches upstream commits for all modules.
1918
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001919 Completely git-specific. Simply runs 'git fetch [args ...]' for each module.
1920 """
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001921 (options, args) = parser.parse_args(args)
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00001922 return CMDrecurse(OptionParser(), [
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001923 '--jobs=%d' % options.jobs, '--scm=git', 'git', 'fetch'] + args)
1924
1925
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02001926class Flattener(object):
1927 """Flattens a gclient solution."""
1928
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02001929 def __init__(self, client, pin_all_deps=False):
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02001930 """Constructor.
1931
1932 Arguments:
1933 client (GClient): client to flatten
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02001934 pin_all_deps (bool): whether to pin all deps, even if they're not pinned
1935 in DEPS
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02001936 """
1937 self._client = client
1938
1939 self._deps_string = None
Paweł Hajdan, Jrfeb01642017-09-12 15:50:46 +02001940 self._deps_files = set()
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02001941
1942 self._allowed_hosts = set()
1943 self._deps = {}
1944 self._deps_os = {}
1945 self._hooks = []
1946 self._hooks_os = {}
1947 self._pre_deps_hooks = []
Paweł Hajdan, Jrfb022012017-07-06 18:00:08 +02001948 self._vars = {}
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02001949
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02001950 self._flatten(pin_all_deps=pin_all_deps)
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02001951
1952 @property
1953 def deps_string(self):
1954 assert self._deps_string is not None
1955 return self._deps_string
1956
Paweł Hajdan, Jrfeb01642017-09-12 15:50:46 +02001957 @property
1958 def deps_files(self):
1959 return self._deps_files
1960
Paweł Hajdan, Jr5ec77132017-08-16 19:21:06 +02001961 def _pin_dep(self, dep):
1962 """Pins a dependency to specific full revision sha.
1963
1964 Arguments:
1965 dep (Dependency): dependency to process
1966 """
1967 if dep.parsed_url is None:
1968 return
1969
1970 # Make sure the revision is always fully specified (a hash),
1971 # as opposed to refs or tags which might change. Similarly,
1972 # shortened shas might become ambiguous; make sure to always
1973 # use full one for pinning.
1974 url, revision = gclient_utils.SplitUrlRevision(dep.parsed_url)
1975 if revision and gclient_utils.IsFullGitSha(revision):
1976 return
1977
John Budorick0f7b2002018-01-19 15:46:17 -08001978 scm = dep.CreateSCM(
Paweł Hajdan, Jr5ec77132017-08-16 19:21:06 +02001979 dep.parsed_url, self._client.root_dir, dep.name, dep.outbuf)
1980 revinfo = scm.revinfo(self._client._options, [], None)
1981
1982 dep._parsed_url = dep._url = '%s@%s' % (url, revinfo)
1983 raw_url, _ = gclient_utils.SplitUrlRevision(dep._raw_url)
1984 dep._raw_url = '%s@%s' % (raw_url, revinfo)
1985
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02001986 def _flatten(self, pin_all_deps=False):
1987 """Runs the flattener. Saves resulting DEPS string.
1988
1989 Arguments:
1990 pin_all_deps (bool): whether to pin all deps, even if they're not pinned
1991 in DEPS
1992 """
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02001993 for solution in self._client.dependencies:
Paweł Hajdan, Jr11eb7152017-08-10 12:50:11 +02001994 self._add_dep(solution)
Paweł Hajdan, Jrb0ad16e2017-08-03 15:33:21 +02001995 self._flatten_dep(solution)
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02001996
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02001997 if pin_all_deps:
1998 for dep in self._deps.itervalues():
Paweł Hajdan, Jr5ec77132017-08-16 19:21:06 +02001999 self._pin_dep(dep)
Paweł Hajdan, Jr39300ba2017-08-11 16:52:38 +02002000
Paweł Hajdan, Jr5ec77132017-08-16 19:21:06 +02002001 for os_deps in self._deps_os.itervalues():
2002 for dep in os_deps.itervalues():
Paweł Hajdan, Jr5ec77132017-08-16 19:21:06 +02002003 self._pin_dep(dep)
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02002004
Paweł Hajdan, Jr76a9d042017-08-18 20:05:41 +02002005 def add_deps_file(dep):
Paweł Hajdan, Jr0870df22017-08-23 17:59:29 +02002006 # Only include DEPS files referenced by recursedeps.
2007 if not (dep.parent is None or
2008 (dep.name in (dep.parent.recursedeps or {}))):
2009 return
Paweł Hajdan, Jrfeb01642017-09-12 15:50:46 +02002010 deps_file = dep.deps_file
2011 deps_path = os.path.join(self._client.root_dir, dep.name, deps_file)
Paweł Hajdan, Jr76a9d042017-08-18 20:05:41 +02002012 if not os.path.exists(deps_path):
Paweł Hajdan, Jrfeb01642017-09-12 15:50:46 +02002013 # gclient has a fallback that if deps_file doesn't exist, it'll try
2014 # DEPS. Do the same here.
2015 deps_file = 'DEPS'
2016 deps_path = os.path.join(self._client.root_dir, dep.name, deps_file)
2017 if not os.path.exists(deps_path):
2018 return
Paweł Hajdan, Jr76a9d042017-08-18 20:05:41 +02002019 assert dep.parsed_url
Paweł Hajdan, Jrfeb01642017-09-12 15:50:46 +02002020 self._deps_files.add((dep.parsed_url, deps_file))
Paweł Hajdan, Jr76a9d042017-08-18 20:05:41 +02002021 for dep in self._deps.itervalues():
2022 add_deps_file(dep)
2023 for os_deps in self._deps_os.itervalues():
2024 for dep in os_deps.itervalues():
2025 add_deps_file(dep)
2026
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002027 self._deps_string = '\n'.join(
2028 _GNSettingsToLines(
2029 self._client.dependencies[0]._gn_args_file,
2030 self._client.dependencies[0]._gn_args) +
2031 _AllowedHostsToLines(self._allowed_hosts) +
2032 _DepsToLines(self._deps) +
2033 _DepsOsToLines(self._deps_os) +
2034 _HooksToLines('hooks', self._hooks) +
2035 _HooksToLines('pre_deps_hooks', self._pre_deps_hooks) +
2036 _HooksOsToLines(self._hooks_os) +
Paweł Hajdan, Jrfb022012017-07-06 18:00:08 +02002037 _VarsToLines(self._vars) +
Paweł Hajdan, Jr76a9d042017-08-18 20:05:41 +02002038 ['# %s, %s' % (url, deps_file)
Paweł Hajdan, Jrfeb01642017-09-12 15:50:46 +02002039 for url, deps_file in sorted(self._deps_files)] +
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002040 ['']) # Ensure newline at end of file.
2041
Paweł Hajdan, Jr11eb7152017-08-10 12:50:11 +02002042 def _add_dep(self, dep):
2043 """Helper to add a dependency to flattened DEPS.
2044
2045 Arguments:
2046 dep (Dependency): dependency to add
2047 """
2048 assert dep.name not in self._deps or self._deps.get(dep.name) == dep, (
2049 dep.name, self._deps.get(dep.name))
Paweł Hajdan, Jr9a289022017-08-10 16:04:24 +02002050 if dep.url:
2051 self._deps[dep.name] = dep
Paweł Hajdan, Jr11eb7152017-08-10 12:50:11 +02002052
Paweł Hajdan, Jradae2a62017-08-18 16:49:57 +02002053 def _add_os_dep(self, os_dep, dep_os):
2054 """Helper to add an OS-specific dependency to flattened DEPS.
2055
2056 Arguments:
2057 os_dep (Dependency): dependency to add
2058 dep_os (str): name of the OS
2059 """
2060 assert (
2061 os_dep.name not in self._deps_os.get(dep_os, {}) or
2062 self._deps_os.get(dep_os, {}).get(os_dep.name) == os_dep), (
2063 os_dep.name, self._deps_os.get(dep_os, {}).get(os_dep.name))
2064 if os_dep.url:
Paweł Hajdan, Jr76a9d042017-08-18 20:05:41 +02002065 # OS-specific deps need to have their full URL resolved manually.
2066 assert not os_dep.parsed_url, (os_dep, os_dep.parsed_url)
2067 os_dep._parsed_url = os_dep.LateOverride(os_dep.url)
2068
Paweł Hajdan, Jradae2a62017-08-18 16:49:57 +02002069 self._deps_os.setdefault(dep_os, {})[os_dep.name] = os_dep
2070
2071 def _flatten_dep(self, dep, dep_os=None):
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002072 """Visits a dependency in order to flatten it (see CMDflatten).
2073
2074 Arguments:
2075 dep (Dependency): dependency to process
Paweł Hajdan, Jradae2a62017-08-18 16:49:57 +02002076 dep_os (str or None): name of the OS |dep| is specific to
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002077 """
Paweł Hajdan, Jradae2a62017-08-18 16:49:57 +02002078 logging.debug('_flatten_dep(%s, %s)', dep.name, dep_os)
2079
Paweł Hajdan, Jrc69b32e2017-08-17 18:47:48 +02002080 if not dep.deps_parsed:
2081 dep.ParseDepsFile()
2082
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002083 self._allowed_hosts.update(dep.allowed_hosts)
2084
Paweł Hajdan, Jra3b67ae2017-08-30 15:18:21 +02002085 # Only include vars listed in the DEPS files, not possible local overrides.
2086 for key, value in dep._vars.iteritems():
Paweł Hajdan, Jrc9353602017-08-02 17:52:08 +02002087 # Make sure there are no conflicting variables. It is fine however
2088 # to use same variable name, as long as the value is consistent.
2089 assert key not in self._vars or self._vars[key][1] == value
Paweł Hajdan, Jrfb022012017-07-06 18:00:08 +02002090 self._vars[key] = (dep, value)
2091
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002092 self._pre_deps_hooks.extend([(dep, hook) for hook in dep.pre_deps_hooks])
2093
Paweł Hajdan, Jradae2a62017-08-18 16:49:57 +02002094 if dep_os:
2095 if dep.deps_hooks:
2096 self._hooks_os.setdefault(dep_os, []).extend(
2097 [(dep, hook) for hook in dep.deps_hooks])
2098 else:
2099 self._hooks.extend([(dep, hook) for hook in dep.deps_hooks])
2100
Paweł Hajdan, Jrb0ad16e2017-08-03 15:33:21 +02002101 for sub_dep in dep.dependencies:
Paweł Hajdan, Jradae2a62017-08-18 16:49:57 +02002102 if dep_os:
2103 self._add_os_dep(sub_dep, dep_os)
2104 else:
2105 self._add_dep(sub_dep)
Paweł Hajdan, Jrb0ad16e2017-08-03 15:33:21 +02002106
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002107 for hook_os, os_hooks in dep.os_deps_hooks.iteritems():
2108 self._hooks_os.setdefault(hook_os, []).extend(
2109 [(dep, hook) for hook in os_hooks])
2110
Paweł Hajdan, Jradae2a62017-08-18 16:49:57 +02002111 for sub_dep_os, os_deps in dep.os_dependencies.iteritems():
Paweł Hajdan, Jre2deb1e2017-08-09 17:29:21 +02002112 for os_dep in os_deps:
Paweł Hajdan, Jradae2a62017-08-18 16:49:57 +02002113 self._add_os_dep(os_dep, sub_dep_os)
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002114
Paweł Hajdan, Jradae2a62017-08-18 16:49:57 +02002115 # Process recursedeps. |deps_by_name| is a map where keys are dependency
2116 # names, and values are maps of OS names to |Dependency| instances.
2117 # |None| in place of OS name means the dependency is not OS-specific.
2118 deps_by_name = dict((d.name, {None: d}) for d in dep.dependencies)
2119 for sub_dep_os, os_deps in dep.os_dependencies.iteritems():
Paweł Hajdan, Jrc9353602017-08-02 17:52:08 +02002120 for os_dep in os_deps:
Paweł Hajdan, Jradae2a62017-08-18 16:49:57 +02002121 assert sub_dep_os not in deps_by_name.get(os_dep.name, {}), (
2122 os_dep.name, sub_dep_os)
2123 deps_by_name.setdefault(os_dep.name, {})[sub_dep_os] = os_dep
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002124 for recurse_dep_name in (dep.recursedeps or []):
Paweł Hajdan, Jradae2a62017-08-18 16:49:57 +02002125 dep_info = deps_by_name[recurse_dep_name]
2126 for sub_dep_os, os_dep in dep_info.iteritems():
2127 self._flatten_dep(os_dep, dep_os=(sub_dep_os or dep_os))
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002128
2129
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002130def CMDflatten(parser, args):
2131 """Flattens the solutions into a single DEPS file."""
2132 parser.add_option('--output-deps', help='Path to the output DEPS file')
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02002133 parser.add_option(
Paweł Hajdan, Jrfeb01642017-09-12 15:50:46 +02002134 '--output-deps-files',
2135 help=('Path to the output metadata about DEPS files referenced by '
2136 'recursedeps.'))
2137 parser.add_option(
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02002138 '--pin-all-deps', action='store_true',
2139 help=('Pin all deps, even if not pinned in DEPS. CAVEAT: only does so '
2140 'for checked out deps, NOT deps_os.'))
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002141 options, args = parser.parse_args(args)
2142
Paweł Hajdan, Jr357415c2017-07-24 14:35:28 +02002143 options.do_not_merge_os_specific_entries = True
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002144 options.nohooks = True
Paweł Hajdan, Jr5ec77132017-08-16 19:21:06 +02002145 options.process_all_deps = True
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002146 client = GClient.LoadCurrentConfig(options)
2147
2148 # Only print progress if we're writing to a file. Otherwise, progress updates
2149 # could obscure intended output.
2150 code = client.RunOnDeps('flatten', args, progress=options.output_deps)
2151 if code != 0:
2152 return code
2153
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02002154 flattener = Flattener(client, pin_all_deps=options.pin_all_deps)
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002155
2156 if options.output_deps:
2157 with open(options.output_deps, 'w') as f:
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002158 f.write(flattener.deps_string)
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002159 else:
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002160 print(flattener.deps_string)
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002161
Paweł Hajdan, Jrfeb01642017-09-12 15:50:46 +02002162 deps_files = [{'url': d[0], 'deps_file': d[1]}
2163 for d in sorted(flattener.deps_files)]
2164 if options.output_deps_files:
2165 with open(options.output_deps_files, 'w') as f:
2166 json.dump(deps_files, f)
2167
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002168 return 0
2169
2170
Paweł Hajdan, Jr3c2aa832017-06-07 20:22:16 +02002171def _GNSettingsToLines(gn_args_file, gn_args):
2172 s = []
2173 if gn_args_file:
2174 s.extend([
2175 'gclient_gn_args_file = "%s"' % gn_args_file,
2176 'gclient_gn_args = %r' % gn_args,
2177 ])
2178 return s
2179
2180
Paweł Hajdan, Jr6014b562017-06-30 17:43:42 +02002181def _AllowedHostsToLines(allowed_hosts):
2182 """Converts |allowed_hosts| set to list of lines for output."""
2183 if not allowed_hosts:
2184 return []
2185 s = ['allowed_hosts = [']
2186 for h in sorted(allowed_hosts):
2187 s.append(' "%s",' % h)
2188 s.extend([']', ''])
2189 return s
2190
2191
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002192def _DepsToLines(deps):
2193 """Converts |deps| dict to list of lines for output."""
Paweł Hajdan, Jr5b593352017-06-29 18:37:45 +02002194 if not deps:
2195 return []
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002196 s = ['deps = {']
John Budorick0f7b2002018-01-19 15:46:17 -08002197 for _, dep in sorted(deps.iteritems()):
2198 s.extend(dep.ToLines())
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002199 s.extend(['}', ''])
2200 return s
2201
2202
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02002203def _DepsOsToLines(deps_os):
2204 """Converts |deps_os| dict to list of lines for output."""
Paweł Hajdan, Jr5b593352017-06-29 18:37:45 +02002205 if not deps_os:
2206 return []
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02002207 s = ['deps_os = {']
2208 for dep_os, os_deps in sorted(deps_os.iteritems()):
2209 s.append(' "%s": {' % dep_os)
Paweł Hajdan, Jrad30de62017-06-26 18:51:58 +02002210 for name, dep in sorted(os_deps.iteritems()):
Paweł Hajdan, Jr78ce24e2017-10-03 17:09:13 +02002211 condition_part = ([' "condition": %r,' % dep.condition]
Paweł Hajdan, Jrad30de62017-06-26 18:51:58 +02002212 if dep.condition else [])
2213 s.extend([
2214 ' # %s' % dep.hierarchy(include_url=False),
2215 ' "%s": {' % (name,),
Paweł Hajdan, Jrde86ab32017-08-10 13:55:16 +02002216 ' "url": "%s",' % (dep.raw_url,),
Paweł Hajdan, Jrad30de62017-06-26 18:51:58 +02002217 ] + condition_part + [
2218 ' },',
2219 '',
2220 ])
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02002221 s.extend([' },', ''])
2222 s.extend(['}', ''])
2223 return s
2224
2225
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002226def _HooksToLines(name, hooks):
2227 """Converts |hooks| list to list of lines for output."""
Paweł Hajdan, Jr5b593352017-06-29 18:37:45 +02002228 if not hooks:
2229 return []
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002230 s = ['%s = [' % name]
2231 for dep, hook in hooks:
2232 s.extend([
2233 ' # %s' % dep.hierarchy(include_url=False),
2234 ' {',
2235 ])
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +02002236 if hook.name is not None:
2237 s.append(' "name": "%s",' % hook.name)
2238 if hook.pattern is not None:
2239 s.append(' "pattern": "%s",' % hook.pattern)
Paweł Hajdan, Jrecf53fe2017-09-29 18:28:49 +02002240 if hook.condition is not None:
Paweł Hajdan, Jr78ce24e2017-10-03 17:09:13 +02002241 s.append(' "condition": %r,' % hook.condition)
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002242 s.extend(
Paweł Hajdan, Jrc9364392017-06-14 17:11:56 +02002243 # Hooks run in the parent directory of their dep.
Paweł Hajdan, Jrad30de62017-06-26 18:51:58 +02002244 [' "cwd": "%s",' % os.path.normpath(os.path.dirname(dep.name))] +
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002245 [' "action": ['] +
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +02002246 [' "%s",' % arg for arg in hook.action] +
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002247 [' ]', ' },', '']
2248 )
2249 s.extend([']', ''])
2250 return s
2251
2252
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +02002253def _HooksOsToLines(hooks_os):
2254 """Converts |hooks| list to list of lines for output."""
Paweł Hajdan, Jr5b593352017-06-29 18:37:45 +02002255 if not hooks_os:
2256 return []
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +02002257 s = ['hooks_os = {']
2258 for hook_os, os_hooks in hooks_os.iteritems():
Michael Moss017bcf62017-06-28 15:26:38 -07002259 s.append(' "%s": [' % hook_os)
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +02002260 for dep, hook in os_hooks:
2261 s.extend([
2262 ' # %s' % dep.hierarchy(include_url=False),
2263 ' {',
2264 ])
2265 if hook.name is not None:
2266 s.append(' "name": "%s",' % hook.name)
2267 if hook.pattern is not None:
2268 s.append(' "pattern": "%s",' % hook.pattern)
Paweł Hajdan, Jrecf53fe2017-09-29 18:28:49 +02002269 if hook.condition is not None:
Paweł Hajdan, Jr78ce24e2017-10-03 17:09:13 +02002270 s.append(' "condition": %r,' % hook.condition)
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +02002271 s.extend(
2272 # Hooks run in the parent directory of their dep.
2273 [' "cwd": "%s",' % os.path.normpath(os.path.dirname(dep.name))] +
2274 [' "action": ['] +
2275 [' "%s",' % arg for arg in hook.action] +
2276 [' ]', ' },', '']
2277 )
Michael Moss017bcf62017-06-28 15:26:38 -07002278 s.extend([' ],', ''])
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +02002279 s.extend(['}', ''])
2280 return s
2281
2282
Paweł Hajdan, Jrfb022012017-07-06 18:00:08 +02002283def _VarsToLines(variables):
2284 """Converts |variables| dict to list of lines for output."""
2285 if not variables:
2286 return []
2287 s = ['vars = {']
2288 for key, tup in sorted(variables.iteritems()):
2289 dep, value = tup
2290 s.extend([
2291 ' # %s' % dep.hierarchy(include_url=False),
2292 ' "%s": %r,' % (key, value),
2293 '',
2294 ])
2295 s.extend(['}', ''])
2296 return s
2297
2298
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00002299def CMDgrep(parser, args):
2300 """Greps through git repos managed by gclient.
2301
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002302 Runs 'git grep [args...]' for each module.
2303 """
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00002304 # We can't use optparse because it will try to parse arguments sent
2305 # to git grep and throw an error. :-(
2306 if not args or re.match('(-h|--help)$', args[0]):
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002307 print(
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00002308 'Usage: gclient grep [-j <N>] git-grep-args...\n\n'
2309 'Example: "gclient grep -j10 -A2 RefCountedBase" runs\n"git grep '
2310 '-A2 RefCountedBase" on each of gclient\'s git\nrepos with up to '
2311 '10 jobs.\n\nBonus: page output by appending "|& less -FRSX" to the'
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002312 ' end of your query.',
2313 file=sys.stderr)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00002314 return 1
2315
2316 jobs_arg = ['--jobs=1']
2317 if re.match(r'(-j|--jobs=)\d+$', args[0]):
2318 jobs_arg, args = args[:1], args[1:]
2319 elif re.match(r'(-j|--jobs)$', args[0]):
2320 jobs_arg, args = args[:2], args[2:]
2321
2322 return CMDrecurse(
2323 parser,
2324 jobs_arg + ['--ignore', '--prepend-dir', '--no-progress', '--scm=git',
2325 'git', 'grep', '--null', '--color=Always'] + args)
davidbarr@chromium.org12f944e2012-03-01 02:18:31 +00002326
2327
stip@chromium.orga735da22015-04-29 23:18:20 +00002328def CMDroot(parser, args):
2329 """Outputs the solution root (or current dir if there isn't one)."""
2330 (options, args) = parser.parse_args(args)
2331 client = GClient.LoadCurrentConfig(options)
2332 if client:
2333 print(os.path.abspath(client.root_dir))
2334 else:
2335 print(os.path.abspath('.'))
2336
2337
agablea98a6cd2016-11-15 14:30:10 -08002338@subcommand.usage('[url]')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002339def CMDconfig(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002340 """Creates a .gclient file in the current directory.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00002341
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002342 This specifies the configuration for further commands. After update/sync,
2343 top-level DEPS files in each module are read to determine dependent
2344 modules to operate on as well. If optional [url] parameter is
2345 provided, then configuration is read from a specified Subversion server
2346 URL.
2347 """
szager@chromium.orge2e03202012-07-31 18:05:16 +00002348 # We do a little dance with the --gclientfile option. 'gclient config' is the
2349 # only command where it's acceptable to have both '--gclientfile' and '--spec'
2350 # arguments. So, we temporarily stash any --gclientfile parameter into
2351 # options.output_config_file until after the (gclientfile xor spec) error
2352 # check.
2353 parser.remove_option('--gclientfile')
2354 parser.add_option('--gclientfile', dest='output_config_file',
2355 help='Specify an alternate .gclient file')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002356 parser.add_option('--name',
2357 help='overrides the default name for the solution')
nsylvain@google.comefc80932011-05-31 21:27:56 +00002358 parser.add_option('--deps-file', default='DEPS',
David Benjamin105e11e2017-10-16 10:39:35 -04002359 help='overrides the default name for the DEPS file for the '
nsylvain@google.comefc80932011-05-31 21:27:56 +00002360 'main solutions and all sub-dependencies')
smutae7ea312016-07-18 11:59:41 -07002361 parser.add_option('--unmanaged', action='store_true', default=False,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00002362 help='overrides the default behavior to make it possible '
smutae7ea312016-07-18 11:59:41 -07002363 'to have the main solution untouched by gclient '
2364 '(gclient will check out unmanaged dependencies but '
2365 'will never sync them)')
Paweł Hajdan, Jr3ba2a7c2017-10-04 19:24:46 +02002366 parser.add_option('--custom-var', action='append', dest='custom_vars',
2367 default=[],
2368 help='overrides variables; key=value syntax')
szager@chromium.orge2e03202012-07-31 18:05:16 +00002369 parser.set_defaults(config_filename=None)
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002370 (options, args) = parser.parse_args(args)
szager@chromium.orge2e03202012-07-31 18:05:16 +00002371 if options.output_config_file:
2372 setattr(options, 'config_filename', getattr(options, 'output_config_file'))
maruel@chromium.org5fc2a332010-05-26 19:37:15 +00002373 if ((options.spec and args) or len(args) > 2 or
2374 (not options.spec and not args)):
2375 parser.error('Inconsistent arguments. Use either --spec or one or 2 args')
2376
Paweł Hajdan, Jr3ba2a7c2017-10-04 19:24:46 +02002377 custom_vars = {}
2378 for arg in options.custom_vars:
2379 kv = arg.split('=', 1)
2380 if len(kv) != 2:
2381 parser.error('Invalid --custom-var argument: %r' % arg)
2382 custom_vars[kv[0]] = gclient_eval.EvaluateCondition(kv[1], {})
2383
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002384 client = GClient('.', options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002385 if options.spec:
2386 client.SetConfig(options.spec)
2387 else:
maruel@chromium.org1ab7ffc2009-06-03 17:21:37 +00002388 base_url = args[0].rstrip('/')
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00002389 if not options.name:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002390 name = base_url.split('/')[-1]
nsylvain@google.com12649ef2011-06-01 17:11:20 +00002391 if name.endswith('.git'):
2392 name = name[:-4]
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00002393 else:
2394 # specify an alternate relpath for the given URL.
2395 name = options.name
agable@chromium.orgf2214672015-10-27 21:02:48 +00002396 if not os.path.abspath(os.path.join(os.getcwd(), name)).startswith(
2397 os.getcwd()):
2398 parser.error('Do not pass a relative path for --name.')
2399 if any(x in ('..', '.', '/', '\\') for x in name.split(os.sep)):
2400 parser.error('Do not include relative path components in --name.')
2401
nsylvain@google.comefc80932011-05-31 21:27:56 +00002402 deps_file = options.deps_file
agablea98a6cd2016-11-15 14:30:10 -08002403 client.SetDefaultConfig(name, deps_file, base_url,
smutae7ea312016-07-18 11:59:41 -07002404 managed=not options.unmanaged,
Paweł Hajdan, Jr3ba2a7c2017-10-04 19:24:46 +02002405 cache_dir=options.cache_dir,
2406 custom_vars=custom_vars)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002407 client.SaveConfig()
maruel@chromium.org79692d62010-05-14 18:57:13 +00002408 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002409
2410
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002411@subcommand.epilog("""Example:
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002412 gclient pack > patch.txt
2413 generate simple patch for configured client and dependences
2414""")
2415def CMDpack(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002416 """Generates a patch which can be applied at the root of the tree.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00002417
agabled437d762016-10-17 09:35:11 -07002418 Internally, runs 'git diff' on each checked out module and
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002419 dependencies, and performs minimal postprocessing of the output. The
2420 resulting patch is printed to stdout and can be applied to a freshly
2421 checked out tree via 'patch -p0 < patchfile'.
2422 """
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002423 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2424 help='override deps for the specified (comma-separated) '
2425 'platform(s); \'all\' will process all deps_os '
2426 'references')
haitao.feng@intel.com306080c2012-05-04 13:11:29 +00002427 parser.remove_option('--jobs')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002428 (options, args) = parser.parse_args(args)
iannucci@chromium.org50395ea2013-04-04 04:47:42 +00002429 # Force jobs to 1 so the stdout is not annotated with the thread ids
haitao.feng@intel.com306080c2012-05-04 13:11:29 +00002430 options.jobs = 1
kbr@google.comab318592009-09-04 00:54:55 +00002431 client = GClient.LoadCurrentConfig(options)
2432 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002433 raise gclient_utils.Error('client not configured; see \'gclient config\'')
kbr@google.comab318592009-09-04 00:54:55 +00002434 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002435 client.PrintLocationAndContents()
kbr@google.comab318592009-09-04 00:54:55 +00002436 return client.RunOnDeps('pack', args)
2437
2438
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002439def CMDstatus(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002440 """Shows modification status for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002441 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2442 help='override deps for the specified (comma-separated) '
2443 'platform(s); \'all\' will process all deps_os '
2444 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002445 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002446 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002447 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002448 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002449 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002450 client.PrintLocationAndContents()
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002451 return client.RunOnDeps('status', args)
2452
2453
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002454@subcommand.epilog("""Examples:
maruel@chromium.org79692d62010-05-14 18:57:13 +00002455 gclient sync
2456 update files from SCM according to current configuration,
2457 *for modules which have changed since last update or sync*
2458 gclient sync --force
2459 update files from SCM according to current configuration, for
2460 all modules (useful for recovering files deleted from local copy)
2461 gclient sync --revision src@31000
2462 update src directory to r31000
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002463
2464JSON output format:
2465If the --output-json option is specified, the following document structure will
2466be emitted to the provided file. 'null' entries may occur for subprojects which
2467are present in the gclient solution, but were not processed (due to custom_deps,
2468os_deps, etc.)
2469
2470{
2471 "solutions" : {
2472 "<name>": { # <name> is the posix-normalized path to the solution.
agabled437d762016-10-17 09:35:11 -07002473 "revision": [<git id hex string>|null],
2474 "scm": ["git"|null],
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002475 }
2476 }
2477}
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002478""")
2479def CMDsync(parser, args):
2480 """Checkout/update all modules."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002481 parser.add_option('-f', '--force', action='store_true',
2482 help='force update even for unchanged modules')
2483 parser.add_option('-n', '--nohooks', action='store_true',
2484 help='don\'t run hooks after the update is complete')
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00002485 parser.add_option('-p', '--noprehooks', action='store_true',
2486 help='don\'t run pre-DEPS hooks', default=False)
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002487 parser.add_option('-r', '--revision', action='append',
2488 dest='revisions', metavar='REV', default=[],
2489 help='Enforces revision/hash for the solutions with the '
2490 'format src@rev. The src@ part is optional and can be '
2491 'skipped. -r can be used multiple times when .gclient '
2492 'has multiple solutions configured and will work even '
agablea98a6cd2016-11-15 14:30:10 -08002493 'if the src@ part is skipped.')
maruel@chromium.org794207e2013-03-08 15:29:43 +00002494 parser.add_option('--with_branch_heads', action='store_true',
2495 help='Clone git "branch_heads" refspecs in addition to '
2496 'the default refspecs. This adds about 1/2GB to a '
2497 'full checkout. (git only)')
szager@chromium.org8d3348f2014-08-19 22:49:16 +00002498 parser.add_option('--with_tags', action='store_true',
2499 help='Clone git tags in addition to the default refspecs.')
agable2697cd12016-06-28 10:23:53 -07002500 parser.add_option('-H', '--head', action='store_true',
agablea98a6cd2016-11-15 14:30:10 -08002501 help='DEPRECATED: only made sense with safesync urls.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002502 parser.add_option('-D', '--delete_unversioned_trees', action='store_true',
steveblock@chromium.org98e69452012-02-16 16:36:43 +00002503 help='Deletes from the working copy any dependencies that '
2504 'have been removed since the last sync, as long as '
2505 'there are no local modifications. When used with '
2506 '--force, such dependencies are removed even if they '
2507 'have local modifications. When used with --reset, '
2508 'all untracked directories are removed from the '
tnagel@chromium.orga2aaa632014-02-28 21:47:27 +00002509 'working copy, excluding those which are explicitly '
steveblock@chromium.org98e69452012-02-16 16:36:43 +00002510 'ignored in the repository.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002511 parser.add_option('-R', '--reset', action='store_true',
2512 help='resets any local changes before updating (git only)')
bauerb@chromium.org2aad1b22011-07-22 12:00:41 +00002513 parser.add_option('-M', '--merge', action='store_true',
2514 help='merge upstream changes instead of trying to '
2515 'fast-forward or rebase')
dnj@chromium.org5b23e872015-02-20 21:25:57 +00002516 parser.add_option('-A', '--auto_rebase', action='store_true',
2517 help='Automatically rebase repositories against local '
2518 'checkout during update (git only).')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002519 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2520 help='override deps for the specified (comma-separated) '
2521 'platform(s); \'all\' will process all deps_os '
2522 'references')
Paweł Hajdan, Jr357415c2017-07-24 14:35:28 +02002523 # TODO(phajdan.jr): use argparse.SUPPRESS to hide internal flags.
2524 parser.add_option('--do-not-merge-os-specific-entries', action='store_true',
2525 help='INTERNAL ONLY - disables merging of deps_os and '
2526 'hooks_os to dependencies and hooks')
Paweł Hajdan, Jr5ec77132017-08-16 19:21:06 +02002527 parser.add_option('--process-all-deps', action='store_true',
2528 help='Check out all deps, even for different OS-es, '
2529 'or with conditions evaluating to false')
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +00002530 parser.add_option('--upstream', action='store_true',
2531 help='Make repo state match upstream branch.')
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002532 parser.add_option('--output-json',
2533 help='Output a json document to this path containing '
2534 'summary information about the sync.')
primiano@chromium.org5439ea52014-08-06 17:18:18 +00002535 parser.add_option('--no-history', action='store_true',
2536 help='GIT ONLY - Reduces the size/time of the checkout at '
2537 'the cost of no history. Requires Git 1.9+')
hinoka@chromium.org46b87412014-05-15 00:42:05 +00002538 parser.add_option('--shallow', action='store_true',
2539 help='GIT ONLY - Do a shallow clone into the cache dir. '
2540 'Requires Git 1.9+')
e.hakkinen@samsung.come8bc1aa2015-04-08 08:00:37 +00002541 parser.add_option('--no_bootstrap', '--no-bootstrap',
2542 action='store_true',
2543 help='Don\'t bootstrap from Google Storage.')
Vadim Shtayura08049e22017-10-11 00:14:52 +00002544 parser.add_option('--ignore_locks', action='store_true',
2545 help='GIT ONLY - Ignore cache locks.')
iannucci@chromium.org30a07982016-04-07 21:35:19 +00002546 parser.add_option('--break_repo_locks', action='store_true',
2547 help='GIT ONLY - Forcibly remove repo locks (e.g. '
2548 'index.lock). This should only be used if you know for '
2549 'certain that this invocation of gclient is the only '
2550 'thing operating on the git repos (e.g. on a bot).')
Vadim Shtayura08049e22017-10-11 00:14:52 +00002551 parser.add_option('--lock_timeout', type='int', default=5000,
2552 help='GIT ONLY - Deadline (in seconds) to wait for git '
2553 'cache lock to become available. Default is %default.')
agabled437d762016-10-17 09:35:11 -07002554 # TODO(agable): Remove these when the oldest CrOS release milestone is M56.
2555 parser.add_option('-t', '--transitive', action='store_true',
2556 help='DEPRECATED: This is a no-op.')
sdefresne69b1be12016-10-18 05:48:02 -07002557 parser.add_option('-m', '--manually_grab_svn_rev', action='store_true',
agabled437d762016-10-17 09:35:11 -07002558 help='DEPRECATED: This is a no-op.')
Paweł Hajdan, Jr7c7b5592017-05-23 15:06:05 +02002559 # TODO(phajdan.jr): Remove validation options once default (crbug/570091).
Paweł Hajdan, Jr694773d2017-05-29 16:06:23 +02002560 parser.add_option('--validate-syntax', action='store_true', default=True,
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +02002561 help='Validate the .gclient and DEPS syntax')
Paweł Hajdan, Jr7c7b5592017-05-23 15:06:05 +02002562 parser.add_option('--disable-syntax-validation', action='store_false',
2563 dest='validate_syntax',
2564 help='Disable validation of .gclient and DEPS syntax.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002565 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002566 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002567
2568 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002569 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002570
smutae7ea312016-07-18 11:59:41 -07002571 if options.revisions and options.head:
2572 # TODO(maruel): Make it a parser.error if it doesn't break any builder.
2573 print('Warning: you cannot use both --head and --revision')
2574
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002575 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002576 client.PrintLocationAndContents()
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002577 ret = client.RunOnDeps('update', args)
2578 if options.output_json:
2579 slns = {}
2580 for d in client.subtree(True):
2581 normed = d.name.replace('\\', '/').rstrip('/') + '/'
2582 slns[normed] = {
2583 'revision': d.got_revision,
2584 'scm': d.used_scm.name if d.used_scm else None,
hinoka@chromium.org17db9052014-05-10 01:11:29 +00002585 'url': str(d.url) if d.url else None,
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002586 }
2587 with open(options.output_json, 'wb') as f:
2588 json.dump({'solutions': slns}, f)
2589 return ret
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002590
2591
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002592CMDupdate = CMDsync
2593
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002594
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +02002595def CMDvalidate(parser, args):
2596 """Validates the .gclient and DEPS syntax."""
2597 options, args = parser.parse_args(args)
2598 options.validate_syntax = True
2599 client = GClient.LoadCurrentConfig(options)
2600 rv = client.RunOnDeps('validate', args)
2601 if rv == 0:
2602 print('validate: SUCCESS')
2603 else:
2604 print('validate: FAILURE')
2605 return rv
2606
2607
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002608def CMDdiff(parser, args):
2609 """Displays local diff for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002610 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2611 help='override deps for the specified (comma-separated) '
2612 'platform(s); \'all\' will process all deps_os '
2613 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002614 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002615 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002616 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002617 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002618 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002619 client.PrintLocationAndContents()
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002620 return client.RunOnDeps('diff', args)
2621
2622
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002623def CMDrevert(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002624 """Reverts all modifications in every dependencies.
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00002625
2626 That's the nuclear option to get back to a 'clean' state. It removes anything
agabled437d762016-10-17 09:35:11 -07002627 that shows up in git status."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002628 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2629 help='override deps for the specified (comma-separated) '
2630 'platform(s); \'all\' will process all deps_os '
2631 'references')
2632 parser.add_option('-n', '--nohooks', action='store_true',
2633 help='don\'t run hooks after the revert is complete')
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00002634 parser.add_option('-p', '--noprehooks', action='store_true',
2635 help='don\'t run pre-DEPS hooks', default=False)
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +00002636 parser.add_option('--upstream', action='store_true',
2637 help='Make repo state match upstream branch.')
iannucci@chromium.orgbf525dc2016-04-07 22:00:28 +00002638 parser.add_option('--break_repo_locks', action='store_true',
2639 help='GIT ONLY - Forcibly remove repo locks (e.g. '
2640 'index.lock). This should only be used if you know for '
2641 'certain that this invocation of gclient is the only '
2642 'thing operating on the git repos (e.g. on a bot).')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002643 (options, args) = parser.parse_args(args)
2644 # --force is implied.
2645 options.force = True
steveblock@chromium.org98e69452012-02-16 16:36:43 +00002646 options.reset = False
2647 options.delete_unversioned_trees = False
agablec903d732016-07-26 09:07:24 -07002648 options.merge = False
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002649 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002650 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002651 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002652 return client.RunOnDeps('revert', args)
2653
2654
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002655def CMDrunhooks(parser, args):
2656 """Runs hooks for files that have been modified in the local working copy."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002657 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2658 help='override deps for the specified (comma-separated) '
2659 'platform(s); \'all\' will process all deps_os '
2660 'references')
2661 parser.add_option('-f', '--force', action='store_true', default=True,
2662 help='Deprecated. No effect.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002663 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002664 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002665 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002666 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002667 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002668 client.PrintLocationAndContents()
maruel@chromium.org5df6a462009-08-28 18:52:26 +00002669 options.force = True
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002670 options.nohooks = False
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002671 return client.RunOnDeps('runhooks', args)
2672
2673
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002674def CMDrevinfo(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002675 """Outputs revision info mapping for the client and its dependencies.
maruel@chromium.org9eda4112010-06-11 18:56:10 +00002676
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002677 This allows the capture of an overall 'revision' for the source tree that
maruel@chromium.org9eda4112010-06-11 18:56:10 +00002678 can be used to reproduce the same tree in the future. It is only useful for
agabled437d762016-10-17 09:35:11 -07002679 'unpinned dependencies', i.e. DEPS/deps references without a git hash.
2680 A git branch name isn't 'pinned' since the actual commit can change.
maruel@chromium.org9eda4112010-06-11 18:56:10 +00002681 """
2682 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2683 help='override deps for the specified (comma-separated) '
2684 'platform(s); \'all\' will process all deps_os '
2685 'references')
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00002686 parser.add_option('-a', '--actual', action='store_true',
2687 help='gets the actual checked out revisions instead of the '
2688 'ones specified in the DEPS and .gclient files')
maruel@chromium.org9eda4112010-06-11 18:56:10 +00002689 parser.add_option('-s', '--snapshot', action='store_true',
2690 help='creates a snapshot .gclient file of the current '
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00002691 'version of all repositories to reproduce the tree, '
2692 'implies -a')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002693 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002694 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002695 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002696 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002697 client.PrintRevInfo()
maruel@chromium.org79692d62010-05-14 18:57:13 +00002698 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002699
2700
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00002701def CMDverify(parser, args):
2702 """Verifies the DEPS file deps are only from allowed_hosts."""
2703 (options, args) = parser.parse_args(args)
2704 client = GClient.LoadCurrentConfig(options)
2705 if not client:
2706 raise gclient_utils.Error('client not configured; see \'gclient config\'')
2707 client.RunOnDeps(None, [])
2708 # Look at each first-level dependency of this gclient only.
2709 for dep in client.dependencies:
2710 bad_deps = dep.findDepsFromNotAllowedHosts()
2711 if not bad_deps:
2712 continue
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002713 print("There are deps from not allowed hosts in file %s" % dep.deps_file)
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00002714 for bad_dep in bad_deps:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002715 print("\t%s at %s" % (bad_dep.name, bad_dep.url))
2716 print("allowed_hosts:", ', '.join(dep.allowed_hosts))
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00002717 sys.stdout.flush()
2718 raise gclient_utils.Error(
2719 'dependencies from disallowed hosts; check your DEPS file.')
2720 return 0
2721
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002722class OptionParser(optparse.OptionParser):
szager@chromium.orge2e03202012-07-31 18:05:16 +00002723 gclientfile_default = os.environ.get('GCLIENT_FILE', '.gclient')
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002724
2725 def __init__(self, **kwargs):
2726 optparse.OptionParser.__init__(
2727 self, version='%prog ' + __version__, **kwargs)
2728
2729 # Some arm boards have issues with parallel sync.
2730 if platform.machine().startswith('arm'):
2731 jobs = 1
2732 else:
2733 jobs = max(8, gclient_utils.NumLocalCpus())
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002734
2735 self.add_option(
2736 '-j', '--jobs', default=jobs, type='int',
2737 help='Specify how many SCM commands can run in parallel; defaults to '
tnagel@chromium.orga2aaa632014-02-28 21:47:27 +00002738 '%default on this machine')
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002739 self.add_option(
2740 '-v', '--verbose', action='count', default=0,
2741 help='Produces additional output for diagnostics. Can be used up to '
2742 'three times for more logging info.')
2743 self.add_option(
2744 '--gclientfile', dest='config_filename',
2745 help='Specify an alternate %s file' % self.gclientfile_default)
2746 self.add_option(
2747 '--spec',
2748 help='create a gclient file containing the provided string. Due to '
2749 'Cygwin/Python brokenness, it can\'t contain any newlines.')
2750 self.add_option(
Aleksandr Derbenev9e8fb0e2017-08-01 20:18:31 +03002751 '--cache-dir',
2752 help='(git only) Cache all git repos into this dir and do '
2753 'shared clones from the cache, instead of cloning '
2754 'directly from the remote. (experimental)',
2755 default=os.environ.get('GCLIENT_CACHE_DIR'))
2756 self.add_option(
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002757 '--no-nag-max', default=False, action='store_true',
scottmg@chromium.orgf547c802013-09-27 17:55:26 +00002758 help='Ignored for backwards compatibility.')
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002759
2760 def parse_args(self, args=None, values=None):
2761 """Integrates standard options processing."""
2762 options, args = optparse.OptionParser.parse_args(self, args, values)
2763 levels = [logging.ERROR, logging.WARNING, logging.INFO, logging.DEBUG]
2764 logging.basicConfig(
2765 level=levels[min(options.verbose, len(levels) - 1)],
maruel@chromium.org0895b752011-08-26 20:40:33 +00002766 format='%(module)s(%(lineno)d) %(funcName)s:%(message)s')
szager@chromium.orge2e03202012-07-31 18:05:16 +00002767 if options.config_filename and options.spec:
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002768 self.error('Cannot specifiy both --gclientfile and --spec')
rdsmith@chromium.orgd9591f02014-02-05 19:28:20 +00002769 if (options.config_filename and
2770 options.config_filename != os.path.basename(options.config_filename)):
2771 self.error('--gclientfile target must be a filename, not a path')
szager@chromium.orge2e03202012-07-31 18:05:16 +00002772 if not options.config_filename:
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002773 options.config_filename = self.gclientfile_default
maruel@chromium.org0895b752011-08-26 20:40:33 +00002774 options.entries_filename = options.config_filename + '_entries'
2775 if options.jobs < 1:
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002776 self.error('--jobs must be 1 or higher')
maruel@chromium.org0895b752011-08-26 20:40:33 +00002777
2778 # These hacks need to die.
2779 if not hasattr(options, 'revisions'):
2780 # GClient.RunOnDeps expects it even if not applicable.
2781 options.revisions = []
smutae7ea312016-07-18 11:59:41 -07002782 if not hasattr(options, 'head'):
2783 options.head = None
maruel@chromium.org0895b752011-08-26 20:40:33 +00002784 if not hasattr(options, 'nohooks'):
2785 options.nohooks = True
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00002786 if not hasattr(options, 'noprehooks'):
2787 options.noprehooks = True
maruel@chromium.org0895b752011-08-26 20:40:33 +00002788 if not hasattr(options, 'deps_os'):
2789 options.deps_os = None
maruel@chromium.org0895b752011-08-26 20:40:33 +00002790 if not hasattr(options, 'force'):
2791 options.force = None
2792 return (options, args)
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002793
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002794
2795def disable_buffering():
2796 # Make stdout auto-flush so buildbot doesn't kill us during lengthy
2797 # operations. Python as a strong tendency to buffer sys.stdout.
2798 sys.stdout = gclient_utils.MakeFileAutoFlush(sys.stdout)
2799 # Make stdout annotated with the thread ids.
2800 sys.stdout = gclient_utils.MakeFileAnnotated(sys.stdout)
maruel@chromium.org0895b752011-08-26 20:40:33 +00002801
2802
sbc@chromium.org013731e2015-02-26 18:28:43 +00002803def main(argv):
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002804 """Doesn't parse the arguments here, just find the right subcommand to
2805 execute."""
maruel@chromium.org82798cb2012-02-23 18:16:12 +00002806 if sys.hexversion < 0x02060000:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002807 print(
maruel@chromium.org82798cb2012-02-23 18:16:12 +00002808 '\nYour python version %s is unsupported, please upgrade.\n' %
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002809 sys.version.split(' ', 1)[0],
2810 file=sys.stderr)
maruel@chromium.org82798cb2012-02-23 18:16:12 +00002811 return 2
bcwhite@chromium.org6683ab42013-02-11 16:13:47 +00002812 if not sys.executable:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002813 print(
2814 '\nPython cannot find the location of it\'s own executable.\n',
2815 file=sys.stderr)
bcwhite@chromium.org6683ab42013-02-11 16:13:47 +00002816 return 2
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002817 fix_encoding.fix_encoding()
2818 disable_buffering()
iannucci@chromium.org596cd5c2016-04-04 21:34:39 +00002819 setup_color.init()
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002820 dispatcher = subcommand.CommandDispatcher(__name__)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00002821 try:
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002822 return dispatcher.execute(OptionParser(), argv)
xusydoc@chromium.org2fd6c3f2013-05-03 21:57:55 +00002823 except KeyboardInterrupt:
2824 gclient_utils.GClientChildren.KillAllRemainingChildren()
2825 raise
vapier@chromium.orga81a56e2015-11-11 07:56:13 +00002826 except (gclient_utils.Error, subprocess2.CalledProcessError) as e:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002827 print('Error: %s' % str(e), file=sys.stderr)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00002828 return 1
borenet@google.com6a9b1682014-03-24 18:35:23 +00002829 finally:
2830 gclient_utils.PrintWarnings()
sbc@chromium.org013731e2015-02-26 18:28:43 +00002831 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002832
2833
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00002834if '__main__' == __name__:
sbc@chromium.org013731e2015-02-26 18:28:43 +00002835 try:
2836 sys.exit(main(sys.argv[1:]))
2837 except KeyboardInterrupt:
2838 sys.stderr.write('interrupted\n')
2839 sys.exit(1)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002840
2841# vim: ts=2:sw=2:tw=80:et: