blob: b25dfaa317db52b51c230cff5b8c35480dabb380 [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, Jrc9364392017-06-14 17:11:56 +0200142 def __init__(self, action, pattern=None, name=None, cwd=None):
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200143 """Constructor.
144
145 Arguments:
146 action (list of basestring): argv of the command to run
147 pattern (basestring regex): noop with git; deprecated
148 name (basestring): optional name; no effect on operation
Paweł Hajdan, Jrc9364392017-06-14 17:11:56 +0200149 cwd (basestring): working directory to use
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200150 """
151 self._action = gclient_utils.freeze(action)
152 self._pattern = pattern
153 self._name = name
Paweł Hajdan, Jrc9364392017-06-14 17:11:56 +0200154 self._cwd = cwd
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200155
156 @staticmethod
157 def from_dict(d):
158 """Creates a Hook instance from a dict like in the DEPS file."""
Paweł Hajdan, Jrc9364392017-06-14 17:11:56 +0200159 return Hook(d['action'], d.get('pattern'), d.get('name'), d.get('cwd'))
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200160
161 @property
162 def action(self):
163 return self._action
164
165 @property
166 def pattern(self):
167 return self._pattern
168
169 @property
170 def name(self):
171 return self._name
172
173 def matches(self, file_list):
174 """Returns true if the pattern matches any of files in the list."""
175 if not self._pattern:
176 return True
177 pattern = re.compile(self._pattern)
178 return bool([f for f in file_list if pattern.search(f)])
179
180 def run(self, root):
181 """Executes the hook's command."""
182 cmd = list(self._action)
183 if cmd[0] == 'python':
184 # If the hook specified "python" as the first item, the action is a
185 # Python script. Run it by starting a new copy of the same
186 # interpreter.
187 cmd[0] = sys.executable
Paweł Hajdan, Jrc9364392017-06-14 17:11:56 +0200188
189 cwd = root
190 if self._cwd:
191 cwd = os.path.join(cwd, self._cwd)
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200192 try:
193 start_time = time.time()
194 gclient_utils.CheckCallAndFilterAndHeader(
Paweł Hajdan, Jrc9364392017-06-14 17:11:56 +0200195 cmd, cwd=cwd, always=True)
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200196 except (gclient_utils.Error, subprocess2.CalledProcessError) as e:
197 # Use a discrete exit status code of 2 to indicate that a hook action
198 # failed. Users of this script may wish to treat hook action failures
199 # differently from VC failures.
200 print('Error: %s' % str(e), file=sys.stderr)
201 sys.exit(2)
202 finally:
203 elapsed_time = time.time() - start_time
204 if elapsed_time > 10:
205 print("Hook '%s' took %.2f secs" % (
206 gclient_utils.CommandToStr(cmd), elapsed_time))
207
208
maruel@chromium.org116704f2010-06-11 17:34:38 +0000209class GClientKeywords(object):
maruel@chromium.org116704f2010-06-11 17:34:38 +0000210 class VarImpl(object):
211 def __init__(self, custom_vars, local_scope):
212 self._custom_vars = custom_vars
213 self._local_scope = local_scope
214
215 def Lookup(self, var_name):
216 """Implements the Var syntax."""
217 if var_name in self._custom_vars:
218 return self._custom_vars[var_name]
219 elif var_name in self._local_scope.get("vars", {}):
220 return self._local_scope["vars"][var_name]
221 raise gclient_utils.Error("Var is not defined: %s" % var_name)
222
223
maruel@chromium.org064186c2011-09-27 23:53:33 +0000224class DependencySettings(GClientKeywords):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000225 """Immutable configuration settings."""
226 def __init__(
agablea98a6cd2016-11-15 14:30:10 -0800227 self, parent, url, managed, custom_deps, custom_vars,
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +0200228 custom_hooks, deps_file, should_process, relative,
229 condition, condition_value):
maruel@chromium.org064186c2011-09-27 23:53:33 +0000230 GClientKeywords.__init__(self)
231
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000232 # These are not mutable:
233 self._parent = parent
mmoss@chromium.org8f93f792014-08-26 23:24:09 +0000234 self._deps_file = deps_file
maruel@chromium.org064186c2011-09-27 23:53:33 +0000235 self._url = url
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +0200236 # The condition as string (or None). Useful to keep e.g. for flatten.
237 self._condition = condition
238 # Boolean value of the condition. If there's no condition, just True.
239 self._condition_value = condition_value
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000240 # 'managed' determines whether or not this dependency is synced/updated by
241 # gclient after gclient checks it out initially. The difference between
242 # 'managed' and 'should_process' is that the user specifies 'managed' via
smutae7ea312016-07-18 11:59:41 -0700243 # the --unmanaged command-line flag or a .gclient config, where
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000244 # 'should_process' is dynamically set by gclient if it goes over its
245 # recursion limit and controls gclient's behavior so it does not misbehave.
246 self._managed = managed
247 self._should_process = should_process
agabledce6ddc2016-09-08 10:02:16 -0700248 # If this is a recursed-upon sub-dependency, and the parent has
249 # use_relative_paths set, then this dependency should check out its own
250 # dependencies relative to that parent's path for this, rather than
251 # relative to the .gclient file.
252 self._relative = relative
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +0000253 # This is a mutable value which has the list of 'target_os' OSes listed in
254 # the current deps file.
255 self.local_target_os = None
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000256
257 # These are only set in .gclient and not in DEPS files.
258 self._custom_vars = custom_vars or {}
259 self._custom_deps = custom_deps or {}
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000260 self._custom_hooks = custom_hooks or []
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000261
maruel@chromium.org064186c2011-09-27 23:53:33 +0000262 # Post process the url to remove trailing slashes.
263 if isinstance(self._url, basestring):
264 # urls are sometime incorrectly written as proto://host/path/@rev. Replace
265 # it to proto://host/path@rev.
maruel@chromium.org064186c2011-09-27 23:53:33 +0000266 self._url = self._url.replace('/@', '@')
Paweł Hajdan, Jr7e9303b2017-05-23 14:38:27 +0200267 elif not isinstance(self._url, (None.__class__)):
maruel@chromium.org064186c2011-09-27 23:53:33 +0000268 raise gclient_utils.Error(
Paweł Hajdan, Jr7e9303b2017-05-23 14:38:27 +0200269 ('dependency url must be either string or None, '
270 'instead of %s') % self._url.__class__.__name__)
mmoss@chromium.orgd0b272b2013-01-30 23:55:33 +0000271 # Make any deps_file path platform-appropriate.
272 for sep in ['/', '\\']:
273 self._deps_file = self._deps_file.replace(sep, os.sep)
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000274
275 @property
276 def deps_file(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000277 return self._deps_file
278
279 @property
280 def managed(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000281 return self._managed
282
283 @property
284 def parent(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000285 return self._parent
286
287 @property
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000288 def root(self):
289 """Returns the root node, a GClient object."""
290 if not self.parent:
291 # This line is to signal pylint that it could be a GClient instance.
292 return self or GClient(None, None)
293 return self.parent.root
294
295 @property
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000296 def should_process(self):
297 """True if this dependency should be processed, i.e. checked out."""
298 return self._should_process
299
300 @property
301 def custom_vars(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000302 return self._custom_vars.copy()
303
304 @property
305 def custom_deps(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000306 return self._custom_deps.copy()
307
maruel@chromium.org064186c2011-09-27 23:53:33 +0000308 @property
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000309 def custom_hooks(self):
310 return self._custom_hooks[:]
311
312 @property
maruel@chromium.org064186c2011-09-27 23:53:33 +0000313 def url(self):
314 return self._url
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000315
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000316 @property
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +0200317 def condition(self):
318 return self._condition
319
320 @property
321 def condition_value(self):
322 return self._condition_value
323
324 @property
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +0000325 def target_os(self):
326 if self.local_target_os is not None:
327 return tuple(set(self.local_target_os).union(self.parent.target_os))
328 else:
329 return self.parent.target_os
330
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000331 def get_custom_deps(self, name, url):
332 """Returns a custom deps if applicable."""
333 if self.parent:
334 url = self.parent.get_custom_deps(name, url)
335 # None is a valid return value to disable a dependency.
336 return self.custom_deps.get(name, url)
337
maruel@chromium.org064186c2011-09-27 23:53:33 +0000338
339class Dependency(gclient_utils.WorkItem, DependencySettings):
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000340 """Object that represents a dependency checkout."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000341
agablea98a6cd2016-11-15 14:30:10 -0800342 def __init__(self, parent, name, url, managed, custom_deps,
agabledce6ddc2016-09-08 10:02:16 -0700343 custom_vars, custom_hooks, deps_file, should_process,
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +0200344 relative, condition, condition_value):
maruel@chromium.org6ca8bf82011-09-19 23:04:30 +0000345 gclient_utils.WorkItem.__init__(self, name)
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000346 DependencySettings.__init__(
agablea98a6cd2016-11-15 14:30:10 -0800347 self, parent, url, managed, custom_deps, custom_vars,
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +0200348 custom_hooks, deps_file, should_process, relative,
349 condition, condition_value)
maruel@chromium.org68988972011-09-20 14:11:42 +0000350
351 # This is in both .gclient and DEPS files:
maruel@chromium.org064186c2011-09-27 23:53:33 +0000352 self._deps_hooks = []
maruel@chromium.org68988972011-09-20 14:11:42 +0000353
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000354 self._pre_deps_hooks = []
355
maruel@chromium.org68988972011-09-20 14:11:42 +0000356 # Calculates properties:
maruel@chromium.org064186c2011-09-27 23:53:33 +0000357 self._parsed_url = None
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +0000358 self._dependencies = []
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200359 # Keep track of original values, before post-processing (e.g. deps_os).
360 self._orig_dependencies = []
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +0200361 self._vars = {}
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +0200362 self._os_dependencies = {}
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200363
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000364 # A cache of the files affected by the current operation, necessary for
365 # hooks.
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000366 self._file_list = []
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000367 # List of host names from which dependencies are allowed.
368 # Default is an empty set, meaning unspecified in DEPS file, and hence all
369 # hosts will be allowed. Non-empty set means whitelist of hosts.
370 # allowed_hosts var is scoped to its DEPS file, and so it isn't recursive.
371 self._allowed_hosts = frozenset()
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +0200372 # Spec for .gni output to write (if any).
373 self._gn_args_file = None
374 self._gn_args = []
maruel@chromium.org85c2a192010-07-22 21:14:43 +0000375 # If it is not set to True, the dependency wasn't processed for its child
376 # dependency, i.e. its DEPS wasn't read.
maruel@chromium.org064186c2011-09-27 23:53:33 +0000377 self._deps_parsed = False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000378 # This dependency has been processed, i.e. checked out
maruel@chromium.org064186c2011-09-27 23:53:33 +0000379 self._processed = False
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000380 # This dependency had its pre-DEPS hooks run
381 self._pre_deps_hooks_ran = False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000382 # This dependency had its hook run
maruel@chromium.org064186c2011-09-27 23:53:33 +0000383 self._hooks_ran = False
kustermann@google.coma692e8f2013-04-18 08:32:04 +0000384 # This is the scm used to checkout self.url. It may be used by dependencies
385 # to get the datetime of the revision we checked out.
386 self._used_scm = None
szager@chromium.org4ad264b2014-05-20 04:43:47 +0000387 self._used_revision = None
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +0000388 # The actual revision we ended up getting, or None if that information is
389 # unavailable
390 self._got_revision = None
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000391
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000392 # This is a mutable value that overrides the normal recursion limit for this
393 # dependency. It is read from the actual DEPS file so cannot be set on
394 # class instantiation.
395 self.recursion_override = None
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000396 # recursedeps is a mutable value that selectively overrides the default
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000397 # 'no recursion' setting on a dep-by-dep basis. It will replace
398 # recursion_override.
iannucci@chromium.orgafa11ac2016-05-04 22:17:34 +0000399 #
400 # It will be a dictionary of {deps_name: {"deps_file": depfile_name}} or
401 # None.
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000402 self.recursedeps = None
hinoka885e5b12016-06-08 14:40:09 -0700403 # This is inherited from WorkItem. We want the URL to be a resource.
404 if url and isinstance(url, basestring):
405 # The url is usually given to gclient either as https://blah@123
qyearsley12fa6ff2016-08-24 09:18:40 -0700406 # or just https://blah. The @123 portion is irrelevant.
hinoka885e5b12016-06-08 14:40:09 -0700407 self.resources.append(url.split('@')[0])
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000408
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000409 if not self.name and self.parent:
410 raise gclient_utils.Error('Dependency without name')
411
maruel@chromium.org470b5432011-10-11 18:18:19 +0000412 @property
413 def requirements(self):
414 """Calculate the list of requirements."""
415 requirements = set()
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000416 # self.parent is implicitly a requirement. This will be recursive by
417 # definition.
418 if self.parent and self.parent.name:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000419 requirements.add(self.parent.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000420
421 # For a tree with at least 2 levels*, the leaf node needs to depend
422 # on the level higher up in an orderly way.
423 # This becomes messy for >2 depth as the DEPS file format is a dictionary,
424 # thus unsorted, while the .gclient format is a list thus sorted.
425 #
426 # * _recursion_limit is hard coded 2 and there is no hope to change this
427 # value.
428 #
429 # Interestingly enough, the following condition only works in the case we
430 # want: self is a 2nd level node. 3nd level node wouldn't need this since
431 # they already have their parent as a requirement.
maruel@chromium.org470b5432011-10-11 18:18:19 +0000432 if self.parent and self.parent.parent and not self.parent.parent.parent:
433 requirements |= set(i.name for i in self.root.dependencies if i.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000434
maruel@chromium.org470b5432011-10-11 18:18:19 +0000435 if self.name:
436 requirements |= set(
437 obj.name for obj in self.root.subtree(False)
438 if (obj is not self
439 and obj.name and
440 self.name.startswith(posixpath.join(obj.name, ''))))
441 requirements = tuple(sorted(requirements))
442 logging.info('Dependency(%s).requirements = %s' % (self.name, requirements))
443 return requirements
444
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000445 @property
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000446 def try_recursedeps(self):
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000447 """Returns False if recursion_override is ever specified."""
448 if self.recursion_override is not None:
449 return False
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000450 return self.parent.try_recursedeps
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000451
452 @property
453 def recursion_limit(self):
454 """Returns > 0 if this dependency is not too recursed to be processed."""
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000455 # We continue to support the absence of recursedeps until tools and DEPS
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000456 # using recursion_override are updated.
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000457 if self.try_recursedeps and self.parent.recursedeps != None:
458 if self.name in self.parent.recursedeps:
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000459 return 1
460
461 if self.recursion_override is not None:
462 return self.recursion_override
463 return max(self.parent.recursion_limit - 1, 0)
464
maruel@chromium.org470b5432011-10-11 18:18:19 +0000465 def verify_validity(self):
466 """Verifies that this Dependency is fine to add as a child of another one.
467
468 Returns True if this entry should be added, False if it is a duplicate of
469 another entry.
470 """
471 logging.info('Dependency(%s).verify_validity()' % self.name)
472 if self.name in [s.name for s in self.parent.dependencies]:
473 raise gclient_utils.Error(
474 'The same name "%s" appears multiple times in the deps section' %
475 self.name)
476 if not self.should_process:
477 # Return early, no need to set requirements.
478 return True
479
480 # This require a full tree traversal with locks.
481 siblings = [d for d in self.root.subtree(False) if d.name == self.name]
482 for sibling in siblings:
maruel@chromium.orgb848d5b2012-10-10 23:25:50 +0000483 self_url = self.LateOverride(self.url)
484 sibling_url = sibling.LateOverride(sibling.url)
485 # Allow to have only one to be None or ''.
486 if self_url != sibling_url and bool(self_url) == bool(sibling_url):
maruel@chromium.org470b5432011-10-11 18:18:19 +0000487 raise gclient_utils.Error(
maruel@chromium.orgb848d5b2012-10-10 23:25:50 +0000488 ('Dependency %s specified more than once:\n'
489 ' %s [%s]\n'
490 'vs\n'
491 ' %s [%s]') % (
492 self.name,
493 sibling.hierarchy(),
494 sibling_url,
495 self.hierarchy(),
496 self_url))
maruel@chromium.org470b5432011-10-11 18:18:19 +0000497 # In theory we could keep it as a shadow of the other one. In
498 # practice, simply ignore it.
499 logging.warn('Won\'t process duplicate dependency %s' % sibling)
500 return False
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000501 return True
maruel@chromium.org064186c2011-09-27 23:53:33 +0000502
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000503 def LateOverride(self, url):
Paweł Hajdan, Jr7e9303b2017-05-23 14:38:27 +0200504 """Resolves the parsed url from url."""
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000505 assert self.parsed_url == None or not self.should_process, self.parsed_url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000506 parsed_url = self.get_custom_deps(self.name, url)
507 if parsed_url != url:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000508 logging.info(
509 'Dependency(%s).LateOverride(%s) -> %s' %
510 (self.name, url, parsed_url))
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000511 return parsed_url
512
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000513 if isinstance(url, basestring):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000514 parsed_url = urlparse.urlparse(url)
scr@chromium.orgf1eccaf2014-04-11 15:51:33 +0000515 if (not parsed_url[0] and
516 not re.match(r'^\w+\@[\w\.-]+\:[\w\/]+', parsed_url[2])):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000517 # A relative url. Fetch the real base.
518 path = parsed_url[2]
519 if not path.startswith('/'):
520 raise gclient_utils.Error(
521 'relative DEPS entry \'%s\' must begin with a slash' % url)
522 # Create a scm just to query the full url.
523 parent_url = self.parent.parsed_url
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000524 scm = gclient_scm.CreateSCM(
525 parent_url, self.root.root_dir, None, self.outbuf)
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000526 parsed_url = scm.FullUrlForRelativeUrl(url)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000527 else:
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000528 parsed_url = url
maruel@chromium.org470b5432011-10-11 18:18:19 +0000529 logging.info(
530 'Dependency(%s).LateOverride(%s) -> %s' %
531 (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000532 return parsed_url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000533
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000534 if url is None:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000535 logging.info(
536 'Dependency(%s).LateOverride(%s) -> %s' % (self.name, url, url))
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000537 return url
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000538
539 raise gclient_utils.Error('Unknown url type')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000540
bratell@opera.comed2b4fe2013-12-16 14:34:12 +0000541 @staticmethod
542 def MergeWithOsDeps(deps, deps_os, target_os_list):
543 """Returns a new "deps" structure that is the deps sent in updated
544 with information from deps_os (the deps_os section of the DEPS
545 file) that matches the list of target os."""
546 os_overrides = {}
547 for the_target_os in target_os_list:
548 the_target_os_deps = deps_os.get(the_target_os, {})
549 for os_dep_key, os_dep_value in the_target_os_deps.iteritems():
550 overrides = os_overrides.setdefault(os_dep_key, [])
551 overrides.append((the_target_os, os_dep_value))
552
553 # If any os didn't specify a value (we have fewer value entries
554 # than in the os list), then it wants to use the default value.
555 for os_dep_key, os_dep_value in os_overrides.iteritems():
556 if len(os_dep_value) != len(target_os_list):
qyearsley12fa6ff2016-08-24 09:18:40 -0700557 # Record the default value too so that we don't accidentally
bratell@opera.comed2b4fe2013-12-16 14:34:12 +0000558 # set it to None or miss a conflicting DEPS.
559 if os_dep_key in deps:
560 os_dep_value.append(('default', deps[os_dep_key]))
561
562 target_os_deps = {}
563 for os_dep_key, os_dep_value in os_overrides.iteritems():
564 # os_dep_value is a list of (os, value) pairs.
565 possible_values = set(x[1] for x in os_dep_value if x[1] is not None)
566 if not possible_values:
567 target_os_deps[os_dep_key] = None
568 else:
569 if len(possible_values) > 1:
570 # It would be possible to abort here but it would be
571 # unfortunate if we end up preventing any kind of checkout.
572 logging.error('Conflicting dependencies for %s: %s. (target_os=%s)',
573 os_dep_key, os_dep_value, target_os_list)
574 # Sorting to get the same result every time in case of conflicts.
575 target_os_deps[os_dep_key] = sorted(possible_values)[0]
576
577 new_deps = deps.copy()
578 new_deps.update(target_os_deps)
579 return new_deps
580
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200581 def _postprocess_deps(self, deps, rel_prefix):
582 """Performs post-processing of deps compared to what's in the DEPS file."""
Paweł Hajdan, Jr4426eaf2017-06-13 12:25:47 +0200583 # Make sure the dict is mutable, e.g. in case it's frozen.
584 deps = dict(deps)
585
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200586 # If a line is in custom_deps, but not in the solution, we want to append
587 # this line to the solution.
588 for d in self.custom_deps:
589 if d not in deps:
590 deps[d] = self.custom_deps[d]
591
592 if rel_prefix:
593 logging.warning('use_relative_paths enabled.')
594 rel_deps = {}
595 for d, url in deps.items():
596 # normpath is required to allow DEPS to use .. in their
597 # dependency local path.
598 rel_deps[os.path.normpath(os.path.join(rel_prefix, d))] = url
599 logging.warning('Updating deps by prepending %s.', rel_prefix)
600 deps = rel_deps
601
602 return deps
603
604 def _deps_to_objects(self, deps, use_relative_paths):
605 """Convert a deps dict to a dict of Dependency objects."""
606 deps_to_add = []
607 for name, dep_value in deps.iteritems():
608 should_process = self.recursion_limit and self.should_process
609 deps_file = self.deps_file
610 if self.recursedeps is not None:
611 ent = self.recursedeps.get(name)
612 if ent is not None:
613 deps_file = ent['deps_file']
614 if dep_value is None:
615 continue
616 condition = None
617 condition_value = True
618 if isinstance(dep_value, basestring):
619 url = dep_value
620 else:
621 # This should be guaranteed by schema checking in gclient_eval.
622 assert isinstance(dep_value, collections.Mapping)
623 url = dep_value['url']
624 condition = dep_value.get('condition')
625 if condition:
626 # TODO(phajdan.jr): should we also take custom vars into account?
627 condition_value = gclient_eval.EvaluateCondition(condition, self._vars)
628 should_process = should_process and condition_value
629 deps_to_add.append(Dependency(
630 self, name, url, None, None, self.custom_vars, None,
631 deps_file, should_process, use_relative_paths, condition,
632 condition_value))
633 deps_to_add.sort(key=lambda x: x.name)
634 return deps_to_add
635
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000636 def ParseDepsFile(self):
maruel@chromium.org271375b2010-06-23 19:17:38 +0000637 """Parses the DEPS file for this dependency."""
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000638 assert not self.deps_parsed
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000639 assert not self.dependencies
iannucci@chromium.org0c3f3052014-05-22 00:29:22 +0000640
641 deps_content = None
642 use_strict = False
cmp@chromium.org76ce73c2014-07-02 00:13:18 +0000643
644 # First try to locate the configured deps file. If it's missing, fallback
645 # to DEPS.
646 deps_files = [self.deps_file]
647 if 'DEPS' not in deps_files:
648 deps_files.append('DEPS')
649 for deps_file in deps_files:
650 filepath = os.path.join(self.root.root_dir, self.name, deps_file)
651 if os.path.isfile(filepath):
652 logging.info(
653 'ParseDepsFile(%s): %s file found at %s', self.name, deps_file,
654 filepath)
655 break
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000656 logging.info(
cmp@chromium.org76ce73c2014-07-02 00:13:18 +0000657 'ParseDepsFile(%s): No %s file found at %s', self.name, deps_file,
658 filepath)
659
660 if os.path.isfile(filepath):
maruel@chromium.org46304292010-10-28 11:42:00 +0000661 deps_content = gclient_utils.FileRead(filepath)
cmp@chromium.org76ce73c2014-07-02 00:13:18 +0000662 logging.debug('ParseDepsFile(%s) read:\n%s', self.name, deps_content)
iannucci@chromium.org0c3f3052014-05-22 00:29:22 +0000663 use_strict = 'use strict' in deps_content.splitlines()[0]
664
665 local_scope = {}
666 if deps_content:
667 # One thing is unintuitive, vars = {} must happen before Var() use.
668 var = self.VarImpl(self.custom_vars, local_scope)
669 if use_strict:
670 logging.info(
671 'ParseDepsFile(%s): Strict Mode Enabled', self.name)
672 global_scope = {
673 '__builtins__': {'None': None},
674 'Var': var.Lookup,
675 'deps_os': {},
676 }
677 else:
678 global_scope = {
iannucci@chromium.org0c3f3052014-05-22 00:29:22 +0000679 'Var': var.Lookup,
680 'deps_os': {},
681 }
maruel@chromium.org46304292010-10-28 11:42:00 +0000682 # Eval the content.
683 try:
Paweł Hajdan, Jrc485d5a2017-06-02 12:08:09 +0200684 if self._get_option('validate_syntax', False):
685 gclient_eval.Exec(deps_content, global_scope, local_scope, filepath)
686 else:
687 exec(deps_content, global_scope, local_scope)
vapier@chromium.orga81a56e2015-11-11 07:56:13 +0000688 except SyntaxError as e:
maruel@chromium.org46304292010-10-28 11:42:00 +0000689 gclient_utils.SyntaxErrorToError(filepath, e)
iannucci@chromium.org0c3f3052014-05-22 00:29:22 +0000690 if use_strict:
691 for key, val in local_scope.iteritems():
692 if not isinstance(val, (dict, list, tuple, str)):
693 raise gclient_utils.Error(
694 'ParseDepsFile(%s): Strict mode disallows %r -> %r' %
695 (self.name, key, val))
696
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000697 if 'allowed_hosts' in local_scope:
698 try:
699 self._allowed_hosts = frozenset(local_scope.get('allowed_hosts'))
700 except TypeError: # raised if non-iterable
701 pass
702 if not self._allowed_hosts:
703 logging.warning("allowed_hosts is specified but empty %s",
704 self._allowed_hosts)
705 raise gclient_utils.Error(
706 'ParseDepsFile(%s): allowed_hosts must be absent '
707 'or a non-empty iterable' % self.name)
708
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +0200709 self._gn_args_file = local_scope.get('gclient_gn_args_file')
710 self._gn_args = local_scope.get('gclient_gn_args', [])
711
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200712 # Since we heavily post-process things, freeze ones which should
713 # reflect original state of DEPS.
714 self._vars = gclient_utils.freeze(local_scope.get('vars', {}))
715
716 # If use_relative_paths is set in the DEPS file, regenerate
717 # the dictionary using paths relative to the directory containing
718 # the DEPS file. Also update recursedeps if use_relative_paths is
719 # enabled.
720 # If the deps file doesn't set use_relative_paths, but the parent did
721 # (and therefore set self.relative on this Dependency object), then we
722 # want to modify the deps and recursedeps by prepending the parent
723 # directory of this dependency.
724 use_relative_paths = local_scope.get('use_relative_paths', False)
725 rel_prefix = None
726 if use_relative_paths:
727 rel_prefix = self.name
728 elif self._relative:
729 rel_prefix = os.path.dirname(self.name)
730
731 deps = local_scope.get('deps', {})
732 orig_deps = gclient_utils.freeze(deps)
733 if 'recursion' in local_scope:
734 self.recursion_override = local_scope.get('recursion')
735 logging.warning(
736 'Setting %s recursion to %d.', self.name, self.recursion_limit)
737 self.recursedeps = None
738 if 'recursedeps' in local_scope:
739 self.recursedeps = {}
740 for ent in local_scope['recursedeps']:
741 if isinstance(ent, basestring):
742 self.recursedeps[ent] = {"deps_file": self.deps_file}
743 else: # (depname, depsfilename)
744 self.recursedeps[ent[0]] = {"deps_file": ent[1]}
745 logging.warning('Found recursedeps %r.', repr(self.recursedeps))
746
747 if rel_prefix:
748 logging.warning('Updating recursedeps by prepending %s.', rel_prefix)
749 rel_deps = {}
750 for depname, options in self.recursedeps.iteritems():
751 rel_deps[
752 os.path.normpath(os.path.join(rel_prefix, depname))] = options
753 self.recursedeps = rel_deps
754
755 # If present, save 'target_os' in the local_target_os property.
756 if 'target_os' in local_scope:
757 self.local_target_os = local_scope['target_os']
758 # load os specific dependencies if defined. these dependencies may
759 # override or extend the values defined by the 'deps' member.
760 target_os_list = self.target_os
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +0200761 if 'deps_os' in local_scope:
762 for dep_os, os_deps in local_scope['deps_os'].iteritems():
763 self._os_dependencies[dep_os] = self._deps_to_objects(
764 self._postprocess_deps(os_deps, rel_prefix), use_relative_paths)
765 if target_os_list:
766 deps = self.MergeWithOsDeps(
767 deps, local_scope['deps_os'], target_os_list)
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200768
769 deps_to_add = self._deps_to_objects(
770 self._postprocess_deps(deps, rel_prefix), use_relative_paths)
771 orig_deps_to_add = self._deps_to_objects(
772 self._postprocess_deps(orig_deps, rel_prefix), use_relative_paths)
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000773
774 # override named sets of hooks by the custom hooks
775 hooks_to_run = []
776 hook_names_to_suppress = [c.get('name', '') for c in self.custom_hooks]
777 for hook in local_scope.get('hooks', []):
778 if hook.get('name', '') not in hook_names_to_suppress:
779 hooks_to_run.append(hook)
Scott Grahamc4826742017-05-11 16:59:23 -0700780 if 'hooks_os' in local_scope and target_os_list:
781 hooks_os = local_scope['hooks_os']
782 # Specifically append these to ensure that hooks_os run after hooks.
783 for the_target_os in target_os_list:
784 the_target_os_hooks = hooks_os.get(the_target_os, [])
785 hooks_to_run.extend(the_target_os_hooks)
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000786
787 # add the replacements and any additions
788 for hook in self.custom_hooks:
789 if 'action' in hook:
790 hooks_to_run.append(hook)
791
Dirk Prankeda3a29e2017-02-27 15:29:36 -0800792 if self.recursion_limit:
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200793 self._pre_deps_hooks = [Hook.from_dict(hook) for hook in
Dirk Prankeda3a29e2017-02-27 15:29:36 -0800794 local_scope.get('pre_deps_hooks', [])]
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000795
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200796 self.add_dependencies_and_close(
797 deps_to_add, hooks_to_run, orig_deps_to_add=orig_deps_to_add)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000798 logging.info('ParseDepsFile(%s) done' % self.name)
799
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +0200800 def _get_option(self, attr, default):
801 obj = self
802 while not hasattr(obj, '_options'):
803 obj = obj.parent
804 return getattr(obj._options, attr, default)
805
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200806 def add_dependencies_and_close(
807 self, deps_to_add, hooks, orig_deps_to_add=None):
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000808 """Adds the dependencies, hooks and mark the parsing as done."""
maruel@chromium.orgb9be0652011-10-14 18:05:40 +0000809 for dep in deps_to_add:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000810 if dep.verify_validity():
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000811 self.add_dependency(dep)
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200812 for dep in (orig_deps_to_add or deps_to_add):
813 self.add_orig_dependency(dep)
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200814 self._mark_as_parsed([Hook.from_dict(h) for h in hooks])
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000815
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000816 def findDepsFromNotAllowedHosts(self):
817 """Returns a list of depenecies from not allowed hosts.
818
819 If allowed_hosts is not set, allows all hosts and returns empty list.
820 """
821 if not self._allowed_hosts:
822 return []
823 bad_deps = []
824 for dep in self._dependencies:
szager@chromium.orgbd772dd2014-11-05 18:43:08 +0000825 # Don't enforce this for custom_deps.
826 if dep.name in self._custom_deps:
827 continue
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000828 if isinstance(dep.url, basestring):
829 parsed_url = urlparse.urlparse(dep.url)
830 if parsed_url.netloc and parsed_url.netloc not in self._allowed_hosts:
831 bad_deps.append(dep)
832 return bad_deps
833
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +0000834 # Arguments number differs from overridden method
Quinten Yearsleyb2cc4a92016-12-15 13:53:26 -0800835 # pylint: disable=arguments-differ
maruel@chromium.org3742c842010-09-09 19:27:14 +0000836 def run(self, revision_overrides, command, args, work_queue, options):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000837 """Runs |command| then parse the DEPS file."""
maruel@chromium.org470b5432011-10-11 18:18:19 +0000838 logging.info('Dependency(%s).run()' % self.name)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000839 assert self._file_list == []
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000840 if not self.should_process:
841 return
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000842 # When running runhooks, there's no need to consult the SCM.
843 # All known hooks are expected to run unconditionally regardless of working
844 # copy state, so skip the SCM status check.
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +0200845 run_scm = command not in (
846 'flatten', 'runhooks', 'recurse', 'validate', None)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000847 parsed_url = self.LateOverride(self.url)
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000848 file_list = [] if not options.nohooks else None
szager@chromium.org3a3608d2014-10-22 21:13:52 +0000849 revision_override = revision_overrides.pop(self.name, None)
Dave Tubbda9712017-06-01 15:10:53 -0700850 if not revision_override and parsed_url:
851 revision_override = revision_overrides.get(parsed_url.split('@')[0], None)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000852 if run_scm and parsed_url:
agabled437d762016-10-17 09:35:11 -0700853 # Create a shallow copy to mutate revision.
854 options = copy.copy(options)
855 options.revision = revision_override
856 self._used_revision = options.revision
857 self._used_scm = gclient_scm.CreateSCM(
858 parsed_url, self.root.root_dir, self.name, self.outbuf,
859 out_cb=work_queue.out_cb)
860 self._got_revision = self._used_scm.RunCommand(command, options, args,
861 file_list)
862 if file_list:
863 file_list = [os.path.join(self.name, f.strip()) for f in file_list]
maruel@chromium.org68988972011-09-20 14:11:42 +0000864
865 # TODO(phajdan.jr): We should know exactly when the paths are absolute.
866 # Convert all absolute paths to relative.
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000867 for i in range(len(file_list or [])):
maruel@chromium.org68988972011-09-20 14:11:42 +0000868 # It depends on the command being executed (like runhooks vs sync).
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000869 if not os.path.isabs(file_list[i]):
maruel@chromium.org68988972011-09-20 14:11:42 +0000870 continue
871 prefix = os.path.commonprefix(
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000872 [self.root.root_dir.lower(), file_list[i].lower()])
873 file_list[i] = file_list[i][len(prefix):]
maruel@chromium.org68988972011-09-20 14:11:42 +0000874 # Strip any leading path separators.
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000875 while file_list[i].startswith(('\\', '/')):
876 file_list[i] = file_list[i][1:]
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000877
878 # Always parse the DEPS file.
879 self.ParseDepsFile()
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +0200880 if self._gn_args_file and command == 'update':
881 self.WriteGNArgsFile()
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000882 self._run_is_done(file_list or [], parsed_url)
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000883 if command in ('update', 'revert') and not options.noprehooks:
884 self.RunPreDepsHooks()
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000885
886 if self.recursion_limit:
887 # Parse the dependencies of this dependency.
888 for s in self.dependencies:
889 work_queue.enqueue(s)
890
891 if command == 'recurse':
agabled437d762016-10-17 09:35:11 -0700892 # Skip file only checkout.
893 scm = gclient_scm.GetScmName(parsed_url)
894 if not options.scm or scm in options.scm:
895 cwd = os.path.normpath(os.path.join(self.root.root_dir, self.name))
896 # Pass in the SCM type as an env variable. Make sure we don't put
897 # unicode strings in the environment.
898 env = os.environ.copy()
899 if scm:
900 env['GCLIENT_SCM'] = str(scm)
901 if parsed_url:
902 env['GCLIENT_URL'] = str(parsed_url)
903 env['GCLIENT_DEP_PATH'] = str(self.name)
904 if options.prepend_dir and scm == 'git':
905 print_stdout = False
906 def filter_fn(line):
907 """Git-specific path marshaling. It is optimized for git-grep."""
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000908
agabled437d762016-10-17 09:35:11 -0700909 def mod_path(git_pathspec):
910 match = re.match('^(\\S+?:)?([^\0]+)$', git_pathspec)
911 modified_path = os.path.join(self.name, match.group(2))
912 branch = match.group(1) or ''
913 return '%s%s' % (branch, modified_path)
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000914
agabled437d762016-10-17 09:35:11 -0700915 match = re.match('^Binary file ([^\0]+) matches$', line)
916 if match:
917 print('Binary file %s matches\n' % mod_path(match.group(1)))
918 return
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000919
agabled437d762016-10-17 09:35:11 -0700920 items = line.split('\0')
921 if len(items) == 2 and items[1]:
922 print('%s : %s' % (mod_path(items[0]), items[1]))
923 elif len(items) >= 2:
924 # Multiple null bytes or a single trailing null byte indicate
925 # git is likely displaying filenames only (such as with -l)
926 print('\n'.join(mod_path(path) for path in items if path))
927 else:
928 print(line)
929 else:
930 print_stdout = True
931 filter_fn = None
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000932
agabled437d762016-10-17 09:35:11 -0700933 if parsed_url is None:
934 print('Skipped omitted dependency %s' % cwd, file=sys.stderr)
935 elif os.path.isdir(cwd):
936 try:
937 gclient_utils.CheckCallAndFilter(
938 args, cwd=cwd, env=env, print_stdout=print_stdout,
939 filter_fn=filter_fn,
940 )
941 except subprocess2.CalledProcessError:
942 if not options.ignore:
943 raise
944 else:
945 print('Skipped missing %s' % cwd, file=sys.stderr)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000946
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +0200947 def WriteGNArgsFile(self):
948 lines = ['# Generated from %r' % self.deps_file]
949 for arg in self._gn_args:
950 lines.append('%s = %s' % (arg, ToGNString(self._vars[arg])))
951 with open(os.path.join(self.root.root_dir, self._gn_args_file), 'w') as f:
952 f.write('\n'.join(lines))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000953
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000954 @gclient_utils.lockedmethod
955 def _run_is_done(self, file_list, parsed_url):
956 # Both these are kept for hooks that are run as a separate tree traversal.
957 self._file_list = file_list
958 self._parsed_url = parsed_url
959 self._processed = True
960
szager@google.comb9a78d32012-03-13 18:46:21 +0000961 def GetHooks(self, options):
962 """Evaluates all hooks, and return them in a flat list.
963
964 RunOnDeps() must have been called before to load the DEPS.
965 """
966 result = []
maruel@chromium.org68988972011-09-20 14:11:42 +0000967 if not self.should_process or not self.recursion_limit:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000968 # Don't run the hook when it is above recursion_limit.
szager@google.comb9a78d32012-03-13 18:46:21 +0000969 return result
maruel@chromium.orgdc7445d2010-07-09 21:05:29 +0000970 # If "--force" was specified, run all hooks regardless of what files have
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000971 # changed.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000972 if self.deps_hooks:
agabled437d762016-10-17 09:35:11 -0700973 # TODO(maruel): If the user is using git, then we don't know
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000974 # what files have changed so we always run all hooks. It'd be nice to fix
975 # that.
976 if (options.force or
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000977 gclient_scm.GetScmName(self.parsed_url) in ('git', None) or
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000978 os.path.isdir(os.path.join(self.root.root_dir, self.name, '.git'))):
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200979 result.extend(self.deps_hooks)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000980 else:
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200981 for hook in self.deps_hooks:
982 if hook.matches(self.file_list_and_children):
983 result.append(hook)
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000984 for s in self.dependencies:
szager@google.comb9a78d32012-03-13 18:46:21 +0000985 result.extend(s.GetHooks(options))
986 return result
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000987
szager@google.comb9a78d32012-03-13 18:46:21 +0000988 def RunHooksRecursively(self, options):
989 assert self.hooks_ran == False
maruel@chromium.org064186c2011-09-27 23:53:33 +0000990 self._hooks_ran = True
szager@google.comb9a78d32012-03-13 18:46:21 +0000991 for hook in self.GetHooks(options):
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200992 hook.run(self.root.root_dir)
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000993
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000994 def RunPreDepsHooks(self):
995 assert self.processed
996 assert self.deps_parsed
997 assert not self.pre_deps_hooks_ran
998 assert not self.hooks_ran
999 for s in self.dependencies:
1000 assert not s.processed
1001 self._pre_deps_hooks_ran = True
1002 for hook in self.pre_deps_hooks:
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +02001003 hook.run(self.root.root_dir)
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001004
1005
maruel@chromium.org0d812442010-08-10 12:41:08 +00001006 def subtree(self, include_all):
maruel@chromium.orgad3287e2011-10-03 19:15:10 +00001007 """Breadth first recursion excluding root node."""
maruel@chromium.orgf13a4182011-09-22 00:26:15 +00001008 dependencies = self.dependencies
1009 for d in dependencies:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001010 if d.should_process or include_all:
maruel@chromium.orgad3287e2011-10-03 19:15:10 +00001011 yield d
maruel@chromium.orgf13a4182011-09-22 00:26:15 +00001012 for d in dependencies:
maruel@chromium.orgad3287e2011-10-03 19:15:10 +00001013 for i in d.subtree(include_all):
1014 yield i
1015
1016 def depth_first_tree(self):
1017 """Depth-first recursion including the root node."""
1018 yield self
1019 for i in self.dependencies:
1020 for j in i.depth_first_tree():
1021 if j.should_process:
1022 yield j
maruel@chromium.orgc57e4f22010-07-22 21:37:46 +00001023
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: