blob: 8d449c79a3b3b0686db08c63504ac4583a4755d4 [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,
143 variables=None):
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
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200160
161 @staticmethod
Paweł Hajdan, Jr032d5452017-06-22 20:43:53 +0200162 def from_dict(d, variables=None):
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200163 """Creates a Hook instance from a dict like in the DEPS file."""
Paweł Hajdan, Jr032d5452017-06-22 20:43:53 +0200164 return Hook(
165 d['action'],
166 d.get('pattern'),
167 d.get('name'),
168 d.get('cwd'),
169 d.get('condition'),
170 variables=variables)
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200171
172 @property
173 def action(self):
174 return self._action
175
176 @property
177 def pattern(self):
178 return self._pattern
179
180 @property
181 def name(self):
182 return self._name
183
Paweł Hajdan, Jrecf53fe2017-09-29 18:28:49 +0200184 @property
185 def condition(self):
186 return self._condition
187
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200188 def matches(self, file_list):
189 """Returns true if the pattern matches any of files in the list."""
190 if not self._pattern:
191 return True
192 pattern = re.compile(self._pattern)
193 return bool([f for f in file_list if pattern.search(f)])
194
195 def run(self, root):
Paweł Hajdan, Jr032d5452017-06-22 20:43:53 +0200196 """Executes the hook's command (provided the condition is met)."""
197 if (self._condition and
198 not gclient_eval.EvaluateCondition(self._condition, self._variables)):
199 return
200
Paweł Hajdan, Jrfc6196b2017-07-27 13:15:25 +0200201 cmd = [arg.format(**self._variables) for arg in self._action]
202
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200203 if cmd[0] == 'python':
204 # If the hook specified "python" as the first item, the action is a
205 # Python script. Run it by starting a new copy of the same
206 # interpreter.
207 cmd[0] = sys.executable
Paweł Hajdan, Jrc9364392017-06-14 17:11:56 +0200208
209 cwd = root
210 if self._cwd:
211 cwd = os.path.join(cwd, self._cwd)
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200212 try:
213 start_time = time.time()
214 gclient_utils.CheckCallAndFilterAndHeader(
Paweł Hajdan, Jrc9364392017-06-14 17:11:56 +0200215 cmd, cwd=cwd, always=True)
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200216 except (gclient_utils.Error, subprocess2.CalledProcessError) as e:
217 # Use a discrete exit status code of 2 to indicate that a hook action
218 # failed. Users of this script may wish to treat hook action failures
219 # differently from VC failures.
220 print('Error: %s' % str(e), file=sys.stderr)
221 sys.exit(2)
222 finally:
223 elapsed_time = time.time() - start_time
224 if elapsed_time > 10:
225 print("Hook '%s' took %.2f secs" % (
226 gclient_utils.CommandToStr(cmd), elapsed_time))
227
228
Paweł Hajdan, Jrfc6196b2017-07-27 13:15:25 +0200229class DependencySettings(object):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000230 """Immutable configuration settings."""
231 def __init__(
Paweł Hajdan, Jrfc6196b2017-07-27 13:15:25 +0200232 self, parent, raw_url, url, managed, custom_deps, custom_vars,
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +0200233 custom_hooks, deps_file, should_process, relative,
234 condition, condition_value):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000235 # These are not mutable:
236 self._parent = parent
mmoss@chromium.org8f93f792014-08-26 23:24:09 +0000237 self._deps_file = deps_file
Paweł Hajdan, Jrfc6196b2017-07-27 13:15:25 +0200238 self._raw_url = raw_url
maruel@chromium.org064186c2011-09-27 23:53:33 +0000239 self._url = url
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +0200240 # The condition as string (or None). Useful to keep e.g. for flatten.
241 self._condition = condition
242 # Boolean value of the condition. If there's no condition, just True.
243 self._condition_value = condition_value
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000244 # 'managed' determines whether or not this dependency is synced/updated by
245 # gclient after gclient checks it out initially. The difference between
246 # 'managed' and 'should_process' is that the user specifies 'managed' via
smutae7ea312016-07-18 11:59:41 -0700247 # the --unmanaged command-line flag or a .gclient config, where
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000248 # 'should_process' is dynamically set by gclient if it goes over its
249 # recursion limit and controls gclient's behavior so it does not misbehave.
250 self._managed = managed
251 self._should_process = should_process
agabledce6ddc2016-09-08 10:02:16 -0700252 # If this is a recursed-upon sub-dependency, and the parent has
253 # use_relative_paths set, then this dependency should check out its own
254 # dependencies relative to that parent's path for this, rather than
255 # relative to the .gclient file.
256 self._relative = relative
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +0000257 # This is a mutable value which has the list of 'target_os' OSes listed in
258 # the current deps file.
259 self.local_target_os = None
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000260
261 # These are only set in .gclient and not in DEPS files.
262 self._custom_vars = custom_vars or {}
263 self._custom_deps = custom_deps or {}
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000264 self._custom_hooks = custom_hooks or []
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000265
maruel@chromium.org064186c2011-09-27 23:53:33 +0000266 # Post process the url to remove trailing slashes.
267 if isinstance(self._url, basestring):
268 # urls are sometime incorrectly written as proto://host/path/@rev. Replace
269 # it to proto://host/path@rev.
maruel@chromium.org064186c2011-09-27 23:53:33 +0000270 self._url = self._url.replace('/@', '@')
Paweł Hajdan, Jr7e9303b2017-05-23 14:38:27 +0200271 elif not isinstance(self._url, (None.__class__)):
maruel@chromium.org064186c2011-09-27 23:53:33 +0000272 raise gclient_utils.Error(
Paweł Hajdan, Jr7e9303b2017-05-23 14:38:27 +0200273 ('dependency url must be either string or None, '
274 'instead of %s') % self._url.__class__.__name__)
mmoss@chromium.orgd0b272b2013-01-30 23:55:33 +0000275 # Make any deps_file path platform-appropriate.
276 for sep in ['/', '\\']:
277 self._deps_file = self._deps_file.replace(sep, os.sep)
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000278
279 @property
280 def deps_file(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000281 return self._deps_file
282
283 @property
284 def managed(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000285 return self._managed
286
287 @property
288 def parent(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000289 return self._parent
290
291 @property
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000292 def root(self):
293 """Returns the root node, a GClient object."""
294 if not self.parent:
295 # This line is to signal pylint that it could be a GClient instance.
296 return self or GClient(None, None)
297 return self.parent.root
298
299 @property
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000300 def should_process(self):
301 """True if this dependency should be processed, i.e. checked out."""
302 return self._should_process
303
304 @property
305 def custom_vars(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000306 return self._custom_vars.copy()
307
308 @property
309 def custom_deps(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000310 return self._custom_deps.copy()
311
maruel@chromium.org064186c2011-09-27 23:53:33 +0000312 @property
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000313 def custom_hooks(self):
314 return self._custom_hooks[:]
315
316 @property
Paweł Hajdan, Jrfc6196b2017-07-27 13:15:25 +0200317 def raw_url(self):
318 """URL before variable expansion."""
319 return self._raw_url
320
321 @property
maruel@chromium.org064186c2011-09-27 23:53:33 +0000322 def url(self):
Paweł Hajdan, Jrfc6196b2017-07-27 13:15:25 +0200323 """URL after variable expansion."""
maruel@chromium.org064186c2011-09-27 23:53:33 +0000324 return self._url
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000325
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000326 @property
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +0200327 def condition(self):
328 return self._condition
329
330 @property
331 def condition_value(self):
332 return self._condition_value
333
334 @property
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +0000335 def target_os(self):
336 if self.local_target_os is not None:
337 return tuple(set(self.local_target_os).union(self.parent.target_os))
338 else:
339 return self.parent.target_os
340
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000341 def get_custom_deps(self, name, url):
342 """Returns a custom deps if applicable."""
343 if self.parent:
344 url = self.parent.get_custom_deps(name, url)
345 # None is a valid return value to disable a dependency.
346 return self.custom_deps.get(name, url)
347
maruel@chromium.org064186c2011-09-27 23:53:33 +0000348
349class Dependency(gclient_utils.WorkItem, DependencySettings):
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000350 """Object that represents a dependency checkout."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000351
Paweł Hajdan, Jrfc6196b2017-07-27 13:15:25 +0200352 def __init__(self, parent, name, raw_url, url, managed, custom_deps,
agabledce6ddc2016-09-08 10:02:16 -0700353 custom_vars, custom_hooks, deps_file, should_process,
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +0200354 relative, condition, condition_value):
maruel@chromium.org6ca8bf82011-09-19 23:04:30 +0000355 gclient_utils.WorkItem.__init__(self, name)
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000356 DependencySettings.__init__(
Paweł Hajdan, Jrfc6196b2017-07-27 13:15:25 +0200357 self, parent, raw_url, url, managed, custom_deps, custom_vars,
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +0200358 custom_hooks, deps_file, should_process, relative,
359 condition, condition_value)
maruel@chromium.org68988972011-09-20 14:11:42 +0000360
361 # This is in both .gclient and DEPS files:
maruel@chromium.org064186c2011-09-27 23:53:33 +0000362 self._deps_hooks = []
maruel@chromium.org68988972011-09-20 14:11:42 +0000363
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000364 self._pre_deps_hooks = []
365
maruel@chromium.org68988972011-09-20 14:11:42 +0000366 # Calculates properties:
maruel@chromium.org064186c2011-09-27 23:53:33 +0000367 self._parsed_url = None
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +0000368 self._dependencies = []
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +0200369 self._vars = {}
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +0200370 self._os_dependencies = {}
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +0200371 self._os_deps_hooks = {}
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200372
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000373 # A cache of the files affected by the current operation, necessary for
374 # hooks.
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000375 self._file_list = []
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000376 # List of host names from which dependencies are allowed.
377 # Default is an empty set, meaning unspecified in DEPS file, and hence all
378 # hosts will be allowed. Non-empty set means whitelist of hosts.
379 # allowed_hosts var is scoped to its DEPS file, and so it isn't recursive.
380 self._allowed_hosts = frozenset()
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +0200381 # Spec for .gni output to write (if any).
382 self._gn_args_file = None
383 self._gn_args = []
maruel@chromium.org85c2a192010-07-22 21:14:43 +0000384 # If it is not set to True, the dependency wasn't processed for its child
385 # dependency, i.e. its DEPS wasn't read.
maruel@chromium.org064186c2011-09-27 23:53:33 +0000386 self._deps_parsed = False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000387 # This dependency has been processed, i.e. checked out
maruel@chromium.org064186c2011-09-27 23:53:33 +0000388 self._processed = False
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000389 # This dependency had its pre-DEPS hooks run
390 self._pre_deps_hooks_ran = False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000391 # This dependency had its hook run
maruel@chromium.org064186c2011-09-27 23:53:33 +0000392 self._hooks_ran = False
kustermann@google.coma692e8f2013-04-18 08:32:04 +0000393 # This is the scm used to checkout self.url. It may be used by dependencies
394 # to get the datetime of the revision we checked out.
395 self._used_scm = None
szager@chromium.org4ad264b2014-05-20 04:43:47 +0000396 self._used_revision = None
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +0000397 # The actual revision we ended up getting, or None if that information is
398 # unavailable
399 self._got_revision = None
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000400
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000401 # This is a mutable value that overrides the normal recursion limit for this
402 # dependency. It is read from the actual DEPS file so cannot be set on
403 # class instantiation.
404 self.recursion_override = None
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000405 # recursedeps is a mutable value that selectively overrides the default
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000406 # 'no recursion' setting on a dep-by-dep basis. It will replace
407 # recursion_override.
iannucci@chromium.orgafa11ac2016-05-04 22:17:34 +0000408 #
409 # It will be a dictionary of {deps_name: {"deps_file": depfile_name}} or
410 # None.
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000411 self.recursedeps = None
hinoka885e5b12016-06-08 14:40:09 -0700412 # This is inherited from WorkItem. We want the URL to be a resource.
413 if url and isinstance(url, basestring):
414 # The url is usually given to gclient either as https://blah@123
qyearsley12fa6ff2016-08-24 09:18:40 -0700415 # or just https://blah. The @123 portion is irrelevant.
hinoka885e5b12016-06-08 14:40:09 -0700416 self.resources.append(url.split('@')[0])
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000417
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000418 if not self.name and self.parent:
419 raise gclient_utils.Error('Dependency without name')
420
maruel@chromium.org470b5432011-10-11 18:18:19 +0000421 @property
422 def requirements(self):
423 """Calculate the list of requirements."""
424 requirements = set()
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000425 # self.parent is implicitly a requirement. This will be recursive by
426 # definition.
427 if self.parent and self.parent.name:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000428 requirements.add(self.parent.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000429
430 # For a tree with at least 2 levels*, the leaf node needs to depend
431 # on the level higher up in an orderly way.
432 # This becomes messy for >2 depth as the DEPS file format is a dictionary,
433 # thus unsorted, while the .gclient format is a list thus sorted.
434 #
435 # * _recursion_limit is hard coded 2 and there is no hope to change this
436 # value.
437 #
438 # Interestingly enough, the following condition only works in the case we
439 # want: self is a 2nd level node. 3nd level node wouldn't need this since
440 # they already have their parent as a requirement.
maruel@chromium.org470b5432011-10-11 18:18:19 +0000441 if self.parent and self.parent.parent and not self.parent.parent.parent:
442 requirements |= set(i.name for i in self.root.dependencies if i.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000443
maruel@chromium.org470b5432011-10-11 18:18:19 +0000444 if self.name:
445 requirements |= set(
446 obj.name for obj in self.root.subtree(False)
447 if (obj is not self
448 and obj.name and
449 self.name.startswith(posixpath.join(obj.name, ''))))
450 requirements = tuple(sorted(requirements))
451 logging.info('Dependency(%s).requirements = %s' % (self.name, requirements))
452 return requirements
453
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000454 @property
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000455 def try_recursedeps(self):
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000456 """Returns False if recursion_override is ever specified."""
457 if self.recursion_override is not None:
458 return False
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000459 return self.parent.try_recursedeps
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000460
461 @property
462 def recursion_limit(self):
463 """Returns > 0 if this dependency is not too recursed to be processed."""
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000464 # We continue to support the absence of recursedeps until tools and DEPS
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000465 # using recursion_override are updated.
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000466 if self.try_recursedeps and self.parent.recursedeps != None:
467 if self.name in self.parent.recursedeps:
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000468 return 1
469
470 if self.recursion_override is not None:
471 return self.recursion_override
472 return max(self.parent.recursion_limit - 1, 0)
473
maruel@chromium.org470b5432011-10-11 18:18:19 +0000474 def verify_validity(self):
475 """Verifies that this Dependency is fine to add as a child of another one.
476
477 Returns True if this entry should be added, False if it is a duplicate of
478 another entry.
479 """
480 logging.info('Dependency(%s).verify_validity()' % self.name)
481 if self.name in [s.name for s in self.parent.dependencies]:
482 raise gclient_utils.Error(
483 'The same name "%s" appears multiple times in the deps section' %
484 self.name)
485 if not self.should_process:
486 # Return early, no need to set requirements.
487 return True
488
489 # This require a full tree traversal with locks.
490 siblings = [d for d in self.root.subtree(False) if d.name == self.name]
491 for sibling in siblings:
maruel@chromium.orgb848d5b2012-10-10 23:25:50 +0000492 self_url = self.LateOverride(self.url)
493 sibling_url = sibling.LateOverride(sibling.url)
494 # Allow to have only one to be None or ''.
495 if self_url != sibling_url and bool(self_url) == bool(sibling_url):
maruel@chromium.org470b5432011-10-11 18:18:19 +0000496 raise gclient_utils.Error(
maruel@chromium.orgb848d5b2012-10-10 23:25:50 +0000497 ('Dependency %s specified more than once:\n'
498 ' %s [%s]\n'
499 'vs\n'
500 ' %s [%s]') % (
501 self.name,
502 sibling.hierarchy(),
503 sibling_url,
504 self.hierarchy(),
505 self_url))
maruel@chromium.org470b5432011-10-11 18:18:19 +0000506 # In theory we could keep it as a shadow of the other one. In
507 # practice, simply ignore it.
508 logging.warn('Won\'t process duplicate dependency %s' % sibling)
509 return False
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000510 return True
maruel@chromium.org064186c2011-09-27 23:53:33 +0000511
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000512 def LateOverride(self, url):
Paweł Hajdan, Jr7e9303b2017-05-23 14:38:27 +0200513 """Resolves the parsed url from url."""
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000514 assert self.parsed_url == None or not self.should_process, self.parsed_url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000515 parsed_url = self.get_custom_deps(self.name, url)
516 if parsed_url != url:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000517 logging.info(
518 'Dependency(%s).LateOverride(%s) -> %s' %
519 (self.name, url, parsed_url))
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000520 return parsed_url
521
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000522 if isinstance(url, basestring):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000523 parsed_url = urlparse.urlparse(url)
scr@chromium.orgf1eccaf2014-04-11 15:51:33 +0000524 if (not parsed_url[0] and
525 not re.match(r'^\w+\@[\w\.-]+\:[\w\/]+', parsed_url[2])):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000526 # A relative url. Fetch the real base.
527 path = parsed_url[2]
528 if not path.startswith('/'):
529 raise gclient_utils.Error(
530 'relative DEPS entry \'%s\' must begin with a slash' % url)
531 # Create a scm just to query the full url.
532 parent_url = self.parent.parsed_url
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000533 scm = gclient_scm.CreateSCM(
534 parent_url, self.root.root_dir, None, self.outbuf)
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000535 parsed_url = scm.FullUrlForRelativeUrl(url)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000536 else:
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000537 parsed_url = url
maruel@chromium.org470b5432011-10-11 18:18:19 +0000538 logging.info(
539 'Dependency(%s).LateOverride(%s) -> %s' %
540 (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000541 return parsed_url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000542
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000543 if url is None:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000544 logging.info(
545 'Dependency(%s).LateOverride(%s) -> %s' % (self.name, url, url))
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000546 return url
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000547
548 raise gclient_utils.Error('Unknown url type')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000549
bratell@opera.comed2b4fe2013-12-16 14:34:12 +0000550 @staticmethod
Paweł Hajdan, Jr5ec77132017-08-16 19:21:06 +0200551 def MergeWithOsDeps(deps, deps_os, target_os_list, process_all_deps):
bratell@opera.comed2b4fe2013-12-16 14:34:12 +0000552 """Returns a new "deps" structure that is the deps sent in updated
553 with information from deps_os (the deps_os section of the DEPS
554 file) that matches the list of target os."""
bratell@opera.comed2b4fe2013-12-16 14:34:12 +0000555 new_deps = deps.copy()
Paweł Hajdan, Jrfd0057e2017-06-21 14:20:21 +0200556 for dep_os, os_deps in deps_os.iteritems():
557 for key, value in os_deps.iteritems():
558 if value is None:
559 # Make this condition very visible, so it's not a silent failure.
560 # It's unclear how to support None override in deps_os.
561 logging.error('Ignoring %r:%r in %r deps_os', key, value, dep_os)
562 continue
563
564 # Normalize value to be a dict which contains |should_process| metadata.
565 if isinstance(value, basestring):
566 value = {'url': value}
567 assert isinstance(value, collections.Mapping), (key, value)
Paweł Hajdan, Jr5ec77132017-08-16 19:21:06 +0200568 value['should_process'] = dep_os in target_os_list or process_all_deps
Paweł Hajdan, Jrfd0057e2017-06-21 14:20:21 +0200569
570 # Handle collisions/overrides.
571 if key in new_deps and new_deps[key] != value:
572 # Normalize the existing new_deps entry.
573 if isinstance(new_deps[key], basestring):
574 new_deps[key] = {'url': new_deps[key]}
575 assert isinstance(new_deps[key],
576 collections.Mapping), (key, new_deps[key])
577
578 # It's OK if the "override" sets the key to the same value.
579 # This is mostly for legacy reasons to keep existing DEPS files
580 # working. Often mac/ios and unix/android will do this.
581 if value['url'] != new_deps[key]['url']:
582 raise gclient_utils.Error(
583 ('Value from deps_os (%r; %r: %r) conflicts with existing deps '
584 'entry (%r).') % (dep_os, key, value, new_deps[key]))
585
586 # We'd otherwise overwrite |should_process| metadata, but a dep should
587 # be processed if _any_ of its references call for that.
588 value['should_process'] = (
589 value['should_process'] or
590 new_deps[key].get('should_process', True))
591
592 new_deps[key] = value
593
bratell@opera.comed2b4fe2013-12-16 14:34:12 +0000594 return new_deps
595
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200596 def _postprocess_deps(self, deps, rel_prefix):
597 """Performs post-processing of deps compared to what's in the DEPS file."""
Paweł Hajdan, Jr4426eaf2017-06-13 12:25:47 +0200598 # Make sure the dict is mutable, e.g. in case it's frozen.
599 deps = dict(deps)
600
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200601 # If a line is in custom_deps, but not in the solution, we want to append
602 # this line to the solution.
603 for d in self.custom_deps:
604 if d not in deps:
605 deps[d] = self.custom_deps[d]
606
607 if rel_prefix:
608 logging.warning('use_relative_paths enabled.')
609 rel_deps = {}
610 for d, url in deps.items():
611 # normpath is required to allow DEPS to use .. in their
612 # dependency local path.
613 rel_deps[os.path.normpath(os.path.join(rel_prefix, d))] = url
614 logging.warning('Updating deps by prepending %s.', rel_prefix)
615 deps = rel_deps
616
617 return deps
618
619 def _deps_to_objects(self, deps, use_relative_paths):
620 """Convert a deps dict to a dict of Dependency objects."""
621 deps_to_add = []
622 for name, dep_value in deps.iteritems():
623 should_process = self.recursion_limit and self.should_process
624 deps_file = self.deps_file
625 if self.recursedeps is not None:
626 ent = self.recursedeps.get(name)
627 if ent is not None:
628 deps_file = ent['deps_file']
629 if dep_value is None:
630 continue
631 condition = None
632 condition_value = True
633 if isinstance(dep_value, basestring):
Paweł Hajdan, Jrfc6196b2017-07-27 13:15:25 +0200634 raw_url = dep_value
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200635 else:
636 # This should be guaranteed by schema checking in gclient_eval.
637 assert isinstance(dep_value, collections.Mapping)
Paweł Hajdan, Jrfc6196b2017-07-27 13:15:25 +0200638 raw_url = dep_value['url']
Paweł Hajdan, Jrfd0057e2017-06-21 14:20:21 +0200639 # Take into account should_process metadata set by MergeWithOsDeps.
640 should_process = (should_process and
641 dep_value.get('should_process', True))
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200642 condition = dep_value.get('condition')
Paweł Hajdan, Jrfc6196b2017-07-27 13:15:25 +0200643
644 url = raw_url.format(**self.get_vars())
645
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200646 if condition:
Paweł Hajdan, Jrd3790252017-07-03 21:06:24 +0200647 condition_value = gclient_eval.EvaluateCondition(
648 condition, self.get_vars())
Paweł Hajdan, Jr5ec77132017-08-16 19:21:06 +0200649 if not self._get_option('process_all_deps', False):
650 should_process = should_process and condition_value
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200651 deps_to_add.append(Dependency(
Paweł Hajdan, Jrfc6196b2017-07-27 13:15:25 +0200652 self, name, raw_url, url, None, None, self.custom_vars, None,
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200653 deps_file, should_process, use_relative_paths, condition,
654 condition_value))
655 deps_to_add.sort(key=lambda x: x.name)
656 return deps_to_add
657
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000658 def ParseDepsFile(self):
maruel@chromium.org271375b2010-06-23 19:17:38 +0000659 """Parses the DEPS file for this dependency."""
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000660 assert not self.deps_parsed
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000661 assert not self.dependencies
iannucci@chromium.org0c3f3052014-05-22 00:29:22 +0000662
663 deps_content = None
cmp@chromium.org76ce73c2014-07-02 00:13:18 +0000664
665 # First try to locate the configured deps file. If it's missing, fallback
666 # to DEPS.
667 deps_files = [self.deps_file]
668 if 'DEPS' not in deps_files:
669 deps_files.append('DEPS')
670 for deps_file in deps_files:
671 filepath = os.path.join(self.root.root_dir, self.name, deps_file)
672 if os.path.isfile(filepath):
673 logging.info(
674 'ParseDepsFile(%s): %s file found at %s', self.name, deps_file,
675 filepath)
676 break
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000677 logging.info(
cmp@chromium.org76ce73c2014-07-02 00:13:18 +0000678 'ParseDepsFile(%s): No %s file found at %s', self.name, deps_file,
679 filepath)
680
681 if os.path.isfile(filepath):
maruel@chromium.org46304292010-10-28 11:42:00 +0000682 deps_content = gclient_utils.FileRead(filepath)
cmp@chromium.org76ce73c2014-07-02 00:13:18 +0000683 logging.debug('ParseDepsFile(%s) read:\n%s', self.name, deps_content)
iannucci@chromium.org0c3f3052014-05-22 00:29:22 +0000684
685 local_scope = {}
686 if deps_content:
Paweł Hajdan, Jrf1587bf2017-06-20 21:19:07 +0200687 global_scope = {
Paweł Hajdan, Jrfc6196b2017-07-27 13:15:25 +0200688 'Var': lambda var_name: '{%s}' % var_name,
Paweł Hajdan, Jrf1587bf2017-06-20 21:19:07 +0200689 'deps_os': {},
690 }
maruel@chromium.org46304292010-10-28 11:42:00 +0000691 # Eval the content.
692 try:
Paweł Hajdan, Jrc485d5a2017-06-02 12:08:09 +0200693 if self._get_option('validate_syntax', False):
694 gclient_eval.Exec(deps_content, global_scope, local_scope, filepath)
695 else:
696 exec(deps_content, global_scope, local_scope)
vapier@chromium.orga81a56e2015-11-11 07:56:13 +0000697 except SyntaxError as e:
maruel@chromium.org46304292010-10-28 11:42:00 +0000698 gclient_utils.SyntaxErrorToError(filepath, e)
iannucci@chromium.org0c3f3052014-05-22 00:29:22 +0000699
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000700 if 'allowed_hosts' in local_scope:
701 try:
702 self._allowed_hosts = frozenset(local_scope.get('allowed_hosts'))
703 except TypeError: # raised if non-iterable
704 pass
705 if not self._allowed_hosts:
706 logging.warning("allowed_hosts is specified but empty %s",
707 self._allowed_hosts)
708 raise gclient_utils.Error(
709 'ParseDepsFile(%s): allowed_hosts must be absent '
710 'or a non-empty iterable' % self.name)
711
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +0200712 self._gn_args_file = local_scope.get('gclient_gn_args_file')
713 self._gn_args = local_scope.get('gclient_gn_args', [])
714
Paweł Hajdan, Jr1407d002017-08-01 20:01:01 +0200715 self._vars = local_scope.get('vars', {})
716 if self.parent:
717 for key, value in self.parent.get_vars().iteritems():
718 if key in self._vars:
719 self._vars[key] = value
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200720 # Since we heavily post-process things, freeze ones which should
721 # reflect original state of DEPS.
Paweł Hajdan, Jr1407d002017-08-01 20:01:01 +0200722 self._vars = gclient_utils.freeze(self._vars)
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200723
724 # If use_relative_paths is set in the DEPS file, regenerate
725 # the dictionary using paths relative to the directory containing
726 # the DEPS file. Also update recursedeps if use_relative_paths is
727 # enabled.
728 # If the deps file doesn't set use_relative_paths, but the parent did
729 # (and therefore set self.relative on this Dependency object), then we
730 # want to modify the deps and recursedeps by prepending the parent
731 # directory of this dependency.
732 use_relative_paths = local_scope.get('use_relative_paths', False)
733 rel_prefix = None
734 if use_relative_paths:
735 rel_prefix = self.name
736 elif self._relative:
737 rel_prefix = os.path.dirname(self.name)
738
Paweł Hajdan, Jrfc6196b2017-07-27 13:15:25 +0200739 deps = {}
740 for key, value in local_scope.get('deps', {}).iteritems():
741 deps[key.format(**self.get_vars())] = value
742
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200743 if 'recursion' in local_scope:
744 self.recursion_override = local_scope.get('recursion')
745 logging.warning(
746 'Setting %s recursion to %d.', self.name, self.recursion_limit)
747 self.recursedeps = None
748 if 'recursedeps' in local_scope:
749 self.recursedeps = {}
750 for ent in local_scope['recursedeps']:
751 if isinstance(ent, basestring):
752 self.recursedeps[ent] = {"deps_file": self.deps_file}
753 else: # (depname, depsfilename)
754 self.recursedeps[ent[0]] = {"deps_file": ent[1]}
755 logging.warning('Found recursedeps %r.', repr(self.recursedeps))
756
757 if rel_prefix:
758 logging.warning('Updating recursedeps by prepending %s.', rel_prefix)
759 rel_deps = {}
760 for depname, options in self.recursedeps.iteritems():
761 rel_deps[
762 os.path.normpath(os.path.join(rel_prefix, depname))] = options
763 self.recursedeps = rel_deps
764
765 # If present, save 'target_os' in the local_target_os property.
766 if 'target_os' in local_scope:
767 self.local_target_os = local_scope['target_os']
768 # load os specific dependencies if defined. these dependencies may
769 # override or extend the values defined by the 'deps' member.
770 target_os_list = self.target_os
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +0200771 if 'deps_os' in local_scope:
772 for dep_os, os_deps in local_scope['deps_os'].iteritems():
773 self._os_dependencies[dep_os] = self._deps_to_objects(
774 self._postprocess_deps(os_deps, rel_prefix), use_relative_paths)
Paweł Hajdan, Jr357415c2017-07-24 14:35:28 +0200775 if target_os_list and not self._get_option(
776 'do_not_merge_os_specific_entries', False):
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +0200777 deps = self.MergeWithOsDeps(
Paweł Hajdan, Jr5ec77132017-08-16 19:21:06 +0200778 deps, local_scope['deps_os'], target_os_list,
779 self._get_option('process_all_deps', False))
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200780
781 deps_to_add = self._deps_to_objects(
782 self._postprocess_deps(deps, rel_prefix), use_relative_paths)
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000783
784 # override named sets of hooks by the custom hooks
785 hooks_to_run = []
786 hook_names_to_suppress = [c.get('name', '') for c in self.custom_hooks]
787 for hook in local_scope.get('hooks', []):
788 if hook.get('name', '') not in hook_names_to_suppress:
789 hooks_to_run.append(hook)
Scott Grahamc4826742017-05-11 16:59:23 -0700790 if 'hooks_os' in local_scope and target_os_list:
791 hooks_os = local_scope['hooks_os']
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +0200792
793 # Keep original contents of hooks_os for flatten.
794 for hook_os, os_hooks in hooks_os.iteritems():
795 self._os_deps_hooks[hook_os] = [
Paweł Hajdan, Jrd3790252017-07-03 21:06:24 +0200796 Hook.from_dict(hook, variables=self.get_vars())
797 for hook in os_hooks]
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +0200798
Scott Grahamc4826742017-05-11 16:59:23 -0700799 # Specifically append these to ensure that hooks_os run after hooks.
Paweł Hajdan, Jr357415c2017-07-24 14:35:28 +0200800 if not self._get_option('do_not_merge_os_specific_entries', False):
801 for the_target_os in target_os_list:
802 the_target_os_hooks = hooks_os.get(the_target_os, [])
803 hooks_to_run.extend(the_target_os_hooks)
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000804
805 # add the replacements and any additions
806 for hook in self.custom_hooks:
807 if 'action' in hook:
808 hooks_to_run.append(hook)
809
Dirk Prankeda3a29e2017-02-27 15:29:36 -0800810 if self.recursion_limit:
Paweł Hajdan, Jr032d5452017-06-22 20:43:53 +0200811 self._pre_deps_hooks = [
Paweł Hajdan, Jrd3790252017-07-03 21:06:24 +0200812 Hook.from_dict(hook, variables=self.get_vars()) for hook in
Paweł Hajdan, Jr032d5452017-06-22 20:43:53 +0200813 local_scope.get('pre_deps_hooks', [])]
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000814
Paweł Hajdan, Jr357415c2017-07-24 14:35:28 +0200815 self.add_dependencies_and_close(deps_to_add, hooks_to_run)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000816 logging.info('ParseDepsFile(%s) done' % self.name)
817
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +0200818 def _get_option(self, attr, default):
819 obj = self
820 while not hasattr(obj, '_options'):
821 obj = obj.parent
822 return getattr(obj._options, attr, default)
823
Paweł Hajdan, Jr357415c2017-07-24 14:35:28 +0200824 def add_dependencies_and_close(self, deps_to_add, hooks):
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000825 """Adds the dependencies, hooks and mark the parsing as done."""
maruel@chromium.orgb9be0652011-10-14 18:05:40 +0000826 for dep in deps_to_add:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000827 if dep.verify_validity():
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000828 self.add_dependency(dep)
Paweł Hajdan, Jr032d5452017-06-22 20:43:53 +0200829 self._mark_as_parsed(
Paweł Hajdan, Jr357415c2017-07-24 14:35:28 +0200830 [Hook.from_dict(h, variables=self.get_vars()) for h in hooks])
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000831
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000832 def findDepsFromNotAllowedHosts(self):
833 """Returns a list of depenecies from not allowed hosts.
834
835 If allowed_hosts is not set, allows all hosts and returns empty list.
836 """
837 if not self._allowed_hosts:
838 return []
839 bad_deps = []
840 for dep in self._dependencies:
szager@chromium.orgbd772dd2014-11-05 18:43:08 +0000841 # Don't enforce this for custom_deps.
842 if dep.name in self._custom_deps:
843 continue
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000844 if isinstance(dep.url, basestring):
845 parsed_url = urlparse.urlparse(dep.url)
846 if parsed_url.netloc and parsed_url.netloc not in self._allowed_hosts:
847 bad_deps.append(dep)
848 return bad_deps
849
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +0000850 # Arguments number differs from overridden method
Quinten Yearsleyb2cc4a92016-12-15 13:53:26 -0800851 # pylint: disable=arguments-differ
maruel@chromium.org3742c842010-09-09 19:27:14 +0000852 def run(self, revision_overrides, command, args, work_queue, options):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000853 """Runs |command| then parse the DEPS file."""
maruel@chromium.org470b5432011-10-11 18:18:19 +0000854 logging.info('Dependency(%s).run()' % self.name)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000855 assert self._file_list == []
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000856 if not self.should_process:
857 return
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000858 # When running runhooks, there's no need to consult the SCM.
859 # All known hooks are expected to run unconditionally regardless of working
860 # copy state, so skip the SCM status check.
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +0200861 run_scm = command not in (
862 'flatten', 'runhooks', 'recurse', 'validate', None)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000863 parsed_url = self.LateOverride(self.url)
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000864 file_list = [] if not options.nohooks else None
szager@chromium.org3a3608d2014-10-22 21:13:52 +0000865 revision_override = revision_overrides.pop(self.name, None)
Dave Tubbda9712017-06-01 15:10:53 -0700866 if not revision_override and parsed_url:
867 revision_override = revision_overrides.get(parsed_url.split('@')[0], None)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000868 if run_scm and parsed_url:
agabled437d762016-10-17 09:35:11 -0700869 # Create a shallow copy to mutate revision.
870 options = copy.copy(options)
871 options.revision = revision_override
872 self._used_revision = options.revision
873 self._used_scm = gclient_scm.CreateSCM(
874 parsed_url, self.root.root_dir, self.name, self.outbuf,
875 out_cb=work_queue.out_cb)
876 self._got_revision = self._used_scm.RunCommand(command, options, args,
877 file_list)
878 if file_list:
879 file_list = [os.path.join(self.name, f.strip()) for f in file_list]
maruel@chromium.org68988972011-09-20 14:11:42 +0000880
881 # TODO(phajdan.jr): We should know exactly when the paths are absolute.
882 # Convert all absolute paths to relative.
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000883 for i in range(len(file_list or [])):
maruel@chromium.org68988972011-09-20 14:11:42 +0000884 # It depends on the command being executed (like runhooks vs sync).
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000885 if not os.path.isabs(file_list[i]):
maruel@chromium.org68988972011-09-20 14:11:42 +0000886 continue
887 prefix = os.path.commonprefix(
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000888 [self.root.root_dir.lower(), file_list[i].lower()])
889 file_list[i] = file_list[i][len(prefix):]
maruel@chromium.org68988972011-09-20 14:11:42 +0000890 # Strip any leading path separators.
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000891 while file_list[i].startswith(('\\', '/')):
892 file_list[i] = file_list[i][1:]
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000893
894 # Always parse the DEPS file.
895 self.ParseDepsFile()
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000896 self._run_is_done(file_list or [], parsed_url)
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000897 if command in ('update', 'revert') and not options.noprehooks:
898 self.RunPreDepsHooks()
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000899
900 if self.recursion_limit:
901 # Parse the dependencies of this dependency.
902 for s in self.dependencies:
Paweł Hajdan, Jr4baaa112017-07-04 19:09:32 +0200903 if s.should_process:
904 work_queue.enqueue(s)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000905
906 if command == 'recurse':
agabled437d762016-10-17 09:35:11 -0700907 # Skip file only checkout.
908 scm = gclient_scm.GetScmName(parsed_url)
909 if not options.scm or scm in options.scm:
910 cwd = os.path.normpath(os.path.join(self.root.root_dir, self.name))
911 # Pass in the SCM type as an env variable. Make sure we don't put
912 # unicode strings in the environment.
913 env = os.environ.copy()
914 if scm:
915 env['GCLIENT_SCM'] = str(scm)
916 if parsed_url:
917 env['GCLIENT_URL'] = str(parsed_url)
918 env['GCLIENT_DEP_PATH'] = str(self.name)
919 if options.prepend_dir and scm == 'git':
920 print_stdout = False
921 def filter_fn(line):
922 """Git-specific path marshaling. It is optimized for git-grep."""
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000923
agabled437d762016-10-17 09:35:11 -0700924 def mod_path(git_pathspec):
925 match = re.match('^(\\S+?:)?([^\0]+)$', git_pathspec)
926 modified_path = os.path.join(self.name, match.group(2))
927 branch = match.group(1) or ''
928 return '%s%s' % (branch, modified_path)
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000929
agabled437d762016-10-17 09:35:11 -0700930 match = re.match('^Binary file ([^\0]+) matches$', line)
931 if match:
932 print('Binary file %s matches\n' % mod_path(match.group(1)))
933 return
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000934
agabled437d762016-10-17 09:35:11 -0700935 items = line.split('\0')
936 if len(items) == 2 and items[1]:
937 print('%s : %s' % (mod_path(items[0]), items[1]))
938 elif len(items) >= 2:
939 # Multiple null bytes or a single trailing null byte indicate
940 # git is likely displaying filenames only (such as with -l)
941 print('\n'.join(mod_path(path) for path in items if path))
942 else:
943 print(line)
944 else:
945 print_stdout = True
946 filter_fn = None
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000947
agabled437d762016-10-17 09:35:11 -0700948 if parsed_url is None:
949 print('Skipped omitted dependency %s' % cwd, file=sys.stderr)
950 elif os.path.isdir(cwd):
951 try:
952 gclient_utils.CheckCallAndFilter(
953 args, cwd=cwd, env=env, print_stdout=print_stdout,
954 filter_fn=filter_fn,
955 )
956 except subprocess2.CalledProcessError:
957 if not options.ignore:
958 raise
959 else:
960 print('Skipped missing %s' % cwd, file=sys.stderr)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000961
Dirk Pranke9f20d022017-10-11 18:36:54 -0700962 def HasGNArgsFile(self):
963 return self._gn_args_file is not None
964
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +0200965 def WriteGNArgsFile(self):
966 lines = ['# Generated from %r' % self.deps_file]
Paweł Hajdan, Jrb495bf52017-09-25 19:33:50 +0200967 variables = self.get_vars()
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +0200968 for arg in self._gn_args:
Paweł Hajdan, Jre0214742017-09-28 12:21:01 +0200969 value = variables[arg]
970 if isinstance(value, basestring):
971 value = gclient_eval.EvaluateCondition(value, variables)
Paweł Hajdan, Jrb495bf52017-09-25 19:33:50 +0200972 lines.append('%s = %s' % (arg, ToGNString(value)))
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +0200973 with open(os.path.join(self.root.root_dir, self._gn_args_file), 'w') as f:
974 f.write('\n'.join(lines))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000975
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000976 @gclient_utils.lockedmethod
977 def _run_is_done(self, file_list, parsed_url):
978 # Both these are kept for hooks that are run as a separate tree traversal.
979 self._file_list = file_list
980 self._parsed_url = parsed_url
981 self._processed = True
982
szager@google.comb9a78d32012-03-13 18:46:21 +0000983 def GetHooks(self, options):
984 """Evaluates all hooks, and return them in a flat list.
985
986 RunOnDeps() must have been called before to load the DEPS.
987 """
988 result = []
maruel@chromium.org68988972011-09-20 14:11:42 +0000989 if not self.should_process or not self.recursion_limit:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000990 # Don't run the hook when it is above recursion_limit.
szager@google.comb9a78d32012-03-13 18:46:21 +0000991 return result
maruel@chromium.orgdc7445d2010-07-09 21:05:29 +0000992 # If "--force" was specified, run all hooks regardless of what files have
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000993 # changed.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000994 if self.deps_hooks:
agabled437d762016-10-17 09:35:11 -0700995 # TODO(maruel): If the user is using git, then we don't know
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000996 # what files have changed so we always run all hooks. It'd be nice to fix
997 # that.
998 if (options.force or
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000999 gclient_scm.GetScmName(self.parsed_url) in ('git', None) or
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001000 os.path.isdir(os.path.join(self.root.root_dir, self.name, '.git'))):
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +02001001 result.extend(self.deps_hooks)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001002 else:
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +02001003 for hook in self.deps_hooks:
1004 if hook.matches(self.file_list_and_children):
1005 result.append(hook)
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001006 for s in self.dependencies:
szager@google.comb9a78d32012-03-13 18:46:21 +00001007 result.extend(s.GetHooks(options))
1008 return result
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001009
Dirk Pranke9f20d022017-10-11 18:36:54 -07001010 def WriteGNArgsFilesRecursively(self, dependencies):
1011 for dep in dependencies:
1012 if dep.HasGNArgsFile():
1013 dep.WriteGNArgsFile()
1014 self.WriteGNArgsFilesRecursively(dep.dependencies)
1015
szager@google.comb9a78d32012-03-13 18:46:21 +00001016 def RunHooksRecursively(self, options):
1017 assert self.hooks_ran == False
maruel@chromium.org064186c2011-09-27 23:53:33 +00001018 self._hooks_ran = True
szager@google.comb9a78d32012-03-13 18:46:21 +00001019 for hook in self.GetHooks(options):
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +02001020 hook.run(self.root.root_dir)
maruel@chromium.orgeaf61062010-07-07 18:42:39 +00001021
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001022 def RunPreDepsHooks(self):
1023 assert self.processed
1024 assert self.deps_parsed
1025 assert not self.pre_deps_hooks_ran
1026 assert not self.hooks_ran
1027 for s in self.dependencies:
1028 assert not s.processed
1029 self._pre_deps_hooks_ran = True
1030 for hook in self.pre_deps_hooks:
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +02001031 hook.run(self.root.root_dir)
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001032
maruel@chromium.org0d812442010-08-10 12:41:08 +00001033 def subtree(self, include_all):
maruel@chromium.orgad3287e2011-10-03 19:15:10 +00001034 """Breadth first recursion excluding root node."""
maruel@chromium.orgf13a4182011-09-22 00:26:15 +00001035 dependencies = self.dependencies
1036 for d in dependencies:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001037 if d.should_process or include_all:
maruel@chromium.orgad3287e2011-10-03 19:15:10 +00001038 yield d
maruel@chromium.orgf13a4182011-09-22 00:26:15 +00001039 for d in dependencies:
maruel@chromium.orgad3287e2011-10-03 19:15:10 +00001040 for i in d.subtree(include_all):
1041 yield i
1042
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001043 @gclient_utils.lockedmethod
1044 def add_dependency(self, new_dep):
1045 self._dependencies.append(new_dep)
1046
1047 @gclient_utils.lockedmethod
Paweł Hajdan, Jr357415c2017-07-24 14:35:28 +02001048 def _mark_as_parsed(self, new_hooks):
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001049 self._deps_hooks.extend(new_hooks)
1050 self._deps_parsed = True
1051
maruel@chromium.org68988972011-09-20 14:11:42 +00001052 @property
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001053 @gclient_utils.lockedmethod
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +00001054 def dependencies(self):
1055 return tuple(self._dependencies)
1056
1057 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001058 @gclient_utils.lockedmethod
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02001059 def os_dependencies(self):
1060 return dict(self._os_dependencies)
1061
1062 @property
1063 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001064 def deps_hooks(self):
1065 return tuple(self._deps_hooks)
1066
1067 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001068 @gclient_utils.lockedmethod
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +02001069 def os_deps_hooks(self):
1070 return dict(self._os_deps_hooks)
1071
1072 @property
1073 @gclient_utils.lockedmethod
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001074 def pre_deps_hooks(self):
1075 return tuple(self._pre_deps_hooks)
1076
1077 @property
1078 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001079 def parsed_url(self):
1080 return self._parsed_url
1081
1082 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001083 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001084 def deps_parsed(self):
maruel@chromium.org3223edd2011-10-10 23:17:39 +00001085 """This is purely for debugging purposes. It's not used anywhere."""
maruel@chromium.org064186c2011-09-27 23:53:33 +00001086 return self._deps_parsed
1087
1088 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001089 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001090 def processed(self):
1091 return self._processed
1092
1093 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001094 @gclient_utils.lockedmethod
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001095 def pre_deps_hooks_ran(self):
1096 return self._pre_deps_hooks_ran
1097
1098 @property
1099 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001100 def hooks_ran(self):
1101 return self._hooks_ran
1102
1103 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001104 @gclient_utils.lockedmethod
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00001105 def allowed_hosts(self):
1106 return self._allowed_hosts
1107
1108 @property
1109 @gclient_utils.lockedmethod
maruel@chromium.org861fd0f2010-07-23 03:05:05 +00001110 def file_list(self):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001111 return tuple(self._file_list)
1112
1113 @property
kustermann@google.coma692e8f2013-04-18 08:32:04 +00001114 def used_scm(self):
1115 """SCMWrapper instance for this dependency or None if not processed yet."""
1116 return self._used_scm
1117
1118 @property
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00001119 @gclient_utils.lockedmethod
1120 def got_revision(self):
1121 return self._got_revision
1122
1123 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001124 def file_list_and_children(self):
1125 result = list(self.file_list)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +00001126 for d in self.dependencies:
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001127 result.extend(d.file_list_and_children)
maruel@chromium.org68988972011-09-20 14:11:42 +00001128 return tuple(result)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +00001129
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001130 def __str__(self):
1131 out = []
agablea98a6cd2016-11-15 14:30:10 -08001132 for i in ('name', 'url', 'parsed_url', 'custom_deps',
maruel@chromium.org3c74bc92011-09-15 19:17:21 +00001133 'custom_vars', 'deps_hooks', 'file_list', 'should_process',
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00001134 'processed', 'hooks_ran', 'deps_parsed', 'requirements',
1135 'allowed_hosts'):
maruel@chromium.org3c74bc92011-09-15 19:17:21 +00001136 # First try the native property if it exists.
1137 if hasattr(self, '_' + i):
1138 value = getattr(self, '_' + i, False)
1139 else:
1140 value = getattr(self, i, False)
1141 if value:
1142 out.append('%s: %s' % (i, value))
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001143
1144 for d in self.dependencies:
1145 out.extend([' ' + x for x in str(d).splitlines()])
1146 out.append('')
1147 return '\n'.join(out)
1148
1149 def __repr__(self):
1150 return '%s: %s' % (self.name, self.url)
1151
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001152 def hierarchy(self, include_url=True):
maruel@chromium.orgbc2d2f92010-07-22 21:26:48 +00001153 """Returns a human-readable hierarchical reference to a Dependency."""
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001154 def format_name(d):
1155 if include_url:
1156 return '%s(%s)' % (d.name, d.url)
1157 return d.name
1158 out = format_name(self)
maruel@chromium.orgbffb9042010-07-22 20:59:36 +00001159 i = self.parent
1160 while i and i.name:
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001161 out = '%s -> %s' % (format_name(i), out)
maruel@chromium.orgbffb9042010-07-22 20:59:36 +00001162 i = i.parent
1163 return out
1164
Paweł Hajdan, Jrd3790252017-07-03 21:06:24 +02001165 def get_vars(self):
1166 """Returns a dictionary of effective variable values
1167 (DEPS file contents with applied custom_vars overrides)."""
Paweł Hajdan, Jra3b67ae2017-08-30 15:18:21 +02001168 # Provide some built-in variables.
1169 result = {
Paweł Hajdan, Jrd325eb32017-10-03 17:43:37 +02001170 'checkout_android': 'android' in self.target_os,
1171 'checkout_fuchsia': 'fuchsia' in self.target_os,
1172 'checkout_ios': 'ios' in self.target_os,
1173 'checkout_linux': 'unix' in self.target_os,
1174 'checkout_mac': 'mac' in self.target_os,
1175 'checkout_win': 'win' in self.target_os,
1176 'host_os': _detect_host_os(),
Paweł Hajdan, Jra3b67ae2017-08-30 15:18:21 +02001177 }
1178 # Variables defined in DEPS file override built-in ones.
1179 result.update(self._vars)
Paweł Hajdan, Jrd3790252017-07-03 21:06:24 +02001180 result.update(self.custom_vars or {})
1181 return result
1182
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001183
Paweł Hajdan, Jra3b67ae2017-08-30 15:18:21 +02001184_PLATFORM_MAPPING = {
1185 'cygwin': 'win',
1186 'darwin': 'mac',
1187 'linux2': 'linux',
1188 'win32': 'win',
Jaideep Bajwad05f3582017-09-11 12:31:48 -04001189 'aix6': 'aix',
Paweł Hajdan, Jra3b67ae2017-08-30 15:18:21 +02001190}
1191
1192
1193def _detect_host_os():
1194 return _PLATFORM_MAPPING[sys.platform]
1195
1196
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001197class GClient(Dependency):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001198 """Object that represent a gclient checkout. A tree of Dependency(), one per
1199 solution or DEPS entry."""
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001200
1201 DEPS_OS_CHOICES = {
Jaideep Bajwad05f3582017-09-11 12:31:48 -04001202 "aix6": "unix",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001203 "win32": "win",
1204 "win": "win",
1205 "cygwin": "win",
1206 "darwin": "mac",
1207 "mac": "mac",
1208 "unix": "unix",
1209 "linux": "unix",
1210 "linux2": "unix",
maruel@chromium.org244e3442011-06-12 15:20:55 +00001211 "linux3": "unix",
szager@chromium.orgf8c95cd2012-06-01 22:26:52 +00001212 "android": "android",
Michael Mossc54fa812017-08-17 11:27:58 -07001213 "ios": "ios",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001214 }
1215
1216 DEFAULT_CLIENT_FILE_TEXT = ("""\
1217solutions = [
smutae7ea312016-07-18 11:59:41 -07001218 { "name" : "%(solution_name)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001219 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +00001220 "deps_file" : "%(deps_file)s",
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001221 "managed" : %(managed)s,
smutae7ea312016-07-18 11:59:41 -07001222 "custom_deps" : {
1223 },
Paweł Hajdan, Jr3ba2a7c2017-10-04 19:24:46 +02001224 "custom_vars": %(custom_vars)r,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001225 },
1226]
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001227cache_dir = %(cache_dir)r
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001228""")
1229
1230 DEFAULT_SNAPSHOT_SOLUTION_TEXT = ("""\
smutae7ea312016-07-18 11:59:41 -07001231 { "name" : "%(solution_name)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001232 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +00001233 "deps_file" : "%(deps_file)s",
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001234 "managed" : %(managed)s,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001235 "custom_deps" : {
smutae7ea312016-07-18 11:59:41 -07001236%(solution_deps)s },
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001237 },
1238""")
1239
1240 DEFAULT_SNAPSHOT_FILE_TEXT = ("""\
1241# Snapshot generated with gclient revinfo --snapshot
1242solutions = [
maruel@chromium.org73e21142010-07-05 13:32:01 +00001243%(solution_list)s]
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001244""")
1245
1246 def __init__(self, root_dir, options):
maruel@chromium.org0d812442010-08-10 12:41:08 +00001247 # Do not change previous behavior. Only solution level and immediate DEPS
1248 # are processed.
1249 self._recursion_limit = 2
Paweł Hajdan, Jrfc6196b2017-07-27 13:15:25 +02001250 Dependency.__init__(self, None, None, None, None, True, None, None, None,
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +02001251 'unused', True, None, None, True)
maruel@chromium.org0d425922010-06-21 19:22:24 +00001252 self._options = options
maruel@chromium.org271375b2010-06-23 19:17:38 +00001253 if options.deps_os:
1254 enforced_os = options.deps_os.split(',')
1255 else:
1256 enforced_os = [self.DEPS_OS_CHOICES.get(sys.platform, 'unix')]
1257 if 'all' in enforced_os:
1258 enforced_os = self.DEPS_OS_CHOICES.itervalues()
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001259 self._enforced_os = tuple(set(enforced_os))
maruel@chromium.org271375b2010-06-23 19:17:38 +00001260 self._root_dir = root_dir
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001261 self.config_content = None
1262
borenet@google.com88d10082014-03-21 17:24:48 +00001263 def _CheckConfig(self):
1264 """Verify that the config matches the state of the existing checked-out
1265 solutions."""
1266 for dep in self.dependencies:
1267 if dep.managed and dep.url:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001268 scm = gclient_scm.CreateSCM(
1269 dep.url, self.root_dir, dep.name, self.outbuf)
smut@google.comd33eab32014-07-07 19:35:18 +00001270 actual_url = scm.GetActualRemoteURL(self._options)
borenet@google.com4e9be262014-04-08 19:40:30 +00001271 if actual_url and not scm.DoesRemoteURLMatch(self._options):
levarum@chromium.org27a6f9a2016-05-28 00:21:49 +00001272 mirror = scm.GetCacheMirror()
1273 if mirror:
1274 mirror_string = '%s (exists=%s)' % (mirror.mirror_path,
1275 mirror.exists())
1276 else:
1277 mirror_string = 'not used'
borenet@google.com0a427372014-04-02 19:12:13 +00001278 raise gclient_utils.Error('''
borenet@google.com88d10082014-03-21 17:24:48 +00001279Your .gclient file seems to be broken. The requested URL is different from what
borenet@google.com0a427372014-04-02 19:12:13 +00001280is actually checked out in %(checkout_path)s.
borenet@google.com88d10082014-03-21 17:24:48 +00001281
borenet@google.com97882362014-04-07 20:06:02 +00001282The .gclient file contains:
levarum@chromium.org27a6f9a2016-05-28 00:21:49 +00001283URL: %(expected_url)s (%(expected_scm)s)
1284Cache mirror: %(mirror_string)s
borenet@google.com97882362014-04-07 20:06:02 +00001285
1286The local checkout in %(checkout_path)s reports:
1287%(actual_url)s (%(actual_scm)s)
borenet@google.com88d10082014-03-21 17:24:48 +00001288
1289You should ensure that the URL listed in .gclient is correct and either change
agabled437d762016-10-17 09:35:11 -07001290it or fix the checkout.
borenet@google.com88d10082014-03-21 17:24:48 +00001291''' % {'checkout_path': os.path.join(self.root_dir, dep.name),
1292 'expected_url': dep.url,
1293 'expected_scm': gclient_scm.GetScmName(dep.url),
levarum@chromium.org27a6f9a2016-05-28 00:21:49 +00001294 'mirror_string' : mirror_string,
borenet@google.com88d10082014-03-21 17:24:48 +00001295 'actual_url': actual_url,
1296 'actual_scm': gclient_scm.GetScmName(actual_url)})
1297
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001298 def SetConfig(self, content):
maruel@chromium.orgf13a4182011-09-22 00:26:15 +00001299 assert not self.dependencies
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001300 config_dict = {}
1301 self.config_content = content
1302 try:
1303 exec(content, config_dict)
vapier@chromium.orga81a56e2015-11-11 07:56:13 +00001304 except SyntaxError as e:
maruel@chromium.org5990f9d2010-07-07 18:02:58 +00001305 gclient_utils.SyntaxErrorToError('.gclient', e)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001306
peter@chromium.org1efccc82012-04-27 16:34:38 +00001307 # Append any target OS that is not already being enforced to the tuple.
1308 target_os = config_dict.get('target_os', [])
stuartmorgan@chromium.org18a4f6a2012-11-02 13:37:24 +00001309 if config_dict.get('target_os_only', False):
1310 self._enforced_os = tuple(set(target_os))
1311 else:
1312 self._enforced_os = tuple(set(self._enforced_os).union(target_os))
1313
Aleksandr Derbenev9e8fb0e2017-08-01 20:18:31 +03001314 cache_dir = config_dict.get('cache_dir', self._options.cache_dir)
dyen@chromium.orgd915cca2014-08-07 21:41:37 +00001315 if cache_dir:
1316 cache_dir = os.path.join(self.root_dir, cache_dir)
1317 cache_dir = os.path.abspath(cache_dir)
Vadim Shtayura08049e22017-10-11 00:14:52 +00001318 # If running on a bot, force break any stale git cache locks.
1319 if os.path.exists(cache_dir) and os.environ.get('CHROME_HEADLESS'):
1320 subprocess2.check_call(['git', 'cache', 'unlock', '--cache-dir',
1321 cache_dir, '--force', '--all'])
dyen@chromium.orgd915cca2014-08-07 21:41:37 +00001322 gclient_scm.GitWrapper.cache_dir = cache_dir
1323 git_cache.Mirror.SetCachePath(cache_dir)
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001324
stuartmorgan@chromium.org18a4f6a2012-11-02 13:37:24 +00001325 if not target_os and config_dict.get('target_os_only', False):
1326 raise gclient_utils.Error('Can\'t use target_os_only if target_os is '
1327 'not specified')
peter@chromium.org1efccc82012-04-27 16:34:38 +00001328
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001329 deps_to_add = []
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001330 for s in config_dict.get('solutions', []):
maruel@chromium.org81843b82010-06-28 16:49:26 +00001331 try:
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001332 deps_to_add.append(Dependency(
Paweł Hajdan, Jrfc6196b2017-07-27 13:15:25 +02001333 self, s['name'], s['url'], s['url'],
smutae7ea312016-07-18 11:59:41 -07001334 s.get('managed', True),
maruel@chromium.org81843b82010-06-28 16:49:26 +00001335 s.get('custom_deps', {}),
maruel@chromium.org0d812442010-08-10 12:41:08 +00001336 s.get('custom_vars', {}),
petermayo@chromium.orge79161a2013-07-09 14:40:37 +00001337 s.get('custom_hooks', []),
nsylvain@google.comefc80932011-05-31 21:27:56 +00001338 s.get('deps_file', 'DEPS'),
agabledce6ddc2016-09-08 10:02:16 -07001339 True,
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +02001340 None,
1341 None,
1342 True))
maruel@chromium.org81843b82010-06-28 16:49:26 +00001343 except KeyError:
1344 raise gclient_utils.Error('Invalid .gclient file. Solution is '
1345 'incomplete: %s' % s)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001346 self.add_dependencies_and_close(deps_to_add, config_dict.get('hooks', []))
1347 logging.info('SetConfig() done')
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001348
1349 def SaveConfig(self):
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001350 gclient_utils.FileWrite(os.path.join(self.root_dir,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001351 self._options.config_filename),
1352 self.config_content)
1353
1354 @staticmethod
1355 def LoadCurrentConfig(options):
1356 """Searches for and loads a .gclient file relative to the current working
1357 dir. Returns a GClient object."""
szager@chromium.orge2e03202012-07-31 18:05:16 +00001358 if options.spec:
1359 client = GClient('.', options)
1360 client.SetConfig(options.spec)
1361 else:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00001362 if options.verbose:
1363 print('Looking for %s starting from %s\n' % (
1364 options.config_filename, os.getcwd()))
szager@chromium.orge2e03202012-07-31 18:05:16 +00001365 path = gclient_utils.FindGclientRoot(os.getcwd(), options.config_filename)
1366 if not path:
Michael Achenbachb3ce73d2017-10-11 16:41:27 +02001367 if options.verbose:
1368 print('Couldn\'t find configuration file.')
szager@chromium.orge2e03202012-07-31 18:05:16 +00001369 return None
1370 client = GClient(path, options)
1371 client.SetConfig(gclient_utils.FileRead(
1372 os.path.join(path, options.config_filename)))
maruel@chromium.org69392e72011-10-13 22:09:00 +00001373
1374 if (options.revisions and
1375 len(client.dependencies) > 1 and
1376 any('@' not in r for r in options.revisions)):
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001377 print(
1378 ('You must specify the full solution name like --revision %s@%s\n'
1379 'when you have multiple solutions setup in your .gclient file.\n'
1380 'Other solutions present are: %s.') % (
maruel@chromium.org69392e72011-10-13 22:09:00 +00001381 client.dependencies[0].name,
1382 options.revisions[0],
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001383 ', '.join(s.name for s in client.dependencies[1:])),
1384 file=sys.stderr)
maruel@chromium.org15804092010-09-02 17:07:37 +00001385 return client
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001386
nsylvain@google.comefc80932011-05-31 21:27:56 +00001387 def SetDefaultConfig(self, solution_name, deps_file, solution_url,
Paweł Hajdan, Jr3ba2a7c2017-10-04 19:24:46 +02001388 managed=True, cache_dir=None, custom_vars=None):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001389 self.SetConfig(self.DEFAULT_CLIENT_FILE_TEXT % {
1390 'solution_name': solution_name,
1391 'solution_url': solution_url,
nsylvain@google.comefc80932011-05-31 21:27:56 +00001392 'deps_file': deps_file,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001393 'managed': managed,
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001394 'cache_dir': cache_dir,
Paweł Hajdan, Jr3ba2a7c2017-10-04 19:24:46 +02001395 'custom_vars': custom_vars or {},
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001396 })
1397
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001398 def _SaveEntries(self):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001399 """Creates a .gclient_entries file to record the list of unique checkouts.
1400
1401 The .gclient_entries file lives in the same directory as .gclient.
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001402 """
1403 # Sometimes pprint.pformat will use {', sometimes it'll use { ' ... It
1404 # makes testing a bit too fun.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001405 result = 'entries = {\n'
maruel@chromium.org68988972011-09-20 14:11:42 +00001406 for entry in self.root.subtree(False):
agabled437d762016-10-17 09:35:11 -07001407 result += ' %s: %s,\n' % (pprint.pformat(entry.name),
1408 pprint.pformat(entry.parsed_url))
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001409 result += '}\n'
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001410 file_path = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org1333cb32011-10-04 23:40:16 +00001411 logging.debug(result)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001412 gclient_utils.FileWrite(file_path, result)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001413
1414 def _ReadEntries(self):
1415 """Read the .gclient_entries file for the given client.
1416
1417 Returns:
1418 A sequence of solution names, which will be empty if there is the
1419 entries file hasn't been created yet.
1420 """
1421 scope = {}
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001422 filename = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001423 if not os.path.exists(filename):
maruel@chromium.org73e21142010-07-05 13:32:01 +00001424 return {}
maruel@chromium.org5990f9d2010-07-07 18:02:58 +00001425 try:
1426 exec(gclient_utils.FileRead(filename), scope)
vapier@chromium.orga81a56e2015-11-11 07:56:13 +00001427 except SyntaxError as e:
maruel@chromium.org5990f9d2010-07-07 18:02:58 +00001428 gclient_utils.SyntaxErrorToError(filename, e)
Aaron Gable3721ee92017-04-03 14:53:14 -07001429 return scope.get('entries', {})
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001430
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001431 def _EnforceRevisions(self):
maruel@chromium.org918a9ae2010-05-28 15:50:30 +00001432 """Checks for revision overrides."""
1433 revision_overrides = {}
smutae7ea312016-07-18 11:59:41 -07001434 if self._options.head:
1435 return revision_overrides
joi@chromium.org792ea882010-11-10 02:37:27 +00001436 if not self._options.revisions:
1437 for s in self.dependencies:
smutae7ea312016-07-18 11:59:41 -07001438 if not s.managed:
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001439 self._options.revisions.append('%s@unmanaged' % s.name)
maruel@chromium.org307d1792010-05-31 20:03:13 +00001440 if not self._options.revisions:
1441 return revision_overrides
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001442 solutions_names = [s.name for s in self.dependencies]
smutae7ea312016-07-18 11:59:41 -07001443 index = 0
1444 for revision in self._options.revisions:
1445 if not '@' in revision:
maruel@chromium.org307d1792010-05-31 20:03:13 +00001446 # Support for --revision 123
smutae7ea312016-07-18 11:59:41 -07001447 revision = '%s@%s' % (solutions_names[index], revision)
1448 name, rev = revision.split('@', 1)
szager@chromium.org4ad264b2014-05-20 04:43:47 +00001449 revision_overrides[name] = rev
smutae7ea312016-07-18 11:59:41 -07001450 index += 1
maruel@chromium.org918a9ae2010-05-28 15:50:30 +00001451 return revision_overrides
1452
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001453 def RunOnDeps(self, command, args, ignore_requirements=False, progress=True):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001454 """Runs a command on each dependency in a client and its dependencies.
1455
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001456 Args:
1457 command: The command to use (e.g., 'status' or 'diff')
1458 args: list of str - extra arguments to add to the command line.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001459 """
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001460 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +00001461 raise gclient_utils.Error('No solution specified')
borenet@google.com0a427372014-04-02 19:12:13 +00001462
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001463 revision_overrides = {}
1464 # It's unnecessary to check for revision overrides for 'recurse'.
1465 # Save a few seconds by not calling _EnforceRevisions() in that case.
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +02001466 if command not in ('diff', 'recurse', 'runhooks', 'status', 'revert',
1467 'validate'):
szager@chromium.org5273b8a2014-08-21 15:10:10 +00001468 self._CheckConfig()
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001469 revision_overrides = self._EnforceRevisions()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001470 pm = None
maruel@chromium.org5b3f8852010-09-10 16:49:54 +00001471 # Disable progress for non-tty stdout.
iannucci@chromium.org596cd5c2016-04-04 21:34:39 +00001472 if (setup_color.IS_TTY and not self._options.verbose and progress):
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001473 if command in ('update', 'revert'):
1474 pm = Progress('Syncing projects', 1)
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +02001475 elif command in ('recurse', 'validate'):
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001476 pm = Progress(' '.join(args), 1)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001477 work_queue = gclient_utils.ExecutionQueue(
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001478 self._options.jobs, pm, ignore_requirements=ignore_requirements,
1479 verbose=self._options.verbose)
maruel@chromium.org049bced2010-08-12 13:37:20 +00001480 for s in self.dependencies:
Paweł Hajdan, Jr4baaa112017-07-04 19:09:32 +02001481 if s.should_process:
1482 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +00001483 work_queue.flush(revision_overrides, command, args, options=self._options)
szager@chromium.org4ad264b2014-05-20 04:43:47 +00001484 if revision_overrides:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001485 print('Please fix your script, having invalid --revision flags will soon '
1486 'considered an error.', file=sys.stderr)
piman@chromium.org6f363722010-04-27 00:41:09 +00001487
Dirk Pranke9f20d022017-10-11 18:36:54 -07001488 # Once all the dependencies have been processed, it's now safe to write
1489 # out any gn_args_files and run the hooks.
1490 if command == 'update':
1491 self.WriteGNArgsFilesRecursively(self.dependencies)
1492
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001493 if not self._options.nohooks:
1494 self.RunHooksRecursively(self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001495
1496 if command == 'update':
ajwong@chromium.orgcdcee802009-06-23 15:30:42 +00001497 # Notify the user if there is an orphaned entry in their working copy.
1498 # Only delete the directory if there are no changes in it, and
1499 # delete_unversioned_trees is set to true.
maruel@chromium.org68988972011-09-20 14:11:42 +00001500 entries = [i.name for i in self.root.subtree(False) if i.url]
xusydoc@chromium.org885a9602013-05-31 09:54:40 +00001501 full_entries = [os.path.join(self.root_dir, e.replace('/', os.path.sep))
1502 for e in entries]
1503
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001504 for entry, prev_url in self._ReadEntries().iteritems():
maruel@chromium.org04dd7de2010-10-14 13:25:49 +00001505 if not prev_url:
1506 # entry must have been overridden via .gclient custom_deps
1507 continue
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001508 # Fix path separator on Windows.
1509 entry_fixed = entry.replace('/', os.path.sep)
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001510 e_dir = os.path.join(self.root_dir, entry_fixed)
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001511 # Use entry and not entry_fixed there.
jochen@chromium.orga78e5532013-03-11 13:33:03 +00001512 if (entry not in entries and
1513 (not any(path.startswith(entry + '/') for path in entries)) and
jochen@chromium.orgcc475722013-03-11 13:07:40 +00001514 os.path.exists(e_dir)):
primiano@chromium.org1c127382015-02-17 11:15:40 +00001515 # The entry has been removed from DEPS.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001516 scm = gclient_scm.CreateSCM(
1517 prev_url, self.root_dir, entry_fixed, self.outbuf)
xusydoc@chromium.org885a9602013-05-31 09:54:40 +00001518
1519 # Check to see if this directory is now part of a higher-up checkout.
borenet@google.com359bb642014-05-13 17:28:19 +00001520 scm_root = None
agabled437d762016-10-17 09:35:11 -07001521 try:
1522 scm_root = gclient_scm.scm.GIT.GetCheckoutRoot(scm.checkout_path)
1523 except subprocess2.CalledProcessError:
1524 pass
1525 if not scm_root:
borenet@google.com359bb642014-05-13 17:28:19 +00001526 logging.warning('Could not find checkout root for %s. Unable to '
1527 'determine whether it is part of a higher-level '
1528 'checkout, so not removing.' % entry)
1529 continue
primiano@chromium.org1c127382015-02-17 11:15:40 +00001530
1531 # This is to handle the case of third_party/WebKit migrating from
1532 # being a DEPS entry to being part of the main project.
1533 # If the subproject is a Git project, we need to remove its .git
1534 # folder. Otherwise git operations on that folder will have different
1535 # effects depending on the current working directory.
agabled437d762016-10-17 09:35:11 -07001536 if os.path.abspath(scm_root) == os.path.abspath(e_dir):
primiano@chromium.org1c127382015-02-17 11:15:40 +00001537 e_par_dir = os.path.join(e_dir, os.pardir)
agabled437d762016-10-17 09:35:11 -07001538 if gclient_scm.scm.GIT.IsInsideWorkTree(e_par_dir):
1539 par_scm_root = gclient_scm.scm.GIT.GetCheckoutRoot(e_par_dir)
primiano@chromium.org1c127382015-02-17 11:15:40 +00001540 # rel_e_dir : relative path of entry w.r.t. its parent repo.
1541 rel_e_dir = os.path.relpath(e_dir, par_scm_root)
agabled437d762016-10-17 09:35:11 -07001542 if gclient_scm.scm.GIT.IsDirectoryVersioned(
1543 par_scm_root, rel_e_dir):
primiano@chromium.org1c127382015-02-17 11:15:40 +00001544 save_dir = scm.GetGitBackupDirPath()
1545 # Remove any eventual stale backup dir for the same project.
1546 if os.path.exists(save_dir):
1547 gclient_utils.rmtree(save_dir)
1548 os.rename(os.path.join(e_dir, '.git'), save_dir)
1549 # When switching between the two states (entry/ is a subproject
1550 # -> entry/ is part of the outer project), it is very likely
1551 # that some files are changed in the checkout, unless we are
1552 # jumping *exactly* across the commit which changed just DEPS.
1553 # In such case we want to cleanup any eventual stale files
1554 # (coming from the old subproject) in order to end up with a
1555 # clean checkout.
agabled437d762016-10-17 09:35:11 -07001556 gclient_scm.scm.GIT.CleanupDir(par_scm_root, rel_e_dir)
primiano@chromium.org1c127382015-02-17 11:15:40 +00001557 assert not os.path.exists(os.path.join(e_dir, '.git'))
1558 print(('\nWARNING: \'%s\' has been moved from DEPS to a higher '
1559 'level checkout. The git folder containing all the local'
1560 ' branches has been saved to %s.\n'
1561 'If you don\'t care about its state you can safely '
1562 'remove that folder to free up space.') %
1563 (entry, save_dir))
1564 continue
1565
borenet@google.com359bb642014-05-13 17:28:19 +00001566 if scm_root in full_entries:
primiano@chromium.org1c127382015-02-17 11:15:40 +00001567 logging.info('%s is part of a higher level checkout, not removing',
1568 scm.GetCheckoutRoot())
xusydoc@chromium.org885a9602013-05-31 09:54:40 +00001569 continue
1570
1571 file_list = []
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001572 scm.status(self._options, [], file_list)
1573 modified_files = file_list != []
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001574 if (not self._options.delete_unversioned_trees or
1575 (modified_files and not self._options.force)):
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001576 # There are modified files in this entry. Keep warning until
1577 # removed.
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001578 print(('\nWARNING: \'%s\' is no longer part of this client. '
1579 'It is recommended that you manually remove it.\n') %
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001580 entry_fixed)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001581 else:
1582 # Delete the entry
maruel@chromium.org73e21142010-07-05 13:32:01 +00001583 print('\n________ deleting \'%s\' in \'%s\'' % (
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001584 entry_fixed, self.root_dir))
digit@chromium.orgdc112ac2013-04-24 13:00:19 +00001585 gclient_utils.rmtree(e_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001586 # record the current list of entries for next time
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001587 self._SaveEntries()
maruel@chromium.org17cdf762010-05-28 17:30:52 +00001588 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001589
1590 def PrintRevInfo(self):
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001591 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +00001592 raise gclient_utils.Error('No solution specified')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001593 # Load all the settings.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001594 work_queue = gclient_utils.ExecutionQueue(
1595 self._options.jobs, None, False, verbose=self._options.verbose)
maruel@chromium.org049bced2010-08-12 13:37:20 +00001596 for s in self.dependencies:
Paweł Hajdan, Jr4baaa112017-07-04 19:09:32 +02001597 if s.should_process:
1598 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +00001599 work_queue.flush({}, None, [], options=self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001600
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001601 def GetURLAndRev(dep):
1602 """Returns the revision-qualified SCM url for a Dependency."""
1603 if dep.parsed_url is None:
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001604 return None
agabled437d762016-10-17 09:35:11 -07001605 url, _ = gclient_utils.SplitUrlRevision(dep.parsed_url)
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001606 scm = gclient_scm.CreateSCM(
agabled437d762016-10-17 09:35:11 -07001607 dep.parsed_url, self.root_dir, dep.name, self.outbuf)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001608 if not os.path.isdir(scm.checkout_path):
1609 return None
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001610 return '%s@%s' % (url, scm.revinfo(self._options, [], None))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001611
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001612 if self._options.snapshot:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001613 new_gclient = ''
1614 # First level at .gclient
1615 for d in self.dependencies:
1616 entries = {}
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001617 def GrabDeps(dep):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001618 """Recursively grab dependencies."""
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001619 for d in dep.dependencies:
1620 entries[d.name] = GetURLAndRev(d)
1621 GrabDeps(d)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001622 GrabDeps(d)
1623 custom_deps = []
1624 for k in sorted(entries.keys()):
1625 if entries[k]:
1626 # Quotes aren't escaped...
1627 custom_deps.append(' \"%s\": \'%s\',\n' % (k, entries[k]))
1628 else:
1629 custom_deps.append(' \"%s\": None,\n' % k)
1630 new_gclient += self.DEFAULT_SNAPSHOT_SOLUTION_TEXT % {
1631 'solution_name': d.name,
1632 'solution_url': d.url,
nsylvain@google.comefc80932011-05-31 21:27:56 +00001633 'deps_file': d.deps_file,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001634 'managed': d.managed,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001635 'solution_deps': ''.join(custom_deps),
1636 }
1637 # Print the snapshot configuration file
1638 print(self.DEFAULT_SNAPSHOT_FILE_TEXT % {'solution_list': new_gclient})
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +00001639 else:
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001640 entries = {}
maruel@chromium.org68988972011-09-20 14:11:42 +00001641 for d in self.root.subtree(False):
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001642 if self._options.actual:
1643 entries[d.name] = GetURLAndRev(d)
1644 else:
1645 entries[d.name] = d.parsed_url
1646 keys = sorted(entries.keys())
1647 for x in keys:
maruel@chromium.orgce464892010-08-12 17:12:18 +00001648 print('%s: %s' % (x, entries[x]))
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +00001649 logging.info(str(self))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001650
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001651 def ParseDepsFile(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001652 """No DEPS to parse for a .gclient file."""
maruel@chromium.org049bced2010-08-12 13:37:20 +00001653 raise gclient_utils.Error('Internal error')
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001654
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00001655 def PrintLocationAndContents(self):
1656 # Print out the .gclient file. This is longer than if we just printed the
1657 # client dict, but more legible, and it might contain helpful comments.
1658 print('Loaded .gclient config in %s:\n%s' % (
1659 self.root_dir, self.config_content))
1660
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001661 @property
maruel@chromium.org75a59272010-06-11 22:34:03 +00001662 def root_dir(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001663 """Root directory of gclient checkout."""
maruel@chromium.org75a59272010-06-11 22:34:03 +00001664 return self._root_dir
1665
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001666 @property
maruel@chromium.org271375b2010-06-23 19:17:38 +00001667 def enforced_os(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001668 """What deps_os entries that are to be parsed."""
maruel@chromium.org271375b2010-06-23 19:17:38 +00001669 return self._enforced_os
1670
maruel@chromium.org68988972011-09-20 14:11:42 +00001671 @property
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001672 def recursion_limit(self):
1673 """How recursive can each dependencies in DEPS file can load DEPS file."""
1674 return self._recursion_limit
1675
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +00001676 @property
cmp@chromium.orgc401ad12014-07-02 23:20:08 +00001677 def try_recursedeps(self):
1678 """Whether to attempt using recursedeps-style recursion processing."""
cmp@chromium.orge84ac912014-06-30 23:14:35 +00001679 return True
1680
1681 @property
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +00001682 def target_os(self):
1683 return self._enforced_os
1684
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001685
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001686#### gclient commands.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001687
1688
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001689@subcommand.usage('[command] [args ...]')
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001690def CMDrecurse(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001691 """Operates [command args ...] on all the dependencies.
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001692
1693 Runs a shell command on all entries.
qyearsley12fa6ff2016-08-24 09:18:40 -07001694 Sets GCLIENT_DEP_PATH environment variable as the dep's relative location to
ilevy@chromium.org37116242012-11-28 01:32:48 +00001695 root directory of the checkout.
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001696 """
1697 # Stop parsing at the first non-arg so that these go through to the command
1698 parser.disable_interspersed_args()
1699 parser.add_option('-s', '--scm', action='append', default=[],
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001700 help='Choose scm types to operate upon.')
maruel@chromium.org288054d2012-03-05 00:43:07 +00001701 parser.add_option('-i', '--ignore', action='store_true',
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001702 help='Ignore non-zero return codes from subcommands.')
1703 parser.add_option('--prepend-dir', action='store_true',
1704 help='Prepend relative dir for use with git <cmd> --null.')
1705 parser.add_option('--no-progress', action='store_true',
1706 help='Disable progress bar that shows sub-command updates')
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001707 options, args = parser.parse_args(args)
maruel@chromium.org45e9f2d2010-10-18 13:33:46 +00001708 if not args:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001709 print('Need to supply a command!', file=sys.stderr)
maruel@chromium.org45e9f2d2010-10-18 13:33:46 +00001710 return 1
maruel@chromium.org78cba522010-10-18 13:32:05 +00001711 root_and_entries = gclient_utils.GetGClientRootAndEntries()
1712 if not root_and_entries:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001713 print(
maruel@chromium.org78cba522010-10-18 13:32:05 +00001714 'You need to run gclient sync at least once to use \'recurse\'.\n'
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001715 'This is because .gclient_entries needs to exist and be up to date.',
1716 file=sys.stderr)
maruel@chromium.org78cba522010-10-18 13:32:05 +00001717 return 1
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001718
1719 # Normalize options.scm to a set()
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001720 scm_set = set()
1721 for scm in options.scm:
1722 scm_set.update(scm.split(','))
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001723 options.scm = scm_set
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001724
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001725 options.nohooks = True
1726 client = GClient.LoadCurrentConfig(options)
Marc-Antoine Ruele6e06412017-10-18 13:47:02 -04001727 if not client:
1728 raise gclient_utils.Error('client not configured; see \'gclient config\'')
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001729 return client.RunOnDeps('recurse', args, ignore_requirements=True,
1730 progress=not options.no_progress)
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001731
1732
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001733@subcommand.usage('[args ...]')
davidbarr@chromium.org12f944e2012-03-01 02:18:31 +00001734def CMDfetch(parser, args):
1735 """Fetches upstream commits for all modules.
1736
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001737 Completely git-specific. Simply runs 'git fetch [args ...]' for each module.
1738 """
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001739 (options, args) = parser.parse_args(args)
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00001740 return CMDrecurse(OptionParser(), [
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001741 '--jobs=%d' % options.jobs, '--scm=git', 'git', 'fetch'] + args)
1742
1743
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02001744class Flattener(object):
1745 """Flattens a gclient solution."""
1746
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02001747 def __init__(self, client, pin_all_deps=False):
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02001748 """Constructor.
1749
1750 Arguments:
1751 client (GClient): client to flatten
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02001752 pin_all_deps (bool): whether to pin all deps, even if they're not pinned
1753 in DEPS
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02001754 """
1755 self._client = client
1756
1757 self._deps_string = None
Paweł Hajdan, Jrfeb01642017-09-12 15:50:46 +02001758 self._deps_files = set()
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02001759
1760 self._allowed_hosts = set()
1761 self._deps = {}
1762 self._deps_os = {}
1763 self._hooks = []
1764 self._hooks_os = {}
1765 self._pre_deps_hooks = []
Paweł Hajdan, Jrfb022012017-07-06 18:00:08 +02001766 self._vars = {}
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02001767
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02001768 self._flatten(pin_all_deps=pin_all_deps)
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02001769
1770 @property
1771 def deps_string(self):
1772 assert self._deps_string is not None
1773 return self._deps_string
1774
Paweł Hajdan, Jrfeb01642017-09-12 15:50:46 +02001775 @property
1776 def deps_files(self):
1777 return self._deps_files
1778
Paweł Hajdan, Jr5ec77132017-08-16 19:21:06 +02001779 def _pin_dep(self, dep):
1780 """Pins a dependency to specific full revision sha.
1781
1782 Arguments:
1783 dep (Dependency): dependency to process
1784 """
1785 if dep.parsed_url is None:
1786 return
1787
1788 # Make sure the revision is always fully specified (a hash),
1789 # as opposed to refs or tags which might change. Similarly,
1790 # shortened shas might become ambiguous; make sure to always
1791 # use full one for pinning.
1792 url, revision = gclient_utils.SplitUrlRevision(dep.parsed_url)
1793 if revision and gclient_utils.IsFullGitSha(revision):
1794 return
1795
1796 scm = gclient_scm.CreateSCM(
1797 dep.parsed_url, self._client.root_dir, dep.name, dep.outbuf)
1798 revinfo = scm.revinfo(self._client._options, [], None)
1799
1800 dep._parsed_url = dep._url = '%s@%s' % (url, revinfo)
1801 raw_url, _ = gclient_utils.SplitUrlRevision(dep._raw_url)
1802 dep._raw_url = '%s@%s' % (raw_url, revinfo)
1803
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02001804 def _flatten(self, pin_all_deps=False):
1805 """Runs the flattener. Saves resulting DEPS string.
1806
1807 Arguments:
1808 pin_all_deps (bool): whether to pin all deps, even if they're not pinned
1809 in DEPS
1810 """
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02001811 for solution in self._client.dependencies:
Paweł Hajdan, Jr11eb7152017-08-10 12:50:11 +02001812 self._add_dep(solution)
Paweł Hajdan, Jrb0ad16e2017-08-03 15:33:21 +02001813 self._flatten_dep(solution)
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02001814
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02001815 if pin_all_deps:
1816 for dep in self._deps.itervalues():
Paweł Hajdan, Jr5ec77132017-08-16 19:21:06 +02001817 self._pin_dep(dep)
Paweł Hajdan, Jr39300ba2017-08-11 16:52:38 +02001818
Paweł Hajdan, Jr5ec77132017-08-16 19:21:06 +02001819 for os_deps in self._deps_os.itervalues():
1820 for dep in os_deps.itervalues():
Paweł Hajdan, Jr5ec77132017-08-16 19:21:06 +02001821 self._pin_dep(dep)
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02001822
Paweł Hajdan, Jr76a9d042017-08-18 20:05:41 +02001823 def add_deps_file(dep):
Paweł Hajdan, Jr0870df22017-08-23 17:59:29 +02001824 # Only include DEPS files referenced by recursedeps.
1825 if not (dep.parent is None or
1826 (dep.name in (dep.parent.recursedeps or {}))):
1827 return
Paweł Hajdan, Jrfeb01642017-09-12 15:50:46 +02001828 deps_file = dep.deps_file
1829 deps_path = os.path.join(self._client.root_dir, dep.name, deps_file)
Paweł Hajdan, Jr76a9d042017-08-18 20:05:41 +02001830 if not os.path.exists(deps_path):
Paweł Hajdan, Jrfeb01642017-09-12 15:50:46 +02001831 # gclient has a fallback that if deps_file doesn't exist, it'll try
1832 # DEPS. Do the same here.
1833 deps_file = 'DEPS'
1834 deps_path = os.path.join(self._client.root_dir, dep.name, deps_file)
1835 if not os.path.exists(deps_path):
1836 return
Paweł Hajdan, Jr76a9d042017-08-18 20:05:41 +02001837 assert dep.parsed_url
Paweł Hajdan, Jrfeb01642017-09-12 15:50:46 +02001838 self._deps_files.add((dep.parsed_url, deps_file))
Paweł Hajdan, Jr76a9d042017-08-18 20:05:41 +02001839 for dep in self._deps.itervalues():
1840 add_deps_file(dep)
1841 for os_deps in self._deps_os.itervalues():
1842 for dep in os_deps.itervalues():
1843 add_deps_file(dep)
1844
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02001845 self._deps_string = '\n'.join(
1846 _GNSettingsToLines(
1847 self._client.dependencies[0]._gn_args_file,
1848 self._client.dependencies[0]._gn_args) +
1849 _AllowedHostsToLines(self._allowed_hosts) +
1850 _DepsToLines(self._deps) +
1851 _DepsOsToLines(self._deps_os) +
1852 _HooksToLines('hooks', self._hooks) +
1853 _HooksToLines('pre_deps_hooks', self._pre_deps_hooks) +
1854 _HooksOsToLines(self._hooks_os) +
Paweł Hajdan, Jrfb022012017-07-06 18:00:08 +02001855 _VarsToLines(self._vars) +
Paweł Hajdan, Jr76a9d042017-08-18 20:05:41 +02001856 ['# %s, %s' % (url, deps_file)
Paweł Hajdan, Jrfeb01642017-09-12 15:50:46 +02001857 for url, deps_file in sorted(self._deps_files)] +
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02001858 ['']) # Ensure newline at end of file.
1859
Paweł Hajdan, Jr11eb7152017-08-10 12:50:11 +02001860 def _add_dep(self, dep):
1861 """Helper to add a dependency to flattened DEPS.
1862
1863 Arguments:
1864 dep (Dependency): dependency to add
1865 """
1866 assert dep.name not in self._deps or self._deps.get(dep.name) == dep, (
1867 dep.name, self._deps.get(dep.name))
Paweł Hajdan, Jr9a289022017-08-10 16:04:24 +02001868 if dep.url:
1869 self._deps[dep.name] = dep
Paweł Hajdan, Jr11eb7152017-08-10 12:50:11 +02001870
Paweł Hajdan, Jradae2a62017-08-18 16:49:57 +02001871 def _add_os_dep(self, os_dep, dep_os):
1872 """Helper to add an OS-specific dependency to flattened DEPS.
1873
1874 Arguments:
1875 os_dep (Dependency): dependency to add
1876 dep_os (str): name of the OS
1877 """
1878 assert (
1879 os_dep.name not in self._deps_os.get(dep_os, {}) or
1880 self._deps_os.get(dep_os, {}).get(os_dep.name) == os_dep), (
1881 os_dep.name, self._deps_os.get(dep_os, {}).get(os_dep.name))
1882 if os_dep.url:
Paweł Hajdan, Jr76a9d042017-08-18 20:05:41 +02001883 # OS-specific deps need to have their full URL resolved manually.
1884 assert not os_dep.parsed_url, (os_dep, os_dep.parsed_url)
1885 os_dep._parsed_url = os_dep.LateOverride(os_dep.url)
1886
Paweł Hajdan, Jradae2a62017-08-18 16:49:57 +02001887 self._deps_os.setdefault(dep_os, {})[os_dep.name] = os_dep
1888
1889 def _flatten_dep(self, dep, dep_os=None):
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02001890 """Visits a dependency in order to flatten it (see CMDflatten).
1891
1892 Arguments:
1893 dep (Dependency): dependency to process
Paweł Hajdan, Jradae2a62017-08-18 16:49:57 +02001894 dep_os (str or None): name of the OS |dep| is specific to
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02001895 """
Paweł Hajdan, Jradae2a62017-08-18 16:49:57 +02001896 logging.debug('_flatten_dep(%s, %s)', dep.name, dep_os)
1897
Paweł Hajdan, Jrc69b32e2017-08-17 18:47:48 +02001898 if not dep.deps_parsed:
1899 dep.ParseDepsFile()
1900
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02001901 self._allowed_hosts.update(dep.allowed_hosts)
1902
Paweł Hajdan, Jra3b67ae2017-08-30 15:18:21 +02001903 # Only include vars listed in the DEPS files, not possible local overrides.
1904 for key, value in dep._vars.iteritems():
Paweł Hajdan, Jrc9353602017-08-02 17:52:08 +02001905 # Make sure there are no conflicting variables. It is fine however
1906 # to use same variable name, as long as the value is consistent.
1907 assert key not in self._vars or self._vars[key][1] == value
Paweł Hajdan, Jrfb022012017-07-06 18:00:08 +02001908 self._vars[key] = (dep, value)
1909
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02001910 self._pre_deps_hooks.extend([(dep, hook) for hook in dep.pre_deps_hooks])
1911
Paweł Hajdan, Jradae2a62017-08-18 16:49:57 +02001912 if dep_os:
1913 if dep.deps_hooks:
1914 self._hooks_os.setdefault(dep_os, []).extend(
1915 [(dep, hook) for hook in dep.deps_hooks])
1916 else:
1917 self._hooks.extend([(dep, hook) for hook in dep.deps_hooks])
1918
Paweł Hajdan, Jrb0ad16e2017-08-03 15:33:21 +02001919 for sub_dep in dep.dependencies:
Paweł Hajdan, Jradae2a62017-08-18 16:49:57 +02001920 if dep_os:
1921 self._add_os_dep(sub_dep, dep_os)
1922 else:
1923 self._add_dep(sub_dep)
Paweł Hajdan, Jrb0ad16e2017-08-03 15:33:21 +02001924
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02001925 for hook_os, os_hooks in dep.os_deps_hooks.iteritems():
1926 self._hooks_os.setdefault(hook_os, []).extend(
1927 [(dep, hook) for hook in os_hooks])
1928
Paweł Hajdan, Jradae2a62017-08-18 16:49:57 +02001929 for sub_dep_os, os_deps in dep.os_dependencies.iteritems():
Paweł Hajdan, Jre2deb1e2017-08-09 17:29:21 +02001930 for os_dep in os_deps:
Paweł Hajdan, Jradae2a62017-08-18 16:49:57 +02001931 self._add_os_dep(os_dep, sub_dep_os)
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02001932
Paweł Hajdan, Jradae2a62017-08-18 16:49:57 +02001933 # Process recursedeps. |deps_by_name| is a map where keys are dependency
1934 # names, and values are maps of OS names to |Dependency| instances.
1935 # |None| in place of OS name means the dependency is not OS-specific.
1936 deps_by_name = dict((d.name, {None: d}) for d in dep.dependencies)
1937 for sub_dep_os, os_deps in dep.os_dependencies.iteritems():
Paweł Hajdan, Jrc9353602017-08-02 17:52:08 +02001938 for os_dep in os_deps:
Paweł Hajdan, Jradae2a62017-08-18 16:49:57 +02001939 assert sub_dep_os not in deps_by_name.get(os_dep.name, {}), (
1940 os_dep.name, sub_dep_os)
1941 deps_by_name.setdefault(os_dep.name, {})[sub_dep_os] = os_dep
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02001942 for recurse_dep_name in (dep.recursedeps or []):
Paweł Hajdan, Jradae2a62017-08-18 16:49:57 +02001943 dep_info = deps_by_name[recurse_dep_name]
1944 for sub_dep_os, os_dep in dep_info.iteritems():
1945 self._flatten_dep(os_dep, dep_os=(sub_dep_os or dep_os))
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02001946
1947
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001948def CMDflatten(parser, args):
1949 """Flattens the solutions into a single DEPS file."""
1950 parser.add_option('--output-deps', help='Path to the output DEPS file')
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02001951 parser.add_option(
Paweł Hajdan, Jrfeb01642017-09-12 15:50:46 +02001952 '--output-deps-files',
1953 help=('Path to the output metadata about DEPS files referenced by '
1954 'recursedeps.'))
1955 parser.add_option(
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02001956 '--pin-all-deps', action='store_true',
1957 help=('Pin all deps, even if not pinned in DEPS. CAVEAT: only does so '
1958 'for checked out deps, NOT deps_os.'))
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001959 options, args = parser.parse_args(args)
1960
Paweł Hajdan, Jr357415c2017-07-24 14:35:28 +02001961 options.do_not_merge_os_specific_entries = True
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001962 options.nohooks = True
Paweł Hajdan, Jr5ec77132017-08-16 19:21:06 +02001963 options.process_all_deps = True
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001964 client = GClient.LoadCurrentConfig(options)
1965
1966 # Only print progress if we're writing to a file. Otherwise, progress updates
1967 # could obscure intended output.
1968 code = client.RunOnDeps('flatten', args, progress=options.output_deps)
1969 if code != 0:
1970 return code
1971
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02001972 flattener = Flattener(client, pin_all_deps=options.pin_all_deps)
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001973
1974 if options.output_deps:
1975 with open(options.output_deps, 'w') as f:
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02001976 f.write(flattener.deps_string)
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001977 else:
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02001978 print(flattener.deps_string)
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001979
Paweł Hajdan, Jrfeb01642017-09-12 15:50:46 +02001980 deps_files = [{'url': d[0], 'deps_file': d[1]}
1981 for d in sorted(flattener.deps_files)]
1982 if options.output_deps_files:
1983 with open(options.output_deps_files, 'w') as f:
1984 json.dump(deps_files, f)
1985
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001986 return 0
1987
1988
Paweł Hajdan, Jr3c2aa832017-06-07 20:22:16 +02001989def _GNSettingsToLines(gn_args_file, gn_args):
1990 s = []
1991 if gn_args_file:
1992 s.extend([
1993 'gclient_gn_args_file = "%s"' % gn_args_file,
1994 'gclient_gn_args = %r' % gn_args,
1995 ])
1996 return s
1997
1998
Paweł Hajdan, Jr6014b562017-06-30 17:43:42 +02001999def _AllowedHostsToLines(allowed_hosts):
2000 """Converts |allowed_hosts| set to list of lines for output."""
2001 if not allowed_hosts:
2002 return []
2003 s = ['allowed_hosts = [']
2004 for h in sorted(allowed_hosts):
2005 s.append(' "%s",' % h)
2006 s.extend([']', ''])
2007 return s
2008
2009
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002010def _DepsToLines(deps):
2011 """Converts |deps| dict to list of lines for output."""
Paweł Hajdan, Jr5b593352017-06-29 18:37:45 +02002012 if not deps:
2013 return []
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002014 s = ['deps = {']
2015 for name, dep in sorted(deps.iteritems()):
Paweł Hajdan, Jr78ce24e2017-10-03 17:09:13 +02002016 condition_part = ([' "condition": %r,' % dep.condition]
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +02002017 if dep.condition else [])
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002018 s.extend([
2019 ' # %s' % dep.hierarchy(include_url=False),
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +02002020 ' "%s": {' % (name,),
Paweł Hajdan, Jrfc6196b2017-07-27 13:15:25 +02002021 ' "url": "%s",' % (dep.raw_url,),
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +02002022 ] + condition_part + [
2023 ' },',
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002024 '',
2025 ])
2026 s.extend(['}', ''])
2027 return s
2028
2029
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02002030def _DepsOsToLines(deps_os):
2031 """Converts |deps_os| dict to list of lines for output."""
Paweł Hajdan, Jr5b593352017-06-29 18:37:45 +02002032 if not deps_os:
2033 return []
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02002034 s = ['deps_os = {']
2035 for dep_os, os_deps in sorted(deps_os.iteritems()):
2036 s.append(' "%s": {' % dep_os)
Paweł Hajdan, Jrad30de62017-06-26 18:51:58 +02002037 for name, dep in sorted(os_deps.iteritems()):
Paweł Hajdan, Jr78ce24e2017-10-03 17:09:13 +02002038 condition_part = ([' "condition": %r,' % dep.condition]
Paweł Hajdan, Jrad30de62017-06-26 18:51:58 +02002039 if dep.condition else [])
2040 s.extend([
2041 ' # %s' % dep.hierarchy(include_url=False),
2042 ' "%s": {' % (name,),
Paweł Hajdan, Jrde86ab32017-08-10 13:55:16 +02002043 ' "url": "%s",' % (dep.raw_url,),
Paweł Hajdan, Jrad30de62017-06-26 18:51:58 +02002044 ] + condition_part + [
2045 ' },',
2046 '',
2047 ])
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02002048 s.extend([' },', ''])
2049 s.extend(['}', ''])
2050 return s
2051
2052
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002053def _HooksToLines(name, hooks):
2054 """Converts |hooks| list to list of lines for output."""
Paweł Hajdan, Jr5b593352017-06-29 18:37:45 +02002055 if not hooks:
2056 return []
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002057 s = ['%s = [' % name]
2058 for dep, hook in hooks:
2059 s.extend([
2060 ' # %s' % dep.hierarchy(include_url=False),
2061 ' {',
2062 ])
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +02002063 if hook.name is not None:
2064 s.append(' "name": "%s",' % hook.name)
2065 if hook.pattern is not None:
2066 s.append(' "pattern": "%s",' % hook.pattern)
Paweł Hajdan, Jrecf53fe2017-09-29 18:28:49 +02002067 if hook.condition is not None:
Paweł Hajdan, Jr78ce24e2017-10-03 17:09:13 +02002068 s.append(' "condition": %r,' % hook.condition)
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002069 s.extend(
Paweł Hajdan, Jrc9364392017-06-14 17:11:56 +02002070 # Hooks run in the parent directory of their dep.
Paweł Hajdan, Jrad30de62017-06-26 18:51:58 +02002071 [' "cwd": "%s",' % os.path.normpath(os.path.dirname(dep.name))] +
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002072 [' "action": ['] +
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +02002073 [' "%s",' % arg for arg in hook.action] +
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002074 [' ]', ' },', '']
2075 )
2076 s.extend([']', ''])
2077 return s
2078
2079
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +02002080def _HooksOsToLines(hooks_os):
2081 """Converts |hooks| list to list of lines for output."""
Paweł Hajdan, Jr5b593352017-06-29 18:37:45 +02002082 if not hooks_os:
2083 return []
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +02002084 s = ['hooks_os = {']
2085 for hook_os, os_hooks in hooks_os.iteritems():
Michael Moss017bcf62017-06-28 15:26:38 -07002086 s.append(' "%s": [' % hook_os)
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +02002087 for dep, hook in os_hooks:
2088 s.extend([
2089 ' # %s' % dep.hierarchy(include_url=False),
2090 ' {',
2091 ])
2092 if hook.name is not None:
2093 s.append(' "name": "%s",' % hook.name)
2094 if hook.pattern is not None:
2095 s.append(' "pattern": "%s",' % hook.pattern)
Paweł Hajdan, Jrecf53fe2017-09-29 18:28:49 +02002096 if hook.condition is not None:
Paweł Hajdan, Jr78ce24e2017-10-03 17:09:13 +02002097 s.append(' "condition": %r,' % hook.condition)
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +02002098 s.extend(
2099 # Hooks run in the parent directory of their dep.
2100 [' "cwd": "%s",' % os.path.normpath(os.path.dirname(dep.name))] +
2101 [' "action": ['] +
2102 [' "%s",' % arg for arg in hook.action] +
2103 [' ]', ' },', '']
2104 )
Michael Moss017bcf62017-06-28 15:26:38 -07002105 s.extend([' ],', ''])
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +02002106 s.extend(['}', ''])
2107 return s
2108
2109
Paweł Hajdan, Jrfb022012017-07-06 18:00:08 +02002110def _VarsToLines(variables):
2111 """Converts |variables| dict to list of lines for output."""
2112 if not variables:
2113 return []
2114 s = ['vars = {']
2115 for key, tup in sorted(variables.iteritems()):
2116 dep, value = tup
2117 s.extend([
2118 ' # %s' % dep.hierarchy(include_url=False),
2119 ' "%s": %r,' % (key, value),
2120 '',
2121 ])
2122 s.extend(['}', ''])
2123 return s
2124
2125
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00002126def CMDgrep(parser, args):
2127 """Greps through git repos managed by gclient.
2128
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002129 Runs 'git grep [args...]' for each module.
2130 """
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00002131 # We can't use optparse because it will try to parse arguments sent
2132 # to git grep and throw an error. :-(
2133 if not args or re.match('(-h|--help)$', args[0]):
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002134 print(
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00002135 'Usage: gclient grep [-j <N>] git-grep-args...\n\n'
2136 'Example: "gclient grep -j10 -A2 RefCountedBase" runs\n"git grep '
2137 '-A2 RefCountedBase" on each of gclient\'s git\nrepos with up to '
2138 '10 jobs.\n\nBonus: page output by appending "|& less -FRSX" to the'
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002139 ' end of your query.',
2140 file=sys.stderr)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00002141 return 1
2142
2143 jobs_arg = ['--jobs=1']
2144 if re.match(r'(-j|--jobs=)\d+$', args[0]):
2145 jobs_arg, args = args[:1], args[1:]
2146 elif re.match(r'(-j|--jobs)$', args[0]):
2147 jobs_arg, args = args[:2], args[2:]
2148
2149 return CMDrecurse(
2150 parser,
2151 jobs_arg + ['--ignore', '--prepend-dir', '--no-progress', '--scm=git',
2152 'git', 'grep', '--null', '--color=Always'] + args)
davidbarr@chromium.org12f944e2012-03-01 02:18:31 +00002153
2154
stip@chromium.orga735da22015-04-29 23:18:20 +00002155def CMDroot(parser, args):
2156 """Outputs the solution root (or current dir if there isn't one)."""
2157 (options, args) = parser.parse_args(args)
2158 client = GClient.LoadCurrentConfig(options)
2159 if client:
2160 print(os.path.abspath(client.root_dir))
2161 else:
2162 print(os.path.abspath('.'))
2163
2164
agablea98a6cd2016-11-15 14:30:10 -08002165@subcommand.usage('[url]')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002166def CMDconfig(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002167 """Creates a .gclient file in the current directory.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00002168
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002169 This specifies the configuration for further commands. After update/sync,
2170 top-level DEPS files in each module are read to determine dependent
2171 modules to operate on as well. If optional [url] parameter is
2172 provided, then configuration is read from a specified Subversion server
2173 URL.
2174 """
szager@chromium.orge2e03202012-07-31 18:05:16 +00002175 # We do a little dance with the --gclientfile option. 'gclient config' is the
2176 # only command where it's acceptable to have both '--gclientfile' and '--spec'
2177 # arguments. So, we temporarily stash any --gclientfile parameter into
2178 # options.output_config_file until after the (gclientfile xor spec) error
2179 # check.
2180 parser.remove_option('--gclientfile')
2181 parser.add_option('--gclientfile', dest='output_config_file',
2182 help='Specify an alternate .gclient file')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002183 parser.add_option('--name',
2184 help='overrides the default name for the solution')
nsylvain@google.comefc80932011-05-31 21:27:56 +00002185 parser.add_option('--deps-file', default='DEPS',
2186 help='overrides the default name for the DEPS file for the'
2187 'main solutions and all sub-dependencies')
smutae7ea312016-07-18 11:59:41 -07002188 parser.add_option('--unmanaged', action='store_true', default=False,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00002189 help='overrides the default behavior to make it possible '
smutae7ea312016-07-18 11:59:41 -07002190 'to have the main solution untouched by gclient '
2191 '(gclient will check out unmanaged dependencies but '
2192 'will never sync them)')
Paweł Hajdan, Jr3ba2a7c2017-10-04 19:24:46 +02002193 parser.add_option('--custom-var', action='append', dest='custom_vars',
2194 default=[],
2195 help='overrides variables; key=value syntax')
szager@chromium.orge2e03202012-07-31 18:05:16 +00002196 parser.set_defaults(config_filename=None)
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002197 (options, args) = parser.parse_args(args)
szager@chromium.orge2e03202012-07-31 18:05:16 +00002198 if options.output_config_file:
2199 setattr(options, 'config_filename', getattr(options, 'output_config_file'))
maruel@chromium.org5fc2a332010-05-26 19:37:15 +00002200 if ((options.spec and args) or len(args) > 2 or
2201 (not options.spec and not args)):
2202 parser.error('Inconsistent arguments. Use either --spec or one or 2 args')
2203
Paweł Hajdan, Jr3ba2a7c2017-10-04 19:24:46 +02002204 custom_vars = {}
2205 for arg in options.custom_vars:
2206 kv = arg.split('=', 1)
2207 if len(kv) != 2:
2208 parser.error('Invalid --custom-var argument: %r' % arg)
2209 custom_vars[kv[0]] = gclient_eval.EvaluateCondition(kv[1], {})
2210
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002211 client = GClient('.', options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002212 if options.spec:
2213 client.SetConfig(options.spec)
2214 else:
maruel@chromium.org1ab7ffc2009-06-03 17:21:37 +00002215 base_url = args[0].rstrip('/')
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00002216 if not options.name:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002217 name = base_url.split('/')[-1]
nsylvain@google.com12649ef2011-06-01 17:11:20 +00002218 if name.endswith('.git'):
2219 name = name[:-4]
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00002220 else:
2221 # specify an alternate relpath for the given URL.
2222 name = options.name
agable@chromium.orgf2214672015-10-27 21:02:48 +00002223 if not os.path.abspath(os.path.join(os.getcwd(), name)).startswith(
2224 os.getcwd()):
2225 parser.error('Do not pass a relative path for --name.')
2226 if any(x in ('..', '.', '/', '\\') for x in name.split(os.sep)):
2227 parser.error('Do not include relative path components in --name.')
2228
nsylvain@google.comefc80932011-05-31 21:27:56 +00002229 deps_file = options.deps_file
agablea98a6cd2016-11-15 14:30:10 -08002230 client.SetDefaultConfig(name, deps_file, base_url,
smutae7ea312016-07-18 11:59:41 -07002231 managed=not options.unmanaged,
Paweł Hajdan, Jr3ba2a7c2017-10-04 19:24:46 +02002232 cache_dir=options.cache_dir,
2233 custom_vars=custom_vars)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002234 client.SaveConfig()
maruel@chromium.org79692d62010-05-14 18:57:13 +00002235 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002236
2237
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002238@subcommand.epilog("""Example:
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002239 gclient pack > patch.txt
2240 generate simple patch for configured client and dependences
2241""")
2242def CMDpack(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002243 """Generates a patch which can be applied at the root of the tree.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00002244
agabled437d762016-10-17 09:35:11 -07002245 Internally, runs 'git diff' on each checked out module and
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002246 dependencies, and performs minimal postprocessing of the output. The
2247 resulting patch is printed to stdout and can be applied to a freshly
2248 checked out tree via 'patch -p0 < patchfile'.
2249 """
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002250 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2251 help='override deps for the specified (comma-separated) '
2252 'platform(s); \'all\' will process all deps_os '
2253 'references')
haitao.feng@intel.com306080c2012-05-04 13:11:29 +00002254 parser.remove_option('--jobs')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002255 (options, args) = parser.parse_args(args)
iannucci@chromium.org50395ea2013-04-04 04:47:42 +00002256 # Force jobs to 1 so the stdout is not annotated with the thread ids
haitao.feng@intel.com306080c2012-05-04 13:11:29 +00002257 options.jobs = 1
kbr@google.comab318592009-09-04 00:54:55 +00002258 client = GClient.LoadCurrentConfig(options)
2259 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002260 raise gclient_utils.Error('client not configured; see \'gclient config\'')
kbr@google.comab318592009-09-04 00:54:55 +00002261 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002262 client.PrintLocationAndContents()
kbr@google.comab318592009-09-04 00:54:55 +00002263 return client.RunOnDeps('pack', args)
2264
2265
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002266def CMDstatus(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002267 """Shows modification status for every dependencies."""
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')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002272 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002273 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002274 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002275 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002276 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002277 client.PrintLocationAndContents()
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002278 return client.RunOnDeps('status', args)
2279
2280
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002281@subcommand.epilog("""Examples:
maruel@chromium.org79692d62010-05-14 18:57:13 +00002282 gclient sync
2283 update files from SCM according to current configuration,
2284 *for modules which have changed since last update or sync*
2285 gclient sync --force
2286 update files from SCM according to current configuration, for
2287 all modules (useful for recovering files deleted from local copy)
2288 gclient sync --revision src@31000
2289 update src directory to r31000
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002290
2291JSON output format:
2292If the --output-json option is specified, the following document structure will
2293be emitted to the provided file. 'null' entries may occur for subprojects which
2294are present in the gclient solution, but were not processed (due to custom_deps,
2295os_deps, etc.)
2296
2297{
2298 "solutions" : {
2299 "<name>": { # <name> is the posix-normalized path to the solution.
agabled437d762016-10-17 09:35:11 -07002300 "revision": [<git id hex string>|null],
2301 "scm": ["git"|null],
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002302 }
2303 }
2304}
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002305""")
2306def CMDsync(parser, args):
2307 """Checkout/update all modules."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002308 parser.add_option('-f', '--force', action='store_true',
2309 help='force update even for unchanged modules')
2310 parser.add_option('-n', '--nohooks', action='store_true',
2311 help='don\'t run hooks after the update is complete')
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00002312 parser.add_option('-p', '--noprehooks', action='store_true',
2313 help='don\'t run pre-DEPS hooks', default=False)
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002314 parser.add_option('-r', '--revision', action='append',
2315 dest='revisions', metavar='REV', default=[],
2316 help='Enforces revision/hash for the solutions with the '
2317 'format src@rev. The src@ part is optional and can be '
2318 'skipped. -r can be used multiple times when .gclient '
2319 'has multiple solutions configured and will work even '
agablea98a6cd2016-11-15 14:30:10 -08002320 'if the src@ part is skipped.')
maruel@chromium.org794207e2013-03-08 15:29:43 +00002321 parser.add_option('--with_branch_heads', action='store_true',
2322 help='Clone git "branch_heads" refspecs in addition to '
2323 'the default refspecs. This adds about 1/2GB to a '
2324 'full checkout. (git only)')
szager@chromium.org8d3348f2014-08-19 22:49:16 +00002325 parser.add_option('--with_tags', action='store_true',
2326 help='Clone git tags in addition to the default refspecs.')
agable2697cd12016-06-28 10:23:53 -07002327 parser.add_option('-H', '--head', action='store_true',
agablea98a6cd2016-11-15 14:30:10 -08002328 help='DEPRECATED: only made sense with safesync urls.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002329 parser.add_option('-D', '--delete_unversioned_trees', action='store_true',
steveblock@chromium.org98e69452012-02-16 16:36:43 +00002330 help='Deletes from the working copy any dependencies that '
2331 'have been removed since the last sync, as long as '
2332 'there are no local modifications. When used with '
2333 '--force, such dependencies are removed even if they '
2334 'have local modifications. When used with --reset, '
2335 'all untracked directories are removed from the '
tnagel@chromium.orga2aaa632014-02-28 21:47:27 +00002336 'working copy, excluding those which are explicitly '
steveblock@chromium.org98e69452012-02-16 16:36:43 +00002337 'ignored in the repository.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002338 parser.add_option('-R', '--reset', action='store_true',
2339 help='resets any local changes before updating (git only)')
bauerb@chromium.org2aad1b22011-07-22 12:00:41 +00002340 parser.add_option('-M', '--merge', action='store_true',
2341 help='merge upstream changes instead of trying to '
2342 'fast-forward or rebase')
dnj@chromium.org5b23e872015-02-20 21:25:57 +00002343 parser.add_option('-A', '--auto_rebase', action='store_true',
2344 help='Automatically rebase repositories against local '
2345 'checkout during update (git only).')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002346 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2347 help='override deps for the specified (comma-separated) '
2348 'platform(s); \'all\' will process all deps_os '
2349 'references')
Paweł Hajdan, Jr357415c2017-07-24 14:35:28 +02002350 # TODO(phajdan.jr): use argparse.SUPPRESS to hide internal flags.
2351 parser.add_option('--do-not-merge-os-specific-entries', action='store_true',
2352 help='INTERNAL ONLY - disables merging of deps_os and '
2353 'hooks_os to dependencies and hooks')
Paweł Hajdan, Jr5ec77132017-08-16 19:21:06 +02002354 parser.add_option('--process-all-deps', action='store_true',
2355 help='Check out all deps, even for different OS-es, '
2356 'or with conditions evaluating to false')
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +00002357 parser.add_option('--upstream', action='store_true',
2358 help='Make repo state match upstream branch.')
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002359 parser.add_option('--output-json',
2360 help='Output a json document to this path containing '
2361 'summary information about the sync.')
primiano@chromium.org5439ea52014-08-06 17:18:18 +00002362 parser.add_option('--no-history', action='store_true',
2363 help='GIT ONLY - Reduces the size/time of the checkout at '
2364 'the cost of no history. Requires Git 1.9+')
hinoka@chromium.org46b87412014-05-15 00:42:05 +00002365 parser.add_option('--shallow', action='store_true',
2366 help='GIT ONLY - Do a shallow clone into the cache dir. '
2367 'Requires Git 1.9+')
e.hakkinen@samsung.come8bc1aa2015-04-08 08:00:37 +00002368 parser.add_option('--no_bootstrap', '--no-bootstrap',
2369 action='store_true',
2370 help='Don\'t bootstrap from Google Storage.')
Vadim Shtayura08049e22017-10-11 00:14:52 +00002371 parser.add_option('--ignore_locks', action='store_true',
2372 help='GIT ONLY - Ignore cache locks.')
iannucci@chromium.org30a07982016-04-07 21:35:19 +00002373 parser.add_option('--break_repo_locks', action='store_true',
2374 help='GIT ONLY - Forcibly remove repo locks (e.g. '
2375 'index.lock). This should only be used if you know for '
2376 'certain that this invocation of gclient is the only '
2377 'thing operating on the git repos (e.g. on a bot).')
Vadim Shtayura08049e22017-10-11 00:14:52 +00002378 parser.add_option('--lock_timeout', type='int', default=5000,
2379 help='GIT ONLY - Deadline (in seconds) to wait for git '
2380 'cache lock to become available. Default is %default.')
agabled437d762016-10-17 09:35:11 -07002381 # TODO(agable): Remove these when the oldest CrOS release milestone is M56.
2382 parser.add_option('-t', '--transitive', action='store_true',
2383 help='DEPRECATED: This is a no-op.')
sdefresne69b1be12016-10-18 05:48:02 -07002384 parser.add_option('-m', '--manually_grab_svn_rev', action='store_true',
agabled437d762016-10-17 09:35:11 -07002385 help='DEPRECATED: This is a no-op.')
Paweł Hajdan, Jr7c7b5592017-05-23 15:06:05 +02002386 # TODO(phajdan.jr): Remove validation options once default (crbug/570091).
Paweł Hajdan, Jr694773d2017-05-29 16:06:23 +02002387 parser.add_option('--validate-syntax', action='store_true', default=True,
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +02002388 help='Validate the .gclient and DEPS syntax')
Paweł Hajdan, Jr7c7b5592017-05-23 15:06:05 +02002389 parser.add_option('--disable-syntax-validation', action='store_false',
2390 dest='validate_syntax',
2391 help='Disable validation of .gclient and DEPS syntax.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002392 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002393 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002394
2395 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002396 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002397
smutae7ea312016-07-18 11:59:41 -07002398 if options.revisions and options.head:
2399 # TODO(maruel): Make it a parser.error if it doesn't break any builder.
2400 print('Warning: you cannot use both --head and --revision')
2401
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002402 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002403 client.PrintLocationAndContents()
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002404 ret = client.RunOnDeps('update', args)
2405 if options.output_json:
2406 slns = {}
2407 for d in client.subtree(True):
2408 normed = d.name.replace('\\', '/').rstrip('/') + '/'
2409 slns[normed] = {
2410 'revision': d.got_revision,
2411 'scm': d.used_scm.name if d.used_scm else None,
hinoka@chromium.org17db9052014-05-10 01:11:29 +00002412 'url': str(d.url) if d.url else None,
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002413 }
2414 with open(options.output_json, 'wb') as f:
2415 json.dump({'solutions': slns}, f)
2416 return ret
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002417
2418
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002419CMDupdate = CMDsync
2420
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002421
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +02002422def CMDvalidate(parser, args):
2423 """Validates the .gclient and DEPS syntax."""
2424 options, args = parser.parse_args(args)
2425 options.validate_syntax = True
2426 client = GClient.LoadCurrentConfig(options)
2427 rv = client.RunOnDeps('validate', args)
2428 if rv == 0:
2429 print('validate: SUCCESS')
2430 else:
2431 print('validate: FAILURE')
2432 return rv
2433
2434
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002435def CMDdiff(parser, args):
2436 """Displays local diff for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002437 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2438 help='override deps for the specified (comma-separated) '
2439 'platform(s); \'all\' will process all deps_os '
2440 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002441 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002442 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002443 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002444 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002445 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002446 client.PrintLocationAndContents()
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002447 return client.RunOnDeps('diff', args)
2448
2449
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002450def CMDrevert(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002451 """Reverts all modifications in every dependencies.
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00002452
2453 That's the nuclear option to get back to a 'clean' state. It removes anything
agabled437d762016-10-17 09:35:11 -07002454 that shows up in git status."""
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')
2459 parser.add_option('-n', '--nohooks', action='store_true',
2460 help='don\'t run hooks after the revert is complete')
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00002461 parser.add_option('-p', '--noprehooks', action='store_true',
2462 help='don\'t run pre-DEPS hooks', default=False)
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +00002463 parser.add_option('--upstream', action='store_true',
2464 help='Make repo state match upstream branch.')
iannucci@chromium.orgbf525dc2016-04-07 22:00:28 +00002465 parser.add_option('--break_repo_locks', action='store_true',
2466 help='GIT ONLY - Forcibly remove repo locks (e.g. '
2467 'index.lock). This should only be used if you know for '
2468 'certain that this invocation of gclient is the only '
2469 'thing operating on the git repos (e.g. on a bot).')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002470 (options, args) = parser.parse_args(args)
2471 # --force is implied.
2472 options.force = True
steveblock@chromium.org98e69452012-02-16 16:36:43 +00002473 options.reset = False
2474 options.delete_unversioned_trees = False
agablec903d732016-07-26 09:07:24 -07002475 options.merge = False
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002476 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002477 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002478 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002479 return client.RunOnDeps('revert', args)
2480
2481
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002482def CMDrunhooks(parser, args):
2483 """Runs hooks for files that have been modified in the local working copy."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002484 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2485 help='override deps for the specified (comma-separated) '
2486 'platform(s); \'all\' will process all deps_os '
2487 'references')
2488 parser.add_option('-f', '--force', action='store_true', default=True,
2489 help='Deprecated. No effect.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002490 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002491 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002492 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002493 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002494 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002495 client.PrintLocationAndContents()
maruel@chromium.org5df6a462009-08-28 18:52:26 +00002496 options.force = True
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002497 options.nohooks = False
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002498 return client.RunOnDeps('runhooks', args)
2499
2500
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002501def CMDrevinfo(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002502 """Outputs revision info mapping for the client and its dependencies.
maruel@chromium.org9eda4112010-06-11 18:56:10 +00002503
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002504 This allows the capture of an overall 'revision' for the source tree that
maruel@chromium.org9eda4112010-06-11 18:56:10 +00002505 can be used to reproduce the same tree in the future. It is only useful for
agabled437d762016-10-17 09:35:11 -07002506 'unpinned dependencies', i.e. DEPS/deps references without a git hash.
2507 A git branch name isn't 'pinned' since the actual commit can change.
maruel@chromium.org9eda4112010-06-11 18:56:10 +00002508 """
2509 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2510 help='override deps for the specified (comma-separated) '
2511 'platform(s); \'all\' will process all deps_os '
2512 'references')
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00002513 parser.add_option('-a', '--actual', action='store_true',
2514 help='gets the actual checked out revisions instead of the '
2515 'ones specified in the DEPS and .gclient files')
maruel@chromium.org9eda4112010-06-11 18:56:10 +00002516 parser.add_option('-s', '--snapshot', action='store_true',
2517 help='creates a snapshot .gclient file of the current '
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00002518 'version of all repositories to reproduce the tree, '
2519 'implies -a')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002520 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002521 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002522 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002523 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002524 client.PrintRevInfo()
maruel@chromium.org79692d62010-05-14 18:57:13 +00002525 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002526
2527
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00002528def CMDverify(parser, args):
2529 """Verifies the DEPS file deps are only from allowed_hosts."""
2530 (options, args) = parser.parse_args(args)
2531 client = GClient.LoadCurrentConfig(options)
2532 if not client:
2533 raise gclient_utils.Error('client not configured; see \'gclient config\'')
2534 client.RunOnDeps(None, [])
2535 # Look at each first-level dependency of this gclient only.
2536 for dep in client.dependencies:
2537 bad_deps = dep.findDepsFromNotAllowedHosts()
2538 if not bad_deps:
2539 continue
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002540 print("There are deps from not allowed hosts in file %s" % dep.deps_file)
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00002541 for bad_dep in bad_deps:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002542 print("\t%s at %s" % (bad_dep.name, bad_dep.url))
2543 print("allowed_hosts:", ', '.join(dep.allowed_hosts))
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00002544 sys.stdout.flush()
2545 raise gclient_utils.Error(
2546 'dependencies from disallowed hosts; check your DEPS file.')
2547 return 0
2548
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002549class OptionParser(optparse.OptionParser):
szager@chromium.orge2e03202012-07-31 18:05:16 +00002550 gclientfile_default = os.environ.get('GCLIENT_FILE', '.gclient')
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002551
2552 def __init__(self, **kwargs):
2553 optparse.OptionParser.__init__(
2554 self, version='%prog ' + __version__, **kwargs)
2555
2556 # Some arm boards have issues with parallel sync.
2557 if platform.machine().startswith('arm'):
2558 jobs = 1
2559 else:
2560 jobs = max(8, gclient_utils.NumLocalCpus())
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002561
2562 self.add_option(
2563 '-j', '--jobs', default=jobs, type='int',
2564 help='Specify how many SCM commands can run in parallel; defaults to '
tnagel@chromium.orga2aaa632014-02-28 21:47:27 +00002565 '%default on this machine')
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002566 self.add_option(
2567 '-v', '--verbose', action='count', default=0,
2568 help='Produces additional output for diagnostics. Can be used up to '
2569 'three times for more logging info.')
2570 self.add_option(
2571 '--gclientfile', dest='config_filename',
2572 help='Specify an alternate %s file' % self.gclientfile_default)
2573 self.add_option(
2574 '--spec',
2575 help='create a gclient file containing the provided string. Due to '
2576 'Cygwin/Python brokenness, it can\'t contain any newlines.')
2577 self.add_option(
Aleksandr Derbenev9e8fb0e2017-08-01 20:18:31 +03002578 '--cache-dir',
2579 help='(git only) Cache all git repos into this dir and do '
2580 'shared clones from the cache, instead of cloning '
2581 'directly from the remote. (experimental)',
2582 default=os.environ.get('GCLIENT_CACHE_DIR'))
2583 self.add_option(
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002584 '--no-nag-max', default=False, action='store_true',
scottmg@chromium.orgf547c802013-09-27 17:55:26 +00002585 help='Ignored for backwards compatibility.')
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002586
2587 def parse_args(self, args=None, values=None):
2588 """Integrates standard options processing."""
2589 options, args = optparse.OptionParser.parse_args(self, args, values)
2590 levels = [logging.ERROR, logging.WARNING, logging.INFO, logging.DEBUG]
2591 logging.basicConfig(
2592 level=levels[min(options.verbose, len(levels) - 1)],
maruel@chromium.org0895b752011-08-26 20:40:33 +00002593 format='%(module)s(%(lineno)d) %(funcName)s:%(message)s')
szager@chromium.orge2e03202012-07-31 18:05:16 +00002594 if options.config_filename and options.spec:
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002595 self.error('Cannot specifiy both --gclientfile and --spec')
rdsmith@chromium.orgd9591f02014-02-05 19:28:20 +00002596 if (options.config_filename and
2597 options.config_filename != os.path.basename(options.config_filename)):
2598 self.error('--gclientfile target must be a filename, not a path')
szager@chromium.orge2e03202012-07-31 18:05:16 +00002599 if not options.config_filename:
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002600 options.config_filename = self.gclientfile_default
maruel@chromium.org0895b752011-08-26 20:40:33 +00002601 options.entries_filename = options.config_filename + '_entries'
2602 if options.jobs < 1:
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002603 self.error('--jobs must be 1 or higher')
maruel@chromium.org0895b752011-08-26 20:40:33 +00002604
2605 # These hacks need to die.
2606 if not hasattr(options, 'revisions'):
2607 # GClient.RunOnDeps expects it even if not applicable.
2608 options.revisions = []
smutae7ea312016-07-18 11:59:41 -07002609 if not hasattr(options, 'head'):
2610 options.head = None
maruel@chromium.org0895b752011-08-26 20:40:33 +00002611 if not hasattr(options, 'nohooks'):
2612 options.nohooks = True
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00002613 if not hasattr(options, 'noprehooks'):
2614 options.noprehooks = True
maruel@chromium.org0895b752011-08-26 20:40:33 +00002615 if not hasattr(options, 'deps_os'):
2616 options.deps_os = None
maruel@chromium.org0895b752011-08-26 20:40:33 +00002617 if not hasattr(options, 'force'):
2618 options.force = None
2619 return (options, args)
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002620
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002621
2622def disable_buffering():
2623 # Make stdout auto-flush so buildbot doesn't kill us during lengthy
2624 # operations. Python as a strong tendency to buffer sys.stdout.
2625 sys.stdout = gclient_utils.MakeFileAutoFlush(sys.stdout)
2626 # Make stdout annotated with the thread ids.
2627 sys.stdout = gclient_utils.MakeFileAnnotated(sys.stdout)
maruel@chromium.org0895b752011-08-26 20:40:33 +00002628
2629
sbc@chromium.org013731e2015-02-26 18:28:43 +00002630def main(argv):
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002631 """Doesn't parse the arguments here, just find the right subcommand to
2632 execute."""
maruel@chromium.org82798cb2012-02-23 18:16:12 +00002633 if sys.hexversion < 0x02060000:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002634 print(
maruel@chromium.org82798cb2012-02-23 18:16:12 +00002635 '\nYour python version %s is unsupported, please upgrade.\n' %
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002636 sys.version.split(' ', 1)[0],
2637 file=sys.stderr)
maruel@chromium.org82798cb2012-02-23 18:16:12 +00002638 return 2
bcwhite@chromium.org6683ab42013-02-11 16:13:47 +00002639 if not sys.executable:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002640 print(
2641 '\nPython cannot find the location of it\'s own executable.\n',
2642 file=sys.stderr)
bcwhite@chromium.org6683ab42013-02-11 16:13:47 +00002643 return 2
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002644 fix_encoding.fix_encoding()
2645 disable_buffering()
iannucci@chromium.org596cd5c2016-04-04 21:34:39 +00002646 setup_color.init()
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002647 dispatcher = subcommand.CommandDispatcher(__name__)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00002648 try:
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002649 return dispatcher.execute(OptionParser(), argv)
xusydoc@chromium.org2fd6c3f2013-05-03 21:57:55 +00002650 except KeyboardInterrupt:
2651 gclient_utils.GClientChildren.KillAllRemainingChildren()
2652 raise
vapier@chromium.orga81a56e2015-11-11 07:56:13 +00002653 except (gclient_utils.Error, subprocess2.CalledProcessError) as e:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002654 print('Error: %s' % str(e), file=sys.stderr)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00002655 return 1
borenet@google.com6a9b1682014-03-24 18:35:23 +00002656 finally:
2657 gclient_utils.PrintWarnings()
sbc@chromium.org013731e2015-02-26 18:28:43 +00002658 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002659
2660
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00002661if '__main__' == __name__:
sbc@chromium.org013731e2015-02-26 18:28:43 +00002662 try:
2663 sys.exit(main(sys.argv[1:]))
2664 except KeyboardInterrupt:
2665 sys.stderr.write('interrupted\n')
2666 sys.exit(1)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002667
2668# vim: ts=2:sw=2:tw=80:et: