blob: ec6faa99bcb51d9500bd369485301f56728b95ad [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
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000095import urllib
bradnelson@google.com4949dab2012-04-19 16:41:07 +000096import urlparse
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000097
maruel@chromium.org35625c72011-03-23 17:34:02 +000098import fix_encoding
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +020099import gclient_eval
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000100import gclient_scm
101import gclient_utils
szager@chromium.org848fd492014-04-09 19:06:44 +0000102import git_cache
nasser@codeaurora.org1f7a3d12010-02-04 15:11:50 +0000103from third_party.repo.progress import Progress
maruel@chromium.org39c0b222013-08-17 16:57:01 +0000104import subcommand
maruel@chromium.org31cb48a2011-04-04 18:01:36 +0000105import subprocess2
iannucci@chromium.org596cd5c2016-04-04 21:34:39 +0000106import setup_color
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000107
108
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +0200109class GNException(Exception):
110 pass
111
112
113def ToGNString(value, allow_dicts = True):
114 """Returns a stringified GN equivalent of the Python value.
115
116 allow_dicts indicates if this function will allow converting dictionaries
117 to GN scopes. This is only possible at the top level, you can't nest a
118 GN scope in a list, so this should be set to False for recursive calls."""
119 if isinstance(value, basestring):
120 if value.find('\n') >= 0:
121 raise GNException("Trying to print a string with a newline in it.")
122 return '"' + \
123 value.replace('\\', '\\\\').replace('"', '\\"').replace('$', '\\$') + \
124 '"'
125
126 if isinstance(value, unicode):
127 return ToGNString(value.encode('utf-8'))
128
129 if isinstance(value, bool):
130 if value:
131 return "true"
132 return "false"
133
134 # NOTE: some type handling removed compared to chromium/src copy.
135
136 raise GNException("Unsupported type when printing to GN.")
137
138
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200139class Hook(object):
140 """Descriptor of command ran before/after sync or on demand."""
141
Paweł Hajdan, Jr032d5452017-06-22 20:43:53 +0200142 def __init__(self, action, pattern=None, name=None, cwd=None, condition=None,
Daniel Chenga0c5f082017-10-19 13:35:19 -0700143 variables=None, verbose=False):
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200144 """Constructor.
145
146 Arguments:
147 action (list of basestring): argv of the command to run
148 pattern (basestring regex): noop with git; deprecated
149 name (basestring): optional name; no effect on operation
Paweł Hajdan, Jrc9364392017-06-14 17:11:56 +0200150 cwd (basestring): working directory to use
Paweł Hajdan, Jr032d5452017-06-22 20:43:53 +0200151 condition (basestring): condition when to run the hook
152 variables (dict): variables for evaluating the condition
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200153 """
154 self._action = gclient_utils.freeze(action)
155 self._pattern = pattern
156 self._name = name
Paweł Hajdan, Jrc9364392017-06-14 17:11:56 +0200157 self._cwd = cwd
Paweł Hajdan, Jr032d5452017-06-22 20:43:53 +0200158 self._condition = condition
159 self._variables = variables
Daniel Chenga0c5f082017-10-19 13:35:19 -0700160 self._verbose = verbose
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200161
162 @staticmethod
Daniel Chenga0c5f082017-10-19 13:35:19 -0700163 def from_dict(d, variables=None, verbose=False):
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200164 """Creates a Hook instance from a dict like in the DEPS file."""
Paweł Hajdan, Jr032d5452017-06-22 20:43:53 +0200165 return Hook(
166 d['action'],
167 d.get('pattern'),
168 d.get('name'),
169 d.get('cwd'),
170 d.get('condition'),
Daniel Chenga0c5f082017-10-19 13:35:19 -0700171 variables=variables,
172 # Always print the header if not printing to a TTY.
173 verbose=verbose or not setup_color.IS_TTY)
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200174
175 @property
176 def action(self):
177 return self._action
178
179 @property
180 def pattern(self):
181 return self._pattern
182
183 @property
184 def name(self):
185 return self._name
186
Paweł Hajdan, Jrecf53fe2017-09-29 18:28:49 +0200187 @property
188 def condition(self):
189 return self._condition
190
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200191 def matches(self, file_list):
192 """Returns true if the pattern matches any of files in the list."""
193 if not self._pattern:
194 return True
195 pattern = re.compile(self._pattern)
196 return bool([f for f in file_list if pattern.search(f)])
197
198 def run(self, root):
Paweł Hajdan, Jr032d5452017-06-22 20:43:53 +0200199 """Executes the hook's command (provided the condition is met)."""
200 if (self._condition and
201 not gclient_eval.EvaluateCondition(self._condition, self._variables)):
202 return
203
Paweł Hajdan, Jrfc6196b2017-07-27 13:15:25 +0200204 cmd = [arg.format(**self._variables) for arg in self._action]
205
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200206 if cmd[0] == 'python':
207 # If the hook specified "python" as the first item, the action is a
208 # Python script. Run it by starting a new copy of the same
209 # interpreter.
210 cmd[0] = sys.executable
Paweł Hajdan, Jrc9364392017-06-14 17:11:56 +0200211
212 cwd = root
213 if self._cwd:
214 cwd = os.path.join(cwd, self._cwd)
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200215 try:
216 start_time = time.time()
217 gclient_utils.CheckCallAndFilterAndHeader(
Daniel Chenga0c5f082017-10-19 13:35:19 -0700218 cmd, cwd=cwd, always=self._verbose)
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200219 except (gclient_utils.Error, subprocess2.CalledProcessError) as e:
220 # Use a discrete exit status code of 2 to indicate that a hook action
221 # failed. Users of this script may wish to treat hook action failures
222 # differently from VC failures.
223 print('Error: %s' % str(e), file=sys.stderr)
224 sys.exit(2)
225 finally:
226 elapsed_time = time.time() - start_time
227 if elapsed_time > 10:
228 print("Hook '%s' took %.2f secs" % (
229 gclient_utils.CommandToStr(cmd), elapsed_time))
230
231
Paweł Hajdan, Jrfc6196b2017-07-27 13:15:25 +0200232class DependencySettings(object):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000233 """Immutable configuration settings."""
234 def __init__(
Paweł Hajdan, Jrfc6196b2017-07-27 13:15:25 +0200235 self, parent, raw_url, url, managed, custom_deps, custom_vars,
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +0200236 custom_hooks, deps_file, should_process, relative,
237 condition, condition_value):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000238 # These are not mutable:
239 self._parent = parent
mmoss@chromium.org8f93f792014-08-26 23:24:09 +0000240 self._deps_file = deps_file
Paweł Hajdan, Jrfc6196b2017-07-27 13:15:25 +0200241 self._raw_url = raw_url
maruel@chromium.org064186c2011-09-27 23:53:33 +0000242 self._url = url
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +0200243 # The condition as string (or None). Useful to keep e.g. for flatten.
244 self._condition = condition
245 # Boolean value of the condition. If there's no condition, just True.
246 self._condition_value = condition_value
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000247 # 'managed' determines whether or not this dependency is synced/updated by
248 # gclient after gclient checks it out initially. The difference between
249 # 'managed' and 'should_process' is that the user specifies 'managed' via
smutae7ea312016-07-18 11:59:41 -0700250 # the --unmanaged command-line flag or a .gclient config, where
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000251 # 'should_process' is dynamically set by gclient if it goes over its
252 # recursion limit and controls gclient's behavior so it does not misbehave.
253 self._managed = managed
254 self._should_process = should_process
agabledce6ddc2016-09-08 10:02:16 -0700255 # If this is a recursed-upon sub-dependency, and the parent has
256 # use_relative_paths set, then this dependency should check out its own
257 # dependencies relative to that parent's path for this, rather than
258 # relative to the .gclient file.
259 self._relative = relative
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +0000260 # This is a mutable value which has the list of 'target_os' OSes listed in
261 # the current deps file.
262 self.local_target_os = None
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000263
264 # These are only set in .gclient and not in DEPS files.
265 self._custom_vars = custom_vars or {}
266 self._custom_deps = custom_deps or {}
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000267 self._custom_hooks = custom_hooks or []
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000268
maruel@chromium.org064186c2011-09-27 23:53:33 +0000269 # Post process the url to remove trailing slashes.
270 if isinstance(self._url, basestring):
271 # urls are sometime incorrectly written as proto://host/path/@rev. Replace
272 # it to proto://host/path@rev.
maruel@chromium.org064186c2011-09-27 23:53:33 +0000273 self._url = self._url.replace('/@', '@')
Paweł Hajdan, Jr7e9303b2017-05-23 14:38:27 +0200274 elif not isinstance(self._url, (None.__class__)):
maruel@chromium.org064186c2011-09-27 23:53:33 +0000275 raise gclient_utils.Error(
Paweł Hajdan, Jr7e9303b2017-05-23 14:38:27 +0200276 ('dependency url must be either string or None, '
277 'instead of %s') % self._url.__class__.__name__)
mmoss@chromium.orgd0b272b2013-01-30 23:55:33 +0000278 # Make any deps_file path platform-appropriate.
279 for sep in ['/', '\\']:
280 self._deps_file = self._deps_file.replace(sep, os.sep)
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000281
282 @property
283 def deps_file(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000284 return self._deps_file
285
286 @property
287 def managed(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000288 return self._managed
289
290 @property
291 def parent(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000292 return self._parent
293
294 @property
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000295 def root(self):
296 """Returns the root node, a GClient object."""
297 if not self.parent:
298 # This line is to signal pylint that it could be a GClient instance.
299 return self or GClient(None, None)
300 return self.parent.root
301
302 @property
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000303 def should_process(self):
304 """True if this dependency should be processed, i.e. checked out."""
305 return self._should_process
306
307 @property
308 def custom_vars(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000309 return self._custom_vars.copy()
310
311 @property
312 def custom_deps(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000313 return self._custom_deps.copy()
314
maruel@chromium.org064186c2011-09-27 23:53:33 +0000315 @property
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000316 def custom_hooks(self):
317 return self._custom_hooks[:]
318
319 @property
Paweł Hajdan, Jrfc6196b2017-07-27 13:15:25 +0200320 def raw_url(self):
321 """URL before variable expansion."""
322 return self._raw_url
323
324 @property
maruel@chromium.org064186c2011-09-27 23:53:33 +0000325 def url(self):
Paweł Hajdan, Jrfc6196b2017-07-27 13:15:25 +0200326 """URL after variable expansion."""
maruel@chromium.org064186c2011-09-27 23:53:33 +0000327 return self._url
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000328
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000329 @property
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +0200330 def condition(self):
331 return self._condition
332
333 @property
334 def condition_value(self):
335 return self._condition_value
336
337 @property
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +0000338 def target_os(self):
339 if self.local_target_os is not None:
340 return tuple(set(self.local_target_os).union(self.parent.target_os))
341 else:
342 return self.parent.target_os
343
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000344 def get_custom_deps(self, name, url):
345 """Returns a custom deps if applicable."""
346 if self.parent:
347 url = self.parent.get_custom_deps(name, url)
348 # None is a valid return value to disable a dependency.
349 return self.custom_deps.get(name, url)
350
maruel@chromium.org064186c2011-09-27 23:53:33 +0000351
352class Dependency(gclient_utils.WorkItem, DependencySettings):
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000353 """Object that represents a dependency checkout."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000354
Paweł Hajdan, Jrfc6196b2017-07-27 13:15:25 +0200355 def __init__(self, parent, name, raw_url, url, managed, custom_deps,
agabledce6ddc2016-09-08 10:02:16 -0700356 custom_vars, custom_hooks, deps_file, should_process,
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +0200357 relative, condition, condition_value):
maruel@chromium.org6ca8bf82011-09-19 23:04:30 +0000358 gclient_utils.WorkItem.__init__(self, name)
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000359 DependencySettings.__init__(
Paweł Hajdan, Jrfc6196b2017-07-27 13:15:25 +0200360 self, parent, raw_url, url, managed, custom_deps, custom_vars,
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +0200361 custom_hooks, deps_file, should_process, relative,
362 condition, condition_value)
maruel@chromium.org68988972011-09-20 14:11:42 +0000363
364 # This is in both .gclient and DEPS files:
maruel@chromium.org064186c2011-09-27 23:53:33 +0000365 self._deps_hooks = []
maruel@chromium.org68988972011-09-20 14:11:42 +0000366
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000367 self._pre_deps_hooks = []
368
maruel@chromium.org68988972011-09-20 14:11:42 +0000369 # Calculates properties:
maruel@chromium.org064186c2011-09-27 23:53:33 +0000370 self._parsed_url = None
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +0000371 self._dependencies = []
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +0200372 self._vars = {}
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +0200373 self._os_dependencies = {}
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +0200374 self._os_deps_hooks = {}
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200375
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000376 # A cache of the files affected by the current operation, necessary for
377 # hooks.
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000378 self._file_list = []
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000379 # List of host names from which dependencies are allowed.
380 # Default is an empty set, meaning unspecified in DEPS file, and hence all
381 # hosts will be allowed. Non-empty set means whitelist of hosts.
382 # allowed_hosts var is scoped to its DEPS file, and so it isn't recursive.
383 self._allowed_hosts = frozenset()
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +0200384 # Spec for .gni output to write (if any).
385 self._gn_args_file = None
386 self._gn_args = []
maruel@chromium.org85c2a192010-07-22 21:14:43 +0000387 # If it is not set to True, the dependency wasn't processed for its child
388 # dependency, i.e. its DEPS wasn't read.
maruel@chromium.org064186c2011-09-27 23:53:33 +0000389 self._deps_parsed = False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000390 # This dependency has been processed, i.e. checked out
maruel@chromium.org064186c2011-09-27 23:53:33 +0000391 self._processed = False
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000392 # This dependency had its pre-DEPS hooks run
393 self._pre_deps_hooks_ran = False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000394 # This dependency had its hook run
maruel@chromium.org064186c2011-09-27 23:53:33 +0000395 self._hooks_ran = False
kustermann@google.coma692e8f2013-04-18 08:32:04 +0000396 # This is the scm used to checkout self.url. It may be used by dependencies
397 # to get the datetime of the revision we checked out.
398 self._used_scm = None
szager@chromium.org4ad264b2014-05-20 04:43:47 +0000399 self._used_revision = None
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +0000400 # The actual revision we ended up getting, or None if that information is
401 # unavailable
402 self._got_revision = None
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000403
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000404 # This is a mutable value that overrides the normal recursion limit for this
405 # dependency. It is read from the actual DEPS file so cannot be set on
406 # class instantiation.
407 self.recursion_override = None
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000408 # recursedeps is a mutable value that selectively overrides the default
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000409 # 'no recursion' setting on a dep-by-dep basis. It will replace
410 # recursion_override.
iannucci@chromium.orgafa11ac2016-05-04 22:17:34 +0000411 #
412 # It will be a dictionary of {deps_name: {"deps_file": depfile_name}} or
413 # None.
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000414 self.recursedeps = None
hinoka885e5b12016-06-08 14:40:09 -0700415 # This is inherited from WorkItem. We want the URL to be a resource.
416 if url and isinstance(url, basestring):
417 # The url is usually given to gclient either as https://blah@123
qyearsley12fa6ff2016-08-24 09:18:40 -0700418 # or just https://blah. The @123 portion is irrelevant.
hinoka885e5b12016-06-08 14:40:09 -0700419 self.resources.append(url.split('@')[0])
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000420
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000421 if not self.name and self.parent:
422 raise gclient_utils.Error('Dependency without name')
423
maruel@chromium.org470b5432011-10-11 18:18:19 +0000424 @property
425 def requirements(self):
426 """Calculate the list of requirements."""
427 requirements = set()
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000428 # self.parent is implicitly a requirement. This will be recursive by
429 # definition.
430 if self.parent and self.parent.name:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000431 requirements.add(self.parent.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000432
433 # For a tree with at least 2 levels*, the leaf node needs to depend
434 # on the level higher up in an orderly way.
435 # This becomes messy for >2 depth as the DEPS file format is a dictionary,
436 # thus unsorted, while the .gclient format is a list thus sorted.
437 #
438 # * _recursion_limit is hard coded 2 and there is no hope to change this
439 # value.
440 #
441 # Interestingly enough, the following condition only works in the case we
442 # want: self is a 2nd level node. 3nd level node wouldn't need this since
443 # they already have their parent as a requirement.
maruel@chromium.org470b5432011-10-11 18:18:19 +0000444 if self.parent and self.parent.parent and not self.parent.parent.parent:
445 requirements |= set(i.name for i in self.root.dependencies if i.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000446
maruel@chromium.org470b5432011-10-11 18:18:19 +0000447 if self.name:
448 requirements |= set(
449 obj.name for obj in self.root.subtree(False)
450 if (obj is not self
451 and obj.name and
452 self.name.startswith(posixpath.join(obj.name, ''))))
453 requirements = tuple(sorted(requirements))
454 logging.info('Dependency(%s).requirements = %s' % (self.name, requirements))
455 return requirements
456
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000457 @property
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000458 def try_recursedeps(self):
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000459 """Returns False if recursion_override is ever specified."""
460 if self.recursion_override is not None:
461 return False
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000462 return self.parent.try_recursedeps
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000463
464 @property
465 def recursion_limit(self):
466 """Returns > 0 if this dependency is not too recursed to be processed."""
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000467 # We continue to support the absence of recursedeps until tools and DEPS
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000468 # using recursion_override are updated.
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000469 if self.try_recursedeps and self.parent.recursedeps != None:
470 if self.name in self.parent.recursedeps:
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000471 return 1
472
473 if self.recursion_override is not None:
474 return self.recursion_override
475 return max(self.parent.recursion_limit - 1, 0)
476
maruel@chromium.org470b5432011-10-11 18:18:19 +0000477 def verify_validity(self):
478 """Verifies that this Dependency is fine to add as a child of another one.
479
480 Returns True if this entry should be added, False if it is a duplicate of
481 another entry.
482 """
483 logging.info('Dependency(%s).verify_validity()' % self.name)
484 if self.name in [s.name for s in self.parent.dependencies]:
485 raise gclient_utils.Error(
486 'The same name "%s" appears multiple times in the deps section' %
487 self.name)
488 if not self.should_process:
489 # Return early, no need to set requirements.
490 return True
491
492 # This require a full tree traversal with locks.
493 siblings = [d for d in self.root.subtree(False) if d.name == self.name]
494 for sibling in siblings:
maruel@chromium.orgb848d5b2012-10-10 23:25:50 +0000495 self_url = self.LateOverride(self.url)
496 sibling_url = sibling.LateOverride(sibling.url)
497 # Allow to have only one to be None or ''.
498 if self_url != sibling_url and bool(self_url) == bool(sibling_url):
maruel@chromium.org470b5432011-10-11 18:18:19 +0000499 raise gclient_utils.Error(
maruel@chromium.orgb848d5b2012-10-10 23:25:50 +0000500 ('Dependency %s specified more than once:\n'
501 ' %s [%s]\n'
502 'vs\n'
503 ' %s [%s]') % (
504 self.name,
505 sibling.hierarchy(),
506 sibling_url,
507 self.hierarchy(),
508 self_url))
maruel@chromium.org470b5432011-10-11 18:18:19 +0000509 # In theory we could keep it as a shadow of the other one. In
510 # practice, simply ignore it.
511 logging.warn('Won\'t process duplicate dependency %s' % sibling)
512 return False
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000513 return True
maruel@chromium.org064186c2011-09-27 23:53:33 +0000514
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000515 def LateOverride(self, url):
Paweł Hajdan, Jr7e9303b2017-05-23 14:38:27 +0200516 """Resolves the parsed url from url."""
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000517 assert self.parsed_url == None or not self.should_process, self.parsed_url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000518 parsed_url = self.get_custom_deps(self.name, url)
519 if parsed_url != url:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000520 logging.info(
521 'Dependency(%s).LateOverride(%s) -> %s' %
522 (self.name, url, parsed_url))
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000523 return parsed_url
524
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000525 if isinstance(url, basestring):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000526 parsed_url = urlparse.urlparse(url)
scr@chromium.orgf1eccaf2014-04-11 15:51:33 +0000527 if (not parsed_url[0] and
528 not re.match(r'^\w+\@[\w\.-]+\:[\w\/]+', parsed_url[2])):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000529 # A relative url. Fetch the real base.
530 path = parsed_url[2]
531 if not path.startswith('/'):
532 raise gclient_utils.Error(
533 'relative DEPS entry \'%s\' must begin with a slash' % url)
534 # Create a scm just to query the full url.
535 parent_url = self.parent.parsed_url
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000536 scm = gclient_scm.CreateSCM(
537 parent_url, self.root.root_dir, None, self.outbuf)
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000538 parsed_url = scm.FullUrlForRelativeUrl(url)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000539 else:
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000540 parsed_url = url
maruel@chromium.org470b5432011-10-11 18:18:19 +0000541 logging.info(
542 'Dependency(%s).LateOverride(%s) -> %s' %
543 (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000544 return parsed_url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000545
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000546 if url is None:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000547 logging.info(
548 'Dependency(%s).LateOverride(%s) -> %s' % (self.name, url, url))
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000549 return url
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000550
551 raise gclient_utils.Error('Unknown url type')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000552
bratell@opera.comed2b4fe2013-12-16 14:34:12 +0000553 @staticmethod
Paweł Hajdan, Jr5ec77132017-08-16 19:21:06 +0200554 def MergeWithOsDeps(deps, deps_os, target_os_list, process_all_deps):
bratell@opera.comed2b4fe2013-12-16 14:34:12 +0000555 """Returns a new "deps" structure that is the deps sent in updated
556 with information from deps_os (the deps_os section of the DEPS
557 file) that matches the list of target os."""
bratell@opera.comed2b4fe2013-12-16 14:34:12 +0000558 new_deps = deps.copy()
Paweł Hajdan, Jrfd0057e2017-06-21 14:20:21 +0200559 for dep_os, os_deps in deps_os.iteritems():
560 for key, value in os_deps.iteritems():
561 if value is None:
562 # Make this condition very visible, so it's not a silent failure.
563 # It's unclear how to support None override in deps_os.
564 logging.error('Ignoring %r:%r in %r deps_os', key, value, dep_os)
565 continue
566
567 # Normalize value to be a dict which contains |should_process| metadata.
568 if isinstance(value, basestring):
569 value = {'url': value}
570 assert isinstance(value, collections.Mapping), (key, value)
Paweł Hajdan, Jr5ec77132017-08-16 19:21:06 +0200571 value['should_process'] = dep_os in target_os_list or process_all_deps
Paweł Hajdan, Jrfd0057e2017-06-21 14:20:21 +0200572
573 # Handle collisions/overrides.
574 if key in new_deps and new_deps[key] != value:
575 # Normalize the existing new_deps entry.
576 if isinstance(new_deps[key], basestring):
577 new_deps[key] = {'url': new_deps[key]}
578 assert isinstance(new_deps[key],
579 collections.Mapping), (key, new_deps[key])
580
581 # It's OK if the "override" sets the key to the same value.
582 # This is mostly for legacy reasons to keep existing DEPS files
583 # working. Often mac/ios and unix/android will do this.
584 if value['url'] != new_deps[key]['url']:
585 raise gclient_utils.Error(
586 ('Value from deps_os (%r; %r: %r) conflicts with existing deps '
587 'entry (%r).') % (dep_os, key, value, new_deps[key]))
588
589 # We'd otherwise overwrite |should_process| metadata, but a dep should
590 # be processed if _any_ of its references call for that.
591 value['should_process'] = (
592 value['should_process'] or
593 new_deps[key].get('should_process', True))
594
595 new_deps[key] = value
596
bratell@opera.comed2b4fe2013-12-16 14:34:12 +0000597 return new_deps
598
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200599 def _postprocess_deps(self, deps, rel_prefix):
600 """Performs post-processing of deps compared to what's in the DEPS file."""
Paweł Hajdan, Jr4426eaf2017-06-13 12:25:47 +0200601 # Make sure the dict is mutable, e.g. in case it's frozen.
602 deps = dict(deps)
603
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200604 # If a line is in custom_deps, but not in the solution, we want to append
605 # this line to the solution.
606 for d in self.custom_deps:
607 if d not in deps:
608 deps[d] = self.custom_deps[d]
609
610 if rel_prefix:
611 logging.warning('use_relative_paths enabled.')
612 rel_deps = {}
613 for d, url in deps.items():
614 # normpath is required to allow DEPS to use .. in their
615 # dependency local path.
616 rel_deps[os.path.normpath(os.path.join(rel_prefix, d))] = url
617 logging.warning('Updating deps by prepending %s.', rel_prefix)
618 deps = rel_deps
619
620 return deps
621
622 def _deps_to_objects(self, deps, use_relative_paths):
623 """Convert a deps dict to a dict of Dependency objects."""
624 deps_to_add = []
625 for name, dep_value in deps.iteritems():
626 should_process = self.recursion_limit and self.should_process
627 deps_file = self.deps_file
628 if self.recursedeps is not None:
629 ent = self.recursedeps.get(name)
630 if ent is not None:
631 deps_file = ent['deps_file']
632 if dep_value is None:
633 continue
634 condition = None
635 condition_value = True
636 if isinstance(dep_value, basestring):
Paweł Hajdan, Jrfc6196b2017-07-27 13:15:25 +0200637 raw_url = dep_value
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200638 else:
639 # This should be guaranteed by schema checking in gclient_eval.
640 assert isinstance(dep_value, collections.Mapping)
Paweł Hajdan, Jrfc6196b2017-07-27 13:15:25 +0200641 raw_url = dep_value['url']
Paweł Hajdan, Jrfd0057e2017-06-21 14:20:21 +0200642 # Take into account should_process metadata set by MergeWithOsDeps.
643 should_process = (should_process and
644 dep_value.get('should_process', True))
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200645 condition = dep_value.get('condition')
Paweł Hajdan, Jrfc6196b2017-07-27 13:15:25 +0200646
647 url = raw_url.format(**self.get_vars())
648
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200649 if condition:
Paweł Hajdan, Jrd3790252017-07-03 21:06:24 +0200650 condition_value = gclient_eval.EvaluateCondition(
651 condition, self.get_vars())
Paweł Hajdan, Jr5ec77132017-08-16 19:21:06 +0200652 if not self._get_option('process_all_deps', False):
653 should_process = should_process and condition_value
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200654 deps_to_add.append(Dependency(
Paweł Hajdan, Jrfc6196b2017-07-27 13:15:25 +0200655 self, name, raw_url, url, None, None, self.custom_vars, None,
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200656 deps_file, should_process, use_relative_paths, condition,
657 condition_value))
658 deps_to_add.sort(key=lambda x: x.name)
659 return deps_to_add
660
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000661 def ParseDepsFile(self):
maruel@chromium.org271375b2010-06-23 19:17:38 +0000662 """Parses the DEPS file for this dependency."""
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000663 assert not self.deps_parsed
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000664 assert not self.dependencies
iannucci@chromium.org0c3f3052014-05-22 00:29:22 +0000665
666 deps_content = None
cmp@chromium.org76ce73c2014-07-02 00:13:18 +0000667
668 # First try to locate the configured deps file. If it's missing, fallback
669 # to DEPS.
670 deps_files = [self.deps_file]
671 if 'DEPS' not in deps_files:
672 deps_files.append('DEPS')
673 for deps_file in deps_files:
674 filepath = os.path.join(self.root.root_dir, self.name, deps_file)
675 if os.path.isfile(filepath):
676 logging.info(
677 'ParseDepsFile(%s): %s file found at %s', self.name, deps_file,
678 filepath)
679 break
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000680 logging.info(
cmp@chromium.org76ce73c2014-07-02 00:13:18 +0000681 'ParseDepsFile(%s): No %s file found at %s', self.name, deps_file,
682 filepath)
683
684 if os.path.isfile(filepath):
maruel@chromium.org46304292010-10-28 11:42:00 +0000685 deps_content = gclient_utils.FileRead(filepath)
cmp@chromium.org76ce73c2014-07-02 00:13:18 +0000686 logging.debug('ParseDepsFile(%s) read:\n%s', self.name, deps_content)
iannucci@chromium.org0c3f3052014-05-22 00:29:22 +0000687
688 local_scope = {}
689 if deps_content:
Paweł Hajdan, Jrf1587bf2017-06-20 21:19:07 +0200690 global_scope = {
Paweł Hajdan, Jrfc6196b2017-07-27 13:15:25 +0200691 'Var': lambda var_name: '{%s}' % var_name,
Paweł Hajdan, Jrf1587bf2017-06-20 21:19:07 +0200692 'deps_os': {},
693 }
maruel@chromium.org46304292010-10-28 11:42:00 +0000694 # Eval the content.
695 try:
Paweł Hajdan, Jrc485d5a2017-06-02 12:08:09 +0200696 if self._get_option('validate_syntax', False):
697 gclient_eval.Exec(deps_content, global_scope, local_scope, filepath)
698 else:
699 exec(deps_content, global_scope, local_scope)
vapier@chromium.orga81a56e2015-11-11 07:56:13 +0000700 except SyntaxError as e:
maruel@chromium.org46304292010-10-28 11:42:00 +0000701 gclient_utils.SyntaxErrorToError(filepath, e)
iannucci@chromium.org0c3f3052014-05-22 00:29:22 +0000702
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000703 if 'allowed_hosts' in local_scope:
704 try:
705 self._allowed_hosts = frozenset(local_scope.get('allowed_hosts'))
706 except TypeError: # raised if non-iterable
707 pass
708 if not self._allowed_hosts:
709 logging.warning("allowed_hosts is specified but empty %s",
710 self._allowed_hosts)
711 raise gclient_utils.Error(
712 'ParseDepsFile(%s): allowed_hosts must be absent '
713 'or a non-empty iterable' % self.name)
714
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +0200715 self._gn_args_file = local_scope.get('gclient_gn_args_file')
716 self._gn_args = local_scope.get('gclient_gn_args', [])
717
Paweł Hajdan, Jr1407d002017-08-01 20:01:01 +0200718 self._vars = local_scope.get('vars', {})
719 if self.parent:
720 for key, value in self.parent.get_vars().iteritems():
721 if key in self._vars:
722 self._vars[key] = value
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200723 # Since we heavily post-process things, freeze ones which should
724 # reflect original state of DEPS.
Paweł Hajdan, Jr1407d002017-08-01 20:01:01 +0200725 self._vars = gclient_utils.freeze(self._vars)
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200726
727 # If use_relative_paths is set in the DEPS file, regenerate
728 # the dictionary using paths relative to the directory containing
729 # the DEPS file. Also update recursedeps if use_relative_paths is
730 # enabled.
731 # If the deps file doesn't set use_relative_paths, but the parent did
732 # (and therefore set self.relative on this Dependency object), then we
733 # want to modify the deps and recursedeps by prepending the parent
734 # directory of this dependency.
735 use_relative_paths = local_scope.get('use_relative_paths', False)
736 rel_prefix = None
737 if use_relative_paths:
738 rel_prefix = self.name
739 elif self._relative:
740 rel_prefix = os.path.dirname(self.name)
741
Paweł Hajdan, Jrfc6196b2017-07-27 13:15:25 +0200742 deps = {}
743 for key, value in local_scope.get('deps', {}).iteritems():
744 deps[key.format(**self.get_vars())] = value
745
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200746 if 'recursion' in local_scope:
747 self.recursion_override = local_scope.get('recursion')
748 logging.warning(
749 'Setting %s recursion to %d.', self.name, self.recursion_limit)
750 self.recursedeps = None
751 if 'recursedeps' in local_scope:
752 self.recursedeps = {}
753 for ent in local_scope['recursedeps']:
754 if isinstance(ent, basestring):
755 self.recursedeps[ent] = {"deps_file": self.deps_file}
756 else: # (depname, depsfilename)
757 self.recursedeps[ent[0]] = {"deps_file": ent[1]}
758 logging.warning('Found recursedeps %r.', repr(self.recursedeps))
759
760 if rel_prefix:
761 logging.warning('Updating recursedeps by prepending %s.', rel_prefix)
762 rel_deps = {}
763 for depname, options in self.recursedeps.iteritems():
764 rel_deps[
765 os.path.normpath(os.path.join(rel_prefix, depname))] = options
766 self.recursedeps = rel_deps
767
768 # If present, save 'target_os' in the local_target_os property.
769 if 'target_os' in local_scope:
770 self.local_target_os = local_scope['target_os']
771 # load os specific dependencies if defined. these dependencies may
772 # override or extend the values defined by the 'deps' member.
773 target_os_list = self.target_os
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +0200774 if 'deps_os' in local_scope:
775 for dep_os, os_deps in local_scope['deps_os'].iteritems():
776 self._os_dependencies[dep_os] = self._deps_to_objects(
777 self._postprocess_deps(os_deps, rel_prefix), use_relative_paths)
Paweł Hajdan, Jr357415c2017-07-24 14:35:28 +0200778 if target_os_list and not self._get_option(
779 'do_not_merge_os_specific_entries', False):
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +0200780 deps = self.MergeWithOsDeps(
Paweł Hajdan, Jr5ec77132017-08-16 19:21:06 +0200781 deps, local_scope['deps_os'], target_os_list,
782 self._get_option('process_all_deps', False))
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200783
784 deps_to_add = self._deps_to_objects(
785 self._postprocess_deps(deps, rel_prefix), use_relative_paths)
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000786
787 # override named sets of hooks by the custom hooks
788 hooks_to_run = []
789 hook_names_to_suppress = [c.get('name', '') for c in self.custom_hooks]
790 for hook in local_scope.get('hooks', []):
791 if hook.get('name', '') not in hook_names_to_suppress:
792 hooks_to_run.append(hook)
Scott Grahamc4826742017-05-11 16:59:23 -0700793 if 'hooks_os' in local_scope and target_os_list:
794 hooks_os = local_scope['hooks_os']
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +0200795
796 # Keep original contents of hooks_os for flatten.
797 for hook_os, os_hooks in hooks_os.iteritems():
798 self._os_deps_hooks[hook_os] = [
Daniel Chenga0c5f082017-10-19 13:35:19 -0700799 Hook.from_dict(hook, variables=self.get_vars(), verbose=True)
Paweł Hajdan, Jrd3790252017-07-03 21:06:24 +0200800 for hook in os_hooks]
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +0200801
Scott Grahamc4826742017-05-11 16:59:23 -0700802 # Specifically append these to ensure that hooks_os run after hooks.
Paweł Hajdan, Jr357415c2017-07-24 14:35:28 +0200803 if not self._get_option('do_not_merge_os_specific_entries', False):
804 for the_target_os in target_os_list:
805 the_target_os_hooks = hooks_os.get(the_target_os, [])
806 hooks_to_run.extend(the_target_os_hooks)
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000807
808 # add the replacements and any additions
809 for hook in self.custom_hooks:
810 if 'action' in hook:
811 hooks_to_run.append(hook)
812
Dirk Prankeda3a29e2017-02-27 15:29:36 -0800813 if self.recursion_limit:
Paweł Hajdan, Jr032d5452017-06-22 20:43:53 +0200814 self._pre_deps_hooks = [
Daniel Chenga0c5f082017-10-19 13:35:19 -0700815 Hook.from_dict(hook, variables=self.get_vars(), verbose=True)
816 for hook in local_scope.get('pre_deps_hooks', [])
817 ]
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000818
Paweł Hajdan, Jr357415c2017-07-24 14:35:28 +0200819 self.add_dependencies_and_close(deps_to_add, hooks_to_run)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000820 logging.info('ParseDepsFile(%s) done' % self.name)
821
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +0200822 def _get_option(self, attr, default):
823 obj = self
824 while not hasattr(obj, '_options'):
825 obj = obj.parent
826 return getattr(obj._options, attr, default)
827
Paweł Hajdan, Jr357415c2017-07-24 14:35:28 +0200828 def add_dependencies_and_close(self, deps_to_add, hooks):
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000829 """Adds the dependencies, hooks and mark the parsing as done."""
maruel@chromium.orgb9be0652011-10-14 18:05:40 +0000830 for dep in deps_to_add:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000831 if dep.verify_validity():
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000832 self.add_dependency(dep)
Daniel Chenga0c5f082017-10-19 13:35:19 -0700833 self._mark_as_parsed([
834 Hook.from_dict(
835 h, variables=self.get_vars(), verbose=self.root._options.verbose)
836 for h in hooks
837 ])
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000838
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000839 def findDepsFromNotAllowedHosts(self):
840 """Returns a list of depenecies from not allowed hosts.
841
842 If allowed_hosts is not set, allows all hosts and returns empty list.
843 """
844 if not self._allowed_hosts:
845 return []
846 bad_deps = []
847 for dep in self._dependencies:
szager@chromium.orgbd772dd2014-11-05 18:43:08 +0000848 # Don't enforce this for custom_deps.
849 if dep.name in self._custom_deps:
850 continue
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000851 if isinstance(dep.url, basestring):
852 parsed_url = urlparse.urlparse(dep.url)
853 if parsed_url.netloc and parsed_url.netloc not in self._allowed_hosts:
854 bad_deps.append(dep)
855 return bad_deps
856
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +0000857 # Arguments number differs from overridden method
Quinten Yearsleyb2cc4a92016-12-15 13:53:26 -0800858 # pylint: disable=arguments-differ
maruel@chromium.org3742c842010-09-09 19:27:14 +0000859 def run(self, revision_overrides, command, args, work_queue, options):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000860 """Runs |command| then parse the DEPS file."""
maruel@chromium.org470b5432011-10-11 18:18:19 +0000861 logging.info('Dependency(%s).run()' % self.name)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000862 assert self._file_list == []
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000863 if not self.should_process:
864 return
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000865 # When running runhooks, there's no need to consult the SCM.
866 # All known hooks are expected to run unconditionally regardless of working
867 # copy state, so skip the SCM status check.
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +0200868 run_scm = command not in (
869 'flatten', 'runhooks', 'recurse', 'validate', None)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000870 parsed_url = self.LateOverride(self.url)
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000871 file_list = [] if not options.nohooks else None
szager@chromium.org3a3608d2014-10-22 21:13:52 +0000872 revision_override = revision_overrides.pop(self.name, None)
Dave Tubbda9712017-06-01 15:10:53 -0700873 if not revision_override and parsed_url:
874 revision_override = revision_overrides.get(parsed_url.split('@')[0], None)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000875 if run_scm and parsed_url:
agabled437d762016-10-17 09:35:11 -0700876 # Create a shallow copy to mutate revision.
877 options = copy.copy(options)
878 options.revision = revision_override
879 self._used_revision = options.revision
880 self._used_scm = gclient_scm.CreateSCM(
881 parsed_url, self.root.root_dir, self.name, self.outbuf,
882 out_cb=work_queue.out_cb)
883 self._got_revision = self._used_scm.RunCommand(command, options, args,
884 file_list)
885 if file_list:
886 file_list = [os.path.join(self.name, f.strip()) for f in file_list]
maruel@chromium.org68988972011-09-20 14:11:42 +0000887
888 # TODO(phajdan.jr): We should know exactly when the paths are absolute.
889 # Convert all absolute paths to relative.
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000890 for i in range(len(file_list or [])):
maruel@chromium.org68988972011-09-20 14:11:42 +0000891 # It depends on the command being executed (like runhooks vs sync).
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000892 if not os.path.isabs(file_list[i]):
maruel@chromium.org68988972011-09-20 14:11:42 +0000893 continue
894 prefix = os.path.commonprefix(
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000895 [self.root.root_dir.lower(), file_list[i].lower()])
896 file_list[i] = file_list[i][len(prefix):]
maruel@chromium.org68988972011-09-20 14:11:42 +0000897 # Strip any leading path separators.
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000898 while file_list[i].startswith(('\\', '/')):
899 file_list[i] = file_list[i][1:]
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000900
901 # Always parse the DEPS file.
902 self.ParseDepsFile()
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000903 self._run_is_done(file_list or [], parsed_url)
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000904 if command in ('update', 'revert') and not options.noprehooks:
905 self.RunPreDepsHooks()
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000906
907 if self.recursion_limit:
908 # Parse the dependencies of this dependency.
909 for s in self.dependencies:
Paweł Hajdan, Jr4baaa112017-07-04 19:09:32 +0200910 if s.should_process:
911 work_queue.enqueue(s)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000912
913 if command == 'recurse':
agabled437d762016-10-17 09:35:11 -0700914 # Skip file only checkout.
915 scm = gclient_scm.GetScmName(parsed_url)
916 if not options.scm or scm in options.scm:
917 cwd = os.path.normpath(os.path.join(self.root.root_dir, self.name))
918 # Pass in the SCM type as an env variable. Make sure we don't put
919 # unicode strings in the environment.
920 env = os.environ.copy()
921 if scm:
922 env['GCLIENT_SCM'] = str(scm)
923 if parsed_url:
924 env['GCLIENT_URL'] = str(parsed_url)
925 env['GCLIENT_DEP_PATH'] = str(self.name)
926 if options.prepend_dir and scm == 'git':
927 print_stdout = False
928 def filter_fn(line):
929 """Git-specific path marshaling. It is optimized for git-grep."""
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000930
agabled437d762016-10-17 09:35:11 -0700931 def mod_path(git_pathspec):
932 match = re.match('^(\\S+?:)?([^\0]+)$', git_pathspec)
933 modified_path = os.path.join(self.name, match.group(2))
934 branch = match.group(1) or ''
935 return '%s%s' % (branch, modified_path)
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000936
agabled437d762016-10-17 09:35:11 -0700937 match = re.match('^Binary file ([^\0]+) matches$', line)
938 if match:
939 print('Binary file %s matches\n' % mod_path(match.group(1)))
940 return
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000941
agabled437d762016-10-17 09:35:11 -0700942 items = line.split('\0')
943 if len(items) == 2 and items[1]:
944 print('%s : %s' % (mod_path(items[0]), items[1]))
945 elif len(items) >= 2:
946 # Multiple null bytes or a single trailing null byte indicate
947 # git is likely displaying filenames only (such as with -l)
948 print('\n'.join(mod_path(path) for path in items if path))
949 else:
950 print(line)
951 else:
952 print_stdout = True
953 filter_fn = None
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000954
agabled437d762016-10-17 09:35:11 -0700955 if parsed_url is None:
956 print('Skipped omitted dependency %s' % cwd, file=sys.stderr)
957 elif os.path.isdir(cwd):
958 try:
959 gclient_utils.CheckCallAndFilter(
960 args, cwd=cwd, env=env, print_stdout=print_stdout,
961 filter_fn=filter_fn,
962 )
963 except subprocess2.CalledProcessError:
964 if not options.ignore:
965 raise
966 else:
967 print('Skipped missing %s' % cwd, file=sys.stderr)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000968
Dirk Pranke9f20d022017-10-11 18:36:54 -0700969 def HasGNArgsFile(self):
970 return self._gn_args_file is not None
971
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +0200972 def WriteGNArgsFile(self):
973 lines = ['# Generated from %r' % self.deps_file]
Paweł Hajdan, Jrb495bf52017-09-25 19:33:50 +0200974 variables = self.get_vars()
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +0200975 for arg in self._gn_args:
Paweł Hajdan, Jre0214742017-09-28 12:21:01 +0200976 value = variables[arg]
977 if isinstance(value, basestring):
978 value = gclient_eval.EvaluateCondition(value, variables)
Paweł Hajdan, Jrb495bf52017-09-25 19:33:50 +0200979 lines.append('%s = %s' % (arg, ToGNString(value)))
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +0200980 with open(os.path.join(self.root.root_dir, self._gn_args_file), 'w') as f:
981 f.write('\n'.join(lines))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000982
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000983 @gclient_utils.lockedmethod
984 def _run_is_done(self, file_list, parsed_url):
985 # Both these are kept for hooks that are run as a separate tree traversal.
986 self._file_list = file_list
987 self._parsed_url = parsed_url
988 self._processed = True
989
szager@google.comb9a78d32012-03-13 18:46:21 +0000990 def GetHooks(self, options):
991 """Evaluates all hooks, and return them in a flat list.
992
993 RunOnDeps() must have been called before to load the DEPS.
994 """
995 result = []
maruel@chromium.org68988972011-09-20 14:11:42 +0000996 if not self.should_process or not self.recursion_limit:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000997 # Don't run the hook when it is above recursion_limit.
szager@google.comb9a78d32012-03-13 18:46:21 +0000998 return result
maruel@chromium.orgdc7445d2010-07-09 21:05:29 +0000999 # If "--force" was specified, run all hooks regardless of what files have
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001000 # changed.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001001 if self.deps_hooks:
agabled437d762016-10-17 09:35:11 -07001002 # TODO(maruel): If the user is using git, then we don't know
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001003 # what files have changed so we always run all hooks. It'd be nice to fix
1004 # that.
1005 if (options.force or
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001006 gclient_scm.GetScmName(self.parsed_url) in ('git', None) or
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001007 os.path.isdir(os.path.join(self.root.root_dir, self.name, '.git'))):
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +02001008 result.extend(self.deps_hooks)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001009 else:
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +02001010 for hook in self.deps_hooks:
1011 if hook.matches(self.file_list_and_children):
1012 result.append(hook)
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001013 for s in self.dependencies:
szager@google.comb9a78d32012-03-13 18:46:21 +00001014 result.extend(s.GetHooks(options))
1015 return result
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001016
Dirk Pranke9f20d022017-10-11 18:36:54 -07001017 def WriteGNArgsFilesRecursively(self, dependencies):
1018 for dep in dependencies:
1019 if dep.HasGNArgsFile():
1020 dep.WriteGNArgsFile()
1021 self.WriteGNArgsFilesRecursively(dep.dependencies)
1022
Daniel Chenga0c5f082017-10-19 13:35:19 -07001023 def RunHooksRecursively(self, options, progress):
szager@google.comb9a78d32012-03-13 18:46:21 +00001024 assert self.hooks_ran == False
maruel@chromium.org064186c2011-09-27 23:53:33 +00001025 self._hooks_ran = True
Daniel Chenga0c5f082017-10-19 13:35:19 -07001026 hooks = self.GetHooks(options)
1027 if progress:
1028 progress._total = len(hooks)
1029 for hook in hooks:
Daniel Chenga0c5f082017-10-19 13:35:19 -07001030 if progress:
1031 progress.update(extra=hook.name or '')
Daniel Cheng93c5d602017-10-20 11:40:17 -07001032 hook.run(self.root.root_dir)
Daniel Chenga0c5f082017-10-19 13:35:19 -07001033 if progress:
1034 progress.end()
maruel@chromium.orgeaf61062010-07-07 18:42:39 +00001035
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001036 def RunPreDepsHooks(self):
1037 assert self.processed
1038 assert self.deps_parsed
1039 assert not self.pre_deps_hooks_ran
1040 assert not self.hooks_ran
1041 for s in self.dependencies:
1042 assert not s.processed
1043 self._pre_deps_hooks_ran = True
1044 for hook in self.pre_deps_hooks:
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +02001045 hook.run(self.root.root_dir)
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001046
maruel@chromium.org0d812442010-08-10 12:41:08 +00001047 def subtree(self, include_all):
maruel@chromium.orgad3287e2011-10-03 19:15:10 +00001048 """Breadth first recursion excluding root node."""
maruel@chromium.orgf13a4182011-09-22 00:26:15 +00001049 dependencies = self.dependencies
1050 for d in dependencies:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001051 if d.should_process or include_all:
maruel@chromium.orgad3287e2011-10-03 19:15:10 +00001052 yield d
maruel@chromium.orgf13a4182011-09-22 00:26:15 +00001053 for d in dependencies:
maruel@chromium.orgad3287e2011-10-03 19:15:10 +00001054 for i in d.subtree(include_all):
1055 yield i
1056
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001057 @gclient_utils.lockedmethod
1058 def add_dependency(self, new_dep):
1059 self._dependencies.append(new_dep)
1060
1061 @gclient_utils.lockedmethod
Paweł Hajdan, Jr357415c2017-07-24 14:35:28 +02001062 def _mark_as_parsed(self, new_hooks):
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001063 self._deps_hooks.extend(new_hooks)
1064 self._deps_parsed = True
1065
maruel@chromium.org68988972011-09-20 14:11:42 +00001066 @property
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001067 @gclient_utils.lockedmethod
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +00001068 def dependencies(self):
1069 return tuple(self._dependencies)
1070
1071 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001072 @gclient_utils.lockedmethod
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02001073 def os_dependencies(self):
1074 return dict(self._os_dependencies)
1075
1076 @property
1077 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001078 def deps_hooks(self):
1079 return tuple(self._deps_hooks)
1080
1081 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001082 @gclient_utils.lockedmethod
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +02001083 def os_deps_hooks(self):
1084 return dict(self._os_deps_hooks)
1085
1086 @property
1087 @gclient_utils.lockedmethod
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001088 def pre_deps_hooks(self):
1089 return tuple(self._pre_deps_hooks)
1090
1091 @property
1092 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001093 def parsed_url(self):
1094 return self._parsed_url
1095
1096 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001097 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001098 def deps_parsed(self):
maruel@chromium.org3223edd2011-10-10 23:17:39 +00001099 """This is purely for debugging purposes. It's not used anywhere."""
maruel@chromium.org064186c2011-09-27 23:53:33 +00001100 return self._deps_parsed
1101
1102 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001103 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001104 def processed(self):
1105 return self._processed
1106
1107 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001108 @gclient_utils.lockedmethod
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001109 def pre_deps_hooks_ran(self):
1110 return self._pre_deps_hooks_ran
1111
1112 @property
1113 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001114 def hooks_ran(self):
1115 return self._hooks_ran
1116
1117 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001118 @gclient_utils.lockedmethod
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00001119 def allowed_hosts(self):
1120 return self._allowed_hosts
1121
1122 @property
1123 @gclient_utils.lockedmethod
maruel@chromium.org861fd0f2010-07-23 03:05:05 +00001124 def file_list(self):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001125 return tuple(self._file_list)
1126
1127 @property
kustermann@google.coma692e8f2013-04-18 08:32:04 +00001128 def used_scm(self):
1129 """SCMWrapper instance for this dependency or None if not processed yet."""
1130 return self._used_scm
1131
1132 @property
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00001133 @gclient_utils.lockedmethod
1134 def got_revision(self):
1135 return self._got_revision
1136
1137 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001138 def file_list_and_children(self):
1139 result = list(self.file_list)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +00001140 for d in self.dependencies:
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001141 result.extend(d.file_list_and_children)
maruel@chromium.org68988972011-09-20 14:11:42 +00001142 return tuple(result)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +00001143
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001144 def __str__(self):
1145 out = []
agablea98a6cd2016-11-15 14:30:10 -08001146 for i in ('name', 'url', 'parsed_url', 'custom_deps',
maruel@chromium.org3c74bc92011-09-15 19:17:21 +00001147 'custom_vars', 'deps_hooks', 'file_list', 'should_process',
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00001148 'processed', 'hooks_ran', 'deps_parsed', 'requirements',
1149 'allowed_hosts'):
maruel@chromium.org3c74bc92011-09-15 19:17:21 +00001150 # First try the native property if it exists.
1151 if hasattr(self, '_' + i):
1152 value = getattr(self, '_' + i, False)
1153 else:
1154 value = getattr(self, i, False)
1155 if value:
1156 out.append('%s: %s' % (i, value))
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001157
1158 for d in self.dependencies:
1159 out.extend([' ' + x for x in str(d).splitlines()])
1160 out.append('')
1161 return '\n'.join(out)
1162
1163 def __repr__(self):
1164 return '%s: %s' % (self.name, self.url)
1165
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001166 def hierarchy(self, include_url=True):
maruel@chromium.orgbc2d2f92010-07-22 21:26:48 +00001167 """Returns a human-readable hierarchical reference to a Dependency."""
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001168 def format_name(d):
1169 if include_url:
1170 return '%s(%s)' % (d.name, d.url)
1171 return d.name
1172 out = format_name(self)
maruel@chromium.orgbffb9042010-07-22 20:59:36 +00001173 i = self.parent
1174 while i and i.name:
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001175 out = '%s -> %s' % (format_name(i), out)
maruel@chromium.orgbffb9042010-07-22 20:59:36 +00001176 i = i.parent
1177 return out
1178
Paweł Hajdan, Jrd3790252017-07-03 21:06:24 +02001179 def get_vars(self):
1180 """Returns a dictionary of effective variable values
1181 (DEPS file contents with applied custom_vars overrides)."""
Paweł Hajdan, Jra3b67ae2017-08-30 15:18:21 +02001182 # Provide some built-in variables.
1183 result = {
Paweł Hajdan, Jrd325eb32017-10-03 17:43:37 +02001184 'checkout_android': 'android' in self.target_os,
1185 'checkout_fuchsia': 'fuchsia' in self.target_os,
1186 'checkout_ios': 'ios' in self.target_os,
1187 'checkout_linux': 'unix' in self.target_os,
1188 'checkout_mac': 'mac' in self.target_os,
1189 'checkout_win': 'win' in self.target_os,
1190 'host_os': _detect_host_os(),
Paweł Hajdan, Jra3b67ae2017-08-30 15:18:21 +02001191 }
1192 # Variables defined in DEPS file override built-in ones.
1193 result.update(self._vars)
Paweł Hajdan, Jrd3790252017-07-03 21:06:24 +02001194 result.update(self.custom_vars or {})
1195 return result
1196
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001197
Paweł Hajdan, Jra3b67ae2017-08-30 15:18:21 +02001198_PLATFORM_MAPPING = {
1199 'cygwin': 'win',
1200 'darwin': 'mac',
1201 'linux2': 'linux',
1202 'win32': 'win',
Jaideep Bajwad05f3582017-09-11 12:31:48 -04001203 'aix6': 'aix',
Paweł Hajdan, Jra3b67ae2017-08-30 15:18:21 +02001204}
1205
1206
1207def _detect_host_os():
1208 return _PLATFORM_MAPPING[sys.platform]
1209
1210
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001211class GClient(Dependency):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001212 """Object that represent a gclient checkout. A tree of Dependency(), one per
1213 solution or DEPS entry."""
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001214
1215 DEPS_OS_CHOICES = {
Jaideep Bajwad05f3582017-09-11 12:31:48 -04001216 "aix6": "unix",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001217 "win32": "win",
1218 "win": "win",
1219 "cygwin": "win",
1220 "darwin": "mac",
1221 "mac": "mac",
1222 "unix": "unix",
1223 "linux": "unix",
1224 "linux2": "unix",
maruel@chromium.org244e3442011-06-12 15:20:55 +00001225 "linux3": "unix",
szager@chromium.orgf8c95cd2012-06-01 22:26:52 +00001226 "android": "android",
Michael Mossc54fa812017-08-17 11:27:58 -07001227 "ios": "ios",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001228 }
1229
1230 DEFAULT_CLIENT_FILE_TEXT = ("""\
1231solutions = [
smutae7ea312016-07-18 11:59:41 -07001232 { "name" : "%(solution_name)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001233 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +00001234 "deps_file" : "%(deps_file)s",
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001235 "managed" : %(managed)s,
smutae7ea312016-07-18 11:59:41 -07001236 "custom_deps" : {
1237 },
Paweł Hajdan, Jr3ba2a7c2017-10-04 19:24:46 +02001238 "custom_vars": %(custom_vars)r,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001239 },
1240]
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001241cache_dir = %(cache_dir)r
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001242""")
1243
1244 DEFAULT_SNAPSHOT_SOLUTION_TEXT = ("""\
smutae7ea312016-07-18 11:59:41 -07001245 { "name" : "%(solution_name)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001246 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +00001247 "deps_file" : "%(deps_file)s",
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001248 "managed" : %(managed)s,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001249 "custom_deps" : {
smutae7ea312016-07-18 11:59:41 -07001250%(solution_deps)s },
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001251 },
1252""")
1253
1254 DEFAULT_SNAPSHOT_FILE_TEXT = ("""\
1255# Snapshot generated with gclient revinfo --snapshot
1256solutions = [
maruel@chromium.org73e21142010-07-05 13:32:01 +00001257%(solution_list)s]
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001258""")
1259
1260 def __init__(self, root_dir, options):
maruel@chromium.org0d812442010-08-10 12:41:08 +00001261 # Do not change previous behavior. Only solution level and immediate DEPS
1262 # are processed.
1263 self._recursion_limit = 2
Paweł Hajdan, Jrfc6196b2017-07-27 13:15:25 +02001264 Dependency.__init__(self, None, None, None, None, True, None, None, None,
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +02001265 'unused', True, None, None, True)
maruel@chromium.org0d425922010-06-21 19:22:24 +00001266 self._options = options
maruel@chromium.org271375b2010-06-23 19:17:38 +00001267 if options.deps_os:
1268 enforced_os = options.deps_os.split(',')
1269 else:
1270 enforced_os = [self.DEPS_OS_CHOICES.get(sys.platform, 'unix')]
1271 if 'all' in enforced_os:
1272 enforced_os = self.DEPS_OS_CHOICES.itervalues()
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001273 self._enforced_os = tuple(set(enforced_os))
maruel@chromium.org271375b2010-06-23 19:17:38 +00001274 self._root_dir = root_dir
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001275 self.config_content = None
1276
borenet@google.com88d10082014-03-21 17:24:48 +00001277 def _CheckConfig(self):
1278 """Verify that the config matches the state of the existing checked-out
1279 solutions."""
1280 for dep in self.dependencies:
1281 if dep.managed and dep.url:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001282 scm = gclient_scm.CreateSCM(
1283 dep.url, self.root_dir, dep.name, self.outbuf)
smut@google.comd33eab32014-07-07 19:35:18 +00001284 actual_url = scm.GetActualRemoteURL(self._options)
borenet@google.com4e9be262014-04-08 19:40:30 +00001285 if actual_url and not scm.DoesRemoteURLMatch(self._options):
levarum@chromium.org27a6f9a2016-05-28 00:21:49 +00001286 mirror = scm.GetCacheMirror()
1287 if mirror:
1288 mirror_string = '%s (exists=%s)' % (mirror.mirror_path,
1289 mirror.exists())
1290 else:
1291 mirror_string = 'not used'
borenet@google.com0a427372014-04-02 19:12:13 +00001292 raise gclient_utils.Error('''
borenet@google.com88d10082014-03-21 17:24:48 +00001293Your .gclient file seems to be broken. The requested URL is different from what
borenet@google.com0a427372014-04-02 19:12:13 +00001294is actually checked out in %(checkout_path)s.
borenet@google.com88d10082014-03-21 17:24:48 +00001295
borenet@google.com97882362014-04-07 20:06:02 +00001296The .gclient file contains:
levarum@chromium.org27a6f9a2016-05-28 00:21:49 +00001297URL: %(expected_url)s (%(expected_scm)s)
1298Cache mirror: %(mirror_string)s
borenet@google.com97882362014-04-07 20:06:02 +00001299
1300The local checkout in %(checkout_path)s reports:
1301%(actual_url)s (%(actual_scm)s)
borenet@google.com88d10082014-03-21 17:24:48 +00001302
1303You should ensure that the URL listed in .gclient is correct and either change
agabled437d762016-10-17 09:35:11 -07001304it or fix the checkout.
borenet@google.com88d10082014-03-21 17:24:48 +00001305''' % {'checkout_path': os.path.join(self.root_dir, dep.name),
1306 'expected_url': dep.url,
1307 'expected_scm': gclient_scm.GetScmName(dep.url),
levarum@chromium.org27a6f9a2016-05-28 00:21:49 +00001308 'mirror_string' : mirror_string,
borenet@google.com88d10082014-03-21 17:24:48 +00001309 'actual_url': actual_url,
1310 'actual_scm': gclient_scm.GetScmName(actual_url)})
1311
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001312 def SetConfig(self, content):
maruel@chromium.orgf13a4182011-09-22 00:26:15 +00001313 assert not self.dependencies
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001314 config_dict = {}
1315 self.config_content = content
1316 try:
1317 exec(content, config_dict)
vapier@chromium.orga81a56e2015-11-11 07:56:13 +00001318 except SyntaxError as e:
maruel@chromium.org5990f9d2010-07-07 18:02:58 +00001319 gclient_utils.SyntaxErrorToError('.gclient', e)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001320
peter@chromium.org1efccc82012-04-27 16:34:38 +00001321 # Append any target OS that is not already being enforced to the tuple.
1322 target_os = config_dict.get('target_os', [])
stuartmorgan@chromium.org18a4f6a2012-11-02 13:37:24 +00001323 if config_dict.get('target_os_only', False):
1324 self._enforced_os = tuple(set(target_os))
1325 else:
1326 self._enforced_os = tuple(set(self._enforced_os).union(target_os))
1327
Aleksandr Derbenev9e8fb0e2017-08-01 20:18:31 +03001328 cache_dir = config_dict.get('cache_dir', self._options.cache_dir)
dyen@chromium.orgd915cca2014-08-07 21:41:37 +00001329 if cache_dir:
1330 cache_dir = os.path.join(self.root_dir, cache_dir)
1331 cache_dir = os.path.abspath(cache_dir)
Vadim Shtayura08049e22017-10-11 00:14:52 +00001332 # If running on a bot, force break any stale git cache locks.
1333 if os.path.exists(cache_dir) and os.environ.get('CHROME_HEADLESS'):
1334 subprocess2.check_call(['git', 'cache', 'unlock', '--cache-dir',
1335 cache_dir, '--force', '--all'])
dyen@chromium.orgd915cca2014-08-07 21:41:37 +00001336 gclient_scm.GitWrapper.cache_dir = cache_dir
1337 git_cache.Mirror.SetCachePath(cache_dir)
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001338
stuartmorgan@chromium.org18a4f6a2012-11-02 13:37:24 +00001339 if not target_os and config_dict.get('target_os_only', False):
1340 raise gclient_utils.Error('Can\'t use target_os_only if target_os is '
1341 'not specified')
peter@chromium.org1efccc82012-04-27 16:34:38 +00001342
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001343 deps_to_add = []
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001344 for s in config_dict.get('solutions', []):
maruel@chromium.org81843b82010-06-28 16:49:26 +00001345 try:
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001346 deps_to_add.append(Dependency(
Paweł Hajdan, Jrfc6196b2017-07-27 13:15:25 +02001347 self, s['name'], s['url'], s['url'],
smutae7ea312016-07-18 11:59:41 -07001348 s.get('managed', True),
maruel@chromium.org81843b82010-06-28 16:49:26 +00001349 s.get('custom_deps', {}),
maruel@chromium.org0d812442010-08-10 12:41:08 +00001350 s.get('custom_vars', {}),
petermayo@chromium.orge79161a2013-07-09 14:40:37 +00001351 s.get('custom_hooks', []),
nsylvain@google.comefc80932011-05-31 21:27:56 +00001352 s.get('deps_file', 'DEPS'),
agabledce6ddc2016-09-08 10:02:16 -07001353 True,
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +02001354 None,
1355 None,
1356 True))
maruel@chromium.org81843b82010-06-28 16:49:26 +00001357 except KeyError:
1358 raise gclient_utils.Error('Invalid .gclient file. Solution is '
1359 'incomplete: %s' % s)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001360 self.add_dependencies_and_close(deps_to_add, config_dict.get('hooks', []))
1361 logging.info('SetConfig() done')
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001362
1363 def SaveConfig(self):
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001364 gclient_utils.FileWrite(os.path.join(self.root_dir,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001365 self._options.config_filename),
1366 self.config_content)
1367
1368 @staticmethod
1369 def LoadCurrentConfig(options):
1370 """Searches for and loads a .gclient file relative to the current working
1371 dir. Returns a GClient object."""
szager@chromium.orge2e03202012-07-31 18:05:16 +00001372 if options.spec:
1373 client = GClient('.', options)
1374 client.SetConfig(options.spec)
1375 else:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00001376 if options.verbose:
1377 print('Looking for %s starting from %s\n' % (
1378 options.config_filename, os.getcwd()))
szager@chromium.orge2e03202012-07-31 18:05:16 +00001379 path = gclient_utils.FindGclientRoot(os.getcwd(), options.config_filename)
1380 if not path:
Michael Achenbachb3ce73d2017-10-11 16:41:27 +02001381 if options.verbose:
1382 print('Couldn\'t find configuration file.')
szager@chromium.orge2e03202012-07-31 18:05:16 +00001383 return None
1384 client = GClient(path, options)
1385 client.SetConfig(gclient_utils.FileRead(
1386 os.path.join(path, options.config_filename)))
maruel@chromium.org69392e72011-10-13 22:09:00 +00001387
1388 if (options.revisions and
1389 len(client.dependencies) > 1 and
1390 any('@' not in r for r in options.revisions)):
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001391 print(
1392 ('You must specify the full solution name like --revision %s@%s\n'
1393 'when you have multiple solutions setup in your .gclient file.\n'
1394 'Other solutions present are: %s.') % (
maruel@chromium.org69392e72011-10-13 22:09:00 +00001395 client.dependencies[0].name,
1396 options.revisions[0],
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001397 ', '.join(s.name for s in client.dependencies[1:])),
1398 file=sys.stderr)
maruel@chromium.org15804092010-09-02 17:07:37 +00001399 return client
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001400
nsylvain@google.comefc80932011-05-31 21:27:56 +00001401 def SetDefaultConfig(self, solution_name, deps_file, solution_url,
Paweł Hajdan, Jr3ba2a7c2017-10-04 19:24:46 +02001402 managed=True, cache_dir=None, custom_vars=None):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001403 self.SetConfig(self.DEFAULT_CLIENT_FILE_TEXT % {
1404 'solution_name': solution_name,
1405 'solution_url': solution_url,
nsylvain@google.comefc80932011-05-31 21:27:56 +00001406 'deps_file': deps_file,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001407 'managed': managed,
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001408 'cache_dir': cache_dir,
Paweł Hajdan, Jr3ba2a7c2017-10-04 19:24:46 +02001409 'custom_vars': custom_vars or {},
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001410 })
1411
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001412 def _SaveEntries(self):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001413 """Creates a .gclient_entries file to record the list of unique checkouts.
1414
1415 The .gclient_entries file lives in the same directory as .gclient.
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001416 """
1417 # Sometimes pprint.pformat will use {', sometimes it'll use { ' ... It
1418 # makes testing a bit too fun.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001419 result = 'entries = {\n'
maruel@chromium.org68988972011-09-20 14:11:42 +00001420 for entry in self.root.subtree(False):
agabled437d762016-10-17 09:35:11 -07001421 result += ' %s: %s,\n' % (pprint.pformat(entry.name),
1422 pprint.pformat(entry.parsed_url))
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001423 result += '}\n'
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001424 file_path = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org1333cb32011-10-04 23:40:16 +00001425 logging.debug(result)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001426 gclient_utils.FileWrite(file_path, result)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001427
1428 def _ReadEntries(self):
1429 """Read the .gclient_entries file for the given client.
1430
1431 Returns:
1432 A sequence of solution names, which will be empty if there is the
1433 entries file hasn't been created yet.
1434 """
1435 scope = {}
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001436 filename = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001437 if not os.path.exists(filename):
maruel@chromium.org73e21142010-07-05 13:32:01 +00001438 return {}
maruel@chromium.org5990f9d2010-07-07 18:02:58 +00001439 try:
1440 exec(gclient_utils.FileRead(filename), scope)
vapier@chromium.orga81a56e2015-11-11 07:56:13 +00001441 except SyntaxError as e:
maruel@chromium.org5990f9d2010-07-07 18:02:58 +00001442 gclient_utils.SyntaxErrorToError(filename, e)
Aaron Gable3721ee92017-04-03 14:53:14 -07001443 return scope.get('entries', {})
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001444
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001445 def _EnforceRevisions(self):
maruel@chromium.org918a9ae2010-05-28 15:50:30 +00001446 """Checks for revision overrides."""
1447 revision_overrides = {}
smutae7ea312016-07-18 11:59:41 -07001448 if self._options.head:
1449 return revision_overrides
joi@chromium.org792ea882010-11-10 02:37:27 +00001450 if not self._options.revisions:
1451 for s in self.dependencies:
smutae7ea312016-07-18 11:59:41 -07001452 if not s.managed:
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001453 self._options.revisions.append('%s@unmanaged' % s.name)
maruel@chromium.org307d1792010-05-31 20:03:13 +00001454 if not self._options.revisions:
1455 return revision_overrides
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001456 solutions_names = [s.name for s in self.dependencies]
smutae7ea312016-07-18 11:59:41 -07001457 index = 0
1458 for revision in self._options.revisions:
1459 if not '@' in revision:
maruel@chromium.org307d1792010-05-31 20:03:13 +00001460 # Support for --revision 123
smutae7ea312016-07-18 11:59:41 -07001461 revision = '%s@%s' % (solutions_names[index], revision)
1462 name, rev = revision.split('@', 1)
szager@chromium.org4ad264b2014-05-20 04:43:47 +00001463 revision_overrides[name] = rev
smutae7ea312016-07-18 11:59:41 -07001464 index += 1
maruel@chromium.org918a9ae2010-05-28 15:50:30 +00001465 return revision_overrides
1466
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001467 def RunOnDeps(self, command, args, ignore_requirements=False, progress=True):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001468 """Runs a command on each dependency in a client and its dependencies.
1469
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001470 Args:
1471 command: The command to use (e.g., 'status' or 'diff')
1472 args: list of str - extra arguments to add to the command line.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001473 """
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001474 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +00001475 raise gclient_utils.Error('No solution specified')
borenet@google.com0a427372014-04-02 19:12:13 +00001476
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001477 revision_overrides = {}
1478 # It's unnecessary to check for revision overrides for 'recurse'.
1479 # Save a few seconds by not calling _EnforceRevisions() in that case.
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +02001480 if command not in ('diff', 'recurse', 'runhooks', 'status', 'revert',
1481 'validate'):
szager@chromium.org5273b8a2014-08-21 15:10:10 +00001482 self._CheckConfig()
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001483 revision_overrides = self._EnforceRevisions()
Daniel Chenga21b5b32017-10-19 20:07:48 +00001484 # Disable progress for non-tty stdout.
Daniel Chenga0c5f082017-10-19 13:35:19 -07001485 should_show_progress = (
1486 setup_color.IS_TTY and not self._options.verbose and progress)
1487 pm = None
1488 if should_show_progress:
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001489 if command in ('update', 'revert'):
1490 pm = Progress('Syncing projects', 1)
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +02001491 elif command in ('recurse', 'validate'):
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001492 pm = Progress(' '.join(args), 1)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001493 work_queue = gclient_utils.ExecutionQueue(
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001494 self._options.jobs, pm, ignore_requirements=ignore_requirements,
1495 verbose=self._options.verbose)
maruel@chromium.org049bced2010-08-12 13:37:20 +00001496 for s in self.dependencies:
Paweł Hajdan, Jr4baaa112017-07-04 19:09:32 +02001497 if s.should_process:
1498 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +00001499 work_queue.flush(revision_overrides, command, args, options=self._options)
szager@chromium.org4ad264b2014-05-20 04:43:47 +00001500 if revision_overrides:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001501 print('Please fix your script, having invalid --revision flags will soon '
1502 'considered an error.', file=sys.stderr)
piman@chromium.org6f363722010-04-27 00:41:09 +00001503
Dirk Pranke9f20d022017-10-11 18:36:54 -07001504 # Once all the dependencies have been processed, it's now safe to write
1505 # out any gn_args_files and run the hooks.
1506 if command == 'update':
1507 self.WriteGNArgsFilesRecursively(self.dependencies)
1508
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001509 if not self._options.nohooks:
Daniel Chenga0c5f082017-10-19 13:35:19 -07001510 if should_show_progress:
1511 pm = Progress('Running hooks', 1)
1512 self.RunHooksRecursively(self._options, pm)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001513
1514 if command == 'update':
ajwong@chromium.orgcdcee802009-06-23 15:30:42 +00001515 # Notify the user if there is an orphaned entry in their working copy.
1516 # Only delete the directory if there are no changes in it, and
1517 # delete_unversioned_trees is set to true.
maruel@chromium.org68988972011-09-20 14:11:42 +00001518 entries = [i.name for i in self.root.subtree(False) if i.url]
xusydoc@chromium.org885a9602013-05-31 09:54:40 +00001519 full_entries = [os.path.join(self.root_dir, e.replace('/', os.path.sep))
1520 for e in entries]
1521
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001522 for entry, prev_url in self._ReadEntries().iteritems():
maruel@chromium.org04dd7de2010-10-14 13:25:49 +00001523 if not prev_url:
1524 # entry must have been overridden via .gclient custom_deps
1525 continue
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001526 # Fix path separator on Windows.
1527 entry_fixed = entry.replace('/', os.path.sep)
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001528 e_dir = os.path.join(self.root_dir, entry_fixed)
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001529 # Use entry and not entry_fixed there.
jochen@chromium.orga78e5532013-03-11 13:33:03 +00001530 if (entry not in entries and
1531 (not any(path.startswith(entry + '/') for path in entries)) and
jochen@chromium.orgcc475722013-03-11 13:07:40 +00001532 os.path.exists(e_dir)):
primiano@chromium.org1c127382015-02-17 11:15:40 +00001533 # The entry has been removed from DEPS.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001534 scm = gclient_scm.CreateSCM(
1535 prev_url, self.root_dir, entry_fixed, self.outbuf)
xusydoc@chromium.org885a9602013-05-31 09:54:40 +00001536
1537 # Check to see if this directory is now part of a higher-up checkout.
borenet@google.com359bb642014-05-13 17:28:19 +00001538 scm_root = None
agabled437d762016-10-17 09:35:11 -07001539 try:
1540 scm_root = gclient_scm.scm.GIT.GetCheckoutRoot(scm.checkout_path)
1541 except subprocess2.CalledProcessError:
1542 pass
1543 if not scm_root:
borenet@google.com359bb642014-05-13 17:28:19 +00001544 logging.warning('Could not find checkout root for %s. Unable to '
1545 'determine whether it is part of a higher-level '
1546 'checkout, so not removing.' % entry)
1547 continue
primiano@chromium.org1c127382015-02-17 11:15:40 +00001548
1549 # This is to handle the case of third_party/WebKit migrating from
1550 # being a DEPS entry to being part of the main project.
1551 # If the subproject is a Git project, we need to remove its .git
1552 # folder. Otherwise git operations on that folder will have different
1553 # effects depending on the current working directory.
agabled437d762016-10-17 09:35:11 -07001554 if os.path.abspath(scm_root) == os.path.abspath(e_dir):
primiano@chromium.org1c127382015-02-17 11:15:40 +00001555 e_par_dir = os.path.join(e_dir, os.pardir)
agabled437d762016-10-17 09:35:11 -07001556 if gclient_scm.scm.GIT.IsInsideWorkTree(e_par_dir):
1557 par_scm_root = gclient_scm.scm.GIT.GetCheckoutRoot(e_par_dir)
primiano@chromium.org1c127382015-02-17 11:15:40 +00001558 # rel_e_dir : relative path of entry w.r.t. its parent repo.
1559 rel_e_dir = os.path.relpath(e_dir, par_scm_root)
agabled437d762016-10-17 09:35:11 -07001560 if gclient_scm.scm.GIT.IsDirectoryVersioned(
1561 par_scm_root, rel_e_dir):
primiano@chromium.org1c127382015-02-17 11:15:40 +00001562 save_dir = scm.GetGitBackupDirPath()
1563 # Remove any eventual stale backup dir for the same project.
1564 if os.path.exists(save_dir):
1565 gclient_utils.rmtree(save_dir)
1566 os.rename(os.path.join(e_dir, '.git'), save_dir)
1567 # When switching between the two states (entry/ is a subproject
1568 # -> entry/ is part of the outer project), it is very likely
1569 # that some files are changed in the checkout, unless we are
1570 # jumping *exactly* across the commit which changed just DEPS.
1571 # In such case we want to cleanup any eventual stale files
1572 # (coming from the old subproject) in order to end up with a
1573 # clean checkout.
agabled437d762016-10-17 09:35:11 -07001574 gclient_scm.scm.GIT.CleanupDir(par_scm_root, rel_e_dir)
primiano@chromium.org1c127382015-02-17 11:15:40 +00001575 assert not os.path.exists(os.path.join(e_dir, '.git'))
1576 print(('\nWARNING: \'%s\' has been moved from DEPS to a higher '
1577 'level checkout. The git folder containing all the local'
1578 ' branches has been saved to %s.\n'
1579 'If you don\'t care about its state you can safely '
1580 'remove that folder to free up space.') %
1581 (entry, save_dir))
1582 continue
1583
borenet@google.com359bb642014-05-13 17:28:19 +00001584 if scm_root in full_entries:
primiano@chromium.org1c127382015-02-17 11:15:40 +00001585 logging.info('%s is part of a higher level checkout, not removing',
1586 scm.GetCheckoutRoot())
xusydoc@chromium.org885a9602013-05-31 09:54:40 +00001587 continue
1588
1589 file_list = []
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001590 scm.status(self._options, [], file_list)
1591 modified_files = file_list != []
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001592 if (not self._options.delete_unversioned_trees or
1593 (modified_files and not self._options.force)):
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001594 # There are modified files in this entry. Keep warning until
1595 # removed.
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001596 print(('\nWARNING: \'%s\' is no longer part of this client. '
1597 'It is recommended that you manually remove it.\n') %
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001598 entry_fixed)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001599 else:
1600 # Delete the entry
maruel@chromium.org73e21142010-07-05 13:32:01 +00001601 print('\n________ deleting \'%s\' in \'%s\'' % (
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001602 entry_fixed, self.root_dir))
digit@chromium.orgdc112ac2013-04-24 13:00:19 +00001603 gclient_utils.rmtree(e_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001604 # record the current list of entries for next time
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001605 self._SaveEntries()
maruel@chromium.org17cdf762010-05-28 17:30:52 +00001606 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001607
1608 def PrintRevInfo(self):
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001609 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +00001610 raise gclient_utils.Error('No solution specified')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001611 # Load all the settings.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001612 work_queue = gclient_utils.ExecutionQueue(
1613 self._options.jobs, None, False, verbose=self._options.verbose)
maruel@chromium.org049bced2010-08-12 13:37:20 +00001614 for s in self.dependencies:
Paweł Hajdan, Jr4baaa112017-07-04 19:09:32 +02001615 if s.should_process:
1616 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +00001617 work_queue.flush({}, None, [], options=self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001618
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001619 def GetURLAndRev(dep):
1620 """Returns the revision-qualified SCM url for a Dependency."""
1621 if dep.parsed_url is None:
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001622 return None
agabled437d762016-10-17 09:35:11 -07001623 url, _ = gclient_utils.SplitUrlRevision(dep.parsed_url)
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001624 scm = gclient_scm.CreateSCM(
agabled437d762016-10-17 09:35:11 -07001625 dep.parsed_url, self.root_dir, dep.name, self.outbuf)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001626 if not os.path.isdir(scm.checkout_path):
1627 return None
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001628 return '%s@%s' % (url, scm.revinfo(self._options, [], None))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001629
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001630 if self._options.snapshot:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001631 new_gclient = ''
1632 # First level at .gclient
1633 for d in self.dependencies:
1634 entries = {}
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001635 def GrabDeps(dep):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001636 """Recursively grab dependencies."""
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001637 for d in dep.dependencies:
1638 entries[d.name] = GetURLAndRev(d)
1639 GrabDeps(d)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001640 GrabDeps(d)
1641 custom_deps = []
1642 for k in sorted(entries.keys()):
1643 if entries[k]:
1644 # Quotes aren't escaped...
1645 custom_deps.append(' \"%s\": \'%s\',\n' % (k, entries[k]))
1646 else:
1647 custom_deps.append(' \"%s\": None,\n' % k)
1648 new_gclient += self.DEFAULT_SNAPSHOT_SOLUTION_TEXT % {
1649 'solution_name': d.name,
1650 'solution_url': d.url,
nsylvain@google.comefc80932011-05-31 21:27:56 +00001651 'deps_file': d.deps_file,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001652 'managed': d.managed,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001653 'solution_deps': ''.join(custom_deps),
1654 }
1655 # Print the snapshot configuration file
1656 print(self.DEFAULT_SNAPSHOT_FILE_TEXT % {'solution_list': new_gclient})
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +00001657 else:
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001658 entries = {}
maruel@chromium.org68988972011-09-20 14:11:42 +00001659 for d in self.root.subtree(False):
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001660 if self._options.actual:
1661 entries[d.name] = GetURLAndRev(d)
1662 else:
1663 entries[d.name] = d.parsed_url
1664 keys = sorted(entries.keys())
1665 for x in keys:
maruel@chromium.orgce464892010-08-12 17:12:18 +00001666 print('%s: %s' % (x, entries[x]))
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +00001667 logging.info(str(self))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001668
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001669 def ParseDepsFile(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001670 """No DEPS to parse for a .gclient file."""
maruel@chromium.org049bced2010-08-12 13:37:20 +00001671 raise gclient_utils.Error('Internal error')
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001672
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00001673 def PrintLocationAndContents(self):
1674 # Print out the .gclient file. This is longer than if we just printed the
1675 # client dict, but more legible, and it might contain helpful comments.
1676 print('Loaded .gclient config in %s:\n%s' % (
1677 self.root_dir, self.config_content))
1678
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001679 @property
maruel@chromium.org75a59272010-06-11 22:34:03 +00001680 def root_dir(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001681 """Root directory of gclient checkout."""
maruel@chromium.org75a59272010-06-11 22:34:03 +00001682 return self._root_dir
1683
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001684 @property
maruel@chromium.org271375b2010-06-23 19:17:38 +00001685 def enforced_os(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001686 """What deps_os entries that are to be parsed."""
maruel@chromium.org271375b2010-06-23 19:17:38 +00001687 return self._enforced_os
1688
maruel@chromium.org68988972011-09-20 14:11:42 +00001689 @property
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001690 def recursion_limit(self):
1691 """How recursive can each dependencies in DEPS file can load DEPS file."""
1692 return self._recursion_limit
1693
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +00001694 @property
cmp@chromium.orgc401ad12014-07-02 23:20:08 +00001695 def try_recursedeps(self):
1696 """Whether to attempt using recursedeps-style recursion processing."""
cmp@chromium.orge84ac912014-06-30 23:14:35 +00001697 return True
1698
1699 @property
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +00001700 def target_os(self):
1701 return self._enforced_os
1702
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001703
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001704#### gclient commands.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001705
1706
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001707@subcommand.usage('[command] [args ...]')
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001708def CMDrecurse(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001709 """Operates [command args ...] on all the dependencies.
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001710
1711 Runs a shell command on all entries.
qyearsley12fa6ff2016-08-24 09:18:40 -07001712 Sets GCLIENT_DEP_PATH environment variable as the dep's relative location to
ilevy@chromium.org37116242012-11-28 01:32:48 +00001713 root directory of the checkout.
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001714 """
1715 # Stop parsing at the first non-arg so that these go through to the command
1716 parser.disable_interspersed_args()
1717 parser.add_option('-s', '--scm', action='append', default=[],
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001718 help='Choose scm types to operate upon.')
maruel@chromium.org288054d2012-03-05 00:43:07 +00001719 parser.add_option('-i', '--ignore', action='store_true',
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001720 help='Ignore non-zero return codes from subcommands.')
1721 parser.add_option('--prepend-dir', action='store_true',
1722 help='Prepend relative dir for use with git <cmd> --null.')
1723 parser.add_option('--no-progress', action='store_true',
1724 help='Disable progress bar that shows sub-command updates')
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001725 options, args = parser.parse_args(args)
maruel@chromium.org45e9f2d2010-10-18 13:33:46 +00001726 if not args:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001727 print('Need to supply a command!', file=sys.stderr)
maruel@chromium.org45e9f2d2010-10-18 13:33:46 +00001728 return 1
maruel@chromium.org78cba522010-10-18 13:32:05 +00001729 root_and_entries = gclient_utils.GetGClientRootAndEntries()
1730 if not root_and_entries:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001731 print(
maruel@chromium.org78cba522010-10-18 13:32:05 +00001732 'You need to run gclient sync at least once to use \'recurse\'.\n'
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001733 'This is because .gclient_entries needs to exist and be up to date.',
1734 file=sys.stderr)
maruel@chromium.org78cba522010-10-18 13:32:05 +00001735 return 1
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001736
1737 # Normalize options.scm to a set()
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001738 scm_set = set()
1739 for scm in options.scm:
1740 scm_set.update(scm.split(','))
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001741 options.scm = scm_set
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001742
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001743 options.nohooks = True
1744 client = GClient.LoadCurrentConfig(options)
Marc-Antoine Ruele6e06412017-10-18 13:47:02 -04001745 if not client:
1746 raise gclient_utils.Error('client not configured; see \'gclient config\'')
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001747 return client.RunOnDeps('recurse', args, ignore_requirements=True,
1748 progress=not options.no_progress)
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001749
1750
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001751@subcommand.usage('[args ...]')
davidbarr@chromium.org12f944e2012-03-01 02:18:31 +00001752def CMDfetch(parser, args):
1753 """Fetches upstream commits for all modules.
1754
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001755 Completely git-specific. Simply runs 'git fetch [args ...]' for each module.
1756 """
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001757 (options, args) = parser.parse_args(args)
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00001758 return CMDrecurse(OptionParser(), [
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001759 '--jobs=%d' % options.jobs, '--scm=git', 'git', 'fetch'] + args)
1760
1761
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02001762class Flattener(object):
1763 """Flattens a gclient solution."""
1764
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02001765 def __init__(self, client, pin_all_deps=False):
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02001766 """Constructor.
1767
1768 Arguments:
1769 client (GClient): client to flatten
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02001770 pin_all_deps (bool): whether to pin all deps, even if they're not pinned
1771 in DEPS
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02001772 """
1773 self._client = client
1774
1775 self._deps_string = None
Paweł Hajdan, Jrfeb01642017-09-12 15:50:46 +02001776 self._deps_files = set()
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02001777
1778 self._allowed_hosts = set()
1779 self._deps = {}
1780 self._deps_os = {}
1781 self._hooks = []
1782 self._hooks_os = {}
1783 self._pre_deps_hooks = []
Paweł Hajdan, Jrfb022012017-07-06 18:00:08 +02001784 self._vars = {}
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02001785
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02001786 self._flatten(pin_all_deps=pin_all_deps)
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02001787
1788 @property
1789 def deps_string(self):
1790 assert self._deps_string is not None
1791 return self._deps_string
1792
Paweł Hajdan, Jrfeb01642017-09-12 15:50:46 +02001793 @property
1794 def deps_files(self):
1795 return self._deps_files
1796
Paweł Hajdan, Jr5ec77132017-08-16 19:21:06 +02001797 def _pin_dep(self, dep):
1798 """Pins a dependency to specific full revision sha.
1799
1800 Arguments:
1801 dep (Dependency): dependency to process
1802 """
1803 if dep.parsed_url is None:
1804 return
1805
1806 # Make sure the revision is always fully specified (a hash),
1807 # as opposed to refs or tags which might change. Similarly,
1808 # shortened shas might become ambiguous; make sure to always
1809 # use full one for pinning.
1810 url, revision = gclient_utils.SplitUrlRevision(dep.parsed_url)
1811 if revision and gclient_utils.IsFullGitSha(revision):
1812 return
1813
1814 scm = gclient_scm.CreateSCM(
1815 dep.parsed_url, self._client.root_dir, dep.name, dep.outbuf)
1816 revinfo = scm.revinfo(self._client._options, [], None)
1817
1818 dep._parsed_url = dep._url = '%s@%s' % (url, revinfo)
1819 raw_url, _ = gclient_utils.SplitUrlRevision(dep._raw_url)
1820 dep._raw_url = '%s@%s' % (raw_url, revinfo)
1821
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02001822 def _flatten(self, pin_all_deps=False):
1823 """Runs the flattener. Saves resulting DEPS string.
1824
1825 Arguments:
1826 pin_all_deps (bool): whether to pin all deps, even if they're not pinned
1827 in DEPS
1828 """
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02001829 for solution in self._client.dependencies:
Paweł Hajdan, Jr11eb7152017-08-10 12:50:11 +02001830 self._add_dep(solution)
Paweł Hajdan, Jrb0ad16e2017-08-03 15:33:21 +02001831 self._flatten_dep(solution)
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02001832
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02001833 if pin_all_deps:
1834 for dep in self._deps.itervalues():
Paweł Hajdan, Jr5ec77132017-08-16 19:21:06 +02001835 self._pin_dep(dep)
Paweł Hajdan, Jr39300ba2017-08-11 16:52:38 +02001836
Paweł Hajdan, Jr5ec77132017-08-16 19:21:06 +02001837 for os_deps in self._deps_os.itervalues():
1838 for dep in os_deps.itervalues():
Paweł Hajdan, Jr5ec77132017-08-16 19:21:06 +02001839 self._pin_dep(dep)
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02001840
Paweł Hajdan, Jr76a9d042017-08-18 20:05:41 +02001841 def add_deps_file(dep):
Paweł Hajdan, Jr0870df22017-08-23 17:59:29 +02001842 # Only include DEPS files referenced by recursedeps.
1843 if not (dep.parent is None or
1844 (dep.name in (dep.parent.recursedeps or {}))):
1845 return
Paweł Hajdan, Jrfeb01642017-09-12 15:50:46 +02001846 deps_file = dep.deps_file
1847 deps_path = os.path.join(self._client.root_dir, dep.name, deps_file)
Paweł Hajdan, Jr76a9d042017-08-18 20:05:41 +02001848 if not os.path.exists(deps_path):
Paweł Hajdan, Jrfeb01642017-09-12 15:50:46 +02001849 # gclient has a fallback that if deps_file doesn't exist, it'll try
1850 # DEPS. Do the same here.
1851 deps_file = 'DEPS'
1852 deps_path = os.path.join(self._client.root_dir, dep.name, deps_file)
1853 if not os.path.exists(deps_path):
1854 return
Paweł Hajdan, Jr76a9d042017-08-18 20:05:41 +02001855 assert dep.parsed_url
Paweł Hajdan, Jrfeb01642017-09-12 15:50:46 +02001856 self._deps_files.add((dep.parsed_url, deps_file))
Paweł Hajdan, Jr76a9d042017-08-18 20:05:41 +02001857 for dep in self._deps.itervalues():
1858 add_deps_file(dep)
1859 for os_deps in self._deps_os.itervalues():
1860 for dep in os_deps.itervalues():
1861 add_deps_file(dep)
1862
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02001863 self._deps_string = '\n'.join(
1864 _GNSettingsToLines(
1865 self._client.dependencies[0]._gn_args_file,
1866 self._client.dependencies[0]._gn_args) +
1867 _AllowedHostsToLines(self._allowed_hosts) +
1868 _DepsToLines(self._deps) +
1869 _DepsOsToLines(self._deps_os) +
1870 _HooksToLines('hooks', self._hooks) +
1871 _HooksToLines('pre_deps_hooks', self._pre_deps_hooks) +
1872 _HooksOsToLines(self._hooks_os) +
Paweł Hajdan, Jrfb022012017-07-06 18:00:08 +02001873 _VarsToLines(self._vars) +
Paweł Hajdan, Jr76a9d042017-08-18 20:05:41 +02001874 ['# %s, %s' % (url, deps_file)
Paweł Hajdan, Jrfeb01642017-09-12 15:50:46 +02001875 for url, deps_file in sorted(self._deps_files)] +
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02001876 ['']) # Ensure newline at end of file.
1877
Paweł Hajdan, Jr11eb7152017-08-10 12:50:11 +02001878 def _add_dep(self, dep):
1879 """Helper to add a dependency to flattened DEPS.
1880
1881 Arguments:
1882 dep (Dependency): dependency to add
1883 """
1884 assert dep.name not in self._deps or self._deps.get(dep.name) == dep, (
1885 dep.name, self._deps.get(dep.name))
Paweł Hajdan, Jr9a289022017-08-10 16:04:24 +02001886 if dep.url:
1887 self._deps[dep.name] = dep
Paweł Hajdan, Jr11eb7152017-08-10 12:50:11 +02001888
Paweł Hajdan, Jradae2a62017-08-18 16:49:57 +02001889 def _add_os_dep(self, os_dep, dep_os):
1890 """Helper to add an OS-specific dependency to flattened DEPS.
1891
1892 Arguments:
1893 os_dep (Dependency): dependency to add
1894 dep_os (str): name of the OS
1895 """
1896 assert (
1897 os_dep.name not in self._deps_os.get(dep_os, {}) or
1898 self._deps_os.get(dep_os, {}).get(os_dep.name) == os_dep), (
1899 os_dep.name, self._deps_os.get(dep_os, {}).get(os_dep.name))
1900 if os_dep.url:
Paweł Hajdan, Jr76a9d042017-08-18 20:05:41 +02001901 # OS-specific deps need to have their full URL resolved manually.
1902 assert not os_dep.parsed_url, (os_dep, os_dep.parsed_url)
1903 os_dep._parsed_url = os_dep.LateOverride(os_dep.url)
1904
Paweł Hajdan, Jradae2a62017-08-18 16:49:57 +02001905 self._deps_os.setdefault(dep_os, {})[os_dep.name] = os_dep
1906
1907 def _flatten_dep(self, dep, dep_os=None):
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02001908 """Visits a dependency in order to flatten it (see CMDflatten).
1909
1910 Arguments:
1911 dep (Dependency): dependency to process
Paweł Hajdan, Jradae2a62017-08-18 16:49:57 +02001912 dep_os (str or None): name of the OS |dep| is specific to
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02001913 """
Paweł Hajdan, Jradae2a62017-08-18 16:49:57 +02001914 logging.debug('_flatten_dep(%s, %s)', dep.name, dep_os)
1915
Paweł Hajdan, Jrc69b32e2017-08-17 18:47:48 +02001916 if not dep.deps_parsed:
1917 dep.ParseDepsFile()
1918
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02001919 self._allowed_hosts.update(dep.allowed_hosts)
1920
Paweł Hajdan, Jra3b67ae2017-08-30 15:18:21 +02001921 # Only include vars listed in the DEPS files, not possible local overrides.
1922 for key, value in dep._vars.iteritems():
Paweł Hajdan, Jrc9353602017-08-02 17:52:08 +02001923 # Make sure there are no conflicting variables. It is fine however
1924 # to use same variable name, as long as the value is consistent.
1925 assert key not in self._vars or self._vars[key][1] == value
Paweł Hajdan, Jrfb022012017-07-06 18:00:08 +02001926 self._vars[key] = (dep, value)
1927
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02001928 self._pre_deps_hooks.extend([(dep, hook) for hook in dep.pre_deps_hooks])
1929
Paweł Hajdan, Jradae2a62017-08-18 16:49:57 +02001930 if dep_os:
1931 if dep.deps_hooks:
1932 self._hooks_os.setdefault(dep_os, []).extend(
1933 [(dep, hook) for hook in dep.deps_hooks])
1934 else:
1935 self._hooks.extend([(dep, hook) for hook in dep.deps_hooks])
1936
Paweł Hajdan, Jrb0ad16e2017-08-03 15:33:21 +02001937 for sub_dep in dep.dependencies:
Paweł Hajdan, Jradae2a62017-08-18 16:49:57 +02001938 if dep_os:
1939 self._add_os_dep(sub_dep, dep_os)
1940 else:
1941 self._add_dep(sub_dep)
Paweł Hajdan, Jrb0ad16e2017-08-03 15:33:21 +02001942
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02001943 for hook_os, os_hooks in dep.os_deps_hooks.iteritems():
1944 self._hooks_os.setdefault(hook_os, []).extend(
1945 [(dep, hook) for hook in os_hooks])
1946
Paweł Hajdan, Jradae2a62017-08-18 16:49:57 +02001947 for sub_dep_os, os_deps in dep.os_dependencies.iteritems():
Paweł Hajdan, Jre2deb1e2017-08-09 17:29:21 +02001948 for os_dep in os_deps:
Paweł Hajdan, Jradae2a62017-08-18 16:49:57 +02001949 self._add_os_dep(os_dep, sub_dep_os)
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02001950
Paweł Hajdan, Jradae2a62017-08-18 16:49:57 +02001951 # Process recursedeps. |deps_by_name| is a map where keys are dependency
1952 # names, and values are maps of OS names to |Dependency| instances.
1953 # |None| in place of OS name means the dependency is not OS-specific.
1954 deps_by_name = dict((d.name, {None: d}) for d in dep.dependencies)
1955 for sub_dep_os, os_deps in dep.os_dependencies.iteritems():
Paweł Hajdan, Jrc9353602017-08-02 17:52:08 +02001956 for os_dep in os_deps:
Paweł Hajdan, Jradae2a62017-08-18 16:49:57 +02001957 assert sub_dep_os not in deps_by_name.get(os_dep.name, {}), (
1958 os_dep.name, sub_dep_os)
1959 deps_by_name.setdefault(os_dep.name, {})[sub_dep_os] = os_dep
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02001960 for recurse_dep_name in (dep.recursedeps or []):
Paweł Hajdan, Jradae2a62017-08-18 16:49:57 +02001961 dep_info = deps_by_name[recurse_dep_name]
1962 for sub_dep_os, os_dep in dep_info.iteritems():
1963 self._flatten_dep(os_dep, dep_os=(sub_dep_os or dep_os))
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02001964
1965
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001966def CMDflatten(parser, args):
1967 """Flattens the solutions into a single DEPS file."""
1968 parser.add_option('--output-deps', help='Path to the output DEPS file')
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02001969 parser.add_option(
Paweł Hajdan, Jrfeb01642017-09-12 15:50:46 +02001970 '--output-deps-files',
1971 help=('Path to the output metadata about DEPS files referenced by '
1972 'recursedeps.'))
1973 parser.add_option(
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02001974 '--pin-all-deps', action='store_true',
1975 help=('Pin all deps, even if not pinned in DEPS. CAVEAT: only does so '
1976 'for checked out deps, NOT deps_os.'))
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001977 options, args = parser.parse_args(args)
1978
Paweł Hajdan, Jr357415c2017-07-24 14:35:28 +02001979 options.do_not_merge_os_specific_entries = True
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001980 options.nohooks = True
Paweł Hajdan, Jr5ec77132017-08-16 19:21:06 +02001981 options.process_all_deps = True
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001982 client = GClient.LoadCurrentConfig(options)
1983
1984 # Only print progress if we're writing to a file. Otherwise, progress updates
1985 # could obscure intended output.
1986 code = client.RunOnDeps('flatten', args, progress=options.output_deps)
1987 if code != 0:
1988 return code
1989
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02001990 flattener = Flattener(client, pin_all_deps=options.pin_all_deps)
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001991
1992 if options.output_deps:
1993 with open(options.output_deps, 'w') as f:
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02001994 f.write(flattener.deps_string)
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001995 else:
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02001996 print(flattener.deps_string)
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001997
Paweł Hajdan, Jrfeb01642017-09-12 15:50:46 +02001998 deps_files = [{'url': d[0], 'deps_file': d[1]}
1999 for d in sorted(flattener.deps_files)]
2000 if options.output_deps_files:
2001 with open(options.output_deps_files, 'w') as f:
2002 json.dump(deps_files, f)
2003
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002004 return 0
2005
2006
Paweł Hajdan, Jr3c2aa832017-06-07 20:22:16 +02002007def _GNSettingsToLines(gn_args_file, gn_args):
2008 s = []
2009 if gn_args_file:
2010 s.extend([
2011 'gclient_gn_args_file = "%s"' % gn_args_file,
2012 'gclient_gn_args = %r' % gn_args,
2013 ])
2014 return s
2015
2016
Paweł Hajdan, Jr6014b562017-06-30 17:43:42 +02002017def _AllowedHostsToLines(allowed_hosts):
2018 """Converts |allowed_hosts| set to list of lines for output."""
2019 if not allowed_hosts:
2020 return []
2021 s = ['allowed_hosts = [']
2022 for h in sorted(allowed_hosts):
2023 s.append(' "%s",' % h)
2024 s.extend([']', ''])
2025 return s
2026
2027
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002028def _DepsToLines(deps):
2029 """Converts |deps| dict to list of lines for output."""
Paweł Hajdan, Jr5b593352017-06-29 18:37:45 +02002030 if not deps:
2031 return []
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002032 s = ['deps = {']
2033 for name, dep in sorted(deps.iteritems()):
Paweł Hajdan, Jr78ce24e2017-10-03 17:09:13 +02002034 condition_part = ([' "condition": %r,' % dep.condition]
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +02002035 if dep.condition else [])
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002036 s.extend([
2037 ' # %s' % dep.hierarchy(include_url=False),
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +02002038 ' "%s": {' % (name,),
Paweł Hajdan, Jrfc6196b2017-07-27 13:15:25 +02002039 ' "url": "%s",' % (dep.raw_url,),
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +02002040 ] + condition_part + [
2041 ' },',
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002042 '',
2043 ])
2044 s.extend(['}', ''])
2045 return s
2046
2047
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02002048def _DepsOsToLines(deps_os):
2049 """Converts |deps_os| dict to list of lines for output."""
Paweł Hajdan, Jr5b593352017-06-29 18:37:45 +02002050 if not deps_os:
2051 return []
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02002052 s = ['deps_os = {']
2053 for dep_os, os_deps in sorted(deps_os.iteritems()):
2054 s.append(' "%s": {' % dep_os)
Paweł Hajdan, Jrad30de62017-06-26 18:51:58 +02002055 for name, dep in sorted(os_deps.iteritems()):
Paweł Hajdan, Jr78ce24e2017-10-03 17:09:13 +02002056 condition_part = ([' "condition": %r,' % dep.condition]
Paweł Hajdan, Jrad30de62017-06-26 18:51:58 +02002057 if dep.condition else [])
2058 s.extend([
2059 ' # %s' % dep.hierarchy(include_url=False),
2060 ' "%s": {' % (name,),
Paweł Hajdan, Jrde86ab32017-08-10 13:55:16 +02002061 ' "url": "%s",' % (dep.raw_url,),
Paweł Hajdan, Jrad30de62017-06-26 18:51:58 +02002062 ] + condition_part + [
2063 ' },',
2064 '',
2065 ])
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02002066 s.extend([' },', ''])
2067 s.extend(['}', ''])
2068 return s
2069
2070
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002071def _HooksToLines(name, hooks):
2072 """Converts |hooks| list to list of lines for output."""
Paweł Hajdan, Jr5b593352017-06-29 18:37:45 +02002073 if not hooks:
2074 return []
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002075 s = ['%s = [' % name]
2076 for dep, hook in hooks:
2077 s.extend([
2078 ' # %s' % dep.hierarchy(include_url=False),
2079 ' {',
2080 ])
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +02002081 if hook.name is not None:
2082 s.append(' "name": "%s",' % hook.name)
2083 if hook.pattern is not None:
2084 s.append(' "pattern": "%s",' % hook.pattern)
Paweł Hajdan, Jrecf53fe2017-09-29 18:28:49 +02002085 if hook.condition is not None:
Paweł Hajdan, Jr78ce24e2017-10-03 17:09:13 +02002086 s.append(' "condition": %r,' % hook.condition)
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002087 s.extend(
Paweł Hajdan, Jrc9364392017-06-14 17:11:56 +02002088 # Hooks run in the parent directory of their dep.
Paweł Hajdan, Jrad30de62017-06-26 18:51:58 +02002089 [' "cwd": "%s",' % os.path.normpath(os.path.dirname(dep.name))] +
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002090 [' "action": ['] +
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +02002091 [' "%s",' % arg for arg in hook.action] +
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002092 [' ]', ' },', '']
2093 )
2094 s.extend([']', ''])
2095 return s
2096
2097
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +02002098def _HooksOsToLines(hooks_os):
2099 """Converts |hooks| list to list of lines for output."""
Paweł Hajdan, Jr5b593352017-06-29 18:37:45 +02002100 if not hooks_os:
2101 return []
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +02002102 s = ['hooks_os = {']
2103 for hook_os, os_hooks in hooks_os.iteritems():
Michael Moss017bcf62017-06-28 15:26:38 -07002104 s.append(' "%s": [' % hook_os)
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +02002105 for dep, hook in os_hooks:
2106 s.extend([
2107 ' # %s' % dep.hierarchy(include_url=False),
2108 ' {',
2109 ])
2110 if hook.name is not None:
2111 s.append(' "name": "%s",' % hook.name)
2112 if hook.pattern is not None:
2113 s.append(' "pattern": "%s",' % hook.pattern)
Paweł Hajdan, Jrecf53fe2017-09-29 18:28:49 +02002114 if hook.condition is not None:
Paweł Hajdan, Jr78ce24e2017-10-03 17:09:13 +02002115 s.append(' "condition": %r,' % hook.condition)
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +02002116 s.extend(
2117 # Hooks run in the parent directory of their dep.
2118 [' "cwd": "%s",' % os.path.normpath(os.path.dirname(dep.name))] +
2119 [' "action": ['] +
2120 [' "%s",' % arg for arg in hook.action] +
2121 [' ]', ' },', '']
2122 )
Michael Moss017bcf62017-06-28 15:26:38 -07002123 s.extend([' ],', ''])
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +02002124 s.extend(['}', ''])
2125 return s
2126
2127
Paweł Hajdan, Jrfb022012017-07-06 18:00:08 +02002128def _VarsToLines(variables):
2129 """Converts |variables| dict to list of lines for output."""
2130 if not variables:
2131 return []
2132 s = ['vars = {']
2133 for key, tup in sorted(variables.iteritems()):
2134 dep, value = tup
2135 s.extend([
2136 ' # %s' % dep.hierarchy(include_url=False),
2137 ' "%s": %r,' % (key, value),
2138 '',
2139 ])
2140 s.extend(['}', ''])
2141 return s
2142
2143
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00002144def CMDgrep(parser, args):
2145 """Greps through git repos managed by gclient.
2146
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002147 Runs 'git grep [args...]' for each module.
2148 """
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00002149 # We can't use optparse because it will try to parse arguments sent
2150 # to git grep and throw an error. :-(
2151 if not args or re.match('(-h|--help)$', args[0]):
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002152 print(
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00002153 'Usage: gclient grep [-j <N>] git-grep-args...\n\n'
2154 'Example: "gclient grep -j10 -A2 RefCountedBase" runs\n"git grep '
2155 '-A2 RefCountedBase" on each of gclient\'s git\nrepos with up to '
2156 '10 jobs.\n\nBonus: page output by appending "|& less -FRSX" to the'
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002157 ' end of your query.',
2158 file=sys.stderr)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00002159 return 1
2160
2161 jobs_arg = ['--jobs=1']
2162 if re.match(r'(-j|--jobs=)\d+$', args[0]):
2163 jobs_arg, args = args[:1], args[1:]
2164 elif re.match(r'(-j|--jobs)$', args[0]):
2165 jobs_arg, args = args[:2], args[2:]
2166
2167 return CMDrecurse(
2168 parser,
2169 jobs_arg + ['--ignore', '--prepend-dir', '--no-progress', '--scm=git',
2170 'git', 'grep', '--null', '--color=Always'] + args)
davidbarr@chromium.org12f944e2012-03-01 02:18:31 +00002171
2172
stip@chromium.orga735da22015-04-29 23:18:20 +00002173def CMDroot(parser, args):
2174 """Outputs the solution root (or current dir if there isn't one)."""
2175 (options, args) = parser.parse_args(args)
2176 client = GClient.LoadCurrentConfig(options)
2177 if client:
2178 print(os.path.abspath(client.root_dir))
2179 else:
2180 print(os.path.abspath('.'))
2181
2182
agablea98a6cd2016-11-15 14:30:10 -08002183@subcommand.usage('[url]')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002184def CMDconfig(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002185 """Creates a .gclient file in the current directory.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00002186
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002187 This specifies the configuration for further commands. After update/sync,
2188 top-level DEPS files in each module are read to determine dependent
2189 modules to operate on as well. If optional [url] parameter is
2190 provided, then configuration is read from a specified Subversion server
2191 URL.
2192 """
szager@chromium.orge2e03202012-07-31 18:05:16 +00002193 # We do a little dance with the --gclientfile option. 'gclient config' is the
2194 # only command where it's acceptable to have both '--gclientfile' and '--spec'
2195 # arguments. So, we temporarily stash any --gclientfile parameter into
2196 # options.output_config_file until after the (gclientfile xor spec) error
2197 # check.
2198 parser.remove_option('--gclientfile')
2199 parser.add_option('--gclientfile', dest='output_config_file',
2200 help='Specify an alternate .gclient file')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002201 parser.add_option('--name',
2202 help='overrides the default name for the solution')
nsylvain@google.comefc80932011-05-31 21:27:56 +00002203 parser.add_option('--deps-file', default='DEPS',
David Benjamin105e11e2017-10-16 10:39:35 -04002204 help='overrides the default name for the DEPS file for the '
nsylvain@google.comefc80932011-05-31 21:27:56 +00002205 'main solutions and all sub-dependencies')
smutae7ea312016-07-18 11:59:41 -07002206 parser.add_option('--unmanaged', action='store_true', default=False,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00002207 help='overrides the default behavior to make it possible '
smutae7ea312016-07-18 11:59:41 -07002208 'to have the main solution untouched by gclient '
2209 '(gclient will check out unmanaged dependencies but '
2210 'will never sync them)')
Paweł Hajdan, Jr3ba2a7c2017-10-04 19:24:46 +02002211 parser.add_option('--custom-var', action='append', dest='custom_vars',
2212 default=[],
2213 help='overrides variables; key=value syntax')
szager@chromium.orge2e03202012-07-31 18:05:16 +00002214 parser.set_defaults(config_filename=None)
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002215 (options, args) = parser.parse_args(args)
szager@chromium.orge2e03202012-07-31 18:05:16 +00002216 if options.output_config_file:
2217 setattr(options, 'config_filename', getattr(options, 'output_config_file'))
maruel@chromium.org5fc2a332010-05-26 19:37:15 +00002218 if ((options.spec and args) or len(args) > 2 or
2219 (not options.spec and not args)):
2220 parser.error('Inconsistent arguments. Use either --spec or one or 2 args')
2221
Paweł Hajdan, Jr3ba2a7c2017-10-04 19:24:46 +02002222 custom_vars = {}
2223 for arg in options.custom_vars:
2224 kv = arg.split('=', 1)
2225 if len(kv) != 2:
2226 parser.error('Invalid --custom-var argument: %r' % arg)
2227 custom_vars[kv[0]] = gclient_eval.EvaluateCondition(kv[1], {})
2228
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002229 client = GClient('.', options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002230 if options.spec:
2231 client.SetConfig(options.spec)
2232 else:
maruel@chromium.org1ab7ffc2009-06-03 17:21:37 +00002233 base_url = args[0].rstrip('/')
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00002234 if not options.name:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002235 name = base_url.split('/')[-1]
nsylvain@google.com12649ef2011-06-01 17:11:20 +00002236 if name.endswith('.git'):
2237 name = name[:-4]
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00002238 else:
2239 # specify an alternate relpath for the given URL.
2240 name = options.name
agable@chromium.orgf2214672015-10-27 21:02:48 +00002241 if not os.path.abspath(os.path.join(os.getcwd(), name)).startswith(
2242 os.getcwd()):
2243 parser.error('Do not pass a relative path for --name.')
2244 if any(x in ('..', '.', '/', '\\') for x in name.split(os.sep)):
2245 parser.error('Do not include relative path components in --name.')
2246
nsylvain@google.comefc80932011-05-31 21:27:56 +00002247 deps_file = options.deps_file
agablea98a6cd2016-11-15 14:30:10 -08002248 client.SetDefaultConfig(name, deps_file, base_url,
smutae7ea312016-07-18 11:59:41 -07002249 managed=not options.unmanaged,
Paweł Hajdan, Jr3ba2a7c2017-10-04 19:24:46 +02002250 cache_dir=options.cache_dir,
2251 custom_vars=custom_vars)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002252 client.SaveConfig()
maruel@chromium.org79692d62010-05-14 18:57:13 +00002253 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002254
2255
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002256@subcommand.epilog("""Example:
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002257 gclient pack > patch.txt
2258 generate simple patch for configured client and dependences
2259""")
2260def CMDpack(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002261 """Generates a patch which can be applied at the root of the tree.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00002262
agabled437d762016-10-17 09:35:11 -07002263 Internally, runs 'git diff' on each checked out module and
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002264 dependencies, and performs minimal postprocessing of the output. The
2265 resulting patch is printed to stdout and can be applied to a freshly
2266 checked out tree via 'patch -p0 < patchfile'.
2267 """
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002268 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2269 help='override deps for the specified (comma-separated) '
2270 'platform(s); \'all\' will process all deps_os '
2271 'references')
haitao.feng@intel.com306080c2012-05-04 13:11:29 +00002272 parser.remove_option('--jobs')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002273 (options, args) = parser.parse_args(args)
iannucci@chromium.org50395ea2013-04-04 04:47:42 +00002274 # Force jobs to 1 so the stdout is not annotated with the thread ids
haitao.feng@intel.com306080c2012-05-04 13:11:29 +00002275 options.jobs = 1
kbr@google.comab318592009-09-04 00:54:55 +00002276 client = GClient.LoadCurrentConfig(options)
2277 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002278 raise gclient_utils.Error('client not configured; see \'gclient config\'')
kbr@google.comab318592009-09-04 00:54:55 +00002279 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002280 client.PrintLocationAndContents()
kbr@google.comab318592009-09-04 00:54:55 +00002281 return client.RunOnDeps('pack', args)
2282
2283
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002284def CMDstatus(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002285 """Shows modification status for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002286 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2287 help='override deps for the specified (comma-separated) '
2288 'platform(s); \'all\' will process all deps_os '
2289 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002290 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002291 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002292 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002293 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002294 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002295 client.PrintLocationAndContents()
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002296 return client.RunOnDeps('status', args)
2297
2298
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002299@subcommand.epilog("""Examples:
maruel@chromium.org79692d62010-05-14 18:57:13 +00002300 gclient sync
2301 update files from SCM according to current configuration,
2302 *for modules which have changed since last update or sync*
2303 gclient sync --force
2304 update files from SCM according to current configuration, for
2305 all modules (useful for recovering files deleted from local copy)
2306 gclient sync --revision src@31000
2307 update src directory to r31000
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002308
2309JSON output format:
2310If the --output-json option is specified, the following document structure will
2311be emitted to the provided file. 'null' entries may occur for subprojects which
2312are present in the gclient solution, but were not processed (due to custom_deps,
2313os_deps, etc.)
2314
2315{
2316 "solutions" : {
2317 "<name>": { # <name> is the posix-normalized path to the solution.
agabled437d762016-10-17 09:35:11 -07002318 "revision": [<git id hex string>|null],
2319 "scm": ["git"|null],
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002320 }
2321 }
2322}
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002323""")
2324def CMDsync(parser, args):
2325 """Checkout/update all modules."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002326 parser.add_option('-f', '--force', action='store_true',
2327 help='force update even for unchanged modules')
2328 parser.add_option('-n', '--nohooks', action='store_true',
2329 help='don\'t run hooks after the update is complete')
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00002330 parser.add_option('-p', '--noprehooks', action='store_true',
2331 help='don\'t run pre-DEPS hooks', default=False)
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002332 parser.add_option('-r', '--revision', action='append',
2333 dest='revisions', metavar='REV', default=[],
2334 help='Enforces revision/hash for the solutions with the '
2335 'format src@rev. The src@ part is optional and can be '
2336 'skipped. -r can be used multiple times when .gclient '
2337 'has multiple solutions configured and will work even '
agablea98a6cd2016-11-15 14:30:10 -08002338 'if the src@ part is skipped.')
maruel@chromium.org794207e2013-03-08 15:29:43 +00002339 parser.add_option('--with_branch_heads', action='store_true',
2340 help='Clone git "branch_heads" refspecs in addition to '
2341 'the default refspecs. This adds about 1/2GB to a '
2342 'full checkout. (git only)')
szager@chromium.org8d3348f2014-08-19 22:49:16 +00002343 parser.add_option('--with_tags', action='store_true',
2344 help='Clone git tags in addition to the default refspecs.')
agable2697cd12016-06-28 10:23:53 -07002345 parser.add_option('-H', '--head', action='store_true',
agablea98a6cd2016-11-15 14:30:10 -08002346 help='DEPRECATED: only made sense with safesync urls.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002347 parser.add_option('-D', '--delete_unversioned_trees', action='store_true',
steveblock@chromium.org98e69452012-02-16 16:36:43 +00002348 help='Deletes from the working copy any dependencies that '
2349 'have been removed since the last sync, as long as '
2350 'there are no local modifications. When used with '
2351 '--force, such dependencies are removed even if they '
2352 'have local modifications. When used with --reset, '
2353 'all untracked directories are removed from the '
tnagel@chromium.orga2aaa632014-02-28 21:47:27 +00002354 'working copy, excluding those which are explicitly '
steveblock@chromium.org98e69452012-02-16 16:36:43 +00002355 'ignored in the repository.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002356 parser.add_option('-R', '--reset', action='store_true',
2357 help='resets any local changes before updating (git only)')
bauerb@chromium.org2aad1b22011-07-22 12:00:41 +00002358 parser.add_option('-M', '--merge', action='store_true',
2359 help='merge upstream changes instead of trying to '
2360 'fast-forward or rebase')
dnj@chromium.org5b23e872015-02-20 21:25:57 +00002361 parser.add_option('-A', '--auto_rebase', action='store_true',
2362 help='Automatically rebase repositories against local '
2363 'checkout during update (git only).')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002364 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2365 help='override deps for the specified (comma-separated) '
2366 'platform(s); \'all\' will process all deps_os '
2367 'references')
Paweł Hajdan, Jr357415c2017-07-24 14:35:28 +02002368 # TODO(phajdan.jr): use argparse.SUPPRESS to hide internal flags.
2369 parser.add_option('--do-not-merge-os-specific-entries', action='store_true',
2370 help='INTERNAL ONLY - disables merging of deps_os and '
2371 'hooks_os to dependencies and hooks')
Paweł Hajdan, Jr5ec77132017-08-16 19:21:06 +02002372 parser.add_option('--process-all-deps', action='store_true',
2373 help='Check out all deps, even for different OS-es, '
2374 'or with conditions evaluating to false')
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +00002375 parser.add_option('--upstream', action='store_true',
2376 help='Make repo state match upstream branch.')
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002377 parser.add_option('--output-json',
2378 help='Output a json document to this path containing '
2379 'summary information about the sync.')
primiano@chromium.org5439ea52014-08-06 17:18:18 +00002380 parser.add_option('--no-history', action='store_true',
2381 help='GIT ONLY - Reduces the size/time of the checkout at '
2382 'the cost of no history. Requires Git 1.9+')
hinoka@chromium.org46b87412014-05-15 00:42:05 +00002383 parser.add_option('--shallow', action='store_true',
2384 help='GIT ONLY - Do a shallow clone into the cache dir. '
2385 'Requires Git 1.9+')
e.hakkinen@samsung.come8bc1aa2015-04-08 08:00:37 +00002386 parser.add_option('--no_bootstrap', '--no-bootstrap',
2387 action='store_true',
2388 help='Don\'t bootstrap from Google Storage.')
Vadim Shtayura08049e22017-10-11 00:14:52 +00002389 parser.add_option('--ignore_locks', action='store_true',
2390 help='GIT ONLY - Ignore cache locks.')
iannucci@chromium.org30a07982016-04-07 21:35:19 +00002391 parser.add_option('--break_repo_locks', action='store_true',
2392 help='GIT ONLY - Forcibly remove repo locks (e.g. '
2393 'index.lock). This should only be used if you know for '
2394 'certain that this invocation of gclient is the only '
2395 'thing operating on the git repos (e.g. on a bot).')
Vadim Shtayura08049e22017-10-11 00:14:52 +00002396 parser.add_option('--lock_timeout', type='int', default=5000,
2397 help='GIT ONLY - Deadline (in seconds) to wait for git '
2398 'cache lock to become available. Default is %default.')
agabled437d762016-10-17 09:35:11 -07002399 # TODO(agable): Remove these when the oldest CrOS release milestone is M56.
2400 parser.add_option('-t', '--transitive', action='store_true',
2401 help='DEPRECATED: This is a no-op.')
sdefresne69b1be12016-10-18 05:48:02 -07002402 parser.add_option('-m', '--manually_grab_svn_rev', action='store_true',
agabled437d762016-10-17 09:35:11 -07002403 help='DEPRECATED: This is a no-op.')
Paweł Hajdan, Jr7c7b5592017-05-23 15:06:05 +02002404 # TODO(phajdan.jr): Remove validation options once default (crbug/570091).
Paweł Hajdan, Jr694773d2017-05-29 16:06:23 +02002405 parser.add_option('--validate-syntax', action='store_true', default=True,
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +02002406 help='Validate the .gclient and DEPS syntax')
Paweł Hajdan, Jr7c7b5592017-05-23 15:06:05 +02002407 parser.add_option('--disable-syntax-validation', action='store_false',
2408 dest='validate_syntax',
2409 help='Disable validation of .gclient and DEPS syntax.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002410 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002411 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002412
2413 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002414 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002415
smutae7ea312016-07-18 11:59:41 -07002416 if options.revisions and options.head:
2417 # TODO(maruel): Make it a parser.error if it doesn't break any builder.
2418 print('Warning: you cannot use both --head and --revision')
2419
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002420 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002421 client.PrintLocationAndContents()
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002422 ret = client.RunOnDeps('update', args)
2423 if options.output_json:
2424 slns = {}
2425 for d in client.subtree(True):
2426 normed = d.name.replace('\\', '/').rstrip('/') + '/'
2427 slns[normed] = {
2428 'revision': d.got_revision,
2429 'scm': d.used_scm.name if d.used_scm else None,
hinoka@chromium.org17db9052014-05-10 01:11:29 +00002430 'url': str(d.url) if d.url else None,
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002431 }
2432 with open(options.output_json, 'wb') as f:
2433 json.dump({'solutions': slns}, f)
2434 return ret
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002435
2436
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002437CMDupdate = CMDsync
2438
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002439
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +02002440def CMDvalidate(parser, args):
2441 """Validates the .gclient and DEPS syntax."""
2442 options, args = parser.parse_args(args)
2443 options.validate_syntax = True
2444 client = GClient.LoadCurrentConfig(options)
2445 rv = client.RunOnDeps('validate', args)
2446 if rv == 0:
2447 print('validate: SUCCESS')
2448 else:
2449 print('validate: FAILURE')
2450 return rv
2451
2452
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002453def CMDdiff(parser, args):
2454 """Displays local diff for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002455 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2456 help='override deps for the specified (comma-separated) '
2457 'platform(s); \'all\' will process all deps_os '
2458 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002459 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002460 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002461 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002462 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002463 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002464 client.PrintLocationAndContents()
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002465 return client.RunOnDeps('diff', args)
2466
2467
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002468def CMDrevert(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002469 """Reverts all modifications in every dependencies.
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00002470
2471 That's the nuclear option to get back to a 'clean' state. It removes anything
agabled437d762016-10-17 09:35:11 -07002472 that shows up in git status."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002473 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2474 help='override deps for the specified (comma-separated) '
2475 'platform(s); \'all\' will process all deps_os '
2476 'references')
2477 parser.add_option('-n', '--nohooks', action='store_true',
2478 help='don\'t run hooks after the revert is complete')
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00002479 parser.add_option('-p', '--noprehooks', action='store_true',
2480 help='don\'t run pre-DEPS hooks', default=False)
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +00002481 parser.add_option('--upstream', action='store_true',
2482 help='Make repo state match upstream branch.')
iannucci@chromium.orgbf525dc2016-04-07 22:00:28 +00002483 parser.add_option('--break_repo_locks', action='store_true',
2484 help='GIT ONLY - Forcibly remove repo locks (e.g. '
2485 'index.lock). This should only be used if you know for '
2486 'certain that this invocation of gclient is the only '
2487 'thing operating on the git repos (e.g. on a bot).')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002488 (options, args) = parser.parse_args(args)
2489 # --force is implied.
2490 options.force = True
steveblock@chromium.org98e69452012-02-16 16:36:43 +00002491 options.reset = False
2492 options.delete_unversioned_trees = False
agablec903d732016-07-26 09:07:24 -07002493 options.merge = False
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002494 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002495 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002496 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002497 return client.RunOnDeps('revert', args)
2498
2499
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002500def CMDrunhooks(parser, args):
2501 """Runs hooks for files that have been modified in the local working copy."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002502 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2503 help='override deps for the specified (comma-separated) '
2504 'platform(s); \'all\' will process all deps_os '
2505 'references')
2506 parser.add_option('-f', '--force', action='store_true', default=True,
2507 help='Deprecated. No effect.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002508 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002509 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002510 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002511 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002512 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002513 client.PrintLocationAndContents()
maruel@chromium.org5df6a462009-08-28 18:52:26 +00002514 options.force = True
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002515 options.nohooks = False
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002516 return client.RunOnDeps('runhooks', args)
2517
2518
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002519def CMDrevinfo(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002520 """Outputs revision info mapping for the client and its dependencies.
maruel@chromium.org9eda4112010-06-11 18:56:10 +00002521
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002522 This allows the capture of an overall 'revision' for the source tree that
maruel@chromium.org9eda4112010-06-11 18:56:10 +00002523 can be used to reproduce the same tree in the future. It is only useful for
agabled437d762016-10-17 09:35:11 -07002524 'unpinned dependencies', i.e. DEPS/deps references without a git hash.
2525 A git branch name isn't 'pinned' since the actual commit can change.
maruel@chromium.org9eda4112010-06-11 18:56:10 +00002526 """
2527 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2528 help='override deps for the specified (comma-separated) '
2529 'platform(s); \'all\' will process all deps_os '
2530 'references')
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00002531 parser.add_option('-a', '--actual', action='store_true',
2532 help='gets the actual checked out revisions instead of the '
2533 'ones specified in the DEPS and .gclient files')
maruel@chromium.org9eda4112010-06-11 18:56:10 +00002534 parser.add_option('-s', '--snapshot', action='store_true',
2535 help='creates a snapshot .gclient file of the current '
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00002536 'version of all repositories to reproduce the tree, '
2537 'implies -a')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002538 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002539 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002540 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002541 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002542 client.PrintRevInfo()
maruel@chromium.org79692d62010-05-14 18:57:13 +00002543 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002544
2545
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00002546def CMDverify(parser, args):
2547 """Verifies the DEPS file deps are only from allowed_hosts."""
2548 (options, args) = parser.parse_args(args)
2549 client = GClient.LoadCurrentConfig(options)
2550 if not client:
2551 raise gclient_utils.Error('client not configured; see \'gclient config\'')
2552 client.RunOnDeps(None, [])
2553 # Look at each first-level dependency of this gclient only.
2554 for dep in client.dependencies:
2555 bad_deps = dep.findDepsFromNotAllowedHosts()
2556 if not bad_deps:
2557 continue
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002558 print("There are deps from not allowed hosts in file %s" % dep.deps_file)
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00002559 for bad_dep in bad_deps:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002560 print("\t%s at %s" % (bad_dep.name, bad_dep.url))
2561 print("allowed_hosts:", ', '.join(dep.allowed_hosts))
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00002562 sys.stdout.flush()
2563 raise gclient_utils.Error(
2564 'dependencies from disallowed hosts; check your DEPS file.')
2565 return 0
2566
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002567class OptionParser(optparse.OptionParser):
szager@chromium.orge2e03202012-07-31 18:05:16 +00002568 gclientfile_default = os.environ.get('GCLIENT_FILE', '.gclient')
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002569
2570 def __init__(self, **kwargs):
2571 optparse.OptionParser.__init__(
2572 self, version='%prog ' + __version__, **kwargs)
2573
2574 # Some arm boards have issues with parallel sync.
2575 if platform.machine().startswith('arm'):
2576 jobs = 1
2577 else:
2578 jobs = max(8, gclient_utils.NumLocalCpus())
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002579
2580 self.add_option(
2581 '-j', '--jobs', default=jobs, type='int',
2582 help='Specify how many SCM commands can run in parallel; defaults to '
tnagel@chromium.orga2aaa632014-02-28 21:47:27 +00002583 '%default on this machine')
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002584 self.add_option(
2585 '-v', '--verbose', action='count', default=0,
2586 help='Produces additional output for diagnostics. Can be used up to '
2587 'three times for more logging info.')
2588 self.add_option(
2589 '--gclientfile', dest='config_filename',
2590 help='Specify an alternate %s file' % self.gclientfile_default)
2591 self.add_option(
2592 '--spec',
2593 help='create a gclient file containing the provided string. Due to '
2594 'Cygwin/Python brokenness, it can\'t contain any newlines.')
2595 self.add_option(
Aleksandr Derbenev9e8fb0e2017-08-01 20:18:31 +03002596 '--cache-dir',
2597 help='(git only) Cache all git repos into this dir and do '
2598 'shared clones from the cache, instead of cloning '
2599 'directly from the remote. (experimental)',
2600 default=os.environ.get('GCLIENT_CACHE_DIR'))
2601 self.add_option(
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002602 '--no-nag-max', default=False, action='store_true',
scottmg@chromium.orgf547c802013-09-27 17:55:26 +00002603 help='Ignored for backwards compatibility.')
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002604
2605 def parse_args(self, args=None, values=None):
2606 """Integrates standard options processing."""
2607 options, args = optparse.OptionParser.parse_args(self, args, values)
2608 levels = [logging.ERROR, logging.WARNING, logging.INFO, logging.DEBUG]
2609 logging.basicConfig(
2610 level=levels[min(options.verbose, len(levels) - 1)],
maruel@chromium.org0895b752011-08-26 20:40:33 +00002611 format='%(module)s(%(lineno)d) %(funcName)s:%(message)s')
szager@chromium.orge2e03202012-07-31 18:05:16 +00002612 if options.config_filename and options.spec:
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002613 self.error('Cannot specifiy both --gclientfile and --spec')
rdsmith@chromium.orgd9591f02014-02-05 19:28:20 +00002614 if (options.config_filename and
2615 options.config_filename != os.path.basename(options.config_filename)):
2616 self.error('--gclientfile target must be a filename, not a path')
szager@chromium.orge2e03202012-07-31 18:05:16 +00002617 if not options.config_filename:
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002618 options.config_filename = self.gclientfile_default
maruel@chromium.org0895b752011-08-26 20:40:33 +00002619 options.entries_filename = options.config_filename + '_entries'
2620 if options.jobs < 1:
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002621 self.error('--jobs must be 1 or higher')
maruel@chromium.org0895b752011-08-26 20:40:33 +00002622
2623 # These hacks need to die.
2624 if not hasattr(options, 'revisions'):
2625 # GClient.RunOnDeps expects it even if not applicable.
2626 options.revisions = []
smutae7ea312016-07-18 11:59:41 -07002627 if not hasattr(options, 'head'):
2628 options.head = None
maruel@chromium.org0895b752011-08-26 20:40:33 +00002629 if not hasattr(options, 'nohooks'):
2630 options.nohooks = True
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00002631 if not hasattr(options, 'noprehooks'):
2632 options.noprehooks = True
maruel@chromium.org0895b752011-08-26 20:40:33 +00002633 if not hasattr(options, 'deps_os'):
2634 options.deps_os = None
maruel@chromium.org0895b752011-08-26 20:40:33 +00002635 if not hasattr(options, 'force'):
2636 options.force = None
2637 return (options, args)
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002638
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002639
2640def disable_buffering():
2641 # Make stdout auto-flush so buildbot doesn't kill us during lengthy
2642 # operations. Python as a strong tendency to buffer sys.stdout.
2643 sys.stdout = gclient_utils.MakeFileAutoFlush(sys.stdout)
2644 # Make stdout annotated with the thread ids.
2645 sys.stdout = gclient_utils.MakeFileAnnotated(sys.stdout)
maruel@chromium.org0895b752011-08-26 20:40:33 +00002646
2647
sbc@chromium.org013731e2015-02-26 18:28:43 +00002648def main(argv):
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002649 """Doesn't parse the arguments here, just find the right subcommand to
2650 execute."""
maruel@chromium.org82798cb2012-02-23 18:16:12 +00002651 if sys.hexversion < 0x02060000:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002652 print(
maruel@chromium.org82798cb2012-02-23 18:16:12 +00002653 '\nYour python version %s is unsupported, please upgrade.\n' %
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002654 sys.version.split(' ', 1)[0],
2655 file=sys.stderr)
maruel@chromium.org82798cb2012-02-23 18:16:12 +00002656 return 2
bcwhite@chromium.org6683ab42013-02-11 16:13:47 +00002657 if not sys.executable:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002658 print(
2659 '\nPython cannot find the location of it\'s own executable.\n',
2660 file=sys.stderr)
bcwhite@chromium.org6683ab42013-02-11 16:13:47 +00002661 return 2
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002662 fix_encoding.fix_encoding()
2663 disable_buffering()
iannucci@chromium.org596cd5c2016-04-04 21:34:39 +00002664 setup_color.init()
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002665 dispatcher = subcommand.CommandDispatcher(__name__)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00002666 try:
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002667 return dispatcher.execute(OptionParser(), argv)
xusydoc@chromium.org2fd6c3f2013-05-03 21:57:55 +00002668 except KeyboardInterrupt:
2669 gclient_utils.GClientChildren.KillAllRemainingChildren()
2670 raise
vapier@chromium.orga81a56e2015-11-11 07:56:13 +00002671 except (gclient_utils.Error, subprocess2.CalledProcessError) as e:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002672 print('Error: %s' % str(e), file=sys.stderr)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00002673 return 1
borenet@google.com6a9b1682014-03-24 18:35:23 +00002674 finally:
2675 gclient_utils.PrintWarnings()
sbc@chromium.org013731e2015-02-26 18:28:43 +00002676 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002677
2678
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00002679if '__main__' == __name__:
sbc@chromium.org013731e2015-02-26 18:28:43 +00002680 try:
2681 sys.exit(main(sys.argv[1:]))
2682 except KeyboardInterrupt:
2683 sys.stderr.write('interrupted\n')
2684 sys.exit(1)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002685
2686# vim: ts=2:sw=2:tw=80:et: