blob: e46556ed0853c3057fc539160bed34e35163d1b5 [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
184 def matches(self, file_list):
185 """Returns true if the pattern matches any of files in the list."""
186 if not self._pattern:
187 return True
188 pattern = re.compile(self._pattern)
189 return bool([f for f in file_list if pattern.search(f)])
190
191 def run(self, root):
Paweł Hajdan, Jr032d5452017-06-22 20:43:53 +0200192 """Executes the hook's command (provided the condition is met)."""
193 if (self._condition and
194 not gclient_eval.EvaluateCondition(self._condition, self._variables)):
195 return
196
Paweł Hajdan, Jrfc6196b2017-07-27 13:15:25 +0200197 cmd = [arg.format(**self._variables) for arg in self._action]
198
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200199 if cmd[0] == 'python':
200 # If the hook specified "python" as the first item, the action is a
201 # Python script. Run it by starting a new copy of the same
202 # interpreter.
203 cmd[0] = sys.executable
Paweł Hajdan, Jrc9364392017-06-14 17:11:56 +0200204
205 cwd = root
206 if self._cwd:
207 cwd = os.path.join(cwd, self._cwd)
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200208 try:
209 start_time = time.time()
210 gclient_utils.CheckCallAndFilterAndHeader(
Paweł Hajdan, Jrc9364392017-06-14 17:11:56 +0200211 cmd, cwd=cwd, always=True)
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200212 except (gclient_utils.Error, subprocess2.CalledProcessError) as e:
213 # Use a discrete exit status code of 2 to indicate that a hook action
214 # failed. Users of this script may wish to treat hook action failures
215 # differently from VC failures.
216 print('Error: %s' % str(e), file=sys.stderr)
217 sys.exit(2)
218 finally:
219 elapsed_time = time.time() - start_time
220 if elapsed_time > 10:
221 print("Hook '%s' took %.2f secs" % (
222 gclient_utils.CommandToStr(cmd), elapsed_time))
223
224
Paweł Hajdan, Jrfc6196b2017-07-27 13:15:25 +0200225class DependencySettings(object):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000226 """Immutable configuration settings."""
227 def __init__(
Paweł Hajdan, Jrfc6196b2017-07-27 13:15:25 +0200228 self, parent, raw_url, url, managed, custom_deps, custom_vars,
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +0200229 custom_hooks, deps_file, should_process, relative,
230 condition, condition_value):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000231 # These are not mutable:
232 self._parent = parent
mmoss@chromium.org8f93f792014-08-26 23:24:09 +0000233 self._deps_file = deps_file
Paweł Hajdan, Jrfc6196b2017-07-27 13:15:25 +0200234 self._raw_url = raw_url
maruel@chromium.org064186c2011-09-27 23:53:33 +0000235 self._url = url
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +0200236 # The condition as string (or None). Useful to keep e.g. for flatten.
237 self._condition = condition
238 # Boolean value of the condition. If there's no condition, just True.
239 self._condition_value = condition_value
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000240 # 'managed' determines whether or not this dependency is synced/updated by
241 # gclient after gclient checks it out initially. The difference between
242 # 'managed' and 'should_process' is that the user specifies 'managed' via
smutae7ea312016-07-18 11:59:41 -0700243 # the --unmanaged command-line flag or a .gclient config, where
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000244 # 'should_process' is dynamically set by gclient if it goes over its
245 # recursion limit and controls gclient's behavior so it does not misbehave.
246 self._managed = managed
247 self._should_process = should_process
agabledce6ddc2016-09-08 10:02:16 -0700248 # If this is a recursed-upon sub-dependency, and the parent has
249 # use_relative_paths set, then this dependency should check out its own
250 # dependencies relative to that parent's path for this, rather than
251 # relative to the .gclient file.
252 self._relative = relative
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +0000253 # This is a mutable value which has the list of 'target_os' OSes listed in
254 # the current deps file.
255 self.local_target_os = None
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000256
257 # These are only set in .gclient and not in DEPS files.
258 self._custom_vars = custom_vars or {}
259 self._custom_deps = custom_deps or {}
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000260 self._custom_hooks = custom_hooks or []
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000261
maruel@chromium.org064186c2011-09-27 23:53:33 +0000262 # Post process the url to remove trailing slashes.
263 if isinstance(self._url, basestring):
264 # urls are sometime incorrectly written as proto://host/path/@rev. Replace
265 # it to proto://host/path@rev.
maruel@chromium.org064186c2011-09-27 23:53:33 +0000266 self._url = self._url.replace('/@', '@')
Paweł Hajdan, Jr7e9303b2017-05-23 14:38:27 +0200267 elif not isinstance(self._url, (None.__class__)):
maruel@chromium.org064186c2011-09-27 23:53:33 +0000268 raise gclient_utils.Error(
Paweł Hajdan, Jr7e9303b2017-05-23 14:38:27 +0200269 ('dependency url must be either string or None, '
270 'instead of %s') % self._url.__class__.__name__)
mmoss@chromium.orgd0b272b2013-01-30 23:55:33 +0000271 # Make any deps_file path platform-appropriate.
272 for sep in ['/', '\\']:
273 self._deps_file = self._deps_file.replace(sep, os.sep)
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000274
275 @property
276 def deps_file(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000277 return self._deps_file
278
279 @property
280 def managed(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000281 return self._managed
282
283 @property
284 def parent(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000285 return self._parent
286
287 @property
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000288 def root(self):
289 """Returns the root node, a GClient object."""
290 if not self.parent:
291 # This line is to signal pylint that it could be a GClient instance.
292 return self or GClient(None, None)
293 return self.parent.root
294
295 @property
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000296 def should_process(self):
297 """True if this dependency should be processed, i.e. checked out."""
298 return self._should_process
299
300 @property
301 def custom_vars(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000302 return self._custom_vars.copy()
303
304 @property
305 def custom_deps(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000306 return self._custom_deps.copy()
307
maruel@chromium.org064186c2011-09-27 23:53:33 +0000308 @property
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000309 def custom_hooks(self):
310 return self._custom_hooks[:]
311
312 @property
Paweł Hajdan, Jrfc6196b2017-07-27 13:15:25 +0200313 def raw_url(self):
314 """URL before variable expansion."""
315 return self._raw_url
316
317 @property
maruel@chromium.org064186c2011-09-27 23:53:33 +0000318 def url(self):
Paweł Hajdan, Jrfc6196b2017-07-27 13:15:25 +0200319 """URL after variable expansion."""
maruel@chromium.org064186c2011-09-27 23:53:33 +0000320 return self._url
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000321
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000322 @property
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +0200323 def condition(self):
324 return self._condition
325
326 @property
327 def condition_value(self):
328 return self._condition_value
329
330 @property
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +0000331 def target_os(self):
332 if self.local_target_os is not None:
333 return tuple(set(self.local_target_os).union(self.parent.target_os))
334 else:
335 return self.parent.target_os
336
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000337 def get_custom_deps(self, name, url):
338 """Returns a custom deps if applicable."""
339 if self.parent:
340 url = self.parent.get_custom_deps(name, url)
341 # None is a valid return value to disable a dependency.
342 return self.custom_deps.get(name, url)
343
maruel@chromium.org064186c2011-09-27 23:53:33 +0000344
345class Dependency(gclient_utils.WorkItem, DependencySettings):
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000346 """Object that represents a dependency checkout."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000347
Paweł Hajdan, Jrfc6196b2017-07-27 13:15:25 +0200348 def __init__(self, parent, name, raw_url, url, managed, custom_deps,
agabledce6ddc2016-09-08 10:02:16 -0700349 custom_vars, custom_hooks, deps_file, should_process,
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +0200350 relative, condition, condition_value):
maruel@chromium.org6ca8bf82011-09-19 23:04:30 +0000351 gclient_utils.WorkItem.__init__(self, name)
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000352 DependencySettings.__init__(
Paweł Hajdan, Jrfc6196b2017-07-27 13:15:25 +0200353 self, parent, raw_url, url, managed, custom_deps, custom_vars,
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +0200354 custom_hooks, deps_file, should_process, relative,
355 condition, condition_value)
maruel@chromium.org68988972011-09-20 14:11:42 +0000356
357 # This is in both .gclient and DEPS files:
maruel@chromium.org064186c2011-09-27 23:53:33 +0000358 self._deps_hooks = []
maruel@chromium.org68988972011-09-20 14:11:42 +0000359
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000360 self._pre_deps_hooks = []
361
maruel@chromium.org68988972011-09-20 14:11:42 +0000362 # Calculates properties:
maruel@chromium.org064186c2011-09-27 23:53:33 +0000363 self._parsed_url = None
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +0000364 self._dependencies = []
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +0200365 self._vars = {}
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +0200366 self._os_dependencies = {}
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +0200367 self._os_deps_hooks = {}
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200368
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000369 # A cache of the files affected by the current operation, necessary for
370 # hooks.
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000371 self._file_list = []
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000372 # List of host names from which dependencies are allowed.
373 # Default is an empty set, meaning unspecified in DEPS file, and hence all
374 # hosts will be allowed. Non-empty set means whitelist of hosts.
375 # allowed_hosts var is scoped to its DEPS file, and so it isn't recursive.
376 self._allowed_hosts = frozenset()
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +0200377 # Spec for .gni output to write (if any).
378 self._gn_args_file = None
379 self._gn_args = []
maruel@chromium.org85c2a192010-07-22 21:14:43 +0000380 # If it is not set to True, the dependency wasn't processed for its child
381 # dependency, i.e. its DEPS wasn't read.
maruel@chromium.org064186c2011-09-27 23:53:33 +0000382 self._deps_parsed = False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000383 # This dependency has been processed, i.e. checked out
maruel@chromium.org064186c2011-09-27 23:53:33 +0000384 self._processed = False
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000385 # This dependency had its pre-DEPS hooks run
386 self._pre_deps_hooks_ran = False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000387 # This dependency had its hook run
maruel@chromium.org064186c2011-09-27 23:53:33 +0000388 self._hooks_ran = False
kustermann@google.coma692e8f2013-04-18 08:32:04 +0000389 # This is the scm used to checkout self.url. It may be used by dependencies
390 # to get the datetime of the revision we checked out.
391 self._used_scm = None
szager@chromium.org4ad264b2014-05-20 04:43:47 +0000392 self._used_revision = None
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +0000393 # The actual revision we ended up getting, or None if that information is
394 # unavailable
395 self._got_revision = None
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000396
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000397 # This is a mutable value that overrides the normal recursion limit for this
398 # dependency. It is read from the actual DEPS file so cannot be set on
399 # class instantiation.
400 self.recursion_override = None
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000401 # recursedeps is a mutable value that selectively overrides the default
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000402 # 'no recursion' setting on a dep-by-dep basis. It will replace
403 # recursion_override.
iannucci@chromium.orgafa11ac2016-05-04 22:17:34 +0000404 #
405 # It will be a dictionary of {deps_name: {"deps_file": depfile_name}} or
406 # None.
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000407 self.recursedeps = None
hinoka885e5b12016-06-08 14:40:09 -0700408 # This is inherited from WorkItem. We want the URL to be a resource.
409 if url and isinstance(url, basestring):
410 # The url is usually given to gclient either as https://blah@123
qyearsley12fa6ff2016-08-24 09:18:40 -0700411 # or just https://blah. The @123 portion is irrelevant.
hinoka885e5b12016-06-08 14:40:09 -0700412 self.resources.append(url.split('@')[0])
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000413
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000414 if not self.name and self.parent:
415 raise gclient_utils.Error('Dependency without name')
416
maruel@chromium.org470b5432011-10-11 18:18:19 +0000417 @property
418 def requirements(self):
419 """Calculate the list of requirements."""
420 requirements = set()
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000421 # self.parent is implicitly a requirement. This will be recursive by
422 # definition.
423 if self.parent and self.parent.name:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000424 requirements.add(self.parent.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000425
426 # For a tree with at least 2 levels*, the leaf node needs to depend
427 # on the level higher up in an orderly way.
428 # This becomes messy for >2 depth as the DEPS file format is a dictionary,
429 # thus unsorted, while the .gclient format is a list thus sorted.
430 #
431 # * _recursion_limit is hard coded 2 and there is no hope to change this
432 # value.
433 #
434 # Interestingly enough, the following condition only works in the case we
435 # want: self is a 2nd level node. 3nd level node wouldn't need this since
436 # they already have their parent as a requirement.
maruel@chromium.org470b5432011-10-11 18:18:19 +0000437 if self.parent and self.parent.parent and not self.parent.parent.parent:
438 requirements |= set(i.name for i in self.root.dependencies if i.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000439
maruel@chromium.org470b5432011-10-11 18:18:19 +0000440 if self.name:
441 requirements |= set(
442 obj.name for obj in self.root.subtree(False)
443 if (obj is not self
444 and obj.name and
445 self.name.startswith(posixpath.join(obj.name, ''))))
446 requirements = tuple(sorted(requirements))
447 logging.info('Dependency(%s).requirements = %s' % (self.name, requirements))
448 return requirements
449
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000450 @property
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000451 def try_recursedeps(self):
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000452 """Returns False if recursion_override is ever specified."""
453 if self.recursion_override is not None:
454 return False
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000455 return self.parent.try_recursedeps
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000456
457 @property
458 def recursion_limit(self):
459 """Returns > 0 if this dependency is not too recursed to be processed."""
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000460 # We continue to support the absence of recursedeps until tools and DEPS
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000461 # using recursion_override are updated.
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000462 if self.try_recursedeps and self.parent.recursedeps != None:
463 if self.name in self.parent.recursedeps:
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000464 return 1
465
466 if self.recursion_override is not None:
467 return self.recursion_override
468 return max(self.parent.recursion_limit - 1, 0)
469
maruel@chromium.org470b5432011-10-11 18:18:19 +0000470 def verify_validity(self):
471 """Verifies that this Dependency is fine to add as a child of another one.
472
473 Returns True if this entry should be added, False if it is a duplicate of
474 another entry.
475 """
476 logging.info('Dependency(%s).verify_validity()' % self.name)
477 if self.name in [s.name for s in self.parent.dependencies]:
478 raise gclient_utils.Error(
479 'The same name "%s" appears multiple times in the deps section' %
480 self.name)
481 if not self.should_process:
482 # Return early, no need to set requirements.
483 return True
484
485 # This require a full tree traversal with locks.
486 siblings = [d for d in self.root.subtree(False) if d.name == self.name]
487 for sibling in siblings:
maruel@chromium.orgb848d5b2012-10-10 23:25:50 +0000488 self_url = self.LateOverride(self.url)
489 sibling_url = sibling.LateOverride(sibling.url)
490 # Allow to have only one to be None or ''.
491 if self_url != sibling_url and bool(self_url) == bool(sibling_url):
maruel@chromium.org470b5432011-10-11 18:18:19 +0000492 raise gclient_utils.Error(
maruel@chromium.orgb848d5b2012-10-10 23:25:50 +0000493 ('Dependency %s specified more than once:\n'
494 ' %s [%s]\n'
495 'vs\n'
496 ' %s [%s]') % (
497 self.name,
498 sibling.hierarchy(),
499 sibling_url,
500 self.hierarchy(),
501 self_url))
maruel@chromium.org470b5432011-10-11 18:18:19 +0000502 # In theory we could keep it as a shadow of the other one. In
503 # practice, simply ignore it.
504 logging.warn('Won\'t process duplicate dependency %s' % sibling)
505 return False
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000506 return True
maruel@chromium.org064186c2011-09-27 23:53:33 +0000507
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000508 def LateOverride(self, url):
Paweł Hajdan, Jr7e9303b2017-05-23 14:38:27 +0200509 """Resolves the parsed url from url."""
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000510 assert self.parsed_url == None or not self.should_process, self.parsed_url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000511 parsed_url = self.get_custom_deps(self.name, url)
512 if parsed_url != url:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000513 logging.info(
514 'Dependency(%s).LateOverride(%s) -> %s' %
515 (self.name, url, parsed_url))
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000516 return parsed_url
517
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000518 if isinstance(url, basestring):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000519 parsed_url = urlparse.urlparse(url)
scr@chromium.orgf1eccaf2014-04-11 15:51:33 +0000520 if (not parsed_url[0] and
521 not re.match(r'^\w+\@[\w\.-]+\:[\w\/]+', parsed_url[2])):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000522 # A relative url. Fetch the real base.
523 path = parsed_url[2]
524 if not path.startswith('/'):
525 raise gclient_utils.Error(
526 'relative DEPS entry \'%s\' must begin with a slash' % url)
527 # Create a scm just to query the full url.
528 parent_url = self.parent.parsed_url
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000529 scm = gclient_scm.CreateSCM(
530 parent_url, self.root.root_dir, None, self.outbuf)
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000531 parsed_url = scm.FullUrlForRelativeUrl(url)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000532 else:
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000533 parsed_url = url
maruel@chromium.org470b5432011-10-11 18:18:19 +0000534 logging.info(
535 'Dependency(%s).LateOverride(%s) -> %s' %
536 (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000537 return parsed_url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000538
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000539 if url is None:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000540 logging.info(
541 'Dependency(%s).LateOverride(%s) -> %s' % (self.name, url, url))
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000542 return url
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000543
544 raise gclient_utils.Error('Unknown url type')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000545
bratell@opera.comed2b4fe2013-12-16 14:34:12 +0000546 @staticmethod
547 def MergeWithOsDeps(deps, deps_os, target_os_list):
548 """Returns a new "deps" structure that is the deps sent in updated
549 with information from deps_os (the deps_os section of the DEPS
550 file) that matches the list of target os."""
bratell@opera.comed2b4fe2013-12-16 14:34:12 +0000551 new_deps = deps.copy()
Paweł Hajdan, Jrfd0057e2017-06-21 14:20:21 +0200552 for dep_os, os_deps in deps_os.iteritems():
553 for key, value in os_deps.iteritems():
554 if value is None:
555 # Make this condition very visible, so it's not a silent failure.
556 # It's unclear how to support None override in deps_os.
557 logging.error('Ignoring %r:%r in %r deps_os', key, value, dep_os)
558 continue
559
560 # Normalize value to be a dict which contains |should_process| metadata.
561 if isinstance(value, basestring):
562 value = {'url': value}
563 assert isinstance(value, collections.Mapping), (key, value)
564 value['should_process'] = dep_os in target_os_list
565
566 # Handle collisions/overrides.
567 if key in new_deps and new_deps[key] != value:
568 # Normalize the existing new_deps entry.
569 if isinstance(new_deps[key], basestring):
570 new_deps[key] = {'url': new_deps[key]}
571 assert isinstance(new_deps[key],
572 collections.Mapping), (key, new_deps[key])
573
574 # It's OK if the "override" sets the key to the same value.
575 # This is mostly for legacy reasons to keep existing DEPS files
576 # working. Often mac/ios and unix/android will do this.
577 if value['url'] != new_deps[key]['url']:
578 raise gclient_utils.Error(
579 ('Value from deps_os (%r; %r: %r) conflicts with existing deps '
580 'entry (%r).') % (dep_os, key, value, new_deps[key]))
581
582 # We'd otherwise overwrite |should_process| metadata, but a dep should
583 # be processed if _any_ of its references call for that.
584 value['should_process'] = (
585 value['should_process'] or
586 new_deps[key].get('should_process', True))
587
588 new_deps[key] = value
589
bratell@opera.comed2b4fe2013-12-16 14:34:12 +0000590 return new_deps
591
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200592 def _postprocess_deps(self, deps, rel_prefix):
593 """Performs post-processing of deps compared to what's in the DEPS file."""
Paweł Hajdan, Jr4426eaf2017-06-13 12:25:47 +0200594 # Make sure the dict is mutable, e.g. in case it's frozen.
595 deps = dict(deps)
596
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200597 # If a line is in custom_deps, but not in the solution, we want to append
598 # this line to the solution.
599 for d in self.custom_deps:
600 if d not in deps:
601 deps[d] = self.custom_deps[d]
602
603 if rel_prefix:
604 logging.warning('use_relative_paths enabled.')
605 rel_deps = {}
606 for d, url in deps.items():
607 # normpath is required to allow DEPS to use .. in their
608 # dependency local path.
609 rel_deps[os.path.normpath(os.path.join(rel_prefix, d))] = url
610 logging.warning('Updating deps by prepending %s.', rel_prefix)
611 deps = rel_deps
612
613 return deps
614
615 def _deps_to_objects(self, deps, use_relative_paths):
616 """Convert a deps dict to a dict of Dependency objects."""
617 deps_to_add = []
618 for name, dep_value in deps.iteritems():
619 should_process = self.recursion_limit and self.should_process
620 deps_file = self.deps_file
621 if self.recursedeps is not None:
622 ent = self.recursedeps.get(name)
623 if ent is not None:
624 deps_file = ent['deps_file']
625 if dep_value is None:
626 continue
627 condition = None
628 condition_value = True
629 if isinstance(dep_value, basestring):
Paweł Hajdan, Jrfc6196b2017-07-27 13:15:25 +0200630 raw_url = dep_value
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200631 else:
632 # This should be guaranteed by schema checking in gclient_eval.
633 assert isinstance(dep_value, collections.Mapping)
Paweł Hajdan, Jrfc6196b2017-07-27 13:15:25 +0200634 raw_url = dep_value['url']
Paweł Hajdan, Jrfd0057e2017-06-21 14:20:21 +0200635 # Take into account should_process metadata set by MergeWithOsDeps.
636 should_process = (should_process and
637 dep_value.get('should_process', True))
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200638 condition = dep_value.get('condition')
Paweł Hajdan, Jrfc6196b2017-07-27 13:15:25 +0200639
640 url = raw_url.format(**self.get_vars())
641
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200642 if condition:
Paweł Hajdan, Jrd3790252017-07-03 21:06:24 +0200643 condition_value = gclient_eval.EvaluateCondition(
644 condition, self.get_vars())
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200645 should_process = should_process and condition_value
646 deps_to_add.append(Dependency(
Paweł Hajdan, Jrfc6196b2017-07-27 13:15:25 +0200647 self, name, raw_url, url, None, None, self.custom_vars, None,
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200648 deps_file, should_process, use_relative_paths, condition,
649 condition_value))
650 deps_to_add.sort(key=lambda x: x.name)
651 return deps_to_add
652
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000653 def ParseDepsFile(self):
maruel@chromium.org271375b2010-06-23 19:17:38 +0000654 """Parses the DEPS file for this dependency."""
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000655 assert not self.deps_parsed
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000656 assert not self.dependencies
iannucci@chromium.org0c3f3052014-05-22 00:29:22 +0000657
658 deps_content = None
cmp@chromium.org76ce73c2014-07-02 00:13:18 +0000659
660 # First try to locate the configured deps file. If it's missing, fallback
661 # to DEPS.
662 deps_files = [self.deps_file]
663 if 'DEPS' not in deps_files:
664 deps_files.append('DEPS')
665 for deps_file in deps_files:
666 filepath = os.path.join(self.root.root_dir, self.name, deps_file)
667 if os.path.isfile(filepath):
668 logging.info(
669 'ParseDepsFile(%s): %s file found at %s', self.name, deps_file,
670 filepath)
671 break
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000672 logging.info(
cmp@chromium.org76ce73c2014-07-02 00:13:18 +0000673 'ParseDepsFile(%s): No %s file found at %s', self.name, deps_file,
674 filepath)
675
676 if os.path.isfile(filepath):
maruel@chromium.org46304292010-10-28 11:42:00 +0000677 deps_content = gclient_utils.FileRead(filepath)
cmp@chromium.org76ce73c2014-07-02 00:13:18 +0000678 logging.debug('ParseDepsFile(%s) read:\n%s', self.name, deps_content)
iannucci@chromium.org0c3f3052014-05-22 00:29:22 +0000679
680 local_scope = {}
681 if deps_content:
Paweł Hajdan, Jrf1587bf2017-06-20 21:19:07 +0200682 global_scope = {
Paweł Hajdan, Jrfc6196b2017-07-27 13:15:25 +0200683 'Var': lambda var_name: '{%s}' % var_name,
Paweł Hajdan, Jrf1587bf2017-06-20 21:19:07 +0200684 'deps_os': {},
685 }
maruel@chromium.org46304292010-10-28 11:42:00 +0000686 # Eval the content.
687 try:
Paweł Hajdan, Jrc485d5a2017-06-02 12:08:09 +0200688 if self._get_option('validate_syntax', False):
689 gclient_eval.Exec(deps_content, global_scope, local_scope, filepath)
690 else:
691 exec(deps_content, global_scope, local_scope)
vapier@chromium.orga81a56e2015-11-11 07:56:13 +0000692 except SyntaxError as e:
maruel@chromium.org46304292010-10-28 11:42:00 +0000693 gclient_utils.SyntaxErrorToError(filepath, e)
iannucci@chromium.org0c3f3052014-05-22 00:29:22 +0000694
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000695 if 'allowed_hosts' in local_scope:
696 try:
697 self._allowed_hosts = frozenset(local_scope.get('allowed_hosts'))
698 except TypeError: # raised if non-iterable
699 pass
700 if not self._allowed_hosts:
701 logging.warning("allowed_hosts is specified but empty %s",
702 self._allowed_hosts)
703 raise gclient_utils.Error(
704 'ParseDepsFile(%s): allowed_hosts must be absent '
705 'or a non-empty iterable' % self.name)
706
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +0200707 self._gn_args_file = local_scope.get('gclient_gn_args_file')
708 self._gn_args = local_scope.get('gclient_gn_args', [])
709
Paweł Hajdan, Jr1407d002017-08-01 20:01:01 +0200710 self._vars = local_scope.get('vars', {})
711 if self.parent:
712 for key, value in self.parent.get_vars().iteritems():
713 if key in self._vars:
714 self._vars[key] = value
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200715 # Since we heavily post-process things, freeze ones which should
716 # reflect original state of DEPS.
Paweł Hajdan, Jr1407d002017-08-01 20:01:01 +0200717 self._vars = gclient_utils.freeze(self._vars)
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200718
719 # If use_relative_paths is set in the DEPS file, regenerate
720 # the dictionary using paths relative to the directory containing
721 # the DEPS file. Also update recursedeps if use_relative_paths is
722 # enabled.
723 # If the deps file doesn't set use_relative_paths, but the parent did
724 # (and therefore set self.relative on this Dependency object), then we
725 # want to modify the deps and recursedeps by prepending the parent
726 # directory of this dependency.
727 use_relative_paths = local_scope.get('use_relative_paths', False)
728 rel_prefix = None
729 if use_relative_paths:
730 rel_prefix = self.name
731 elif self._relative:
732 rel_prefix = os.path.dirname(self.name)
733
Paweł Hajdan, Jrfc6196b2017-07-27 13:15:25 +0200734 deps = {}
735 for key, value in local_scope.get('deps', {}).iteritems():
736 deps[key.format(**self.get_vars())] = value
737
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200738 if 'recursion' in local_scope:
739 self.recursion_override = local_scope.get('recursion')
740 logging.warning(
741 'Setting %s recursion to %d.', self.name, self.recursion_limit)
742 self.recursedeps = None
743 if 'recursedeps' in local_scope:
744 self.recursedeps = {}
745 for ent in local_scope['recursedeps']:
746 if isinstance(ent, basestring):
747 self.recursedeps[ent] = {"deps_file": self.deps_file}
748 else: # (depname, depsfilename)
749 self.recursedeps[ent[0]] = {"deps_file": ent[1]}
750 logging.warning('Found recursedeps %r.', repr(self.recursedeps))
751
752 if rel_prefix:
753 logging.warning('Updating recursedeps by prepending %s.', rel_prefix)
754 rel_deps = {}
755 for depname, options in self.recursedeps.iteritems():
756 rel_deps[
757 os.path.normpath(os.path.join(rel_prefix, depname))] = options
758 self.recursedeps = rel_deps
759
760 # If present, save 'target_os' in the local_target_os property.
761 if 'target_os' in local_scope:
762 self.local_target_os = local_scope['target_os']
763 # load os specific dependencies if defined. these dependencies may
764 # override or extend the values defined by the 'deps' member.
765 target_os_list = self.target_os
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +0200766 if 'deps_os' in local_scope:
767 for dep_os, os_deps in local_scope['deps_os'].iteritems():
768 self._os_dependencies[dep_os] = self._deps_to_objects(
769 self._postprocess_deps(os_deps, rel_prefix), use_relative_paths)
Paweł Hajdan, Jr357415c2017-07-24 14:35:28 +0200770 if target_os_list and not self._get_option(
771 'do_not_merge_os_specific_entries', False):
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +0200772 deps = self.MergeWithOsDeps(
773 deps, local_scope['deps_os'], target_os_list)
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200774
775 deps_to_add = self._deps_to_objects(
776 self._postprocess_deps(deps, rel_prefix), use_relative_paths)
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000777
778 # override named sets of hooks by the custom hooks
779 hooks_to_run = []
780 hook_names_to_suppress = [c.get('name', '') for c in self.custom_hooks]
781 for hook in local_scope.get('hooks', []):
782 if hook.get('name', '') not in hook_names_to_suppress:
783 hooks_to_run.append(hook)
Scott Grahamc4826742017-05-11 16:59:23 -0700784 if 'hooks_os' in local_scope and target_os_list:
785 hooks_os = local_scope['hooks_os']
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +0200786
787 # Keep original contents of hooks_os for flatten.
788 for hook_os, os_hooks in hooks_os.iteritems():
789 self._os_deps_hooks[hook_os] = [
Paweł Hajdan, Jrd3790252017-07-03 21:06:24 +0200790 Hook.from_dict(hook, variables=self.get_vars())
791 for hook in os_hooks]
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +0200792
Scott Grahamc4826742017-05-11 16:59:23 -0700793 # Specifically append these to ensure that hooks_os run after hooks.
Paweł Hajdan, Jr357415c2017-07-24 14:35:28 +0200794 if not self._get_option('do_not_merge_os_specific_entries', False):
795 for the_target_os in target_os_list:
796 the_target_os_hooks = hooks_os.get(the_target_os, [])
797 hooks_to_run.extend(the_target_os_hooks)
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000798
799 # add the replacements and any additions
800 for hook in self.custom_hooks:
801 if 'action' in hook:
802 hooks_to_run.append(hook)
803
Dirk Prankeda3a29e2017-02-27 15:29:36 -0800804 if self.recursion_limit:
Paweł Hajdan, Jr032d5452017-06-22 20:43:53 +0200805 self._pre_deps_hooks = [
Paweł Hajdan, Jrd3790252017-07-03 21:06:24 +0200806 Hook.from_dict(hook, variables=self.get_vars()) for hook in
Paweł Hajdan, Jr032d5452017-06-22 20:43:53 +0200807 local_scope.get('pre_deps_hooks', [])]
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000808
Paweł Hajdan, Jr357415c2017-07-24 14:35:28 +0200809 self.add_dependencies_and_close(deps_to_add, hooks_to_run)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000810 logging.info('ParseDepsFile(%s) done' % self.name)
811
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +0200812 def _get_option(self, attr, default):
813 obj = self
814 while not hasattr(obj, '_options'):
815 obj = obj.parent
816 return getattr(obj._options, attr, default)
817
Paweł Hajdan, Jr357415c2017-07-24 14:35:28 +0200818 def add_dependencies_and_close(self, deps_to_add, hooks):
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000819 """Adds the dependencies, hooks and mark the parsing as done."""
maruel@chromium.orgb9be0652011-10-14 18:05:40 +0000820 for dep in deps_to_add:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000821 if dep.verify_validity():
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000822 self.add_dependency(dep)
Paweł Hajdan, Jr032d5452017-06-22 20:43:53 +0200823 self._mark_as_parsed(
Paweł Hajdan, Jr357415c2017-07-24 14:35:28 +0200824 [Hook.from_dict(h, variables=self.get_vars()) for h in hooks])
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000825
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000826 def findDepsFromNotAllowedHosts(self):
827 """Returns a list of depenecies from not allowed hosts.
828
829 If allowed_hosts is not set, allows all hosts and returns empty list.
830 """
831 if not self._allowed_hosts:
832 return []
833 bad_deps = []
834 for dep in self._dependencies:
szager@chromium.orgbd772dd2014-11-05 18:43:08 +0000835 # Don't enforce this for custom_deps.
836 if dep.name in self._custom_deps:
837 continue
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000838 if isinstance(dep.url, basestring):
839 parsed_url = urlparse.urlparse(dep.url)
840 if parsed_url.netloc and parsed_url.netloc not in self._allowed_hosts:
841 bad_deps.append(dep)
842 return bad_deps
843
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +0000844 # Arguments number differs from overridden method
Quinten Yearsleyb2cc4a92016-12-15 13:53:26 -0800845 # pylint: disable=arguments-differ
maruel@chromium.org3742c842010-09-09 19:27:14 +0000846 def run(self, revision_overrides, command, args, work_queue, options):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000847 """Runs |command| then parse the DEPS file."""
maruel@chromium.org470b5432011-10-11 18:18:19 +0000848 logging.info('Dependency(%s).run()' % self.name)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000849 assert self._file_list == []
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000850 if not self.should_process:
851 return
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000852 # When running runhooks, there's no need to consult the SCM.
853 # All known hooks are expected to run unconditionally regardless of working
854 # copy state, so skip the SCM status check.
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +0200855 run_scm = command not in (
856 'flatten', 'runhooks', 'recurse', 'validate', None)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000857 parsed_url = self.LateOverride(self.url)
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000858 file_list = [] if not options.nohooks else None
szager@chromium.org3a3608d2014-10-22 21:13:52 +0000859 revision_override = revision_overrides.pop(self.name, None)
Dave Tubbda9712017-06-01 15:10:53 -0700860 if not revision_override and parsed_url:
861 revision_override = revision_overrides.get(parsed_url.split('@')[0], None)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000862 if run_scm and parsed_url:
agabled437d762016-10-17 09:35:11 -0700863 # Create a shallow copy to mutate revision.
864 options = copy.copy(options)
865 options.revision = revision_override
866 self._used_revision = options.revision
867 self._used_scm = gclient_scm.CreateSCM(
868 parsed_url, self.root.root_dir, self.name, self.outbuf,
869 out_cb=work_queue.out_cb)
870 self._got_revision = self._used_scm.RunCommand(command, options, args,
871 file_list)
872 if file_list:
873 file_list = [os.path.join(self.name, f.strip()) for f in file_list]
maruel@chromium.org68988972011-09-20 14:11:42 +0000874
875 # TODO(phajdan.jr): We should know exactly when the paths are absolute.
876 # Convert all absolute paths to relative.
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000877 for i in range(len(file_list or [])):
maruel@chromium.org68988972011-09-20 14:11:42 +0000878 # It depends on the command being executed (like runhooks vs sync).
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000879 if not os.path.isabs(file_list[i]):
maruel@chromium.org68988972011-09-20 14:11:42 +0000880 continue
881 prefix = os.path.commonprefix(
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000882 [self.root.root_dir.lower(), file_list[i].lower()])
883 file_list[i] = file_list[i][len(prefix):]
maruel@chromium.org68988972011-09-20 14:11:42 +0000884 # Strip any leading path separators.
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000885 while file_list[i].startswith(('\\', '/')):
886 file_list[i] = file_list[i][1:]
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000887
888 # Always parse the DEPS file.
889 self.ParseDepsFile()
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +0200890 if self._gn_args_file and command == 'update':
891 self.WriteGNArgsFile()
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000892 self._run_is_done(file_list or [], parsed_url)
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000893 if command in ('update', 'revert') and not options.noprehooks:
894 self.RunPreDepsHooks()
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000895
896 if self.recursion_limit:
897 # Parse the dependencies of this dependency.
898 for s in self.dependencies:
Paweł Hajdan, Jr4baaa112017-07-04 19:09:32 +0200899 if s.should_process:
900 work_queue.enqueue(s)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000901
902 if command == 'recurse':
agabled437d762016-10-17 09:35:11 -0700903 # Skip file only checkout.
904 scm = gclient_scm.GetScmName(parsed_url)
905 if not options.scm or scm in options.scm:
906 cwd = os.path.normpath(os.path.join(self.root.root_dir, self.name))
907 # Pass in the SCM type as an env variable. Make sure we don't put
908 # unicode strings in the environment.
909 env = os.environ.copy()
910 if scm:
911 env['GCLIENT_SCM'] = str(scm)
912 if parsed_url:
913 env['GCLIENT_URL'] = str(parsed_url)
914 env['GCLIENT_DEP_PATH'] = str(self.name)
915 if options.prepend_dir and scm == 'git':
916 print_stdout = False
917 def filter_fn(line):
918 """Git-specific path marshaling. It is optimized for git-grep."""
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000919
agabled437d762016-10-17 09:35:11 -0700920 def mod_path(git_pathspec):
921 match = re.match('^(\\S+?:)?([^\0]+)$', git_pathspec)
922 modified_path = os.path.join(self.name, match.group(2))
923 branch = match.group(1) or ''
924 return '%s%s' % (branch, modified_path)
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000925
agabled437d762016-10-17 09:35:11 -0700926 match = re.match('^Binary file ([^\0]+) matches$', line)
927 if match:
928 print('Binary file %s matches\n' % mod_path(match.group(1)))
929 return
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000930
agabled437d762016-10-17 09:35:11 -0700931 items = line.split('\0')
932 if len(items) == 2 and items[1]:
933 print('%s : %s' % (mod_path(items[0]), items[1]))
934 elif len(items) >= 2:
935 # Multiple null bytes or a single trailing null byte indicate
936 # git is likely displaying filenames only (such as with -l)
937 print('\n'.join(mod_path(path) for path in items if path))
938 else:
939 print(line)
940 else:
941 print_stdout = True
942 filter_fn = None
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000943
agabled437d762016-10-17 09:35:11 -0700944 if parsed_url is None:
945 print('Skipped omitted dependency %s' % cwd, file=sys.stderr)
946 elif os.path.isdir(cwd):
947 try:
948 gclient_utils.CheckCallAndFilter(
949 args, cwd=cwd, env=env, print_stdout=print_stdout,
950 filter_fn=filter_fn,
951 )
952 except subprocess2.CalledProcessError:
953 if not options.ignore:
954 raise
955 else:
956 print('Skipped missing %s' % cwd, file=sys.stderr)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000957
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +0200958 def WriteGNArgsFile(self):
959 lines = ['# Generated from %r' % self.deps_file]
960 for arg in self._gn_args:
Paweł Hajdan, Jrd3790252017-07-03 21:06:24 +0200961 lines.append('%s = %s' % (arg, ToGNString(self.get_vars()[arg])))
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +0200962 with open(os.path.join(self.root.root_dir, self._gn_args_file), 'w') as f:
963 f.write('\n'.join(lines))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000964
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000965 @gclient_utils.lockedmethod
966 def _run_is_done(self, file_list, parsed_url):
967 # Both these are kept for hooks that are run as a separate tree traversal.
968 self._file_list = file_list
969 self._parsed_url = parsed_url
970 self._processed = True
971
szager@google.comb9a78d32012-03-13 18:46:21 +0000972 def GetHooks(self, options):
973 """Evaluates all hooks, and return them in a flat list.
974
975 RunOnDeps() must have been called before to load the DEPS.
976 """
977 result = []
maruel@chromium.org68988972011-09-20 14:11:42 +0000978 if not self.should_process or not self.recursion_limit:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000979 # Don't run the hook when it is above recursion_limit.
szager@google.comb9a78d32012-03-13 18:46:21 +0000980 return result
maruel@chromium.orgdc7445d2010-07-09 21:05:29 +0000981 # If "--force" was specified, run all hooks regardless of what files have
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000982 # changed.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000983 if self.deps_hooks:
agabled437d762016-10-17 09:35:11 -0700984 # TODO(maruel): If the user is using git, then we don't know
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000985 # what files have changed so we always run all hooks. It'd be nice to fix
986 # that.
987 if (options.force or
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000988 gclient_scm.GetScmName(self.parsed_url) in ('git', None) or
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000989 os.path.isdir(os.path.join(self.root.root_dir, self.name, '.git'))):
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200990 result.extend(self.deps_hooks)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000991 else:
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200992 for hook in self.deps_hooks:
993 if hook.matches(self.file_list_and_children):
994 result.append(hook)
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000995 for s in self.dependencies:
szager@google.comb9a78d32012-03-13 18:46:21 +0000996 result.extend(s.GetHooks(options))
997 return result
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000998
szager@google.comb9a78d32012-03-13 18:46:21 +0000999 def RunHooksRecursively(self, options):
1000 assert self.hooks_ran == False
maruel@chromium.org064186c2011-09-27 23:53:33 +00001001 self._hooks_ran = True
szager@google.comb9a78d32012-03-13 18:46:21 +00001002 for hook in self.GetHooks(options):
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +02001003 hook.run(self.root.root_dir)
maruel@chromium.orgeaf61062010-07-07 18:42:39 +00001004
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001005 def RunPreDepsHooks(self):
1006 assert self.processed
1007 assert self.deps_parsed
1008 assert not self.pre_deps_hooks_ran
1009 assert not self.hooks_ran
1010 for s in self.dependencies:
1011 assert not s.processed
1012 self._pre_deps_hooks_ran = True
1013 for hook in self.pre_deps_hooks:
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +02001014 hook.run(self.root.root_dir)
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001015
maruel@chromium.org0d812442010-08-10 12:41:08 +00001016 def subtree(self, include_all):
maruel@chromium.orgad3287e2011-10-03 19:15:10 +00001017 """Breadth first recursion excluding root node."""
maruel@chromium.orgf13a4182011-09-22 00:26:15 +00001018 dependencies = self.dependencies
1019 for d in dependencies:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001020 if d.should_process or include_all:
maruel@chromium.orgad3287e2011-10-03 19:15:10 +00001021 yield d
maruel@chromium.orgf13a4182011-09-22 00:26:15 +00001022 for d in dependencies:
maruel@chromium.orgad3287e2011-10-03 19:15:10 +00001023 for i in d.subtree(include_all):
1024 yield i
1025
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001026 @gclient_utils.lockedmethod
1027 def add_dependency(self, new_dep):
1028 self._dependencies.append(new_dep)
1029
1030 @gclient_utils.lockedmethod
Paweł Hajdan, Jr357415c2017-07-24 14:35:28 +02001031 def _mark_as_parsed(self, new_hooks):
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001032 self._deps_hooks.extend(new_hooks)
1033 self._deps_parsed = True
1034
maruel@chromium.org68988972011-09-20 14:11:42 +00001035 @property
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001036 @gclient_utils.lockedmethod
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +00001037 def dependencies(self):
1038 return tuple(self._dependencies)
1039
1040 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001041 @gclient_utils.lockedmethod
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02001042 def os_dependencies(self):
1043 return dict(self._os_dependencies)
1044
1045 @property
1046 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001047 def deps_hooks(self):
1048 return tuple(self._deps_hooks)
1049
1050 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001051 @gclient_utils.lockedmethod
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +02001052 def os_deps_hooks(self):
1053 return dict(self._os_deps_hooks)
1054
1055 @property
1056 @gclient_utils.lockedmethod
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001057 def pre_deps_hooks(self):
1058 return tuple(self._pre_deps_hooks)
1059
1060 @property
1061 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001062 def parsed_url(self):
1063 return self._parsed_url
1064
1065 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001066 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001067 def deps_parsed(self):
maruel@chromium.org3223edd2011-10-10 23:17:39 +00001068 """This is purely for debugging purposes. It's not used anywhere."""
maruel@chromium.org064186c2011-09-27 23:53:33 +00001069 return self._deps_parsed
1070
1071 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001072 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001073 def processed(self):
1074 return self._processed
1075
1076 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001077 @gclient_utils.lockedmethod
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001078 def pre_deps_hooks_ran(self):
1079 return self._pre_deps_hooks_ran
1080
1081 @property
1082 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001083 def hooks_ran(self):
1084 return self._hooks_ran
1085
1086 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001087 @gclient_utils.lockedmethod
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00001088 def allowed_hosts(self):
1089 return self._allowed_hosts
1090
1091 @property
1092 @gclient_utils.lockedmethod
maruel@chromium.org861fd0f2010-07-23 03:05:05 +00001093 def file_list(self):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001094 return tuple(self._file_list)
1095
1096 @property
kustermann@google.coma692e8f2013-04-18 08:32:04 +00001097 def used_scm(self):
1098 """SCMWrapper instance for this dependency or None if not processed yet."""
1099 return self._used_scm
1100
1101 @property
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00001102 @gclient_utils.lockedmethod
1103 def got_revision(self):
1104 return self._got_revision
1105
1106 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001107 def file_list_and_children(self):
1108 result = list(self.file_list)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +00001109 for d in self.dependencies:
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001110 result.extend(d.file_list_and_children)
maruel@chromium.org68988972011-09-20 14:11:42 +00001111 return tuple(result)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +00001112
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001113 def __str__(self):
1114 out = []
agablea98a6cd2016-11-15 14:30:10 -08001115 for i in ('name', 'url', 'parsed_url', 'custom_deps',
maruel@chromium.org3c74bc92011-09-15 19:17:21 +00001116 'custom_vars', 'deps_hooks', 'file_list', 'should_process',
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00001117 'processed', 'hooks_ran', 'deps_parsed', 'requirements',
1118 'allowed_hosts'):
maruel@chromium.org3c74bc92011-09-15 19:17:21 +00001119 # First try the native property if it exists.
1120 if hasattr(self, '_' + i):
1121 value = getattr(self, '_' + i, False)
1122 else:
1123 value = getattr(self, i, False)
1124 if value:
1125 out.append('%s: %s' % (i, value))
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001126
1127 for d in self.dependencies:
1128 out.extend([' ' + x for x in str(d).splitlines()])
1129 out.append('')
1130 return '\n'.join(out)
1131
1132 def __repr__(self):
1133 return '%s: %s' % (self.name, self.url)
1134
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001135 def hierarchy(self, include_url=True):
maruel@chromium.orgbc2d2f92010-07-22 21:26:48 +00001136 """Returns a human-readable hierarchical reference to a Dependency."""
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001137 def format_name(d):
1138 if include_url:
1139 return '%s(%s)' % (d.name, d.url)
1140 return d.name
1141 out = format_name(self)
maruel@chromium.orgbffb9042010-07-22 20:59:36 +00001142 i = self.parent
1143 while i and i.name:
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001144 out = '%s -> %s' % (format_name(i), out)
maruel@chromium.orgbffb9042010-07-22 20:59:36 +00001145 i = i.parent
1146 return out
1147
Paweł Hajdan, Jrd3790252017-07-03 21:06:24 +02001148 def get_vars(self):
1149 """Returns a dictionary of effective variable values
1150 (DEPS file contents with applied custom_vars overrides)."""
1151 result = dict(self._vars)
1152 result.update(self.custom_vars or {})
1153 return result
1154
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001155
1156class GClient(Dependency):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001157 """Object that represent a gclient checkout. A tree of Dependency(), one per
1158 solution or DEPS entry."""
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001159
1160 DEPS_OS_CHOICES = {
1161 "win32": "win",
1162 "win": "win",
1163 "cygwin": "win",
1164 "darwin": "mac",
1165 "mac": "mac",
1166 "unix": "unix",
1167 "linux": "unix",
1168 "linux2": "unix",
maruel@chromium.org244e3442011-06-12 15:20:55 +00001169 "linux3": "unix",
szager@chromium.orgf8c95cd2012-06-01 22:26:52 +00001170 "android": "android",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001171 }
1172
1173 DEFAULT_CLIENT_FILE_TEXT = ("""\
1174solutions = [
smutae7ea312016-07-18 11:59:41 -07001175 { "name" : "%(solution_name)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001176 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +00001177 "deps_file" : "%(deps_file)s",
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001178 "managed" : %(managed)s,
smutae7ea312016-07-18 11:59:41 -07001179 "custom_deps" : {
1180 },
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001181 },
1182]
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001183cache_dir = %(cache_dir)r
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001184""")
1185
1186 DEFAULT_SNAPSHOT_SOLUTION_TEXT = ("""\
smutae7ea312016-07-18 11:59:41 -07001187 { "name" : "%(solution_name)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001188 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +00001189 "deps_file" : "%(deps_file)s",
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001190 "managed" : %(managed)s,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001191 "custom_deps" : {
smutae7ea312016-07-18 11:59:41 -07001192%(solution_deps)s },
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001193 },
1194""")
1195
1196 DEFAULT_SNAPSHOT_FILE_TEXT = ("""\
1197# Snapshot generated with gclient revinfo --snapshot
1198solutions = [
maruel@chromium.org73e21142010-07-05 13:32:01 +00001199%(solution_list)s]
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001200""")
1201
1202 def __init__(self, root_dir, options):
maruel@chromium.org0d812442010-08-10 12:41:08 +00001203 # Do not change previous behavior. Only solution level and immediate DEPS
1204 # are processed.
1205 self._recursion_limit = 2
Paweł Hajdan, Jrfc6196b2017-07-27 13:15:25 +02001206 Dependency.__init__(self, None, None, None, None, True, None, None, None,
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +02001207 'unused', True, None, None, True)
maruel@chromium.org0d425922010-06-21 19:22:24 +00001208 self._options = options
maruel@chromium.org271375b2010-06-23 19:17:38 +00001209 if options.deps_os:
1210 enforced_os = options.deps_os.split(',')
1211 else:
1212 enforced_os = [self.DEPS_OS_CHOICES.get(sys.platform, 'unix')]
1213 if 'all' in enforced_os:
1214 enforced_os = self.DEPS_OS_CHOICES.itervalues()
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001215 self._enforced_os = tuple(set(enforced_os))
maruel@chromium.org271375b2010-06-23 19:17:38 +00001216 self._root_dir = root_dir
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001217 self.config_content = None
1218
borenet@google.com88d10082014-03-21 17:24:48 +00001219 def _CheckConfig(self):
1220 """Verify that the config matches the state of the existing checked-out
1221 solutions."""
1222 for dep in self.dependencies:
1223 if dep.managed and dep.url:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001224 scm = gclient_scm.CreateSCM(
1225 dep.url, self.root_dir, dep.name, self.outbuf)
smut@google.comd33eab32014-07-07 19:35:18 +00001226 actual_url = scm.GetActualRemoteURL(self._options)
borenet@google.com4e9be262014-04-08 19:40:30 +00001227 if actual_url and not scm.DoesRemoteURLMatch(self._options):
levarum@chromium.org27a6f9a2016-05-28 00:21:49 +00001228 mirror = scm.GetCacheMirror()
1229 if mirror:
1230 mirror_string = '%s (exists=%s)' % (mirror.mirror_path,
1231 mirror.exists())
1232 else:
1233 mirror_string = 'not used'
borenet@google.com0a427372014-04-02 19:12:13 +00001234 raise gclient_utils.Error('''
borenet@google.com88d10082014-03-21 17:24:48 +00001235Your .gclient file seems to be broken. The requested URL is different from what
borenet@google.com0a427372014-04-02 19:12:13 +00001236is actually checked out in %(checkout_path)s.
borenet@google.com88d10082014-03-21 17:24:48 +00001237
borenet@google.com97882362014-04-07 20:06:02 +00001238The .gclient file contains:
levarum@chromium.org27a6f9a2016-05-28 00:21:49 +00001239URL: %(expected_url)s (%(expected_scm)s)
1240Cache mirror: %(mirror_string)s
borenet@google.com97882362014-04-07 20:06:02 +00001241
1242The local checkout in %(checkout_path)s reports:
1243%(actual_url)s (%(actual_scm)s)
borenet@google.com88d10082014-03-21 17:24:48 +00001244
1245You should ensure that the URL listed in .gclient is correct and either change
agabled437d762016-10-17 09:35:11 -07001246it or fix the checkout.
borenet@google.com88d10082014-03-21 17:24:48 +00001247''' % {'checkout_path': os.path.join(self.root_dir, dep.name),
1248 'expected_url': dep.url,
1249 'expected_scm': gclient_scm.GetScmName(dep.url),
levarum@chromium.org27a6f9a2016-05-28 00:21:49 +00001250 'mirror_string' : mirror_string,
borenet@google.com88d10082014-03-21 17:24:48 +00001251 'actual_url': actual_url,
1252 'actual_scm': gclient_scm.GetScmName(actual_url)})
1253
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001254 def SetConfig(self, content):
maruel@chromium.orgf13a4182011-09-22 00:26:15 +00001255 assert not self.dependencies
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001256 config_dict = {}
1257 self.config_content = content
1258 try:
1259 exec(content, config_dict)
vapier@chromium.orga81a56e2015-11-11 07:56:13 +00001260 except SyntaxError as e:
maruel@chromium.org5990f9d2010-07-07 18:02:58 +00001261 gclient_utils.SyntaxErrorToError('.gclient', e)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001262
peter@chromium.org1efccc82012-04-27 16:34:38 +00001263 # Append any target OS that is not already being enforced to the tuple.
1264 target_os = config_dict.get('target_os', [])
stuartmorgan@chromium.org18a4f6a2012-11-02 13:37:24 +00001265 if config_dict.get('target_os_only', False):
1266 self._enforced_os = tuple(set(target_os))
1267 else:
1268 self._enforced_os = tuple(set(self._enforced_os).union(target_os))
1269
dyen@chromium.orgd915cca2014-08-07 21:41:37 +00001270 cache_dir = config_dict.get('cache_dir')
1271 if cache_dir:
1272 cache_dir = os.path.join(self.root_dir, cache_dir)
1273 cache_dir = os.path.abspath(cache_dir)
szager@chromium.orgcaf5bef2014-08-24 18:56:32 +00001274 # If running on a bot, force break any stale git cache locks.
dnj@chromium.orgb682b3e2014-08-25 19:17:12 +00001275 if os.path.exists(cache_dir) and os.environ.get('CHROME_HEADLESS'):
szager@chromium.org4848fb62014-08-24 19:16:31 +00001276 subprocess2.check_call(['git', 'cache', 'unlock', '--cache-dir',
1277 cache_dir, '--force', '--all'])
dyen@chromium.orgd915cca2014-08-07 21:41:37 +00001278 gclient_scm.GitWrapper.cache_dir = cache_dir
1279 git_cache.Mirror.SetCachePath(cache_dir)
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001280
stuartmorgan@chromium.org18a4f6a2012-11-02 13:37:24 +00001281 if not target_os and config_dict.get('target_os_only', False):
1282 raise gclient_utils.Error('Can\'t use target_os_only if target_os is '
1283 'not specified')
peter@chromium.org1efccc82012-04-27 16:34:38 +00001284
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001285 deps_to_add = []
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001286 for s in config_dict.get('solutions', []):
maruel@chromium.org81843b82010-06-28 16:49:26 +00001287 try:
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001288 deps_to_add.append(Dependency(
Paweł Hajdan, Jrfc6196b2017-07-27 13:15:25 +02001289 self, s['name'], s['url'], s['url'],
smutae7ea312016-07-18 11:59:41 -07001290 s.get('managed', True),
maruel@chromium.org81843b82010-06-28 16:49:26 +00001291 s.get('custom_deps', {}),
maruel@chromium.org0d812442010-08-10 12:41:08 +00001292 s.get('custom_vars', {}),
petermayo@chromium.orge79161a2013-07-09 14:40:37 +00001293 s.get('custom_hooks', []),
nsylvain@google.comefc80932011-05-31 21:27:56 +00001294 s.get('deps_file', 'DEPS'),
agabledce6ddc2016-09-08 10:02:16 -07001295 True,
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +02001296 None,
1297 None,
1298 True))
maruel@chromium.org81843b82010-06-28 16:49:26 +00001299 except KeyError:
1300 raise gclient_utils.Error('Invalid .gclient file. Solution is '
1301 'incomplete: %s' % s)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001302 self.add_dependencies_and_close(deps_to_add, config_dict.get('hooks', []))
1303 logging.info('SetConfig() done')
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001304
1305 def SaveConfig(self):
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001306 gclient_utils.FileWrite(os.path.join(self.root_dir,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001307 self._options.config_filename),
1308 self.config_content)
1309
1310 @staticmethod
1311 def LoadCurrentConfig(options):
1312 """Searches for and loads a .gclient file relative to the current working
1313 dir. Returns a GClient object."""
szager@chromium.orge2e03202012-07-31 18:05:16 +00001314 if options.spec:
1315 client = GClient('.', options)
1316 client.SetConfig(options.spec)
1317 else:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00001318 if options.verbose:
1319 print('Looking for %s starting from %s\n' % (
1320 options.config_filename, os.getcwd()))
szager@chromium.orge2e03202012-07-31 18:05:16 +00001321 path = gclient_utils.FindGclientRoot(os.getcwd(), options.config_filename)
1322 if not path:
1323 return None
1324 client = GClient(path, options)
1325 client.SetConfig(gclient_utils.FileRead(
1326 os.path.join(path, options.config_filename)))
maruel@chromium.org69392e72011-10-13 22:09:00 +00001327
1328 if (options.revisions and
1329 len(client.dependencies) > 1 and
1330 any('@' not in r for r in options.revisions)):
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001331 print(
1332 ('You must specify the full solution name like --revision %s@%s\n'
1333 'when you have multiple solutions setup in your .gclient file.\n'
1334 'Other solutions present are: %s.') % (
maruel@chromium.org69392e72011-10-13 22:09:00 +00001335 client.dependencies[0].name,
1336 options.revisions[0],
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001337 ', '.join(s.name for s in client.dependencies[1:])),
1338 file=sys.stderr)
maruel@chromium.org15804092010-09-02 17:07:37 +00001339 return client
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001340
nsylvain@google.comefc80932011-05-31 21:27:56 +00001341 def SetDefaultConfig(self, solution_name, deps_file, solution_url,
agablea98a6cd2016-11-15 14:30:10 -08001342 managed=True, cache_dir=None):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001343 self.SetConfig(self.DEFAULT_CLIENT_FILE_TEXT % {
1344 'solution_name': solution_name,
1345 'solution_url': solution_url,
nsylvain@google.comefc80932011-05-31 21:27:56 +00001346 'deps_file': deps_file,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001347 'managed': managed,
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001348 'cache_dir': cache_dir,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001349 })
1350
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001351 def _SaveEntries(self):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001352 """Creates a .gclient_entries file to record the list of unique checkouts.
1353
1354 The .gclient_entries file lives in the same directory as .gclient.
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001355 """
1356 # Sometimes pprint.pformat will use {', sometimes it'll use { ' ... It
1357 # makes testing a bit too fun.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001358 result = 'entries = {\n'
maruel@chromium.org68988972011-09-20 14:11:42 +00001359 for entry in self.root.subtree(False):
agabled437d762016-10-17 09:35:11 -07001360 result += ' %s: %s,\n' % (pprint.pformat(entry.name),
1361 pprint.pformat(entry.parsed_url))
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001362 result += '}\n'
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001363 file_path = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org1333cb32011-10-04 23:40:16 +00001364 logging.debug(result)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001365 gclient_utils.FileWrite(file_path, result)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001366
1367 def _ReadEntries(self):
1368 """Read the .gclient_entries file for the given client.
1369
1370 Returns:
1371 A sequence of solution names, which will be empty if there is the
1372 entries file hasn't been created yet.
1373 """
1374 scope = {}
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001375 filename = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001376 if not os.path.exists(filename):
maruel@chromium.org73e21142010-07-05 13:32:01 +00001377 return {}
maruel@chromium.org5990f9d2010-07-07 18:02:58 +00001378 try:
1379 exec(gclient_utils.FileRead(filename), scope)
vapier@chromium.orga81a56e2015-11-11 07:56:13 +00001380 except SyntaxError as e:
maruel@chromium.org5990f9d2010-07-07 18:02:58 +00001381 gclient_utils.SyntaxErrorToError(filename, e)
Aaron Gable3721ee92017-04-03 14:53:14 -07001382 return scope.get('entries', {})
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001383
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001384 def _EnforceRevisions(self):
maruel@chromium.org918a9ae2010-05-28 15:50:30 +00001385 """Checks for revision overrides."""
1386 revision_overrides = {}
smutae7ea312016-07-18 11:59:41 -07001387 if self._options.head:
1388 return revision_overrides
joi@chromium.org792ea882010-11-10 02:37:27 +00001389 if not self._options.revisions:
1390 for s in self.dependencies:
smutae7ea312016-07-18 11:59:41 -07001391 if not s.managed:
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001392 self._options.revisions.append('%s@unmanaged' % s.name)
maruel@chromium.org307d1792010-05-31 20:03:13 +00001393 if not self._options.revisions:
1394 return revision_overrides
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001395 solutions_names = [s.name for s in self.dependencies]
smutae7ea312016-07-18 11:59:41 -07001396 index = 0
1397 for revision in self._options.revisions:
1398 if not '@' in revision:
maruel@chromium.org307d1792010-05-31 20:03:13 +00001399 # Support for --revision 123
smutae7ea312016-07-18 11:59:41 -07001400 revision = '%s@%s' % (solutions_names[index], revision)
1401 name, rev = revision.split('@', 1)
szager@chromium.org4ad264b2014-05-20 04:43:47 +00001402 revision_overrides[name] = rev
smutae7ea312016-07-18 11:59:41 -07001403 index += 1
maruel@chromium.org918a9ae2010-05-28 15:50:30 +00001404 return revision_overrides
1405
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001406 def RunOnDeps(self, command, args, ignore_requirements=False, progress=True):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001407 """Runs a command on each dependency in a client and its dependencies.
1408
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001409 Args:
1410 command: The command to use (e.g., 'status' or 'diff')
1411 args: list of str - extra arguments to add to the command line.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001412 """
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001413 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +00001414 raise gclient_utils.Error('No solution specified')
borenet@google.com0a427372014-04-02 19:12:13 +00001415
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001416 revision_overrides = {}
1417 # It's unnecessary to check for revision overrides for 'recurse'.
1418 # Save a few seconds by not calling _EnforceRevisions() in that case.
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +02001419 if command not in ('diff', 'recurse', 'runhooks', 'status', 'revert',
1420 'validate'):
szager@chromium.org5273b8a2014-08-21 15:10:10 +00001421 self._CheckConfig()
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001422 revision_overrides = self._EnforceRevisions()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001423 pm = None
maruel@chromium.org5b3f8852010-09-10 16:49:54 +00001424 # Disable progress for non-tty stdout.
iannucci@chromium.org596cd5c2016-04-04 21:34:39 +00001425 if (setup_color.IS_TTY and not self._options.verbose and progress):
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001426 if command in ('update', 'revert'):
1427 pm = Progress('Syncing projects', 1)
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +02001428 elif command in ('recurse', 'validate'):
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001429 pm = Progress(' '.join(args), 1)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001430 work_queue = gclient_utils.ExecutionQueue(
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001431 self._options.jobs, pm, ignore_requirements=ignore_requirements,
1432 verbose=self._options.verbose)
maruel@chromium.org049bced2010-08-12 13:37:20 +00001433 for s in self.dependencies:
Paweł Hajdan, Jr4baaa112017-07-04 19:09:32 +02001434 if s.should_process:
1435 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +00001436 work_queue.flush(revision_overrides, command, args, options=self._options)
szager@chromium.org4ad264b2014-05-20 04:43:47 +00001437 if revision_overrides:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001438 print('Please fix your script, having invalid --revision flags will soon '
1439 'considered an error.', file=sys.stderr)
piman@chromium.org6f363722010-04-27 00:41:09 +00001440
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001441 # Once all the dependencies have been processed, it's now safe to run the
1442 # hooks.
1443 if not self._options.nohooks:
1444 self.RunHooksRecursively(self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001445
1446 if command == 'update':
ajwong@chromium.orgcdcee802009-06-23 15:30:42 +00001447 # Notify the user if there is an orphaned entry in their working copy.
1448 # Only delete the directory if there are no changes in it, and
1449 # delete_unversioned_trees is set to true.
maruel@chromium.org68988972011-09-20 14:11:42 +00001450 entries = [i.name for i in self.root.subtree(False) if i.url]
xusydoc@chromium.org885a9602013-05-31 09:54:40 +00001451 full_entries = [os.path.join(self.root_dir, e.replace('/', os.path.sep))
1452 for e in entries]
1453
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001454 for entry, prev_url in self._ReadEntries().iteritems():
maruel@chromium.org04dd7de2010-10-14 13:25:49 +00001455 if not prev_url:
1456 # entry must have been overridden via .gclient custom_deps
1457 continue
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001458 # Fix path separator on Windows.
1459 entry_fixed = entry.replace('/', os.path.sep)
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001460 e_dir = os.path.join(self.root_dir, entry_fixed)
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001461 # Use entry and not entry_fixed there.
jochen@chromium.orga78e5532013-03-11 13:33:03 +00001462 if (entry not in entries and
1463 (not any(path.startswith(entry + '/') for path in entries)) and
jochen@chromium.orgcc475722013-03-11 13:07:40 +00001464 os.path.exists(e_dir)):
primiano@chromium.org1c127382015-02-17 11:15:40 +00001465 # The entry has been removed from DEPS.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001466 scm = gclient_scm.CreateSCM(
1467 prev_url, self.root_dir, entry_fixed, self.outbuf)
xusydoc@chromium.org885a9602013-05-31 09:54:40 +00001468
1469 # Check to see if this directory is now part of a higher-up checkout.
borenet@google.com359bb642014-05-13 17:28:19 +00001470 scm_root = None
agabled437d762016-10-17 09:35:11 -07001471 try:
1472 scm_root = gclient_scm.scm.GIT.GetCheckoutRoot(scm.checkout_path)
1473 except subprocess2.CalledProcessError:
1474 pass
1475 if not scm_root:
borenet@google.com359bb642014-05-13 17:28:19 +00001476 logging.warning('Could not find checkout root for %s. Unable to '
1477 'determine whether it is part of a higher-level '
1478 'checkout, so not removing.' % entry)
1479 continue
primiano@chromium.org1c127382015-02-17 11:15:40 +00001480
1481 # This is to handle the case of third_party/WebKit migrating from
1482 # being a DEPS entry to being part of the main project.
1483 # If the subproject is a Git project, we need to remove its .git
1484 # folder. Otherwise git operations on that folder will have different
1485 # effects depending on the current working directory.
agabled437d762016-10-17 09:35:11 -07001486 if os.path.abspath(scm_root) == os.path.abspath(e_dir):
primiano@chromium.org1c127382015-02-17 11:15:40 +00001487 e_par_dir = os.path.join(e_dir, os.pardir)
agabled437d762016-10-17 09:35:11 -07001488 if gclient_scm.scm.GIT.IsInsideWorkTree(e_par_dir):
1489 par_scm_root = gclient_scm.scm.GIT.GetCheckoutRoot(e_par_dir)
primiano@chromium.org1c127382015-02-17 11:15:40 +00001490 # rel_e_dir : relative path of entry w.r.t. its parent repo.
1491 rel_e_dir = os.path.relpath(e_dir, par_scm_root)
agabled437d762016-10-17 09:35:11 -07001492 if gclient_scm.scm.GIT.IsDirectoryVersioned(
1493 par_scm_root, rel_e_dir):
primiano@chromium.org1c127382015-02-17 11:15:40 +00001494 save_dir = scm.GetGitBackupDirPath()
1495 # Remove any eventual stale backup dir for the same project.
1496 if os.path.exists(save_dir):
1497 gclient_utils.rmtree(save_dir)
1498 os.rename(os.path.join(e_dir, '.git'), save_dir)
1499 # When switching between the two states (entry/ is a subproject
1500 # -> entry/ is part of the outer project), it is very likely
1501 # that some files are changed in the checkout, unless we are
1502 # jumping *exactly* across the commit which changed just DEPS.
1503 # In such case we want to cleanup any eventual stale files
1504 # (coming from the old subproject) in order to end up with a
1505 # clean checkout.
agabled437d762016-10-17 09:35:11 -07001506 gclient_scm.scm.GIT.CleanupDir(par_scm_root, rel_e_dir)
primiano@chromium.org1c127382015-02-17 11:15:40 +00001507 assert not os.path.exists(os.path.join(e_dir, '.git'))
1508 print(('\nWARNING: \'%s\' has been moved from DEPS to a higher '
1509 'level checkout. The git folder containing all the local'
1510 ' branches has been saved to %s.\n'
1511 'If you don\'t care about its state you can safely '
1512 'remove that folder to free up space.') %
1513 (entry, save_dir))
1514 continue
1515
borenet@google.com359bb642014-05-13 17:28:19 +00001516 if scm_root in full_entries:
primiano@chromium.org1c127382015-02-17 11:15:40 +00001517 logging.info('%s is part of a higher level checkout, not removing',
1518 scm.GetCheckoutRoot())
xusydoc@chromium.org885a9602013-05-31 09:54:40 +00001519 continue
1520
1521 file_list = []
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001522 scm.status(self._options, [], file_list)
1523 modified_files = file_list != []
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001524 if (not self._options.delete_unversioned_trees or
1525 (modified_files and not self._options.force)):
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001526 # There are modified files in this entry. Keep warning until
1527 # removed.
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001528 print(('\nWARNING: \'%s\' is no longer part of this client. '
1529 'It is recommended that you manually remove it.\n') %
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001530 entry_fixed)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001531 else:
1532 # Delete the entry
maruel@chromium.org73e21142010-07-05 13:32:01 +00001533 print('\n________ deleting \'%s\' in \'%s\'' % (
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001534 entry_fixed, self.root_dir))
digit@chromium.orgdc112ac2013-04-24 13:00:19 +00001535 gclient_utils.rmtree(e_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001536 # record the current list of entries for next time
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001537 self._SaveEntries()
maruel@chromium.org17cdf762010-05-28 17:30:52 +00001538 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001539
1540 def PrintRevInfo(self):
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001541 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +00001542 raise gclient_utils.Error('No solution specified')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001543 # Load all the settings.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001544 work_queue = gclient_utils.ExecutionQueue(
1545 self._options.jobs, None, False, verbose=self._options.verbose)
maruel@chromium.org049bced2010-08-12 13:37:20 +00001546 for s in self.dependencies:
Paweł Hajdan, Jr4baaa112017-07-04 19:09:32 +02001547 if s.should_process:
1548 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +00001549 work_queue.flush({}, None, [], options=self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001550
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001551 def GetURLAndRev(dep):
1552 """Returns the revision-qualified SCM url for a Dependency."""
1553 if dep.parsed_url is None:
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001554 return None
agabled437d762016-10-17 09:35:11 -07001555 url, _ = gclient_utils.SplitUrlRevision(dep.parsed_url)
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001556 scm = gclient_scm.CreateSCM(
agabled437d762016-10-17 09:35:11 -07001557 dep.parsed_url, self.root_dir, dep.name, self.outbuf)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001558 if not os.path.isdir(scm.checkout_path):
1559 return None
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001560 return '%s@%s' % (url, scm.revinfo(self._options, [], None))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001561
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001562 if self._options.snapshot:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001563 new_gclient = ''
1564 # First level at .gclient
1565 for d in self.dependencies:
1566 entries = {}
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001567 def GrabDeps(dep):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001568 """Recursively grab dependencies."""
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001569 for d in dep.dependencies:
1570 entries[d.name] = GetURLAndRev(d)
1571 GrabDeps(d)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001572 GrabDeps(d)
1573 custom_deps = []
1574 for k in sorted(entries.keys()):
1575 if entries[k]:
1576 # Quotes aren't escaped...
1577 custom_deps.append(' \"%s\": \'%s\',\n' % (k, entries[k]))
1578 else:
1579 custom_deps.append(' \"%s\": None,\n' % k)
1580 new_gclient += self.DEFAULT_SNAPSHOT_SOLUTION_TEXT % {
1581 'solution_name': d.name,
1582 'solution_url': d.url,
nsylvain@google.comefc80932011-05-31 21:27:56 +00001583 'deps_file': d.deps_file,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001584 'managed': d.managed,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001585 'solution_deps': ''.join(custom_deps),
1586 }
1587 # Print the snapshot configuration file
1588 print(self.DEFAULT_SNAPSHOT_FILE_TEXT % {'solution_list': new_gclient})
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +00001589 else:
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001590 entries = {}
maruel@chromium.org68988972011-09-20 14:11:42 +00001591 for d in self.root.subtree(False):
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001592 if self._options.actual:
1593 entries[d.name] = GetURLAndRev(d)
1594 else:
1595 entries[d.name] = d.parsed_url
1596 keys = sorted(entries.keys())
1597 for x in keys:
maruel@chromium.orgce464892010-08-12 17:12:18 +00001598 print('%s: %s' % (x, entries[x]))
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +00001599 logging.info(str(self))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001600
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001601 def ParseDepsFile(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001602 """No DEPS to parse for a .gclient file."""
maruel@chromium.org049bced2010-08-12 13:37:20 +00001603 raise gclient_utils.Error('Internal error')
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001604
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00001605 def PrintLocationAndContents(self):
1606 # Print out the .gclient file. This is longer than if we just printed the
1607 # client dict, but more legible, and it might contain helpful comments.
1608 print('Loaded .gclient config in %s:\n%s' % (
1609 self.root_dir, self.config_content))
1610
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001611 @property
maruel@chromium.org75a59272010-06-11 22:34:03 +00001612 def root_dir(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001613 """Root directory of gclient checkout."""
maruel@chromium.org75a59272010-06-11 22:34:03 +00001614 return self._root_dir
1615
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001616 @property
maruel@chromium.org271375b2010-06-23 19:17:38 +00001617 def enforced_os(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001618 """What deps_os entries that are to be parsed."""
maruel@chromium.org271375b2010-06-23 19:17:38 +00001619 return self._enforced_os
1620
maruel@chromium.org68988972011-09-20 14:11:42 +00001621 @property
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001622 def recursion_limit(self):
1623 """How recursive can each dependencies in DEPS file can load DEPS file."""
1624 return self._recursion_limit
1625
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +00001626 @property
cmp@chromium.orgc401ad12014-07-02 23:20:08 +00001627 def try_recursedeps(self):
1628 """Whether to attempt using recursedeps-style recursion processing."""
cmp@chromium.orge84ac912014-06-30 23:14:35 +00001629 return True
1630
1631 @property
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +00001632 def target_os(self):
1633 return self._enforced_os
1634
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001635
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001636#### gclient commands.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001637
1638
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001639@subcommand.usage('[command] [args ...]')
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001640def CMDrecurse(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001641 """Operates [command args ...] on all the dependencies.
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001642
1643 Runs a shell command on all entries.
qyearsley12fa6ff2016-08-24 09:18:40 -07001644 Sets GCLIENT_DEP_PATH environment variable as the dep's relative location to
ilevy@chromium.org37116242012-11-28 01:32:48 +00001645 root directory of the checkout.
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001646 """
1647 # Stop parsing at the first non-arg so that these go through to the command
1648 parser.disable_interspersed_args()
1649 parser.add_option('-s', '--scm', action='append', default=[],
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001650 help='Choose scm types to operate upon.')
maruel@chromium.org288054d2012-03-05 00:43:07 +00001651 parser.add_option('-i', '--ignore', action='store_true',
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001652 help='Ignore non-zero return codes from subcommands.')
1653 parser.add_option('--prepend-dir', action='store_true',
1654 help='Prepend relative dir for use with git <cmd> --null.')
1655 parser.add_option('--no-progress', action='store_true',
1656 help='Disable progress bar that shows sub-command updates')
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001657 options, args = parser.parse_args(args)
maruel@chromium.org45e9f2d2010-10-18 13:33:46 +00001658 if not args:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001659 print('Need to supply a command!', file=sys.stderr)
maruel@chromium.org45e9f2d2010-10-18 13:33:46 +00001660 return 1
maruel@chromium.org78cba522010-10-18 13:32:05 +00001661 root_and_entries = gclient_utils.GetGClientRootAndEntries()
1662 if not root_and_entries:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001663 print(
maruel@chromium.org78cba522010-10-18 13:32:05 +00001664 'You need to run gclient sync at least once to use \'recurse\'.\n'
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001665 'This is because .gclient_entries needs to exist and be up to date.',
1666 file=sys.stderr)
maruel@chromium.org78cba522010-10-18 13:32:05 +00001667 return 1
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001668
1669 # Normalize options.scm to a set()
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001670 scm_set = set()
1671 for scm in options.scm:
1672 scm_set.update(scm.split(','))
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001673 options.scm = scm_set
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001674
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001675 options.nohooks = True
1676 client = GClient.LoadCurrentConfig(options)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001677 return client.RunOnDeps('recurse', args, ignore_requirements=True,
1678 progress=not options.no_progress)
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001679
1680
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001681@subcommand.usage('[args ...]')
davidbarr@chromium.org12f944e2012-03-01 02:18:31 +00001682def CMDfetch(parser, args):
1683 """Fetches upstream commits for all modules.
1684
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001685 Completely git-specific. Simply runs 'git fetch [args ...]' for each module.
1686 """
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001687 (options, args) = parser.parse_args(args)
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00001688 return CMDrecurse(OptionParser(), [
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001689 '--jobs=%d' % options.jobs, '--scm=git', 'git', 'fetch'] + args)
1690
1691
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02001692class Flattener(object):
1693 """Flattens a gclient solution."""
1694
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02001695 def __init__(self, client, pin_all_deps=False):
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02001696 """Constructor.
1697
1698 Arguments:
1699 client (GClient): client to flatten
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02001700 pin_all_deps (bool): whether to pin all deps, even if they're not pinned
1701 in DEPS
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02001702 """
1703 self._client = client
1704
1705 self._deps_string = None
1706
1707 self._allowed_hosts = set()
1708 self._deps = {}
1709 self._deps_os = {}
1710 self._hooks = []
1711 self._hooks_os = {}
1712 self._pre_deps_hooks = []
Paweł Hajdan, Jrfb022012017-07-06 18:00:08 +02001713 self._vars = {}
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02001714
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02001715 self._flatten(pin_all_deps=pin_all_deps)
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02001716
1717 @property
1718 def deps_string(self):
1719 assert self._deps_string is not None
1720 return self._deps_string
1721
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02001722 def _flatten(self, pin_all_deps=False):
1723 """Runs the flattener. Saves resulting DEPS string.
1724
1725 Arguments:
1726 pin_all_deps (bool): whether to pin all deps, even if they're not pinned
1727 in DEPS
1728 """
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02001729 for solution in self._client.dependencies:
Paweł Hajdan, Jr11eb7152017-08-10 12:50:11 +02001730 self._add_dep(solution)
Paweł Hajdan, Jrb0ad16e2017-08-03 15:33:21 +02001731 self._flatten_dep(solution)
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02001732
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02001733 if pin_all_deps:
1734 for dep in self._deps.itervalues():
1735 if dep.parsed_url is None:
1736 continue
1737 url, revision = gclient_utils.SplitUrlRevision(dep.parsed_url)
1738 if revision and gclient_utils.IsGitSha(revision):
1739 continue
1740 scm = gclient_scm.CreateSCM(
1741 dep.parsed_url, self._client.root_dir, dep.name, dep.outbuf)
Paweł Hajdan, Jr05bcb892017-08-10 14:21:26 +02001742 revinfo = scm.revinfo(self._client._options, [], None)
1743 dep._parsed_url = dep._url = '%s@%s' % (url, revinfo)
1744 dep._raw_url = '%s@%s' % (dep._raw_url, revinfo)
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02001745
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02001746 self._deps_string = '\n'.join(
1747 _GNSettingsToLines(
1748 self._client.dependencies[0]._gn_args_file,
1749 self._client.dependencies[0]._gn_args) +
1750 _AllowedHostsToLines(self._allowed_hosts) +
1751 _DepsToLines(self._deps) +
1752 _DepsOsToLines(self._deps_os) +
1753 _HooksToLines('hooks', self._hooks) +
1754 _HooksToLines('pre_deps_hooks', self._pre_deps_hooks) +
1755 _HooksOsToLines(self._hooks_os) +
Paweł Hajdan, Jrfb022012017-07-06 18:00:08 +02001756 _VarsToLines(self._vars) +
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02001757 ['']) # Ensure newline at end of file.
1758
Paweł Hajdan, Jr11eb7152017-08-10 12:50:11 +02001759 def _add_dep(self, dep):
1760 """Helper to add a dependency to flattened DEPS.
1761
1762 Arguments:
1763 dep (Dependency): dependency to add
1764 """
1765 assert dep.name not in self._deps or self._deps.get(dep.name) == dep, (
1766 dep.name, self._deps.get(dep.name))
1767 self._deps[dep.name] = dep
1768
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02001769 def _flatten_dep(self, dep):
1770 """Visits a dependency in order to flatten it (see CMDflatten).
1771
1772 Arguments:
1773 dep (Dependency): dependency to process
1774 """
1775 self._allowed_hosts.update(dep.allowed_hosts)
1776
Paweł Hajdan, Jrfb022012017-07-06 18:00:08 +02001777 for key, value in dep.get_vars().iteritems():
Paweł Hajdan, Jrc9353602017-08-02 17:52:08 +02001778 # Make sure there are no conflicting variables. It is fine however
1779 # to use same variable name, as long as the value is consistent.
1780 assert key not in self._vars or self._vars[key][1] == value
Paweł Hajdan, Jrfb022012017-07-06 18:00:08 +02001781 self._vars[key] = (dep, value)
1782
Paweł Hajdan, Jr357415c2017-07-24 14:35:28 +02001783 self._hooks.extend([(dep, hook) for hook in dep.deps_hooks])
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02001784 self._pre_deps_hooks.extend([(dep, hook) for hook in dep.pre_deps_hooks])
1785
Paweł Hajdan, Jrb0ad16e2017-08-03 15:33:21 +02001786 for sub_dep in dep.dependencies:
Paweł Hajdan, Jr11eb7152017-08-10 12:50:11 +02001787 self._add_dep(sub_dep)
Paweł Hajdan, Jrb0ad16e2017-08-03 15:33:21 +02001788
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02001789 for hook_os, os_hooks in dep.os_deps_hooks.iteritems():
1790 self._hooks_os.setdefault(hook_os, []).extend(
1791 [(dep, hook) for hook in os_hooks])
1792
Paweł Hajdan, Jre2deb1e2017-08-09 17:29:21 +02001793 for dep_os, os_deps in dep.os_dependencies.iteritems():
1794 for os_dep in os_deps:
1795 self._deps_os.setdefault(dep_os, {})[os_dep.name] = os_dep
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02001796
Paweł Hajdan, Jrb0ad16e2017-08-03 15:33:21 +02001797 # Process recursedeps.
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02001798 deps_by_name = dict((d.name, d) for d in dep.dependencies)
Paweł Hajdan, Jrc9353602017-08-02 17:52:08 +02001799 # Allow recursedeps entries that refer to deps_os entries.
1800 # In case there are multiple entries with the same name,
1801 # we have to pick something - e.g. the first one.
1802 for os_deps in dep.os_dependencies.itervalues():
1803 for os_dep in os_deps:
1804 if os_dep.name not in deps_by_name:
1805 deps_by_name[os_dep.name] = os_dep
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02001806 for recurse_dep_name in (dep.recursedeps or []):
Paweł Hajdan, Jre2deb1e2017-08-09 17:29:21 +02001807 self._flatten_dep(deps_by_name[recurse_dep_name])
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02001808
1809
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001810def CMDflatten(parser, args):
1811 """Flattens the solutions into a single DEPS file."""
1812 parser.add_option('--output-deps', help='Path to the output DEPS file')
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02001813 parser.add_option(
1814 '--pin-all-deps', action='store_true',
1815 help=('Pin all deps, even if not pinned in DEPS. CAVEAT: only does so '
1816 'for checked out deps, NOT deps_os.'))
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001817 options, args = parser.parse_args(args)
1818
Paweł Hajdan, Jr357415c2017-07-24 14:35:28 +02001819 options.do_not_merge_os_specific_entries = True
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001820 options.nohooks = True
1821 client = GClient.LoadCurrentConfig(options)
1822
1823 # Only print progress if we're writing to a file. Otherwise, progress updates
1824 # could obscure intended output.
1825 code = client.RunOnDeps('flatten', args, progress=options.output_deps)
1826 if code != 0:
1827 return code
1828
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02001829 flattener = Flattener(client, pin_all_deps=options.pin_all_deps)
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001830
1831 if options.output_deps:
1832 with open(options.output_deps, 'w') as f:
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02001833 f.write(flattener.deps_string)
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001834 else:
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02001835 print(flattener.deps_string)
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001836
1837 return 0
1838
1839
Paweł Hajdan, Jr3c2aa832017-06-07 20:22:16 +02001840def _GNSettingsToLines(gn_args_file, gn_args):
1841 s = []
1842 if gn_args_file:
1843 s.extend([
1844 'gclient_gn_args_file = "%s"' % gn_args_file,
1845 'gclient_gn_args = %r' % gn_args,
1846 ])
1847 return s
1848
1849
Paweł Hajdan, Jr6014b562017-06-30 17:43:42 +02001850def _AllowedHostsToLines(allowed_hosts):
1851 """Converts |allowed_hosts| set to list of lines for output."""
1852 if not allowed_hosts:
1853 return []
1854 s = ['allowed_hosts = [']
1855 for h in sorted(allowed_hosts):
1856 s.append(' "%s",' % h)
1857 s.extend([']', ''])
1858 return s
1859
1860
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001861def _DepsToLines(deps):
1862 """Converts |deps| dict to list of lines for output."""
Paweł Hajdan, Jr5b593352017-06-29 18:37:45 +02001863 if not deps:
1864 return []
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001865 s = ['deps = {']
1866 for name, dep in sorted(deps.iteritems()):
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +02001867 condition_part = ([' "condition": "%s",' % dep.condition]
1868 if dep.condition else [])
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001869 s.extend([
1870 ' # %s' % dep.hierarchy(include_url=False),
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +02001871 ' "%s": {' % (name,),
Paweł Hajdan, Jrfc6196b2017-07-27 13:15:25 +02001872 ' "url": "%s",' % (dep.raw_url,),
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +02001873 ] + condition_part + [
1874 ' },',
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001875 '',
1876 ])
1877 s.extend(['}', ''])
1878 return s
1879
1880
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02001881def _DepsOsToLines(deps_os):
1882 """Converts |deps_os| dict to list of lines for output."""
Paweł Hajdan, Jr5b593352017-06-29 18:37:45 +02001883 if not deps_os:
1884 return []
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02001885 s = ['deps_os = {']
1886 for dep_os, os_deps in sorted(deps_os.iteritems()):
1887 s.append(' "%s": {' % dep_os)
Paweł Hajdan, Jrad30de62017-06-26 18:51:58 +02001888 for name, dep in sorted(os_deps.iteritems()):
1889 condition_part = ([' "condition": "%s",' % dep.condition]
1890 if dep.condition else [])
1891 s.extend([
1892 ' # %s' % dep.hierarchy(include_url=False),
1893 ' "%s": {' % (name,),
Paweł Hajdan, Jrde86ab32017-08-10 13:55:16 +02001894 ' "url": "%s",' % (dep.raw_url,),
Paweł Hajdan, Jrad30de62017-06-26 18:51:58 +02001895 ] + condition_part + [
1896 ' },',
1897 '',
1898 ])
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02001899 s.extend([' },', ''])
1900 s.extend(['}', ''])
1901 return s
1902
1903
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001904def _HooksToLines(name, hooks):
1905 """Converts |hooks| list to list of lines for output."""
Paweł Hajdan, Jr5b593352017-06-29 18:37:45 +02001906 if not hooks:
1907 return []
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001908 s = ['%s = [' % name]
1909 for dep, hook in hooks:
1910 s.extend([
1911 ' # %s' % dep.hierarchy(include_url=False),
1912 ' {',
1913 ])
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +02001914 if hook.name is not None:
1915 s.append(' "name": "%s",' % hook.name)
1916 if hook.pattern is not None:
1917 s.append(' "pattern": "%s",' % hook.pattern)
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001918 s.extend(
Paweł Hajdan, Jrc9364392017-06-14 17:11:56 +02001919 # Hooks run in the parent directory of their dep.
Paweł Hajdan, Jrad30de62017-06-26 18:51:58 +02001920 [' "cwd": "%s",' % os.path.normpath(os.path.dirname(dep.name))] +
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001921 [' "action": ['] +
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +02001922 [' "%s",' % arg for arg in hook.action] +
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001923 [' ]', ' },', '']
1924 )
1925 s.extend([']', ''])
1926 return s
1927
1928
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +02001929def _HooksOsToLines(hooks_os):
1930 """Converts |hooks| list to list of lines for output."""
Paweł Hajdan, Jr5b593352017-06-29 18:37:45 +02001931 if not hooks_os:
1932 return []
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +02001933 s = ['hooks_os = {']
1934 for hook_os, os_hooks in hooks_os.iteritems():
Michael Moss017bcf62017-06-28 15:26:38 -07001935 s.append(' "%s": [' % hook_os)
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +02001936 for dep, hook in os_hooks:
1937 s.extend([
1938 ' # %s' % dep.hierarchy(include_url=False),
1939 ' {',
1940 ])
1941 if hook.name is not None:
1942 s.append(' "name": "%s",' % hook.name)
1943 if hook.pattern is not None:
1944 s.append(' "pattern": "%s",' % hook.pattern)
1945 s.extend(
1946 # Hooks run in the parent directory of their dep.
1947 [' "cwd": "%s",' % os.path.normpath(os.path.dirname(dep.name))] +
1948 [' "action": ['] +
1949 [' "%s",' % arg for arg in hook.action] +
1950 [' ]', ' },', '']
1951 )
Michael Moss017bcf62017-06-28 15:26:38 -07001952 s.extend([' ],', ''])
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +02001953 s.extend(['}', ''])
1954 return s
1955
1956
Paweł Hajdan, Jrfb022012017-07-06 18:00:08 +02001957def _VarsToLines(variables):
1958 """Converts |variables| dict to list of lines for output."""
1959 if not variables:
1960 return []
1961 s = ['vars = {']
1962 for key, tup in sorted(variables.iteritems()):
1963 dep, value = tup
1964 s.extend([
1965 ' # %s' % dep.hierarchy(include_url=False),
1966 ' "%s": %r,' % (key, value),
1967 '',
1968 ])
1969 s.extend(['}', ''])
1970 return s
1971
1972
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001973def CMDgrep(parser, args):
1974 """Greps through git repos managed by gclient.
1975
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001976 Runs 'git grep [args...]' for each module.
1977 """
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001978 # We can't use optparse because it will try to parse arguments sent
1979 # to git grep and throw an error. :-(
1980 if not args or re.match('(-h|--help)$', args[0]):
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001981 print(
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001982 'Usage: gclient grep [-j <N>] git-grep-args...\n\n'
1983 'Example: "gclient grep -j10 -A2 RefCountedBase" runs\n"git grep '
1984 '-A2 RefCountedBase" on each of gclient\'s git\nrepos with up to '
1985 '10 jobs.\n\nBonus: page output by appending "|& less -FRSX" to the'
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001986 ' end of your query.',
1987 file=sys.stderr)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001988 return 1
1989
1990 jobs_arg = ['--jobs=1']
1991 if re.match(r'(-j|--jobs=)\d+$', args[0]):
1992 jobs_arg, args = args[:1], args[1:]
1993 elif re.match(r'(-j|--jobs)$', args[0]):
1994 jobs_arg, args = args[:2], args[2:]
1995
1996 return CMDrecurse(
1997 parser,
1998 jobs_arg + ['--ignore', '--prepend-dir', '--no-progress', '--scm=git',
1999 'git', 'grep', '--null', '--color=Always'] + args)
davidbarr@chromium.org12f944e2012-03-01 02:18:31 +00002000
2001
stip@chromium.orga735da22015-04-29 23:18:20 +00002002def CMDroot(parser, args):
2003 """Outputs the solution root (or current dir if there isn't one)."""
2004 (options, args) = parser.parse_args(args)
2005 client = GClient.LoadCurrentConfig(options)
2006 if client:
2007 print(os.path.abspath(client.root_dir))
2008 else:
2009 print(os.path.abspath('.'))
2010
2011
agablea98a6cd2016-11-15 14:30:10 -08002012@subcommand.usage('[url]')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002013def CMDconfig(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002014 """Creates a .gclient file in the current directory.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00002015
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002016 This specifies the configuration for further commands. After update/sync,
2017 top-level DEPS files in each module are read to determine dependent
2018 modules to operate on as well. If optional [url] parameter is
2019 provided, then configuration is read from a specified Subversion server
2020 URL.
2021 """
szager@chromium.orge2e03202012-07-31 18:05:16 +00002022 # We do a little dance with the --gclientfile option. 'gclient config' is the
2023 # only command where it's acceptable to have both '--gclientfile' and '--spec'
2024 # arguments. So, we temporarily stash any --gclientfile parameter into
2025 # options.output_config_file until after the (gclientfile xor spec) error
2026 # check.
2027 parser.remove_option('--gclientfile')
2028 parser.add_option('--gclientfile', dest='output_config_file',
2029 help='Specify an alternate .gclient file')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002030 parser.add_option('--name',
2031 help='overrides the default name for the solution')
nsylvain@google.comefc80932011-05-31 21:27:56 +00002032 parser.add_option('--deps-file', default='DEPS',
2033 help='overrides the default name for the DEPS file for the'
2034 'main solutions and all sub-dependencies')
smutae7ea312016-07-18 11:59:41 -07002035 parser.add_option('--unmanaged', action='store_true', default=False,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00002036 help='overrides the default behavior to make it possible '
smutae7ea312016-07-18 11:59:41 -07002037 'to have the main solution untouched by gclient '
2038 '(gclient will check out unmanaged dependencies but '
2039 'will never sync them)')
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00002040 parser.add_option('--cache-dir',
2041 help='(git only) Cache all git repos into this dir and do '
2042 'shared clones from the cache, instead of cloning '
2043 'directly from the remote. (experimental)')
szager@chromium.orge2e03202012-07-31 18:05:16 +00002044 parser.set_defaults(config_filename=None)
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002045 (options, args) = parser.parse_args(args)
szager@chromium.orge2e03202012-07-31 18:05:16 +00002046 if options.output_config_file:
2047 setattr(options, 'config_filename', getattr(options, 'output_config_file'))
maruel@chromium.org5fc2a332010-05-26 19:37:15 +00002048 if ((options.spec and args) or len(args) > 2 or
2049 (not options.spec and not args)):
2050 parser.error('Inconsistent arguments. Use either --spec or one or 2 args')
2051
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002052 client = GClient('.', options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002053 if options.spec:
2054 client.SetConfig(options.spec)
2055 else:
maruel@chromium.org1ab7ffc2009-06-03 17:21:37 +00002056 base_url = args[0].rstrip('/')
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00002057 if not options.name:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002058 name = base_url.split('/')[-1]
nsylvain@google.com12649ef2011-06-01 17:11:20 +00002059 if name.endswith('.git'):
2060 name = name[:-4]
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00002061 else:
2062 # specify an alternate relpath for the given URL.
2063 name = options.name
agable@chromium.orgf2214672015-10-27 21:02:48 +00002064 if not os.path.abspath(os.path.join(os.getcwd(), name)).startswith(
2065 os.getcwd()):
2066 parser.error('Do not pass a relative path for --name.')
2067 if any(x in ('..', '.', '/', '\\') for x in name.split(os.sep)):
2068 parser.error('Do not include relative path components in --name.')
2069
nsylvain@google.comefc80932011-05-31 21:27:56 +00002070 deps_file = options.deps_file
agablea98a6cd2016-11-15 14:30:10 -08002071 client.SetDefaultConfig(name, deps_file, base_url,
smutae7ea312016-07-18 11:59:41 -07002072 managed=not options.unmanaged,
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00002073 cache_dir=options.cache_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002074 client.SaveConfig()
maruel@chromium.org79692d62010-05-14 18:57:13 +00002075 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002076
2077
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002078@subcommand.epilog("""Example:
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002079 gclient pack > patch.txt
2080 generate simple patch for configured client and dependences
2081""")
2082def CMDpack(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002083 """Generates a patch which can be applied at the root of the tree.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00002084
agabled437d762016-10-17 09:35:11 -07002085 Internally, runs 'git diff' on each checked out module and
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002086 dependencies, and performs minimal postprocessing of the output. The
2087 resulting patch is printed to stdout and can be applied to a freshly
2088 checked out tree via 'patch -p0 < patchfile'.
2089 """
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002090 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2091 help='override deps for the specified (comma-separated) '
2092 'platform(s); \'all\' will process all deps_os '
2093 'references')
haitao.feng@intel.com306080c2012-05-04 13:11:29 +00002094 parser.remove_option('--jobs')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002095 (options, args) = parser.parse_args(args)
iannucci@chromium.org50395ea2013-04-04 04:47:42 +00002096 # Force jobs to 1 so the stdout is not annotated with the thread ids
haitao.feng@intel.com306080c2012-05-04 13:11:29 +00002097 options.jobs = 1
kbr@google.comab318592009-09-04 00:54:55 +00002098 client = GClient.LoadCurrentConfig(options)
2099 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002100 raise gclient_utils.Error('client not configured; see \'gclient config\'')
kbr@google.comab318592009-09-04 00:54:55 +00002101 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002102 client.PrintLocationAndContents()
kbr@google.comab318592009-09-04 00:54:55 +00002103 return client.RunOnDeps('pack', args)
2104
2105
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002106def CMDstatus(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002107 """Shows modification status for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002108 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2109 help='override deps for the specified (comma-separated) '
2110 'platform(s); \'all\' will process all deps_os '
2111 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002112 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002113 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002114 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002115 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002116 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002117 client.PrintLocationAndContents()
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002118 return client.RunOnDeps('status', args)
2119
2120
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002121@subcommand.epilog("""Examples:
maruel@chromium.org79692d62010-05-14 18:57:13 +00002122 gclient sync
2123 update files from SCM according to current configuration,
2124 *for modules which have changed since last update or sync*
2125 gclient sync --force
2126 update files from SCM according to current configuration, for
2127 all modules (useful for recovering files deleted from local copy)
2128 gclient sync --revision src@31000
2129 update src directory to r31000
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002130
2131JSON output format:
2132If the --output-json option is specified, the following document structure will
2133be emitted to the provided file. 'null' entries may occur for subprojects which
2134are present in the gclient solution, but were not processed (due to custom_deps,
2135os_deps, etc.)
2136
2137{
2138 "solutions" : {
2139 "<name>": { # <name> is the posix-normalized path to the solution.
agabled437d762016-10-17 09:35:11 -07002140 "revision": [<git id hex string>|null],
2141 "scm": ["git"|null],
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002142 }
2143 }
2144}
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002145""")
2146def CMDsync(parser, args):
2147 """Checkout/update all modules."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002148 parser.add_option('-f', '--force', action='store_true',
2149 help='force update even for unchanged modules')
2150 parser.add_option('-n', '--nohooks', action='store_true',
2151 help='don\'t run hooks after the update is complete')
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00002152 parser.add_option('-p', '--noprehooks', action='store_true',
2153 help='don\'t run pre-DEPS hooks', default=False)
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002154 parser.add_option('-r', '--revision', action='append',
2155 dest='revisions', metavar='REV', default=[],
2156 help='Enforces revision/hash for the solutions with the '
2157 'format src@rev. The src@ part is optional and can be '
2158 'skipped. -r can be used multiple times when .gclient '
2159 'has multiple solutions configured and will work even '
agablea98a6cd2016-11-15 14:30:10 -08002160 'if the src@ part is skipped.')
maruel@chromium.org794207e2013-03-08 15:29:43 +00002161 parser.add_option('--with_branch_heads', action='store_true',
2162 help='Clone git "branch_heads" refspecs in addition to '
2163 'the default refspecs. This adds about 1/2GB to a '
2164 'full checkout. (git only)')
szager@chromium.org8d3348f2014-08-19 22:49:16 +00002165 parser.add_option('--with_tags', action='store_true',
2166 help='Clone git tags in addition to the default refspecs.')
agable2697cd12016-06-28 10:23:53 -07002167 parser.add_option('-H', '--head', action='store_true',
agablea98a6cd2016-11-15 14:30:10 -08002168 help='DEPRECATED: only made sense with safesync urls.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002169 parser.add_option('-D', '--delete_unversioned_trees', action='store_true',
steveblock@chromium.org98e69452012-02-16 16:36:43 +00002170 help='Deletes from the working copy any dependencies that '
2171 'have been removed since the last sync, as long as '
2172 'there are no local modifications. When used with '
2173 '--force, such dependencies are removed even if they '
2174 'have local modifications. When used with --reset, '
2175 'all untracked directories are removed from the '
tnagel@chromium.orga2aaa632014-02-28 21:47:27 +00002176 'working copy, excluding those which are explicitly '
steveblock@chromium.org98e69452012-02-16 16:36:43 +00002177 'ignored in the repository.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002178 parser.add_option('-R', '--reset', action='store_true',
2179 help='resets any local changes before updating (git only)')
bauerb@chromium.org2aad1b22011-07-22 12:00:41 +00002180 parser.add_option('-M', '--merge', action='store_true',
2181 help='merge upstream changes instead of trying to '
2182 'fast-forward or rebase')
dnj@chromium.org5b23e872015-02-20 21:25:57 +00002183 parser.add_option('-A', '--auto_rebase', action='store_true',
2184 help='Automatically rebase repositories against local '
2185 'checkout during update (git only).')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002186 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2187 help='override deps for the specified (comma-separated) '
2188 'platform(s); \'all\' will process all deps_os '
2189 'references')
Paweł Hajdan, Jr357415c2017-07-24 14:35:28 +02002190 # TODO(phajdan.jr): use argparse.SUPPRESS to hide internal flags.
2191 parser.add_option('--do-not-merge-os-specific-entries', action='store_true',
2192 help='INTERNAL ONLY - disables merging of deps_os and '
2193 'hooks_os to dependencies and hooks')
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +00002194 parser.add_option('--upstream', action='store_true',
2195 help='Make repo state match upstream branch.')
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002196 parser.add_option('--output-json',
2197 help='Output a json document to this path containing '
2198 'summary information about the sync.')
primiano@chromium.org5439ea52014-08-06 17:18:18 +00002199 parser.add_option('--no-history', action='store_true',
2200 help='GIT ONLY - Reduces the size/time of the checkout at '
2201 'the cost of no history. Requires Git 1.9+')
hinoka@chromium.org46b87412014-05-15 00:42:05 +00002202 parser.add_option('--shallow', action='store_true',
2203 help='GIT ONLY - Do a shallow clone into the cache dir. '
2204 'Requires Git 1.9+')
e.hakkinen@samsung.come8bc1aa2015-04-08 08:00:37 +00002205 parser.add_option('--no_bootstrap', '--no-bootstrap',
2206 action='store_true',
2207 help='Don\'t bootstrap from Google Storage.')
hinoka@chromium.org8a10f6d2014-06-23 18:38:57 +00002208 parser.add_option('--ignore_locks', action='store_true',
2209 help='GIT ONLY - Ignore cache locks.')
iannucci@chromium.org30a07982016-04-07 21:35:19 +00002210 parser.add_option('--break_repo_locks', action='store_true',
2211 help='GIT ONLY - Forcibly remove repo locks (e.g. '
2212 'index.lock). This should only be used if you know for '
2213 'certain that this invocation of gclient is the only '
2214 'thing operating on the git repos (e.g. on a bot).')
nodir@chromium.org5b48e482016-03-18 20:27:54 +00002215 parser.add_option('--lock_timeout', type='int', default=5000,
szager@chromium.orgdbb6f822016-02-02 22:59:30 +00002216 help='GIT ONLY - Deadline (in seconds) to wait for git '
nodir@chromium.org5b48e482016-03-18 20:27:54 +00002217 'cache lock to become available. Default is %default.')
agabled437d762016-10-17 09:35:11 -07002218 # TODO(agable): Remove these when the oldest CrOS release milestone is M56.
2219 parser.add_option('-t', '--transitive', action='store_true',
2220 help='DEPRECATED: This is a no-op.')
sdefresne69b1be12016-10-18 05:48:02 -07002221 parser.add_option('-m', '--manually_grab_svn_rev', action='store_true',
agabled437d762016-10-17 09:35:11 -07002222 help='DEPRECATED: This is a no-op.')
Paweł Hajdan, Jr7c7b5592017-05-23 15:06:05 +02002223 # TODO(phajdan.jr): Remove validation options once default (crbug/570091).
Paweł Hajdan, Jr694773d2017-05-29 16:06:23 +02002224 parser.add_option('--validate-syntax', action='store_true', default=True,
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +02002225 help='Validate the .gclient and DEPS syntax')
Paweł Hajdan, Jr7c7b5592017-05-23 15:06:05 +02002226 parser.add_option('--disable-syntax-validation', action='store_false',
2227 dest='validate_syntax',
2228 help='Disable validation of .gclient and DEPS syntax.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002229 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002230 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002231
2232 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002233 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002234
smutae7ea312016-07-18 11:59:41 -07002235 if options.revisions and options.head:
2236 # TODO(maruel): Make it a parser.error if it doesn't break any builder.
2237 print('Warning: you cannot use both --head and --revision')
2238
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002239 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002240 client.PrintLocationAndContents()
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002241 ret = client.RunOnDeps('update', args)
2242 if options.output_json:
2243 slns = {}
2244 for d in client.subtree(True):
2245 normed = d.name.replace('\\', '/').rstrip('/') + '/'
2246 slns[normed] = {
2247 'revision': d.got_revision,
2248 'scm': d.used_scm.name if d.used_scm else None,
hinoka@chromium.org17db9052014-05-10 01:11:29 +00002249 'url': str(d.url) if d.url else None,
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002250 }
2251 with open(options.output_json, 'wb') as f:
2252 json.dump({'solutions': slns}, f)
2253 return ret
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002254
2255
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002256CMDupdate = CMDsync
2257
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002258
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +02002259def CMDvalidate(parser, args):
2260 """Validates the .gclient and DEPS syntax."""
2261 options, args = parser.parse_args(args)
2262 options.validate_syntax = True
2263 client = GClient.LoadCurrentConfig(options)
2264 rv = client.RunOnDeps('validate', args)
2265 if rv == 0:
2266 print('validate: SUCCESS')
2267 else:
2268 print('validate: FAILURE')
2269 return rv
2270
2271
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002272def CMDdiff(parser, args):
2273 """Displays local diff for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002274 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2275 help='override deps for the specified (comma-separated) '
2276 'platform(s); \'all\' will process all deps_os '
2277 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002278 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002279 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002280 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002281 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002282 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002283 client.PrintLocationAndContents()
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002284 return client.RunOnDeps('diff', args)
2285
2286
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002287def CMDrevert(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002288 """Reverts all modifications in every dependencies.
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00002289
2290 That's the nuclear option to get back to a 'clean' state. It removes anything
agabled437d762016-10-17 09:35:11 -07002291 that shows up in git status."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002292 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2293 help='override deps for the specified (comma-separated) '
2294 'platform(s); \'all\' will process all deps_os '
2295 'references')
2296 parser.add_option('-n', '--nohooks', action='store_true',
2297 help='don\'t run hooks after the revert is complete')
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00002298 parser.add_option('-p', '--noprehooks', action='store_true',
2299 help='don\'t run pre-DEPS hooks', default=False)
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +00002300 parser.add_option('--upstream', action='store_true',
2301 help='Make repo state match upstream branch.')
iannucci@chromium.orgbf525dc2016-04-07 22:00:28 +00002302 parser.add_option('--break_repo_locks', action='store_true',
2303 help='GIT ONLY - Forcibly remove repo locks (e.g. '
2304 'index.lock). This should only be used if you know for '
2305 'certain that this invocation of gclient is the only '
2306 'thing operating on the git repos (e.g. on a bot).')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002307 (options, args) = parser.parse_args(args)
2308 # --force is implied.
2309 options.force = True
steveblock@chromium.org98e69452012-02-16 16:36:43 +00002310 options.reset = False
2311 options.delete_unversioned_trees = False
agablec903d732016-07-26 09:07:24 -07002312 options.merge = False
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002313 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002314 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002315 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002316 return client.RunOnDeps('revert', args)
2317
2318
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002319def CMDrunhooks(parser, args):
2320 """Runs hooks for files that have been modified in the local working copy."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002321 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2322 help='override deps for the specified (comma-separated) '
2323 'platform(s); \'all\' will process all deps_os '
2324 'references')
2325 parser.add_option('-f', '--force', action='store_true', default=True,
2326 help='Deprecated. No effect.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002327 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002328 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002329 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002330 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002331 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002332 client.PrintLocationAndContents()
maruel@chromium.org5df6a462009-08-28 18:52:26 +00002333 options.force = True
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002334 options.nohooks = False
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002335 return client.RunOnDeps('runhooks', args)
2336
2337
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002338def CMDrevinfo(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002339 """Outputs revision info mapping for the client and its dependencies.
maruel@chromium.org9eda4112010-06-11 18:56:10 +00002340
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002341 This allows the capture of an overall 'revision' for the source tree that
maruel@chromium.org9eda4112010-06-11 18:56:10 +00002342 can be used to reproduce the same tree in the future. It is only useful for
agabled437d762016-10-17 09:35:11 -07002343 'unpinned dependencies', i.e. DEPS/deps references without a git hash.
2344 A git branch name isn't 'pinned' since the actual commit can change.
maruel@chromium.org9eda4112010-06-11 18:56:10 +00002345 """
2346 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')
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00002350 parser.add_option('-a', '--actual', action='store_true',
2351 help='gets the actual checked out revisions instead of the '
2352 'ones specified in the DEPS and .gclient files')
maruel@chromium.org9eda4112010-06-11 18:56:10 +00002353 parser.add_option('-s', '--snapshot', action='store_true',
2354 help='creates a snapshot .gclient file of the current '
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00002355 'version of all repositories to reproduce the tree, '
2356 'implies -a')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002357 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002358 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002359 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002360 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002361 client.PrintRevInfo()
maruel@chromium.org79692d62010-05-14 18:57:13 +00002362 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002363
2364
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00002365def CMDverify(parser, args):
2366 """Verifies the DEPS file deps are only from allowed_hosts."""
2367 (options, args) = parser.parse_args(args)
2368 client = GClient.LoadCurrentConfig(options)
2369 if not client:
2370 raise gclient_utils.Error('client not configured; see \'gclient config\'')
2371 client.RunOnDeps(None, [])
2372 # Look at each first-level dependency of this gclient only.
2373 for dep in client.dependencies:
2374 bad_deps = dep.findDepsFromNotAllowedHosts()
2375 if not bad_deps:
2376 continue
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002377 print("There are deps from not allowed hosts in file %s" % dep.deps_file)
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00002378 for bad_dep in bad_deps:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002379 print("\t%s at %s" % (bad_dep.name, bad_dep.url))
2380 print("allowed_hosts:", ', '.join(dep.allowed_hosts))
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00002381 sys.stdout.flush()
2382 raise gclient_utils.Error(
2383 'dependencies from disallowed hosts; check your DEPS file.')
2384 return 0
2385
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002386class OptionParser(optparse.OptionParser):
szager@chromium.orge2e03202012-07-31 18:05:16 +00002387 gclientfile_default = os.environ.get('GCLIENT_FILE', '.gclient')
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002388
2389 def __init__(self, **kwargs):
2390 optparse.OptionParser.__init__(
2391 self, version='%prog ' + __version__, **kwargs)
2392
2393 # Some arm boards have issues with parallel sync.
2394 if platform.machine().startswith('arm'):
2395 jobs = 1
2396 else:
2397 jobs = max(8, gclient_utils.NumLocalCpus())
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002398
2399 self.add_option(
2400 '-j', '--jobs', default=jobs, type='int',
2401 help='Specify how many SCM commands can run in parallel; defaults to '
tnagel@chromium.orga2aaa632014-02-28 21:47:27 +00002402 '%default on this machine')
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002403 self.add_option(
2404 '-v', '--verbose', action='count', default=0,
2405 help='Produces additional output for diagnostics. Can be used up to '
2406 'three times for more logging info.')
2407 self.add_option(
2408 '--gclientfile', dest='config_filename',
2409 help='Specify an alternate %s file' % self.gclientfile_default)
2410 self.add_option(
2411 '--spec',
2412 help='create a gclient file containing the provided string. Due to '
2413 'Cygwin/Python brokenness, it can\'t contain any newlines.')
2414 self.add_option(
2415 '--no-nag-max', default=False, action='store_true',
scottmg@chromium.orgf547c802013-09-27 17:55:26 +00002416 help='Ignored for backwards compatibility.')
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002417
2418 def parse_args(self, args=None, values=None):
2419 """Integrates standard options processing."""
2420 options, args = optparse.OptionParser.parse_args(self, args, values)
2421 levels = [logging.ERROR, logging.WARNING, logging.INFO, logging.DEBUG]
2422 logging.basicConfig(
2423 level=levels[min(options.verbose, len(levels) - 1)],
maruel@chromium.org0895b752011-08-26 20:40:33 +00002424 format='%(module)s(%(lineno)d) %(funcName)s:%(message)s')
szager@chromium.orge2e03202012-07-31 18:05:16 +00002425 if options.config_filename and options.spec:
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002426 self.error('Cannot specifiy both --gclientfile and --spec')
rdsmith@chromium.orgd9591f02014-02-05 19:28:20 +00002427 if (options.config_filename and
2428 options.config_filename != os.path.basename(options.config_filename)):
2429 self.error('--gclientfile target must be a filename, not a path')
szager@chromium.orge2e03202012-07-31 18:05:16 +00002430 if not options.config_filename:
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002431 options.config_filename = self.gclientfile_default
maruel@chromium.org0895b752011-08-26 20:40:33 +00002432 options.entries_filename = options.config_filename + '_entries'
2433 if options.jobs < 1:
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002434 self.error('--jobs must be 1 or higher')
maruel@chromium.org0895b752011-08-26 20:40:33 +00002435
2436 # These hacks need to die.
2437 if not hasattr(options, 'revisions'):
2438 # GClient.RunOnDeps expects it even if not applicable.
2439 options.revisions = []
smutae7ea312016-07-18 11:59:41 -07002440 if not hasattr(options, 'head'):
2441 options.head = None
maruel@chromium.org0895b752011-08-26 20:40:33 +00002442 if not hasattr(options, 'nohooks'):
2443 options.nohooks = True
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00002444 if not hasattr(options, 'noprehooks'):
2445 options.noprehooks = True
maruel@chromium.org0895b752011-08-26 20:40:33 +00002446 if not hasattr(options, 'deps_os'):
2447 options.deps_os = None
maruel@chromium.org0895b752011-08-26 20:40:33 +00002448 if not hasattr(options, 'force'):
2449 options.force = None
2450 return (options, args)
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002451
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002452
2453def disable_buffering():
2454 # Make stdout auto-flush so buildbot doesn't kill us during lengthy
2455 # operations. Python as a strong tendency to buffer sys.stdout.
2456 sys.stdout = gclient_utils.MakeFileAutoFlush(sys.stdout)
2457 # Make stdout annotated with the thread ids.
2458 sys.stdout = gclient_utils.MakeFileAnnotated(sys.stdout)
maruel@chromium.org0895b752011-08-26 20:40:33 +00002459
2460
sbc@chromium.org013731e2015-02-26 18:28:43 +00002461def main(argv):
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002462 """Doesn't parse the arguments here, just find the right subcommand to
2463 execute."""
maruel@chromium.org82798cb2012-02-23 18:16:12 +00002464 if sys.hexversion < 0x02060000:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002465 print(
maruel@chromium.org82798cb2012-02-23 18:16:12 +00002466 '\nYour python version %s is unsupported, please upgrade.\n' %
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002467 sys.version.split(' ', 1)[0],
2468 file=sys.stderr)
maruel@chromium.org82798cb2012-02-23 18:16:12 +00002469 return 2
bcwhite@chromium.org6683ab42013-02-11 16:13:47 +00002470 if not sys.executable:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002471 print(
2472 '\nPython cannot find the location of it\'s own executable.\n',
2473 file=sys.stderr)
bcwhite@chromium.org6683ab42013-02-11 16:13:47 +00002474 return 2
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002475 fix_encoding.fix_encoding()
2476 disable_buffering()
iannucci@chromium.org596cd5c2016-04-04 21:34:39 +00002477 setup_color.init()
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002478 dispatcher = subcommand.CommandDispatcher(__name__)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00002479 try:
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002480 return dispatcher.execute(OptionParser(), argv)
xusydoc@chromium.org2fd6c3f2013-05-03 21:57:55 +00002481 except KeyboardInterrupt:
2482 gclient_utils.GClientChildren.KillAllRemainingChildren()
2483 raise
vapier@chromium.orga81a56e2015-11-11 07:56:13 +00002484 except (gclient_utils.Error, subprocess2.CalledProcessError) as e:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002485 print('Error: %s' % str(e), file=sys.stderr)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00002486 return 1
borenet@google.com6a9b1682014-03-24 18:35:23 +00002487 finally:
2488 gclient_utils.PrintWarnings()
sbc@chromium.org013731e2015-02-26 18:28:43 +00002489 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002490
2491
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00002492if '__main__' == __name__:
sbc@chromium.org013731e2015-02-26 18:28:43 +00002493 try:
2494 sys.exit(main(sys.argv[1:]))
2495 except KeyboardInterrupt:
2496 sys.stderr.write('interrupted\n')
2497 sys.exit(1)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002498
2499# vim: ts=2:sw=2:tw=80:et: