blob: e1b1be2ff441ef9d26debd55463f1509ee8ded2e [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
maruel@chromium.org116704f2010-06-11 17:34:38 +0000224class GClientKeywords(object):
maruel@chromium.org116704f2010-06-11 17:34:38 +0000225 class VarImpl(object):
226 def __init__(self, custom_vars, local_scope):
227 self._custom_vars = custom_vars
228 self._local_scope = local_scope
229
230 def Lookup(self, var_name):
231 """Implements the Var syntax."""
232 if var_name in self._custom_vars:
233 return self._custom_vars[var_name]
234 elif var_name in self._local_scope.get("vars", {}):
235 return self._local_scope["vars"][var_name]
236 raise gclient_utils.Error("Var is not defined: %s" % var_name)
237
238
maruel@chromium.org064186c2011-09-27 23:53:33 +0000239class DependencySettings(GClientKeywords):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000240 """Immutable configuration settings."""
241 def __init__(
agablea98a6cd2016-11-15 14:30:10 -0800242 self, parent, url, managed, custom_deps, custom_vars,
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +0200243 custom_hooks, deps_file, should_process, relative,
244 condition, condition_value):
maruel@chromium.org064186c2011-09-27 23:53:33 +0000245 GClientKeywords.__init__(self)
246
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000247 # These are not mutable:
248 self._parent = parent
mmoss@chromium.org8f93f792014-08-26 23:24:09 +0000249 self._deps_file = deps_file
maruel@chromium.org064186c2011-09-27 23:53:33 +0000250 self._url = url
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +0200251 # The condition as string (or None). Useful to keep e.g. for flatten.
252 self._condition = condition
253 # Boolean value of the condition. If there's no condition, just True.
254 self._condition_value = condition_value
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000255 # 'managed' determines whether or not this dependency is synced/updated by
256 # gclient after gclient checks it out initially. The difference between
257 # 'managed' and 'should_process' is that the user specifies 'managed' via
smutae7ea312016-07-18 11:59:41 -0700258 # the --unmanaged command-line flag or a .gclient config, where
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000259 # 'should_process' is dynamically set by gclient if it goes over its
260 # recursion limit and controls gclient's behavior so it does not misbehave.
261 self._managed = managed
262 self._should_process = should_process
agabledce6ddc2016-09-08 10:02:16 -0700263 # If this is a recursed-upon sub-dependency, and the parent has
264 # use_relative_paths set, then this dependency should check out its own
265 # dependencies relative to that parent's path for this, rather than
266 # relative to the .gclient file.
267 self._relative = relative
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +0000268 # This is a mutable value which has the list of 'target_os' OSes listed in
269 # the current deps file.
270 self.local_target_os = None
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000271
272 # These are only set in .gclient and not in DEPS files.
273 self._custom_vars = custom_vars or {}
274 self._custom_deps = custom_deps or {}
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000275 self._custom_hooks = custom_hooks or []
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000276
maruel@chromium.org064186c2011-09-27 23:53:33 +0000277 # Post process the url to remove trailing slashes.
278 if isinstance(self._url, basestring):
279 # urls are sometime incorrectly written as proto://host/path/@rev. Replace
280 # it to proto://host/path@rev.
maruel@chromium.org064186c2011-09-27 23:53:33 +0000281 self._url = self._url.replace('/@', '@')
Paweł Hajdan, Jr7e9303b2017-05-23 14:38:27 +0200282 elif not isinstance(self._url, (None.__class__)):
maruel@chromium.org064186c2011-09-27 23:53:33 +0000283 raise gclient_utils.Error(
Paweł Hajdan, Jr7e9303b2017-05-23 14:38:27 +0200284 ('dependency url must be either string or None, '
285 'instead of %s') % self._url.__class__.__name__)
mmoss@chromium.orgd0b272b2013-01-30 23:55:33 +0000286 # Make any deps_file path platform-appropriate.
287 for sep in ['/', '\\']:
288 self._deps_file = self._deps_file.replace(sep, os.sep)
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000289
290 @property
291 def deps_file(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000292 return self._deps_file
293
294 @property
295 def managed(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000296 return self._managed
297
298 @property
299 def parent(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000300 return self._parent
301
302 @property
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000303 def root(self):
304 """Returns the root node, a GClient object."""
305 if not self.parent:
306 # This line is to signal pylint that it could be a GClient instance.
307 return self or GClient(None, None)
308 return self.parent.root
309
310 @property
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000311 def should_process(self):
312 """True if this dependency should be processed, i.e. checked out."""
313 return self._should_process
314
315 @property
316 def custom_vars(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000317 return self._custom_vars.copy()
318
319 @property
320 def custom_deps(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000321 return self._custom_deps.copy()
322
maruel@chromium.org064186c2011-09-27 23:53:33 +0000323 @property
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000324 def custom_hooks(self):
325 return self._custom_hooks[:]
326
327 @property
maruel@chromium.org064186c2011-09-27 23:53:33 +0000328 def url(self):
329 return self._url
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000330
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000331 @property
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +0200332 def condition(self):
333 return self._condition
334
335 @property
336 def condition_value(self):
337 return self._condition_value
338
339 @property
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +0000340 def target_os(self):
341 if self.local_target_os is not None:
342 return tuple(set(self.local_target_os).union(self.parent.target_os))
343 else:
344 return self.parent.target_os
345
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000346 def get_custom_deps(self, name, url):
347 """Returns a custom deps if applicable."""
348 if self.parent:
349 url = self.parent.get_custom_deps(name, url)
350 # None is a valid return value to disable a dependency.
351 return self.custom_deps.get(name, url)
352
maruel@chromium.org064186c2011-09-27 23:53:33 +0000353
354class Dependency(gclient_utils.WorkItem, DependencySettings):
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000355 """Object that represents a dependency checkout."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000356
agablea98a6cd2016-11-15 14:30:10 -0800357 def __init__(self, parent, name, url, managed, custom_deps,
agabledce6ddc2016-09-08 10:02:16 -0700358 custom_vars, custom_hooks, deps_file, should_process,
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +0200359 relative, condition, condition_value):
maruel@chromium.org6ca8bf82011-09-19 23:04:30 +0000360 gclient_utils.WorkItem.__init__(self, name)
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000361 DependencySettings.__init__(
agablea98a6cd2016-11-15 14:30:10 -0800362 self, parent, url, managed, custom_deps, custom_vars,
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +0200363 custom_hooks, deps_file, should_process, relative,
364 condition, condition_value)
maruel@chromium.org68988972011-09-20 14:11:42 +0000365
366 # This is in both .gclient and DEPS files:
maruel@chromium.org064186c2011-09-27 23:53:33 +0000367 self._deps_hooks = []
maruel@chromium.org68988972011-09-20 14:11:42 +0000368
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000369 self._pre_deps_hooks = []
370
maruel@chromium.org68988972011-09-20 14:11:42 +0000371 # Calculates properties:
maruel@chromium.org064186c2011-09-27 23:53:33 +0000372 self._parsed_url = None
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +0000373 self._dependencies = []
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200374 # Keep track of original values, before post-processing (e.g. deps_os).
375 self._orig_dependencies = []
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +0200376 self._orig_deps_hooks = []
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +0200377 self._vars = {}
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +0200378 self._os_dependencies = {}
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +0200379 self._os_deps_hooks = {}
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200380
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000381 # A cache of the files affected by the current operation, necessary for
382 # hooks.
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000383 self._file_list = []
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000384 # List of host names from which dependencies are allowed.
385 # Default is an empty set, meaning unspecified in DEPS file, and hence all
386 # hosts will be allowed. Non-empty set means whitelist of hosts.
387 # allowed_hosts var is scoped to its DEPS file, and so it isn't recursive.
388 self._allowed_hosts = frozenset()
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +0200389 # Spec for .gni output to write (if any).
390 self._gn_args_file = None
391 self._gn_args = []
maruel@chromium.org85c2a192010-07-22 21:14:43 +0000392 # If it is not set to True, the dependency wasn't processed for its child
393 # dependency, i.e. its DEPS wasn't read.
maruel@chromium.org064186c2011-09-27 23:53:33 +0000394 self._deps_parsed = False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000395 # This dependency has been processed, i.e. checked out
maruel@chromium.org064186c2011-09-27 23:53:33 +0000396 self._processed = False
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000397 # This dependency had its pre-DEPS hooks run
398 self._pre_deps_hooks_ran = False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000399 # This dependency had its hook run
maruel@chromium.org064186c2011-09-27 23:53:33 +0000400 self._hooks_ran = False
kustermann@google.coma692e8f2013-04-18 08:32:04 +0000401 # This is the scm used to checkout self.url. It may be used by dependencies
402 # to get the datetime of the revision we checked out.
403 self._used_scm = None
szager@chromium.org4ad264b2014-05-20 04:43:47 +0000404 self._used_revision = None
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +0000405 # The actual revision we ended up getting, or None if that information is
406 # unavailable
407 self._got_revision = None
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000408
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000409 # This is a mutable value that overrides the normal recursion limit for this
410 # dependency. It is read from the actual DEPS file so cannot be set on
411 # class instantiation.
412 self.recursion_override = None
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000413 # recursedeps is a mutable value that selectively overrides the default
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000414 # 'no recursion' setting on a dep-by-dep basis. It will replace
415 # recursion_override.
iannucci@chromium.orgafa11ac2016-05-04 22:17:34 +0000416 #
417 # It will be a dictionary of {deps_name: {"deps_file": depfile_name}} or
418 # None.
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000419 self.recursedeps = None
hinoka885e5b12016-06-08 14:40:09 -0700420 # This is inherited from WorkItem. We want the URL to be a resource.
421 if url and isinstance(url, basestring):
422 # The url is usually given to gclient either as https://blah@123
qyearsley12fa6ff2016-08-24 09:18:40 -0700423 # or just https://blah. The @123 portion is irrelevant.
hinoka885e5b12016-06-08 14:40:09 -0700424 self.resources.append(url.split('@')[0])
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000425
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000426 if not self.name and self.parent:
427 raise gclient_utils.Error('Dependency without name')
428
maruel@chromium.org470b5432011-10-11 18:18:19 +0000429 @property
430 def requirements(self):
431 """Calculate the list of requirements."""
432 requirements = set()
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000433 # self.parent is implicitly a requirement. This will be recursive by
434 # definition.
435 if self.parent and self.parent.name:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000436 requirements.add(self.parent.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000437
438 # For a tree with at least 2 levels*, the leaf node needs to depend
439 # on the level higher up in an orderly way.
440 # This becomes messy for >2 depth as the DEPS file format is a dictionary,
441 # thus unsorted, while the .gclient format is a list thus sorted.
442 #
443 # * _recursion_limit is hard coded 2 and there is no hope to change this
444 # value.
445 #
446 # Interestingly enough, the following condition only works in the case we
447 # want: self is a 2nd level node. 3nd level node wouldn't need this since
448 # they already have their parent as a requirement.
maruel@chromium.org470b5432011-10-11 18:18:19 +0000449 if self.parent and self.parent.parent and not self.parent.parent.parent:
450 requirements |= set(i.name for i in self.root.dependencies if i.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000451
maruel@chromium.org470b5432011-10-11 18:18:19 +0000452 if self.name:
453 requirements |= set(
454 obj.name for obj in self.root.subtree(False)
455 if (obj is not self
456 and obj.name and
457 self.name.startswith(posixpath.join(obj.name, ''))))
458 requirements = tuple(sorted(requirements))
459 logging.info('Dependency(%s).requirements = %s' % (self.name, requirements))
460 return requirements
461
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000462 @property
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000463 def try_recursedeps(self):
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000464 """Returns False if recursion_override is ever specified."""
465 if self.recursion_override is not None:
466 return False
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000467 return self.parent.try_recursedeps
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000468
469 @property
470 def recursion_limit(self):
471 """Returns > 0 if this dependency is not too recursed to be processed."""
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000472 # We continue to support the absence of recursedeps until tools and DEPS
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000473 # using recursion_override are updated.
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000474 if self.try_recursedeps and self.parent.recursedeps != None:
475 if self.name in self.parent.recursedeps:
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000476 return 1
477
478 if self.recursion_override is not None:
479 return self.recursion_override
480 return max(self.parent.recursion_limit - 1, 0)
481
maruel@chromium.org470b5432011-10-11 18:18:19 +0000482 def verify_validity(self):
483 """Verifies that this Dependency is fine to add as a child of another one.
484
485 Returns True if this entry should be added, False if it is a duplicate of
486 another entry.
487 """
488 logging.info('Dependency(%s).verify_validity()' % self.name)
489 if self.name in [s.name for s in self.parent.dependencies]:
490 raise gclient_utils.Error(
491 'The same name "%s" appears multiple times in the deps section' %
492 self.name)
493 if not self.should_process:
494 # Return early, no need to set requirements.
495 return True
496
497 # This require a full tree traversal with locks.
498 siblings = [d for d in self.root.subtree(False) if d.name == self.name]
499 for sibling in siblings:
maruel@chromium.orgb848d5b2012-10-10 23:25:50 +0000500 self_url = self.LateOverride(self.url)
501 sibling_url = sibling.LateOverride(sibling.url)
502 # Allow to have only one to be None or ''.
503 if self_url != sibling_url and bool(self_url) == bool(sibling_url):
maruel@chromium.org470b5432011-10-11 18:18:19 +0000504 raise gclient_utils.Error(
maruel@chromium.orgb848d5b2012-10-10 23:25:50 +0000505 ('Dependency %s specified more than once:\n'
506 ' %s [%s]\n'
507 'vs\n'
508 ' %s [%s]') % (
509 self.name,
510 sibling.hierarchy(),
511 sibling_url,
512 self.hierarchy(),
513 self_url))
maruel@chromium.org470b5432011-10-11 18:18:19 +0000514 # In theory we could keep it as a shadow of the other one. In
515 # practice, simply ignore it.
516 logging.warn('Won\'t process duplicate dependency %s' % sibling)
517 return False
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000518 return True
maruel@chromium.org064186c2011-09-27 23:53:33 +0000519
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000520 def LateOverride(self, url):
Paweł Hajdan, Jr7e9303b2017-05-23 14:38:27 +0200521 """Resolves the parsed url from url."""
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000522 assert self.parsed_url == None or not self.should_process, self.parsed_url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000523 parsed_url = self.get_custom_deps(self.name, url)
524 if parsed_url != url:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000525 logging.info(
526 'Dependency(%s).LateOverride(%s) -> %s' %
527 (self.name, url, parsed_url))
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000528 return parsed_url
529
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000530 if isinstance(url, basestring):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000531 parsed_url = urlparse.urlparse(url)
scr@chromium.orgf1eccaf2014-04-11 15:51:33 +0000532 if (not parsed_url[0] and
533 not re.match(r'^\w+\@[\w\.-]+\:[\w\/]+', parsed_url[2])):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000534 # A relative url. Fetch the real base.
535 path = parsed_url[2]
536 if not path.startswith('/'):
537 raise gclient_utils.Error(
538 'relative DEPS entry \'%s\' must begin with a slash' % url)
539 # Create a scm just to query the full url.
540 parent_url = self.parent.parsed_url
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000541 scm = gclient_scm.CreateSCM(
542 parent_url, self.root.root_dir, None, self.outbuf)
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000543 parsed_url = scm.FullUrlForRelativeUrl(url)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000544 else:
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000545 parsed_url = url
maruel@chromium.org470b5432011-10-11 18:18:19 +0000546 logging.info(
547 'Dependency(%s).LateOverride(%s) -> %s' %
548 (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000549 return parsed_url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000550
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000551 if url is None:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000552 logging.info(
553 'Dependency(%s).LateOverride(%s) -> %s' % (self.name, url, url))
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000554 return url
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000555
556 raise gclient_utils.Error('Unknown url type')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000557
bratell@opera.comed2b4fe2013-12-16 14:34:12 +0000558 @staticmethod
559 def MergeWithOsDeps(deps, deps_os, target_os_list):
560 """Returns a new "deps" structure that is the deps sent in updated
561 with information from deps_os (the deps_os section of the DEPS
562 file) that matches the list of target os."""
bratell@opera.comed2b4fe2013-12-16 14:34:12 +0000563 new_deps = deps.copy()
Paweł Hajdan, Jrfd0057e2017-06-21 14:20:21 +0200564 for dep_os, os_deps in deps_os.iteritems():
565 for key, value in os_deps.iteritems():
566 if value is None:
567 # Make this condition very visible, so it's not a silent failure.
568 # It's unclear how to support None override in deps_os.
569 logging.error('Ignoring %r:%r in %r deps_os', key, value, dep_os)
570 continue
571
572 # Normalize value to be a dict which contains |should_process| metadata.
573 if isinstance(value, basestring):
574 value = {'url': value}
575 assert isinstance(value, collections.Mapping), (key, value)
576 value['should_process'] = dep_os in target_os_list
577
578 # Handle collisions/overrides.
579 if key in new_deps and new_deps[key] != value:
580 # Normalize the existing new_deps entry.
581 if isinstance(new_deps[key], basestring):
582 new_deps[key] = {'url': new_deps[key]}
583 assert isinstance(new_deps[key],
584 collections.Mapping), (key, new_deps[key])
585
586 # It's OK if the "override" sets the key to the same value.
587 # This is mostly for legacy reasons to keep existing DEPS files
588 # working. Often mac/ios and unix/android will do this.
589 if value['url'] != new_deps[key]['url']:
590 raise gclient_utils.Error(
591 ('Value from deps_os (%r; %r: %r) conflicts with existing deps '
592 'entry (%r).') % (dep_os, key, value, new_deps[key]))
593
594 # We'd otherwise overwrite |should_process| metadata, but a dep should
595 # be processed if _any_ of its references call for that.
596 value['should_process'] = (
597 value['should_process'] or
598 new_deps[key].get('should_process', True))
599
600 new_deps[key] = value
601
bratell@opera.comed2b4fe2013-12-16 14:34:12 +0000602 return new_deps
603
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200604 def _postprocess_deps(self, deps, rel_prefix):
605 """Performs post-processing of deps compared to what's in the DEPS file."""
Paweł Hajdan, Jr4426eaf2017-06-13 12:25:47 +0200606 # Make sure the dict is mutable, e.g. in case it's frozen.
607 deps = dict(deps)
608
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200609 # If a line is in custom_deps, but not in the solution, we want to append
610 # this line to the solution.
611 for d in self.custom_deps:
612 if d not in deps:
613 deps[d] = self.custom_deps[d]
614
615 if rel_prefix:
616 logging.warning('use_relative_paths enabled.')
617 rel_deps = {}
618 for d, url in deps.items():
619 # normpath is required to allow DEPS to use .. in their
620 # dependency local path.
621 rel_deps[os.path.normpath(os.path.join(rel_prefix, d))] = url
622 logging.warning('Updating deps by prepending %s.', rel_prefix)
623 deps = rel_deps
624
625 return deps
626
627 def _deps_to_objects(self, deps, use_relative_paths):
628 """Convert a deps dict to a dict of Dependency objects."""
629 deps_to_add = []
630 for name, dep_value in deps.iteritems():
631 should_process = self.recursion_limit and self.should_process
632 deps_file = self.deps_file
633 if self.recursedeps is not None:
634 ent = self.recursedeps.get(name)
635 if ent is not None:
636 deps_file = ent['deps_file']
637 if dep_value is None:
638 continue
639 condition = None
640 condition_value = True
641 if isinstance(dep_value, basestring):
642 url = dep_value
643 else:
644 # This should be guaranteed by schema checking in gclient_eval.
645 assert isinstance(dep_value, collections.Mapping)
646 url = dep_value['url']
Paweł Hajdan, Jrfd0057e2017-06-21 14:20:21 +0200647 # Take into account should_process metadata set by MergeWithOsDeps.
648 should_process = (should_process and
649 dep_value.get('should_process', True))
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200650 condition = dep_value.get('condition')
651 if condition:
Paweł Hajdan, Jrd3790252017-07-03 21:06:24 +0200652 condition_value = gclient_eval.EvaluateCondition(
653 condition, self.get_vars())
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200654 should_process = should_process and condition_value
655 deps_to_add.append(Dependency(
656 self, name, url, None, None, self.custom_vars, None,
657 deps_file, should_process, use_relative_paths, condition,
658 condition_value))
659 deps_to_add.sort(key=lambda x: x.name)
660 return deps_to_add
661
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000662 def ParseDepsFile(self):
maruel@chromium.org271375b2010-06-23 19:17:38 +0000663 """Parses the DEPS file for this dependency."""
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000664 assert not self.deps_parsed
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000665 assert not self.dependencies
iannucci@chromium.org0c3f3052014-05-22 00:29:22 +0000666
667 deps_content = None
cmp@chromium.org76ce73c2014-07-02 00:13:18 +0000668
669 # First try to locate the configured deps file. If it's missing, fallback
670 # to DEPS.
671 deps_files = [self.deps_file]
672 if 'DEPS' not in deps_files:
673 deps_files.append('DEPS')
674 for deps_file in deps_files:
675 filepath = os.path.join(self.root.root_dir, self.name, deps_file)
676 if os.path.isfile(filepath):
677 logging.info(
678 'ParseDepsFile(%s): %s file found at %s', self.name, deps_file,
679 filepath)
680 break
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000681 logging.info(
cmp@chromium.org76ce73c2014-07-02 00:13:18 +0000682 'ParseDepsFile(%s): No %s file found at %s', self.name, deps_file,
683 filepath)
684
685 if os.path.isfile(filepath):
maruel@chromium.org46304292010-10-28 11:42:00 +0000686 deps_content = gclient_utils.FileRead(filepath)
cmp@chromium.org76ce73c2014-07-02 00:13:18 +0000687 logging.debug('ParseDepsFile(%s) read:\n%s', self.name, deps_content)
iannucci@chromium.org0c3f3052014-05-22 00:29:22 +0000688
689 local_scope = {}
690 if deps_content:
691 # One thing is unintuitive, vars = {} must happen before Var() use.
692 var = self.VarImpl(self.custom_vars, local_scope)
Paweł Hajdan, Jrf1587bf2017-06-20 21:19:07 +0200693 global_scope = {
694 'Var': var.Lookup,
695 'deps_os': {},
696 }
maruel@chromium.org46304292010-10-28 11:42:00 +0000697 # Eval the content.
698 try:
Paweł Hajdan, Jrc485d5a2017-06-02 12:08:09 +0200699 if self._get_option('validate_syntax', False):
700 gclient_eval.Exec(deps_content, global_scope, local_scope, filepath)
701 else:
702 exec(deps_content, global_scope, local_scope)
vapier@chromium.orga81a56e2015-11-11 07:56:13 +0000703 except SyntaxError as e:
maruel@chromium.org46304292010-10-28 11:42:00 +0000704 gclient_utils.SyntaxErrorToError(filepath, e)
iannucci@chromium.org0c3f3052014-05-22 00:29:22 +0000705
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000706 if 'allowed_hosts' in local_scope:
707 try:
708 self._allowed_hosts = frozenset(local_scope.get('allowed_hosts'))
709 except TypeError: # raised if non-iterable
710 pass
711 if not self._allowed_hosts:
712 logging.warning("allowed_hosts is specified but empty %s",
713 self._allowed_hosts)
714 raise gclient_utils.Error(
715 'ParseDepsFile(%s): allowed_hosts must be absent '
716 'or a non-empty iterable' % self.name)
717
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +0200718 self._gn_args_file = local_scope.get('gclient_gn_args_file')
719 self._gn_args = local_scope.get('gclient_gn_args', [])
720
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200721 # Since we heavily post-process things, freeze ones which should
722 # reflect original state of DEPS.
723 self._vars = gclient_utils.freeze(local_scope.get('vars', {}))
724
725 # If use_relative_paths is set in the DEPS file, regenerate
726 # the dictionary using paths relative to the directory containing
727 # the DEPS file. Also update recursedeps if use_relative_paths is
728 # enabled.
729 # If the deps file doesn't set use_relative_paths, but the parent did
730 # (and therefore set self.relative on this Dependency object), then we
731 # want to modify the deps and recursedeps by prepending the parent
732 # directory of this dependency.
733 use_relative_paths = local_scope.get('use_relative_paths', False)
734 rel_prefix = None
735 if use_relative_paths:
736 rel_prefix = self.name
737 elif self._relative:
738 rel_prefix = os.path.dirname(self.name)
739
740 deps = local_scope.get('deps', {})
741 orig_deps = gclient_utils.freeze(deps)
742 if 'recursion' in local_scope:
743 self.recursion_override = local_scope.get('recursion')
744 logging.warning(
745 'Setting %s recursion to %d.', self.name, self.recursion_limit)
746 self.recursedeps = None
747 if 'recursedeps' in local_scope:
748 self.recursedeps = {}
749 for ent in local_scope['recursedeps']:
750 if isinstance(ent, basestring):
751 self.recursedeps[ent] = {"deps_file": self.deps_file}
752 else: # (depname, depsfilename)
753 self.recursedeps[ent[0]] = {"deps_file": ent[1]}
754 logging.warning('Found recursedeps %r.', repr(self.recursedeps))
755
756 if rel_prefix:
757 logging.warning('Updating recursedeps by prepending %s.', rel_prefix)
758 rel_deps = {}
759 for depname, options in self.recursedeps.iteritems():
760 rel_deps[
761 os.path.normpath(os.path.join(rel_prefix, depname))] = options
762 self.recursedeps = rel_deps
763
764 # If present, save 'target_os' in the local_target_os property.
765 if 'target_os' in local_scope:
766 self.local_target_os = local_scope['target_os']
767 # load os specific dependencies if defined. these dependencies may
768 # override or extend the values defined by the 'deps' member.
769 target_os_list = self.target_os
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +0200770 if 'deps_os' in local_scope:
771 for dep_os, os_deps in local_scope['deps_os'].iteritems():
772 self._os_dependencies[dep_os] = self._deps_to_objects(
773 self._postprocess_deps(os_deps, rel_prefix), use_relative_paths)
774 if target_os_list:
775 deps = self.MergeWithOsDeps(
776 deps, local_scope['deps_os'], target_os_list)
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200777
778 deps_to_add = self._deps_to_objects(
779 self._postprocess_deps(deps, rel_prefix), use_relative_paths)
780 orig_deps_to_add = self._deps_to_objects(
781 self._postprocess_deps(orig_deps, rel_prefix), use_relative_paths)
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000782
783 # override named sets of hooks by the custom hooks
784 hooks_to_run = []
785 hook_names_to_suppress = [c.get('name', '') for c in self.custom_hooks]
786 for hook in local_scope.get('hooks', []):
787 if hook.get('name', '') not in hook_names_to_suppress:
788 hooks_to_run.append(hook)
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +0200789 orig_hooks = gclient_utils.freeze(hooks_to_run)
Scott Grahamc4826742017-05-11 16:59:23 -0700790 if 'hooks_os' in local_scope and target_os_list:
791 hooks_os = local_scope['hooks_os']
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +0200792
793 # Keep original contents of hooks_os for flatten.
794 for hook_os, os_hooks in hooks_os.iteritems():
795 self._os_deps_hooks[hook_os] = [
Paweł Hajdan, Jrd3790252017-07-03 21:06:24 +0200796 Hook.from_dict(hook, variables=self.get_vars())
797 for hook in os_hooks]
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +0200798
Scott Grahamc4826742017-05-11 16:59:23 -0700799 # Specifically append these to ensure that hooks_os run after hooks.
800 for the_target_os in target_os_list:
801 the_target_os_hooks = hooks_os.get(the_target_os, [])
802 hooks_to_run.extend(the_target_os_hooks)
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000803
804 # add the replacements and any additions
805 for hook in self.custom_hooks:
806 if 'action' in hook:
807 hooks_to_run.append(hook)
808
Dirk Prankeda3a29e2017-02-27 15:29:36 -0800809 if self.recursion_limit:
Paweł Hajdan, Jr032d5452017-06-22 20:43:53 +0200810 self._pre_deps_hooks = [
Paweł Hajdan, Jrd3790252017-07-03 21:06:24 +0200811 Hook.from_dict(hook, variables=self.get_vars()) for hook in
Paweł Hajdan, Jr032d5452017-06-22 20:43:53 +0200812 local_scope.get('pre_deps_hooks', [])]
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000813
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200814 self.add_dependencies_and_close(
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +0200815 deps_to_add, hooks_to_run, orig_deps_to_add=orig_deps_to_add,
816 orig_hooks=orig_hooks)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000817 logging.info('ParseDepsFile(%s) done' % self.name)
818
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +0200819 def _get_option(self, attr, default):
820 obj = self
821 while not hasattr(obj, '_options'):
822 obj = obj.parent
823 return getattr(obj._options, attr, default)
824
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200825 def add_dependencies_and_close(
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +0200826 self, deps_to_add, hooks, orig_deps_to_add=None, orig_hooks=None):
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000827 """Adds the dependencies, hooks and mark the parsing as done."""
maruel@chromium.orgb9be0652011-10-14 18:05:40 +0000828 for dep in deps_to_add:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000829 if dep.verify_validity():
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000830 self.add_dependency(dep)
Paweł Hajdan, Jre0158782017-06-30 17:14:13 +0200831 for dep in (orig_deps_to_add if orig_deps_to_add is not None
832 else deps_to_add):
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200833 self.add_orig_dependency(dep)
Paweł Hajdan, Jr032d5452017-06-22 20:43:53 +0200834 self._mark_as_parsed(
Paweł Hajdan, Jrd3790252017-07-03 21:06:24 +0200835 [Hook.from_dict(h, variables=self.get_vars()) for h in hooks],
836 orig_hooks=[Hook.from_dict(h, variables=self.get_vars())
Paweł Hajdan, Jre0158782017-06-30 17:14:13 +0200837 for h in (orig_hooks if orig_hooks is not None else hooks)])
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000838
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000839 def findDepsFromNotAllowedHosts(self):
840 """Returns a list of depenecies from not allowed hosts.
841
842 If allowed_hosts is not set, allows all hosts and returns empty list.
843 """
844 if not self._allowed_hosts:
845 return []
846 bad_deps = []
847 for dep in self._dependencies:
szager@chromium.orgbd772dd2014-11-05 18:43:08 +0000848 # Don't enforce this for custom_deps.
849 if dep.name in self._custom_deps:
850 continue
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000851 if isinstance(dep.url, basestring):
852 parsed_url = urlparse.urlparse(dep.url)
853 if parsed_url.netloc and parsed_url.netloc not in self._allowed_hosts:
854 bad_deps.append(dep)
855 return bad_deps
856
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +0000857 # Arguments number differs from overridden method
Quinten Yearsleyb2cc4a92016-12-15 13:53:26 -0800858 # pylint: disable=arguments-differ
maruel@chromium.org3742c842010-09-09 19:27:14 +0000859 def run(self, revision_overrides, command, args, work_queue, options):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000860 """Runs |command| then parse the DEPS file."""
maruel@chromium.org470b5432011-10-11 18:18:19 +0000861 logging.info('Dependency(%s).run()' % self.name)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000862 assert self._file_list == []
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000863 if not self.should_process:
864 return
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000865 # When running runhooks, there's no need to consult the SCM.
866 # All known hooks are expected to run unconditionally regardless of working
867 # copy state, so skip the SCM status check.
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +0200868 run_scm = command not in (
869 'flatten', 'runhooks', 'recurse', 'validate', None)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000870 parsed_url = self.LateOverride(self.url)
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000871 file_list = [] if not options.nohooks else None
szager@chromium.org3a3608d2014-10-22 21:13:52 +0000872 revision_override = revision_overrides.pop(self.name, None)
Dave Tubbda9712017-06-01 15:10:53 -0700873 if not revision_override and parsed_url:
874 revision_override = revision_overrides.get(parsed_url.split('@')[0], None)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000875 if run_scm and parsed_url:
agabled437d762016-10-17 09:35:11 -0700876 # Create a shallow copy to mutate revision.
877 options = copy.copy(options)
878 options.revision = revision_override
879 self._used_revision = options.revision
880 self._used_scm = gclient_scm.CreateSCM(
881 parsed_url, self.root.root_dir, self.name, self.outbuf,
882 out_cb=work_queue.out_cb)
883 self._got_revision = self._used_scm.RunCommand(command, options, args,
884 file_list)
885 if file_list:
886 file_list = [os.path.join(self.name, f.strip()) for f in file_list]
maruel@chromium.org68988972011-09-20 14:11:42 +0000887
888 # TODO(phajdan.jr): We should know exactly when the paths are absolute.
889 # Convert all absolute paths to relative.
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000890 for i in range(len(file_list or [])):
maruel@chromium.org68988972011-09-20 14:11:42 +0000891 # It depends on the command being executed (like runhooks vs sync).
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000892 if not os.path.isabs(file_list[i]):
maruel@chromium.org68988972011-09-20 14:11:42 +0000893 continue
894 prefix = os.path.commonprefix(
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000895 [self.root.root_dir.lower(), file_list[i].lower()])
896 file_list[i] = file_list[i][len(prefix):]
maruel@chromium.org68988972011-09-20 14:11:42 +0000897 # Strip any leading path separators.
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000898 while file_list[i].startswith(('\\', '/')):
899 file_list[i] = file_list[i][1:]
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000900
901 # Always parse the DEPS file.
902 self.ParseDepsFile()
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +0200903 if self._gn_args_file and command == 'update':
904 self.WriteGNArgsFile()
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000905 self._run_is_done(file_list or [], parsed_url)
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000906 if command in ('update', 'revert') and not options.noprehooks:
907 self.RunPreDepsHooks()
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000908
909 if self.recursion_limit:
910 # Parse the dependencies of this dependency.
911 for s in self.dependencies:
Paweł Hajdan, Jr4baaa112017-07-04 19:09:32 +0200912 if s.should_process:
913 work_queue.enqueue(s)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000914
915 if command == 'recurse':
agabled437d762016-10-17 09:35:11 -0700916 # Skip file only checkout.
917 scm = gclient_scm.GetScmName(parsed_url)
918 if not options.scm or scm in options.scm:
919 cwd = os.path.normpath(os.path.join(self.root.root_dir, self.name))
920 # Pass in the SCM type as an env variable. Make sure we don't put
921 # unicode strings in the environment.
922 env = os.environ.copy()
923 if scm:
924 env['GCLIENT_SCM'] = str(scm)
925 if parsed_url:
926 env['GCLIENT_URL'] = str(parsed_url)
927 env['GCLIENT_DEP_PATH'] = str(self.name)
928 if options.prepend_dir and scm == 'git':
929 print_stdout = False
930 def filter_fn(line):
931 """Git-specific path marshaling. It is optimized for git-grep."""
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000932
agabled437d762016-10-17 09:35:11 -0700933 def mod_path(git_pathspec):
934 match = re.match('^(\\S+?:)?([^\0]+)$', git_pathspec)
935 modified_path = os.path.join(self.name, match.group(2))
936 branch = match.group(1) or ''
937 return '%s%s' % (branch, modified_path)
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000938
agabled437d762016-10-17 09:35:11 -0700939 match = re.match('^Binary file ([^\0]+) matches$', line)
940 if match:
941 print('Binary file %s matches\n' % mod_path(match.group(1)))
942 return
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000943
agabled437d762016-10-17 09:35:11 -0700944 items = line.split('\0')
945 if len(items) == 2 and items[1]:
946 print('%s : %s' % (mod_path(items[0]), items[1]))
947 elif len(items) >= 2:
948 # Multiple null bytes or a single trailing null byte indicate
949 # git is likely displaying filenames only (such as with -l)
950 print('\n'.join(mod_path(path) for path in items if path))
951 else:
952 print(line)
953 else:
954 print_stdout = True
955 filter_fn = None
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000956
agabled437d762016-10-17 09:35:11 -0700957 if parsed_url is None:
958 print('Skipped omitted dependency %s' % cwd, file=sys.stderr)
959 elif os.path.isdir(cwd):
960 try:
961 gclient_utils.CheckCallAndFilter(
962 args, cwd=cwd, env=env, print_stdout=print_stdout,
963 filter_fn=filter_fn,
964 )
965 except subprocess2.CalledProcessError:
966 if not options.ignore:
967 raise
968 else:
969 print('Skipped missing %s' % cwd, file=sys.stderr)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000970
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +0200971 def WriteGNArgsFile(self):
972 lines = ['# Generated from %r' % self.deps_file]
973 for arg in self._gn_args:
Paweł Hajdan, Jrd3790252017-07-03 21:06:24 +0200974 lines.append('%s = %s' % (arg, ToGNString(self.get_vars()[arg])))
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +0200975 with open(os.path.join(self.root.root_dir, self._gn_args_file), 'w') as f:
976 f.write('\n'.join(lines))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000977
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000978 @gclient_utils.lockedmethod
979 def _run_is_done(self, file_list, parsed_url):
980 # Both these are kept for hooks that are run as a separate tree traversal.
981 self._file_list = file_list
982 self._parsed_url = parsed_url
983 self._processed = True
984
szager@google.comb9a78d32012-03-13 18:46:21 +0000985 def GetHooks(self, options):
986 """Evaluates all hooks, and return them in a flat list.
987
988 RunOnDeps() must have been called before to load the DEPS.
989 """
990 result = []
maruel@chromium.org68988972011-09-20 14:11:42 +0000991 if not self.should_process or not self.recursion_limit:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000992 # Don't run the hook when it is above recursion_limit.
szager@google.comb9a78d32012-03-13 18:46:21 +0000993 return result
maruel@chromium.orgdc7445d2010-07-09 21:05:29 +0000994 # If "--force" was specified, run all hooks regardless of what files have
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000995 # changed.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000996 if self.deps_hooks:
agabled437d762016-10-17 09:35:11 -0700997 # TODO(maruel): If the user is using git, then we don't know
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000998 # what files have changed so we always run all hooks. It'd be nice to fix
999 # that.
1000 if (options.force or
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001001 gclient_scm.GetScmName(self.parsed_url) in ('git', None) or
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001002 os.path.isdir(os.path.join(self.root.root_dir, self.name, '.git'))):
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +02001003 result.extend(self.deps_hooks)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001004 else:
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +02001005 for hook in self.deps_hooks:
1006 if hook.matches(self.file_list_and_children):
1007 result.append(hook)
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001008 for s in self.dependencies:
szager@google.comb9a78d32012-03-13 18:46:21 +00001009 result.extend(s.GetHooks(options))
1010 return result
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001011
szager@google.comb9a78d32012-03-13 18:46:21 +00001012 def RunHooksRecursively(self, options):
1013 assert self.hooks_ran == False
maruel@chromium.org064186c2011-09-27 23:53:33 +00001014 self._hooks_ran = True
szager@google.comb9a78d32012-03-13 18:46:21 +00001015 for hook in self.GetHooks(options):
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +02001016 hook.run(self.root.root_dir)
maruel@chromium.orgeaf61062010-07-07 18:42:39 +00001017
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001018 def RunPreDepsHooks(self):
1019 assert self.processed
1020 assert self.deps_parsed
1021 assert not self.pre_deps_hooks_ran
1022 assert not self.hooks_ran
1023 for s in self.dependencies:
1024 assert not s.processed
1025 self._pre_deps_hooks_ran = True
1026 for hook in self.pre_deps_hooks:
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +02001027 hook.run(self.root.root_dir)
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001028
maruel@chromium.org0d812442010-08-10 12:41:08 +00001029 def subtree(self, include_all):
maruel@chromium.orgad3287e2011-10-03 19:15:10 +00001030 """Breadth first recursion excluding root node."""
maruel@chromium.orgf13a4182011-09-22 00:26:15 +00001031 dependencies = self.dependencies
1032 for d in dependencies:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001033 if d.should_process or include_all:
maruel@chromium.orgad3287e2011-10-03 19:15:10 +00001034 yield d
maruel@chromium.orgf13a4182011-09-22 00:26:15 +00001035 for d in dependencies:
maruel@chromium.orgad3287e2011-10-03 19:15:10 +00001036 for i in d.subtree(include_all):
1037 yield i
1038
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001039 @gclient_utils.lockedmethod
1040 def add_dependency(self, new_dep):
1041 self._dependencies.append(new_dep)
1042
1043 @gclient_utils.lockedmethod
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +02001044 def add_orig_dependency(self, new_dep):
1045 self._orig_dependencies.append(new_dep)
1046
1047 @gclient_utils.lockedmethod
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +02001048 def _mark_as_parsed(self, new_hooks, orig_hooks=None):
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001049 self._deps_hooks.extend(new_hooks)
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +02001050 self._orig_deps_hooks.extend(orig_hooks or new_hooks)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001051 self._deps_parsed = True
1052
maruel@chromium.org68988972011-09-20 14:11:42 +00001053 @property
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001054 @gclient_utils.lockedmethod
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +00001055 def dependencies(self):
1056 return tuple(self._dependencies)
1057
1058 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001059 @gclient_utils.lockedmethod
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +02001060 def orig_dependencies(self):
1061 return tuple(self._orig_dependencies)
1062
1063 @property
1064 @gclient_utils.lockedmethod
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02001065 def os_dependencies(self):
1066 return dict(self._os_dependencies)
1067
1068 @property
1069 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001070 def deps_hooks(self):
1071 return tuple(self._deps_hooks)
1072
1073 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001074 @gclient_utils.lockedmethod
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +02001075 def orig_deps_hooks(self):
1076 return tuple(self._orig_deps_hooks)
1077
1078 @property
1079 @gclient_utils.lockedmethod
1080 def os_deps_hooks(self):
1081 return dict(self._os_deps_hooks)
1082
1083 @property
1084 @gclient_utils.lockedmethod
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001085 def pre_deps_hooks(self):
1086 return tuple(self._pre_deps_hooks)
1087
1088 @property
1089 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001090 def parsed_url(self):
1091 return self._parsed_url
1092
1093 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001094 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001095 def deps_parsed(self):
maruel@chromium.org3223edd2011-10-10 23:17:39 +00001096 """This is purely for debugging purposes. It's not used anywhere."""
maruel@chromium.org064186c2011-09-27 23:53:33 +00001097 return self._deps_parsed
1098
1099 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001100 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001101 def processed(self):
1102 return self._processed
1103
1104 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001105 @gclient_utils.lockedmethod
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001106 def pre_deps_hooks_ran(self):
1107 return self._pre_deps_hooks_ran
1108
1109 @property
1110 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001111 def hooks_ran(self):
1112 return self._hooks_ran
1113
1114 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001115 @gclient_utils.lockedmethod
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00001116 def allowed_hosts(self):
1117 return self._allowed_hosts
1118
1119 @property
1120 @gclient_utils.lockedmethod
maruel@chromium.org861fd0f2010-07-23 03:05:05 +00001121 def file_list(self):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001122 return tuple(self._file_list)
1123
1124 @property
kustermann@google.coma692e8f2013-04-18 08:32:04 +00001125 def used_scm(self):
1126 """SCMWrapper instance for this dependency or None if not processed yet."""
1127 return self._used_scm
1128
1129 @property
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00001130 @gclient_utils.lockedmethod
1131 def got_revision(self):
1132 return self._got_revision
1133
1134 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001135 def file_list_and_children(self):
1136 result = list(self.file_list)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +00001137 for d in self.dependencies:
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001138 result.extend(d.file_list_and_children)
maruel@chromium.org68988972011-09-20 14:11:42 +00001139 return tuple(result)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +00001140
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001141 def __str__(self):
1142 out = []
agablea98a6cd2016-11-15 14:30:10 -08001143 for i in ('name', 'url', 'parsed_url', 'custom_deps',
maruel@chromium.org3c74bc92011-09-15 19:17:21 +00001144 'custom_vars', 'deps_hooks', 'file_list', 'should_process',
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00001145 'processed', 'hooks_ran', 'deps_parsed', 'requirements',
1146 'allowed_hosts'):
maruel@chromium.org3c74bc92011-09-15 19:17:21 +00001147 # First try the native property if it exists.
1148 if hasattr(self, '_' + i):
1149 value = getattr(self, '_' + i, False)
1150 else:
1151 value = getattr(self, i, False)
1152 if value:
1153 out.append('%s: %s' % (i, value))
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001154
1155 for d in self.dependencies:
1156 out.extend([' ' + x for x in str(d).splitlines()])
1157 out.append('')
1158 return '\n'.join(out)
1159
1160 def __repr__(self):
1161 return '%s: %s' % (self.name, self.url)
1162
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001163 def hierarchy(self, include_url=True):
maruel@chromium.orgbc2d2f92010-07-22 21:26:48 +00001164 """Returns a human-readable hierarchical reference to a Dependency."""
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001165 def format_name(d):
1166 if include_url:
1167 return '%s(%s)' % (d.name, d.url)
1168 return d.name
1169 out = format_name(self)
maruel@chromium.orgbffb9042010-07-22 20:59:36 +00001170 i = self.parent
1171 while i and i.name:
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001172 out = '%s -> %s' % (format_name(i), out)
maruel@chromium.orgbffb9042010-07-22 20:59:36 +00001173 i = i.parent
1174 return out
1175
Paweł Hajdan, Jrd3790252017-07-03 21:06:24 +02001176 def get_vars(self):
1177 """Returns a dictionary of effective variable values
1178 (DEPS file contents with applied custom_vars overrides)."""
1179 result = dict(self._vars)
1180 result.update(self.custom_vars or {})
1181 return result
1182
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001183
1184class GClient(Dependency):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001185 """Object that represent a gclient checkout. A tree of Dependency(), one per
1186 solution or DEPS entry."""
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001187
1188 DEPS_OS_CHOICES = {
1189 "win32": "win",
1190 "win": "win",
1191 "cygwin": "win",
1192 "darwin": "mac",
1193 "mac": "mac",
1194 "unix": "unix",
1195 "linux": "unix",
1196 "linux2": "unix",
maruel@chromium.org244e3442011-06-12 15:20:55 +00001197 "linux3": "unix",
szager@chromium.orgf8c95cd2012-06-01 22:26:52 +00001198 "android": "android",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001199 }
1200
1201 DEFAULT_CLIENT_FILE_TEXT = ("""\
1202solutions = [
smutae7ea312016-07-18 11:59:41 -07001203 { "name" : "%(solution_name)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001204 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +00001205 "deps_file" : "%(deps_file)s",
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001206 "managed" : %(managed)s,
smutae7ea312016-07-18 11:59:41 -07001207 "custom_deps" : {
1208 },
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001209 },
1210]
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001211cache_dir = %(cache_dir)r
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001212""")
1213
1214 DEFAULT_SNAPSHOT_SOLUTION_TEXT = ("""\
smutae7ea312016-07-18 11:59:41 -07001215 { "name" : "%(solution_name)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001216 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +00001217 "deps_file" : "%(deps_file)s",
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001218 "managed" : %(managed)s,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001219 "custom_deps" : {
smutae7ea312016-07-18 11:59:41 -07001220%(solution_deps)s },
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001221 },
1222""")
1223
1224 DEFAULT_SNAPSHOT_FILE_TEXT = ("""\
1225# Snapshot generated with gclient revinfo --snapshot
1226solutions = [
maruel@chromium.org73e21142010-07-05 13:32:01 +00001227%(solution_list)s]
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001228""")
1229
1230 def __init__(self, root_dir, options):
maruel@chromium.org0d812442010-08-10 12:41:08 +00001231 # Do not change previous behavior. Only solution level and immediate DEPS
1232 # are processed.
1233 self._recursion_limit = 2
agablea98a6cd2016-11-15 14:30:10 -08001234 Dependency.__init__(self, None, None, None, True, None, None, None,
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +02001235 'unused', True, None, None, True)
maruel@chromium.org0d425922010-06-21 19:22:24 +00001236 self._options = options
maruel@chromium.org271375b2010-06-23 19:17:38 +00001237 if options.deps_os:
1238 enforced_os = options.deps_os.split(',')
1239 else:
1240 enforced_os = [self.DEPS_OS_CHOICES.get(sys.platform, 'unix')]
1241 if 'all' in enforced_os:
1242 enforced_os = self.DEPS_OS_CHOICES.itervalues()
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001243 self._enforced_os = tuple(set(enforced_os))
maruel@chromium.org271375b2010-06-23 19:17:38 +00001244 self._root_dir = root_dir
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001245 self.config_content = None
1246
borenet@google.com88d10082014-03-21 17:24:48 +00001247 def _CheckConfig(self):
1248 """Verify that the config matches the state of the existing checked-out
1249 solutions."""
1250 for dep in self.dependencies:
1251 if dep.managed and dep.url:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001252 scm = gclient_scm.CreateSCM(
1253 dep.url, self.root_dir, dep.name, self.outbuf)
smut@google.comd33eab32014-07-07 19:35:18 +00001254 actual_url = scm.GetActualRemoteURL(self._options)
borenet@google.com4e9be262014-04-08 19:40:30 +00001255 if actual_url and not scm.DoesRemoteURLMatch(self._options):
levarum@chromium.org27a6f9a2016-05-28 00:21:49 +00001256 mirror = scm.GetCacheMirror()
1257 if mirror:
1258 mirror_string = '%s (exists=%s)' % (mirror.mirror_path,
1259 mirror.exists())
1260 else:
1261 mirror_string = 'not used'
borenet@google.com0a427372014-04-02 19:12:13 +00001262 raise gclient_utils.Error('''
borenet@google.com88d10082014-03-21 17:24:48 +00001263Your .gclient file seems to be broken. The requested URL is different from what
borenet@google.com0a427372014-04-02 19:12:13 +00001264is actually checked out in %(checkout_path)s.
borenet@google.com88d10082014-03-21 17:24:48 +00001265
borenet@google.com97882362014-04-07 20:06:02 +00001266The .gclient file contains:
levarum@chromium.org27a6f9a2016-05-28 00:21:49 +00001267URL: %(expected_url)s (%(expected_scm)s)
1268Cache mirror: %(mirror_string)s
borenet@google.com97882362014-04-07 20:06:02 +00001269
1270The local checkout in %(checkout_path)s reports:
1271%(actual_url)s (%(actual_scm)s)
borenet@google.com88d10082014-03-21 17:24:48 +00001272
1273You should ensure that the URL listed in .gclient is correct and either change
agabled437d762016-10-17 09:35:11 -07001274it or fix the checkout.
borenet@google.com88d10082014-03-21 17:24:48 +00001275''' % {'checkout_path': os.path.join(self.root_dir, dep.name),
1276 'expected_url': dep.url,
1277 'expected_scm': gclient_scm.GetScmName(dep.url),
levarum@chromium.org27a6f9a2016-05-28 00:21:49 +00001278 'mirror_string' : mirror_string,
borenet@google.com88d10082014-03-21 17:24:48 +00001279 'actual_url': actual_url,
1280 'actual_scm': gclient_scm.GetScmName(actual_url)})
1281
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001282 def SetConfig(self, content):
maruel@chromium.orgf13a4182011-09-22 00:26:15 +00001283 assert not self.dependencies
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001284 config_dict = {}
1285 self.config_content = content
1286 try:
1287 exec(content, config_dict)
vapier@chromium.orga81a56e2015-11-11 07:56:13 +00001288 except SyntaxError as e:
maruel@chromium.org5990f9d2010-07-07 18:02:58 +00001289 gclient_utils.SyntaxErrorToError('.gclient', e)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001290
peter@chromium.org1efccc82012-04-27 16:34:38 +00001291 # Append any target OS that is not already being enforced to the tuple.
1292 target_os = config_dict.get('target_os', [])
stuartmorgan@chromium.org18a4f6a2012-11-02 13:37:24 +00001293 if config_dict.get('target_os_only', False):
1294 self._enforced_os = tuple(set(target_os))
1295 else:
1296 self._enforced_os = tuple(set(self._enforced_os).union(target_os))
1297
dyen@chromium.orgd915cca2014-08-07 21:41:37 +00001298 cache_dir = config_dict.get('cache_dir')
1299 if cache_dir:
1300 cache_dir = os.path.join(self.root_dir, cache_dir)
1301 cache_dir = os.path.abspath(cache_dir)
szager@chromium.orgcaf5bef2014-08-24 18:56:32 +00001302 # If running on a bot, force break any stale git cache locks.
dnj@chromium.orgb682b3e2014-08-25 19:17:12 +00001303 if os.path.exists(cache_dir) and os.environ.get('CHROME_HEADLESS'):
szager@chromium.org4848fb62014-08-24 19:16:31 +00001304 subprocess2.check_call(['git', 'cache', 'unlock', '--cache-dir',
1305 cache_dir, '--force', '--all'])
dyen@chromium.orgd915cca2014-08-07 21:41:37 +00001306 gclient_scm.GitWrapper.cache_dir = cache_dir
1307 git_cache.Mirror.SetCachePath(cache_dir)
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001308
stuartmorgan@chromium.org18a4f6a2012-11-02 13:37:24 +00001309 if not target_os and config_dict.get('target_os_only', False):
1310 raise gclient_utils.Error('Can\'t use target_os_only if target_os is '
1311 'not specified')
peter@chromium.org1efccc82012-04-27 16:34:38 +00001312
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001313 deps_to_add = []
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001314 for s in config_dict.get('solutions', []):
maruel@chromium.org81843b82010-06-28 16:49:26 +00001315 try:
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001316 deps_to_add.append(Dependency(
maruel@chromium.org81843b82010-06-28 16:49:26 +00001317 self, s['name'], s['url'],
smutae7ea312016-07-18 11:59:41 -07001318 s.get('managed', True),
maruel@chromium.org81843b82010-06-28 16:49:26 +00001319 s.get('custom_deps', {}),
maruel@chromium.org0d812442010-08-10 12:41:08 +00001320 s.get('custom_vars', {}),
petermayo@chromium.orge79161a2013-07-09 14:40:37 +00001321 s.get('custom_hooks', []),
nsylvain@google.comefc80932011-05-31 21:27:56 +00001322 s.get('deps_file', 'DEPS'),
agabledce6ddc2016-09-08 10:02:16 -07001323 True,
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +02001324 None,
1325 None,
1326 True))
maruel@chromium.org81843b82010-06-28 16:49:26 +00001327 except KeyError:
1328 raise gclient_utils.Error('Invalid .gclient file. Solution is '
1329 'incomplete: %s' % s)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001330 self.add_dependencies_and_close(deps_to_add, config_dict.get('hooks', []))
1331 logging.info('SetConfig() done')
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001332
1333 def SaveConfig(self):
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001334 gclient_utils.FileWrite(os.path.join(self.root_dir,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001335 self._options.config_filename),
1336 self.config_content)
1337
1338 @staticmethod
1339 def LoadCurrentConfig(options):
1340 """Searches for and loads a .gclient file relative to the current working
1341 dir. Returns a GClient object."""
szager@chromium.orge2e03202012-07-31 18:05:16 +00001342 if options.spec:
1343 client = GClient('.', options)
1344 client.SetConfig(options.spec)
1345 else:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00001346 if options.verbose:
1347 print('Looking for %s starting from %s\n' % (
1348 options.config_filename, os.getcwd()))
szager@chromium.orge2e03202012-07-31 18:05:16 +00001349 path = gclient_utils.FindGclientRoot(os.getcwd(), options.config_filename)
1350 if not path:
1351 return None
1352 client = GClient(path, options)
1353 client.SetConfig(gclient_utils.FileRead(
1354 os.path.join(path, options.config_filename)))
maruel@chromium.org69392e72011-10-13 22:09:00 +00001355
1356 if (options.revisions and
1357 len(client.dependencies) > 1 and
1358 any('@' not in r for r in options.revisions)):
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001359 print(
1360 ('You must specify the full solution name like --revision %s@%s\n'
1361 'when you have multiple solutions setup in your .gclient file.\n'
1362 'Other solutions present are: %s.') % (
maruel@chromium.org69392e72011-10-13 22:09:00 +00001363 client.dependencies[0].name,
1364 options.revisions[0],
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001365 ', '.join(s.name for s in client.dependencies[1:])),
1366 file=sys.stderr)
maruel@chromium.org15804092010-09-02 17:07:37 +00001367 return client
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001368
nsylvain@google.comefc80932011-05-31 21:27:56 +00001369 def SetDefaultConfig(self, solution_name, deps_file, solution_url,
agablea98a6cd2016-11-15 14:30:10 -08001370 managed=True, cache_dir=None):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001371 self.SetConfig(self.DEFAULT_CLIENT_FILE_TEXT % {
1372 'solution_name': solution_name,
1373 'solution_url': solution_url,
nsylvain@google.comefc80932011-05-31 21:27:56 +00001374 'deps_file': deps_file,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001375 'managed': managed,
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001376 'cache_dir': cache_dir,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001377 })
1378
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001379 def _SaveEntries(self):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001380 """Creates a .gclient_entries file to record the list of unique checkouts.
1381
1382 The .gclient_entries file lives in the same directory as .gclient.
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001383 """
1384 # Sometimes pprint.pformat will use {', sometimes it'll use { ' ... It
1385 # makes testing a bit too fun.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001386 result = 'entries = {\n'
maruel@chromium.org68988972011-09-20 14:11:42 +00001387 for entry in self.root.subtree(False):
agabled437d762016-10-17 09:35:11 -07001388 result += ' %s: %s,\n' % (pprint.pformat(entry.name),
1389 pprint.pformat(entry.parsed_url))
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001390 result += '}\n'
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001391 file_path = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org1333cb32011-10-04 23:40:16 +00001392 logging.debug(result)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001393 gclient_utils.FileWrite(file_path, result)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001394
1395 def _ReadEntries(self):
1396 """Read the .gclient_entries file for the given client.
1397
1398 Returns:
1399 A sequence of solution names, which will be empty if there is the
1400 entries file hasn't been created yet.
1401 """
1402 scope = {}
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001403 filename = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001404 if not os.path.exists(filename):
maruel@chromium.org73e21142010-07-05 13:32:01 +00001405 return {}
maruel@chromium.org5990f9d2010-07-07 18:02:58 +00001406 try:
1407 exec(gclient_utils.FileRead(filename), scope)
vapier@chromium.orga81a56e2015-11-11 07:56:13 +00001408 except SyntaxError as e:
maruel@chromium.org5990f9d2010-07-07 18:02:58 +00001409 gclient_utils.SyntaxErrorToError(filename, e)
Aaron Gable3721ee92017-04-03 14:53:14 -07001410 return scope.get('entries', {})
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001411
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001412 def _EnforceRevisions(self):
maruel@chromium.org918a9ae2010-05-28 15:50:30 +00001413 """Checks for revision overrides."""
1414 revision_overrides = {}
smutae7ea312016-07-18 11:59:41 -07001415 if self._options.head:
1416 return revision_overrides
joi@chromium.org792ea882010-11-10 02:37:27 +00001417 if not self._options.revisions:
1418 for s in self.dependencies:
smutae7ea312016-07-18 11:59:41 -07001419 if not s.managed:
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001420 self._options.revisions.append('%s@unmanaged' % s.name)
maruel@chromium.org307d1792010-05-31 20:03:13 +00001421 if not self._options.revisions:
1422 return revision_overrides
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001423 solutions_names = [s.name for s in self.dependencies]
smutae7ea312016-07-18 11:59:41 -07001424 index = 0
1425 for revision in self._options.revisions:
1426 if not '@' in revision:
maruel@chromium.org307d1792010-05-31 20:03:13 +00001427 # Support for --revision 123
smutae7ea312016-07-18 11:59:41 -07001428 revision = '%s@%s' % (solutions_names[index], revision)
1429 name, rev = revision.split('@', 1)
szager@chromium.org4ad264b2014-05-20 04:43:47 +00001430 revision_overrides[name] = rev
smutae7ea312016-07-18 11:59:41 -07001431 index += 1
maruel@chromium.org918a9ae2010-05-28 15:50:30 +00001432 return revision_overrides
1433
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001434 def RunOnDeps(self, command, args, ignore_requirements=False, progress=True):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001435 """Runs a command on each dependency in a client and its dependencies.
1436
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001437 Args:
1438 command: The command to use (e.g., 'status' or 'diff')
1439 args: list of str - extra arguments to add to the command line.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001440 """
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001441 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +00001442 raise gclient_utils.Error('No solution specified')
borenet@google.com0a427372014-04-02 19:12:13 +00001443
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001444 revision_overrides = {}
1445 # It's unnecessary to check for revision overrides for 'recurse'.
1446 # Save a few seconds by not calling _EnforceRevisions() in that case.
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +02001447 if command not in ('diff', 'recurse', 'runhooks', 'status', 'revert',
1448 'validate'):
szager@chromium.org5273b8a2014-08-21 15:10:10 +00001449 self._CheckConfig()
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001450 revision_overrides = self._EnforceRevisions()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001451 pm = None
maruel@chromium.org5b3f8852010-09-10 16:49:54 +00001452 # Disable progress for non-tty stdout.
iannucci@chromium.org596cd5c2016-04-04 21:34:39 +00001453 if (setup_color.IS_TTY and not self._options.verbose and progress):
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001454 if command in ('update', 'revert'):
1455 pm = Progress('Syncing projects', 1)
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +02001456 elif command in ('recurse', 'validate'):
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001457 pm = Progress(' '.join(args), 1)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001458 work_queue = gclient_utils.ExecutionQueue(
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001459 self._options.jobs, pm, ignore_requirements=ignore_requirements,
1460 verbose=self._options.verbose)
maruel@chromium.org049bced2010-08-12 13:37:20 +00001461 for s in self.dependencies:
Paweł Hajdan, Jr4baaa112017-07-04 19:09:32 +02001462 if s.should_process:
1463 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +00001464 work_queue.flush(revision_overrides, command, args, options=self._options)
szager@chromium.org4ad264b2014-05-20 04:43:47 +00001465 if revision_overrides:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001466 print('Please fix your script, having invalid --revision flags will soon '
1467 'considered an error.', file=sys.stderr)
piman@chromium.org6f363722010-04-27 00:41:09 +00001468
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001469 # Once all the dependencies have been processed, it's now safe to run the
1470 # hooks.
1471 if not self._options.nohooks:
1472 self.RunHooksRecursively(self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001473
1474 if command == 'update':
ajwong@chromium.orgcdcee802009-06-23 15:30:42 +00001475 # Notify the user if there is an orphaned entry in their working copy.
1476 # Only delete the directory if there are no changes in it, and
1477 # delete_unversioned_trees is set to true.
maruel@chromium.org68988972011-09-20 14:11:42 +00001478 entries = [i.name for i in self.root.subtree(False) if i.url]
xusydoc@chromium.org885a9602013-05-31 09:54:40 +00001479 full_entries = [os.path.join(self.root_dir, e.replace('/', os.path.sep))
1480 for e in entries]
1481
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001482 for entry, prev_url in self._ReadEntries().iteritems():
maruel@chromium.org04dd7de2010-10-14 13:25:49 +00001483 if not prev_url:
1484 # entry must have been overridden via .gclient custom_deps
1485 continue
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001486 # Fix path separator on Windows.
1487 entry_fixed = entry.replace('/', os.path.sep)
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001488 e_dir = os.path.join(self.root_dir, entry_fixed)
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001489 # Use entry and not entry_fixed there.
jochen@chromium.orga78e5532013-03-11 13:33:03 +00001490 if (entry not in entries and
1491 (not any(path.startswith(entry + '/') for path in entries)) and
jochen@chromium.orgcc475722013-03-11 13:07:40 +00001492 os.path.exists(e_dir)):
primiano@chromium.org1c127382015-02-17 11:15:40 +00001493 # The entry has been removed from DEPS.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001494 scm = gclient_scm.CreateSCM(
1495 prev_url, self.root_dir, entry_fixed, self.outbuf)
xusydoc@chromium.org885a9602013-05-31 09:54:40 +00001496
1497 # Check to see if this directory is now part of a higher-up checkout.
borenet@google.com359bb642014-05-13 17:28:19 +00001498 scm_root = None
agabled437d762016-10-17 09:35:11 -07001499 try:
1500 scm_root = gclient_scm.scm.GIT.GetCheckoutRoot(scm.checkout_path)
1501 except subprocess2.CalledProcessError:
1502 pass
1503 if not scm_root:
borenet@google.com359bb642014-05-13 17:28:19 +00001504 logging.warning('Could not find checkout root for %s. Unable to '
1505 'determine whether it is part of a higher-level '
1506 'checkout, so not removing.' % entry)
1507 continue
primiano@chromium.org1c127382015-02-17 11:15:40 +00001508
1509 # This is to handle the case of third_party/WebKit migrating from
1510 # being a DEPS entry to being part of the main project.
1511 # If the subproject is a Git project, we need to remove its .git
1512 # folder. Otherwise git operations on that folder will have different
1513 # effects depending on the current working directory.
agabled437d762016-10-17 09:35:11 -07001514 if os.path.abspath(scm_root) == os.path.abspath(e_dir):
primiano@chromium.org1c127382015-02-17 11:15:40 +00001515 e_par_dir = os.path.join(e_dir, os.pardir)
agabled437d762016-10-17 09:35:11 -07001516 if gclient_scm.scm.GIT.IsInsideWorkTree(e_par_dir):
1517 par_scm_root = gclient_scm.scm.GIT.GetCheckoutRoot(e_par_dir)
primiano@chromium.org1c127382015-02-17 11:15:40 +00001518 # rel_e_dir : relative path of entry w.r.t. its parent repo.
1519 rel_e_dir = os.path.relpath(e_dir, par_scm_root)
agabled437d762016-10-17 09:35:11 -07001520 if gclient_scm.scm.GIT.IsDirectoryVersioned(
1521 par_scm_root, rel_e_dir):
primiano@chromium.org1c127382015-02-17 11:15:40 +00001522 save_dir = scm.GetGitBackupDirPath()
1523 # Remove any eventual stale backup dir for the same project.
1524 if os.path.exists(save_dir):
1525 gclient_utils.rmtree(save_dir)
1526 os.rename(os.path.join(e_dir, '.git'), save_dir)
1527 # When switching between the two states (entry/ is a subproject
1528 # -> entry/ is part of the outer project), it is very likely
1529 # that some files are changed in the checkout, unless we are
1530 # jumping *exactly* across the commit which changed just DEPS.
1531 # In such case we want to cleanup any eventual stale files
1532 # (coming from the old subproject) in order to end up with a
1533 # clean checkout.
agabled437d762016-10-17 09:35:11 -07001534 gclient_scm.scm.GIT.CleanupDir(par_scm_root, rel_e_dir)
primiano@chromium.org1c127382015-02-17 11:15:40 +00001535 assert not os.path.exists(os.path.join(e_dir, '.git'))
1536 print(('\nWARNING: \'%s\' has been moved from DEPS to a higher '
1537 'level checkout. The git folder containing all the local'
1538 ' branches has been saved to %s.\n'
1539 'If you don\'t care about its state you can safely '
1540 'remove that folder to free up space.') %
1541 (entry, save_dir))
1542 continue
1543
borenet@google.com359bb642014-05-13 17:28:19 +00001544 if scm_root in full_entries:
primiano@chromium.org1c127382015-02-17 11:15:40 +00001545 logging.info('%s is part of a higher level checkout, not removing',
1546 scm.GetCheckoutRoot())
xusydoc@chromium.org885a9602013-05-31 09:54:40 +00001547 continue
1548
1549 file_list = []
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001550 scm.status(self._options, [], file_list)
1551 modified_files = file_list != []
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001552 if (not self._options.delete_unversioned_trees or
1553 (modified_files and not self._options.force)):
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001554 # There are modified files in this entry. Keep warning until
1555 # removed.
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001556 print(('\nWARNING: \'%s\' is no longer part of this client. '
1557 'It is recommended that you manually remove it.\n') %
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001558 entry_fixed)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001559 else:
1560 # Delete the entry
maruel@chromium.org73e21142010-07-05 13:32:01 +00001561 print('\n________ deleting \'%s\' in \'%s\'' % (
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001562 entry_fixed, self.root_dir))
digit@chromium.orgdc112ac2013-04-24 13:00:19 +00001563 gclient_utils.rmtree(e_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001564 # record the current list of entries for next time
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001565 self._SaveEntries()
maruel@chromium.org17cdf762010-05-28 17:30:52 +00001566 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001567
1568 def PrintRevInfo(self):
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001569 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +00001570 raise gclient_utils.Error('No solution specified')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001571 # Load all the settings.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001572 work_queue = gclient_utils.ExecutionQueue(
1573 self._options.jobs, None, False, verbose=self._options.verbose)
maruel@chromium.org049bced2010-08-12 13:37:20 +00001574 for s in self.dependencies:
Paweł Hajdan, Jr4baaa112017-07-04 19:09:32 +02001575 if s.should_process:
1576 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +00001577 work_queue.flush({}, None, [], options=self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001578
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001579 def GetURLAndRev(dep):
1580 """Returns the revision-qualified SCM url for a Dependency."""
1581 if dep.parsed_url is None:
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001582 return None
agabled437d762016-10-17 09:35:11 -07001583 url, _ = gclient_utils.SplitUrlRevision(dep.parsed_url)
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001584 scm = gclient_scm.CreateSCM(
agabled437d762016-10-17 09:35:11 -07001585 dep.parsed_url, self.root_dir, dep.name, self.outbuf)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001586 if not os.path.isdir(scm.checkout_path):
1587 return None
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001588 return '%s@%s' % (url, scm.revinfo(self._options, [], None))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001589
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001590 if self._options.snapshot:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001591 new_gclient = ''
1592 # First level at .gclient
1593 for d in self.dependencies:
1594 entries = {}
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001595 def GrabDeps(dep):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001596 """Recursively grab dependencies."""
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001597 for d in dep.dependencies:
1598 entries[d.name] = GetURLAndRev(d)
1599 GrabDeps(d)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001600 GrabDeps(d)
1601 custom_deps = []
1602 for k in sorted(entries.keys()):
1603 if entries[k]:
1604 # Quotes aren't escaped...
1605 custom_deps.append(' \"%s\": \'%s\',\n' % (k, entries[k]))
1606 else:
1607 custom_deps.append(' \"%s\": None,\n' % k)
1608 new_gclient += self.DEFAULT_SNAPSHOT_SOLUTION_TEXT % {
1609 'solution_name': d.name,
1610 'solution_url': d.url,
nsylvain@google.comefc80932011-05-31 21:27:56 +00001611 'deps_file': d.deps_file,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001612 'managed': d.managed,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001613 'solution_deps': ''.join(custom_deps),
1614 }
1615 # Print the snapshot configuration file
1616 print(self.DEFAULT_SNAPSHOT_FILE_TEXT % {'solution_list': new_gclient})
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +00001617 else:
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001618 entries = {}
maruel@chromium.org68988972011-09-20 14:11:42 +00001619 for d in self.root.subtree(False):
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001620 if self._options.actual:
1621 entries[d.name] = GetURLAndRev(d)
1622 else:
1623 entries[d.name] = d.parsed_url
1624 keys = sorted(entries.keys())
1625 for x in keys:
maruel@chromium.orgce464892010-08-12 17:12:18 +00001626 print('%s: %s' % (x, entries[x]))
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +00001627 logging.info(str(self))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001628
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001629 def ParseDepsFile(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001630 """No DEPS to parse for a .gclient file."""
maruel@chromium.org049bced2010-08-12 13:37:20 +00001631 raise gclient_utils.Error('Internal error')
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001632
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00001633 def PrintLocationAndContents(self):
1634 # Print out the .gclient file. This is longer than if we just printed the
1635 # client dict, but more legible, and it might contain helpful comments.
1636 print('Loaded .gclient config in %s:\n%s' % (
1637 self.root_dir, self.config_content))
1638
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001639 @property
maruel@chromium.org75a59272010-06-11 22:34:03 +00001640 def root_dir(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001641 """Root directory of gclient checkout."""
maruel@chromium.org75a59272010-06-11 22:34:03 +00001642 return self._root_dir
1643
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001644 @property
maruel@chromium.org271375b2010-06-23 19:17:38 +00001645 def enforced_os(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001646 """What deps_os entries that are to be parsed."""
maruel@chromium.org271375b2010-06-23 19:17:38 +00001647 return self._enforced_os
1648
maruel@chromium.org68988972011-09-20 14:11:42 +00001649 @property
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001650 def recursion_limit(self):
1651 """How recursive can each dependencies in DEPS file can load DEPS file."""
1652 return self._recursion_limit
1653
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +00001654 @property
cmp@chromium.orgc401ad12014-07-02 23:20:08 +00001655 def try_recursedeps(self):
1656 """Whether to attempt using recursedeps-style recursion processing."""
cmp@chromium.orge84ac912014-06-30 23:14:35 +00001657 return True
1658
1659 @property
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +00001660 def target_os(self):
1661 return self._enforced_os
1662
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001663
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001664#### gclient commands.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001665
1666
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001667@subcommand.usage('[command] [args ...]')
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001668def CMDrecurse(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001669 """Operates [command args ...] on all the dependencies.
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001670
1671 Runs a shell command on all entries.
qyearsley12fa6ff2016-08-24 09:18:40 -07001672 Sets GCLIENT_DEP_PATH environment variable as the dep's relative location to
ilevy@chromium.org37116242012-11-28 01:32:48 +00001673 root directory of the checkout.
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001674 """
1675 # Stop parsing at the first non-arg so that these go through to the command
1676 parser.disable_interspersed_args()
1677 parser.add_option('-s', '--scm', action='append', default=[],
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001678 help='Choose scm types to operate upon.')
maruel@chromium.org288054d2012-03-05 00:43:07 +00001679 parser.add_option('-i', '--ignore', action='store_true',
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001680 help='Ignore non-zero return codes from subcommands.')
1681 parser.add_option('--prepend-dir', action='store_true',
1682 help='Prepend relative dir for use with git <cmd> --null.')
1683 parser.add_option('--no-progress', action='store_true',
1684 help='Disable progress bar that shows sub-command updates')
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001685 options, args = parser.parse_args(args)
maruel@chromium.org45e9f2d2010-10-18 13:33:46 +00001686 if not args:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001687 print('Need to supply a command!', file=sys.stderr)
maruel@chromium.org45e9f2d2010-10-18 13:33:46 +00001688 return 1
maruel@chromium.org78cba522010-10-18 13:32:05 +00001689 root_and_entries = gclient_utils.GetGClientRootAndEntries()
1690 if not root_and_entries:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001691 print(
maruel@chromium.org78cba522010-10-18 13:32:05 +00001692 'You need to run gclient sync at least once to use \'recurse\'.\n'
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001693 'This is because .gclient_entries needs to exist and be up to date.',
1694 file=sys.stderr)
maruel@chromium.org78cba522010-10-18 13:32:05 +00001695 return 1
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001696
1697 # Normalize options.scm to a set()
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001698 scm_set = set()
1699 for scm in options.scm:
1700 scm_set.update(scm.split(','))
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001701 options.scm = scm_set
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001702
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001703 options.nohooks = True
1704 client = GClient.LoadCurrentConfig(options)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001705 return client.RunOnDeps('recurse', args, ignore_requirements=True,
1706 progress=not options.no_progress)
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001707
1708
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001709@subcommand.usage('[args ...]')
davidbarr@chromium.org12f944e2012-03-01 02:18:31 +00001710def CMDfetch(parser, args):
1711 """Fetches upstream commits for all modules.
1712
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001713 Completely git-specific. Simply runs 'git fetch [args ...]' for each module.
1714 """
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001715 (options, args) = parser.parse_args(args)
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00001716 return CMDrecurse(OptionParser(), [
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001717 '--jobs=%d' % options.jobs, '--scm=git', 'git', 'fetch'] + args)
1718
1719
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001720def CMDflatten(parser, args):
1721 """Flattens the solutions into a single DEPS file."""
1722 parser.add_option('--output-deps', help='Path to the output DEPS file')
1723 parser.add_option(
1724 '--require-pinned-revisions', action='store_true',
1725 help='Fail if any of the dependencies uses unpinned revision.')
1726 options, args = parser.parse_args(args)
1727
1728 options.nohooks = True
1729 client = GClient.LoadCurrentConfig(options)
1730
1731 # Only print progress if we're writing to a file. Otherwise, progress updates
1732 # could obscure intended output.
1733 code = client.RunOnDeps('flatten', args, progress=options.output_deps)
1734 if code != 0:
1735 return code
1736
Paweł Hajdan, Jr6014b562017-06-30 17:43:42 +02001737 allowed_hosts = set()
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001738 deps = {}
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02001739 deps_os = {}
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001740 hooks = []
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +02001741 hooks_os = {}
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001742 pre_deps_hooks = []
1743 unpinned_deps = {}
1744
1745 for solution in client.dependencies:
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02001746 _FlattenSolution(
Paweł Hajdan, Jr6014b562017-06-30 17:43:42 +02001747 solution, allowed_hosts, deps, deps_os, hooks, hooks_os, pre_deps_hooks,
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +02001748 unpinned_deps)
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001749
1750 if options.require_pinned_revisions and unpinned_deps:
1751 sys.stderr.write('The following dependencies are not pinned:\n')
1752 sys.stderr.write('\n'.join(sorted(unpinned_deps)))
1753 return 1
1754
1755 flattened_deps = '\n'.join(
Paweł Hajdan, Jr3c2aa832017-06-07 20:22:16 +02001756 _GNSettingsToLines(
1757 client.dependencies[0]._gn_args_file,
1758 client.dependencies[0]._gn_args) +
Paweł Hajdan, Jr6014b562017-06-30 17:43:42 +02001759 _AllowedHostsToLines(allowed_hosts) +
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001760 _DepsToLines(deps) +
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02001761 _DepsOsToLines(deps_os) +
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001762 _HooksToLines('hooks', hooks) +
1763 _HooksToLines('pre_deps_hooks', pre_deps_hooks) +
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +02001764 _HooksOsToLines(hooks_os) +
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001765 [''] # Ensure newline at end of file.
1766 )
1767
1768 if options.output_deps:
1769 with open(options.output_deps, 'w') as f:
1770 f.write(flattened_deps)
1771 else:
1772 print(flattened_deps)
1773
1774 return 0
1775
1776
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02001777def _FlattenSolution(
Paweł Hajdan, Jr6014b562017-06-30 17:43:42 +02001778 solution, allowed_hosts, deps, deps_os, hooks, hooks_os, pre_deps_hooks,
1779 unpinned_deps):
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001780 """Visits a solution in order to flatten it (see CMDflatten).
1781
1782 Arguments:
1783 solution (Dependency): one of top-level solutions in .gclient
1784
1785 Out-parameters:
Paweł Hajdan, Jr6014b562017-06-30 17:43:42 +02001786 allowed_hosts (set of host names): host names from which
1787 dependencies are allowed (whitelist)
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001788 deps (dict of name -> Dependency): will be filled with all Dependency
1789 objects indexed by their name
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02001790 deps_os (dict of os name -> dep name -> Dependency): same as above,
1791 for OS-specific deps
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001792 hooks (list of (Dependency, hook)): will be filled with flattened hooks
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +02001793 hooks_os (dict of os name -> list of (Dependency, hook)): same as above,
1794 for OS-specific hooks
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001795 pre_deps_hooks (list of (Dependency, hook)): will be filled with flattened
1796 pre_deps_hooks
1797 unpinned_deps (dict of name -> Dependency): will be filled with unpinned
1798 deps
1799 """
1800 logging.debug('_FlattenSolution(%r)', solution)
1801
Paweł Hajdan, Jr6014b562017-06-30 17:43:42 +02001802 _FlattenDep(solution, allowed_hosts, deps, deps_os, hooks, hooks_os,
1803 pre_deps_hooks, unpinned_deps)
1804 _FlattenRecurse(solution, allowed_hosts, deps, deps_os, hooks, hooks_os,
1805 pre_deps_hooks, unpinned_deps)
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001806
1807
Paweł Hajdan, Jr6014b562017-06-30 17:43:42 +02001808def _FlattenDep(dep, allowed_hosts, deps, deps_os, hooks, hooks_os,
1809 pre_deps_hooks, unpinned_deps):
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001810 """Visits a dependency in order to flatten it (see CMDflatten).
1811
1812 Arguments:
1813 dep (Dependency): dependency to process
1814
1815 Out-parameters:
Paweł Hajdan, Jr6014b562017-06-30 17:43:42 +02001816 allowed_hosts (set): will be filled with flattened allowed_hosts
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001817 deps (dict): will be filled with flattened deps
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02001818 deps_os (dict): will be filled with flattened deps_os
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001819 hooks (list): will be filled with flattened hooks
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +02001820 hooks_os (dict): will be filled with flattened hooks_os
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001821 pre_deps_hooks (list): will be filled with flattened pre_deps_hooks
1822 unpinned_deps (dict): will be filled with unpinned deps
1823 """
1824 logging.debug('_FlattenDep(%r)', dep)
1825
Paweł Hajdan, Jr6014b562017-06-30 17:43:42 +02001826 _AddDep(dep, allowed_hosts, deps, unpinned_deps)
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001827
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02001828 for dep_os, os_deps in dep.os_dependencies.iteritems():
1829 for os_dep in os_deps:
1830 deps_os.setdefault(dep_os, {})[os_dep.name] = os_dep
1831
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001832 deps_by_name = dict((d.name, d) for d in dep.dependencies)
1833 for recurse_dep_name in (dep.recursedeps or []):
1834 _FlattenRecurse(
Paweł Hajdan, Jr6014b562017-06-30 17:43:42 +02001835 deps_by_name[recurse_dep_name], allowed_hosts, deps, deps_os, hooks,
1836 hooks_os, pre_deps_hooks, unpinned_deps)
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001837
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +02001838 hooks.extend([(dep, hook) for hook in dep.orig_deps_hooks])
Paweł Hajdan, Jrc9364392017-06-14 17:11:56 +02001839 pre_deps_hooks.extend([(dep, hook) for hook in dep.pre_deps_hooks])
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001840
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +02001841 for hook_os, os_hooks in dep.os_deps_hooks.iteritems():
1842 hooks_os.setdefault(hook_os, []).extend([(dep, hook) for hook in os_hooks])
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001843
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +02001844
Paweł Hajdan, Jr6014b562017-06-30 17:43:42 +02001845def _FlattenRecurse(dep, allowed_hosts, deps, deps_os, hooks, hooks_os,
1846 pre_deps_hooks, unpinned_deps):
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001847 """Helper for flatten that recurses into |dep|'s dependencies.
1848
1849 Arguments:
1850 dep (Dependency): dependency to process
1851
1852 Out-parameters:
Paweł Hajdan, Jr6014b562017-06-30 17:43:42 +02001853 allowed_hosts (set): will be filled with flattened allowed_hosts
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001854 deps (dict): will be filled with flattened deps
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02001855 deps_os (dict): will be filled with flattened deps_os
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001856 hooks (list): will be filled with flattened hooks
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +02001857 hooks_os (dict): will be filled with flattened hooks_os
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001858 pre_deps_hooks (list): will be filled with flattened pre_deps_hooks
1859 unpinned_deps (dict): will be filled with unpinned deps
1860 """
1861 logging.debug('_FlattenRecurse(%r)', dep)
1862
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +02001863 for sub_dep in dep.orig_dependencies:
Paweł Hajdan, Jr6014b562017-06-30 17:43:42 +02001864 _FlattenDep(sub_dep, allowed_hosts, deps, deps_os, hooks, hooks_os,
1865 pre_deps_hooks, unpinned_deps)
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001866
1867
Paweł Hajdan, Jr6014b562017-06-30 17:43:42 +02001868def _AddDep(dep, allowed_hosts, deps, unpinned_deps):
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001869 """Helper to add a dependency to flattened lists.
1870
1871 Arguments:
1872 dep (Dependency): dependency to process
1873
1874 Out-parameters:
Paweł Hajdan, Jr6014b562017-06-30 17:43:42 +02001875 allowed_hosts (set): will be filled with flattened allowed_hosts
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001876 deps (dict): will be filled with flattened deps
1877 unpinned_deps (dict): will be filled with unpinned deps
1878 """
1879 logging.debug('_AddDep(%r)', dep)
1880
Paweł Hajdan, Jr6014b562017-06-30 17:43:42 +02001881 allowed_hosts.update(dep.allowed_hosts)
1882
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001883 assert dep.name not in deps
1884 deps[dep.name] = dep
1885
1886 # Detect unpinned deps.
1887 _, revision = gclient_utils.SplitUrlRevision(dep.url)
1888 if not revision or not gclient_utils.IsGitSha(revision):
1889 unpinned_deps[dep.name] = dep
1890
1891
Paweł Hajdan, Jr3c2aa832017-06-07 20:22:16 +02001892def _GNSettingsToLines(gn_args_file, gn_args):
1893 s = []
1894 if gn_args_file:
1895 s.extend([
1896 'gclient_gn_args_file = "%s"' % gn_args_file,
1897 'gclient_gn_args = %r' % gn_args,
1898 ])
1899 return s
1900
1901
Paweł Hajdan, Jr6014b562017-06-30 17:43:42 +02001902def _AllowedHostsToLines(allowed_hosts):
1903 """Converts |allowed_hosts| set to list of lines for output."""
1904 if not allowed_hosts:
1905 return []
1906 s = ['allowed_hosts = [']
1907 for h in sorted(allowed_hosts):
1908 s.append(' "%s",' % h)
1909 s.extend([']', ''])
1910 return s
1911
1912
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001913def _DepsToLines(deps):
1914 """Converts |deps| dict to list of lines for output."""
Paweł Hajdan, Jr5b593352017-06-29 18:37:45 +02001915 if not deps:
1916 return []
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001917 s = ['deps = {']
1918 for name, dep in sorted(deps.iteritems()):
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +02001919 condition_part = ([' "condition": "%s",' % dep.condition]
1920 if dep.condition else [])
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001921 s.extend([
1922 ' # %s' % dep.hierarchy(include_url=False),
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +02001923 ' "%s": {' % (name,),
1924 ' "url": "%s",' % (dep.url,),
1925 ] + condition_part + [
1926 ' },',
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001927 '',
1928 ])
1929 s.extend(['}', ''])
1930 return s
1931
1932
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02001933def _DepsOsToLines(deps_os):
1934 """Converts |deps_os| dict to list of lines for output."""
Paweł Hajdan, Jr5b593352017-06-29 18:37:45 +02001935 if not deps_os:
1936 return []
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02001937 s = ['deps_os = {']
1938 for dep_os, os_deps in sorted(deps_os.iteritems()):
1939 s.append(' "%s": {' % dep_os)
Paweł Hajdan, Jrad30de62017-06-26 18:51:58 +02001940 for name, dep in sorted(os_deps.iteritems()):
1941 condition_part = ([' "condition": "%s",' % dep.condition]
1942 if dep.condition else [])
1943 s.extend([
1944 ' # %s' % dep.hierarchy(include_url=False),
1945 ' "%s": {' % (name,),
1946 ' "url": "%s",' % (dep.url,),
1947 ] + condition_part + [
1948 ' },',
1949 '',
1950 ])
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02001951 s.extend([' },', ''])
1952 s.extend(['}', ''])
1953 return s
1954
1955
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001956def _HooksToLines(name, hooks):
1957 """Converts |hooks| list to list of lines for output."""
Paweł Hajdan, Jr5b593352017-06-29 18:37:45 +02001958 if not hooks:
1959 return []
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001960 s = ['%s = [' % name]
1961 for dep, hook in hooks:
1962 s.extend([
1963 ' # %s' % dep.hierarchy(include_url=False),
1964 ' {',
1965 ])
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +02001966 if hook.name is not None:
1967 s.append(' "name": "%s",' % hook.name)
1968 if hook.pattern is not None:
1969 s.append(' "pattern": "%s",' % hook.pattern)
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001970 s.extend(
Paweł Hajdan, Jrc9364392017-06-14 17:11:56 +02001971 # Hooks run in the parent directory of their dep.
Paweł Hajdan, Jrad30de62017-06-26 18:51:58 +02001972 [' "cwd": "%s",' % os.path.normpath(os.path.dirname(dep.name))] +
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001973 [' "action": ['] +
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +02001974 [' "%s",' % arg for arg in hook.action] +
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001975 [' ]', ' },', '']
1976 )
1977 s.extend([']', ''])
1978 return s
1979
1980
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +02001981def _HooksOsToLines(hooks_os):
1982 """Converts |hooks| list to list of lines for output."""
Paweł Hajdan, Jr5b593352017-06-29 18:37:45 +02001983 if not hooks_os:
1984 return []
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +02001985 s = ['hooks_os = {']
1986 for hook_os, os_hooks in hooks_os.iteritems():
Michael Moss017bcf62017-06-28 15:26:38 -07001987 s.append(' "%s": [' % hook_os)
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +02001988 for dep, hook in os_hooks:
1989 s.extend([
1990 ' # %s' % dep.hierarchy(include_url=False),
1991 ' {',
1992 ])
1993 if hook.name is not None:
1994 s.append(' "name": "%s",' % hook.name)
1995 if hook.pattern is not None:
1996 s.append(' "pattern": "%s",' % hook.pattern)
1997 s.extend(
1998 # Hooks run in the parent directory of their dep.
1999 [' "cwd": "%s",' % os.path.normpath(os.path.dirname(dep.name))] +
2000 [' "action": ['] +
2001 [' "%s",' % arg for arg in hook.action] +
2002 [' ]', ' },', '']
2003 )
Michael Moss017bcf62017-06-28 15:26:38 -07002004 s.extend([' ],', ''])
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +02002005 s.extend(['}', ''])
2006 return s
2007
2008
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00002009def CMDgrep(parser, args):
2010 """Greps through git repos managed by gclient.
2011
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002012 Runs 'git grep [args...]' for each module.
2013 """
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00002014 # We can't use optparse because it will try to parse arguments sent
2015 # to git grep and throw an error. :-(
2016 if not args or re.match('(-h|--help)$', args[0]):
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002017 print(
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00002018 'Usage: gclient grep [-j <N>] git-grep-args...\n\n'
2019 'Example: "gclient grep -j10 -A2 RefCountedBase" runs\n"git grep '
2020 '-A2 RefCountedBase" on each of gclient\'s git\nrepos with up to '
2021 '10 jobs.\n\nBonus: page output by appending "|& less -FRSX" to the'
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002022 ' end of your query.',
2023 file=sys.stderr)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00002024 return 1
2025
2026 jobs_arg = ['--jobs=1']
2027 if re.match(r'(-j|--jobs=)\d+$', args[0]):
2028 jobs_arg, args = args[:1], args[1:]
2029 elif re.match(r'(-j|--jobs)$', args[0]):
2030 jobs_arg, args = args[:2], args[2:]
2031
2032 return CMDrecurse(
2033 parser,
2034 jobs_arg + ['--ignore', '--prepend-dir', '--no-progress', '--scm=git',
2035 'git', 'grep', '--null', '--color=Always'] + args)
davidbarr@chromium.org12f944e2012-03-01 02:18:31 +00002036
2037
stip@chromium.orga735da22015-04-29 23:18:20 +00002038def CMDroot(parser, args):
2039 """Outputs the solution root (or current dir if there isn't one)."""
2040 (options, args) = parser.parse_args(args)
2041 client = GClient.LoadCurrentConfig(options)
2042 if client:
2043 print(os.path.abspath(client.root_dir))
2044 else:
2045 print(os.path.abspath('.'))
2046
2047
agablea98a6cd2016-11-15 14:30:10 -08002048@subcommand.usage('[url]')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002049def CMDconfig(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002050 """Creates a .gclient file in the current directory.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00002051
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002052 This specifies the configuration for further commands. After update/sync,
2053 top-level DEPS files in each module are read to determine dependent
2054 modules to operate on as well. If optional [url] parameter is
2055 provided, then configuration is read from a specified Subversion server
2056 URL.
2057 """
szager@chromium.orge2e03202012-07-31 18:05:16 +00002058 # We do a little dance with the --gclientfile option. 'gclient config' is the
2059 # only command where it's acceptable to have both '--gclientfile' and '--spec'
2060 # arguments. So, we temporarily stash any --gclientfile parameter into
2061 # options.output_config_file until after the (gclientfile xor spec) error
2062 # check.
2063 parser.remove_option('--gclientfile')
2064 parser.add_option('--gclientfile', dest='output_config_file',
2065 help='Specify an alternate .gclient file')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002066 parser.add_option('--name',
2067 help='overrides the default name for the solution')
nsylvain@google.comefc80932011-05-31 21:27:56 +00002068 parser.add_option('--deps-file', default='DEPS',
2069 help='overrides the default name for the DEPS file for the'
2070 'main solutions and all sub-dependencies')
smutae7ea312016-07-18 11:59:41 -07002071 parser.add_option('--unmanaged', action='store_true', default=False,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00002072 help='overrides the default behavior to make it possible '
smutae7ea312016-07-18 11:59:41 -07002073 'to have the main solution untouched by gclient '
2074 '(gclient will check out unmanaged dependencies but '
2075 'will never sync them)')
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00002076 parser.add_option('--cache-dir',
2077 help='(git only) Cache all git repos into this dir and do '
2078 'shared clones from the cache, instead of cloning '
2079 'directly from the remote. (experimental)')
szager@chromium.orge2e03202012-07-31 18:05:16 +00002080 parser.set_defaults(config_filename=None)
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002081 (options, args) = parser.parse_args(args)
szager@chromium.orge2e03202012-07-31 18:05:16 +00002082 if options.output_config_file:
2083 setattr(options, 'config_filename', getattr(options, 'output_config_file'))
maruel@chromium.org5fc2a332010-05-26 19:37:15 +00002084 if ((options.spec and args) or len(args) > 2 or
2085 (not options.spec and not args)):
2086 parser.error('Inconsistent arguments. Use either --spec or one or 2 args')
2087
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002088 client = GClient('.', options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002089 if options.spec:
2090 client.SetConfig(options.spec)
2091 else:
maruel@chromium.org1ab7ffc2009-06-03 17:21:37 +00002092 base_url = args[0].rstrip('/')
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00002093 if not options.name:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002094 name = base_url.split('/')[-1]
nsylvain@google.com12649ef2011-06-01 17:11:20 +00002095 if name.endswith('.git'):
2096 name = name[:-4]
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00002097 else:
2098 # specify an alternate relpath for the given URL.
2099 name = options.name
agable@chromium.orgf2214672015-10-27 21:02:48 +00002100 if not os.path.abspath(os.path.join(os.getcwd(), name)).startswith(
2101 os.getcwd()):
2102 parser.error('Do not pass a relative path for --name.')
2103 if any(x in ('..', '.', '/', '\\') for x in name.split(os.sep)):
2104 parser.error('Do not include relative path components in --name.')
2105
nsylvain@google.comefc80932011-05-31 21:27:56 +00002106 deps_file = options.deps_file
agablea98a6cd2016-11-15 14:30:10 -08002107 client.SetDefaultConfig(name, deps_file, base_url,
smutae7ea312016-07-18 11:59:41 -07002108 managed=not options.unmanaged,
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00002109 cache_dir=options.cache_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002110 client.SaveConfig()
maruel@chromium.org79692d62010-05-14 18:57:13 +00002111 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002112
2113
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002114@subcommand.epilog("""Example:
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002115 gclient pack > patch.txt
2116 generate simple patch for configured client and dependences
2117""")
2118def CMDpack(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002119 """Generates a patch which can be applied at the root of the tree.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00002120
agabled437d762016-10-17 09:35:11 -07002121 Internally, runs 'git diff' on each checked out module and
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002122 dependencies, and performs minimal postprocessing of the output. The
2123 resulting patch is printed to stdout and can be applied to a freshly
2124 checked out tree via 'patch -p0 < patchfile'.
2125 """
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002126 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2127 help='override deps for the specified (comma-separated) '
2128 'platform(s); \'all\' will process all deps_os '
2129 'references')
haitao.feng@intel.com306080c2012-05-04 13:11:29 +00002130 parser.remove_option('--jobs')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002131 (options, args) = parser.parse_args(args)
iannucci@chromium.org50395ea2013-04-04 04:47:42 +00002132 # Force jobs to 1 so the stdout is not annotated with the thread ids
haitao.feng@intel.com306080c2012-05-04 13:11:29 +00002133 options.jobs = 1
kbr@google.comab318592009-09-04 00:54:55 +00002134 client = GClient.LoadCurrentConfig(options)
2135 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002136 raise gclient_utils.Error('client not configured; see \'gclient config\'')
kbr@google.comab318592009-09-04 00:54:55 +00002137 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002138 client.PrintLocationAndContents()
kbr@google.comab318592009-09-04 00:54:55 +00002139 return client.RunOnDeps('pack', args)
2140
2141
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002142def CMDstatus(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002143 """Shows modification status for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002144 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2145 help='override deps for the specified (comma-separated) '
2146 'platform(s); \'all\' will process all deps_os '
2147 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002148 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002149 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002150 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002151 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002152 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002153 client.PrintLocationAndContents()
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002154 return client.RunOnDeps('status', args)
2155
2156
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002157@subcommand.epilog("""Examples:
maruel@chromium.org79692d62010-05-14 18:57:13 +00002158 gclient sync
2159 update files from SCM according to current configuration,
2160 *for modules which have changed since last update or sync*
2161 gclient sync --force
2162 update files from SCM according to current configuration, for
2163 all modules (useful for recovering files deleted from local copy)
2164 gclient sync --revision src@31000
2165 update src directory to r31000
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002166
2167JSON output format:
2168If the --output-json option is specified, the following document structure will
2169be emitted to the provided file. 'null' entries may occur for subprojects which
2170are present in the gclient solution, but were not processed (due to custom_deps,
2171os_deps, etc.)
2172
2173{
2174 "solutions" : {
2175 "<name>": { # <name> is the posix-normalized path to the solution.
agabled437d762016-10-17 09:35:11 -07002176 "revision": [<git id hex string>|null],
2177 "scm": ["git"|null],
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002178 }
2179 }
2180}
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002181""")
2182def CMDsync(parser, args):
2183 """Checkout/update all modules."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002184 parser.add_option('-f', '--force', action='store_true',
2185 help='force update even for unchanged modules')
2186 parser.add_option('-n', '--nohooks', action='store_true',
2187 help='don\'t run hooks after the update is complete')
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00002188 parser.add_option('-p', '--noprehooks', action='store_true',
2189 help='don\'t run pre-DEPS hooks', default=False)
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002190 parser.add_option('-r', '--revision', action='append',
2191 dest='revisions', metavar='REV', default=[],
2192 help='Enforces revision/hash for the solutions with the '
2193 'format src@rev. The src@ part is optional and can be '
2194 'skipped. -r can be used multiple times when .gclient '
2195 'has multiple solutions configured and will work even '
agablea98a6cd2016-11-15 14:30:10 -08002196 'if the src@ part is skipped.')
maruel@chromium.org794207e2013-03-08 15:29:43 +00002197 parser.add_option('--with_branch_heads', action='store_true',
2198 help='Clone git "branch_heads" refspecs in addition to '
2199 'the default refspecs. This adds about 1/2GB to a '
2200 'full checkout. (git only)')
szager@chromium.org8d3348f2014-08-19 22:49:16 +00002201 parser.add_option('--with_tags', action='store_true',
2202 help='Clone git tags in addition to the default refspecs.')
agable2697cd12016-06-28 10:23:53 -07002203 parser.add_option('-H', '--head', action='store_true',
agablea98a6cd2016-11-15 14:30:10 -08002204 help='DEPRECATED: only made sense with safesync urls.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002205 parser.add_option('-D', '--delete_unversioned_trees', action='store_true',
steveblock@chromium.org98e69452012-02-16 16:36:43 +00002206 help='Deletes from the working copy any dependencies that '
2207 'have been removed since the last sync, as long as '
2208 'there are no local modifications. When used with '
2209 '--force, such dependencies are removed even if they '
2210 'have local modifications. When used with --reset, '
2211 'all untracked directories are removed from the '
tnagel@chromium.orga2aaa632014-02-28 21:47:27 +00002212 'working copy, excluding those which are explicitly '
steveblock@chromium.org98e69452012-02-16 16:36:43 +00002213 'ignored in the repository.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002214 parser.add_option('-R', '--reset', action='store_true',
2215 help='resets any local changes before updating (git only)')
bauerb@chromium.org2aad1b22011-07-22 12:00:41 +00002216 parser.add_option('-M', '--merge', action='store_true',
2217 help='merge upstream changes instead of trying to '
2218 'fast-forward or rebase')
dnj@chromium.org5b23e872015-02-20 21:25:57 +00002219 parser.add_option('-A', '--auto_rebase', action='store_true',
2220 help='Automatically rebase repositories against local '
2221 'checkout during update (git only).')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002222 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2223 help='override deps for the specified (comma-separated) '
2224 'platform(s); \'all\' will process all deps_os '
2225 'references')
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +00002226 parser.add_option('--upstream', action='store_true',
2227 help='Make repo state match upstream branch.')
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002228 parser.add_option('--output-json',
2229 help='Output a json document to this path containing '
2230 'summary information about the sync.')
primiano@chromium.org5439ea52014-08-06 17:18:18 +00002231 parser.add_option('--no-history', action='store_true',
2232 help='GIT ONLY - Reduces the size/time of the checkout at '
2233 'the cost of no history. Requires Git 1.9+')
hinoka@chromium.org46b87412014-05-15 00:42:05 +00002234 parser.add_option('--shallow', action='store_true',
2235 help='GIT ONLY - Do a shallow clone into the cache dir. '
2236 'Requires Git 1.9+')
e.hakkinen@samsung.come8bc1aa2015-04-08 08:00:37 +00002237 parser.add_option('--no_bootstrap', '--no-bootstrap',
2238 action='store_true',
2239 help='Don\'t bootstrap from Google Storage.')
hinoka@chromium.org8a10f6d2014-06-23 18:38:57 +00002240 parser.add_option('--ignore_locks', action='store_true',
2241 help='GIT ONLY - Ignore cache locks.')
iannucci@chromium.org30a07982016-04-07 21:35:19 +00002242 parser.add_option('--break_repo_locks', action='store_true',
2243 help='GIT ONLY - Forcibly remove repo locks (e.g. '
2244 'index.lock). This should only be used if you know for '
2245 'certain that this invocation of gclient is the only '
2246 'thing operating on the git repos (e.g. on a bot).')
nodir@chromium.org5b48e482016-03-18 20:27:54 +00002247 parser.add_option('--lock_timeout', type='int', default=5000,
szager@chromium.orgdbb6f822016-02-02 22:59:30 +00002248 help='GIT ONLY - Deadline (in seconds) to wait for git '
nodir@chromium.org5b48e482016-03-18 20:27:54 +00002249 'cache lock to become available. Default is %default.')
agabled437d762016-10-17 09:35:11 -07002250 # TODO(agable): Remove these when the oldest CrOS release milestone is M56.
2251 parser.add_option('-t', '--transitive', action='store_true',
2252 help='DEPRECATED: This is a no-op.')
sdefresne69b1be12016-10-18 05:48:02 -07002253 parser.add_option('-m', '--manually_grab_svn_rev', action='store_true',
agabled437d762016-10-17 09:35:11 -07002254 help='DEPRECATED: This is a no-op.')
Paweł Hajdan, Jr7c7b5592017-05-23 15:06:05 +02002255 # TODO(phajdan.jr): Remove validation options once default (crbug/570091).
Paweł Hajdan, Jr694773d2017-05-29 16:06:23 +02002256 parser.add_option('--validate-syntax', action='store_true', default=True,
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +02002257 help='Validate the .gclient and DEPS syntax')
Paweł Hajdan, Jr7c7b5592017-05-23 15:06:05 +02002258 parser.add_option('--disable-syntax-validation', action='store_false',
2259 dest='validate_syntax',
2260 help='Disable validation of .gclient and DEPS syntax.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002261 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002262 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002263
2264 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002265 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002266
smutae7ea312016-07-18 11:59:41 -07002267 if options.revisions and options.head:
2268 # TODO(maruel): Make it a parser.error if it doesn't break any builder.
2269 print('Warning: you cannot use both --head and --revision')
2270
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002271 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002272 client.PrintLocationAndContents()
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002273 ret = client.RunOnDeps('update', args)
2274 if options.output_json:
2275 slns = {}
2276 for d in client.subtree(True):
2277 normed = d.name.replace('\\', '/').rstrip('/') + '/'
2278 slns[normed] = {
2279 'revision': d.got_revision,
2280 'scm': d.used_scm.name if d.used_scm else None,
hinoka@chromium.org17db9052014-05-10 01:11:29 +00002281 'url': str(d.url) if d.url else None,
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002282 }
2283 with open(options.output_json, 'wb') as f:
2284 json.dump({'solutions': slns}, f)
2285 return ret
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002286
2287
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002288CMDupdate = CMDsync
2289
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002290
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +02002291def CMDvalidate(parser, args):
2292 """Validates the .gclient and DEPS syntax."""
2293 options, args = parser.parse_args(args)
2294 options.validate_syntax = True
2295 client = GClient.LoadCurrentConfig(options)
2296 rv = client.RunOnDeps('validate', args)
2297 if rv == 0:
2298 print('validate: SUCCESS')
2299 else:
2300 print('validate: FAILURE')
2301 return rv
2302
2303
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002304def CMDdiff(parser, args):
2305 """Displays local diff for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002306 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2307 help='override deps for the specified (comma-separated) '
2308 'platform(s); \'all\' will process all deps_os '
2309 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002310 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002311 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002312 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002313 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002314 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002315 client.PrintLocationAndContents()
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002316 return client.RunOnDeps('diff', args)
2317
2318
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002319def CMDrevert(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002320 """Reverts all modifications in every dependencies.
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00002321
2322 That's the nuclear option to get back to a 'clean' state. It removes anything
agabled437d762016-10-17 09:35:11 -07002323 that shows up in git status."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002324 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2325 help='override deps for the specified (comma-separated) '
2326 'platform(s); \'all\' will process all deps_os '
2327 'references')
2328 parser.add_option('-n', '--nohooks', action='store_true',
2329 help='don\'t run hooks after the revert is complete')
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00002330 parser.add_option('-p', '--noprehooks', action='store_true',
2331 help='don\'t run pre-DEPS hooks', default=False)
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +00002332 parser.add_option('--upstream', action='store_true',
2333 help='Make repo state match upstream branch.')
iannucci@chromium.orgbf525dc2016-04-07 22:00:28 +00002334 parser.add_option('--break_repo_locks', action='store_true',
2335 help='GIT ONLY - Forcibly remove repo locks (e.g. '
2336 'index.lock). This should only be used if you know for '
2337 'certain that this invocation of gclient is the only '
2338 'thing operating on the git repos (e.g. on a bot).')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002339 (options, args) = parser.parse_args(args)
2340 # --force is implied.
2341 options.force = True
steveblock@chromium.org98e69452012-02-16 16:36:43 +00002342 options.reset = False
2343 options.delete_unversioned_trees = False
agablec903d732016-07-26 09:07:24 -07002344 options.merge = False
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002345 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002346 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002347 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002348 return client.RunOnDeps('revert', args)
2349
2350
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002351def CMDrunhooks(parser, args):
2352 """Runs hooks for files that have been modified in the local working copy."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002353 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2354 help='override deps for the specified (comma-separated) '
2355 'platform(s); \'all\' will process all deps_os '
2356 'references')
2357 parser.add_option('-f', '--force', action='store_true', default=True,
2358 help='Deprecated. No effect.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002359 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002360 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002361 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002362 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002363 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002364 client.PrintLocationAndContents()
maruel@chromium.org5df6a462009-08-28 18:52:26 +00002365 options.force = True
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002366 options.nohooks = False
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002367 return client.RunOnDeps('runhooks', args)
2368
2369
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002370def CMDrevinfo(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002371 """Outputs revision info mapping for the client and its dependencies.
maruel@chromium.org9eda4112010-06-11 18:56:10 +00002372
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002373 This allows the capture of an overall 'revision' for the source tree that
maruel@chromium.org9eda4112010-06-11 18:56:10 +00002374 can be used to reproduce the same tree in the future. It is only useful for
agabled437d762016-10-17 09:35:11 -07002375 'unpinned dependencies', i.e. DEPS/deps references without a git hash.
2376 A git branch name isn't 'pinned' since the actual commit can change.
maruel@chromium.org9eda4112010-06-11 18:56:10 +00002377 """
2378 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2379 help='override deps for the specified (comma-separated) '
2380 'platform(s); \'all\' will process all deps_os '
2381 'references')
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00002382 parser.add_option('-a', '--actual', action='store_true',
2383 help='gets the actual checked out revisions instead of the '
2384 'ones specified in the DEPS and .gclient files')
maruel@chromium.org9eda4112010-06-11 18:56:10 +00002385 parser.add_option('-s', '--snapshot', action='store_true',
2386 help='creates a snapshot .gclient file of the current '
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00002387 'version of all repositories to reproduce the tree, '
2388 'implies -a')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002389 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002390 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002391 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002392 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002393 client.PrintRevInfo()
maruel@chromium.org79692d62010-05-14 18:57:13 +00002394 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002395
2396
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00002397def CMDverify(parser, args):
2398 """Verifies the DEPS file deps are only from allowed_hosts."""
2399 (options, args) = parser.parse_args(args)
2400 client = GClient.LoadCurrentConfig(options)
2401 if not client:
2402 raise gclient_utils.Error('client not configured; see \'gclient config\'')
2403 client.RunOnDeps(None, [])
2404 # Look at each first-level dependency of this gclient only.
2405 for dep in client.dependencies:
2406 bad_deps = dep.findDepsFromNotAllowedHosts()
2407 if not bad_deps:
2408 continue
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002409 print("There are deps from not allowed hosts in file %s" % dep.deps_file)
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00002410 for bad_dep in bad_deps:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002411 print("\t%s at %s" % (bad_dep.name, bad_dep.url))
2412 print("allowed_hosts:", ', '.join(dep.allowed_hosts))
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00002413 sys.stdout.flush()
2414 raise gclient_utils.Error(
2415 'dependencies from disallowed hosts; check your DEPS file.')
2416 return 0
2417
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002418class OptionParser(optparse.OptionParser):
szager@chromium.orge2e03202012-07-31 18:05:16 +00002419 gclientfile_default = os.environ.get('GCLIENT_FILE', '.gclient')
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002420
2421 def __init__(self, **kwargs):
2422 optparse.OptionParser.__init__(
2423 self, version='%prog ' + __version__, **kwargs)
2424
2425 # Some arm boards have issues with parallel sync.
2426 if platform.machine().startswith('arm'):
2427 jobs = 1
2428 else:
2429 jobs = max(8, gclient_utils.NumLocalCpus())
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002430
2431 self.add_option(
2432 '-j', '--jobs', default=jobs, type='int',
2433 help='Specify how many SCM commands can run in parallel; defaults to '
tnagel@chromium.orga2aaa632014-02-28 21:47:27 +00002434 '%default on this machine')
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002435 self.add_option(
2436 '-v', '--verbose', action='count', default=0,
2437 help='Produces additional output for diagnostics. Can be used up to '
2438 'three times for more logging info.')
2439 self.add_option(
2440 '--gclientfile', dest='config_filename',
2441 help='Specify an alternate %s file' % self.gclientfile_default)
2442 self.add_option(
2443 '--spec',
2444 help='create a gclient file containing the provided string. Due to '
2445 'Cygwin/Python brokenness, it can\'t contain any newlines.')
2446 self.add_option(
2447 '--no-nag-max', default=False, action='store_true',
scottmg@chromium.orgf547c802013-09-27 17:55:26 +00002448 help='Ignored for backwards compatibility.')
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002449
2450 def parse_args(self, args=None, values=None):
2451 """Integrates standard options processing."""
2452 options, args = optparse.OptionParser.parse_args(self, args, values)
2453 levels = [logging.ERROR, logging.WARNING, logging.INFO, logging.DEBUG]
2454 logging.basicConfig(
2455 level=levels[min(options.verbose, len(levels) - 1)],
maruel@chromium.org0895b752011-08-26 20:40:33 +00002456 format='%(module)s(%(lineno)d) %(funcName)s:%(message)s')
szager@chromium.orge2e03202012-07-31 18:05:16 +00002457 if options.config_filename and options.spec:
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002458 self.error('Cannot specifiy both --gclientfile and --spec')
rdsmith@chromium.orgd9591f02014-02-05 19:28:20 +00002459 if (options.config_filename and
2460 options.config_filename != os.path.basename(options.config_filename)):
2461 self.error('--gclientfile target must be a filename, not a path')
szager@chromium.orge2e03202012-07-31 18:05:16 +00002462 if not options.config_filename:
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002463 options.config_filename = self.gclientfile_default
maruel@chromium.org0895b752011-08-26 20:40:33 +00002464 options.entries_filename = options.config_filename + '_entries'
2465 if options.jobs < 1:
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002466 self.error('--jobs must be 1 or higher')
maruel@chromium.org0895b752011-08-26 20:40:33 +00002467
2468 # These hacks need to die.
2469 if not hasattr(options, 'revisions'):
2470 # GClient.RunOnDeps expects it even if not applicable.
2471 options.revisions = []
smutae7ea312016-07-18 11:59:41 -07002472 if not hasattr(options, 'head'):
2473 options.head = None
maruel@chromium.org0895b752011-08-26 20:40:33 +00002474 if not hasattr(options, 'nohooks'):
2475 options.nohooks = True
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00002476 if not hasattr(options, 'noprehooks'):
2477 options.noprehooks = True
maruel@chromium.org0895b752011-08-26 20:40:33 +00002478 if not hasattr(options, 'deps_os'):
2479 options.deps_os = None
maruel@chromium.org0895b752011-08-26 20:40:33 +00002480 if not hasattr(options, 'force'):
2481 options.force = None
2482 return (options, args)
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002483
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002484
2485def disable_buffering():
2486 # Make stdout auto-flush so buildbot doesn't kill us during lengthy
2487 # operations. Python as a strong tendency to buffer sys.stdout.
2488 sys.stdout = gclient_utils.MakeFileAutoFlush(sys.stdout)
2489 # Make stdout annotated with the thread ids.
2490 sys.stdout = gclient_utils.MakeFileAnnotated(sys.stdout)
maruel@chromium.org0895b752011-08-26 20:40:33 +00002491
2492
sbc@chromium.org013731e2015-02-26 18:28:43 +00002493def main(argv):
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002494 """Doesn't parse the arguments here, just find the right subcommand to
2495 execute."""
maruel@chromium.org82798cb2012-02-23 18:16:12 +00002496 if sys.hexversion < 0x02060000:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002497 print(
maruel@chromium.org82798cb2012-02-23 18:16:12 +00002498 '\nYour python version %s is unsupported, please upgrade.\n' %
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002499 sys.version.split(' ', 1)[0],
2500 file=sys.stderr)
maruel@chromium.org82798cb2012-02-23 18:16:12 +00002501 return 2
bcwhite@chromium.org6683ab42013-02-11 16:13:47 +00002502 if not sys.executable:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002503 print(
2504 '\nPython cannot find the location of it\'s own executable.\n',
2505 file=sys.stderr)
bcwhite@chromium.org6683ab42013-02-11 16:13:47 +00002506 return 2
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002507 fix_encoding.fix_encoding()
2508 disable_buffering()
iannucci@chromium.org596cd5c2016-04-04 21:34:39 +00002509 setup_color.init()
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002510 dispatcher = subcommand.CommandDispatcher(__name__)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00002511 try:
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002512 return dispatcher.execute(OptionParser(), argv)
xusydoc@chromium.org2fd6c3f2013-05-03 21:57:55 +00002513 except KeyboardInterrupt:
2514 gclient_utils.GClientChildren.KillAllRemainingChildren()
2515 raise
vapier@chromium.orga81a56e2015-11-11 07:56:13 +00002516 except (gclient_utils.Error, subprocess2.CalledProcessError) as e:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002517 print('Error: %s' % str(e), file=sys.stderr)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00002518 return 1
borenet@google.com6a9b1682014-03-24 18:35:23 +00002519 finally:
2520 gclient_utils.PrintWarnings()
sbc@chromium.org013731e2015-02-26 18:28:43 +00002521 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002522
2523
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00002524if '__main__' == __name__:
sbc@chromium.org013731e2015-02-26 18:28:43 +00002525 try:
2526 sys.exit(main(sys.argv[1:]))
2527 except KeyboardInterrupt:
2528 sys.stderr.write('interrupted\n')
2529 sys.exit(1)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002530
2531# vim: ts=2:sw=2:tw=80:et: