blob: 72c1e0484a24cc63c55fbf897f1bbce0633cc6e6 [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, Jr57253732017-06-06 23:49:11 +0200376 self._vars = {}
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +0200377 self._os_dependencies = {}
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200378
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000379 # A cache of the files affected by the current operation, necessary for
380 # hooks.
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000381 self._file_list = []
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000382 # List of host names from which dependencies are allowed.
383 # Default is an empty set, meaning unspecified in DEPS file, and hence all
384 # hosts will be allowed. Non-empty set means whitelist of hosts.
385 # allowed_hosts var is scoped to its DEPS file, and so it isn't recursive.
386 self._allowed_hosts = frozenset()
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +0200387 # Spec for .gni output to write (if any).
388 self._gn_args_file = None
389 self._gn_args = []
maruel@chromium.org85c2a192010-07-22 21:14:43 +0000390 # If it is not set to True, the dependency wasn't processed for its child
391 # dependency, i.e. its DEPS wasn't read.
maruel@chromium.org064186c2011-09-27 23:53:33 +0000392 self._deps_parsed = False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000393 # This dependency has been processed, i.e. checked out
maruel@chromium.org064186c2011-09-27 23:53:33 +0000394 self._processed = False
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000395 # This dependency had its pre-DEPS hooks run
396 self._pre_deps_hooks_ran = False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000397 # This dependency had its hook run
maruel@chromium.org064186c2011-09-27 23:53:33 +0000398 self._hooks_ran = False
kustermann@google.coma692e8f2013-04-18 08:32:04 +0000399 # This is the scm used to checkout self.url. It may be used by dependencies
400 # to get the datetime of the revision we checked out.
401 self._used_scm = None
szager@chromium.org4ad264b2014-05-20 04:43:47 +0000402 self._used_revision = None
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +0000403 # The actual revision we ended up getting, or None if that information is
404 # unavailable
405 self._got_revision = None
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000406
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000407 # This is a mutable value that overrides the normal recursion limit for this
408 # dependency. It is read from the actual DEPS file so cannot be set on
409 # class instantiation.
410 self.recursion_override = None
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000411 # recursedeps is a mutable value that selectively overrides the default
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000412 # 'no recursion' setting on a dep-by-dep basis. It will replace
413 # recursion_override.
iannucci@chromium.orgafa11ac2016-05-04 22:17:34 +0000414 #
415 # It will be a dictionary of {deps_name: {"deps_file": depfile_name}} or
416 # None.
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000417 self.recursedeps = None
hinoka885e5b12016-06-08 14:40:09 -0700418 # This is inherited from WorkItem. We want the URL to be a resource.
419 if url and isinstance(url, basestring):
420 # The url is usually given to gclient either as https://blah@123
qyearsley12fa6ff2016-08-24 09:18:40 -0700421 # or just https://blah. The @123 portion is irrelevant.
hinoka885e5b12016-06-08 14:40:09 -0700422 self.resources.append(url.split('@')[0])
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000423
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000424 if not self.name and self.parent:
425 raise gclient_utils.Error('Dependency without name')
426
maruel@chromium.org470b5432011-10-11 18:18:19 +0000427 @property
428 def requirements(self):
429 """Calculate the list of requirements."""
430 requirements = set()
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000431 # self.parent is implicitly a requirement. This will be recursive by
432 # definition.
433 if self.parent and self.parent.name:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000434 requirements.add(self.parent.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000435
436 # For a tree with at least 2 levels*, the leaf node needs to depend
437 # on the level higher up in an orderly way.
438 # This becomes messy for >2 depth as the DEPS file format is a dictionary,
439 # thus unsorted, while the .gclient format is a list thus sorted.
440 #
441 # * _recursion_limit is hard coded 2 and there is no hope to change this
442 # value.
443 #
444 # Interestingly enough, the following condition only works in the case we
445 # want: self is a 2nd level node. 3nd level node wouldn't need this since
446 # they already have their parent as a requirement.
maruel@chromium.org470b5432011-10-11 18:18:19 +0000447 if self.parent and self.parent.parent and not self.parent.parent.parent:
448 requirements |= set(i.name for i in self.root.dependencies if i.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000449
maruel@chromium.org470b5432011-10-11 18:18:19 +0000450 if self.name:
451 requirements |= set(
452 obj.name for obj in self.root.subtree(False)
453 if (obj is not self
454 and obj.name and
455 self.name.startswith(posixpath.join(obj.name, ''))))
456 requirements = tuple(sorted(requirements))
457 logging.info('Dependency(%s).requirements = %s' % (self.name, requirements))
458 return requirements
459
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000460 @property
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000461 def try_recursedeps(self):
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000462 """Returns False if recursion_override is ever specified."""
463 if self.recursion_override is not None:
464 return False
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000465 return self.parent.try_recursedeps
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000466
467 @property
468 def recursion_limit(self):
469 """Returns > 0 if this dependency is not too recursed to be processed."""
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000470 # We continue to support the absence of recursedeps until tools and DEPS
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000471 # using recursion_override are updated.
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000472 if self.try_recursedeps and self.parent.recursedeps != None:
473 if self.name in self.parent.recursedeps:
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000474 return 1
475
476 if self.recursion_override is not None:
477 return self.recursion_override
478 return max(self.parent.recursion_limit - 1, 0)
479
maruel@chromium.org470b5432011-10-11 18:18:19 +0000480 def verify_validity(self):
481 """Verifies that this Dependency is fine to add as a child of another one.
482
483 Returns True if this entry should be added, False if it is a duplicate of
484 another entry.
485 """
486 logging.info('Dependency(%s).verify_validity()' % self.name)
487 if self.name in [s.name for s in self.parent.dependencies]:
488 raise gclient_utils.Error(
489 'The same name "%s" appears multiple times in the deps section' %
490 self.name)
491 if not self.should_process:
492 # Return early, no need to set requirements.
493 return True
494
495 # This require a full tree traversal with locks.
496 siblings = [d for d in self.root.subtree(False) if d.name == self.name]
497 for sibling in siblings:
maruel@chromium.orgb848d5b2012-10-10 23:25:50 +0000498 self_url = self.LateOverride(self.url)
499 sibling_url = sibling.LateOverride(sibling.url)
500 # Allow to have only one to be None or ''.
501 if self_url != sibling_url and bool(self_url) == bool(sibling_url):
maruel@chromium.org470b5432011-10-11 18:18:19 +0000502 raise gclient_utils.Error(
maruel@chromium.orgb848d5b2012-10-10 23:25:50 +0000503 ('Dependency %s specified more than once:\n'
504 ' %s [%s]\n'
505 'vs\n'
506 ' %s [%s]') % (
507 self.name,
508 sibling.hierarchy(),
509 sibling_url,
510 self.hierarchy(),
511 self_url))
maruel@chromium.org470b5432011-10-11 18:18:19 +0000512 # In theory we could keep it as a shadow of the other one. In
513 # practice, simply ignore it.
514 logging.warn('Won\'t process duplicate dependency %s' % sibling)
515 return False
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000516 return True
maruel@chromium.org064186c2011-09-27 23:53:33 +0000517
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000518 def LateOverride(self, url):
Paweł Hajdan, Jr7e9303b2017-05-23 14:38:27 +0200519 """Resolves the parsed url from url."""
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000520 assert self.parsed_url == None or not self.should_process, self.parsed_url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000521 parsed_url = self.get_custom_deps(self.name, url)
522 if parsed_url != url:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000523 logging.info(
524 'Dependency(%s).LateOverride(%s) -> %s' %
525 (self.name, url, parsed_url))
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000526 return parsed_url
527
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000528 if isinstance(url, basestring):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000529 parsed_url = urlparse.urlparse(url)
scr@chromium.orgf1eccaf2014-04-11 15:51:33 +0000530 if (not parsed_url[0] and
531 not re.match(r'^\w+\@[\w\.-]+\:[\w\/]+', parsed_url[2])):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000532 # A relative url. Fetch the real base.
533 path = parsed_url[2]
534 if not path.startswith('/'):
535 raise gclient_utils.Error(
536 'relative DEPS entry \'%s\' must begin with a slash' % url)
537 # Create a scm just to query the full url.
538 parent_url = self.parent.parsed_url
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000539 scm = gclient_scm.CreateSCM(
540 parent_url, self.root.root_dir, None, self.outbuf)
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000541 parsed_url = scm.FullUrlForRelativeUrl(url)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000542 else:
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000543 parsed_url = url
maruel@chromium.org470b5432011-10-11 18:18:19 +0000544 logging.info(
545 'Dependency(%s).LateOverride(%s) -> %s' %
546 (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000547 return parsed_url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000548
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000549 if url is None:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000550 logging.info(
551 'Dependency(%s).LateOverride(%s) -> %s' % (self.name, url, url))
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000552 return url
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000553
554 raise gclient_utils.Error('Unknown url type')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000555
bratell@opera.comed2b4fe2013-12-16 14:34:12 +0000556 @staticmethod
557 def MergeWithOsDeps(deps, deps_os, target_os_list):
558 """Returns a new "deps" structure that is the deps sent in updated
559 with information from deps_os (the deps_os section of the DEPS
560 file) that matches the list of target os."""
bratell@opera.comed2b4fe2013-12-16 14:34:12 +0000561 new_deps = deps.copy()
Paweł Hajdan, Jrfd0057e2017-06-21 14:20:21 +0200562 for dep_os, os_deps in deps_os.iteritems():
563 for key, value in os_deps.iteritems():
564 if value is None:
565 # Make this condition very visible, so it's not a silent failure.
566 # It's unclear how to support None override in deps_os.
567 logging.error('Ignoring %r:%r in %r deps_os', key, value, dep_os)
568 continue
569
570 # Normalize value to be a dict which contains |should_process| metadata.
571 if isinstance(value, basestring):
572 value = {'url': value}
573 assert isinstance(value, collections.Mapping), (key, value)
574 value['should_process'] = dep_os in target_os_list
575
576 # Handle collisions/overrides.
577 if key in new_deps and new_deps[key] != value:
578 # Normalize the existing new_deps entry.
579 if isinstance(new_deps[key], basestring):
580 new_deps[key] = {'url': new_deps[key]}
581 assert isinstance(new_deps[key],
582 collections.Mapping), (key, new_deps[key])
583
584 # It's OK if the "override" sets the key to the same value.
585 # This is mostly for legacy reasons to keep existing DEPS files
586 # working. Often mac/ios and unix/android will do this.
587 if value['url'] != new_deps[key]['url']:
588 raise gclient_utils.Error(
589 ('Value from deps_os (%r; %r: %r) conflicts with existing deps '
590 'entry (%r).') % (dep_os, key, value, new_deps[key]))
591
592 # We'd otherwise overwrite |should_process| metadata, but a dep should
593 # be processed if _any_ of its references call for that.
594 value['should_process'] = (
595 value['should_process'] or
596 new_deps[key].get('should_process', True))
597
598 new_deps[key] = value
599
bratell@opera.comed2b4fe2013-12-16 14:34:12 +0000600 return new_deps
601
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200602 def _postprocess_deps(self, deps, rel_prefix):
603 """Performs post-processing of deps compared to what's in the DEPS file."""
Paweł Hajdan, Jr4426eaf2017-06-13 12:25:47 +0200604 # Make sure the dict is mutable, e.g. in case it's frozen.
605 deps = dict(deps)
606
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200607 # If a line is in custom_deps, but not in the solution, we want to append
608 # this line to the solution.
609 for d in self.custom_deps:
610 if d not in deps:
611 deps[d] = self.custom_deps[d]
612
613 if rel_prefix:
614 logging.warning('use_relative_paths enabled.')
615 rel_deps = {}
616 for d, url in deps.items():
617 # normpath is required to allow DEPS to use .. in their
618 # dependency local path.
619 rel_deps[os.path.normpath(os.path.join(rel_prefix, d))] = url
620 logging.warning('Updating deps by prepending %s.', rel_prefix)
621 deps = rel_deps
622
623 return deps
624
625 def _deps_to_objects(self, deps, use_relative_paths):
626 """Convert a deps dict to a dict of Dependency objects."""
627 deps_to_add = []
628 for name, dep_value in deps.iteritems():
629 should_process = self.recursion_limit and self.should_process
630 deps_file = self.deps_file
631 if self.recursedeps is not None:
632 ent = self.recursedeps.get(name)
633 if ent is not None:
634 deps_file = ent['deps_file']
635 if dep_value is None:
636 continue
637 condition = None
638 condition_value = True
639 if isinstance(dep_value, basestring):
640 url = dep_value
641 else:
642 # This should be guaranteed by schema checking in gclient_eval.
643 assert isinstance(dep_value, collections.Mapping)
644 url = dep_value['url']
Paweł Hajdan, Jrfd0057e2017-06-21 14:20:21 +0200645 # Take into account should_process metadata set by MergeWithOsDeps.
646 should_process = (should_process and
647 dep_value.get('should_process', True))
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200648 condition = dep_value.get('condition')
649 if condition:
650 # TODO(phajdan.jr): should we also take custom vars into account?
651 condition_value = gclient_eval.EvaluateCondition(condition, self._vars)
652 should_process = should_process and condition_value
653 deps_to_add.append(Dependency(
654 self, name, url, None, None, self.custom_vars, None,
655 deps_file, should_process, use_relative_paths, condition,
656 condition_value))
657 deps_to_add.sort(key=lambda x: x.name)
658 return deps_to_add
659
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000660 def ParseDepsFile(self):
maruel@chromium.org271375b2010-06-23 19:17:38 +0000661 """Parses the DEPS file for this dependency."""
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000662 assert not self.deps_parsed
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000663 assert not self.dependencies
iannucci@chromium.org0c3f3052014-05-22 00:29:22 +0000664
665 deps_content = None
cmp@chromium.org76ce73c2014-07-02 00:13:18 +0000666
667 # First try to locate the configured deps file. If it's missing, fallback
668 # to DEPS.
669 deps_files = [self.deps_file]
670 if 'DEPS' not in deps_files:
671 deps_files.append('DEPS')
672 for deps_file in deps_files:
673 filepath = os.path.join(self.root.root_dir, self.name, deps_file)
674 if os.path.isfile(filepath):
675 logging.info(
676 'ParseDepsFile(%s): %s file found at %s', self.name, deps_file,
677 filepath)
678 break
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000679 logging.info(
cmp@chromium.org76ce73c2014-07-02 00:13:18 +0000680 'ParseDepsFile(%s): No %s file found at %s', self.name, deps_file,
681 filepath)
682
683 if os.path.isfile(filepath):
maruel@chromium.org46304292010-10-28 11:42:00 +0000684 deps_content = gclient_utils.FileRead(filepath)
cmp@chromium.org76ce73c2014-07-02 00:13:18 +0000685 logging.debug('ParseDepsFile(%s) read:\n%s', self.name, deps_content)
iannucci@chromium.org0c3f3052014-05-22 00:29:22 +0000686
687 local_scope = {}
688 if deps_content:
689 # One thing is unintuitive, vars = {} must happen before Var() use.
690 var = self.VarImpl(self.custom_vars, local_scope)
Paweł Hajdan, Jrf1587bf2017-06-20 21:19:07 +0200691 global_scope = {
692 'Var': var.Lookup,
693 'deps_os': {},
694 }
maruel@chromium.org46304292010-10-28 11:42:00 +0000695 # Eval the content.
696 try:
Paweł Hajdan, Jrc485d5a2017-06-02 12:08:09 +0200697 if self._get_option('validate_syntax', False):
698 gclient_eval.Exec(deps_content, global_scope, local_scope, filepath)
699 else:
700 exec(deps_content, global_scope, local_scope)
vapier@chromium.orga81a56e2015-11-11 07:56:13 +0000701 except SyntaxError as e:
maruel@chromium.org46304292010-10-28 11:42:00 +0000702 gclient_utils.SyntaxErrorToError(filepath, e)
iannucci@chromium.org0c3f3052014-05-22 00:29:22 +0000703
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000704 if 'allowed_hosts' in local_scope:
705 try:
706 self._allowed_hosts = frozenset(local_scope.get('allowed_hosts'))
707 except TypeError: # raised if non-iterable
708 pass
709 if not self._allowed_hosts:
710 logging.warning("allowed_hosts is specified but empty %s",
711 self._allowed_hosts)
712 raise gclient_utils.Error(
713 'ParseDepsFile(%s): allowed_hosts must be absent '
714 'or a non-empty iterable' % self.name)
715
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +0200716 self._gn_args_file = local_scope.get('gclient_gn_args_file')
717 self._gn_args = local_scope.get('gclient_gn_args', [])
718
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200719 # Since we heavily post-process things, freeze ones which should
720 # reflect original state of DEPS.
721 self._vars = gclient_utils.freeze(local_scope.get('vars', {}))
722
723 # If use_relative_paths is set in the DEPS file, regenerate
724 # the dictionary using paths relative to the directory containing
725 # the DEPS file. Also update recursedeps if use_relative_paths is
726 # enabled.
727 # If the deps file doesn't set use_relative_paths, but the parent did
728 # (and therefore set self.relative on this Dependency object), then we
729 # want to modify the deps and recursedeps by prepending the parent
730 # directory of this dependency.
731 use_relative_paths = local_scope.get('use_relative_paths', False)
732 rel_prefix = None
733 if use_relative_paths:
734 rel_prefix = self.name
735 elif self._relative:
736 rel_prefix = os.path.dirname(self.name)
737
738 deps = local_scope.get('deps', {})
739 orig_deps = gclient_utils.freeze(deps)
740 if 'recursion' in local_scope:
741 self.recursion_override = local_scope.get('recursion')
742 logging.warning(
743 'Setting %s recursion to %d.', self.name, self.recursion_limit)
744 self.recursedeps = None
745 if 'recursedeps' in local_scope:
746 self.recursedeps = {}
747 for ent in local_scope['recursedeps']:
748 if isinstance(ent, basestring):
749 self.recursedeps[ent] = {"deps_file": self.deps_file}
750 else: # (depname, depsfilename)
751 self.recursedeps[ent[0]] = {"deps_file": ent[1]}
752 logging.warning('Found recursedeps %r.', repr(self.recursedeps))
753
754 if rel_prefix:
755 logging.warning('Updating recursedeps by prepending %s.', rel_prefix)
756 rel_deps = {}
757 for depname, options in self.recursedeps.iteritems():
758 rel_deps[
759 os.path.normpath(os.path.join(rel_prefix, depname))] = options
760 self.recursedeps = rel_deps
761
762 # If present, save 'target_os' in the local_target_os property.
763 if 'target_os' in local_scope:
764 self.local_target_os = local_scope['target_os']
765 # load os specific dependencies if defined. these dependencies may
766 # override or extend the values defined by the 'deps' member.
767 target_os_list = self.target_os
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +0200768 if 'deps_os' in local_scope:
769 for dep_os, os_deps in local_scope['deps_os'].iteritems():
770 self._os_dependencies[dep_os] = self._deps_to_objects(
771 self._postprocess_deps(os_deps, rel_prefix), use_relative_paths)
772 if target_os_list:
773 deps = self.MergeWithOsDeps(
774 deps, local_scope['deps_os'], target_os_list)
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200775
776 deps_to_add = self._deps_to_objects(
777 self._postprocess_deps(deps, rel_prefix), use_relative_paths)
778 orig_deps_to_add = self._deps_to_objects(
779 self._postprocess_deps(orig_deps, rel_prefix), use_relative_paths)
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000780
781 # override named sets of hooks by the custom hooks
782 hooks_to_run = []
783 hook_names_to_suppress = [c.get('name', '') for c in self.custom_hooks]
784 for hook in local_scope.get('hooks', []):
785 if hook.get('name', '') not in hook_names_to_suppress:
786 hooks_to_run.append(hook)
Scott Grahamc4826742017-05-11 16:59:23 -0700787 if 'hooks_os' in local_scope and target_os_list:
788 hooks_os = local_scope['hooks_os']
789 # Specifically append these to ensure that hooks_os run after hooks.
790 for the_target_os in target_os_list:
791 the_target_os_hooks = hooks_os.get(the_target_os, [])
792 hooks_to_run.extend(the_target_os_hooks)
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000793
794 # add the replacements and any additions
795 for hook in self.custom_hooks:
796 if 'action' in hook:
797 hooks_to_run.append(hook)
798
Dirk Prankeda3a29e2017-02-27 15:29:36 -0800799 if self.recursion_limit:
Paweł Hajdan, Jr032d5452017-06-22 20:43:53 +0200800 self._pre_deps_hooks = [
801 Hook.from_dict(hook, variables=self._vars) for hook in
802 local_scope.get('pre_deps_hooks', [])]
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000803
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200804 self.add_dependencies_and_close(
805 deps_to_add, hooks_to_run, orig_deps_to_add=orig_deps_to_add)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000806 logging.info('ParseDepsFile(%s) done' % self.name)
807
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +0200808 def _get_option(self, attr, default):
809 obj = self
810 while not hasattr(obj, '_options'):
811 obj = obj.parent
812 return getattr(obj._options, attr, default)
813
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200814 def add_dependencies_and_close(
815 self, deps_to_add, hooks, orig_deps_to_add=None):
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000816 """Adds the dependencies, hooks and mark the parsing as done."""
maruel@chromium.orgb9be0652011-10-14 18:05:40 +0000817 for dep in deps_to_add:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000818 if dep.verify_validity():
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000819 self.add_dependency(dep)
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200820 for dep in (orig_deps_to_add or deps_to_add):
821 self.add_orig_dependency(dep)
Paweł Hajdan, Jr032d5452017-06-22 20:43:53 +0200822 self._mark_as_parsed(
823 [Hook.from_dict(h, variables=self._vars) for h in hooks])
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000824
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000825 def findDepsFromNotAllowedHosts(self):
826 """Returns a list of depenecies from not allowed hosts.
827
828 If allowed_hosts is not set, allows all hosts and returns empty list.
829 """
830 if not self._allowed_hosts:
831 return []
832 bad_deps = []
833 for dep in self._dependencies:
szager@chromium.orgbd772dd2014-11-05 18:43:08 +0000834 # Don't enforce this for custom_deps.
835 if dep.name in self._custom_deps:
836 continue
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000837 if isinstance(dep.url, basestring):
838 parsed_url = urlparse.urlparse(dep.url)
839 if parsed_url.netloc and parsed_url.netloc not in self._allowed_hosts:
840 bad_deps.append(dep)
841 return bad_deps
842
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +0000843 # Arguments number differs from overridden method
Quinten Yearsleyb2cc4a92016-12-15 13:53:26 -0800844 # pylint: disable=arguments-differ
maruel@chromium.org3742c842010-09-09 19:27:14 +0000845 def run(self, revision_overrides, command, args, work_queue, options):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000846 """Runs |command| then parse the DEPS file."""
maruel@chromium.org470b5432011-10-11 18:18:19 +0000847 logging.info('Dependency(%s).run()' % self.name)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000848 assert self._file_list == []
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000849 if not self.should_process:
850 return
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000851 # When running runhooks, there's no need to consult the SCM.
852 # All known hooks are expected to run unconditionally regardless of working
853 # copy state, so skip the SCM status check.
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +0200854 run_scm = command not in (
855 'flatten', 'runhooks', 'recurse', 'validate', None)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000856 parsed_url = self.LateOverride(self.url)
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000857 file_list = [] if not options.nohooks else None
szager@chromium.org3a3608d2014-10-22 21:13:52 +0000858 revision_override = revision_overrides.pop(self.name, None)
Dave Tubbda9712017-06-01 15:10:53 -0700859 if not revision_override and parsed_url:
860 revision_override = revision_overrides.get(parsed_url.split('@')[0], None)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000861 if run_scm and parsed_url:
agabled437d762016-10-17 09:35:11 -0700862 # Create a shallow copy to mutate revision.
863 options = copy.copy(options)
864 options.revision = revision_override
865 self._used_revision = options.revision
866 self._used_scm = gclient_scm.CreateSCM(
867 parsed_url, self.root.root_dir, self.name, self.outbuf,
868 out_cb=work_queue.out_cb)
869 self._got_revision = self._used_scm.RunCommand(command, options, args,
870 file_list)
871 if file_list:
872 file_list = [os.path.join(self.name, f.strip()) for f in file_list]
maruel@chromium.org68988972011-09-20 14:11:42 +0000873
874 # TODO(phajdan.jr): We should know exactly when the paths are absolute.
875 # Convert all absolute paths to relative.
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000876 for i in range(len(file_list or [])):
maruel@chromium.org68988972011-09-20 14:11:42 +0000877 # It depends on the command being executed (like runhooks vs sync).
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000878 if not os.path.isabs(file_list[i]):
maruel@chromium.org68988972011-09-20 14:11:42 +0000879 continue
880 prefix = os.path.commonprefix(
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000881 [self.root.root_dir.lower(), file_list[i].lower()])
882 file_list[i] = file_list[i][len(prefix):]
maruel@chromium.org68988972011-09-20 14:11:42 +0000883 # Strip any leading path separators.
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000884 while file_list[i].startswith(('\\', '/')):
885 file_list[i] = file_list[i][1:]
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000886
887 # Always parse the DEPS file.
888 self.ParseDepsFile()
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +0200889 if self._gn_args_file and command == 'update':
890 self.WriteGNArgsFile()
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000891 self._run_is_done(file_list or [], parsed_url)
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000892 if command in ('update', 'revert') and not options.noprehooks:
893 self.RunPreDepsHooks()
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000894
895 if self.recursion_limit:
896 # Parse the dependencies of this dependency.
897 for s in self.dependencies:
898 work_queue.enqueue(s)
899
900 if command == 'recurse':
agabled437d762016-10-17 09:35:11 -0700901 # Skip file only checkout.
902 scm = gclient_scm.GetScmName(parsed_url)
903 if not options.scm or scm in options.scm:
904 cwd = os.path.normpath(os.path.join(self.root.root_dir, self.name))
905 # Pass in the SCM type as an env variable. Make sure we don't put
906 # unicode strings in the environment.
907 env = os.environ.copy()
908 if scm:
909 env['GCLIENT_SCM'] = str(scm)
910 if parsed_url:
911 env['GCLIENT_URL'] = str(parsed_url)
912 env['GCLIENT_DEP_PATH'] = str(self.name)
913 if options.prepend_dir and scm == 'git':
914 print_stdout = False
915 def filter_fn(line):
916 """Git-specific path marshaling. It is optimized for git-grep."""
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000917
agabled437d762016-10-17 09:35:11 -0700918 def mod_path(git_pathspec):
919 match = re.match('^(\\S+?:)?([^\0]+)$', git_pathspec)
920 modified_path = os.path.join(self.name, match.group(2))
921 branch = match.group(1) or ''
922 return '%s%s' % (branch, modified_path)
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000923
agabled437d762016-10-17 09:35:11 -0700924 match = re.match('^Binary file ([^\0]+) matches$', line)
925 if match:
926 print('Binary file %s matches\n' % mod_path(match.group(1)))
927 return
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000928
agabled437d762016-10-17 09:35:11 -0700929 items = line.split('\0')
930 if len(items) == 2 and items[1]:
931 print('%s : %s' % (mod_path(items[0]), items[1]))
932 elif len(items) >= 2:
933 # Multiple null bytes or a single trailing null byte indicate
934 # git is likely displaying filenames only (such as with -l)
935 print('\n'.join(mod_path(path) for path in items if path))
936 else:
937 print(line)
938 else:
939 print_stdout = True
940 filter_fn = None
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000941
agabled437d762016-10-17 09:35:11 -0700942 if parsed_url is None:
943 print('Skipped omitted dependency %s' % cwd, file=sys.stderr)
944 elif os.path.isdir(cwd):
945 try:
946 gclient_utils.CheckCallAndFilter(
947 args, cwd=cwd, env=env, print_stdout=print_stdout,
948 filter_fn=filter_fn,
949 )
950 except subprocess2.CalledProcessError:
951 if not options.ignore:
952 raise
953 else:
954 print('Skipped missing %s' % cwd, file=sys.stderr)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000955
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +0200956 def WriteGNArgsFile(self):
957 lines = ['# Generated from %r' % self.deps_file]
958 for arg in self._gn_args:
959 lines.append('%s = %s' % (arg, ToGNString(self._vars[arg])))
960 with open(os.path.join(self.root.root_dir, self._gn_args_file), 'w') as f:
961 f.write('\n'.join(lines))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000962
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000963 @gclient_utils.lockedmethod
964 def _run_is_done(self, file_list, parsed_url):
965 # Both these are kept for hooks that are run as a separate tree traversal.
966 self._file_list = file_list
967 self._parsed_url = parsed_url
968 self._processed = True
969
szager@google.comb9a78d32012-03-13 18:46:21 +0000970 def GetHooks(self, options):
971 """Evaluates all hooks, and return them in a flat list.
972
973 RunOnDeps() must have been called before to load the DEPS.
974 """
975 result = []
maruel@chromium.org68988972011-09-20 14:11:42 +0000976 if not self.should_process or not self.recursion_limit:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000977 # Don't run the hook when it is above recursion_limit.
szager@google.comb9a78d32012-03-13 18:46:21 +0000978 return result
maruel@chromium.orgdc7445d2010-07-09 21:05:29 +0000979 # If "--force" was specified, run all hooks regardless of what files have
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000980 # changed.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000981 if self.deps_hooks:
agabled437d762016-10-17 09:35:11 -0700982 # TODO(maruel): If the user is using git, then we don't know
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000983 # what files have changed so we always run all hooks. It'd be nice to fix
984 # that.
985 if (options.force or
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000986 gclient_scm.GetScmName(self.parsed_url) in ('git', None) or
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000987 os.path.isdir(os.path.join(self.root.root_dir, self.name, '.git'))):
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200988 result.extend(self.deps_hooks)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000989 else:
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200990 for hook in self.deps_hooks:
991 if hook.matches(self.file_list_and_children):
992 result.append(hook)
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000993 for s in self.dependencies:
szager@google.comb9a78d32012-03-13 18:46:21 +0000994 result.extend(s.GetHooks(options))
995 return result
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000996
szager@google.comb9a78d32012-03-13 18:46:21 +0000997 def RunHooksRecursively(self, options):
998 assert self.hooks_ran == False
maruel@chromium.org064186c2011-09-27 23:53:33 +0000999 self._hooks_ran = True
szager@google.comb9a78d32012-03-13 18:46:21 +00001000 for hook in self.GetHooks(options):
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +02001001 hook.run(self.root.root_dir)
maruel@chromium.orgeaf61062010-07-07 18:42:39 +00001002
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001003 def RunPreDepsHooks(self):
1004 assert self.processed
1005 assert self.deps_parsed
1006 assert not self.pre_deps_hooks_ran
1007 assert not self.hooks_ran
1008 for s in self.dependencies:
1009 assert not s.processed
1010 self._pre_deps_hooks_ran = True
1011 for hook in self.pre_deps_hooks:
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +02001012 hook.run(self.root.root_dir)
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001013
maruel@chromium.org0d812442010-08-10 12:41:08 +00001014 def subtree(self, include_all):
maruel@chromium.orgad3287e2011-10-03 19:15:10 +00001015 """Breadth first recursion excluding root node."""
maruel@chromium.orgf13a4182011-09-22 00:26:15 +00001016 dependencies = self.dependencies
1017 for d in dependencies:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001018 if d.should_process or include_all:
maruel@chromium.orgad3287e2011-10-03 19:15:10 +00001019 yield d
maruel@chromium.orgf13a4182011-09-22 00:26:15 +00001020 for d in dependencies:
maruel@chromium.orgad3287e2011-10-03 19:15:10 +00001021 for i in d.subtree(include_all):
1022 yield i
1023
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001024 @gclient_utils.lockedmethod
1025 def add_dependency(self, new_dep):
1026 self._dependencies.append(new_dep)
1027
1028 @gclient_utils.lockedmethod
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +02001029 def add_orig_dependency(self, new_dep):
1030 self._orig_dependencies.append(new_dep)
1031
1032 @gclient_utils.lockedmethod
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001033 def _mark_as_parsed(self, new_hooks):
1034 self._deps_hooks.extend(new_hooks)
1035 self._deps_parsed = True
1036
maruel@chromium.org68988972011-09-20 14:11:42 +00001037 @property
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001038 @gclient_utils.lockedmethod
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +00001039 def dependencies(self):
1040 return tuple(self._dependencies)
1041
1042 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001043 @gclient_utils.lockedmethod
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +02001044 def orig_dependencies(self):
1045 return tuple(self._orig_dependencies)
1046
1047 @property
1048 @gclient_utils.lockedmethod
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02001049 def os_dependencies(self):
1050 return dict(self._os_dependencies)
1051
1052 @property
1053 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001054 def deps_hooks(self):
1055 return tuple(self._deps_hooks)
1056
1057 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001058 @gclient_utils.lockedmethod
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001059 def pre_deps_hooks(self):
1060 return tuple(self._pre_deps_hooks)
1061
1062 @property
1063 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001064 def parsed_url(self):
1065 return self._parsed_url
1066
1067 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001068 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001069 def deps_parsed(self):
maruel@chromium.org3223edd2011-10-10 23:17:39 +00001070 """This is purely for debugging purposes. It's not used anywhere."""
maruel@chromium.org064186c2011-09-27 23:53:33 +00001071 return self._deps_parsed
1072
1073 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001074 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001075 def processed(self):
1076 return self._processed
1077
1078 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001079 @gclient_utils.lockedmethod
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001080 def pre_deps_hooks_ran(self):
1081 return self._pre_deps_hooks_ran
1082
1083 @property
1084 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001085 def hooks_ran(self):
1086 return self._hooks_ran
1087
1088 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001089 @gclient_utils.lockedmethod
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00001090 def allowed_hosts(self):
1091 return self._allowed_hosts
1092
1093 @property
1094 @gclient_utils.lockedmethod
maruel@chromium.org861fd0f2010-07-23 03:05:05 +00001095 def file_list(self):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001096 return tuple(self._file_list)
1097
1098 @property
kustermann@google.coma692e8f2013-04-18 08:32:04 +00001099 def used_scm(self):
1100 """SCMWrapper instance for this dependency or None if not processed yet."""
1101 return self._used_scm
1102
1103 @property
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00001104 @gclient_utils.lockedmethod
1105 def got_revision(self):
1106 return self._got_revision
1107
1108 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001109 def file_list_and_children(self):
1110 result = list(self.file_list)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +00001111 for d in self.dependencies:
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001112 result.extend(d.file_list_and_children)
maruel@chromium.org68988972011-09-20 14:11:42 +00001113 return tuple(result)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +00001114
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001115 def __str__(self):
1116 out = []
agablea98a6cd2016-11-15 14:30:10 -08001117 for i in ('name', 'url', 'parsed_url', 'custom_deps',
maruel@chromium.org3c74bc92011-09-15 19:17:21 +00001118 'custom_vars', 'deps_hooks', 'file_list', 'should_process',
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00001119 'processed', 'hooks_ran', 'deps_parsed', 'requirements',
1120 'allowed_hosts'):
maruel@chromium.org3c74bc92011-09-15 19:17:21 +00001121 # First try the native property if it exists.
1122 if hasattr(self, '_' + i):
1123 value = getattr(self, '_' + i, False)
1124 else:
1125 value = getattr(self, i, False)
1126 if value:
1127 out.append('%s: %s' % (i, value))
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001128
1129 for d in self.dependencies:
1130 out.extend([' ' + x for x in str(d).splitlines()])
1131 out.append('')
1132 return '\n'.join(out)
1133
1134 def __repr__(self):
1135 return '%s: %s' % (self.name, self.url)
1136
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001137 def hierarchy(self, include_url=True):
maruel@chromium.orgbc2d2f92010-07-22 21:26:48 +00001138 """Returns a human-readable hierarchical reference to a Dependency."""
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001139 def format_name(d):
1140 if include_url:
1141 return '%s(%s)' % (d.name, d.url)
1142 return d.name
1143 out = format_name(self)
maruel@chromium.orgbffb9042010-07-22 20:59:36 +00001144 i = self.parent
1145 while i and i.name:
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001146 out = '%s -> %s' % (format_name(i), out)
maruel@chromium.orgbffb9042010-07-22 20:59:36 +00001147 i = i.parent
1148 return out
1149
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001150
1151class GClient(Dependency):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001152 """Object that represent a gclient checkout. A tree of Dependency(), one per
1153 solution or DEPS entry."""
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001154
1155 DEPS_OS_CHOICES = {
1156 "win32": "win",
1157 "win": "win",
1158 "cygwin": "win",
1159 "darwin": "mac",
1160 "mac": "mac",
1161 "unix": "unix",
1162 "linux": "unix",
1163 "linux2": "unix",
maruel@chromium.org244e3442011-06-12 15:20:55 +00001164 "linux3": "unix",
szager@chromium.orgf8c95cd2012-06-01 22:26:52 +00001165 "android": "android",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001166 }
1167
1168 DEFAULT_CLIENT_FILE_TEXT = ("""\
1169solutions = [
smutae7ea312016-07-18 11:59:41 -07001170 { "name" : "%(solution_name)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001171 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +00001172 "deps_file" : "%(deps_file)s",
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001173 "managed" : %(managed)s,
smutae7ea312016-07-18 11:59:41 -07001174 "custom_deps" : {
1175 },
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001176 },
1177]
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001178cache_dir = %(cache_dir)r
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001179""")
1180
1181 DEFAULT_SNAPSHOT_SOLUTION_TEXT = ("""\
smutae7ea312016-07-18 11:59:41 -07001182 { "name" : "%(solution_name)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001183 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +00001184 "deps_file" : "%(deps_file)s",
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001185 "managed" : %(managed)s,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001186 "custom_deps" : {
smutae7ea312016-07-18 11:59:41 -07001187%(solution_deps)s },
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001188 },
1189""")
1190
1191 DEFAULT_SNAPSHOT_FILE_TEXT = ("""\
1192# Snapshot generated with gclient revinfo --snapshot
1193solutions = [
maruel@chromium.org73e21142010-07-05 13:32:01 +00001194%(solution_list)s]
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001195""")
1196
1197 def __init__(self, root_dir, options):
maruel@chromium.org0d812442010-08-10 12:41:08 +00001198 # Do not change previous behavior. Only solution level and immediate DEPS
1199 # are processed.
1200 self._recursion_limit = 2
agablea98a6cd2016-11-15 14:30:10 -08001201 Dependency.__init__(self, None, None, None, True, None, None, None,
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +02001202 'unused', True, None, None, True)
maruel@chromium.org0d425922010-06-21 19:22:24 +00001203 self._options = options
maruel@chromium.org271375b2010-06-23 19:17:38 +00001204 if options.deps_os:
1205 enforced_os = options.deps_os.split(',')
1206 else:
1207 enforced_os = [self.DEPS_OS_CHOICES.get(sys.platform, 'unix')]
1208 if 'all' in enforced_os:
1209 enforced_os = self.DEPS_OS_CHOICES.itervalues()
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001210 self._enforced_os = tuple(set(enforced_os))
maruel@chromium.org271375b2010-06-23 19:17:38 +00001211 self._root_dir = root_dir
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001212 self.config_content = None
1213
borenet@google.com88d10082014-03-21 17:24:48 +00001214 def _CheckConfig(self):
1215 """Verify that the config matches the state of the existing checked-out
1216 solutions."""
1217 for dep in self.dependencies:
1218 if dep.managed and dep.url:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001219 scm = gclient_scm.CreateSCM(
1220 dep.url, self.root_dir, dep.name, self.outbuf)
smut@google.comd33eab32014-07-07 19:35:18 +00001221 actual_url = scm.GetActualRemoteURL(self._options)
borenet@google.com4e9be262014-04-08 19:40:30 +00001222 if actual_url and not scm.DoesRemoteURLMatch(self._options):
levarum@chromium.org27a6f9a2016-05-28 00:21:49 +00001223 mirror = scm.GetCacheMirror()
1224 if mirror:
1225 mirror_string = '%s (exists=%s)' % (mirror.mirror_path,
1226 mirror.exists())
1227 else:
1228 mirror_string = 'not used'
borenet@google.com0a427372014-04-02 19:12:13 +00001229 raise gclient_utils.Error('''
borenet@google.com88d10082014-03-21 17:24:48 +00001230Your .gclient file seems to be broken. The requested URL is different from what
borenet@google.com0a427372014-04-02 19:12:13 +00001231is actually checked out in %(checkout_path)s.
borenet@google.com88d10082014-03-21 17:24:48 +00001232
borenet@google.com97882362014-04-07 20:06:02 +00001233The .gclient file contains:
levarum@chromium.org27a6f9a2016-05-28 00:21:49 +00001234URL: %(expected_url)s (%(expected_scm)s)
1235Cache mirror: %(mirror_string)s
borenet@google.com97882362014-04-07 20:06:02 +00001236
1237The local checkout in %(checkout_path)s reports:
1238%(actual_url)s (%(actual_scm)s)
borenet@google.com88d10082014-03-21 17:24:48 +00001239
1240You should ensure that the URL listed in .gclient is correct and either change
agabled437d762016-10-17 09:35:11 -07001241it or fix the checkout.
borenet@google.com88d10082014-03-21 17:24:48 +00001242''' % {'checkout_path': os.path.join(self.root_dir, dep.name),
1243 'expected_url': dep.url,
1244 'expected_scm': gclient_scm.GetScmName(dep.url),
levarum@chromium.org27a6f9a2016-05-28 00:21:49 +00001245 'mirror_string' : mirror_string,
borenet@google.com88d10082014-03-21 17:24:48 +00001246 'actual_url': actual_url,
1247 'actual_scm': gclient_scm.GetScmName(actual_url)})
1248
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001249 def SetConfig(self, content):
maruel@chromium.orgf13a4182011-09-22 00:26:15 +00001250 assert not self.dependencies
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001251 config_dict = {}
1252 self.config_content = content
1253 try:
1254 exec(content, config_dict)
vapier@chromium.orga81a56e2015-11-11 07:56:13 +00001255 except SyntaxError as e:
maruel@chromium.org5990f9d2010-07-07 18:02:58 +00001256 gclient_utils.SyntaxErrorToError('.gclient', e)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001257
peter@chromium.org1efccc82012-04-27 16:34:38 +00001258 # Append any target OS that is not already being enforced to the tuple.
1259 target_os = config_dict.get('target_os', [])
stuartmorgan@chromium.org18a4f6a2012-11-02 13:37:24 +00001260 if config_dict.get('target_os_only', False):
1261 self._enforced_os = tuple(set(target_os))
1262 else:
1263 self._enforced_os = tuple(set(self._enforced_os).union(target_os))
1264
dyen@chromium.orgd915cca2014-08-07 21:41:37 +00001265 cache_dir = config_dict.get('cache_dir')
1266 if cache_dir:
1267 cache_dir = os.path.join(self.root_dir, cache_dir)
1268 cache_dir = os.path.abspath(cache_dir)
szager@chromium.orgcaf5bef2014-08-24 18:56:32 +00001269 # If running on a bot, force break any stale git cache locks.
dnj@chromium.orgb682b3e2014-08-25 19:17:12 +00001270 if os.path.exists(cache_dir) and os.environ.get('CHROME_HEADLESS'):
szager@chromium.org4848fb62014-08-24 19:16:31 +00001271 subprocess2.check_call(['git', 'cache', 'unlock', '--cache-dir',
1272 cache_dir, '--force', '--all'])
dyen@chromium.orgd915cca2014-08-07 21:41:37 +00001273 gclient_scm.GitWrapper.cache_dir = cache_dir
1274 git_cache.Mirror.SetCachePath(cache_dir)
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001275
stuartmorgan@chromium.org18a4f6a2012-11-02 13:37:24 +00001276 if not target_os and config_dict.get('target_os_only', False):
1277 raise gclient_utils.Error('Can\'t use target_os_only if target_os is '
1278 'not specified')
peter@chromium.org1efccc82012-04-27 16:34:38 +00001279
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001280 deps_to_add = []
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001281 for s in config_dict.get('solutions', []):
maruel@chromium.org81843b82010-06-28 16:49:26 +00001282 try:
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001283 deps_to_add.append(Dependency(
maruel@chromium.org81843b82010-06-28 16:49:26 +00001284 self, s['name'], s['url'],
smutae7ea312016-07-18 11:59:41 -07001285 s.get('managed', True),
maruel@chromium.org81843b82010-06-28 16:49:26 +00001286 s.get('custom_deps', {}),
maruel@chromium.org0d812442010-08-10 12:41:08 +00001287 s.get('custom_vars', {}),
petermayo@chromium.orge79161a2013-07-09 14:40:37 +00001288 s.get('custom_hooks', []),
nsylvain@google.comefc80932011-05-31 21:27:56 +00001289 s.get('deps_file', 'DEPS'),
agabledce6ddc2016-09-08 10:02:16 -07001290 True,
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +02001291 None,
1292 None,
1293 True))
maruel@chromium.org81843b82010-06-28 16:49:26 +00001294 except KeyError:
1295 raise gclient_utils.Error('Invalid .gclient file. Solution is '
1296 'incomplete: %s' % s)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001297 self.add_dependencies_and_close(deps_to_add, config_dict.get('hooks', []))
1298 logging.info('SetConfig() done')
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001299
1300 def SaveConfig(self):
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001301 gclient_utils.FileWrite(os.path.join(self.root_dir,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001302 self._options.config_filename),
1303 self.config_content)
1304
1305 @staticmethod
1306 def LoadCurrentConfig(options):
1307 """Searches for and loads a .gclient file relative to the current working
1308 dir. Returns a GClient object."""
szager@chromium.orge2e03202012-07-31 18:05:16 +00001309 if options.spec:
1310 client = GClient('.', options)
1311 client.SetConfig(options.spec)
1312 else:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00001313 if options.verbose:
1314 print('Looking for %s starting from %s\n' % (
1315 options.config_filename, os.getcwd()))
szager@chromium.orge2e03202012-07-31 18:05:16 +00001316 path = gclient_utils.FindGclientRoot(os.getcwd(), options.config_filename)
1317 if not path:
1318 return None
1319 client = GClient(path, options)
1320 client.SetConfig(gclient_utils.FileRead(
1321 os.path.join(path, options.config_filename)))
maruel@chromium.org69392e72011-10-13 22:09:00 +00001322
1323 if (options.revisions and
1324 len(client.dependencies) > 1 and
1325 any('@' not in r for r in options.revisions)):
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001326 print(
1327 ('You must specify the full solution name like --revision %s@%s\n'
1328 'when you have multiple solutions setup in your .gclient file.\n'
1329 'Other solutions present are: %s.') % (
maruel@chromium.org69392e72011-10-13 22:09:00 +00001330 client.dependencies[0].name,
1331 options.revisions[0],
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001332 ', '.join(s.name for s in client.dependencies[1:])),
1333 file=sys.stderr)
maruel@chromium.org15804092010-09-02 17:07:37 +00001334 return client
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001335
nsylvain@google.comefc80932011-05-31 21:27:56 +00001336 def SetDefaultConfig(self, solution_name, deps_file, solution_url,
agablea98a6cd2016-11-15 14:30:10 -08001337 managed=True, cache_dir=None):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001338 self.SetConfig(self.DEFAULT_CLIENT_FILE_TEXT % {
1339 'solution_name': solution_name,
1340 'solution_url': solution_url,
nsylvain@google.comefc80932011-05-31 21:27:56 +00001341 'deps_file': deps_file,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001342 'managed': managed,
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001343 'cache_dir': cache_dir,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001344 })
1345
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001346 def _SaveEntries(self):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001347 """Creates a .gclient_entries file to record the list of unique checkouts.
1348
1349 The .gclient_entries file lives in the same directory as .gclient.
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001350 """
1351 # Sometimes pprint.pformat will use {', sometimes it'll use { ' ... It
1352 # makes testing a bit too fun.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001353 result = 'entries = {\n'
maruel@chromium.org68988972011-09-20 14:11:42 +00001354 for entry in self.root.subtree(False):
agabled437d762016-10-17 09:35:11 -07001355 result += ' %s: %s,\n' % (pprint.pformat(entry.name),
1356 pprint.pformat(entry.parsed_url))
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001357 result += '}\n'
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001358 file_path = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org1333cb32011-10-04 23:40:16 +00001359 logging.debug(result)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001360 gclient_utils.FileWrite(file_path, result)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001361
1362 def _ReadEntries(self):
1363 """Read the .gclient_entries file for the given client.
1364
1365 Returns:
1366 A sequence of solution names, which will be empty if there is the
1367 entries file hasn't been created yet.
1368 """
1369 scope = {}
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001370 filename = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001371 if not os.path.exists(filename):
maruel@chromium.org73e21142010-07-05 13:32:01 +00001372 return {}
maruel@chromium.org5990f9d2010-07-07 18:02:58 +00001373 try:
1374 exec(gclient_utils.FileRead(filename), scope)
vapier@chromium.orga81a56e2015-11-11 07:56:13 +00001375 except SyntaxError as e:
maruel@chromium.org5990f9d2010-07-07 18:02:58 +00001376 gclient_utils.SyntaxErrorToError(filename, e)
Aaron Gable3721ee92017-04-03 14:53:14 -07001377 return scope.get('entries', {})
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001378
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001379 def _EnforceRevisions(self):
maruel@chromium.org918a9ae2010-05-28 15:50:30 +00001380 """Checks for revision overrides."""
1381 revision_overrides = {}
smutae7ea312016-07-18 11:59:41 -07001382 if self._options.head:
1383 return revision_overrides
joi@chromium.org792ea882010-11-10 02:37:27 +00001384 if not self._options.revisions:
1385 for s in self.dependencies:
smutae7ea312016-07-18 11:59:41 -07001386 if not s.managed:
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001387 self._options.revisions.append('%s@unmanaged' % s.name)
maruel@chromium.org307d1792010-05-31 20:03:13 +00001388 if not self._options.revisions:
1389 return revision_overrides
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001390 solutions_names = [s.name for s in self.dependencies]
smutae7ea312016-07-18 11:59:41 -07001391 index = 0
1392 for revision in self._options.revisions:
1393 if not '@' in revision:
maruel@chromium.org307d1792010-05-31 20:03:13 +00001394 # Support for --revision 123
smutae7ea312016-07-18 11:59:41 -07001395 revision = '%s@%s' % (solutions_names[index], revision)
1396 name, rev = revision.split('@', 1)
szager@chromium.org4ad264b2014-05-20 04:43:47 +00001397 revision_overrides[name] = rev
smutae7ea312016-07-18 11:59:41 -07001398 index += 1
maruel@chromium.org918a9ae2010-05-28 15:50:30 +00001399 return revision_overrides
1400
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001401 def RunOnDeps(self, command, args, ignore_requirements=False, progress=True):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001402 """Runs a command on each dependency in a client and its dependencies.
1403
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001404 Args:
1405 command: The command to use (e.g., 'status' or 'diff')
1406 args: list of str - extra arguments to add to the command line.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001407 """
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001408 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +00001409 raise gclient_utils.Error('No solution specified')
borenet@google.com0a427372014-04-02 19:12:13 +00001410
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001411 revision_overrides = {}
1412 # It's unnecessary to check for revision overrides for 'recurse'.
1413 # Save a few seconds by not calling _EnforceRevisions() in that case.
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +02001414 if command not in ('diff', 'recurse', 'runhooks', 'status', 'revert',
1415 'validate'):
szager@chromium.org5273b8a2014-08-21 15:10:10 +00001416 self._CheckConfig()
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001417 revision_overrides = self._EnforceRevisions()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001418 pm = None
maruel@chromium.org5b3f8852010-09-10 16:49:54 +00001419 # Disable progress for non-tty stdout.
iannucci@chromium.org596cd5c2016-04-04 21:34:39 +00001420 if (setup_color.IS_TTY and not self._options.verbose and progress):
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001421 if command in ('update', 'revert'):
1422 pm = Progress('Syncing projects', 1)
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +02001423 elif command in ('recurse', 'validate'):
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001424 pm = Progress(' '.join(args), 1)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001425 work_queue = gclient_utils.ExecutionQueue(
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001426 self._options.jobs, pm, ignore_requirements=ignore_requirements,
1427 verbose=self._options.verbose)
maruel@chromium.org049bced2010-08-12 13:37:20 +00001428 for s in self.dependencies:
1429 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +00001430 work_queue.flush(revision_overrides, command, args, options=self._options)
szager@chromium.org4ad264b2014-05-20 04:43:47 +00001431 if revision_overrides:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001432 print('Please fix your script, having invalid --revision flags will soon '
1433 'considered an error.', file=sys.stderr)
piman@chromium.org6f363722010-04-27 00:41:09 +00001434
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001435 # Once all the dependencies have been processed, it's now safe to run the
1436 # hooks.
1437 if not self._options.nohooks:
1438 self.RunHooksRecursively(self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001439
1440 if command == 'update':
ajwong@chromium.orgcdcee802009-06-23 15:30:42 +00001441 # Notify the user if there is an orphaned entry in their working copy.
1442 # Only delete the directory if there are no changes in it, and
1443 # delete_unversioned_trees is set to true.
maruel@chromium.org68988972011-09-20 14:11:42 +00001444 entries = [i.name for i in self.root.subtree(False) if i.url]
xusydoc@chromium.org885a9602013-05-31 09:54:40 +00001445 full_entries = [os.path.join(self.root_dir, e.replace('/', os.path.sep))
1446 for e in entries]
1447
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001448 for entry, prev_url in self._ReadEntries().iteritems():
maruel@chromium.org04dd7de2010-10-14 13:25:49 +00001449 if not prev_url:
1450 # entry must have been overridden via .gclient custom_deps
1451 continue
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001452 # Fix path separator on Windows.
1453 entry_fixed = entry.replace('/', os.path.sep)
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001454 e_dir = os.path.join(self.root_dir, entry_fixed)
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001455 # Use entry and not entry_fixed there.
jochen@chromium.orga78e5532013-03-11 13:33:03 +00001456 if (entry not in entries and
1457 (not any(path.startswith(entry + '/') for path in entries)) and
jochen@chromium.orgcc475722013-03-11 13:07:40 +00001458 os.path.exists(e_dir)):
primiano@chromium.org1c127382015-02-17 11:15:40 +00001459 # The entry has been removed from DEPS.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001460 scm = gclient_scm.CreateSCM(
1461 prev_url, self.root_dir, entry_fixed, self.outbuf)
xusydoc@chromium.org885a9602013-05-31 09:54:40 +00001462
1463 # Check to see if this directory is now part of a higher-up checkout.
borenet@google.com359bb642014-05-13 17:28:19 +00001464 scm_root = None
agabled437d762016-10-17 09:35:11 -07001465 try:
1466 scm_root = gclient_scm.scm.GIT.GetCheckoutRoot(scm.checkout_path)
1467 except subprocess2.CalledProcessError:
1468 pass
1469 if not scm_root:
borenet@google.com359bb642014-05-13 17:28:19 +00001470 logging.warning('Could not find checkout root for %s. Unable to '
1471 'determine whether it is part of a higher-level '
1472 'checkout, so not removing.' % entry)
1473 continue
primiano@chromium.org1c127382015-02-17 11:15:40 +00001474
1475 # This is to handle the case of third_party/WebKit migrating from
1476 # being a DEPS entry to being part of the main project.
1477 # If the subproject is a Git project, we need to remove its .git
1478 # folder. Otherwise git operations on that folder will have different
1479 # effects depending on the current working directory.
agabled437d762016-10-17 09:35:11 -07001480 if os.path.abspath(scm_root) == os.path.abspath(e_dir):
primiano@chromium.org1c127382015-02-17 11:15:40 +00001481 e_par_dir = os.path.join(e_dir, os.pardir)
agabled437d762016-10-17 09:35:11 -07001482 if gclient_scm.scm.GIT.IsInsideWorkTree(e_par_dir):
1483 par_scm_root = gclient_scm.scm.GIT.GetCheckoutRoot(e_par_dir)
primiano@chromium.org1c127382015-02-17 11:15:40 +00001484 # rel_e_dir : relative path of entry w.r.t. its parent repo.
1485 rel_e_dir = os.path.relpath(e_dir, par_scm_root)
agabled437d762016-10-17 09:35:11 -07001486 if gclient_scm.scm.GIT.IsDirectoryVersioned(
1487 par_scm_root, rel_e_dir):
primiano@chromium.org1c127382015-02-17 11:15:40 +00001488 save_dir = scm.GetGitBackupDirPath()
1489 # Remove any eventual stale backup dir for the same project.
1490 if os.path.exists(save_dir):
1491 gclient_utils.rmtree(save_dir)
1492 os.rename(os.path.join(e_dir, '.git'), save_dir)
1493 # When switching between the two states (entry/ is a subproject
1494 # -> entry/ is part of the outer project), it is very likely
1495 # that some files are changed in the checkout, unless we are
1496 # jumping *exactly* across the commit which changed just DEPS.
1497 # In such case we want to cleanup any eventual stale files
1498 # (coming from the old subproject) in order to end up with a
1499 # clean checkout.
agabled437d762016-10-17 09:35:11 -07001500 gclient_scm.scm.GIT.CleanupDir(par_scm_root, rel_e_dir)
primiano@chromium.org1c127382015-02-17 11:15:40 +00001501 assert not os.path.exists(os.path.join(e_dir, '.git'))
1502 print(('\nWARNING: \'%s\' has been moved from DEPS to a higher '
1503 'level checkout. The git folder containing all the local'
1504 ' branches has been saved to %s.\n'
1505 'If you don\'t care about its state you can safely '
1506 'remove that folder to free up space.') %
1507 (entry, save_dir))
1508 continue
1509
borenet@google.com359bb642014-05-13 17:28:19 +00001510 if scm_root in full_entries:
primiano@chromium.org1c127382015-02-17 11:15:40 +00001511 logging.info('%s is part of a higher level checkout, not removing',
1512 scm.GetCheckoutRoot())
xusydoc@chromium.org885a9602013-05-31 09:54:40 +00001513 continue
1514
1515 file_list = []
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001516 scm.status(self._options, [], file_list)
1517 modified_files = file_list != []
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001518 if (not self._options.delete_unversioned_trees or
1519 (modified_files and not self._options.force)):
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001520 # There are modified files in this entry. Keep warning until
1521 # removed.
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001522 print(('\nWARNING: \'%s\' is no longer part of this client. '
1523 'It is recommended that you manually remove it.\n') %
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001524 entry_fixed)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001525 else:
1526 # Delete the entry
maruel@chromium.org73e21142010-07-05 13:32:01 +00001527 print('\n________ deleting \'%s\' in \'%s\'' % (
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001528 entry_fixed, self.root_dir))
digit@chromium.orgdc112ac2013-04-24 13:00:19 +00001529 gclient_utils.rmtree(e_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001530 # record the current list of entries for next time
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001531 self._SaveEntries()
maruel@chromium.org17cdf762010-05-28 17:30:52 +00001532 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001533
1534 def PrintRevInfo(self):
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001535 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +00001536 raise gclient_utils.Error('No solution specified')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001537 # Load all the settings.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001538 work_queue = gclient_utils.ExecutionQueue(
1539 self._options.jobs, None, False, verbose=self._options.verbose)
maruel@chromium.org049bced2010-08-12 13:37:20 +00001540 for s in self.dependencies:
1541 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +00001542 work_queue.flush({}, None, [], options=self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001543
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001544 def GetURLAndRev(dep):
1545 """Returns the revision-qualified SCM url for a Dependency."""
1546 if dep.parsed_url is None:
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001547 return None
agabled437d762016-10-17 09:35:11 -07001548 url, _ = gclient_utils.SplitUrlRevision(dep.parsed_url)
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001549 scm = gclient_scm.CreateSCM(
agabled437d762016-10-17 09:35:11 -07001550 dep.parsed_url, self.root_dir, dep.name, self.outbuf)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001551 if not os.path.isdir(scm.checkout_path):
1552 return None
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001553 return '%s@%s' % (url, scm.revinfo(self._options, [], None))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001554
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001555 if self._options.snapshot:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001556 new_gclient = ''
1557 # First level at .gclient
1558 for d in self.dependencies:
1559 entries = {}
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001560 def GrabDeps(dep):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001561 """Recursively grab dependencies."""
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001562 for d in dep.dependencies:
1563 entries[d.name] = GetURLAndRev(d)
1564 GrabDeps(d)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001565 GrabDeps(d)
1566 custom_deps = []
1567 for k in sorted(entries.keys()):
1568 if entries[k]:
1569 # Quotes aren't escaped...
1570 custom_deps.append(' \"%s\": \'%s\',\n' % (k, entries[k]))
1571 else:
1572 custom_deps.append(' \"%s\": None,\n' % k)
1573 new_gclient += self.DEFAULT_SNAPSHOT_SOLUTION_TEXT % {
1574 'solution_name': d.name,
1575 'solution_url': d.url,
nsylvain@google.comefc80932011-05-31 21:27:56 +00001576 'deps_file': d.deps_file,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001577 'managed': d.managed,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001578 'solution_deps': ''.join(custom_deps),
1579 }
1580 # Print the snapshot configuration file
1581 print(self.DEFAULT_SNAPSHOT_FILE_TEXT % {'solution_list': new_gclient})
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +00001582 else:
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001583 entries = {}
maruel@chromium.org68988972011-09-20 14:11:42 +00001584 for d in self.root.subtree(False):
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001585 if self._options.actual:
1586 entries[d.name] = GetURLAndRev(d)
1587 else:
1588 entries[d.name] = d.parsed_url
1589 keys = sorted(entries.keys())
1590 for x in keys:
maruel@chromium.orgce464892010-08-12 17:12:18 +00001591 print('%s: %s' % (x, entries[x]))
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +00001592 logging.info(str(self))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001593
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001594 def ParseDepsFile(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001595 """No DEPS to parse for a .gclient file."""
maruel@chromium.org049bced2010-08-12 13:37:20 +00001596 raise gclient_utils.Error('Internal error')
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001597
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00001598 def PrintLocationAndContents(self):
1599 # Print out the .gclient file. This is longer than if we just printed the
1600 # client dict, but more legible, and it might contain helpful comments.
1601 print('Loaded .gclient config in %s:\n%s' % (
1602 self.root_dir, self.config_content))
1603
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001604 @property
maruel@chromium.org75a59272010-06-11 22:34:03 +00001605 def root_dir(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001606 """Root directory of gclient checkout."""
maruel@chromium.org75a59272010-06-11 22:34:03 +00001607 return self._root_dir
1608
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001609 @property
maruel@chromium.org271375b2010-06-23 19:17:38 +00001610 def enforced_os(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001611 """What deps_os entries that are to be parsed."""
maruel@chromium.org271375b2010-06-23 19:17:38 +00001612 return self._enforced_os
1613
maruel@chromium.org68988972011-09-20 14:11:42 +00001614 @property
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001615 def recursion_limit(self):
1616 """How recursive can each dependencies in DEPS file can load DEPS file."""
1617 return self._recursion_limit
1618
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +00001619 @property
cmp@chromium.orgc401ad12014-07-02 23:20:08 +00001620 def try_recursedeps(self):
1621 """Whether to attempt using recursedeps-style recursion processing."""
cmp@chromium.orge84ac912014-06-30 23:14:35 +00001622 return True
1623
1624 @property
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +00001625 def target_os(self):
1626 return self._enforced_os
1627
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001628
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001629#### gclient commands.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001630
1631
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001632@subcommand.usage('[command] [args ...]')
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001633def CMDrecurse(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001634 """Operates [command args ...] on all the dependencies.
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001635
1636 Runs a shell command on all entries.
qyearsley12fa6ff2016-08-24 09:18:40 -07001637 Sets GCLIENT_DEP_PATH environment variable as the dep's relative location to
ilevy@chromium.org37116242012-11-28 01:32:48 +00001638 root directory of the checkout.
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001639 """
1640 # Stop parsing at the first non-arg so that these go through to the command
1641 parser.disable_interspersed_args()
1642 parser.add_option('-s', '--scm', action='append', default=[],
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001643 help='Choose scm types to operate upon.')
maruel@chromium.org288054d2012-03-05 00:43:07 +00001644 parser.add_option('-i', '--ignore', action='store_true',
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001645 help='Ignore non-zero return codes from subcommands.')
1646 parser.add_option('--prepend-dir', action='store_true',
1647 help='Prepend relative dir for use with git <cmd> --null.')
1648 parser.add_option('--no-progress', action='store_true',
1649 help='Disable progress bar that shows sub-command updates')
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001650 options, args = parser.parse_args(args)
maruel@chromium.org45e9f2d2010-10-18 13:33:46 +00001651 if not args:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001652 print('Need to supply a command!', file=sys.stderr)
maruel@chromium.org45e9f2d2010-10-18 13:33:46 +00001653 return 1
maruel@chromium.org78cba522010-10-18 13:32:05 +00001654 root_and_entries = gclient_utils.GetGClientRootAndEntries()
1655 if not root_and_entries:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001656 print(
maruel@chromium.org78cba522010-10-18 13:32:05 +00001657 'You need to run gclient sync at least once to use \'recurse\'.\n'
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001658 'This is because .gclient_entries needs to exist and be up to date.',
1659 file=sys.stderr)
maruel@chromium.org78cba522010-10-18 13:32:05 +00001660 return 1
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001661
1662 # Normalize options.scm to a set()
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001663 scm_set = set()
1664 for scm in options.scm:
1665 scm_set.update(scm.split(','))
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001666 options.scm = scm_set
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001667
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001668 options.nohooks = True
1669 client = GClient.LoadCurrentConfig(options)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001670 return client.RunOnDeps('recurse', args, ignore_requirements=True,
1671 progress=not options.no_progress)
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001672
1673
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001674@subcommand.usage('[args ...]')
davidbarr@chromium.org12f944e2012-03-01 02:18:31 +00001675def CMDfetch(parser, args):
1676 """Fetches upstream commits for all modules.
1677
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001678 Completely git-specific. Simply runs 'git fetch [args ...]' for each module.
1679 """
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001680 (options, args) = parser.parse_args(args)
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00001681 return CMDrecurse(OptionParser(), [
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001682 '--jobs=%d' % options.jobs, '--scm=git', 'git', 'fetch'] + args)
1683
1684
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001685def CMDflatten(parser, args):
1686 """Flattens the solutions into a single DEPS file."""
1687 parser.add_option('--output-deps', help='Path to the output DEPS file')
1688 parser.add_option(
1689 '--require-pinned-revisions', action='store_true',
1690 help='Fail if any of the dependencies uses unpinned revision.')
1691 options, args = parser.parse_args(args)
1692
1693 options.nohooks = True
1694 client = GClient.LoadCurrentConfig(options)
1695
1696 # Only print progress if we're writing to a file. Otherwise, progress updates
1697 # could obscure intended output.
1698 code = client.RunOnDeps('flatten', args, progress=options.output_deps)
1699 if code != 0:
1700 return code
1701
1702 deps = {}
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02001703 deps_os = {}
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001704 hooks = []
1705 pre_deps_hooks = []
1706 unpinned_deps = {}
1707
1708 for solution in client.dependencies:
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02001709 _FlattenSolution(
1710 solution, deps, deps_os, hooks, pre_deps_hooks, unpinned_deps)
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001711
1712 if options.require_pinned_revisions and unpinned_deps:
1713 sys.stderr.write('The following dependencies are not pinned:\n')
1714 sys.stderr.write('\n'.join(sorted(unpinned_deps)))
1715 return 1
1716
1717 flattened_deps = '\n'.join(
Paweł Hajdan, Jr3c2aa832017-06-07 20:22:16 +02001718 _GNSettingsToLines(
1719 client.dependencies[0]._gn_args_file,
1720 client.dependencies[0]._gn_args) +
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001721 _DepsToLines(deps) +
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02001722 _DepsOsToLines(deps_os) +
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001723 _HooksToLines('hooks', hooks) +
1724 _HooksToLines('pre_deps_hooks', pre_deps_hooks) +
1725 [''] # Ensure newline at end of file.
1726 )
1727
1728 if options.output_deps:
1729 with open(options.output_deps, 'w') as f:
1730 f.write(flattened_deps)
1731 else:
1732 print(flattened_deps)
1733
1734 return 0
1735
1736
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02001737def _FlattenSolution(
1738 solution, deps, deps_os, hooks, pre_deps_hooks, unpinned_deps):
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001739 """Visits a solution in order to flatten it (see CMDflatten).
1740
1741 Arguments:
1742 solution (Dependency): one of top-level solutions in .gclient
1743
1744 Out-parameters:
1745 deps (dict of name -> Dependency): will be filled with all Dependency
1746 objects indexed by their name
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02001747 deps_os (dict of os name -> dep name -> Dependency): same as above,
1748 for OS-specific deps
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001749 hooks (list of (Dependency, hook)): will be filled with flattened hooks
1750 pre_deps_hooks (list of (Dependency, hook)): will be filled with flattened
1751 pre_deps_hooks
1752 unpinned_deps (dict of name -> Dependency): will be filled with unpinned
1753 deps
1754 """
1755 logging.debug('_FlattenSolution(%r)', solution)
1756
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02001757 _FlattenDep(solution, deps, deps_os, hooks, pre_deps_hooks, unpinned_deps)
1758 _FlattenRecurse(solution, deps, deps_os, hooks, pre_deps_hooks, unpinned_deps)
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001759
1760
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02001761def _FlattenDep(dep, deps, deps_os, hooks, pre_deps_hooks, unpinned_deps):
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001762 """Visits a dependency in order to flatten it (see CMDflatten).
1763
1764 Arguments:
1765 dep (Dependency): dependency to process
1766
1767 Out-parameters:
1768 deps (dict): will be filled with flattened deps
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02001769 deps_os (dict): will be filled with flattened deps_os
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001770 hooks (list): will be filled with flattened hooks
1771 pre_deps_hooks (list): will be filled with flattened pre_deps_hooks
1772 unpinned_deps (dict): will be filled with unpinned deps
1773 """
1774 logging.debug('_FlattenDep(%r)', dep)
1775
1776 _AddDep(dep, deps, unpinned_deps)
1777
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02001778 for dep_os, os_deps in dep.os_dependencies.iteritems():
1779 for os_dep in os_deps:
1780 deps_os.setdefault(dep_os, {})[os_dep.name] = os_dep
1781
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001782 deps_by_name = dict((d.name, d) for d in dep.dependencies)
1783 for recurse_dep_name in (dep.recursedeps or []):
1784 _FlattenRecurse(
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02001785 deps_by_name[recurse_dep_name], deps, deps_os, hooks, pre_deps_hooks,
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001786 unpinned_deps)
1787
1788 # TODO(phajdan.jr): also handle hooks_os.
1789 hooks.extend([(dep, hook) for hook in dep.deps_hooks])
Paweł Hajdan, Jrc9364392017-06-14 17:11:56 +02001790 pre_deps_hooks.extend([(dep, hook) for hook in dep.pre_deps_hooks])
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001791
1792
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02001793def _FlattenRecurse(dep, deps, deps_os, hooks, pre_deps_hooks, unpinned_deps):
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001794 """Helper for flatten that recurses into |dep|'s dependencies.
1795
1796 Arguments:
1797 dep (Dependency): dependency to process
1798
1799 Out-parameters:
1800 deps (dict): will be filled with flattened deps
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02001801 deps_os (dict): will be filled with flattened deps_os
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001802 hooks (list): will be filled with flattened hooks
1803 pre_deps_hooks (list): will be filled with flattened pre_deps_hooks
1804 unpinned_deps (dict): will be filled with unpinned deps
1805 """
1806 logging.debug('_FlattenRecurse(%r)', dep)
1807
1808 # TODO(phajdan.jr): also handle deps_os.
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +02001809 for sub_dep in dep.orig_dependencies:
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02001810 _FlattenDep(sub_dep, deps, deps_os, hooks, pre_deps_hooks, unpinned_deps)
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001811
1812
1813def _AddDep(dep, deps, unpinned_deps):
1814 """Helper to add a dependency to flattened lists.
1815
1816 Arguments:
1817 dep (Dependency): dependency to process
1818
1819 Out-parameters:
1820 deps (dict): will be filled with flattened deps
1821 unpinned_deps (dict): will be filled with unpinned deps
1822 """
1823 logging.debug('_AddDep(%r)', dep)
1824
1825 assert dep.name not in deps
1826 deps[dep.name] = dep
1827
1828 # Detect unpinned deps.
1829 _, revision = gclient_utils.SplitUrlRevision(dep.url)
1830 if not revision or not gclient_utils.IsGitSha(revision):
1831 unpinned_deps[dep.name] = dep
1832
1833
Paweł Hajdan, Jr3c2aa832017-06-07 20:22:16 +02001834def _GNSettingsToLines(gn_args_file, gn_args):
1835 s = []
1836 if gn_args_file:
1837 s.extend([
1838 'gclient_gn_args_file = "%s"' % gn_args_file,
1839 'gclient_gn_args = %r' % gn_args,
1840 ])
1841 return s
1842
1843
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001844def _DepsToLines(deps):
1845 """Converts |deps| dict to list of lines for output."""
1846 s = ['deps = {']
1847 for name, dep in sorted(deps.iteritems()):
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +02001848 condition_part = ([' "condition": "%s",' % dep.condition]
1849 if dep.condition else [])
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001850 s.extend([
1851 ' # %s' % dep.hierarchy(include_url=False),
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +02001852 ' "%s": {' % (name,),
1853 ' "url": "%s",' % (dep.url,),
1854 ] + condition_part + [
1855 ' },',
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001856 '',
1857 ])
1858 s.extend(['}', ''])
1859 return s
1860
1861
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02001862def _DepsOsToLines(deps_os):
1863 """Converts |deps_os| dict to list of lines for output."""
1864 s = ['deps_os = {']
1865 for dep_os, os_deps in sorted(deps_os.iteritems()):
1866 s.append(' "%s": {' % dep_os)
1867 s.extend([' %s' % l for l in _DepsToLines(os_deps)])
1868 s.extend([' },', ''])
1869 s.extend(['}', ''])
1870 return s
1871
1872
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001873def _HooksToLines(name, hooks):
1874 """Converts |hooks| list to list of lines for output."""
1875 s = ['%s = [' % name]
1876 for dep, hook in hooks:
1877 s.extend([
1878 ' # %s' % dep.hierarchy(include_url=False),
1879 ' {',
1880 ])
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +02001881 if hook.name is not None:
1882 s.append(' "name": "%s",' % hook.name)
1883 if hook.pattern is not None:
1884 s.append(' "pattern": "%s",' % hook.pattern)
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001885 s.extend(
Paweł Hajdan, Jrc9364392017-06-14 17:11:56 +02001886 # Hooks run in the parent directory of their dep.
1887 [' "cwd": "%s"' % os.path.normpath(os.path.dirname(dep.name))] +
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001888 [' "action": ['] +
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +02001889 [' "%s",' % arg for arg in hook.action] +
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001890 [' ]', ' },', '']
1891 )
1892 s.extend([']', ''])
1893 return s
1894
1895
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001896def CMDgrep(parser, args):
1897 """Greps through git repos managed by gclient.
1898
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001899 Runs 'git grep [args...]' for each module.
1900 """
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001901 # We can't use optparse because it will try to parse arguments sent
1902 # to git grep and throw an error. :-(
1903 if not args or re.match('(-h|--help)$', args[0]):
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001904 print(
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001905 'Usage: gclient grep [-j <N>] git-grep-args...\n\n'
1906 'Example: "gclient grep -j10 -A2 RefCountedBase" runs\n"git grep '
1907 '-A2 RefCountedBase" on each of gclient\'s git\nrepos with up to '
1908 '10 jobs.\n\nBonus: page output by appending "|& less -FRSX" to the'
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001909 ' end of your query.',
1910 file=sys.stderr)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001911 return 1
1912
1913 jobs_arg = ['--jobs=1']
1914 if re.match(r'(-j|--jobs=)\d+$', args[0]):
1915 jobs_arg, args = args[:1], args[1:]
1916 elif re.match(r'(-j|--jobs)$', args[0]):
1917 jobs_arg, args = args[:2], args[2:]
1918
1919 return CMDrecurse(
1920 parser,
1921 jobs_arg + ['--ignore', '--prepend-dir', '--no-progress', '--scm=git',
1922 'git', 'grep', '--null', '--color=Always'] + args)
davidbarr@chromium.org12f944e2012-03-01 02:18:31 +00001923
1924
stip@chromium.orga735da22015-04-29 23:18:20 +00001925def CMDroot(parser, args):
1926 """Outputs the solution root (or current dir if there isn't one)."""
1927 (options, args) = parser.parse_args(args)
1928 client = GClient.LoadCurrentConfig(options)
1929 if client:
1930 print(os.path.abspath(client.root_dir))
1931 else:
1932 print(os.path.abspath('.'))
1933
1934
agablea98a6cd2016-11-15 14:30:10 -08001935@subcommand.usage('[url]')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001936def CMDconfig(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001937 """Creates a .gclient file in the current directory.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001938
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001939 This specifies the configuration for further commands. After update/sync,
1940 top-level DEPS files in each module are read to determine dependent
1941 modules to operate on as well. If optional [url] parameter is
1942 provided, then configuration is read from a specified Subversion server
1943 URL.
1944 """
szager@chromium.orge2e03202012-07-31 18:05:16 +00001945 # We do a little dance with the --gclientfile option. 'gclient config' is the
1946 # only command where it's acceptable to have both '--gclientfile' and '--spec'
1947 # arguments. So, we temporarily stash any --gclientfile parameter into
1948 # options.output_config_file until after the (gclientfile xor spec) error
1949 # check.
1950 parser.remove_option('--gclientfile')
1951 parser.add_option('--gclientfile', dest='output_config_file',
1952 help='Specify an alternate .gclient file')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001953 parser.add_option('--name',
1954 help='overrides the default name for the solution')
nsylvain@google.comefc80932011-05-31 21:27:56 +00001955 parser.add_option('--deps-file', default='DEPS',
1956 help='overrides the default name for the DEPS file for the'
1957 'main solutions and all sub-dependencies')
smutae7ea312016-07-18 11:59:41 -07001958 parser.add_option('--unmanaged', action='store_true', default=False,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001959 help='overrides the default behavior to make it possible '
smutae7ea312016-07-18 11:59:41 -07001960 'to have the main solution untouched by gclient '
1961 '(gclient will check out unmanaged dependencies but '
1962 'will never sync them)')
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001963 parser.add_option('--cache-dir',
1964 help='(git only) Cache all git repos into this dir and do '
1965 'shared clones from the cache, instead of cloning '
1966 'directly from the remote. (experimental)')
szager@chromium.orge2e03202012-07-31 18:05:16 +00001967 parser.set_defaults(config_filename=None)
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001968 (options, args) = parser.parse_args(args)
szager@chromium.orge2e03202012-07-31 18:05:16 +00001969 if options.output_config_file:
1970 setattr(options, 'config_filename', getattr(options, 'output_config_file'))
maruel@chromium.org5fc2a332010-05-26 19:37:15 +00001971 if ((options.spec and args) or len(args) > 2 or
1972 (not options.spec and not args)):
1973 parser.error('Inconsistent arguments. Use either --spec or one or 2 args')
1974
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001975 client = GClient('.', options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001976 if options.spec:
1977 client.SetConfig(options.spec)
1978 else:
maruel@chromium.org1ab7ffc2009-06-03 17:21:37 +00001979 base_url = args[0].rstrip('/')
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001980 if not options.name:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001981 name = base_url.split('/')[-1]
nsylvain@google.com12649ef2011-06-01 17:11:20 +00001982 if name.endswith('.git'):
1983 name = name[:-4]
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001984 else:
1985 # specify an alternate relpath for the given URL.
1986 name = options.name
agable@chromium.orgf2214672015-10-27 21:02:48 +00001987 if not os.path.abspath(os.path.join(os.getcwd(), name)).startswith(
1988 os.getcwd()):
1989 parser.error('Do not pass a relative path for --name.')
1990 if any(x in ('..', '.', '/', '\\') for x in name.split(os.sep)):
1991 parser.error('Do not include relative path components in --name.')
1992
nsylvain@google.comefc80932011-05-31 21:27:56 +00001993 deps_file = options.deps_file
agablea98a6cd2016-11-15 14:30:10 -08001994 client.SetDefaultConfig(name, deps_file, base_url,
smutae7ea312016-07-18 11:59:41 -07001995 managed=not options.unmanaged,
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001996 cache_dir=options.cache_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001997 client.SaveConfig()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001998 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001999
2000
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002001@subcommand.epilog("""Example:
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002002 gclient pack > patch.txt
2003 generate simple patch for configured client and dependences
2004""")
2005def CMDpack(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002006 """Generates a patch which can be applied at the root of the tree.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00002007
agabled437d762016-10-17 09:35:11 -07002008 Internally, runs 'git diff' on each checked out module and
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002009 dependencies, and performs minimal postprocessing of the output. The
2010 resulting patch is printed to stdout and can be applied to a freshly
2011 checked out tree via 'patch -p0 < patchfile'.
2012 """
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002013 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2014 help='override deps for the specified (comma-separated) '
2015 'platform(s); \'all\' will process all deps_os '
2016 'references')
haitao.feng@intel.com306080c2012-05-04 13:11:29 +00002017 parser.remove_option('--jobs')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002018 (options, args) = parser.parse_args(args)
iannucci@chromium.org50395ea2013-04-04 04:47:42 +00002019 # Force jobs to 1 so the stdout is not annotated with the thread ids
haitao.feng@intel.com306080c2012-05-04 13:11:29 +00002020 options.jobs = 1
kbr@google.comab318592009-09-04 00:54:55 +00002021 client = GClient.LoadCurrentConfig(options)
2022 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002023 raise gclient_utils.Error('client not configured; see \'gclient config\'')
kbr@google.comab318592009-09-04 00:54:55 +00002024 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002025 client.PrintLocationAndContents()
kbr@google.comab318592009-09-04 00:54:55 +00002026 return client.RunOnDeps('pack', args)
2027
2028
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002029def CMDstatus(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002030 """Shows modification status for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002031 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2032 help='override deps for the specified (comma-separated) '
2033 'platform(s); \'all\' will process all deps_os '
2034 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002035 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002036 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002037 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002038 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002039 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002040 client.PrintLocationAndContents()
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002041 return client.RunOnDeps('status', args)
2042
2043
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002044@subcommand.epilog("""Examples:
maruel@chromium.org79692d62010-05-14 18:57:13 +00002045 gclient sync
2046 update files from SCM according to current configuration,
2047 *for modules which have changed since last update or sync*
2048 gclient sync --force
2049 update files from SCM according to current configuration, for
2050 all modules (useful for recovering files deleted from local copy)
2051 gclient sync --revision src@31000
2052 update src directory to r31000
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002053
2054JSON output format:
2055If the --output-json option is specified, the following document structure will
2056be emitted to the provided file. 'null' entries may occur for subprojects which
2057are present in the gclient solution, but were not processed (due to custom_deps,
2058os_deps, etc.)
2059
2060{
2061 "solutions" : {
2062 "<name>": { # <name> is the posix-normalized path to the solution.
agabled437d762016-10-17 09:35:11 -07002063 "revision": [<git id hex string>|null],
2064 "scm": ["git"|null],
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002065 }
2066 }
2067}
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002068""")
2069def CMDsync(parser, args):
2070 """Checkout/update all modules."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002071 parser.add_option('-f', '--force', action='store_true',
2072 help='force update even for unchanged modules')
2073 parser.add_option('-n', '--nohooks', action='store_true',
2074 help='don\'t run hooks after the update is complete')
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00002075 parser.add_option('-p', '--noprehooks', action='store_true',
2076 help='don\'t run pre-DEPS hooks', default=False)
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002077 parser.add_option('-r', '--revision', action='append',
2078 dest='revisions', metavar='REV', default=[],
2079 help='Enforces revision/hash for the solutions with the '
2080 'format src@rev. The src@ part is optional and can be '
2081 'skipped. -r can be used multiple times when .gclient '
2082 'has multiple solutions configured and will work even '
agablea98a6cd2016-11-15 14:30:10 -08002083 'if the src@ part is skipped.')
maruel@chromium.org794207e2013-03-08 15:29:43 +00002084 parser.add_option('--with_branch_heads', action='store_true',
2085 help='Clone git "branch_heads" refspecs in addition to '
2086 'the default refspecs. This adds about 1/2GB to a '
2087 'full checkout. (git only)')
szager@chromium.org8d3348f2014-08-19 22:49:16 +00002088 parser.add_option('--with_tags', action='store_true',
2089 help='Clone git tags in addition to the default refspecs.')
agable2697cd12016-06-28 10:23:53 -07002090 parser.add_option('-H', '--head', action='store_true',
agablea98a6cd2016-11-15 14:30:10 -08002091 help='DEPRECATED: only made sense with safesync urls.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002092 parser.add_option('-D', '--delete_unversioned_trees', action='store_true',
steveblock@chromium.org98e69452012-02-16 16:36:43 +00002093 help='Deletes from the working copy any dependencies that '
2094 'have been removed since the last sync, as long as '
2095 'there are no local modifications. When used with '
2096 '--force, such dependencies are removed even if they '
2097 'have local modifications. When used with --reset, '
2098 'all untracked directories are removed from the '
tnagel@chromium.orga2aaa632014-02-28 21:47:27 +00002099 'working copy, excluding those which are explicitly '
steveblock@chromium.org98e69452012-02-16 16:36:43 +00002100 'ignored in the repository.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002101 parser.add_option('-R', '--reset', action='store_true',
2102 help='resets any local changes before updating (git only)')
bauerb@chromium.org2aad1b22011-07-22 12:00:41 +00002103 parser.add_option('-M', '--merge', action='store_true',
2104 help='merge upstream changes instead of trying to '
2105 'fast-forward or rebase')
dnj@chromium.org5b23e872015-02-20 21:25:57 +00002106 parser.add_option('-A', '--auto_rebase', action='store_true',
2107 help='Automatically rebase repositories against local '
2108 'checkout during update (git only).')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002109 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2110 help='override deps for the specified (comma-separated) '
2111 'platform(s); \'all\' will process all deps_os '
2112 'references')
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +00002113 parser.add_option('--upstream', action='store_true',
2114 help='Make repo state match upstream branch.')
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002115 parser.add_option('--output-json',
2116 help='Output a json document to this path containing '
2117 'summary information about the sync.')
primiano@chromium.org5439ea52014-08-06 17:18:18 +00002118 parser.add_option('--no-history', action='store_true',
2119 help='GIT ONLY - Reduces the size/time of the checkout at '
2120 'the cost of no history. Requires Git 1.9+')
hinoka@chromium.org46b87412014-05-15 00:42:05 +00002121 parser.add_option('--shallow', action='store_true',
2122 help='GIT ONLY - Do a shallow clone into the cache dir. '
2123 'Requires Git 1.9+')
e.hakkinen@samsung.come8bc1aa2015-04-08 08:00:37 +00002124 parser.add_option('--no_bootstrap', '--no-bootstrap',
2125 action='store_true',
2126 help='Don\'t bootstrap from Google Storage.')
hinoka@chromium.org8a10f6d2014-06-23 18:38:57 +00002127 parser.add_option('--ignore_locks', action='store_true',
2128 help='GIT ONLY - Ignore cache locks.')
iannucci@chromium.org30a07982016-04-07 21:35:19 +00002129 parser.add_option('--break_repo_locks', action='store_true',
2130 help='GIT ONLY - Forcibly remove repo locks (e.g. '
2131 'index.lock). This should only be used if you know for '
2132 'certain that this invocation of gclient is the only '
2133 'thing operating on the git repos (e.g. on a bot).')
nodir@chromium.org5b48e482016-03-18 20:27:54 +00002134 parser.add_option('--lock_timeout', type='int', default=5000,
szager@chromium.orgdbb6f822016-02-02 22:59:30 +00002135 help='GIT ONLY - Deadline (in seconds) to wait for git '
nodir@chromium.org5b48e482016-03-18 20:27:54 +00002136 'cache lock to become available. Default is %default.')
agabled437d762016-10-17 09:35:11 -07002137 # TODO(agable): Remove these when the oldest CrOS release milestone is M56.
2138 parser.add_option('-t', '--transitive', action='store_true',
2139 help='DEPRECATED: This is a no-op.')
sdefresne69b1be12016-10-18 05:48:02 -07002140 parser.add_option('-m', '--manually_grab_svn_rev', action='store_true',
agabled437d762016-10-17 09:35:11 -07002141 help='DEPRECATED: This is a no-op.')
Paweł Hajdan, Jr7c7b5592017-05-23 15:06:05 +02002142 # TODO(phajdan.jr): Remove validation options once default (crbug/570091).
Paweł Hajdan, Jr694773d2017-05-29 16:06:23 +02002143 parser.add_option('--validate-syntax', action='store_true', default=True,
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +02002144 help='Validate the .gclient and DEPS syntax')
Paweł Hajdan, Jr7c7b5592017-05-23 15:06:05 +02002145 parser.add_option('--disable-syntax-validation', action='store_false',
2146 dest='validate_syntax',
2147 help='Disable validation of .gclient and DEPS syntax.')
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
2151 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002152 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002153
smutae7ea312016-07-18 11:59:41 -07002154 if options.revisions and options.head:
2155 # TODO(maruel): Make it a parser.error if it doesn't break any builder.
2156 print('Warning: you cannot use both --head and --revision')
2157
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002158 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002159 client.PrintLocationAndContents()
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002160 ret = client.RunOnDeps('update', args)
2161 if options.output_json:
2162 slns = {}
2163 for d in client.subtree(True):
2164 normed = d.name.replace('\\', '/').rstrip('/') + '/'
2165 slns[normed] = {
2166 'revision': d.got_revision,
2167 'scm': d.used_scm.name if d.used_scm else None,
hinoka@chromium.org17db9052014-05-10 01:11:29 +00002168 'url': str(d.url) if d.url else None,
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002169 }
2170 with open(options.output_json, 'wb') as f:
2171 json.dump({'solutions': slns}, f)
2172 return ret
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002173
2174
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002175CMDupdate = CMDsync
2176
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002177
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +02002178def CMDvalidate(parser, args):
2179 """Validates the .gclient and DEPS syntax."""
2180 options, args = parser.parse_args(args)
2181 options.validate_syntax = True
2182 client = GClient.LoadCurrentConfig(options)
2183 rv = client.RunOnDeps('validate', args)
2184 if rv == 0:
2185 print('validate: SUCCESS')
2186 else:
2187 print('validate: FAILURE')
2188 return rv
2189
2190
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002191def CMDdiff(parser, args):
2192 """Displays local diff for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002193 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2194 help='override deps for the specified (comma-separated) '
2195 'platform(s); \'all\' will process all deps_os '
2196 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002197 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002198 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002199 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002200 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002201 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002202 client.PrintLocationAndContents()
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002203 return client.RunOnDeps('diff', args)
2204
2205
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002206def CMDrevert(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002207 """Reverts all modifications in every dependencies.
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00002208
2209 That's the nuclear option to get back to a 'clean' state. It removes anything
agabled437d762016-10-17 09:35:11 -07002210 that shows up in git status."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002211 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2212 help='override deps for the specified (comma-separated) '
2213 'platform(s); \'all\' will process all deps_os '
2214 'references')
2215 parser.add_option('-n', '--nohooks', action='store_true',
2216 help='don\'t run hooks after the revert is complete')
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00002217 parser.add_option('-p', '--noprehooks', action='store_true',
2218 help='don\'t run pre-DEPS hooks', default=False)
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +00002219 parser.add_option('--upstream', action='store_true',
2220 help='Make repo state match upstream branch.')
iannucci@chromium.orgbf525dc2016-04-07 22:00:28 +00002221 parser.add_option('--break_repo_locks', action='store_true',
2222 help='GIT ONLY - Forcibly remove repo locks (e.g. '
2223 'index.lock). This should only be used if you know for '
2224 'certain that this invocation of gclient is the only '
2225 'thing operating on the git repos (e.g. on a bot).')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002226 (options, args) = parser.parse_args(args)
2227 # --force is implied.
2228 options.force = True
steveblock@chromium.org98e69452012-02-16 16:36:43 +00002229 options.reset = False
2230 options.delete_unversioned_trees = False
agablec903d732016-07-26 09:07:24 -07002231 options.merge = False
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002232 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002233 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002234 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002235 return client.RunOnDeps('revert', args)
2236
2237
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002238def CMDrunhooks(parser, args):
2239 """Runs hooks for files that have been modified in the local working copy."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002240 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2241 help='override deps for the specified (comma-separated) '
2242 'platform(s); \'all\' will process all deps_os '
2243 'references')
2244 parser.add_option('-f', '--force', action='store_true', default=True,
2245 help='Deprecated. No effect.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002246 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002247 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002248 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002249 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002250 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002251 client.PrintLocationAndContents()
maruel@chromium.org5df6a462009-08-28 18:52:26 +00002252 options.force = True
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002253 options.nohooks = False
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002254 return client.RunOnDeps('runhooks', args)
2255
2256
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002257def CMDrevinfo(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002258 """Outputs revision info mapping for the client and its dependencies.
maruel@chromium.org9eda4112010-06-11 18:56:10 +00002259
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002260 This allows the capture of an overall 'revision' for the source tree that
maruel@chromium.org9eda4112010-06-11 18:56:10 +00002261 can be used to reproduce the same tree in the future. It is only useful for
agabled437d762016-10-17 09:35:11 -07002262 'unpinned dependencies', i.e. DEPS/deps references without a git hash.
2263 A git branch name isn't 'pinned' since the actual commit can change.
maruel@chromium.org9eda4112010-06-11 18:56:10 +00002264 """
2265 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2266 help='override deps for the specified (comma-separated) '
2267 'platform(s); \'all\' will process all deps_os '
2268 'references')
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00002269 parser.add_option('-a', '--actual', action='store_true',
2270 help='gets the actual checked out revisions instead of the '
2271 'ones specified in the DEPS and .gclient files')
maruel@chromium.org9eda4112010-06-11 18:56:10 +00002272 parser.add_option('-s', '--snapshot', action='store_true',
2273 help='creates a snapshot .gclient file of the current '
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00002274 'version of all repositories to reproduce the tree, '
2275 'implies -a')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002276 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002277 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002278 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002279 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002280 client.PrintRevInfo()
maruel@chromium.org79692d62010-05-14 18:57:13 +00002281 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002282
2283
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00002284def CMDverify(parser, args):
2285 """Verifies the DEPS file deps are only from allowed_hosts."""
2286 (options, args) = parser.parse_args(args)
2287 client = GClient.LoadCurrentConfig(options)
2288 if not client:
2289 raise gclient_utils.Error('client not configured; see \'gclient config\'')
2290 client.RunOnDeps(None, [])
2291 # Look at each first-level dependency of this gclient only.
2292 for dep in client.dependencies:
2293 bad_deps = dep.findDepsFromNotAllowedHosts()
2294 if not bad_deps:
2295 continue
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002296 print("There are deps from not allowed hosts in file %s" % dep.deps_file)
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00002297 for bad_dep in bad_deps:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002298 print("\t%s at %s" % (bad_dep.name, bad_dep.url))
2299 print("allowed_hosts:", ', '.join(dep.allowed_hosts))
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00002300 sys.stdout.flush()
2301 raise gclient_utils.Error(
2302 'dependencies from disallowed hosts; check your DEPS file.')
2303 return 0
2304
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002305class OptionParser(optparse.OptionParser):
szager@chromium.orge2e03202012-07-31 18:05:16 +00002306 gclientfile_default = os.environ.get('GCLIENT_FILE', '.gclient')
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002307
2308 def __init__(self, **kwargs):
2309 optparse.OptionParser.__init__(
2310 self, version='%prog ' + __version__, **kwargs)
2311
2312 # Some arm boards have issues with parallel sync.
2313 if platform.machine().startswith('arm'):
2314 jobs = 1
2315 else:
2316 jobs = max(8, gclient_utils.NumLocalCpus())
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002317
2318 self.add_option(
2319 '-j', '--jobs', default=jobs, type='int',
2320 help='Specify how many SCM commands can run in parallel; defaults to '
tnagel@chromium.orga2aaa632014-02-28 21:47:27 +00002321 '%default on this machine')
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002322 self.add_option(
2323 '-v', '--verbose', action='count', default=0,
2324 help='Produces additional output for diagnostics. Can be used up to '
2325 'three times for more logging info.')
2326 self.add_option(
2327 '--gclientfile', dest='config_filename',
2328 help='Specify an alternate %s file' % self.gclientfile_default)
2329 self.add_option(
2330 '--spec',
2331 help='create a gclient file containing the provided string. Due to '
2332 'Cygwin/Python brokenness, it can\'t contain any newlines.')
2333 self.add_option(
2334 '--no-nag-max', default=False, action='store_true',
scottmg@chromium.orgf547c802013-09-27 17:55:26 +00002335 help='Ignored for backwards compatibility.')
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002336
2337 def parse_args(self, args=None, values=None):
2338 """Integrates standard options processing."""
2339 options, args = optparse.OptionParser.parse_args(self, args, values)
2340 levels = [logging.ERROR, logging.WARNING, logging.INFO, logging.DEBUG]
2341 logging.basicConfig(
2342 level=levels[min(options.verbose, len(levels) - 1)],
maruel@chromium.org0895b752011-08-26 20:40:33 +00002343 format='%(module)s(%(lineno)d) %(funcName)s:%(message)s')
szager@chromium.orge2e03202012-07-31 18:05:16 +00002344 if options.config_filename and options.spec:
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002345 self.error('Cannot specifiy both --gclientfile and --spec')
rdsmith@chromium.orgd9591f02014-02-05 19:28:20 +00002346 if (options.config_filename and
2347 options.config_filename != os.path.basename(options.config_filename)):
2348 self.error('--gclientfile target must be a filename, not a path')
szager@chromium.orge2e03202012-07-31 18:05:16 +00002349 if not options.config_filename:
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002350 options.config_filename = self.gclientfile_default
maruel@chromium.org0895b752011-08-26 20:40:33 +00002351 options.entries_filename = options.config_filename + '_entries'
2352 if options.jobs < 1:
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002353 self.error('--jobs must be 1 or higher')
maruel@chromium.org0895b752011-08-26 20:40:33 +00002354
2355 # These hacks need to die.
2356 if not hasattr(options, 'revisions'):
2357 # GClient.RunOnDeps expects it even if not applicable.
2358 options.revisions = []
smutae7ea312016-07-18 11:59:41 -07002359 if not hasattr(options, 'head'):
2360 options.head = None
maruel@chromium.org0895b752011-08-26 20:40:33 +00002361 if not hasattr(options, 'nohooks'):
2362 options.nohooks = True
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00002363 if not hasattr(options, 'noprehooks'):
2364 options.noprehooks = True
maruel@chromium.org0895b752011-08-26 20:40:33 +00002365 if not hasattr(options, 'deps_os'):
2366 options.deps_os = None
maruel@chromium.org0895b752011-08-26 20:40:33 +00002367 if not hasattr(options, 'force'):
2368 options.force = None
2369 return (options, args)
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002370
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002371
2372def disable_buffering():
2373 # Make stdout auto-flush so buildbot doesn't kill us during lengthy
2374 # operations. Python as a strong tendency to buffer sys.stdout.
2375 sys.stdout = gclient_utils.MakeFileAutoFlush(sys.stdout)
2376 # Make stdout annotated with the thread ids.
2377 sys.stdout = gclient_utils.MakeFileAnnotated(sys.stdout)
maruel@chromium.org0895b752011-08-26 20:40:33 +00002378
2379
sbc@chromium.org013731e2015-02-26 18:28:43 +00002380def main(argv):
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002381 """Doesn't parse the arguments here, just find the right subcommand to
2382 execute."""
maruel@chromium.org82798cb2012-02-23 18:16:12 +00002383 if sys.hexversion < 0x02060000:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002384 print(
maruel@chromium.org82798cb2012-02-23 18:16:12 +00002385 '\nYour python version %s is unsupported, please upgrade.\n' %
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002386 sys.version.split(' ', 1)[0],
2387 file=sys.stderr)
maruel@chromium.org82798cb2012-02-23 18:16:12 +00002388 return 2
bcwhite@chromium.org6683ab42013-02-11 16:13:47 +00002389 if not sys.executable:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002390 print(
2391 '\nPython cannot find the location of it\'s own executable.\n',
2392 file=sys.stderr)
bcwhite@chromium.org6683ab42013-02-11 16:13:47 +00002393 return 2
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002394 fix_encoding.fix_encoding()
2395 disable_buffering()
iannucci@chromium.org596cd5c2016-04-04 21:34:39 +00002396 setup_color.init()
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002397 dispatcher = subcommand.CommandDispatcher(__name__)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00002398 try:
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002399 return dispatcher.execute(OptionParser(), argv)
xusydoc@chromium.org2fd6c3f2013-05-03 21:57:55 +00002400 except KeyboardInterrupt:
2401 gclient_utils.GClientChildren.KillAllRemainingChildren()
2402 raise
vapier@chromium.orga81a56e2015-11-11 07:56:13 +00002403 except (gclient_utils.Error, subprocess2.CalledProcessError) as e:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002404 print('Error: %s' % str(e), file=sys.stderr)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00002405 return 1
borenet@google.com6a9b1682014-03-24 18:35:23 +00002406 finally:
2407 gclient_utils.PrintWarnings()
sbc@chromium.org013731e2015-02-26 18:28:43 +00002408 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002409
2410
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00002411if '__main__' == __name__:
sbc@chromium.org013731e2015-02-26 18:28:43 +00002412 try:
2413 sys.exit(main(sys.argv[1:]))
2414 except KeyboardInterrupt:
2415 sys.stderr.write('interrupted\n')
2416 sys.exit(1)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002417
2418# vim: ts=2:sw=2:tw=80:et: