blob: 4eb04bd6651b113956145d3e09c05edf773569ed [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, Jrc10a4d82017-06-14 14:06:50 +0200197 cmd = list(self._action)
198 if cmd[0] == 'python':
199 # If the hook specified "python" as the first item, the action is a
200 # Python script. Run it by starting a new copy of the same
201 # interpreter.
202 cmd[0] = sys.executable
Paweł Hajdan, Jrc9364392017-06-14 17:11:56 +0200203
204 cwd = root
205 if self._cwd:
206 cwd = os.path.join(cwd, self._cwd)
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200207 try:
208 start_time = time.time()
209 gclient_utils.CheckCallAndFilterAndHeader(
Paweł Hajdan, Jrc9364392017-06-14 17:11:56 +0200210 cmd, cwd=cwd, always=True)
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200211 except (gclient_utils.Error, subprocess2.CalledProcessError) as e:
212 # Use a discrete exit status code of 2 to indicate that a hook action
213 # failed. Users of this script may wish to treat hook action failures
214 # differently from VC failures.
215 print('Error: %s' % str(e), file=sys.stderr)
216 sys.exit(2)
217 finally:
218 elapsed_time = time.time() - start_time
219 if elapsed_time > 10:
220 print("Hook '%s' took %.2f secs" % (
221 gclient_utils.CommandToStr(cmd), elapsed_time))
222
223
Paweł Hajdan, Jre79ddea2017-07-24 22:23:49 +0200224class DependencySettings(object):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000225 """Immutable configuration settings."""
226 def __init__(
Paweł Hajdan, Jre79ddea2017-07-24 22:23:49 +0200227 self, parent, raw_url, url, managed, custom_deps, custom_vars,
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +0200228 custom_hooks, deps_file, should_process, relative,
229 condition, condition_value):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000230 # These are not mutable:
231 self._parent = parent
mmoss@chromium.org8f93f792014-08-26 23:24:09 +0000232 self._deps_file = deps_file
Paweł Hajdan, Jre79ddea2017-07-24 22:23:49 +0200233 self._raw_url = raw_url
maruel@chromium.org064186c2011-09-27 23:53:33 +0000234 self._url = url
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +0200235 # The condition as string (or None). Useful to keep e.g. for flatten.
236 self._condition = condition
237 # Boolean value of the condition. If there's no condition, just True.
238 self._condition_value = condition_value
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000239 # 'managed' determines whether or not this dependency is synced/updated by
240 # gclient after gclient checks it out initially. The difference between
241 # 'managed' and 'should_process' is that the user specifies 'managed' via
smutae7ea312016-07-18 11:59:41 -0700242 # the --unmanaged command-line flag or a .gclient config, where
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000243 # 'should_process' is dynamically set by gclient if it goes over its
244 # recursion limit and controls gclient's behavior so it does not misbehave.
245 self._managed = managed
246 self._should_process = should_process
agabledce6ddc2016-09-08 10:02:16 -0700247 # If this is a recursed-upon sub-dependency, and the parent has
248 # use_relative_paths set, then this dependency should check out its own
249 # dependencies relative to that parent's path for this, rather than
250 # relative to the .gclient file.
251 self._relative = relative
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +0000252 # This is a mutable value which has the list of 'target_os' OSes listed in
253 # the current deps file.
254 self.local_target_os = None
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000255
256 # These are only set in .gclient and not in DEPS files.
257 self._custom_vars = custom_vars or {}
258 self._custom_deps = custom_deps or {}
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000259 self._custom_hooks = custom_hooks or []
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000260
maruel@chromium.org064186c2011-09-27 23:53:33 +0000261 # Post process the url to remove trailing slashes.
262 if isinstance(self._url, basestring):
263 # urls are sometime incorrectly written as proto://host/path/@rev. Replace
264 # it to proto://host/path@rev.
maruel@chromium.org064186c2011-09-27 23:53:33 +0000265 self._url = self._url.replace('/@', '@')
Paweł Hajdan, Jr7e9303b2017-05-23 14:38:27 +0200266 elif not isinstance(self._url, (None.__class__)):
maruel@chromium.org064186c2011-09-27 23:53:33 +0000267 raise gclient_utils.Error(
Paweł Hajdan, Jr7e9303b2017-05-23 14:38:27 +0200268 ('dependency url must be either string or None, '
269 'instead of %s') % self._url.__class__.__name__)
mmoss@chromium.orgd0b272b2013-01-30 23:55:33 +0000270 # Make any deps_file path platform-appropriate.
271 for sep in ['/', '\\']:
272 self._deps_file = self._deps_file.replace(sep, os.sep)
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000273
274 @property
275 def deps_file(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000276 return self._deps_file
277
278 @property
279 def managed(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000280 return self._managed
281
282 @property
283 def parent(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000284 return self._parent
285
286 @property
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000287 def root(self):
288 """Returns the root node, a GClient object."""
289 if not self.parent:
290 # This line is to signal pylint that it could be a GClient instance.
291 return self or GClient(None, None)
292 return self.parent.root
293
294 @property
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000295 def should_process(self):
296 """True if this dependency should be processed, i.e. checked out."""
297 return self._should_process
298
299 @property
300 def custom_vars(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000301 return self._custom_vars.copy()
302
303 @property
304 def custom_deps(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000305 return self._custom_deps.copy()
306
maruel@chromium.org064186c2011-09-27 23:53:33 +0000307 @property
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000308 def custom_hooks(self):
309 return self._custom_hooks[:]
310
311 @property
Paweł Hajdan, Jre79ddea2017-07-24 22:23:49 +0200312 def raw_url(self):
313 """URL before variable expansion."""
314 return self._raw_url
315
316 @property
maruel@chromium.org064186c2011-09-27 23:53:33 +0000317 def url(self):
Paweł Hajdan, Jre79ddea2017-07-24 22:23:49 +0200318 """URL after variable expansion."""
maruel@chromium.org064186c2011-09-27 23:53:33 +0000319 return self._url
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000320
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000321 @property
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +0200322 def condition(self):
323 return self._condition
324
325 @property
326 def condition_value(self):
327 return self._condition_value
328
329 @property
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +0000330 def target_os(self):
331 if self.local_target_os is not None:
332 return tuple(set(self.local_target_os).union(self.parent.target_os))
333 else:
334 return self.parent.target_os
335
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000336 def get_custom_deps(self, name, url):
337 """Returns a custom deps if applicable."""
338 if self.parent:
339 url = self.parent.get_custom_deps(name, url)
340 # None is a valid return value to disable a dependency.
341 return self.custom_deps.get(name, url)
342
maruel@chromium.org064186c2011-09-27 23:53:33 +0000343
344class Dependency(gclient_utils.WorkItem, DependencySettings):
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000345 """Object that represents a dependency checkout."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000346
Paweł Hajdan, Jre79ddea2017-07-24 22:23:49 +0200347 def __init__(self, parent, name, raw_url, url, managed, custom_deps,
agabledce6ddc2016-09-08 10:02:16 -0700348 custom_vars, custom_hooks, deps_file, should_process,
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +0200349 relative, condition, condition_value):
maruel@chromium.org6ca8bf82011-09-19 23:04:30 +0000350 gclient_utils.WorkItem.__init__(self, name)
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000351 DependencySettings.__init__(
Paweł Hajdan, Jre79ddea2017-07-24 22:23:49 +0200352 self, parent, raw_url, url, managed, custom_deps, custom_vars,
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +0200353 custom_hooks, deps_file, should_process, relative,
354 condition, condition_value)
maruel@chromium.org68988972011-09-20 14:11:42 +0000355
356 # This is in both .gclient and DEPS files:
maruel@chromium.org064186c2011-09-27 23:53:33 +0000357 self._deps_hooks = []
maruel@chromium.org68988972011-09-20 14:11:42 +0000358
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000359 self._pre_deps_hooks = []
360
maruel@chromium.org68988972011-09-20 14:11:42 +0000361 # Calculates properties:
maruel@chromium.org064186c2011-09-27 23:53:33 +0000362 self._parsed_url = None
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +0000363 self._dependencies = []
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +0200364 self._vars = {}
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +0200365 self._os_dependencies = {}
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +0200366 self._os_deps_hooks = {}
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200367
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000368 # A cache of the files affected by the current operation, necessary for
369 # hooks.
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000370 self._file_list = []
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000371 # List of host names from which dependencies are allowed.
372 # Default is an empty set, meaning unspecified in DEPS file, and hence all
373 # hosts will be allowed. Non-empty set means whitelist of hosts.
374 # allowed_hosts var is scoped to its DEPS file, and so it isn't recursive.
375 self._allowed_hosts = frozenset()
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +0200376 # Spec for .gni output to write (if any).
377 self._gn_args_file = None
378 self._gn_args = []
maruel@chromium.org85c2a192010-07-22 21:14:43 +0000379 # If it is not set to True, the dependency wasn't processed for its child
380 # dependency, i.e. its DEPS wasn't read.
maruel@chromium.org064186c2011-09-27 23:53:33 +0000381 self._deps_parsed = False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000382 # This dependency has been processed, i.e. checked out
maruel@chromium.org064186c2011-09-27 23:53:33 +0000383 self._processed = False
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000384 # This dependency had its pre-DEPS hooks run
385 self._pre_deps_hooks_ran = False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000386 # This dependency had its hook run
maruel@chromium.org064186c2011-09-27 23:53:33 +0000387 self._hooks_ran = False
kustermann@google.coma692e8f2013-04-18 08:32:04 +0000388 # This is the scm used to checkout self.url. It may be used by dependencies
389 # to get the datetime of the revision we checked out.
390 self._used_scm = None
szager@chromium.org4ad264b2014-05-20 04:43:47 +0000391 self._used_revision = None
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +0000392 # The actual revision we ended up getting, or None if that information is
393 # unavailable
394 self._got_revision = None
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000395
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000396 # This is a mutable value that overrides the normal recursion limit for this
397 # dependency. It is read from the actual DEPS file so cannot be set on
398 # class instantiation.
399 self.recursion_override = None
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000400 # recursedeps is a mutable value that selectively overrides the default
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000401 # 'no recursion' setting on a dep-by-dep basis. It will replace
402 # recursion_override.
iannucci@chromium.orgafa11ac2016-05-04 22:17:34 +0000403 #
404 # It will be a dictionary of {deps_name: {"deps_file": depfile_name}} or
405 # None.
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000406 self.recursedeps = None
hinoka885e5b12016-06-08 14:40:09 -0700407 # This is inherited from WorkItem. We want the URL to be a resource.
408 if url and isinstance(url, basestring):
409 # The url is usually given to gclient either as https://blah@123
qyearsley12fa6ff2016-08-24 09:18:40 -0700410 # or just https://blah. The @123 portion is irrelevant.
hinoka885e5b12016-06-08 14:40:09 -0700411 self.resources.append(url.split('@')[0])
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000412
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000413 if not self.name and self.parent:
414 raise gclient_utils.Error('Dependency without name')
415
maruel@chromium.org470b5432011-10-11 18:18:19 +0000416 @property
417 def requirements(self):
418 """Calculate the list of requirements."""
419 requirements = set()
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000420 # self.parent is implicitly a requirement. This will be recursive by
421 # definition.
422 if self.parent and self.parent.name:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000423 requirements.add(self.parent.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000424
425 # For a tree with at least 2 levels*, the leaf node needs to depend
426 # on the level higher up in an orderly way.
427 # This becomes messy for >2 depth as the DEPS file format is a dictionary,
428 # thus unsorted, while the .gclient format is a list thus sorted.
429 #
430 # * _recursion_limit is hard coded 2 and there is no hope to change this
431 # value.
432 #
433 # Interestingly enough, the following condition only works in the case we
434 # want: self is a 2nd level node. 3nd level node wouldn't need this since
435 # they already have their parent as a requirement.
maruel@chromium.org470b5432011-10-11 18:18:19 +0000436 if self.parent and self.parent.parent and not self.parent.parent.parent:
437 requirements |= set(i.name for i in self.root.dependencies if i.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000438
maruel@chromium.org470b5432011-10-11 18:18:19 +0000439 if self.name:
440 requirements |= set(
441 obj.name for obj in self.root.subtree(False)
442 if (obj is not self
443 and obj.name and
444 self.name.startswith(posixpath.join(obj.name, ''))))
445 requirements = tuple(sorted(requirements))
446 logging.info('Dependency(%s).requirements = %s' % (self.name, requirements))
447 return requirements
448
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000449 @property
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000450 def try_recursedeps(self):
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000451 """Returns False if recursion_override is ever specified."""
452 if self.recursion_override is not None:
453 return False
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000454 return self.parent.try_recursedeps
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000455
456 @property
457 def recursion_limit(self):
458 """Returns > 0 if this dependency is not too recursed to be processed."""
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000459 # We continue to support the absence of recursedeps until tools and DEPS
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000460 # using recursion_override are updated.
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000461 if self.try_recursedeps and self.parent.recursedeps != None:
462 if self.name in self.parent.recursedeps:
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000463 return 1
464
465 if self.recursion_override is not None:
466 return self.recursion_override
467 return max(self.parent.recursion_limit - 1, 0)
468
maruel@chromium.org470b5432011-10-11 18:18:19 +0000469 def verify_validity(self):
470 """Verifies that this Dependency is fine to add as a child of another one.
471
472 Returns True if this entry should be added, False if it is a duplicate of
473 another entry.
474 """
475 logging.info('Dependency(%s).verify_validity()' % self.name)
476 if self.name in [s.name for s in self.parent.dependencies]:
477 raise gclient_utils.Error(
478 'The same name "%s" appears multiple times in the deps section' %
479 self.name)
480 if not self.should_process:
481 # Return early, no need to set requirements.
482 return True
483
484 # This require a full tree traversal with locks.
485 siblings = [d for d in self.root.subtree(False) if d.name == self.name]
486 for sibling in siblings:
maruel@chromium.orgb848d5b2012-10-10 23:25:50 +0000487 self_url = self.LateOverride(self.url)
488 sibling_url = sibling.LateOverride(sibling.url)
489 # Allow to have only one to be None or ''.
490 if self_url != sibling_url and bool(self_url) == bool(sibling_url):
maruel@chromium.org470b5432011-10-11 18:18:19 +0000491 raise gclient_utils.Error(
maruel@chromium.orgb848d5b2012-10-10 23:25:50 +0000492 ('Dependency %s specified more than once:\n'
493 ' %s [%s]\n'
494 'vs\n'
495 ' %s [%s]') % (
496 self.name,
497 sibling.hierarchy(),
498 sibling_url,
499 self.hierarchy(),
500 self_url))
maruel@chromium.org470b5432011-10-11 18:18:19 +0000501 # In theory we could keep it as a shadow of the other one. In
502 # practice, simply ignore it.
503 logging.warn('Won\'t process duplicate dependency %s' % sibling)
504 return False
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000505 return True
maruel@chromium.org064186c2011-09-27 23:53:33 +0000506
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000507 def LateOverride(self, url):
Paweł Hajdan, Jr7e9303b2017-05-23 14:38:27 +0200508 """Resolves the parsed url from url."""
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000509 assert self.parsed_url == None or not self.should_process, self.parsed_url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000510 parsed_url = self.get_custom_deps(self.name, url)
511 if parsed_url != url:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000512 logging.info(
513 'Dependency(%s).LateOverride(%s) -> %s' %
514 (self.name, url, parsed_url))
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000515 return parsed_url
516
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000517 if isinstance(url, basestring):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000518 parsed_url = urlparse.urlparse(url)
scr@chromium.orgf1eccaf2014-04-11 15:51:33 +0000519 if (not parsed_url[0] and
520 not re.match(r'^\w+\@[\w\.-]+\:[\w\/]+', parsed_url[2])):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000521 # A relative url. Fetch the real base.
522 path = parsed_url[2]
523 if not path.startswith('/'):
524 raise gclient_utils.Error(
525 'relative DEPS entry \'%s\' must begin with a slash' % url)
526 # Create a scm just to query the full url.
527 parent_url = self.parent.parsed_url
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000528 scm = gclient_scm.CreateSCM(
529 parent_url, self.root.root_dir, None, self.outbuf)
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000530 parsed_url = scm.FullUrlForRelativeUrl(url)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000531 else:
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000532 parsed_url = url
maruel@chromium.org470b5432011-10-11 18:18:19 +0000533 logging.info(
534 'Dependency(%s).LateOverride(%s) -> %s' %
535 (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000536 return parsed_url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000537
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000538 if url is None:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000539 logging.info(
540 'Dependency(%s).LateOverride(%s) -> %s' % (self.name, url, url))
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000541 return url
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000542
543 raise gclient_utils.Error('Unknown url type')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000544
bratell@opera.comed2b4fe2013-12-16 14:34:12 +0000545 @staticmethod
546 def MergeWithOsDeps(deps, deps_os, target_os_list):
547 """Returns a new "deps" structure that is the deps sent in updated
548 with information from deps_os (the deps_os section of the DEPS
549 file) that matches the list of target os."""
bratell@opera.comed2b4fe2013-12-16 14:34:12 +0000550 new_deps = deps.copy()
Paweł Hajdan, Jrfd0057e2017-06-21 14:20:21 +0200551 for dep_os, os_deps in deps_os.iteritems():
552 for key, value in os_deps.iteritems():
553 if value is None:
554 # Make this condition very visible, so it's not a silent failure.
555 # It's unclear how to support None override in deps_os.
556 logging.error('Ignoring %r:%r in %r deps_os', key, value, dep_os)
557 continue
558
559 # Normalize value to be a dict which contains |should_process| metadata.
560 if isinstance(value, basestring):
561 value = {'url': value}
562 assert isinstance(value, collections.Mapping), (key, value)
563 value['should_process'] = dep_os in target_os_list
564
565 # Handle collisions/overrides.
566 if key in new_deps and new_deps[key] != value:
567 # Normalize the existing new_deps entry.
568 if isinstance(new_deps[key], basestring):
569 new_deps[key] = {'url': new_deps[key]}
570 assert isinstance(new_deps[key],
571 collections.Mapping), (key, new_deps[key])
572
573 # It's OK if the "override" sets the key to the same value.
574 # This is mostly for legacy reasons to keep existing DEPS files
575 # working. Often mac/ios and unix/android will do this.
576 if value['url'] != new_deps[key]['url']:
577 raise gclient_utils.Error(
578 ('Value from deps_os (%r; %r: %r) conflicts with existing deps '
579 'entry (%r).') % (dep_os, key, value, new_deps[key]))
580
581 # We'd otherwise overwrite |should_process| metadata, but a dep should
582 # be processed if _any_ of its references call for that.
583 value['should_process'] = (
584 value['should_process'] or
585 new_deps[key].get('should_process', True))
586
587 new_deps[key] = value
588
bratell@opera.comed2b4fe2013-12-16 14:34:12 +0000589 return new_deps
590
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200591 def _postprocess_deps(self, deps, rel_prefix):
592 """Performs post-processing of deps compared to what's in the DEPS file."""
Paweł Hajdan, Jr4426eaf2017-06-13 12:25:47 +0200593 # Make sure the dict is mutable, e.g. in case it's frozen.
594 deps = dict(deps)
595
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200596 # If a line is in custom_deps, but not in the solution, we want to append
597 # this line to the solution.
598 for d in self.custom_deps:
599 if d not in deps:
600 deps[d] = self.custom_deps[d]
601
602 if rel_prefix:
603 logging.warning('use_relative_paths enabled.')
604 rel_deps = {}
605 for d, url in deps.items():
606 # normpath is required to allow DEPS to use .. in their
607 # dependency local path.
608 rel_deps[os.path.normpath(os.path.join(rel_prefix, d))] = url
609 logging.warning('Updating deps by prepending %s.', rel_prefix)
610 deps = rel_deps
611
612 return deps
613
614 def _deps_to_objects(self, deps, use_relative_paths):
615 """Convert a deps dict to a dict of Dependency objects."""
616 deps_to_add = []
617 for name, dep_value in deps.iteritems():
618 should_process = self.recursion_limit and self.should_process
619 deps_file = self.deps_file
620 if self.recursedeps is not None:
621 ent = self.recursedeps.get(name)
622 if ent is not None:
623 deps_file = ent['deps_file']
624 if dep_value is None:
625 continue
626 condition = None
627 condition_value = True
628 if isinstance(dep_value, basestring):
Paweł Hajdan, Jre79ddea2017-07-24 22:23:49 +0200629 raw_url = dep_value
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200630 else:
631 # This should be guaranteed by schema checking in gclient_eval.
632 assert isinstance(dep_value, collections.Mapping)
Paweł Hajdan, Jre79ddea2017-07-24 22:23:49 +0200633 raw_url = dep_value['url']
Paweł Hajdan, Jrfd0057e2017-06-21 14:20:21 +0200634 # Take into account should_process metadata set by MergeWithOsDeps.
635 should_process = (should_process and
636 dep_value.get('should_process', True))
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200637 condition = dep_value.get('condition')
Paweł Hajdan, Jre79ddea2017-07-24 22:23:49 +0200638
639 url = raw_url.format(**self.get_vars())
640
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200641 if condition:
Paweł Hajdan, Jrd3790252017-07-03 21:06:24 +0200642 condition_value = gclient_eval.EvaluateCondition(
643 condition, self.get_vars())
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200644 should_process = should_process and condition_value
645 deps_to_add.append(Dependency(
Paweł Hajdan, Jre79ddea2017-07-24 22:23:49 +0200646 self, name, raw_url, url, None, None, self.custom_vars, None,
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200647 deps_file, should_process, use_relative_paths, condition,
648 condition_value))
649 deps_to_add.sort(key=lambda x: x.name)
650 return deps_to_add
651
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000652 def ParseDepsFile(self):
maruel@chromium.org271375b2010-06-23 19:17:38 +0000653 """Parses the DEPS file for this dependency."""
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000654 assert not self.deps_parsed
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000655 assert not self.dependencies
iannucci@chromium.org0c3f3052014-05-22 00:29:22 +0000656
657 deps_content = None
cmp@chromium.org76ce73c2014-07-02 00:13:18 +0000658
659 # First try to locate the configured deps file. If it's missing, fallback
660 # to DEPS.
661 deps_files = [self.deps_file]
662 if 'DEPS' not in deps_files:
663 deps_files.append('DEPS')
664 for deps_file in deps_files:
665 filepath = os.path.join(self.root.root_dir, self.name, deps_file)
666 if os.path.isfile(filepath):
667 logging.info(
668 'ParseDepsFile(%s): %s file found at %s', self.name, deps_file,
669 filepath)
670 break
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000671 logging.info(
cmp@chromium.org76ce73c2014-07-02 00:13:18 +0000672 'ParseDepsFile(%s): No %s file found at %s', self.name, deps_file,
673 filepath)
674
675 if os.path.isfile(filepath):
maruel@chromium.org46304292010-10-28 11:42:00 +0000676 deps_content = gclient_utils.FileRead(filepath)
cmp@chromium.org76ce73c2014-07-02 00:13:18 +0000677 logging.debug('ParseDepsFile(%s) read:\n%s', self.name, deps_content)
iannucci@chromium.org0c3f3052014-05-22 00:29:22 +0000678
679 local_scope = {}
680 if deps_content:
Paweł Hajdan, Jrf1587bf2017-06-20 21:19:07 +0200681 global_scope = {
Paweł Hajdan, Jre79ddea2017-07-24 22:23:49 +0200682 'Var': lambda var_name: '{%s}' % var_name,
Paweł Hajdan, Jrf1587bf2017-06-20 21:19:07 +0200683 'deps_os': {},
684 }
maruel@chromium.org46304292010-10-28 11:42:00 +0000685 # Eval the content.
686 try:
Paweł Hajdan, Jrc485d5a2017-06-02 12:08:09 +0200687 if self._get_option('validate_syntax', False):
688 gclient_eval.Exec(deps_content, global_scope, local_scope, filepath)
689 else:
690 exec(deps_content, global_scope, local_scope)
vapier@chromium.orga81a56e2015-11-11 07:56:13 +0000691 except SyntaxError as e:
maruel@chromium.org46304292010-10-28 11:42:00 +0000692 gclient_utils.SyntaxErrorToError(filepath, e)
iannucci@chromium.org0c3f3052014-05-22 00:29:22 +0000693
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000694 if 'allowed_hosts' in local_scope:
695 try:
696 self._allowed_hosts = frozenset(local_scope.get('allowed_hosts'))
697 except TypeError: # raised if non-iterable
698 pass
699 if not self._allowed_hosts:
700 logging.warning("allowed_hosts is specified but empty %s",
701 self._allowed_hosts)
702 raise gclient_utils.Error(
703 'ParseDepsFile(%s): allowed_hosts must be absent '
704 'or a non-empty iterable' % self.name)
705
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +0200706 self._gn_args_file = local_scope.get('gclient_gn_args_file')
707 self._gn_args = local_scope.get('gclient_gn_args', [])
708
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200709 # Since we heavily post-process things, freeze ones which should
710 # reflect original state of DEPS.
711 self._vars = gclient_utils.freeze(local_scope.get('vars', {}))
712
713 # If use_relative_paths is set in the DEPS file, regenerate
714 # the dictionary using paths relative to the directory containing
715 # the DEPS file. Also update recursedeps if use_relative_paths is
716 # enabled.
717 # If the deps file doesn't set use_relative_paths, but the parent did
718 # (and therefore set self.relative on this Dependency object), then we
719 # want to modify the deps and recursedeps by prepending the parent
720 # directory of this dependency.
721 use_relative_paths = local_scope.get('use_relative_paths', False)
722 rel_prefix = None
723 if use_relative_paths:
724 rel_prefix = self.name
725 elif self._relative:
726 rel_prefix = os.path.dirname(self.name)
727
728 deps = local_scope.get('deps', {})
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200729 if 'recursion' in local_scope:
730 self.recursion_override = local_scope.get('recursion')
731 logging.warning(
732 'Setting %s recursion to %d.', self.name, self.recursion_limit)
733 self.recursedeps = None
734 if 'recursedeps' in local_scope:
735 self.recursedeps = {}
736 for ent in local_scope['recursedeps']:
737 if isinstance(ent, basestring):
738 self.recursedeps[ent] = {"deps_file": self.deps_file}
739 else: # (depname, depsfilename)
740 self.recursedeps[ent[0]] = {"deps_file": ent[1]}
741 logging.warning('Found recursedeps %r.', repr(self.recursedeps))
742
743 if rel_prefix:
744 logging.warning('Updating recursedeps by prepending %s.', rel_prefix)
745 rel_deps = {}
746 for depname, options in self.recursedeps.iteritems():
747 rel_deps[
748 os.path.normpath(os.path.join(rel_prefix, depname))] = options
749 self.recursedeps = rel_deps
750
751 # If present, save 'target_os' in the local_target_os property.
752 if 'target_os' in local_scope:
753 self.local_target_os = local_scope['target_os']
754 # load os specific dependencies if defined. these dependencies may
755 # override or extend the values defined by the 'deps' member.
756 target_os_list = self.target_os
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +0200757 if 'deps_os' in local_scope:
758 for dep_os, os_deps in local_scope['deps_os'].iteritems():
759 self._os_dependencies[dep_os] = self._deps_to_objects(
760 self._postprocess_deps(os_deps, rel_prefix), use_relative_paths)
Paweł Hajdan, Jr357415c2017-07-24 14:35:28 +0200761 if target_os_list and not self._get_option(
762 'do_not_merge_os_specific_entries', False):
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +0200763 deps = self.MergeWithOsDeps(
764 deps, local_scope['deps_os'], target_os_list)
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200765
766 deps_to_add = self._deps_to_objects(
767 self._postprocess_deps(deps, rel_prefix), use_relative_paths)
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000768
769 # override named sets of hooks by the custom hooks
770 hooks_to_run = []
771 hook_names_to_suppress = [c.get('name', '') for c in self.custom_hooks]
772 for hook in local_scope.get('hooks', []):
773 if hook.get('name', '') not in hook_names_to_suppress:
774 hooks_to_run.append(hook)
Scott Grahamc4826742017-05-11 16:59:23 -0700775 if 'hooks_os' in local_scope and target_os_list:
776 hooks_os = local_scope['hooks_os']
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +0200777
778 # Keep original contents of hooks_os for flatten.
779 for hook_os, os_hooks in hooks_os.iteritems():
780 self._os_deps_hooks[hook_os] = [
Paweł Hajdan, Jrd3790252017-07-03 21:06:24 +0200781 Hook.from_dict(hook, variables=self.get_vars())
782 for hook in os_hooks]
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +0200783
Scott Grahamc4826742017-05-11 16:59:23 -0700784 # Specifically append these to ensure that hooks_os run after hooks.
Paweł Hajdan, Jr357415c2017-07-24 14:35:28 +0200785 if not self._get_option('do_not_merge_os_specific_entries', False):
786 for the_target_os in target_os_list:
787 the_target_os_hooks = hooks_os.get(the_target_os, [])
788 hooks_to_run.extend(the_target_os_hooks)
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000789
790 # add the replacements and any additions
791 for hook in self.custom_hooks:
792 if 'action' in hook:
793 hooks_to_run.append(hook)
794
Dirk Prankeda3a29e2017-02-27 15:29:36 -0800795 if self.recursion_limit:
Paweł Hajdan, Jr032d5452017-06-22 20:43:53 +0200796 self._pre_deps_hooks = [
Paweł Hajdan, Jrd3790252017-07-03 21:06:24 +0200797 Hook.from_dict(hook, variables=self.get_vars()) for hook in
Paweł Hajdan, Jr032d5452017-06-22 20:43:53 +0200798 local_scope.get('pre_deps_hooks', [])]
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000799
Paweł Hajdan, Jr357415c2017-07-24 14:35:28 +0200800 self.add_dependencies_and_close(deps_to_add, hooks_to_run)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000801 logging.info('ParseDepsFile(%s) done' % self.name)
802
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +0200803 def _get_option(self, attr, default):
804 obj = self
805 while not hasattr(obj, '_options'):
806 obj = obj.parent
807 return getattr(obj._options, attr, default)
808
Paweł Hajdan, Jr357415c2017-07-24 14:35:28 +0200809 def add_dependencies_and_close(self, deps_to_add, hooks):
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000810 """Adds the dependencies, hooks and mark the parsing as done."""
maruel@chromium.orgb9be0652011-10-14 18:05:40 +0000811 for dep in deps_to_add:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000812 if dep.verify_validity():
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000813 self.add_dependency(dep)
Paweł Hajdan, Jr032d5452017-06-22 20:43:53 +0200814 self._mark_as_parsed(
Paweł Hajdan, Jr357415c2017-07-24 14:35:28 +0200815 [Hook.from_dict(h, variables=self.get_vars()) for h in hooks])
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000816
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000817 def findDepsFromNotAllowedHosts(self):
818 """Returns a list of depenecies from not allowed hosts.
819
820 If allowed_hosts is not set, allows all hosts and returns empty list.
821 """
822 if not self._allowed_hosts:
823 return []
824 bad_deps = []
825 for dep in self._dependencies:
szager@chromium.orgbd772dd2014-11-05 18:43:08 +0000826 # Don't enforce this for custom_deps.
827 if dep.name in self._custom_deps:
828 continue
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000829 if isinstance(dep.url, basestring):
830 parsed_url = urlparse.urlparse(dep.url)
831 if parsed_url.netloc and parsed_url.netloc not in self._allowed_hosts:
832 bad_deps.append(dep)
833 return bad_deps
834
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +0000835 # Arguments number differs from overridden method
Quinten Yearsleyb2cc4a92016-12-15 13:53:26 -0800836 # pylint: disable=arguments-differ
maruel@chromium.org3742c842010-09-09 19:27:14 +0000837 def run(self, revision_overrides, command, args, work_queue, options):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000838 """Runs |command| then parse the DEPS file."""
maruel@chromium.org470b5432011-10-11 18:18:19 +0000839 logging.info('Dependency(%s).run()' % self.name)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000840 assert self._file_list == []
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000841 if not self.should_process:
842 return
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000843 # When running runhooks, there's no need to consult the SCM.
844 # All known hooks are expected to run unconditionally regardless of working
845 # copy state, so skip the SCM status check.
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +0200846 run_scm = command not in (
847 'flatten', 'runhooks', 'recurse', 'validate', None)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000848 parsed_url = self.LateOverride(self.url)
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000849 file_list = [] if not options.nohooks else None
szager@chromium.org3a3608d2014-10-22 21:13:52 +0000850 revision_override = revision_overrides.pop(self.name, None)
Dave Tubbda9712017-06-01 15:10:53 -0700851 if not revision_override and parsed_url:
852 revision_override = revision_overrides.get(parsed_url.split('@')[0], None)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000853 if run_scm and parsed_url:
agabled437d762016-10-17 09:35:11 -0700854 # Create a shallow copy to mutate revision.
855 options = copy.copy(options)
856 options.revision = revision_override
857 self._used_revision = options.revision
858 self._used_scm = gclient_scm.CreateSCM(
859 parsed_url, self.root.root_dir, self.name, self.outbuf,
860 out_cb=work_queue.out_cb)
861 self._got_revision = self._used_scm.RunCommand(command, options, args,
862 file_list)
863 if file_list:
864 file_list = [os.path.join(self.name, f.strip()) for f in file_list]
maruel@chromium.org68988972011-09-20 14:11:42 +0000865
866 # TODO(phajdan.jr): We should know exactly when the paths are absolute.
867 # Convert all absolute paths to relative.
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000868 for i in range(len(file_list or [])):
maruel@chromium.org68988972011-09-20 14:11:42 +0000869 # It depends on the command being executed (like runhooks vs sync).
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000870 if not os.path.isabs(file_list[i]):
maruel@chromium.org68988972011-09-20 14:11:42 +0000871 continue
872 prefix = os.path.commonprefix(
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000873 [self.root.root_dir.lower(), file_list[i].lower()])
874 file_list[i] = file_list[i][len(prefix):]
maruel@chromium.org68988972011-09-20 14:11:42 +0000875 # Strip any leading path separators.
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000876 while file_list[i].startswith(('\\', '/')):
877 file_list[i] = file_list[i][1:]
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000878
879 # Always parse the DEPS file.
880 self.ParseDepsFile()
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +0200881 if self._gn_args_file and command == 'update':
882 self.WriteGNArgsFile()
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000883 self._run_is_done(file_list or [], parsed_url)
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000884 if command in ('update', 'revert') and not options.noprehooks:
885 self.RunPreDepsHooks()
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000886
887 if self.recursion_limit:
888 # Parse the dependencies of this dependency.
889 for s in self.dependencies:
Paweł Hajdan, Jr4baaa112017-07-04 19:09:32 +0200890 if s.should_process:
891 work_queue.enqueue(s)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000892
893 if command == 'recurse':
agabled437d762016-10-17 09:35:11 -0700894 # Skip file only checkout.
895 scm = gclient_scm.GetScmName(parsed_url)
896 if not options.scm or scm in options.scm:
897 cwd = os.path.normpath(os.path.join(self.root.root_dir, self.name))
898 # Pass in the SCM type as an env variable. Make sure we don't put
899 # unicode strings in the environment.
900 env = os.environ.copy()
901 if scm:
902 env['GCLIENT_SCM'] = str(scm)
903 if parsed_url:
904 env['GCLIENT_URL'] = str(parsed_url)
905 env['GCLIENT_DEP_PATH'] = str(self.name)
906 if options.prepend_dir and scm == 'git':
907 print_stdout = False
908 def filter_fn(line):
909 """Git-specific path marshaling. It is optimized for git-grep."""
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000910
agabled437d762016-10-17 09:35:11 -0700911 def mod_path(git_pathspec):
912 match = re.match('^(\\S+?:)?([^\0]+)$', git_pathspec)
913 modified_path = os.path.join(self.name, match.group(2))
914 branch = match.group(1) or ''
915 return '%s%s' % (branch, modified_path)
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000916
agabled437d762016-10-17 09:35:11 -0700917 match = re.match('^Binary file ([^\0]+) matches$', line)
918 if match:
919 print('Binary file %s matches\n' % mod_path(match.group(1)))
920 return
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000921
agabled437d762016-10-17 09:35:11 -0700922 items = line.split('\0')
923 if len(items) == 2 and items[1]:
924 print('%s : %s' % (mod_path(items[0]), items[1]))
925 elif len(items) >= 2:
926 # Multiple null bytes or a single trailing null byte indicate
927 # git is likely displaying filenames only (such as with -l)
928 print('\n'.join(mod_path(path) for path in items if path))
929 else:
930 print(line)
931 else:
932 print_stdout = True
933 filter_fn = None
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000934
agabled437d762016-10-17 09:35:11 -0700935 if parsed_url is None:
936 print('Skipped omitted dependency %s' % cwd, file=sys.stderr)
937 elif os.path.isdir(cwd):
938 try:
939 gclient_utils.CheckCallAndFilter(
940 args, cwd=cwd, env=env, print_stdout=print_stdout,
941 filter_fn=filter_fn,
942 )
943 except subprocess2.CalledProcessError:
944 if not options.ignore:
945 raise
946 else:
947 print('Skipped missing %s' % cwd, file=sys.stderr)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000948
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +0200949 def WriteGNArgsFile(self):
950 lines = ['# Generated from %r' % self.deps_file]
951 for arg in self._gn_args:
Paweł Hajdan, Jrd3790252017-07-03 21:06:24 +0200952 lines.append('%s = %s' % (arg, ToGNString(self.get_vars()[arg])))
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +0200953 with open(os.path.join(self.root.root_dir, self._gn_args_file), 'w') as f:
954 f.write('\n'.join(lines))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000955
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000956 @gclient_utils.lockedmethod
957 def _run_is_done(self, file_list, parsed_url):
958 # Both these are kept for hooks that are run as a separate tree traversal.
959 self._file_list = file_list
960 self._parsed_url = parsed_url
961 self._processed = True
962
szager@google.comb9a78d32012-03-13 18:46:21 +0000963 def GetHooks(self, options):
964 """Evaluates all hooks, and return them in a flat list.
965
966 RunOnDeps() must have been called before to load the DEPS.
967 """
968 result = []
maruel@chromium.org68988972011-09-20 14:11:42 +0000969 if not self.should_process or not self.recursion_limit:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000970 # Don't run the hook when it is above recursion_limit.
szager@google.comb9a78d32012-03-13 18:46:21 +0000971 return result
maruel@chromium.orgdc7445d2010-07-09 21:05:29 +0000972 # If "--force" was specified, run all hooks regardless of what files have
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000973 # changed.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000974 if self.deps_hooks:
agabled437d762016-10-17 09:35:11 -0700975 # TODO(maruel): If the user is using git, then we don't know
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000976 # what files have changed so we always run all hooks. It'd be nice to fix
977 # that.
978 if (options.force or
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000979 gclient_scm.GetScmName(self.parsed_url) in ('git', None) or
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000980 os.path.isdir(os.path.join(self.root.root_dir, self.name, '.git'))):
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200981 result.extend(self.deps_hooks)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000982 else:
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200983 for hook in self.deps_hooks:
984 if hook.matches(self.file_list_and_children):
985 result.append(hook)
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000986 for s in self.dependencies:
szager@google.comb9a78d32012-03-13 18:46:21 +0000987 result.extend(s.GetHooks(options))
988 return result
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000989
szager@google.comb9a78d32012-03-13 18:46:21 +0000990 def RunHooksRecursively(self, options):
991 assert self.hooks_ran == False
maruel@chromium.org064186c2011-09-27 23:53:33 +0000992 self._hooks_ran = True
szager@google.comb9a78d32012-03-13 18:46:21 +0000993 for hook in self.GetHooks(options):
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200994 hook.run(self.root.root_dir)
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000995
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000996 def RunPreDepsHooks(self):
997 assert self.processed
998 assert self.deps_parsed
999 assert not self.pre_deps_hooks_ran
1000 assert not self.hooks_ran
1001 for s in self.dependencies:
1002 assert not s.processed
1003 self._pre_deps_hooks_ran = True
1004 for hook in self.pre_deps_hooks:
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +02001005 hook.run(self.root.root_dir)
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001006
maruel@chromium.org0d812442010-08-10 12:41:08 +00001007 def subtree(self, include_all):
maruel@chromium.orgad3287e2011-10-03 19:15:10 +00001008 """Breadth first recursion excluding root node."""
maruel@chromium.orgf13a4182011-09-22 00:26:15 +00001009 dependencies = self.dependencies
1010 for d in dependencies:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001011 if d.should_process or include_all:
maruel@chromium.orgad3287e2011-10-03 19:15:10 +00001012 yield d
maruel@chromium.orgf13a4182011-09-22 00:26:15 +00001013 for d in dependencies:
maruel@chromium.orgad3287e2011-10-03 19:15:10 +00001014 for i in d.subtree(include_all):
1015 yield i
1016
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001017 @gclient_utils.lockedmethod
1018 def add_dependency(self, new_dep):
1019 self._dependencies.append(new_dep)
1020
1021 @gclient_utils.lockedmethod
Paweł Hajdan, Jr357415c2017-07-24 14:35:28 +02001022 def _mark_as_parsed(self, new_hooks):
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001023 self._deps_hooks.extend(new_hooks)
1024 self._deps_parsed = True
1025
maruel@chromium.org68988972011-09-20 14:11:42 +00001026 @property
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001027 @gclient_utils.lockedmethod
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +00001028 def dependencies(self):
1029 return tuple(self._dependencies)
1030
1031 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001032 @gclient_utils.lockedmethod
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02001033 def os_dependencies(self):
1034 return dict(self._os_dependencies)
1035
1036 @property
1037 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001038 def deps_hooks(self):
1039 return tuple(self._deps_hooks)
1040
1041 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001042 @gclient_utils.lockedmethod
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +02001043 def os_deps_hooks(self):
1044 return dict(self._os_deps_hooks)
1045
1046 @property
1047 @gclient_utils.lockedmethod
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001048 def pre_deps_hooks(self):
1049 return tuple(self._pre_deps_hooks)
1050
1051 @property
1052 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001053 def parsed_url(self):
1054 return self._parsed_url
1055
1056 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001057 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001058 def deps_parsed(self):
maruel@chromium.org3223edd2011-10-10 23:17:39 +00001059 """This is purely for debugging purposes. It's not used anywhere."""
maruel@chromium.org064186c2011-09-27 23:53:33 +00001060 return self._deps_parsed
1061
1062 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001063 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001064 def processed(self):
1065 return self._processed
1066
1067 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001068 @gclient_utils.lockedmethod
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001069 def pre_deps_hooks_ran(self):
1070 return self._pre_deps_hooks_ran
1071
1072 @property
1073 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001074 def hooks_ran(self):
1075 return self._hooks_ran
1076
1077 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001078 @gclient_utils.lockedmethod
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00001079 def allowed_hosts(self):
1080 return self._allowed_hosts
1081
1082 @property
1083 @gclient_utils.lockedmethod
maruel@chromium.org861fd0f2010-07-23 03:05:05 +00001084 def file_list(self):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001085 return tuple(self._file_list)
1086
1087 @property
kustermann@google.coma692e8f2013-04-18 08:32:04 +00001088 def used_scm(self):
1089 """SCMWrapper instance for this dependency or None if not processed yet."""
1090 return self._used_scm
1091
1092 @property
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00001093 @gclient_utils.lockedmethod
1094 def got_revision(self):
1095 return self._got_revision
1096
1097 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001098 def file_list_and_children(self):
1099 result = list(self.file_list)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +00001100 for d in self.dependencies:
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001101 result.extend(d.file_list_and_children)
maruel@chromium.org68988972011-09-20 14:11:42 +00001102 return tuple(result)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +00001103
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001104 def __str__(self):
1105 out = []
agablea98a6cd2016-11-15 14:30:10 -08001106 for i in ('name', 'url', 'parsed_url', 'custom_deps',
maruel@chromium.org3c74bc92011-09-15 19:17:21 +00001107 'custom_vars', 'deps_hooks', 'file_list', 'should_process',
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00001108 'processed', 'hooks_ran', 'deps_parsed', 'requirements',
1109 'allowed_hosts'):
maruel@chromium.org3c74bc92011-09-15 19:17:21 +00001110 # First try the native property if it exists.
1111 if hasattr(self, '_' + i):
1112 value = getattr(self, '_' + i, False)
1113 else:
1114 value = getattr(self, i, False)
1115 if value:
1116 out.append('%s: %s' % (i, value))
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001117
1118 for d in self.dependencies:
1119 out.extend([' ' + x for x in str(d).splitlines()])
1120 out.append('')
1121 return '\n'.join(out)
1122
1123 def __repr__(self):
1124 return '%s: %s' % (self.name, self.url)
1125
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001126 def hierarchy(self, include_url=True):
maruel@chromium.orgbc2d2f92010-07-22 21:26:48 +00001127 """Returns a human-readable hierarchical reference to a Dependency."""
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001128 def format_name(d):
1129 if include_url:
1130 return '%s(%s)' % (d.name, d.url)
1131 return d.name
1132 out = format_name(self)
maruel@chromium.orgbffb9042010-07-22 20:59:36 +00001133 i = self.parent
1134 while i and i.name:
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001135 out = '%s -> %s' % (format_name(i), out)
maruel@chromium.orgbffb9042010-07-22 20:59:36 +00001136 i = i.parent
1137 return out
1138
Paweł Hajdan, Jrd3790252017-07-03 21:06:24 +02001139 def get_vars(self):
1140 """Returns a dictionary of effective variable values
1141 (DEPS file contents with applied custom_vars overrides)."""
1142 result = dict(self._vars)
1143 result.update(self.custom_vars or {})
1144 return result
1145
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001146
1147class GClient(Dependency):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001148 """Object that represent a gclient checkout. A tree of Dependency(), one per
1149 solution or DEPS entry."""
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001150
1151 DEPS_OS_CHOICES = {
1152 "win32": "win",
1153 "win": "win",
1154 "cygwin": "win",
1155 "darwin": "mac",
1156 "mac": "mac",
1157 "unix": "unix",
1158 "linux": "unix",
1159 "linux2": "unix",
maruel@chromium.org244e3442011-06-12 15:20:55 +00001160 "linux3": "unix",
szager@chromium.orgf8c95cd2012-06-01 22:26:52 +00001161 "android": "android",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001162 }
1163
1164 DEFAULT_CLIENT_FILE_TEXT = ("""\
1165solutions = [
smutae7ea312016-07-18 11:59:41 -07001166 { "name" : "%(solution_name)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001167 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +00001168 "deps_file" : "%(deps_file)s",
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001169 "managed" : %(managed)s,
smutae7ea312016-07-18 11:59:41 -07001170 "custom_deps" : {
1171 },
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001172 },
1173]
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001174cache_dir = %(cache_dir)r
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001175""")
1176
1177 DEFAULT_SNAPSHOT_SOLUTION_TEXT = ("""\
smutae7ea312016-07-18 11:59:41 -07001178 { "name" : "%(solution_name)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001179 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +00001180 "deps_file" : "%(deps_file)s",
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001181 "managed" : %(managed)s,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001182 "custom_deps" : {
smutae7ea312016-07-18 11:59:41 -07001183%(solution_deps)s },
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001184 },
1185""")
1186
1187 DEFAULT_SNAPSHOT_FILE_TEXT = ("""\
1188# Snapshot generated with gclient revinfo --snapshot
1189solutions = [
maruel@chromium.org73e21142010-07-05 13:32:01 +00001190%(solution_list)s]
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001191""")
1192
1193 def __init__(self, root_dir, options):
maruel@chromium.org0d812442010-08-10 12:41:08 +00001194 # Do not change previous behavior. Only solution level and immediate DEPS
1195 # are processed.
1196 self._recursion_limit = 2
Paweł Hajdan, Jre79ddea2017-07-24 22:23:49 +02001197 Dependency.__init__(self, None, None, None, None, True, None, None, None,
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +02001198 'unused', True, None, None, True)
maruel@chromium.org0d425922010-06-21 19:22:24 +00001199 self._options = options
maruel@chromium.org271375b2010-06-23 19:17:38 +00001200 if options.deps_os:
1201 enforced_os = options.deps_os.split(',')
1202 else:
1203 enforced_os = [self.DEPS_OS_CHOICES.get(sys.platform, 'unix')]
1204 if 'all' in enforced_os:
1205 enforced_os = self.DEPS_OS_CHOICES.itervalues()
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001206 self._enforced_os = tuple(set(enforced_os))
maruel@chromium.org271375b2010-06-23 19:17:38 +00001207 self._root_dir = root_dir
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001208 self.config_content = None
1209
borenet@google.com88d10082014-03-21 17:24:48 +00001210 def _CheckConfig(self):
1211 """Verify that the config matches the state of the existing checked-out
1212 solutions."""
1213 for dep in self.dependencies:
1214 if dep.managed and dep.url:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001215 scm = gclient_scm.CreateSCM(
1216 dep.url, self.root_dir, dep.name, self.outbuf)
smut@google.comd33eab32014-07-07 19:35:18 +00001217 actual_url = scm.GetActualRemoteURL(self._options)
borenet@google.com4e9be262014-04-08 19:40:30 +00001218 if actual_url and not scm.DoesRemoteURLMatch(self._options):
levarum@chromium.org27a6f9a2016-05-28 00:21:49 +00001219 mirror = scm.GetCacheMirror()
1220 if mirror:
1221 mirror_string = '%s (exists=%s)' % (mirror.mirror_path,
1222 mirror.exists())
1223 else:
1224 mirror_string = 'not used'
borenet@google.com0a427372014-04-02 19:12:13 +00001225 raise gclient_utils.Error('''
borenet@google.com88d10082014-03-21 17:24:48 +00001226Your .gclient file seems to be broken. The requested URL is different from what
borenet@google.com0a427372014-04-02 19:12:13 +00001227is actually checked out in %(checkout_path)s.
borenet@google.com88d10082014-03-21 17:24:48 +00001228
borenet@google.com97882362014-04-07 20:06:02 +00001229The .gclient file contains:
levarum@chromium.org27a6f9a2016-05-28 00:21:49 +00001230URL: %(expected_url)s (%(expected_scm)s)
1231Cache mirror: %(mirror_string)s
borenet@google.com97882362014-04-07 20:06:02 +00001232
1233The local checkout in %(checkout_path)s reports:
1234%(actual_url)s (%(actual_scm)s)
borenet@google.com88d10082014-03-21 17:24:48 +00001235
1236You should ensure that the URL listed in .gclient is correct and either change
agabled437d762016-10-17 09:35:11 -07001237it or fix the checkout.
borenet@google.com88d10082014-03-21 17:24:48 +00001238''' % {'checkout_path': os.path.join(self.root_dir, dep.name),
1239 'expected_url': dep.url,
1240 'expected_scm': gclient_scm.GetScmName(dep.url),
levarum@chromium.org27a6f9a2016-05-28 00:21:49 +00001241 'mirror_string' : mirror_string,
borenet@google.com88d10082014-03-21 17:24:48 +00001242 'actual_url': actual_url,
1243 'actual_scm': gclient_scm.GetScmName(actual_url)})
1244
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001245 def SetConfig(self, content):
maruel@chromium.orgf13a4182011-09-22 00:26:15 +00001246 assert not self.dependencies
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001247 config_dict = {}
1248 self.config_content = content
1249 try:
1250 exec(content, config_dict)
vapier@chromium.orga81a56e2015-11-11 07:56:13 +00001251 except SyntaxError as e:
maruel@chromium.org5990f9d2010-07-07 18:02:58 +00001252 gclient_utils.SyntaxErrorToError('.gclient', e)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001253
peter@chromium.org1efccc82012-04-27 16:34:38 +00001254 # Append any target OS that is not already being enforced to the tuple.
1255 target_os = config_dict.get('target_os', [])
stuartmorgan@chromium.org18a4f6a2012-11-02 13:37:24 +00001256 if config_dict.get('target_os_only', False):
1257 self._enforced_os = tuple(set(target_os))
1258 else:
1259 self._enforced_os = tuple(set(self._enforced_os).union(target_os))
1260
dyen@chromium.orgd915cca2014-08-07 21:41:37 +00001261 cache_dir = config_dict.get('cache_dir')
1262 if cache_dir:
1263 cache_dir = os.path.join(self.root_dir, cache_dir)
1264 cache_dir = os.path.abspath(cache_dir)
szager@chromium.orgcaf5bef2014-08-24 18:56:32 +00001265 # If running on a bot, force break any stale git cache locks.
dnj@chromium.orgb682b3e2014-08-25 19:17:12 +00001266 if os.path.exists(cache_dir) and os.environ.get('CHROME_HEADLESS'):
szager@chromium.org4848fb62014-08-24 19:16:31 +00001267 subprocess2.check_call(['git', 'cache', 'unlock', '--cache-dir',
1268 cache_dir, '--force', '--all'])
dyen@chromium.orgd915cca2014-08-07 21:41:37 +00001269 gclient_scm.GitWrapper.cache_dir = cache_dir
1270 git_cache.Mirror.SetCachePath(cache_dir)
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001271
stuartmorgan@chromium.org18a4f6a2012-11-02 13:37:24 +00001272 if not target_os and config_dict.get('target_os_only', False):
1273 raise gclient_utils.Error('Can\'t use target_os_only if target_os is '
1274 'not specified')
peter@chromium.org1efccc82012-04-27 16:34:38 +00001275
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001276 deps_to_add = []
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001277 for s in config_dict.get('solutions', []):
maruel@chromium.org81843b82010-06-28 16:49:26 +00001278 try:
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001279 deps_to_add.append(Dependency(
Paweł Hajdan, Jre79ddea2017-07-24 22:23:49 +02001280 self, s['name'], s['url'], s['url'],
smutae7ea312016-07-18 11:59:41 -07001281 s.get('managed', True),
maruel@chromium.org81843b82010-06-28 16:49:26 +00001282 s.get('custom_deps', {}),
maruel@chromium.org0d812442010-08-10 12:41:08 +00001283 s.get('custom_vars', {}),
petermayo@chromium.orge79161a2013-07-09 14:40:37 +00001284 s.get('custom_hooks', []),
nsylvain@google.comefc80932011-05-31 21:27:56 +00001285 s.get('deps_file', 'DEPS'),
agabledce6ddc2016-09-08 10:02:16 -07001286 True,
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +02001287 None,
1288 None,
1289 True))
maruel@chromium.org81843b82010-06-28 16:49:26 +00001290 except KeyError:
1291 raise gclient_utils.Error('Invalid .gclient file. Solution is '
1292 'incomplete: %s' % s)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001293 self.add_dependencies_and_close(deps_to_add, config_dict.get('hooks', []))
1294 logging.info('SetConfig() done')
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001295
1296 def SaveConfig(self):
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001297 gclient_utils.FileWrite(os.path.join(self.root_dir,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001298 self._options.config_filename),
1299 self.config_content)
1300
1301 @staticmethod
1302 def LoadCurrentConfig(options):
1303 """Searches for and loads a .gclient file relative to the current working
1304 dir. Returns a GClient object."""
szager@chromium.orge2e03202012-07-31 18:05:16 +00001305 if options.spec:
1306 client = GClient('.', options)
1307 client.SetConfig(options.spec)
1308 else:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00001309 if options.verbose:
1310 print('Looking for %s starting from %s\n' % (
1311 options.config_filename, os.getcwd()))
szager@chromium.orge2e03202012-07-31 18:05:16 +00001312 path = gclient_utils.FindGclientRoot(os.getcwd(), options.config_filename)
1313 if not path:
1314 return None
1315 client = GClient(path, options)
1316 client.SetConfig(gclient_utils.FileRead(
1317 os.path.join(path, options.config_filename)))
maruel@chromium.org69392e72011-10-13 22:09:00 +00001318
1319 if (options.revisions and
1320 len(client.dependencies) > 1 and
1321 any('@' not in r for r in options.revisions)):
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001322 print(
1323 ('You must specify the full solution name like --revision %s@%s\n'
1324 'when you have multiple solutions setup in your .gclient file.\n'
1325 'Other solutions present are: %s.') % (
maruel@chromium.org69392e72011-10-13 22:09:00 +00001326 client.dependencies[0].name,
1327 options.revisions[0],
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001328 ', '.join(s.name for s in client.dependencies[1:])),
1329 file=sys.stderr)
maruel@chromium.org15804092010-09-02 17:07:37 +00001330 return client
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001331
nsylvain@google.comefc80932011-05-31 21:27:56 +00001332 def SetDefaultConfig(self, solution_name, deps_file, solution_url,
agablea98a6cd2016-11-15 14:30:10 -08001333 managed=True, cache_dir=None):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001334 self.SetConfig(self.DEFAULT_CLIENT_FILE_TEXT % {
1335 'solution_name': solution_name,
1336 'solution_url': solution_url,
nsylvain@google.comefc80932011-05-31 21:27:56 +00001337 'deps_file': deps_file,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001338 'managed': managed,
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001339 'cache_dir': cache_dir,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001340 })
1341
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001342 def _SaveEntries(self):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001343 """Creates a .gclient_entries file to record the list of unique checkouts.
1344
1345 The .gclient_entries file lives in the same directory as .gclient.
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001346 """
1347 # Sometimes pprint.pformat will use {', sometimes it'll use { ' ... It
1348 # makes testing a bit too fun.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001349 result = 'entries = {\n'
maruel@chromium.org68988972011-09-20 14:11:42 +00001350 for entry in self.root.subtree(False):
agabled437d762016-10-17 09:35:11 -07001351 result += ' %s: %s,\n' % (pprint.pformat(entry.name),
1352 pprint.pformat(entry.parsed_url))
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001353 result += '}\n'
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001354 file_path = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org1333cb32011-10-04 23:40:16 +00001355 logging.debug(result)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001356 gclient_utils.FileWrite(file_path, result)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001357
1358 def _ReadEntries(self):
1359 """Read the .gclient_entries file for the given client.
1360
1361 Returns:
1362 A sequence of solution names, which will be empty if there is the
1363 entries file hasn't been created yet.
1364 """
1365 scope = {}
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001366 filename = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001367 if not os.path.exists(filename):
maruel@chromium.org73e21142010-07-05 13:32:01 +00001368 return {}
maruel@chromium.org5990f9d2010-07-07 18:02:58 +00001369 try:
1370 exec(gclient_utils.FileRead(filename), scope)
vapier@chromium.orga81a56e2015-11-11 07:56:13 +00001371 except SyntaxError as e:
maruel@chromium.org5990f9d2010-07-07 18:02:58 +00001372 gclient_utils.SyntaxErrorToError(filename, e)
Aaron Gable3721ee92017-04-03 14:53:14 -07001373 return scope.get('entries', {})
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001374
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001375 def _EnforceRevisions(self):
maruel@chromium.org918a9ae2010-05-28 15:50:30 +00001376 """Checks for revision overrides."""
1377 revision_overrides = {}
smutae7ea312016-07-18 11:59:41 -07001378 if self._options.head:
1379 return revision_overrides
joi@chromium.org792ea882010-11-10 02:37:27 +00001380 if not self._options.revisions:
1381 for s in self.dependencies:
smutae7ea312016-07-18 11:59:41 -07001382 if not s.managed:
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001383 self._options.revisions.append('%s@unmanaged' % s.name)
maruel@chromium.org307d1792010-05-31 20:03:13 +00001384 if not self._options.revisions:
1385 return revision_overrides
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001386 solutions_names = [s.name for s in self.dependencies]
smutae7ea312016-07-18 11:59:41 -07001387 index = 0
1388 for revision in self._options.revisions:
1389 if not '@' in revision:
maruel@chromium.org307d1792010-05-31 20:03:13 +00001390 # Support for --revision 123
smutae7ea312016-07-18 11:59:41 -07001391 revision = '%s@%s' % (solutions_names[index], revision)
1392 name, rev = revision.split('@', 1)
szager@chromium.org4ad264b2014-05-20 04:43:47 +00001393 revision_overrides[name] = rev
smutae7ea312016-07-18 11:59:41 -07001394 index += 1
maruel@chromium.org918a9ae2010-05-28 15:50:30 +00001395 return revision_overrides
1396
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001397 def RunOnDeps(self, command, args, ignore_requirements=False, progress=True):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001398 """Runs a command on each dependency in a client and its dependencies.
1399
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001400 Args:
1401 command: The command to use (e.g., 'status' or 'diff')
1402 args: list of str - extra arguments to add to the command line.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001403 """
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001404 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +00001405 raise gclient_utils.Error('No solution specified')
borenet@google.com0a427372014-04-02 19:12:13 +00001406
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001407 revision_overrides = {}
1408 # It's unnecessary to check for revision overrides for 'recurse'.
1409 # Save a few seconds by not calling _EnforceRevisions() in that case.
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +02001410 if command not in ('diff', 'recurse', 'runhooks', 'status', 'revert',
1411 'validate'):
szager@chromium.org5273b8a2014-08-21 15:10:10 +00001412 self._CheckConfig()
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001413 revision_overrides = self._EnforceRevisions()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001414 pm = None
maruel@chromium.org5b3f8852010-09-10 16:49:54 +00001415 # Disable progress for non-tty stdout.
iannucci@chromium.org596cd5c2016-04-04 21:34:39 +00001416 if (setup_color.IS_TTY and not self._options.verbose and progress):
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001417 if command in ('update', 'revert'):
1418 pm = Progress('Syncing projects', 1)
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +02001419 elif command in ('recurse', 'validate'):
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001420 pm = Progress(' '.join(args), 1)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001421 work_queue = gclient_utils.ExecutionQueue(
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001422 self._options.jobs, pm, ignore_requirements=ignore_requirements,
1423 verbose=self._options.verbose)
maruel@chromium.org049bced2010-08-12 13:37:20 +00001424 for s in self.dependencies:
Paweł Hajdan, Jr4baaa112017-07-04 19:09:32 +02001425 if s.should_process:
1426 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +00001427 work_queue.flush(revision_overrides, command, args, options=self._options)
szager@chromium.org4ad264b2014-05-20 04:43:47 +00001428 if revision_overrides:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001429 print('Please fix your script, having invalid --revision flags will soon '
1430 'considered an error.', file=sys.stderr)
piman@chromium.org6f363722010-04-27 00:41:09 +00001431
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001432 # Once all the dependencies have been processed, it's now safe to run the
1433 # hooks.
1434 if not self._options.nohooks:
1435 self.RunHooksRecursively(self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001436
1437 if command == 'update':
ajwong@chromium.orgcdcee802009-06-23 15:30:42 +00001438 # Notify the user if there is an orphaned entry in their working copy.
1439 # Only delete the directory if there are no changes in it, and
1440 # delete_unversioned_trees is set to true.
maruel@chromium.org68988972011-09-20 14:11:42 +00001441 entries = [i.name for i in self.root.subtree(False) if i.url]
xusydoc@chromium.org885a9602013-05-31 09:54:40 +00001442 full_entries = [os.path.join(self.root_dir, e.replace('/', os.path.sep))
1443 for e in entries]
1444
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001445 for entry, prev_url in self._ReadEntries().iteritems():
maruel@chromium.org04dd7de2010-10-14 13:25:49 +00001446 if not prev_url:
1447 # entry must have been overridden via .gclient custom_deps
1448 continue
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001449 # Fix path separator on Windows.
1450 entry_fixed = entry.replace('/', os.path.sep)
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001451 e_dir = os.path.join(self.root_dir, entry_fixed)
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001452 # Use entry and not entry_fixed there.
jochen@chromium.orga78e5532013-03-11 13:33:03 +00001453 if (entry not in entries and
1454 (not any(path.startswith(entry + '/') for path in entries)) and
jochen@chromium.orgcc475722013-03-11 13:07:40 +00001455 os.path.exists(e_dir)):
primiano@chromium.org1c127382015-02-17 11:15:40 +00001456 # The entry has been removed from DEPS.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001457 scm = gclient_scm.CreateSCM(
1458 prev_url, self.root_dir, entry_fixed, self.outbuf)
xusydoc@chromium.org885a9602013-05-31 09:54:40 +00001459
1460 # Check to see if this directory is now part of a higher-up checkout.
borenet@google.com359bb642014-05-13 17:28:19 +00001461 scm_root = None
agabled437d762016-10-17 09:35:11 -07001462 try:
1463 scm_root = gclient_scm.scm.GIT.GetCheckoutRoot(scm.checkout_path)
1464 except subprocess2.CalledProcessError:
1465 pass
1466 if not scm_root:
borenet@google.com359bb642014-05-13 17:28:19 +00001467 logging.warning('Could not find checkout root for %s. Unable to '
1468 'determine whether it is part of a higher-level '
1469 'checkout, so not removing.' % entry)
1470 continue
primiano@chromium.org1c127382015-02-17 11:15:40 +00001471
1472 # This is to handle the case of third_party/WebKit migrating from
1473 # being a DEPS entry to being part of the main project.
1474 # If the subproject is a Git project, we need to remove its .git
1475 # folder. Otherwise git operations on that folder will have different
1476 # effects depending on the current working directory.
agabled437d762016-10-17 09:35:11 -07001477 if os.path.abspath(scm_root) == os.path.abspath(e_dir):
primiano@chromium.org1c127382015-02-17 11:15:40 +00001478 e_par_dir = os.path.join(e_dir, os.pardir)
agabled437d762016-10-17 09:35:11 -07001479 if gclient_scm.scm.GIT.IsInsideWorkTree(e_par_dir):
1480 par_scm_root = gclient_scm.scm.GIT.GetCheckoutRoot(e_par_dir)
primiano@chromium.org1c127382015-02-17 11:15:40 +00001481 # rel_e_dir : relative path of entry w.r.t. its parent repo.
1482 rel_e_dir = os.path.relpath(e_dir, par_scm_root)
agabled437d762016-10-17 09:35:11 -07001483 if gclient_scm.scm.GIT.IsDirectoryVersioned(
1484 par_scm_root, rel_e_dir):
primiano@chromium.org1c127382015-02-17 11:15:40 +00001485 save_dir = scm.GetGitBackupDirPath()
1486 # Remove any eventual stale backup dir for the same project.
1487 if os.path.exists(save_dir):
1488 gclient_utils.rmtree(save_dir)
1489 os.rename(os.path.join(e_dir, '.git'), save_dir)
1490 # When switching between the two states (entry/ is a subproject
1491 # -> entry/ is part of the outer project), it is very likely
1492 # that some files are changed in the checkout, unless we are
1493 # jumping *exactly* across the commit which changed just DEPS.
1494 # In such case we want to cleanup any eventual stale files
1495 # (coming from the old subproject) in order to end up with a
1496 # clean checkout.
agabled437d762016-10-17 09:35:11 -07001497 gclient_scm.scm.GIT.CleanupDir(par_scm_root, rel_e_dir)
primiano@chromium.org1c127382015-02-17 11:15:40 +00001498 assert not os.path.exists(os.path.join(e_dir, '.git'))
1499 print(('\nWARNING: \'%s\' has been moved from DEPS to a higher '
1500 'level checkout. The git folder containing all the local'
1501 ' branches has been saved to %s.\n'
1502 'If you don\'t care about its state you can safely '
1503 'remove that folder to free up space.') %
1504 (entry, save_dir))
1505 continue
1506
borenet@google.com359bb642014-05-13 17:28:19 +00001507 if scm_root in full_entries:
primiano@chromium.org1c127382015-02-17 11:15:40 +00001508 logging.info('%s is part of a higher level checkout, not removing',
1509 scm.GetCheckoutRoot())
xusydoc@chromium.org885a9602013-05-31 09:54:40 +00001510 continue
1511
1512 file_list = []
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001513 scm.status(self._options, [], file_list)
1514 modified_files = file_list != []
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001515 if (not self._options.delete_unversioned_trees or
1516 (modified_files and not self._options.force)):
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001517 # There are modified files in this entry. Keep warning until
1518 # removed.
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001519 print(('\nWARNING: \'%s\' is no longer part of this client. '
1520 'It is recommended that you manually remove it.\n') %
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001521 entry_fixed)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001522 else:
1523 # Delete the entry
maruel@chromium.org73e21142010-07-05 13:32:01 +00001524 print('\n________ deleting \'%s\' in \'%s\'' % (
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001525 entry_fixed, self.root_dir))
digit@chromium.orgdc112ac2013-04-24 13:00:19 +00001526 gclient_utils.rmtree(e_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001527 # record the current list of entries for next time
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001528 self._SaveEntries()
maruel@chromium.org17cdf762010-05-28 17:30:52 +00001529 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001530
1531 def PrintRevInfo(self):
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001532 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +00001533 raise gclient_utils.Error('No solution specified')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001534 # Load all the settings.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001535 work_queue = gclient_utils.ExecutionQueue(
1536 self._options.jobs, None, False, verbose=self._options.verbose)
maruel@chromium.org049bced2010-08-12 13:37:20 +00001537 for s in self.dependencies:
Paweł Hajdan, Jr4baaa112017-07-04 19:09:32 +02001538 if s.should_process:
1539 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +00001540 work_queue.flush({}, None, [], options=self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001541
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001542 def GetURLAndRev(dep):
1543 """Returns the revision-qualified SCM url for a Dependency."""
1544 if dep.parsed_url is None:
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001545 return None
agabled437d762016-10-17 09:35:11 -07001546 url, _ = gclient_utils.SplitUrlRevision(dep.parsed_url)
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001547 scm = gclient_scm.CreateSCM(
agabled437d762016-10-17 09:35:11 -07001548 dep.parsed_url, self.root_dir, dep.name, self.outbuf)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001549 if not os.path.isdir(scm.checkout_path):
1550 return None
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001551 return '%s@%s' % (url, scm.revinfo(self._options, [], None))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001552
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001553 if self._options.snapshot:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001554 new_gclient = ''
1555 # First level at .gclient
1556 for d in self.dependencies:
1557 entries = {}
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001558 def GrabDeps(dep):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001559 """Recursively grab dependencies."""
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001560 for d in dep.dependencies:
1561 entries[d.name] = GetURLAndRev(d)
1562 GrabDeps(d)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001563 GrabDeps(d)
1564 custom_deps = []
1565 for k in sorted(entries.keys()):
1566 if entries[k]:
1567 # Quotes aren't escaped...
1568 custom_deps.append(' \"%s\": \'%s\',\n' % (k, entries[k]))
1569 else:
1570 custom_deps.append(' \"%s\": None,\n' % k)
1571 new_gclient += self.DEFAULT_SNAPSHOT_SOLUTION_TEXT % {
1572 'solution_name': d.name,
1573 'solution_url': d.url,
nsylvain@google.comefc80932011-05-31 21:27:56 +00001574 'deps_file': d.deps_file,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001575 'managed': d.managed,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001576 'solution_deps': ''.join(custom_deps),
1577 }
1578 # Print the snapshot configuration file
1579 print(self.DEFAULT_SNAPSHOT_FILE_TEXT % {'solution_list': new_gclient})
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +00001580 else:
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001581 entries = {}
maruel@chromium.org68988972011-09-20 14:11:42 +00001582 for d in self.root.subtree(False):
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001583 if self._options.actual:
1584 entries[d.name] = GetURLAndRev(d)
1585 else:
1586 entries[d.name] = d.parsed_url
1587 keys = sorted(entries.keys())
1588 for x in keys:
maruel@chromium.orgce464892010-08-12 17:12:18 +00001589 print('%s: %s' % (x, entries[x]))
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +00001590 logging.info(str(self))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001591
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001592 def ParseDepsFile(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001593 """No DEPS to parse for a .gclient file."""
maruel@chromium.org049bced2010-08-12 13:37:20 +00001594 raise gclient_utils.Error('Internal error')
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001595
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00001596 def PrintLocationAndContents(self):
1597 # Print out the .gclient file. This is longer than if we just printed the
1598 # client dict, but more legible, and it might contain helpful comments.
1599 print('Loaded .gclient config in %s:\n%s' % (
1600 self.root_dir, self.config_content))
1601
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001602 @property
maruel@chromium.org75a59272010-06-11 22:34:03 +00001603 def root_dir(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001604 """Root directory of gclient checkout."""
maruel@chromium.org75a59272010-06-11 22:34:03 +00001605 return self._root_dir
1606
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001607 @property
maruel@chromium.org271375b2010-06-23 19:17:38 +00001608 def enforced_os(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001609 """What deps_os entries that are to be parsed."""
maruel@chromium.org271375b2010-06-23 19:17:38 +00001610 return self._enforced_os
1611
maruel@chromium.org68988972011-09-20 14:11:42 +00001612 @property
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001613 def recursion_limit(self):
1614 """How recursive can each dependencies in DEPS file can load DEPS file."""
1615 return self._recursion_limit
1616
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +00001617 @property
cmp@chromium.orgc401ad12014-07-02 23:20:08 +00001618 def try_recursedeps(self):
1619 """Whether to attempt using recursedeps-style recursion processing."""
cmp@chromium.orge84ac912014-06-30 23:14:35 +00001620 return True
1621
1622 @property
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +00001623 def target_os(self):
1624 return self._enforced_os
1625
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001626
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001627#### gclient commands.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001628
1629
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001630@subcommand.usage('[command] [args ...]')
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001631def CMDrecurse(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001632 """Operates [command args ...] on all the dependencies.
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001633
1634 Runs a shell command on all entries.
qyearsley12fa6ff2016-08-24 09:18:40 -07001635 Sets GCLIENT_DEP_PATH environment variable as the dep's relative location to
ilevy@chromium.org37116242012-11-28 01:32:48 +00001636 root directory of the checkout.
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001637 """
1638 # Stop parsing at the first non-arg so that these go through to the command
1639 parser.disable_interspersed_args()
1640 parser.add_option('-s', '--scm', action='append', default=[],
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001641 help='Choose scm types to operate upon.')
maruel@chromium.org288054d2012-03-05 00:43:07 +00001642 parser.add_option('-i', '--ignore', action='store_true',
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001643 help='Ignore non-zero return codes from subcommands.')
1644 parser.add_option('--prepend-dir', action='store_true',
1645 help='Prepend relative dir for use with git <cmd> --null.')
1646 parser.add_option('--no-progress', action='store_true',
1647 help='Disable progress bar that shows sub-command updates')
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001648 options, args = parser.parse_args(args)
maruel@chromium.org45e9f2d2010-10-18 13:33:46 +00001649 if not args:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001650 print('Need to supply a command!', file=sys.stderr)
maruel@chromium.org45e9f2d2010-10-18 13:33:46 +00001651 return 1
maruel@chromium.org78cba522010-10-18 13:32:05 +00001652 root_and_entries = gclient_utils.GetGClientRootAndEntries()
1653 if not root_and_entries:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001654 print(
maruel@chromium.org78cba522010-10-18 13:32:05 +00001655 'You need to run gclient sync at least once to use \'recurse\'.\n'
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001656 'This is because .gclient_entries needs to exist and be up to date.',
1657 file=sys.stderr)
maruel@chromium.org78cba522010-10-18 13:32:05 +00001658 return 1
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001659
1660 # Normalize options.scm to a set()
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001661 scm_set = set()
1662 for scm in options.scm:
1663 scm_set.update(scm.split(','))
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001664 options.scm = scm_set
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001665
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001666 options.nohooks = True
1667 client = GClient.LoadCurrentConfig(options)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001668 return client.RunOnDeps('recurse', args, ignore_requirements=True,
1669 progress=not options.no_progress)
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001670
1671
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001672@subcommand.usage('[args ...]')
davidbarr@chromium.org12f944e2012-03-01 02:18:31 +00001673def CMDfetch(parser, args):
1674 """Fetches upstream commits for all modules.
1675
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001676 Completely git-specific. Simply runs 'git fetch [args ...]' for each module.
1677 """
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001678 (options, args) = parser.parse_args(args)
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00001679 return CMDrecurse(OptionParser(), [
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001680 '--jobs=%d' % options.jobs, '--scm=git', 'git', 'fetch'] + args)
1681
1682
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02001683class Flattener(object):
1684 """Flattens a gclient solution."""
1685
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02001686 def __init__(self, client, pin_all_deps=False):
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02001687 """Constructor.
1688
1689 Arguments:
1690 client (GClient): client to flatten
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02001691 pin_all_deps (bool): whether to pin all deps, even if they're not pinned
1692 in DEPS
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02001693 """
1694 self._client = client
1695
1696 self._deps_string = None
1697
1698 self._allowed_hosts = set()
1699 self._deps = {}
1700 self._deps_os = {}
1701 self._hooks = []
1702 self._hooks_os = {}
1703 self._pre_deps_hooks = []
Paweł Hajdan, Jrfb022012017-07-06 18:00:08 +02001704 self._vars = {}
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02001705
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02001706 self._flatten(pin_all_deps=pin_all_deps)
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02001707
1708 @property
1709 def deps_string(self):
1710 assert self._deps_string is not None
1711 return self._deps_string
1712
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02001713 def _flatten(self, pin_all_deps=False):
1714 """Runs the flattener. Saves resulting DEPS string.
1715
1716 Arguments:
1717 pin_all_deps (bool): whether to pin all deps, even if they're not pinned
1718 in DEPS
1719 """
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02001720 for solution in self._client.dependencies:
1721 self._flatten_solution(solution)
1722
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02001723 if pin_all_deps:
1724 for dep in self._deps.itervalues():
1725 if dep.parsed_url is None:
1726 continue
1727 url, revision = gclient_utils.SplitUrlRevision(dep.parsed_url)
1728 if revision and gclient_utils.IsGitSha(revision):
1729 continue
1730 scm = gclient_scm.CreateSCM(
1731 dep.parsed_url, self._client.root_dir, dep.name, dep.outbuf)
Paweł Hajdan, Jre79ddea2017-07-24 22:23:49 +02001732 dep._parsed_url = dep._raw_url = dep._url = '%s@%s' % (
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02001733 url, scm.revinfo(self._client._options, [], None))
1734
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02001735 self._deps_string = '\n'.join(
1736 _GNSettingsToLines(
1737 self._client.dependencies[0]._gn_args_file,
1738 self._client.dependencies[0]._gn_args) +
1739 _AllowedHostsToLines(self._allowed_hosts) +
1740 _DepsToLines(self._deps) +
1741 _DepsOsToLines(self._deps_os) +
1742 _HooksToLines('hooks', self._hooks) +
1743 _HooksToLines('pre_deps_hooks', self._pre_deps_hooks) +
1744 _HooksOsToLines(self._hooks_os) +
Paweł Hajdan, Jrfb022012017-07-06 18:00:08 +02001745 _VarsToLines(self._vars) +
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02001746 ['']) # Ensure newline at end of file.
1747
1748 def _flatten_solution(self, solution):
1749 """Visits a solution in order to flatten it (see CMDflatten).
1750
1751 Arguments:
1752 solution (Dependency): one of top-level solutions in .gclient
1753 """
1754 self._flatten_dep(solution)
1755 self._flatten_recurse(solution)
1756
1757 def _flatten_dep(self, dep):
1758 """Visits a dependency in order to flatten it (see CMDflatten).
1759
1760 Arguments:
1761 dep (Dependency): dependency to process
1762 """
1763 self._allowed_hosts.update(dep.allowed_hosts)
1764
1765 assert dep.name not in self._deps
1766 self._deps[dep.name] = dep
1767
Paweł Hajdan, Jrfb022012017-07-06 18:00:08 +02001768 for key, value in dep.get_vars().iteritems():
1769 assert key not in self._vars
1770 self._vars[key] = (dep, value)
1771
Paweł Hajdan, Jr357415c2017-07-24 14:35:28 +02001772 self._hooks.extend([(dep, hook) for hook in dep.deps_hooks])
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02001773 self._pre_deps_hooks.extend([(dep, hook) for hook in dep.pre_deps_hooks])
1774
1775 for hook_os, os_hooks in dep.os_deps_hooks.iteritems():
1776 self._hooks_os.setdefault(hook_os, []).extend(
1777 [(dep, hook) for hook in os_hooks])
1778
1779 self._add_deps_os(dep)
1780
1781 deps_by_name = dict((d.name, d) for d in dep.dependencies)
1782 for recurse_dep_name in (dep.recursedeps or []):
1783 self._flatten_recurse(deps_by_name[recurse_dep_name])
1784
1785 def _flatten_recurse(self, dep):
1786 """Helper for flatten that recurses into |dep|'s dependencies.
1787
1788 Arguments:
1789 dep (Dependency): dependency to process
1790 """
1791 self._add_deps_os(dep)
1792
Paweł Hajdan, Jr357415c2017-07-24 14:35:28 +02001793 for sub_dep in dep.dependencies:
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02001794 self._flatten_dep(sub_dep)
1795
1796 def _add_deps_os(self, dep):
1797 """Helper for flatten that collects deps_os from |dep|.
1798
1799 Arguments:
1800 dep (Dependency): dependency to process
1801 """
1802 for dep_os, os_deps in dep.os_dependencies.iteritems():
1803 for os_dep in os_deps:
1804 self._deps_os.setdefault(dep_os, {})[os_dep.name] = os_dep
1805
1806
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001807def CMDflatten(parser, args):
1808 """Flattens the solutions into a single DEPS file."""
1809 parser.add_option('--output-deps', help='Path to the output DEPS file')
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02001810 parser.add_option(
1811 '--pin-all-deps', action='store_true',
1812 help=('Pin all deps, even if not pinned in DEPS. CAVEAT: only does so '
1813 'for checked out deps, NOT deps_os.'))
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001814 options, args = parser.parse_args(args)
1815
Paweł Hajdan, Jr357415c2017-07-24 14:35:28 +02001816 options.do_not_merge_os_specific_entries = True
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001817 options.nohooks = True
1818 client = GClient.LoadCurrentConfig(options)
1819
1820 # Only print progress if we're writing to a file. Otherwise, progress updates
1821 # could obscure intended output.
1822 code = client.RunOnDeps('flatten', args, progress=options.output_deps)
1823 if code != 0:
1824 return code
1825
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02001826 flattener = Flattener(client, pin_all_deps=options.pin_all_deps)
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001827
1828 if options.output_deps:
1829 with open(options.output_deps, 'w') as f:
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02001830 f.write(flattener.deps_string)
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001831 else:
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02001832 print(flattener.deps_string)
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001833
1834 return 0
1835
1836
Paweł Hajdan, Jr3c2aa832017-06-07 20:22:16 +02001837def _GNSettingsToLines(gn_args_file, gn_args):
1838 s = []
1839 if gn_args_file:
1840 s.extend([
1841 'gclient_gn_args_file = "%s"' % gn_args_file,
1842 'gclient_gn_args = %r' % gn_args,
1843 ])
1844 return s
1845
1846
Paweł Hajdan, Jr6014b562017-06-30 17:43:42 +02001847def _AllowedHostsToLines(allowed_hosts):
1848 """Converts |allowed_hosts| set to list of lines for output."""
1849 if not allowed_hosts:
1850 return []
1851 s = ['allowed_hosts = [']
1852 for h in sorted(allowed_hosts):
1853 s.append(' "%s",' % h)
1854 s.extend([']', ''])
1855 return s
1856
1857
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001858def _DepsToLines(deps):
1859 """Converts |deps| dict to list of lines for output."""
Paweł Hajdan, Jr5b593352017-06-29 18:37:45 +02001860 if not deps:
1861 return []
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001862 s = ['deps = {']
1863 for name, dep in sorted(deps.iteritems()):
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +02001864 condition_part = ([' "condition": "%s",' % dep.condition]
1865 if dep.condition else [])
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001866 s.extend([
1867 ' # %s' % dep.hierarchy(include_url=False),
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +02001868 ' "%s": {' % (name,),
Paweł Hajdan, Jre79ddea2017-07-24 22:23:49 +02001869 ' "url": "%s",' % (dep.raw_url,),
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +02001870 ] + condition_part + [
1871 ' },',
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001872 '',
1873 ])
1874 s.extend(['}', ''])
1875 return s
1876
1877
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02001878def _DepsOsToLines(deps_os):
1879 """Converts |deps_os| dict to list of lines for output."""
Paweł Hajdan, Jr5b593352017-06-29 18:37:45 +02001880 if not deps_os:
1881 return []
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02001882 s = ['deps_os = {']
1883 for dep_os, os_deps in sorted(deps_os.iteritems()):
1884 s.append(' "%s": {' % dep_os)
Paweł Hajdan, Jrad30de62017-06-26 18:51:58 +02001885 for name, dep in sorted(os_deps.iteritems()):
1886 condition_part = ([' "condition": "%s",' % dep.condition]
1887 if dep.condition else [])
1888 s.extend([
1889 ' # %s' % dep.hierarchy(include_url=False),
1890 ' "%s": {' % (name,),
1891 ' "url": "%s",' % (dep.url,),
1892 ] + condition_part + [
1893 ' },',
1894 '',
1895 ])
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02001896 s.extend([' },', ''])
1897 s.extend(['}', ''])
1898 return s
1899
1900
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001901def _HooksToLines(name, hooks):
1902 """Converts |hooks| list to list of lines for output."""
Paweł Hajdan, Jr5b593352017-06-29 18:37:45 +02001903 if not hooks:
1904 return []
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001905 s = ['%s = [' % name]
1906 for dep, hook in hooks:
1907 s.extend([
1908 ' # %s' % dep.hierarchy(include_url=False),
1909 ' {',
1910 ])
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +02001911 if hook.name is not None:
1912 s.append(' "name": "%s",' % hook.name)
1913 if hook.pattern is not None:
1914 s.append(' "pattern": "%s",' % hook.pattern)
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001915 s.extend(
Paweł Hajdan, Jrc9364392017-06-14 17:11:56 +02001916 # Hooks run in the parent directory of their dep.
Paweł Hajdan, Jrad30de62017-06-26 18:51:58 +02001917 [' "cwd": "%s",' % os.path.normpath(os.path.dirname(dep.name))] +
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001918 [' "action": ['] +
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +02001919 [' "%s",' % arg for arg in hook.action] +
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001920 [' ]', ' },', '']
1921 )
1922 s.extend([']', ''])
1923 return s
1924
1925
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +02001926def _HooksOsToLines(hooks_os):
1927 """Converts |hooks| list to list of lines for output."""
Paweł Hajdan, Jr5b593352017-06-29 18:37:45 +02001928 if not hooks_os:
1929 return []
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +02001930 s = ['hooks_os = {']
1931 for hook_os, os_hooks in hooks_os.iteritems():
Michael Moss017bcf62017-06-28 15:26:38 -07001932 s.append(' "%s": [' % hook_os)
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +02001933 for dep, hook in os_hooks:
1934 s.extend([
1935 ' # %s' % dep.hierarchy(include_url=False),
1936 ' {',
1937 ])
1938 if hook.name is not None:
1939 s.append(' "name": "%s",' % hook.name)
1940 if hook.pattern is not None:
1941 s.append(' "pattern": "%s",' % hook.pattern)
1942 s.extend(
1943 # Hooks run in the parent directory of their dep.
1944 [' "cwd": "%s",' % os.path.normpath(os.path.dirname(dep.name))] +
1945 [' "action": ['] +
1946 [' "%s",' % arg for arg in hook.action] +
1947 [' ]', ' },', '']
1948 )
Michael Moss017bcf62017-06-28 15:26:38 -07001949 s.extend([' ],', ''])
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +02001950 s.extend(['}', ''])
1951 return s
1952
1953
Paweł Hajdan, Jrfb022012017-07-06 18:00:08 +02001954def _VarsToLines(variables):
1955 """Converts |variables| dict to list of lines for output."""
1956 if not variables:
1957 return []
1958 s = ['vars = {']
1959 for key, tup in sorted(variables.iteritems()):
1960 dep, value = tup
1961 s.extend([
1962 ' # %s' % dep.hierarchy(include_url=False),
1963 ' "%s": %r,' % (key, value),
1964 '',
1965 ])
1966 s.extend(['}', ''])
1967 return s
1968
1969
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001970def CMDgrep(parser, args):
1971 """Greps through git repos managed by gclient.
1972
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001973 Runs 'git grep [args...]' for each module.
1974 """
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001975 # We can't use optparse because it will try to parse arguments sent
1976 # to git grep and throw an error. :-(
1977 if not args or re.match('(-h|--help)$', args[0]):
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001978 print(
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001979 'Usage: gclient grep [-j <N>] git-grep-args...\n\n'
1980 'Example: "gclient grep -j10 -A2 RefCountedBase" runs\n"git grep '
1981 '-A2 RefCountedBase" on each of gclient\'s git\nrepos with up to '
1982 '10 jobs.\n\nBonus: page output by appending "|& less -FRSX" to the'
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001983 ' end of your query.',
1984 file=sys.stderr)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001985 return 1
1986
1987 jobs_arg = ['--jobs=1']
1988 if re.match(r'(-j|--jobs=)\d+$', args[0]):
1989 jobs_arg, args = args[:1], args[1:]
1990 elif re.match(r'(-j|--jobs)$', args[0]):
1991 jobs_arg, args = args[:2], args[2:]
1992
1993 return CMDrecurse(
1994 parser,
1995 jobs_arg + ['--ignore', '--prepend-dir', '--no-progress', '--scm=git',
1996 'git', 'grep', '--null', '--color=Always'] + args)
davidbarr@chromium.org12f944e2012-03-01 02:18:31 +00001997
1998
stip@chromium.orga735da22015-04-29 23:18:20 +00001999def CMDroot(parser, args):
2000 """Outputs the solution root (or current dir if there isn't one)."""
2001 (options, args) = parser.parse_args(args)
2002 client = GClient.LoadCurrentConfig(options)
2003 if client:
2004 print(os.path.abspath(client.root_dir))
2005 else:
2006 print(os.path.abspath('.'))
2007
2008
agablea98a6cd2016-11-15 14:30:10 -08002009@subcommand.usage('[url]')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002010def CMDconfig(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002011 """Creates a .gclient file in the current directory.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00002012
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002013 This specifies the configuration for further commands. After update/sync,
2014 top-level DEPS files in each module are read to determine dependent
2015 modules to operate on as well. If optional [url] parameter is
2016 provided, then configuration is read from a specified Subversion server
2017 URL.
2018 """
szager@chromium.orge2e03202012-07-31 18:05:16 +00002019 # We do a little dance with the --gclientfile option. 'gclient config' is the
2020 # only command where it's acceptable to have both '--gclientfile' and '--spec'
2021 # arguments. So, we temporarily stash any --gclientfile parameter into
2022 # options.output_config_file until after the (gclientfile xor spec) error
2023 # check.
2024 parser.remove_option('--gclientfile')
2025 parser.add_option('--gclientfile', dest='output_config_file',
2026 help='Specify an alternate .gclient file')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002027 parser.add_option('--name',
2028 help='overrides the default name for the solution')
nsylvain@google.comefc80932011-05-31 21:27:56 +00002029 parser.add_option('--deps-file', default='DEPS',
2030 help='overrides the default name for the DEPS file for the'
2031 'main solutions and all sub-dependencies')
smutae7ea312016-07-18 11:59:41 -07002032 parser.add_option('--unmanaged', action='store_true', default=False,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00002033 help='overrides the default behavior to make it possible '
smutae7ea312016-07-18 11:59:41 -07002034 'to have the main solution untouched by gclient '
2035 '(gclient will check out unmanaged dependencies but '
2036 'will never sync them)')
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00002037 parser.add_option('--cache-dir',
2038 help='(git only) Cache all git repos into this dir and do '
2039 'shared clones from the cache, instead of cloning '
2040 'directly from the remote. (experimental)')
szager@chromium.orge2e03202012-07-31 18:05:16 +00002041 parser.set_defaults(config_filename=None)
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002042 (options, args) = parser.parse_args(args)
szager@chromium.orge2e03202012-07-31 18:05:16 +00002043 if options.output_config_file:
2044 setattr(options, 'config_filename', getattr(options, 'output_config_file'))
maruel@chromium.org5fc2a332010-05-26 19:37:15 +00002045 if ((options.spec and args) or len(args) > 2 or
2046 (not options.spec and not args)):
2047 parser.error('Inconsistent arguments. Use either --spec or one or 2 args')
2048
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002049 client = GClient('.', options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002050 if options.spec:
2051 client.SetConfig(options.spec)
2052 else:
maruel@chromium.org1ab7ffc2009-06-03 17:21:37 +00002053 base_url = args[0].rstrip('/')
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00002054 if not options.name:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002055 name = base_url.split('/')[-1]
nsylvain@google.com12649ef2011-06-01 17:11:20 +00002056 if name.endswith('.git'):
2057 name = name[:-4]
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00002058 else:
2059 # specify an alternate relpath for the given URL.
2060 name = options.name
agable@chromium.orgf2214672015-10-27 21:02:48 +00002061 if not os.path.abspath(os.path.join(os.getcwd(), name)).startswith(
2062 os.getcwd()):
2063 parser.error('Do not pass a relative path for --name.')
2064 if any(x in ('..', '.', '/', '\\') for x in name.split(os.sep)):
2065 parser.error('Do not include relative path components in --name.')
2066
nsylvain@google.comefc80932011-05-31 21:27:56 +00002067 deps_file = options.deps_file
agablea98a6cd2016-11-15 14:30:10 -08002068 client.SetDefaultConfig(name, deps_file, base_url,
smutae7ea312016-07-18 11:59:41 -07002069 managed=not options.unmanaged,
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00002070 cache_dir=options.cache_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002071 client.SaveConfig()
maruel@chromium.org79692d62010-05-14 18:57:13 +00002072 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002073
2074
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002075@subcommand.epilog("""Example:
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002076 gclient pack > patch.txt
2077 generate simple patch for configured client and dependences
2078""")
2079def CMDpack(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002080 """Generates a patch which can be applied at the root of the tree.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00002081
agabled437d762016-10-17 09:35:11 -07002082 Internally, runs 'git diff' on each checked out module and
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002083 dependencies, and performs minimal postprocessing of the output. The
2084 resulting patch is printed to stdout and can be applied to a freshly
2085 checked out tree via 'patch -p0 < patchfile'.
2086 """
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002087 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2088 help='override deps for the specified (comma-separated) '
2089 'platform(s); \'all\' will process all deps_os '
2090 'references')
haitao.feng@intel.com306080c2012-05-04 13:11:29 +00002091 parser.remove_option('--jobs')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002092 (options, args) = parser.parse_args(args)
iannucci@chromium.org50395ea2013-04-04 04:47:42 +00002093 # Force jobs to 1 so the stdout is not annotated with the thread ids
haitao.feng@intel.com306080c2012-05-04 13:11:29 +00002094 options.jobs = 1
kbr@google.comab318592009-09-04 00:54:55 +00002095 client = GClient.LoadCurrentConfig(options)
2096 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002097 raise gclient_utils.Error('client not configured; see \'gclient config\'')
kbr@google.comab318592009-09-04 00:54:55 +00002098 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002099 client.PrintLocationAndContents()
kbr@google.comab318592009-09-04 00:54:55 +00002100 return client.RunOnDeps('pack', args)
2101
2102
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002103def CMDstatus(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002104 """Shows modification status for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002105 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2106 help='override deps for the specified (comma-separated) '
2107 'platform(s); \'all\' will process all deps_os '
2108 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002109 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002110 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002111 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002112 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002113 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002114 client.PrintLocationAndContents()
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002115 return client.RunOnDeps('status', args)
2116
2117
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002118@subcommand.epilog("""Examples:
maruel@chromium.org79692d62010-05-14 18:57:13 +00002119 gclient sync
2120 update files from SCM according to current configuration,
2121 *for modules which have changed since last update or sync*
2122 gclient sync --force
2123 update files from SCM according to current configuration, for
2124 all modules (useful for recovering files deleted from local copy)
2125 gclient sync --revision src@31000
2126 update src directory to r31000
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002127
2128JSON output format:
2129If the --output-json option is specified, the following document structure will
2130be emitted to the provided file. 'null' entries may occur for subprojects which
2131are present in the gclient solution, but were not processed (due to custom_deps,
2132os_deps, etc.)
2133
2134{
2135 "solutions" : {
2136 "<name>": { # <name> is the posix-normalized path to the solution.
agabled437d762016-10-17 09:35:11 -07002137 "revision": [<git id hex string>|null],
2138 "scm": ["git"|null],
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002139 }
2140 }
2141}
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002142""")
2143def CMDsync(parser, args):
2144 """Checkout/update all modules."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002145 parser.add_option('-f', '--force', action='store_true',
2146 help='force update even for unchanged modules')
2147 parser.add_option('-n', '--nohooks', action='store_true',
2148 help='don\'t run hooks after the update is complete')
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00002149 parser.add_option('-p', '--noprehooks', action='store_true',
2150 help='don\'t run pre-DEPS hooks', default=False)
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002151 parser.add_option('-r', '--revision', action='append',
2152 dest='revisions', metavar='REV', default=[],
2153 help='Enforces revision/hash for the solutions with the '
2154 'format src@rev. The src@ part is optional and can be '
2155 'skipped. -r can be used multiple times when .gclient '
2156 'has multiple solutions configured and will work even '
agablea98a6cd2016-11-15 14:30:10 -08002157 'if the src@ part is skipped.')
maruel@chromium.org794207e2013-03-08 15:29:43 +00002158 parser.add_option('--with_branch_heads', action='store_true',
2159 help='Clone git "branch_heads" refspecs in addition to '
2160 'the default refspecs. This adds about 1/2GB to a '
2161 'full checkout. (git only)')
szager@chromium.org8d3348f2014-08-19 22:49:16 +00002162 parser.add_option('--with_tags', action='store_true',
2163 help='Clone git tags in addition to the default refspecs.')
agable2697cd12016-06-28 10:23:53 -07002164 parser.add_option('-H', '--head', action='store_true',
agablea98a6cd2016-11-15 14:30:10 -08002165 help='DEPRECATED: only made sense with safesync urls.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002166 parser.add_option('-D', '--delete_unversioned_trees', action='store_true',
steveblock@chromium.org98e69452012-02-16 16:36:43 +00002167 help='Deletes from the working copy any dependencies that '
2168 'have been removed since the last sync, as long as '
2169 'there are no local modifications. When used with '
2170 '--force, such dependencies are removed even if they '
2171 'have local modifications. When used with --reset, '
2172 'all untracked directories are removed from the '
tnagel@chromium.orga2aaa632014-02-28 21:47:27 +00002173 'working copy, excluding those which are explicitly '
steveblock@chromium.org98e69452012-02-16 16:36:43 +00002174 'ignored in the repository.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002175 parser.add_option('-R', '--reset', action='store_true',
2176 help='resets any local changes before updating (git only)')
bauerb@chromium.org2aad1b22011-07-22 12:00:41 +00002177 parser.add_option('-M', '--merge', action='store_true',
2178 help='merge upstream changes instead of trying to '
2179 'fast-forward or rebase')
dnj@chromium.org5b23e872015-02-20 21:25:57 +00002180 parser.add_option('-A', '--auto_rebase', action='store_true',
2181 help='Automatically rebase repositories against local '
2182 'checkout during update (git only).')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002183 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2184 help='override deps for the specified (comma-separated) '
2185 'platform(s); \'all\' will process all deps_os '
2186 'references')
Paweł Hajdan, Jr357415c2017-07-24 14:35:28 +02002187 # TODO(phajdan.jr): use argparse.SUPPRESS to hide internal flags.
2188 parser.add_option('--do-not-merge-os-specific-entries', action='store_true',
2189 help='INTERNAL ONLY - disables merging of deps_os and '
2190 'hooks_os to dependencies and hooks')
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +00002191 parser.add_option('--upstream', action='store_true',
2192 help='Make repo state match upstream branch.')
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002193 parser.add_option('--output-json',
2194 help='Output a json document to this path containing '
2195 'summary information about the sync.')
primiano@chromium.org5439ea52014-08-06 17:18:18 +00002196 parser.add_option('--no-history', action='store_true',
2197 help='GIT ONLY - Reduces the size/time of the checkout at '
2198 'the cost of no history. Requires Git 1.9+')
hinoka@chromium.org46b87412014-05-15 00:42:05 +00002199 parser.add_option('--shallow', action='store_true',
2200 help='GIT ONLY - Do a shallow clone into the cache dir. '
2201 'Requires Git 1.9+')
e.hakkinen@samsung.come8bc1aa2015-04-08 08:00:37 +00002202 parser.add_option('--no_bootstrap', '--no-bootstrap',
2203 action='store_true',
2204 help='Don\'t bootstrap from Google Storage.')
hinoka@chromium.org8a10f6d2014-06-23 18:38:57 +00002205 parser.add_option('--ignore_locks', action='store_true',
2206 help='GIT ONLY - Ignore cache locks.')
iannucci@chromium.org30a07982016-04-07 21:35:19 +00002207 parser.add_option('--break_repo_locks', action='store_true',
2208 help='GIT ONLY - Forcibly remove repo locks (e.g. '
2209 'index.lock). This should only be used if you know for '
2210 'certain that this invocation of gclient is the only '
2211 'thing operating on the git repos (e.g. on a bot).')
nodir@chromium.org5b48e482016-03-18 20:27:54 +00002212 parser.add_option('--lock_timeout', type='int', default=5000,
szager@chromium.orgdbb6f822016-02-02 22:59:30 +00002213 help='GIT ONLY - Deadline (in seconds) to wait for git '
nodir@chromium.org5b48e482016-03-18 20:27:54 +00002214 'cache lock to become available. Default is %default.')
agabled437d762016-10-17 09:35:11 -07002215 # TODO(agable): Remove these when the oldest CrOS release milestone is M56.
2216 parser.add_option('-t', '--transitive', action='store_true',
2217 help='DEPRECATED: This is a no-op.')
sdefresne69b1be12016-10-18 05:48:02 -07002218 parser.add_option('-m', '--manually_grab_svn_rev', action='store_true',
agabled437d762016-10-17 09:35:11 -07002219 help='DEPRECATED: This is a no-op.')
Paweł Hajdan, Jr7c7b5592017-05-23 15:06:05 +02002220 # TODO(phajdan.jr): Remove validation options once default (crbug/570091).
Paweł Hajdan, Jr694773d2017-05-29 16:06:23 +02002221 parser.add_option('--validate-syntax', action='store_true', default=True,
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +02002222 help='Validate the .gclient and DEPS syntax')
Paweł Hajdan, Jr7c7b5592017-05-23 15:06:05 +02002223 parser.add_option('--disable-syntax-validation', action='store_false',
2224 dest='validate_syntax',
2225 help='Disable validation of .gclient and DEPS syntax.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002226 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002227 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002228
2229 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002230 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002231
smutae7ea312016-07-18 11:59:41 -07002232 if options.revisions and options.head:
2233 # TODO(maruel): Make it a parser.error if it doesn't break any builder.
2234 print('Warning: you cannot use both --head and --revision')
2235
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002236 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002237 client.PrintLocationAndContents()
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002238 ret = client.RunOnDeps('update', args)
2239 if options.output_json:
2240 slns = {}
2241 for d in client.subtree(True):
2242 normed = d.name.replace('\\', '/').rstrip('/') + '/'
2243 slns[normed] = {
2244 'revision': d.got_revision,
2245 'scm': d.used_scm.name if d.used_scm else None,
hinoka@chromium.org17db9052014-05-10 01:11:29 +00002246 'url': str(d.url) if d.url else None,
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002247 }
2248 with open(options.output_json, 'wb') as f:
2249 json.dump({'solutions': slns}, f)
2250 return ret
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002251
2252
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002253CMDupdate = CMDsync
2254
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002255
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +02002256def CMDvalidate(parser, args):
2257 """Validates the .gclient and DEPS syntax."""
2258 options, args = parser.parse_args(args)
2259 options.validate_syntax = True
2260 client = GClient.LoadCurrentConfig(options)
2261 rv = client.RunOnDeps('validate', args)
2262 if rv == 0:
2263 print('validate: SUCCESS')
2264 else:
2265 print('validate: FAILURE')
2266 return rv
2267
2268
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002269def CMDdiff(parser, args):
2270 """Displays local diff for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002271 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2272 help='override deps for the specified (comma-separated) '
2273 'platform(s); \'all\' will process all deps_os '
2274 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002275 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002276 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002277 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002278 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002279 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002280 client.PrintLocationAndContents()
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002281 return client.RunOnDeps('diff', args)
2282
2283
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002284def CMDrevert(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002285 """Reverts all modifications in every dependencies.
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00002286
2287 That's the nuclear option to get back to a 'clean' state. It removes anything
agabled437d762016-10-17 09:35:11 -07002288 that shows up in git status."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002289 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2290 help='override deps for the specified (comma-separated) '
2291 'platform(s); \'all\' will process all deps_os '
2292 'references')
2293 parser.add_option('-n', '--nohooks', action='store_true',
2294 help='don\'t run hooks after the revert is complete')
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00002295 parser.add_option('-p', '--noprehooks', action='store_true',
2296 help='don\'t run pre-DEPS hooks', default=False)
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +00002297 parser.add_option('--upstream', action='store_true',
2298 help='Make repo state match upstream branch.')
iannucci@chromium.orgbf525dc2016-04-07 22:00:28 +00002299 parser.add_option('--break_repo_locks', action='store_true',
2300 help='GIT ONLY - Forcibly remove repo locks (e.g. '
2301 'index.lock). This should only be used if you know for '
2302 'certain that this invocation of gclient is the only '
2303 'thing operating on the git repos (e.g. on a bot).')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002304 (options, args) = parser.parse_args(args)
2305 # --force is implied.
2306 options.force = True
steveblock@chromium.org98e69452012-02-16 16:36:43 +00002307 options.reset = False
2308 options.delete_unversioned_trees = False
agablec903d732016-07-26 09:07:24 -07002309 options.merge = False
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002310 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002311 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002312 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002313 return client.RunOnDeps('revert', args)
2314
2315
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002316def CMDrunhooks(parser, args):
2317 """Runs hooks for files that have been modified in the local working copy."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002318 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2319 help='override deps for the specified (comma-separated) '
2320 'platform(s); \'all\' will process all deps_os '
2321 'references')
2322 parser.add_option('-f', '--force', action='store_true', default=True,
2323 help='Deprecated. No effect.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002324 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002325 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002326 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002327 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002328 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002329 client.PrintLocationAndContents()
maruel@chromium.org5df6a462009-08-28 18:52:26 +00002330 options.force = True
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002331 options.nohooks = False
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002332 return client.RunOnDeps('runhooks', args)
2333
2334
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002335def CMDrevinfo(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002336 """Outputs revision info mapping for the client and its dependencies.
maruel@chromium.org9eda4112010-06-11 18:56:10 +00002337
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002338 This allows the capture of an overall 'revision' for the source tree that
maruel@chromium.org9eda4112010-06-11 18:56:10 +00002339 can be used to reproduce the same tree in the future. It is only useful for
agabled437d762016-10-17 09:35:11 -07002340 'unpinned dependencies', i.e. DEPS/deps references without a git hash.
2341 A git branch name isn't 'pinned' since the actual commit can change.
maruel@chromium.org9eda4112010-06-11 18:56:10 +00002342 """
2343 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2344 help='override deps for the specified (comma-separated) '
2345 'platform(s); \'all\' will process all deps_os '
2346 'references')
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00002347 parser.add_option('-a', '--actual', action='store_true',
2348 help='gets the actual checked out revisions instead of the '
2349 'ones specified in the DEPS and .gclient files')
maruel@chromium.org9eda4112010-06-11 18:56:10 +00002350 parser.add_option('-s', '--snapshot', action='store_true',
2351 help='creates a snapshot .gclient file of the current '
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00002352 'version of all repositories to reproduce the tree, '
2353 'implies -a')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002354 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002355 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002356 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002357 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002358 client.PrintRevInfo()
maruel@chromium.org79692d62010-05-14 18:57:13 +00002359 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002360
2361
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00002362def CMDverify(parser, args):
2363 """Verifies the DEPS file deps are only from allowed_hosts."""
2364 (options, args) = parser.parse_args(args)
2365 client = GClient.LoadCurrentConfig(options)
2366 if not client:
2367 raise gclient_utils.Error('client not configured; see \'gclient config\'')
2368 client.RunOnDeps(None, [])
2369 # Look at each first-level dependency of this gclient only.
2370 for dep in client.dependencies:
2371 bad_deps = dep.findDepsFromNotAllowedHosts()
2372 if not bad_deps:
2373 continue
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002374 print("There are deps from not allowed hosts in file %s" % dep.deps_file)
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00002375 for bad_dep in bad_deps:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002376 print("\t%s at %s" % (bad_dep.name, bad_dep.url))
2377 print("allowed_hosts:", ', '.join(dep.allowed_hosts))
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00002378 sys.stdout.flush()
2379 raise gclient_utils.Error(
2380 'dependencies from disallowed hosts; check your DEPS file.')
2381 return 0
2382
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002383class OptionParser(optparse.OptionParser):
szager@chromium.orge2e03202012-07-31 18:05:16 +00002384 gclientfile_default = os.environ.get('GCLIENT_FILE', '.gclient')
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002385
2386 def __init__(self, **kwargs):
2387 optparse.OptionParser.__init__(
2388 self, version='%prog ' + __version__, **kwargs)
2389
2390 # Some arm boards have issues with parallel sync.
2391 if platform.machine().startswith('arm'):
2392 jobs = 1
2393 else:
2394 jobs = max(8, gclient_utils.NumLocalCpus())
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002395
2396 self.add_option(
2397 '-j', '--jobs', default=jobs, type='int',
2398 help='Specify how many SCM commands can run in parallel; defaults to '
tnagel@chromium.orga2aaa632014-02-28 21:47:27 +00002399 '%default on this machine')
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002400 self.add_option(
2401 '-v', '--verbose', action='count', default=0,
2402 help='Produces additional output for diagnostics. Can be used up to '
2403 'three times for more logging info.')
2404 self.add_option(
2405 '--gclientfile', dest='config_filename',
2406 help='Specify an alternate %s file' % self.gclientfile_default)
2407 self.add_option(
2408 '--spec',
2409 help='create a gclient file containing the provided string. Due to '
2410 'Cygwin/Python brokenness, it can\'t contain any newlines.')
2411 self.add_option(
2412 '--no-nag-max', default=False, action='store_true',
scottmg@chromium.orgf547c802013-09-27 17:55:26 +00002413 help='Ignored for backwards compatibility.')
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002414
2415 def parse_args(self, args=None, values=None):
2416 """Integrates standard options processing."""
2417 options, args = optparse.OptionParser.parse_args(self, args, values)
2418 levels = [logging.ERROR, logging.WARNING, logging.INFO, logging.DEBUG]
2419 logging.basicConfig(
2420 level=levels[min(options.verbose, len(levels) - 1)],
maruel@chromium.org0895b752011-08-26 20:40:33 +00002421 format='%(module)s(%(lineno)d) %(funcName)s:%(message)s')
szager@chromium.orge2e03202012-07-31 18:05:16 +00002422 if options.config_filename and options.spec:
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002423 self.error('Cannot specifiy both --gclientfile and --spec')
rdsmith@chromium.orgd9591f02014-02-05 19:28:20 +00002424 if (options.config_filename and
2425 options.config_filename != os.path.basename(options.config_filename)):
2426 self.error('--gclientfile target must be a filename, not a path')
szager@chromium.orge2e03202012-07-31 18:05:16 +00002427 if not options.config_filename:
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002428 options.config_filename = self.gclientfile_default
maruel@chromium.org0895b752011-08-26 20:40:33 +00002429 options.entries_filename = options.config_filename + '_entries'
2430 if options.jobs < 1:
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002431 self.error('--jobs must be 1 or higher')
maruel@chromium.org0895b752011-08-26 20:40:33 +00002432
2433 # These hacks need to die.
2434 if not hasattr(options, 'revisions'):
2435 # GClient.RunOnDeps expects it even if not applicable.
2436 options.revisions = []
smutae7ea312016-07-18 11:59:41 -07002437 if not hasattr(options, 'head'):
2438 options.head = None
maruel@chromium.org0895b752011-08-26 20:40:33 +00002439 if not hasattr(options, 'nohooks'):
2440 options.nohooks = True
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00002441 if not hasattr(options, 'noprehooks'):
2442 options.noprehooks = True
maruel@chromium.org0895b752011-08-26 20:40:33 +00002443 if not hasattr(options, 'deps_os'):
2444 options.deps_os = None
maruel@chromium.org0895b752011-08-26 20:40:33 +00002445 if not hasattr(options, 'force'):
2446 options.force = None
2447 return (options, args)
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002448
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002449
2450def disable_buffering():
2451 # Make stdout auto-flush so buildbot doesn't kill us during lengthy
2452 # operations. Python as a strong tendency to buffer sys.stdout.
2453 sys.stdout = gclient_utils.MakeFileAutoFlush(sys.stdout)
2454 # Make stdout annotated with the thread ids.
2455 sys.stdout = gclient_utils.MakeFileAnnotated(sys.stdout)
maruel@chromium.org0895b752011-08-26 20:40:33 +00002456
2457
sbc@chromium.org013731e2015-02-26 18:28:43 +00002458def main(argv):
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002459 """Doesn't parse the arguments here, just find the right subcommand to
2460 execute."""
maruel@chromium.org82798cb2012-02-23 18:16:12 +00002461 if sys.hexversion < 0x02060000:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002462 print(
maruel@chromium.org82798cb2012-02-23 18:16:12 +00002463 '\nYour python version %s is unsupported, please upgrade.\n' %
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002464 sys.version.split(' ', 1)[0],
2465 file=sys.stderr)
maruel@chromium.org82798cb2012-02-23 18:16:12 +00002466 return 2
bcwhite@chromium.org6683ab42013-02-11 16:13:47 +00002467 if not sys.executable:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002468 print(
2469 '\nPython cannot find the location of it\'s own executable.\n',
2470 file=sys.stderr)
bcwhite@chromium.org6683ab42013-02-11 16:13:47 +00002471 return 2
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002472 fix_encoding.fix_encoding()
2473 disable_buffering()
iannucci@chromium.org596cd5c2016-04-04 21:34:39 +00002474 setup_color.init()
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002475 dispatcher = subcommand.CommandDispatcher(__name__)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00002476 try:
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002477 return dispatcher.execute(OptionParser(), argv)
xusydoc@chromium.org2fd6c3f2013-05-03 21:57:55 +00002478 except KeyboardInterrupt:
2479 gclient_utils.GClientChildren.KillAllRemainingChildren()
2480 raise
vapier@chromium.orga81a56e2015-11-11 07:56:13 +00002481 except (gclient_utils.Error, subprocess2.CalledProcessError) as e:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002482 print('Error: %s' % str(e), file=sys.stderr)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00002483 return 1
borenet@google.com6a9b1682014-03-24 18:35:23 +00002484 finally:
2485 gclient_utils.PrintWarnings()
sbc@chromium.org013731e2015-02-26 18:28:43 +00002486 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002487
2488
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00002489if '__main__' == __name__:
sbc@chromium.org013731e2015-02-26 18:28:43 +00002490 try:
2491 sys.exit(main(sys.argv[1:]))
2492 except KeyboardInterrupt:
2493 sys.stderr.write('interrupted\n')
2494 sys.exit(1)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002495
2496# vim: ts=2:sw=2:tw=80:et: