blob: 3e8a7962290a3b63832adc2ad3469ae7ad2d6a53 [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
67# processing the deps_os dict of a DEPS file.
68#
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
szager@chromium.org7b8b6de2014-08-23 00:57:31 +000083import ast
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
maruel@chromium.org5f3eee32009-09-17 00:34:30 +000099import gclient_scm
100import gclient_utils
szager@chromium.org848fd492014-04-09 19:06:44 +0000101import git_cache
nasser@codeaurora.org1f7a3d12010-02-04 15:11:50 +0000102from third_party.repo.progress import Progress
maruel@chromium.org39c0b222013-08-17 16:57:01 +0000103import subcommand
maruel@chromium.org31cb48a2011-04-04 18:01:36 +0000104import subprocess2
iannucci@chromium.org596cd5c2016-04-04 21:34:39 +0000105import setup_color
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000106
szager@chromium.org7b8b6de2014-08-23 00:57:31 +0000107CHROMIUM_SRC_URL = 'https://chromium.googlesource.com/chromium/src.git'
108
109
110def ast_dict_index(dnode, key):
111 """Search an ast.Dict for the argument key, and return its index."""
112 idx = [i for i in range(len(dnode.keys)) if (
113 type(dnode.keys[i]) is ast.Str and dnode.keys[i].s == key)]
114 if not idx:
115 return -1
116 elif len(idx) > 1:
117 raise gclient_utils.Error('Multiple dict entries with same key in AST')
118 return idx[-1]
119
120def ast2str(node, indent=0):
121 """Return a pretty-printed rendition of an ast.Node."""
122 t = type(node)
123 if t is ast.Module:
124 return '\n'.join([ast2str(x, indent) for x in node.body])
125 elif t is ast.Assign:
126 return ((' ' * indent) +
127 ' = '.join([ast2str(x) for x in node.targets] +
128 [ast2str(node.value, indent)]) + '\n')
129 elif t is ast.Name:
130 return node.id
131 elif t is ast.List:
132 if not node.elts:
133 return '[]'
134 elif len(node.elts) == 1:
135 return '[' + ast2str(node.elts[0], indent) + ']'
136 return ('[\n' + (' ' * (indent + 1)) +
137 (',\n' + (' ' * (indent + 1))).join(
138 [ast2str(x, indent + 1) for x in node.elts]) +
139 '\n' + (' ' * indent) + ']')
140 elif t is ast.Dict:
141 if not node.keys:
142 return '{}'
143 elif len(node.keys) == 1:
144 return '{%s: %s}' % (ast2str(node.keys[0]),
145 ast2str(node.values[0], indent + 1))
146 return ('{\n' + (' ' * (indent + 1)) +
147 (',\n' + (' ' * (indent + 1))).join(
148 ['%s: %s' % (ast2str(node.keys[i]),
149 ast2str(node.values[i], indent + 1))
150 for i in range(len(node.keys))]) +
151 '\n' + (' ' * indent) + '}')
152 elif t is ast.Str:
153 return "'%s'" % node.s
154 else:
155 raise gclient_utils.Error("Unexpected AST node at line %d, column %d: %s"
156 % (node.lineno, node.col_offset, t))
157
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000158
maruel@chromium.org116704f2010-06-11 17:34:38 +0000159class GClientKeywords(object):
160 class FromImpl(object):
161 """Used to implement the From() syntax."""
162
163 def __init__(self, module_name, sub_target_name=None):
164 """module_name is the dep module we want to include from. It can also be
165 the name of a subdirectory to include from.
166
167 sub_target_name is an optional parameter if the module name in the other
168 DEPS file is different. E.g., you might want to map src/net to net."""
169 self.module_name = module_name
170 self.sub_target_name = sub_target_name
171
172 def __str__(self):
173 return 'From(%s, %s)' % (repr(self.module_name),
174 repr(self.sub_target_name))
175
maruel@chromium.org116704f2010-06-11 17:34:38 +0000176 class VarImpl(object):
177 def __init__(self, custom_vars, local_scope):
178 self._custom_vars = custom_vars
179 self._local_scope = local_scope
180
181 def Lookup(self, var_name):
182 """Implements the Var syntax."""
183 if var_name in self._custom_vars:
184 return self._custom_vars[var_name]
185 elif var_name in self._local_scope.get("vars", {}):
186 return self._local_scope["vars"][var_name]
187 raise gclient_utils.Error("Var is not defined: %s" % var_name)
188
189
maruel@chromium.org064186c2011-09-27 23:53:33 +0000190class DependencySettings(GClientKeywords):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000191 """Immutable configuration settings."""
192 def __init__(
smutae7ea312016-07-18 11:59:41 -0700193 self, parent, url, safesync_url, managed, custom_deps, custom_vars,
agabledce6ddc2016-09-08 10:02:16 -0700194 custom_hooks, deps_file, should_process, relative):
maruel@chromium.org064186c2011-09-27 23:53:33 +0000195 GClientKeywords.__init__(self)
196
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000197 # These are not mutable:
198 self._parent = parent
smutae7ea312016-07-18 11:59:41 -0700199 self._safesync_url = safesync_url
mmoss@chromium.org8f93f792014-08-26 23:24:09 +0000200 self._deps_file = deps_file
maruel@chromium.org064186c2011-09-27 23:53:33 +0000201 self._url = url
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000202 # 'managed' determines whether or not this dependency is synced/updated by
203 # gclient after gclient checks it out initially. The difference between
204 # 'managed' and 'should_process' is that the user specifies 'managed' via
smutae7ea312016-07-18 11:59:41 -0700205 # the --unmanaged command-line flag or a .gclient config, where
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000206 # 'should_process' is dynamically set by gclient if it goes over its
207 # recursion limit and controls gclient's behavior so it does not misbehave.
208 self._managed = managed
209 self._should_process = should_process
agabledce6ddc2016-09-08 10:02:16 -0700210 # If this is a recursed-upon sub-dependency, and the parent has
211 # use_relative_paths set, then this dependency should check out its own
212 # dependencies relative to that parent's path for this, rather than
213 # relative to the .gclient file.
214 self._relative = relative
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +0000215 # This is a mutable value which has the list of 'target_os' OSes listed in
216 # the current deps file.
217 self.local_target_os = None
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000218
219 # These are only set in .gclient and not in DEPS files.
220 self._custom_vars = custom_vars or {}
221 self._custom_deps = custom_deps or {}
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000222 self._custom_hooks = custom_hooks or []
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000223
maruel@chromium.org064186c2011-09-27 23:53:33 +0000224 # Post process the url to remove trailing slashes.
225 if isinstance(self._url, basestring):
226 # urls are sometime incorrectly written as proto://host/path/@rev. Replace
227 # it to proto://host/path@rev.
maruel@chromium.org064186c2011-09-27 23:53:33 +0000228 self._url = self._url.replace('/@', '@')
agabled437d762016-10-17 09:35:11 -0700229 elif not isinstance(self._url, (self.FromImpl, None.__class__)):
maruel@chromium.org064186c2011-09-27 23:53:33 +0000230 raise gclient_utils.Error(
231 ('dependency url must be either a string, None, '
agabled437d762016-10-17 09:35:11 -0700232 'or From() instead of %s') % self._url.__class__.__name__)
mmoss@chromium.orgd0b272b2013-01-30 23:55:33 +0000233 # Make any deps_file path platform-appropriate.
234 for sep in ['/', '\\']:
235 self._deps_file = self._deps_file.replace(sep, os.sep)
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000236
237 @property
238 def deps_file(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000239 return self._deps_file
240
241 @property
242 def managed(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000243 return self._managed
244
245 @property
246 def parent(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000247 return self._parent
248
249 @property
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000250 def root(self):
251 """Returns the root node, a GClient object."""
252 if not self.parent:
253 # This line is to signal pylint that it could be a GClient instance.
254 return self or GClient(None, None)
255 return self.parent.root
256
257 @property
smutae7ea312016-07-18 11:59:41 -0700258 def safesync_url(self):
259 return self._safesync_url
260
261 @property
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000262 def should_process(self):
263 """True if this dependency should be processed, i.e. checked out."""
264 return self._should_process
265
266 @property
267 def custom_vars(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000268 return self._custom_vars.copy()
269
270 @property
271 def custom_deps(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000272 return self._custom_deps.copy()
273
maruel@chromium.org064186c2011-09-27 23:53:33 +0000274 @property
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000275 def custom_hooks(self):
276 return self._custom_hooks[:]
277
278 @property
maruel@chromium.org064186c2011-09-27 23:53:33 +0000279 def url(self):
280 return self._url
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000281
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000282 @property
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +0000283 def target_os(self):
284 if self.local_target_os is not None:
285 return tuple(set(self.local_target_os).union(self.parent.target_os))
286 else:
287 return self.parent.target_os
288
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000289 def get_custom_deps(self, name, url):
290 """Returns a custom deps if applicable."""
291 if self.parent:
292 url = self.parent.get_custom_deps(name, url)
293 # None is a valid return value to disable a dependency.
294 return self.custom_deps.get(name, url)
295
maruel@chromium.org064186c2011-09-27 23:53:33 +0000296
297class Dependency(gclient_utils.WorkItem, DependencySettings):
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000298 """Object that represents a dependency checkout."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000299
smutae7ea312016-07-18 11:59:41 -0700300 def __init__(self, parent, name, url, safesync_url, managed, custom_deps,
agabledce6ddc2016-09-08 10:02:16 -0700301 custom_vars, custom_hooks, deps_file, should_process,
302 relative):
maruel@chromium.org6ca8bf82011-09-19 23:04:30 +0000303 gclient_utils.WorkItem.__init__(self, name)
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000304 DependencySettings.__init__(
smutae7ea312016-07-18 11:59:41 -0700305 self, parent, url, safesync_url, managed, custom_deps, custom_vars,
agabledce6ddc2016-09-08 10:02:16 -0700306 custom_hooks, deps_file, should_process, relative)
maruel@chromium.org68988972011-09-20 14:11:42 +0000307
308 # This is in both .gclient and DEPS files:
maruel@chromium.org064186c2011-09-27 23:53:33 +0000309 self._deps_hooks = []
maruel@chromium.org68988972011-09-20 14:11:42 +0000310
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000311 self._pre_deps_hooks = []
312
maruel@chromium.org68988972011-09-20 14:11:42 +0000313 # Calculates properties:
maruel@chromium.org064186c2011-09-27 23:53:33 +0000314 self._parsed_url = None
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +0000315 self._dependencies = []
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000316 # A cache of the files affected by the current operation, necessary for
317 # hooks.
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000318 self._file_list = []
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000319 # List of host names from which dependencies are allowed.
320 # Default is an empty set, meaning unspecified in DEPS file, and hence all
321 # hosts will be allowed. Non-empty set means whitelist of hosts.
322 # allowed_hosts var is scoped to its DEPS file, and so it isn't recursive.
323 self._allowed_hosts = frozenset()
maruel@chromium.org85c2a192010-07-22 21:14:43 +0000324 # If it is not set to True, the dependency wasn't processed for its child
325 # dependency, i.e. its DEPS wasn't read.
maruel@chromium.org064186c2011-09-27 23:53:33 +0000326 self._deps_parsed = False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000327 # This dependency has been processed, i.e. checked out
maruel@chromium.org064186c2011-09-27 23:53:33 +0000328 self._processed = False
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000329 # This dependency had its pre-DEPS hooks run
330 self._pre_deps_hooks_ran = False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000331 # This dependency had its hook run
maruel@chromium.org064186c2011-09-27 23:53:33 +0000332 self._hooks_ran = False
kustermann@google.coma692e8f2013-04-18 08:32:04 +0000333 # This is the scm used to checkout self.url. It may be used by dependencies
334 # to get the datetime of the revision we checked out.
335 self._used_scm = None
szager@chromium.org4ad264b2014-05-20 04:43:47 +0000336 self._used_revision = None
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +0000337 # The actual revision we ended up getting, or None if that information is
338 # unavailable
339 self._got_revision = None
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000340
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000341 # This is a mutable value that overrides the normal recursion limit for this
342 # dependency. It is read from the actual DEPS file so cannot be set on
343 # class instantiation.
344 self.recursion_override = None
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000345 # recursedeps is a mutable value that selectively overrides the default
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000346 # 'no recursion' setting on a dep-by-dep basis. It will replace
347 # recursion_override.
iannucci@chromium.orgafa11ac2016-05-04 22:17:34 +0000348 #
349 # It will be a dictionary of {deps_name: {"deps_file": depfile_name}} or
350 # None.
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000351 self.recursedeps = None
hinoka885e5b12016-06-08 14:40:09 -0700352 # This is inherited from WorkItem. We want the URL to be a resource.
353 if url and isinstance(url, basestring):
354 # The url is usually given to gclient either as https://blah@123
qyearsley12fa6ff2016-08-24 09:18:40 -0700355 # or just https://blah. The @123 portion is irrelevant.
hinoka885e5b12016-06-08 14:40:09 -0700356 self.resources.append(url.split('@')[0])
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000357
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000358 if not self.name and self.parent:
359 raise gclient_utils.Error('Dependency without name')
360
maruel@chromium.org470b5432011-10-11 18:18:19 +0000361 @property
362 def requirements(self):
363 """Calculate the list of requirements."""
364 requirements = set()
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000365 # self.parent is implicitly a requirement. This will be recursive by
366 # definition.
367 if self.parent and self.parent.name:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000368 requirements.add(self.parent.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000369
370 # For a tree with at least 2 levels*, the leaf node needs to depend
371 # on the level higher up in an orderly way.
372 # This becomes messy for >2 depth as the DEPS file format is a dictionary,
373 # thus unsorted, while the .gclient format is a list thus sorted.
374 #
375 # * _recursion_limit is hard coded 2 and there is no hope to change this
376 # value.
377 #
378 # Interestingly enough, the following condition only works in the case we
379 # want: self is a 2nd level node. 3nd level node wouldn't need this since
380 # they already have their parent as a requirement.
maruel@chromium.org470b5432011-10-11 18:18:19 +0000381 if self.parent and self.parent.parent and not self.parent.parent.parent:
382 requirements |= set(i.name for i in self.root.dependencies if i.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000383
384 if isinstance(self.url, self.FromImpl):
maruel@chromium.org470b5432011-10-11 18:18:19 +0000385 requirements.add(self.url.module_name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000386
maruel@chromium.org470b5432011-10-11 18:18:19 +0000387 if self.name:
388 requirements |= set(
389 obj.name for obj in self.root.subtree(False)
390 if (obj is not self
391 and obj.name and
392 self.name.startswith(posixpath.join(obj.name, ''))))
393 requirements = tuple(sorted(requirements))
394 logging.info('Dependency(%s).requirements = %s' % (self.name, requirements))
395 return requirements
396
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000397 @property
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000398 def try_recursedeps(self):
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000399 """Returns False if recursion_override is ever specified."""
400 if self.recursion_override is not None:
401 return False
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000402 return self.parent.try_recursedeps
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000403
404 @property
405 def recursion_limit(self):
406 """Returns > 0 if this dependency is not too recursed to be processed."""
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000407 # We continue to support the absence of recursedeps until tools and DEPS
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000408 # using recursion_override are updated.
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000409 if self.try_recursedeps and self.parent.recursedeps != None:
410 if self.name in self.parent.recursedeps:
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000411 return 1
412
413 if self.recursion_override is not None:
414 return self.recursion_override
415 return max(self.parent.recursion_limit - 1, 0)
416
maruel@chromium.org470b5432011-10-11 18:18:19 +0000417 def verify_validity(self):
418 """Verifies that this Dependency is fine to add as a child of another one.
419
420 Returns True if this entry should be added, False if it is a duplicate of
421 another entry.
422 """
423 logging.info('Dependency(%s).verify_validity()' % self.name)
424 if self.name in [s.name for s in self.parent.dependencies]:
425 raise gclient_utils.Error(
426 'The same name "%s" appears multiple times in the deps section' %
427 self.name)
428 if not self.should_process:
429 # Return early, no need to set requirements.
430 return True
431
432 # This require a full tree traversal with locks.
433 siblings = [d for d in self.root.subtree(False) if d.name == self.name]
434 for sibling in siblings:
maruel@chromium.orgb848d5b2012-10-10 23:25:50 +0000435 self_url = self.LateOverride(self.url)
436 sibling_url = sibling.LateOverride(sibling.url)
437 # Allow to have only one to be None or ''.
438 if self_url != sibling_url and bool(self_url) == bool(sibling_url):
maruel@chromium.org470b5432011-10-11 18:18:19 +0000439 raise gclient_utils.Error(
maruel@chromium.orgb848d5b2012-10-10 23:25:50 +0000440 ('Dependency %s specified more than once:\n'
441 ' %s [%s]\n'
442 'vs\n'
443 ' %s [%s]') % (
444 self.name,
445 sibling.hierarchy(),
446 sibling_url,
447 self.hierarchy(),
448 self_url))
maruel@chromium.org470b5432011-10-11 18:18:19 +0000449 # In theory we could keep it as a shadow of the other one. In
450 # practice, simply ignore it.
451 logging.warn('Won\'t process duplicate dependency %s' % sibling)
452 return False
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000453 return True
maruel@chromium.org064186c2011-09-27 23:53:33 +0000454
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000455 def LateOverride(self, url):
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000456 """Resolves the parsed url from url.
457
458 Manages From() keyword accordingly. Do not touch self.parsed_url nor
459 self.url because it may called with other urls due to From()."""
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000460 assert self.parsed_url == None or not self.should_process, self.parsed_url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000461 parsed_url = self.get_custom_deps(self.name, url)
462 if parsed_url != url:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000463 logging.info(
464 'Dependency(%s).LateOverride(%s) -> %s' %
465 (self.name, url, parsed_url))
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000466 return parsed_url
467
468 if isinstance(url, self.FromImpl):
maruel@chromium.org470b5432011-10-11 18:18:19 +0000469 # Requires tree traversal.
maruel@chromium.org68988972011-09-20 14:11:42 +0000470 ref = [
471 dep for dep in self.root.subtree(True) if url.module_name == dep.name
472 ]
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000473 if not ref:
474 raise gclient_utils.Error('Failed to find one reference to %s. %s' % (
475 url.module_name, ref))
476 # It may happen that len(ref) > 1 but it's no big deal.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000477 ref = ref[0]
maruel@chromium.org98d05fa2010-07-22 21:58:01 +0000478 sub_target = url.sub_target_name or self.name
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000479 found_deps = [d for d in ref.dependencies if d.name == sub_target]
480 if len(found_deps) != 1:
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000481 raise gclient_utils.Error(
maruel@chromium.org98023df2011-09-07 18:44:47 +0000482 'Couldn\'t find %s in %s, referenced by %s (parent: %s)\n%s' % (
483 sub_target, ref.name, self.name, self.parent.name,
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000484 str(self.root)))
maruel@chromium.org98023df2011-09-07 18:44:47 +0000485
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000486 # Call LateOverride() again.
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000487 found_dep = found_deps[0]
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000488 parsed_url = found_dep.LateOverride(found_dep.url)
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000489 logging.info(
maruel@chromium.org470b5432011-10-11 18:18:19 +0000490 'Dependency(%s).LateOverride(%s) -> %s (From)' %
491 (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000492 return parsed_url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000493
494 if isinstance(url, basestring):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000495 parsed_url = urlparse.urlparse(url)
scr@chromium.orgf1eccaf2014-04-11 15:51:33 +0000496 if (not parsed_url[0] and
497 not re.match(r'^\w+\@[\w\.-]+\:[\w\/]+', parsed_url[2])):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000498 # A relative url. Fetch the real base.
499 path = parsed_url[2]
500 if not path.startswith('/'):
501 raise gclient_utils.Error(
502 'relative DEPS entry \'%s\' must begin with a slash' % url)
503 # Create a scm just to query the full url.
504 parent_url = self.parent.parsed_url
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000505 scm = gclient_scm.CreateSCM(
506 parent_url, self.root.root_dir, None, self.outbuf)
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000507 parsed_url = scm.FullUrlForRelativeUrl(url)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000508 else:
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000509 parsed_url = url
maruel@chromium.org470b5432011-10-11 18:18:19 +0000510 logging.info(
511 'Dependency(%s).LateOverride(%s) -> %s' %
512 (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000513 return parsed_url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000514
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000515 if url is None:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000516 logging.info(
517 'Dependency(%s).LateOverride(%s) -> %s' % (self.name, url, url))
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000518 return url
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000519
520 raise gclient_utils.Error('Unknown url type')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000521
bratell@opera.comed2b4fe2013-12-16 14:34:12 +0000522 @staticmethod
523 def MergeWithOsDeps(deps, deps_os, target_os_list):
524 """Returns a new "deps" structure that is the deps sent in updated
525 with information from deps_os (the deps_os section of the DEPS
526 file) that matches the list of target os."""
527 os_overrides = {}
528 for the_target_os in target_os_list:
529 the_target_os_deps = deps_os.get(the_target_os, {})
530 for os_dep_key, os_dep_value in the_target_os_deps.iteritems():
531 overrides = os_overrides.setdefault(os_dep_key, [])
532 overrides.append((the_target_os, os_dep_value))
533
534 # If any os didn't specify a value (we have fewer value entries
535 # than in the os list), then it wants to use the default value.
536 for os_dep_key, os_dep_value in os_overrides.iteritems():
537 if len(os_dep_value) != len(target_os_list):
qyearsley12fa6ff2016-08-24 09:18:40 -0700538 # Record the default value too so that we don't accidentally
bratell@opera.comed2b4fe2013-12-16 14:34:12 +0000539 # set it to None or miss a conflicting DEPS.
540 if os_dep_key in deps:
541 os_dep_value.append(('default', deps[os_dep_key]))
542
543 target_os_deps = {}
544 for os_dep_key, os_dep_value in os_overrides.iteritems():
545 # os_dep_value is a list of (os, value) pairs.
546 possible_values = set(x[1] for x in os_dep_value if x[1] is not None)
547 if not possible_values:
548 target_os_deps[os_dep_key] = None
549 else:
550 if len(possible_values) > 1:
551 # It would be possible to abort here but it would be
552 # unfortunate if we end up preventing any kind of checkout.
553 logging.error('Conflicting dependencies for %s: %s. (target_os=%s)',
554 os_dep_key, os_dep_value, target_os_list)
555 # Sorting to get the same result every time in case of conflicts.
556 target_os_deps[os_dep_key] = sorted(possible_values)[0]
557
558 new_deps = deps.copy()
559 new_deps.update(target_os_deps)
560 return new_deps
561
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000562 def ParseDepsFile(self):
maruel@chromium.org271375b2010-06-23 19:17:38 +0000563 """Parses the DEPS file for this dependency."""
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000564 assert not self.deps_parsed
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000565 assert not self.dependencies
iannucci@chromium.org0c3f3052014-05-22 00:29:22 +0000566
567 deps_content = None
568 use_strict = False
cmp@chromium.org76ce73c2014-07-02 00:13:18 +0000569
570 # First try to locate the configured deps file. If it's missing, fallback
571 # to DEPS.
572 deps_files = [self.deps_file]
573 if 'DEPS' not in deps_files:
574 deps_files.append('DEPS')
575 for deps_file in deps_files:
576 filepath = os.path.join(self.root.root_dir, self.name, deps_file)
577 if os.path.isfile(filepath):
578 logging.info(
579 'ParseDepsFile(%s): %s file found at %s', self.name, deps_file,
580 filepath)
581 break
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000582 logging.info(
cmp@chromium.org76ce73c2014-07-02 00:13:18 +0000583 'ParseDepsFile(%s): No %s file found at %s', self.name, deps_file,
584 filepath)
585
586 if os.path.isfile(filepath):
maruel@chromium.org46304292010-10-28 11:42:00 +0000587 deps_content = gclient_utils.FileRead(filepath)
cmp@chromium.org76ce73c2014-07-02 00:13:18 +0000588 logging.debug('ParseDepsFile(%s) read:\n%s', self.name, deps_content)
iannucci@chromium.org0c3f3052014-05-22 00:29:22 +0000589 use_strict = 'use strict' in deps_content.splitlines()[0]
590
591 local_scope = {}
592 if deps_content:
593 # One thing is unintuitive, vars = {} must happen before Var() use.
594 var = self.VarImpl(self.custom_vars, local_scope)
595 if use_strict:
596 logging.info(
597 'ParseDepsFile(%s): Strict Mode Enabled', self.name)
598 global_scope = {
599 '__builtins__': {'None': None},
600 'Var': var.Lookup,
601 'deps_os': {},
602 }
603 else:
604 global_scope = {
iannucci@chromium.org0c3f3052014-05-22 00:29:22 +0000605 'From': self.FromImpl,
606 'Var': var.Lookup,
607 'deps_os': {},
608 }
maruel@chromium.org46304292010-10-28 11:42:00 +0000609 # Eval the content.
610 try:
611 exec(deps_content, global_scope, local_scope)
vapier@chromium.orga81a56e2015-11-11 07:56:13 +0000612 except SyntaxError as e:
maruel@chromium.org46304292010-10-28 11:42:00 +0000613 gclient_utils.SyntaxErrorToError(filepath, e)
iannucci@chromium.org0c3f3052014-05-22 00:29:22 +0000614 if use_strict:
615 for key, val in local_scope.iteritems():
616 if not isinstance(val, (dict, list, tuple, str)):
617 raise gclient_utils.Error(
618 'ParseDepsFile(%s): Strict mode disallows %r -> %r' %
619 (self.name, key, val))
620
maruel@chromium.org271375b2010-06-23 19:17:38 +0000621 deps = local_scope.get('deps', {})
ilevy@chromium.org27ca3a92012-10-17 18:11:02 +0000622 if 'recursion' in local_scope:
623 self.recursion_override = local_scope.get('recursion')
624 logging.warning(
625 'Setting %s recursion to %d.', self.name, self.recursion_limit)
iannucci@chromium.orgafa11ac2016-05-04 22:17:34 +0000626 self.recursedeps = None
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000627 if 'recursedeps' in local_scope:
iannucci@chromium.orgafa11ac2016-05-04 22:17:34 +0000628 self.recursedeps = {}
629 for ent in local_scope['recursedeps']:
630 if isinstance(ent, basestring):
631 self.recursedeps[ent] = {"deps_file": self.deps_file}
632 else: # (depname, depsfilename)
633 self.recursedeps[ent[0]] = {"deps_file": ent[1]}
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000634 logging.warning('Found recursedeps %r.', repr(self.recursedeps))
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +0000635 # If present, save 'target_os' in the local_target_os property.
636 if 'target_os' in local_scope:
637 self.local_target_os = local_scope['target_os']
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000638 # load os specific dependencies if defined. these dependencies may
639 # override or extend the values defined by the 'deps' member.
bratell@opera.comed2b4fe2013-12-16 14:34:12 +0000640 target_os_list = self.target_os
641 if 'deps_os' in local_scope and target_os_list:
642 deps = self.MergeWithOsDeps(deps, local_scope['deps_os'], target_os_list)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000643
maruel@chromium.org271375b2010-06-23 19:17:38 +0000644 # If a line is in custom_deps, but not in the solution, we want to append
645 # this line to the solution.
646 for d in self.custom_deps:
647 if d not in deps:
648 deps[d] = self.custom_deps[d]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000649
650 # If use_relative_paths is set in the DEPS file, regenerate
651 # the dictionary using paths relative to the directory containing
cmp@chromium.orgf2def0a2014-07-16 19:48:54 +0000652 # the DEPS file. Also update recursedeps if use_relative_paths is
653 # enabled.
agabledce6ddc2016-09-08 10:02:16 -0700654 # If the deps file doesn't set use_relative_paths, but the parent did
655 # (and therefore set self.relative on this Dependency object), then we
656 # want to modify the deps and recursedeps by prepending the parent
657 # directory of this dependency.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000658 use_relative_paths = local_scope.get('use_relative_paths', False)
agabledce6ddc2016-09-08 10:02:16 -0700659 rel_prefix = None
maruel@chromium.org271375b2010-06-23 19:17:38 +0000660 if use_relative_paths:
agabledce6ddc2016-09-08 10:02:16 -0700661 rel_prefix = self.name
662 elif self._relative:
663 rel_prefix = os.path.dirname(self.name)
664 if rel_prefix:
cmp@chromium.orgf2def0a2014-07-16 19:48:54 +0000665 logging.warning('use_relative_paths enabled.')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000666 rel_deps = {}
667 for d, url in deps.items():
668 # normpath is required to allow DEPS to use .. in their
669 # dependency local path.
agabledce6ddc2016-09-08 10:02:16 -0700670 rel_deps[os.path.normpath(os.path.join(rel_prefix, d))] = url
671 logging.warning('Updating deps by prepending %s.', rel_prefix)
maruel@chromium.org271375b2010-06-23 19:17:38 +0000672 deps = rel_deps
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000673
cmp@chromium.orgf2def0a2014-07-16 19:48:54 +0000674 # Update recursedeps if it's set.
675 if self.recursedeps is not None:
agabledce6ddc2016-09-08 10:02:16 -0700676 logging.warning('Updating recursedeps by prepending %s.', rel_prefix)
iannucci@chromium.orgafa11ac2016-05-04 22:17:34 +0000677 rel_deps = {}
678 for depname, options in self.recursedeps.iteritems():
agabledce6ddc2016-09-08 10:02:16 -0700679 rel_deps[
680 os.path.normpath(os.path.join(rel_prefix, depname))] = options
cmp@chromium.orgf2def0a2014-07-16 19:48:54 +0000681 self.recursedeps = rel_deps
682
agabledce6ddc2016-09-08 10:02:16 -0700683
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000684 if 'allowed_hosts' in local_scope:
685 try:
686 self._allowed_hosts = frozenset(local_scope.get('allowed_hosts'))
687 except TypeError: # raised if non-iterable
688 pass
689 if not self._allowed_hosts:
690 logging.warning("allowed_hosts is specified but empty %s",
691 self._allowed_hosts)
692 raise gclient_utils.Error(
693 'ParseDepsFile(%s): allowed_hosts must be absent '
694 'or a non-empty iterable' % self.name)
695
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000696 # Convert the deps into real Dependency.
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000697 deps_to_add = []
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000698 for name, url in deps.iteritems():
maruel@chromium.org68988972011-09-20 14:11:42 +0000699 should_process = self.recursion_limit and self.should_process
iannucci@chromium.orgafa11ac2016-05-04 22:17:34 +0000700 deps_file = self.deps_file
701 if self.recursedeps is not None:
702 ent = self.recursedeps.get(name)
703 if ent is not None:
704 deps_file = ent['deps_file']
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000705 deps_to_add.append(Dependency(
smutae7ea312016-07-18 11:59:41 -0700706 self, name, url, None, None, None, self.custom_vars, None,
agabledce6ddc2016-09-08 10:02:16 -0700707 deps_file, should_process, use_relative_paths))
maruel@chromium.orgb9be0652011-10-14 18:05:40 +0000708 deps_to_add.sort(key=lambda x: x.name)
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000709
710 # override named sets of hooks by the custom hooks
711 hooks_to_run = []
712 hook_names_to_suppress = [c.get('name', '') for c in self.custom_hooks]
713 for hook in local_scope.get('hooks', []):
714 if hook.get('name', '') not in hook_names_to_suppress:
715 hooks_to_run.append(hook)
716
717 # add the replacements and any additions
718 for hook in self.custom_hooks:
719 if 'action' in hook:
720 hooks_to_run.append(hook)
721
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000722 self._pre_deps_hooks = [self.GetHookAction(hook, []) for hook in
723 local_scope.get('pre_deps_hooks', [])]
724
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000725 self.add_dependencies_and_close(deps_to_add, hooks_to_run)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000726 logging.info('ParseDepsFile(%s) done' % self.name)
727
728 def add_dependencies_and_close(self, deps_to_add, hooks):
729 """Adds the dependencies, hooks and mark the parsing as done."""
maruel@chromium.orgb9be0652011-10-14 18:05:40 +0000730 for dep in deps_to_add:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000731 if dep.verify_validity():
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000732 self.add_dependency(dep)
733 self._mark_as_parsed(hooks)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000734
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000735 def findDepsFromNotAllowedHosts(self):
736 """Returns a list of depenecies from not allowed hosts.
737
738 If allowed_hosts is not set, allows all hosts and returns empty list.
739 """
740 if not self._allowed_hosts:
741 return []
742 bad_deps = []
743 for dep in self._dependencies:
szager@chromium.orgbd772dd2014-11-05 18:43:08 +0000744 # Don't enforce this for custom_deps.
745 if dep.name in self._custom_deps:
746 continue
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000747 if isinstance(dep.url, basestring):
748 parsed_url = urlparse.urlparse(dep.url)
749 if parsed_url.netloc and parsed_url.netloc not in self._allowed_hosts:
750 bad_deps.append(dep)
751 return bad_deps
752
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +0000753 # Arguments number differs from overridden method
754 # pylint: disable=W0221
maruel@chromium.org3742c842010-09-09 19:27:14 +0000755 def run(self, revision_overrides, command, args, work_queue, options):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000756 """Runs |command| then parse the DEPS file."""
maruel@chromium.org470b5432011-10-11 18:18:19 +0000757 logging.info('Dependency(%s).run()' % self.name)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000758 assert self._file_list == []
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000759 if not self.should_process:
760 return
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000761 # When running runhooks, there's no need to consult the SCM.
762 # All known hooks are expected to run unconditionally regardless of working
763 # copy state, so skip the SCM status check.
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +0000764 run_scm = command not in ('runhooks', 'recurse', None)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000765 parsed_url = self.LateOverride(self.url)
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000766 file_list = [] if not options.nohooks else None
szager@chromium.org3a3608d2014-10-22 21:13:52 +0000767 revision_override = revision_overrides.pop(self.name, None)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000768 if run_scm and parsed_url:
agabled437d762016-10-17 09:35:11 -0700769 # Create a shallow copy to mutate revision.
770 options = copy.copy(options)
771 options.revision = revision_override
772 self._used_revision = options.revision
773 self._used_scm = gclient_scm.CreateSCM(
774 parsed_url, self.root.root_dir, self.name, self.outbuf,
775 out_cb=work_queue.out_cb)
776 self._got_revision = self._used_scm.RunCommand(command, options, args,
777 file_list)
778 if file_list:
779 file_list = [os.path.join(self.name, f.strip()) for f in file_list]
maruel@chromium.org68988972011-09-20 14:11:42 +0000780
781 # TODO(phajdan.jr): We should know exactly when the paths are absolute.
782 # Convert all absolute paths to relative.
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000783 for i in range(len(file_list or [])):
maruel@chromium.org68988972011-09-20 14:11:42 +0000784 # It depends on the command being executed (like runhooks vs sync).
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000785 if not os.path.isabs(file_list[i]):
maruel@chromium.org68988972011-09-20 14:11:42 +0000786 continue
787 prefix = os.path.commonprefix(
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000788 [self.root.root_dir.lower(), file_list[i].lower()])
789 file_list[i] = file_list[i][len(prefix):]
maruel@chromium.org68988972011-09-20 14:11:42 +0000790 # Strip any leading path separators.
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000791 while file_list[i].startswith(('\\', '/')):
792 file_list[i] = file_list[i][1:]
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000793
794 # Always parse the DEPS file.
795 self.ParseDepsFile()
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000796 self._run_is_done(file_list or [], parsed_url)
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000797 if command in ('update', 'revert') and not options.noprehooks:
798 self.RunPreDepsHooks()
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000799
800 if self.recursion_limit:
801 # Parse the dependencies of this dependency.
802 for s in self.dependencies:
803 work_queue.enqueue(s)
804
805 if command == 'recurse':
agabled437d762016-10-17 09:35:11 -0700806 # Skip file only checkout.
807 scm = gclient_scm.GetScmName(parsed_url)
808 if not options.scm or scm in options.scm:
809 cwd = os.path.normpath(os.path.join(self.root.root_dir, self.name))
810 # Pass in the SCM type as an env variable. Make sure we don't put
811 # unicode strings in the environment.
812 env = os.environ.copy()
813 if scm:
814 env['GCLIENT_SCM'] = str(scm)
815 if parsed_url:
816 env['GCLIENT_URL'] = str(parsed_url)
817 env['GCLIENT_DEP_PATH'] = str(self.name)
818 if options.prepend_dir and scm == 'git':
819 print_stdout = False
820 def filter_fn(line):
821 """Git-specific path marshaling. It is optimized for git-grep."""
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000822
agabled437d762016-10-17 09:35:11 -0700823 def mod_path(git_pathspec):
824 match = re.match('^(\\S+?:)?([^\0]+)$', git_pathspec)
825 modified_path = os.path.join(self.name, match.group(2))
826 branch = match.group(1) or ''
827 return '%s%s' % (branch, modified_path)
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000828
agabled437d762016-10-17 09:35:11 -0700829 match = re.match('^Binary file ([^\0]+) matches$', line)
830 if match:
831 print('Binary file %s matches\n' % mod_path(match.group(1)))
832 return
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000833
agabled437d762016-10-17 09:35:11 -0700834 items = line.split('\0')
835 if len(items) == 2 and items[1]:
836 print('%s : %s' % (mod_path(items[0]), items[1]))
837 elif len(items) >= 2:
838 # Multiple null bytes or a single trailing null byte indicate
839 # git is likely displaying filenames only (such as with -l)
840 print('\n'.join(mod_path(path) for path in items if path))
841 else:
842 print(line)
843 else:
844 print_stdout = True
845 filter_fn = None
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000846
agabled437d762016-10-17 09:35:11 -0700847 if parsed_url is None:
848 print('Skipped omitted dependency %s' % cwd, file=sys.stderr)
849 elif os.path.isdir(cwd):
850 try:
851 gclient_utils.CheckCallAndFilter(
852 args, cwd=cwd, env=env, print_stdout=print_stdout,
853 filter_fn=filter_fn,
854 )
855 except subprocess2.CalledProcessError:
856 if not options.ignore:
857 raise
858 else:
859 print('Skipped missing %s' % cwd, file=sys.stderr)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000860
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000861
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000862 @gclient_utils.lockedmethod
863 def _run_is_done(self, file_list, parsed_url):
864 # Both these are kept for hooks that are run as a separate tree traversal.
865 self._file_list = file_list
866 self._parsed_url = parsed_url
867 self._processed = True
868
szager@google.comb9a78d32012-03-13 18:46:21 +0000869 @staticmethod
870 def GetHookAction(hook_dict, matching_file_list):
871 """Turns a parsed 'hook' dict into an executable command."""
872 logging.debug(hook_dict)
873 logging.debug(matching_file_list)
874 command = hook_dict['action'][:]
875 if command[0] == 'python':
876 # If the hook specified "python" as the first item, the action is a
877 # Python script. Run it by starting a new copy of the same
878 # interpreter.
879 command[0] = sys.executable
880 if '$matching_files' in command:
881 splice_index = command.index('$matching_files')
882 command[splice_index:splice_index + 1] = matching_file_list
883 return command
884
885 def GetHooks(self, options):
886 """Evaluates all hooks, and return them in a flat list.
887
888 RunOnDeps() must have been called before to load the DEPS.
889 """
890 result = []
maruel@chromium.org68988972011-09-20 14:11:42 +0000891 if not self.should_process or not self.recursion_limit:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000892 # Don't run the hook when it is above recursion_limit.
szager@google.comb9a78d32012-03-13 18:46:21 +0000893 return result
maruel@chromium.orgdc7445d2010-07-09 21:05:29 +0000894 # If "--force" was specified, run all hooks regardless of what files have
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000895 # changed.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000896 if self.deps_hooks:
agabled437d762016-10-17 09:35:11 -0700897 # TODO(maruel): If the user is using git, then we don't know
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000898 # what files have changed so we always run all hooks. It'd be nice to fix
899 # that.
900 if (options.force or
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000901 gclient_scm.GetScmName(self.parsed_url) in ('git', None) or
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000902 os.path.isdir(os.path.join(self.root.root_dir, self.name, '.git'))):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000903 for hook_dict in self.deps_hooks:
szager@google.comb9a78d32012-03-13 18:46:21 +0000904 result.append(self.GetHookAction(hook_dict, []))
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000905 else:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000906 # Run hooks on the basis of whether the files from the gclient operation
907 # match each hook's pattern.
908 for hook_dict in self.deps_hooks:
909 pattern = re.compile(hook_dict['pattern'])
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000910 matching_file_list = [
911 f for f in self.file_list_and_children if pattern.search(f)
912 ]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000913 if matching_file_list:
szager@google.comb9a78d32012-03-13 18:46:21 +0000914 result.append(self.GetHookAction(hook_dict, matching_file_list))
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000915 for s in self.dependencies:
szager@google.comb9a78d32012-03-13 18:46:21 +0000916 result.extend(s.GetHooks(options))
917 return result
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000918
szager@google.comb9a78d32012-03-13 18:46:21 +0000919 def RunHooksRecursively(self, options):
920 assert self.hooks_ran == False
maruel@chromium.org064186c2011-09-27 23:53:33 +0000921 self._hooks_ran = True
szager@google.comb9a78d32012-03-13 18:46:21 +0000922 for hook in self.GetHooks(options):
923 try:
ilevy@chromium.orgc28d3772013-07-12 19:42:37 +0000924 start_time = time.time()
szager@google.comb9a78d32012-03-13 18:46:21 +0000925 gclient_utils.CheckCallAndFilterAndHeader(
926 hook, cwd=self.root.root_dir, always=True)
vapier@chromium.orga81a56e2015-11-11 07:56:13 +0000927 except (gclient_utils.Error, subprocess2.CalledProcessError) as e:
szager@google.comb9a78d32012-03-13 18:46:21 +0000928 # Use a discrete exit status code of 2 to indicate that a hook action
929 # failed. Users of this script may wish to treat hook action failures
930 # differently from VC failures.
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +0000931 print('Error: %s' % str(e), file=sys.stderr)
szager@google.comb9a78d32012-03-13 18:46:21 +0000932 sys.exit(2)
ilevy@chromium.orgc28d3772013-07-12 19:42:37 +0000933 finally:
934 elapsed_time = time.time() - start_time
935 if elapsed_time > 10:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +0000936 print("Hook '%s' took %.2f secs" % (
937 gclient_utils.CommandToStr(hook), elapsed_time))
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000938
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000939 def RunPreDepsHooks(self):
940 assert self.processed
941 assert self.deps_parsed
942 assert not self.pre_deps_hooks_ran
943 assert not self.hooks_ran
944 for s in self.dependencies:
945 assert not s.processed
946 self._pre_deps_hooks_ran = True
947 for hook in self.pre_deps_hooks:
948 try:
949 start_time = time.time()
950 gclient_utils.CheckCallAndFilterAndHeader(
951 hook, cwd=self.root.root_dir, always=True)
vapier@chromium.orga81a56e2015-11-11 07:56:13 +0000952 except (gclient_utils.Error, subprocess2.CalledProcessError) as e:
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000953 # Use a discrete exit status code of 2 to indicate that a hook action
954 # failed. Users of this script may wish to treat hook action failures
955 # differently from VC failures.
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +0000956 print('Error: %s' % str(e), file=sys.stderr)
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000957 sys.exit(2)
958 finally:
959 elapsed_time = time.time() - start_time
960 if elapsed_time > 10:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +0000961 print("Hook '%s' took %.2f secs" % (
962 gclient_utils.CommandToStr(hook), elapsed_time))
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000963
964
maruel@chromium.org0d812442010-08-10 12:41:08 +0000965 def subtree(self, include_all):
maruel@chromium.orgad3287e2011-10-03 19:15:10 +0000966 """Breadth first recursion excluding root node."""
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000967 dependencies = self.dependencies
968 for d in dependencies:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000969 if d.should_process or include_all:
maruel@chromium.orgad3287e2011-10-03 19:15:10 +0000970 yield d
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000971 for d in dependencies:
maruel@chromium.orgad3287e2011-10-03 19:15:10 +0000972 for i in d.subtree(include_all):
973 yield i
974
975 def depth_first_tree(self):
976 """Depth-first recursion including the root node."""
977 yield self
978 for i in self.dependencies:
979 for j in i.depth_first_tree():
980 if j.should_process:
981 yield j
maruel@chromium.orgc57e4f22010-07-22 21:37:46 +0000982
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000983 @gclient_utils.lockedmethod
984 def add_dependency(self, new_dep):
985 self._dependencies.append(new_dep)
986
987 @gclient_utils.lockedmethod
988 def _mark_as_parsed(self, new_hooks):
989 self._deps_hooks.extend(new_hooks)
990 self._deps_parsed = True
991
maruel@chromium.org68988972011-09-20 14:11:42 +0000992 @property
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000993 @gclient_utils.lockedmethod
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +0000994 def dependencies(self):
995 return tuple(self._dependencies)
996
997 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000998 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000999 def deps_hooks(self):
1000 return tuple(self._deps_hooks)
1001
1002 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001003 @gclient_utils.lockedmethod
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001004 def pre_deps_hooks(self):
1005 return tuple(self._pre_deps_hooks)
1006
1007 @property
1008 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001009 def parsed_url(self):
1010 return self._parsed_url
1011
1012 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001013 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001014 def deps_parsed(self):
maruel@chromium.org3223edd2011-10-10 23:17:39 +00001015 """This is purely for debugging purposes. It's not used anywhere."""
maruel@chromium.org064186c2011-09-27 23:53:33 +00001016 return self._deps_parsed
1017
1018 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001019 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001020 def processed(self):
1021 return self._processed
1022
1023 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001024 @gclient_utils.lockedmethod
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001025 def pre_deps_hooks_ran(self):
1026 return self._pre_deps_hooks_ran
1027
1028 @property
1029 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001030 def hooks_ran(self):
1031 return self._hooks_ran
1032
1033 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001034 @gclient_utils.lockedmethod
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00001035 def allowed_hosts(self):
1036 return self._allowed_hosts
1037
1038 @property
1039 @gclient_utils.lockedmethod
maruel@chromium.org861fd0f2010-07-23 03:05:05 +00001040 def file_list(self):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001041 return tuple(self._file_list)
1042
1043 @property
kustermann@google.coma692e8f2013-04-18 08:32:04 +00001044 def used_scm(self):
1045 """SCMWrapper instance for this dependency or None if not processed yet."""
1046 return self._used_scm
1047
1048 @property
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00001049 @gclient_utils.lockedmethod
1050 def got_revision(self):
1051 return self._got_revision
1052
1053 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001054 def file_list_and_children(self):
1055 result = list(self.file_list)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +00001056 for d in self.dependencies:
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001057 result.extend(d.file_list_and_children)
maruel@chromium.org68988972011-09-20 14:11:42 +00001058 return tuple(result)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +00001059
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001060 def __str__(self):
1061 out = []
smutae7ea312016-07-18 11:59:41 -07001062 for i in ('name', 'url', 'parsed_url', 'safesync_url', 'custom_deps',
maruel@chromium.org3c74bc92011-09-15 19:17:21 +00001063 'custom_vars', 'deps_hooks', 'file_list', 'should_process',
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00001064 'processed', 'hooks_ran', 'deps_parsed', 'requirements',
1065 'allowed_hosts'):
maruel@chromium.org3c74bc92011-09-15 19:17:21 +00001066 # First try the native property if it exists.
1067 if hasattr(self, '_' + i):
1068 value = getattr(self, '_' + i, False)
1069 else:
1070 value = getattr(self, i, False)
1071 if value:
1072 out.append('%s: %s' % (i, value))
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001073
1074 for d in self.dependencies:
1075 out.extend([' ' + x for x in str(d).splitlines()])
1076 out.append('')
1077 return '\n'.join(out)
1078
1079 def __repr__(self):
1080 return '%s: %s' % (self.name, self.url)
1081
maruel@chromium.orgbffb9042010-07-22 20:59:36 +00001082 def hierarchy(self):
maruel@chromium.orgbc2d2f92010-07-22 21:26:48 +00001083 """Returns a human-readable hierarchical reference to a Dependency."""
maruel@chromium.orgbffb9042010-07-22 20:59:36 +00001084 out = '%s(%s)' % (self.name, self.url)
1085 i = self.parent
1086 while i and i.name:
1087 out = '%s(%s) -> %s' % (i.name, i.url, out)
1088 i = i.parent
1089 return out
1090
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001091
1092class GClient(Dependency):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001093 """Object that represent a gclient checkout. A tree of Dependency(), one per
1094 solution or DEPS entry."""
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001095
1096 DEPS_OS_CHOICES = {
1097 "win32": "win",
1098 "win": "win",
1099 "cygwin": "win",
1100 "darwin": "mac",
1101 "mac": "mac",
1102 "unix": "unix",
1103 "linux": "unix",
1104 "linux2": "unix",
maruel@chromium.org244e3442011-06-12 15:20:55 +00001105 "linux3": "unix",
szager@chromium.orgf8c95cd2012-06-01 22:26:52 +00001106 "android": "android",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001107 }
1108
1109 DEFAULT_CLIENT_FILE_TEXT = ("""\
1110solutions = [
smutae7ea312016-07-18 11:59:41 -07001111 { "name" : "%(solution_name)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001112 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +00001113 "deps_file" : "%(deps_file)s",
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001114 "managed" : %(managed)s,
smutae7ea312016-07-18 11:59:41 -07001115 "custom_deps" : {
1116 },
1117 "safesync_url": "%(safesync_url)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001118 },
1119]
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001120cache_dir = %(cache_dir)r
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001121""")
1122
1123 DEFAULT_SNAPSHOT_SOLUTION_TEXT = ("""\
smutae7ea312016-07-18 11:59:41 -07001124 { "name" : "%(solution_name)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001125 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +00001126 "deps_file" : "%(deps_file)s",
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001127 "managed" : %(managed)s,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001128 "custom_deps" : {
smutae7ea312016-07-18 11:59:41 -07001129%(solution_deps)s },
1130 "safesync_url": "%(safesync_url)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001131 },
1132""")
1133
1134 DEFAULT_SNAPSHOT_FILE_TEXT = ("""\
1135# Snapshot generated with gclient revinfo --snapshot
1136solutions = [
maruel@chromium.org73e21142010-07-05 13:32:01 +00001137%(solution_list)s]
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001138""")
1139
1140 def __init__(self, root_dir, options):
maruel@chromium.org0d812442010-08-10 12:41:08 +00001141 # Do not change previous behavior. Only solution level and immediate DEPS
1142 # are processed.
1143 self._recursion_limit = 2
smutae7ea312016-07-18 11:59:41 -07001144 Dependency.__init__(self, None, None, None, None, True, None, None, None,
agabledce6ddc2016-09-08 10:02:16 -07001145 'unused', True, None)
maruel@chromium.org0d425922010-06-21 19:22:24 +00001146 self._options = options
maruel@chromium.org271375b2010-06-23 19:17:38 +00001147 if options.deps_os:
1148 enforced_os = options.deps_os.split(',')
1149 else:
1150 enforced_os = [self.DEPS_OS_CHOICES.get(sys.platform, 'unix')]
1151 if 'all' in enforced_os:
1152 enforced_os = self.DEPS_OS_CHOICES.itervalues()
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001153 self._enforced_os = tuple(set(enforced_os))
maruel@chromium.org271375b2010-06-23 19:17:38 +00001154 self._root_dir = root_dir
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001155 self.config_content = None
1156
borenet@google.com88d10082014-03-21 17:24:48 +00001157 def _CheckConfig(self):
1158 """Verify that the config matches the state of the existing checked-out
1159 solutions."""
1160 for dep in self.dependencies:
1161 if dep.managed and dep.url:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001162 scm = gclient_scm.CreateSCM(
1163 dep.url, self.root_dir, dep.name, self.outbuf)
smut@google.comd33eab32014-07-07 19:35:18 +00001164 actual_url = scm.GetActualRemoteURL(self._options)
borenet@google.com4e9be262014-04-08 19:40:30 +00001165 if actual_url and not scm.DoesRemoteURLMatch(self._options):
levarum@chromium.org27a6f9a2016-05-28 00:21:49 +00001166 mirror = scm.GetCacheMirror()
1167 if mirror:
1168 mirror_string = '%s (exists=%s)' % (mirror.mirror_path,
1169 mirror.exists())
1170 else:
1171 mirror_string = 'not used'
borenet@google.com0a427372014-04-02 19:12:13 +00001172 raise gclient_utils.Error('''
borenet@google.com88d10082014-03-21 17:24:48 +00001173Your .gclient file seems to be broken. The requested URL is different from what
borenet@google.com0a427372014-04-02 19:12:13 +00001174is actually checked out in %(checkout_path)s.
borenet@google.com88d10082014-03-21 17:24:48 +00001175
borenet@google.com97882362014-04-07 20:06:02 +00001176The .gclient file contains:
levarum@chromium.org27a6f9a2016-05-28 00:21:49 +00001177URL: %(expected_url)s (%(expected_scm)s)
1178Cache mirror: %(mirror_string)s
borenet@google.com97882362014-04-07 20:06:02 +00001179
1180The local checkout in %(checkout_path)s reports:
1181%(actual_url)s (%(actual_scm)s)
borenet@google.com88d10082014-03-21 17:24:48 +00001182
1183You should ensure that the URL listed in .gclient is correct and either change
agabled437d762016-10-17 09:35:11 -07001184it or fix the checkout.
borenet@google.com88d10082014-03-21 17:24:48 +00001185''' % {'checkout_path': os.path.join(self.root_dir, dep.name),
1186 'expected_url': dep.url,
1187 'expected_scm': gclient_scm.GetScmName(dep.url),
levarum@chromium.org27a6f9a2016-05-28 00:21:49 +00001188 'mirror_string' : mirror_string,
borenet@google.com88d10082014-03-21 17:24:48 +00001189 'actual_url': actual_url,
1190 'actual_scm': gclient_scm.GetScmName(actual_url)})
1191
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001192 def SetConfig(self, content):
maruel@chromium.orgf13a4182011-09-22 00:26:15 +00001193 assert not self.dependencies
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001194 config_dict = {}
1195 self.config_content = content
1196 try:
1197 exec(content, config_dict)
vapier@chromium.orga81a56e2015-11-11 07:56:13 +00001198 except SyntaxError as e:
maruel@chromium.org5990f9d2010-07-07 18:02:58 +00001199 gclient_utils.SyntaxErrorToError('.gclient', e)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001200
peter@chromium.org1efccc82012-04-27 16:34:38 +00001201 # Append any target OS that is not already being enforced to the tuple.
1202 target_os = config_dict.get('target_os', [])
stuartmorgan@chromium.org18a4f6a2012-11-02 13:37:24 +00001203 if config_dict.get('target_os_only', False):
1204 self._enforced_os = tuple(set(target_os))
1205 else:
1206 self._enforced_os = tuple(set(self._enforced_os).union(target_os))
1207
dyen@chromium.orgd915cca2014-08-07 21:41:37 +00001208 cache_dir = config_dict.get('cache_dir')
1209 if cache_dir:
1210 cache_dir = os.path.join(self.root_dir, cache_dir)
1211 cache_dir = os.path.abspath(cache_dir)
szager@chromium.orgcaf5bef2014-08-24 18:56:32 +00001212 # If running on a bot, force break any stale git cache locks.
dnj@chromium.orgb682b3e2014-08-25 19:17:12 +00001213 if os.path.exists(cache_dir) and os.environ.get('CHROME_HEADLESS'):
szager@chromium.org4848fb62014-08-24 19:16:31 +00001214 subprocess2.check_call(['git', 'cache', 'unlock', '--cache-dir',
1215 cache_dir, '--force', '--all'])
dyen@chromium.orgd915cca2014-08-07 21:41:37 +00001216 gclient_scm.GitWrapper.cache_dir = cache_dir
1217 git_cache.Mirror.SetCachePath(cache_dir)
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001218
stuartmorgan@chromium.org18a4f6a2012-11-02 13:37:24 +00001219 if not target_os and config_dict.get('target_os_only', False):
1220 raise gclient_utils.Error('Can\'t use target_os_only if target_os is '
1221 'not specified')
peter@chromium.org1efccc82012-04-27 16:34:38 +00001222
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001223 deps_to_add = []
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001224 for s in config_dict.get('solutions', []):
maruel@chromium.org81843b82010-06-28 16:49:26 +00001225 try:
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001226 deps_to_add.append(Dependency(
maruel@chromium.org81843b82010-06-28 16:49:26 +00001227 self, s['name'], s['url'],
smutae7ea312016-07-18 11:59:41 -07001228 s.get('safesync_url', None),
1229 s.get('managed', True),
maruel@chromium.org81843b82010-06-28 16:49:26 +00001230 s.get('custom_deps', {}),
maruel@chromium.org0d812442010-08-10 12:41:08 +00001231 s.get('custom_vars', {}),
petermayo@chromium.orge79161a2013-07-09 14:40:37 +00001232 s.get('custom_hooks', []),
nsylvain@google.comefc80932011-05-31 21:27:56 +00001233 s.get('deps_file', 'DEPS'),
agabledce6ddc2016-09-08 10:02:16 -07001234 True,
1235 None))
maruel@chromium.org81843b82010-06-28 16:49:26 +00001236 except KeyError:
1237 raise gclient_utils.Error('Invalid .gclient file. Solution is '
1238 'incomplete: %s' % s)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001239 self.add_dependencies_and_close(deps_to_add, config_dict.get('hooks', []))
1240 logging.info('SetConfig() done')
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001241
1242 def SaveConfig(self):
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001243 gclient_utils.FileWrite(os.path.join(self.root_dir,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001244 self._options.config_filename),
1245 self.config_content)
1246
1247 @staticmethod
1248 def LoadCurrentConfig(options):
1249 """Searches for and loads a .gclient file relative to the current working
1250 dir. Returns a GClient object."""
szager@chromium.orge2e03202012-07-31 18:05:16 +00001251 if options.spec:
1252 client = GClient('.', options)
1253 client.SetConfig(options.spec)
1254 else:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00001255 if options.verbose:
1256 print('Looking for %s starting from %s\n' % (
1257 options.config_filename, os.getcwd()))
szager@chromium.orge2e03202012-07-31 18:05:16 +00001258 path = gclient_utils.FindGclientRoot(os.getcwd(), options.config_filename)
1259 if not path:
1260 return None
1261 client = GClient(path, options)
1262 client.SetConfig(gclient_utils.FileRead(
1263 os.path.join(path, options.config_filename)))
maruel@chromium.org69392e72011-10-13 22:09:00 +00001264
1265 if (options.revisions and
1266 len(client.dependencies) > 1 and
1267 any('@' not in r for r in options.revisions)):
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001268 print(
1269 ('You must specify the full solution name like --revision %s@%s\n'
1270 'when you have multiple solutions setup in your .gclient file.\n'
1271 'Other solutions present are: %s.') % (
maruel@chromium.org69392e72011-10-13 22:09:00 +00001272 client.dependencies[0].name,
1273 options.revisions[0],
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001274 ', '.join(s.name for s in client.dependencies[1:])),
1275 file=sys.stderr)
maruel@chromium.org15804092010-09-02 17:07:37 +00001276 return client
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001277
nsylvain@google.comefc80932011-05-31 21:27:56 +00001278 def SetDefaultConfig(self, solution_name, deps_file, solution_url,
smutae7ea312016-07-18 11:59:41 -07001279 safesync_url, managed=True, cache_dir=None):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001280 self.SetConfig(self.DEFAULT_CLIENT_FILE_TEXT % {
1281 'solution_name': solution_name,
1282 'solution_url': solution_url,
nsylvain@google.comefc80932011-05-31 21:27:56 +00001283 'deps_file': deps_file,
smutae7ea312016-07-18 11:59:41 -07001284 'safesync_url' : safesync_url,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001285 'managed': managed,
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001286 'cache_dir': cache_dir,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001287 })
1288
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001289 def _SaveEntries(self):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001290 """Creates a .gclient_entries file to record the list of unique checkouts.
1291
1292 The .gclient_entries file lives in the same directory as .gclient.
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001293 """
1294 # Sometimes pprint.pformat will use {', sometimes it'll use { ' ... It
1295 # makes testing a bit too fun.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001296 result = 'entries = {\n'
maruel@chromium.org68988972011-09-20 14:11:42 +00001297 for entry in self.root.subtree(False):
agabled437d762016-10-17 09:35:11 -07001298 result += ' %s: %s,\n' % (pprint.pformat(entry.name),
1299 pprint.pformat(entry.parsed_url))
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001300 result += '}\n'
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001301 file_path = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org1333cb32011-10-04 23:40:16 +00001302 logging.debug(result)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001303 gclient_utils.FileWrite(file_path, result)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001304
1305 def _ReadEntries(self):
1306 """Read the .gclient_entries file for the given client.
1307
1308 Returns:
1309 A sequence of solution names, which will be empty if there is the
1310 entries file hasn't been created yet.
1311 """
1312 scope = {}
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001313 filename = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001314 if not os.path.exists(filename):
maruel@chromium.org73e21142010-07-05 13:32:01 +00001315 return {}
maruel@chromium.org5990f9d2010-07-07 18:02:58 +00001316 try:
1317 exec(gclient_utils.FileRead(filename), scope)
vapier@chromium.orga81a56e2015-11-11 07:56:13 +00001318 except SyntaxError as e:
maruel@chromium.org5990f9d2010-07-07 18:02:58 +00001319 gclient_utils.SyntaxErrorToError(filename, e)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001320 return scope['entries']
1321
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001322 def _EnforceRevisions(self):
maruel@chromium.org918a9ae2010-05-28 15:50:30 +00001323 """Checks for revision overrides."""
1324 revision_overrides = {}
smutae7ea312016-07-18 11:59:41 -07001325 if self._options.head:
1326 return revision_overrides
1327 # Do not check safesync_url if one or more --revision flag is specified.
joi@chromium.org792ea882010-11-10 02:37:27 +00001328 if not self._options.revisions:
1329 for s in self.dependencies:
smutae7ea312016-07-18 11:59:41 -07001330 if not s.managed:
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001331 self._options.revisions.append('%s@unmanaged' % s.name)
smutae7ea312016-07-18 11:59:41 -07001332 elif s.safesync_url:
1333 self._ApplySafeSyncRev(dep=s)
maruel@chromium.org307d1792010-05-31 20:03:13 +00001334 if not self._options.revisions:
1335 return revision_overrides
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001336 solutions_names = [s.name for s in self.dependencies]
smutae7ea312016-07-18 11:59:41 -07001337 index = 0
1338 for revision in self._options.revisions:
1339 if not '@' in revision:
maruel@chromium.org307d1792010-05-31 20:03:13 +00001340 # Support for --revision 123
smutae7ea312016-07-18 11:59:41 -07001341 revision = '%s@%s' % (solutions_names[index], revision)
1342 name, rev = revision.split('@', 1)
szager@chromium.org4ad264b2014-05-20 04:43:47 +00001343 revision_overrides[name] = rev
smutae7ea312016-07-18 11:59:41 -07001344 index += 1
maruel@chromium.org918a9ae2010-05-28 15:50:30 +00001345 return revision_overrides
1346
smutae7ea312016-07-18 11:59:41 -07001347 def _ApplySafeSyncRev(self, dep):
1348 """Finds a valid revision from the content of the safesync_url and apply it
1349 by appending revisions to the revision list. Throws if revision appears to
1350 be invalid for the given |dep|."""
1351 assert len(dep.safesync_url) > 0
1352 handle = urllib.urlopen(dep.safesync_url)
1353 rev = handle.read().strip()
1354 handle.close()
1355 if not rev:
1356 raise gclient_utils.Error(
1357 'It appears your safesync_url (%s) is not working properly\n'
1358 '(as it returned an empty response). Check your config.' %
1359 dep.safesync_url)
1360 scm = gclient_scm.CreateSCM(
1361 dep.url, dep.root.root_dir, dep.name, self.outbuf)
1362 safe_rev = scm.GetUsableRev(rev, self._options)
1363 if self._options.verbose:
1364 print('Using safesync_url revision: %s.\n' % safe_rev)
1365 self._options.revisions.append('%s@%s' % (dep.name, safe_rev))
1366
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001367 def RunOnDeps(self, command, args, ignore_requirements=False, progress=True):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001368 """Runs a command on each dependency in a client and its dependencies.
1369
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001370 Args:
1371 command: The command to use (e.g., 'status' or 'diff')
1372 args: list of str - extra arguments to add to the command line.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001373 """
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001374 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +00001375 raise gclient_utils.Error('No solution specified')
borenet@google.com0a427372014-04-02 19:12:13 +00001376
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001377 revision_overrides = {}
1378 # It's unnecessary to check for revision overrides for 'recurse'.
1379 # Save a few seconds by not calling _EnforceRevisions() in that case.
agable@chromium.org0242eb42015-06-09 00:45:31 +00001380 if command not in ('diff', 'recurse', 'runhooks', 'status', 'revert'):
szager@chromium.org5273b8a2014-08-21 15:10:10 +00001381 self._CheckConfig()
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001382 revision_overrides = self._EnforceRevisions()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001383 pm = None
maruel@chromium.org5b3f8852010-09-10 16:49:54 +00001384 # Disable progress for non-tty stdout.
iannucci@chromium.org596cd5c2016-04-04 21:34:39 +00001385 if (setup_color.IS_TTY and not self._options.verbose and progress):
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001386 if command in ('update', 'revert'):
1387 pm = Progress('Syncing projects', 1)
maruel@chromium.orgcd8d8e12012-10-03 17:16:25 +00001388 elif command == 'recurse':
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001389 pm = Progress(' '.join(args), 1)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001390 work_queue = gclient_utils.ExecutionQueue(
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001391 self._options.jobs, pm, ignore_requirements=ignore_requirements,
1392 verbose=self._options.verbose)
maruel@chromium.org049bced2010-08-12 13:37:20 +00001393 for s in self.dependencies:
1394 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +00001395 work_queue.flush(revision_overrides, command, args, options=self._options)
szager@chromium.org4ad264b2014-05-20 04:43:47 +00001396 if revision_overrides:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001397 print('Please fix your script, having invalid --revision flags will soon '
1398 'considered an error.', file=sys.stderr)
piman@chromium.org6f363722010-04-27 00:41:09 +00001399
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001400 # Once all the dependencies have been processed, it's now safe to run the
1401 # hooks.
1402 if not self._options.nohooks:
1403 self.RunHooksRecursively(self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001404
1405 if command == 'update':
ajwong@chromium.orgcdcee802009-06-23 15:30:42 +00001406 # Notify the user if there is an orphaned entry in their working copy.
1407 # Only delete the directory if there are no changes in it, and
1408 # delete_unversioned_trees is set to true.
maruel@chromium.org68988972011-09-20 14:11:42 +00001409 entries = [i.name for i in self.root.subtree(False) if i.url]
xusydoc@chromium.org885a9602013-05-31 09:54:40 +00001410 full_entries = [os.path.join(self.root_dir, e.replace('/', os.path.sep))
1411 for e in entries]
1412
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001413 for entry, prev_url in self._ReadEntries().iteritems():
maruel@chromium.org04dd7de2010-10-14 13:25:49 +00001414 if not prev_url:
1415 # entry must have been overridden via .gclient custom_deps
1416 continue
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001417 # Fix path separator on Windows.
1418 entry_fixed = entry.replace('/', os.path.sep)
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001419 e_dir = os.path.join(self.root_dir, entry_fixed)
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001420 # Use entry and not entry_fixed there.
jochen@chromium.orga78e5532013-03-11 13:33:03 +00001421 if (entry not in entries and
1422 (not any(path.startswith(entry + '/') for path in entries)) and
jochen@chromium.orgcc475722013-03-11 13:07:40 +00001423 os.path.exists(e_dir)):
primiano@chromium.org1c127382015-02-17 11:15:40 +00001424 # The entry has been removed from DEPS.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001425 scm = gclient_scm.CreateSCM(
1426 prev_url, self.root_dir, entry_fixed, self.outbuf)
xusydoc@chromium.org885a9602013-05-31 09:54:40 +00001427
1428 # Check to see if this directory is now part of a higher-up checkout.
borenet@google.com359bb642014-05-13 17:28:19 +00001429 scm_root = None
agabled437d762016-10-17 09:35:11 -07001430 try:
1431 scm_root = gclient_scm.scm.GIT.GetCheckoutRoot(scm.checkout_path)
1432 except subprocess2.CalledProcessError:
1433 pass
1434 if not scm_root:
borenet@google.com359bb642014-05-13 17:28:19 +00001435 logging.warning('Could not find checkout root for %s. Unable to '
1436 'determine whether it is part of a higher-level '
1437 'checkout, so not removing.' % entry)
1438 continue
primiano@chromium.org1c127382015-02-17 11:15:40 +00001439
1440 # This is to handle the case of third_party/WebKit migrating from
1441 # being a DEPS entry to being part of the main project.
1442 # If the subproject is a Git project, we need to remove its .git
1443 # folder. Otherwise git operations on that folder will have different
1444 # effects depending on the current working directory.
agabled437d762016-10-17 09:35:11 -07001445 if os.path.abspath(scm_root) == os.path.abspath(e_dir):
primiano@chromium.org1c127382015-02-17 11:15:40 +00001446 e_par_dir = os.path.join(e_dir, os.pardir)
agabled437d762016-10-17 09:35:11 -07001447 if gclient_scm.scm.GIT.IsInsideWorkTree(e_par_dir):
1448 par_scm_root = gclient_scm.scm.GIT.GetCheckoutRoot(e_par_dir)
primiano@chromium.org1c127382015-02-17 11:15:40 +00001449 # rel_e_dir : relative path of entry w.r.t. its parent repo.
1450 rel_e_dir = os.path.relpath(e_dir, par_scm_root)
agabled437d762016-10-17 09:35:11 -07001451 if gclient_scm.scm.GIT.IsDirectoryVersioned(
1452 par_scm_root, rel_e_dir):
primiano@chromium.org1c127382015-02-17 11:15:40 +00001453 save_dir = scm.GetGitBackupDirPath()
1454 # Remove any eventual stale backup dir for the same project.
1455 if os.path.exists(save_dir):
1456 gclient_utils.rmtree(save_dir)
1457 os.rename(os.path.join(e_dir, '.git'), save_dir)
1458 # When switching between the two states (entry/ is a subproject
1459 # -> entry/ is part of the outer project), it is very likely
1460 # that some files are changed in the checkout, unless we are
1461 # jumping *exactly* across the commit which changed just DEPS.
1462 # In such case we want to cleanup any eventual stale files
1463 # (coming from the old subproject) in order to end up with a
1464 # clean checkout.
agabled437d762016-10-17 09:35:11 -07001465 gclient_scm.scm.GIT.CleanupDir(par_scm_root, rel_e_dir)
primiano@chromium.org1c127382015-02-17 11:15:40 +00001466 assert not os.path.exists(os.path.join(e_dir, '.git'))
1467 print(('\nWARNING: \'%s\' has been moved from DEPS to a higher '
1468 'level checkout. The git folder containing all the local'
1469 ' branches has been saved to %s.\n'
1470 'If you don\'t care about its state you can safely '
1471 'remove that folder to free up space.') %
1472 (entry, save_dir))
1473 continue
1474
borenet@google.com359bb642014-05-13 17:28:19 +00001475 if scm_root in full_entries:
primiano@chromium.org1c127382015-02-17 11:15:40 +00001476 logging.info('%s is part of a higher level checkout, not removing',
1477 scm.GetCheckoutRoot())
xusydoc@chromium.org885a9602013-05-31 09:54:40 +00001478 continue
1479
1480 file_list = []
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001481 scm.status(self._options, [], file_list)
1482 modified_files = file_list != []
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001483 if (not self._options.delete_unversioned_trees or
1484 (modified_files and not self._options.force)):
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001485 # There are modified files in this entry. Keep warning until
1486 # removed.
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001487 print(('\nWARNING: \'%s\' is no longer part of this client. '
1488 'It is recommended that you manually remove it.\n') %
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001489 entry_fixed)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001490 else:
1491 # Delete the entry
maruel@chromium.org73e21142010-07-05 13:32:01 +00001492 print('\n________ deleting \'%s\' in \'%s\'' % (
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001493 entry_fixed, self.root_dir))
digit@chromium.orgdc112ac2013-04-24 13:00:19 +00001494 gclient_utils.rmtree(e_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001495 # record the current list of entries for next time
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001496 self._SaveEntries()
maruel@chromium.org17cdf762010-05-28 17:30:52 +00001497 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001498
1499 def PrintRevInfo(self):
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001500 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +00001501 raise gclient_utils.Error('No solution specified')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001502 # Load all the settings.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001503 work_queue = gclient_utils.ExecutionQueue(
1504 self._options.jobs, None, False, verbose=self._options.verbose)
maruel@chromium.org049bced2010-08-12 13:37:20 +00001505 for s in self.dependencies:
1506 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +00001507 work_queue.flush({}, None, [], options=self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001508
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001509 def GetURLAndRev(dep):
1510 """Returns the revision-qualified SCM url for a Dependency."""
1511 if dep.parsed_url is None:
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001512 return None
agabled437d762016-10-17 09:35:11 -07001513 url, _ = gclient_utils.SplitUrlRevision(dep.parsed_url)
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001514 scm = gclient_scm.CreateSCM(
agabled437d762016-10-17 09:35:11 -07001515 dep.parsed_url, self.root_dir, dep.name, self.outbuf)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001516 if not os.path.isdir(scm.checkout_path):
1517 return None
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001518 return '%s@%s' % (url, scm.revinfo(self._options, [], None))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001519
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001520 if self._options.snapshot:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001521 new_gclient = ''
1522 # First level at .gclient
1523 for d in self.dependencies:
1524 entries = {}
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001525 def GrabDeps(dep):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001526 """Recursively grab dependencies."""
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001527 for d in dep.dependencies:
1528 entries[d.name] = GetURLAndRev(d)
1529 GrabDeps(d)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001530 GrabDeps(d)
1531 custom_deps = []
1532 for k in sorted(entries.keys()):
1533 if entries[k]:
1534 # Quotes aren't escaped...
1535 custom_deps.append(' \"%s\": \'%s\',\n' % (k, entries[k]))
1536 else:
1537 custom_deps.append(' \"%s\": None,\n' % k)
1538 new_gclient += self.DEFAULT_SNAPSHOT_SOLUTION_TEXT % {
1539 'solution_name': d.name,
1540 'solution_url': d.url,
nsylvain@google.comefc80932011-05-31 21:27:56 +00001541 'deps_file': d.deps_file,
smutae7ea312016-07-18 11:59:41 -07001542 'safesync_url' : d.safesync_url or '',
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001543 'managed': d.managed,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001544 'solution_deps': ''.join(custom_deps),
1545 }
1546 # Print the snapshot configuration file
1547 print(self.DEFAULT_SNAPSHOT_FILE_TEXT % {'solution_list': new_gclient})
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +00001548 else:
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001549 entries = {}
maruel@chromium.org68988972011-09-20 14:11:42 +00001550 for d in self.root.subtree(False):
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001551 if self._options.actual:
1552 entries[d.name] = GetURLAndRev(d)
1553 else:
1554 entries[d.name] = d.parsed_url
1555 keys = sorted(entries.keys())
1556 for x in keys:
maruel@chromium.orgce464892010-08-12 17:12:18 +00001557 print('%s: %s' % (x, entries[x]))
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +00001558 logging.info(str(self))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001559
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001560 def ParseDepsFile(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001561 """No DEPS to parse for a .gclient file."""
maruel@chromium.org049bced2010-08-12 13:37:20 +00001562 raise gclient_utils.Error('Internal error')
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001563
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00001564 def PrintLocationAndContents(self):
1565 # Print out the .gclient file. This is longer than if we just printed the
1566 # client dict, but more legible, and it might contain helpful comments.
1567 print('Loaded .gclient config in %s:\n%s' % (
1568 self.root_dir, self.config_content))
1569
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001570 @property
maruel@chromium.org75a59272010-06-11 22:34:03 +00001571 def root_dir(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001572 """Root directory of gclient checkout."""
maruel@chromium.org75a59272010-06-11 22:34:03 +00001573 return self._root_dir
1574
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001575 @property
maruel@chromium.org271375b2010-06-23 19:17:38 +00001576 def enforced_os(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001577 """What deps_os entries that are to be parsed."""
maruel@chromium.org271375b2010-06-23 19:17:38 +00001578 return self._enforced_os
1579
maruel@chromium.org68988972011-09-20 14:11:42 +00001580 @property
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001581 def recursion_limit(self):
1582 """How recursive can each dependencies in DEPS file can load DEPS file."""
1583 return self._recursion_limit
1584
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +00001585 @property
cmp@chromium.orgc401ad12014-07-02 23:20:08 +00001586 def try_recursedeps(self):
1587 """Whether to attempt using recursedeps-style recursion processing."""
cmp@chromium.orge84ac912014-06-30 23:14:35 +00001588 return True
1589
1590 @property
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +00001591 def target_os(self):
1592 return self._enforced_os
1593
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001594
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001595#### gclient commands.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001596
1597
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001598def CMDcleanup(parser, args):
agabled437d762016-10-17 09:35:11 -07001599 """DEPRECATED: SVN-only. Cleaned up all working copies.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001600
agabled437d762016-10-17 09:35:11 -07001601 This is a no-op in Git.
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001602 """
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001603 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1604 help='override deps for the specified (comma-separated) '
1605 'platform(s); \'all\' will process all deps_os '
1606 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001607 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001608 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001609 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001610 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001611 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00001612 client.PrintLocationAndContents()
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001613 return client.RunOnDeps('cleanup', args)
1614
1615
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001616@subcommand.usage('[command] [args ...]')
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001617def CMDrecurse(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001618 """Operates [command args ...] on all the dependencies.
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001619
1620 Runs a shell command on all entries.
qyearsley12fa6ff2016-08-24 09:18:40 -07001621 Sets GCLIENT_DEP_PATH environment variable as the dep's relative location to
ilevy@chromium.org37116242012-11-28 01:32:48 +00001622 root directory of the checkout.
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001623 """
1624 # Stop parsing at the first non-arg so that these go through to the command
1625 parser.disable_interspersed_args()
1626 parser.add_option('-s', '--scm', action='append', default=[],
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001627 help='Choose scm types to operate upon.')
maruel@chromium.org288054d2012-03-05 00:43:07 +00001628 parser.add_option('-i', '--ignore', action='store_true',
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001629 help='Ignore non-zero return codes from subcommands.')
1630 parser.add_option('--prepend-dir', action='store_true',
1631 help='Prepend relative dir for use with git <cmd> --null.')
1632 parser.add_option('--no-progress', action='store_true',
1633 help='Disable progress bar that shows sub-command updates')
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001634 options, args = parser.parse_args(args)
maruel@chromium.org45e9f2d2010-10-18 13:33:46 +00001635 if not args:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001636 print('Need to supply a command!', file=sys.stderr)
maruel@chromium.org45e9f2d2010-10-18 13:33:46 +00001637 return 1
maruel@chromium.org78cba522010-10-18 13:32:05 +00001638 root_and_entries = gclient_utils.GetGClientRootAndEntries()
1639 if not root_and_entries:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001640 print(
maruel@chromium.org78cba522010-10-18 13:32:05 +00001641 'You need to run gclient sync at least once to use \'recurse\'.\n'
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001642 'This is because .gclient_entries needs to exist and be up to date.',
1643 file=sys.stderr)
maruel@chromium.org78cba522010-10-18 13:32:05 +00001644 return 1
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001645
1646 # Normalize options.scm to a set()
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001647 scm_set = set()
1648 for scm in options.scm:
1649 scm_set.update(scm.split(','))
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001650 options.scm = scm_set
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001651
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001652 options.nohooks = True
1653 client = GClient.LoadCurrentConfig(options)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001654 return client.RunOnDeps('recurse', args, ignore_requirements=True,
1655 progress=not options.no_progress)
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001656
1657
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001658@subcommand.usage('[args ...]')
davidbarr@chromium.org12f944e2012-03-01 02:18:31 +00001659def CMDfetch(parser, args):
1660 """Fetches upstream commits for all modules.
1661
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001662 Completely git-specific. Simply runs 'git fetch [args ...]' for each module.
1663 """
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001664 (options, args) = parser.parse_args(args)
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00001665 return CMDrecurse(OptionParser(), [
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001666 '--jobs=%d' % options.jobs, '--scm=git', 'git', 'fetch'] + args)
1667
1668
1669def CMDgrep(parser, args):
1670 """Greps through git repos managed by gclient.
1671
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001672 Runs 'git grep [args...]' for each module.
1673 """
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001674 # We can't use optparse because it will try to parse arguments sent
1675 # to git grep and throw an error. :-(
1676 if not args or re.match('(-h|--help)$', args[0]):
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001677 print(
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001678 'Usage: gclient grep [-j <N>] git-grep-args...\n\n'
1679 'Example: "gclient grep -j10 -A2 RefCountedBase" runs\n"git grep '
1680 '-A2 RefCountedBase" on each of gclient\'s git\nrepos with up to '
1681 '10 jobs.\n\nBonus: page output by appending "|& less -FRSX" to the'
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001682 ' end of your query.',
1683 file=sys.stderr)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001684 return 1
1685
1686 jobs_arg = ['--jobs=1']
1687 if re.match(r'(-j|--jobs=)\d+$', args[0]):
1688 jobs_arg, args = args[:1], args[1:]
1689 elif re.match(r'(-j|--jobs)$', args[0]):
1690 jobs_arg, args = args[:2], args[2:]
1691
1692 return CMDrecurse(
1693 parser,
1694 jobs_arg + ['--ignore', '--prepend-dir', '--no-progress', '--scm=git',
1695 'git', 'grep', '--null', '--color=Always'] + args)
davidbarr@chromium.org12f944e2012-03-01 02:18:31 +00001696
1697
stip@chromium.orga735da22015-04-29 23:18:20 +00001698def CMDroot(parser, args):
1699 """Outputs the solution root (or current dir if there isn't one)."""
1700 (options, args) = parser.parse_args(args)
1701 client = GClient.LoadCurrentConfig(options)
1702 if client:
1703 print(os.path.abspath(client.root_dir))
1704 else:
1705 print(os.path.abspath('.'))
1706
1707
smutae7ea312016-07-18 11:59:41 -07001708@subcommand.usage('[url] [safesync url]')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001709def CMDconfig(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001710 """Creates a .gclient file in the current directory.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001711
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001712 This specifies the configuration for further commands. After update/sync,
1713 top-level DEPS files in each module are read to determine dependent
1714 modules to operate on as well. If optional [url] parameter is
1715 provided, then configuration is read from a specified Subversion server
1716 URL.
1717 """
szager@chromium.orge2e03202012-07-31 18:05:16 +00001718 # We do a little dance with the --gclientfile option. 'gclient config' is the
1719 # only command where it's acceptable to have both '--gclientfile' and '--spec'
1720 # arguments. So, we temporarily stash any --gclientfile parameter into
1721 # options.output_config_file until after the (gclientfile xor spec) error
1722 # check.
1723 parser.remove_option('--gclientfile')
1724 parser.add_option('--gclientfile', dest='output_config_file',
1725 help='Specify an alternate .gclient file')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001726 parser.add_option('--name',
1727 help='overrides the default name for the solution')
nsylvain@google.comefc80932011-05-31 21:27:56 +00001728 parser.add_option('--deps-file', default='DEPS',
1729 help='overrides the default name for the DEPS file for the'
1730 'main solutions and all sub-dependencies')
smutae7ea312016-07-18 11:59:41 -07001731 parser.add_option('--unmanaged', action='store_true', default=False,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001732 help='overrides the default behavior to make it possible '
smutae7ea312016-07-18 11:59:41 -07001733 'to have the main solution untouched by gclient '
1734 '(gclient will check out unmanaged dependencies but '
1735 'will never sync them)')
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001736 parser.add_option('--cache-dir',
1737 help='(git only) Cache all git repos into this dir and do '
1738 'shared clones from the cache, instead of cloning '
1739 'directly from the remote. (experimental)')
szager@chromium.orge2e03202012-07-31 18:05:16 +00001740 parser.set_defaults(config_filename=None)
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001741 (options, args) = parser.parse_args(args)
szager@chromium.orge2e03202012-07-31 18:05:16 +00001742 if options.output_config_file:
1743 setattr(options, 'config_filename', getattr(options, 'output_config_file'))
maruel@chromium.org5fc2a332010-05-26 19:37:15 +00001744 if ((options.spec and args) or len(args) > 2 or
1745 (not options.spec and not args)):
1746 parser.error('Inconsistent arguments. Use either --spec or one or 2 args')
1747
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001748 client = GClient('.', options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001749 if options.spec:
1750 client.SetConfig(options.spec)
1751 else:
maruel@chromium.org1ab7ffc2009-06-03 17:21:37 +00001752 base_url = args[0].rstrip('/')
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001753 if not options.name:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001754 name = base_url.split('/')[-1]
nsylvain@google.com12649ef2011-06-01 17:11:20 +00001755 if name.endswith('.git'):
1756 name = name[:-4]
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001757 else:
1758 # specify an alternate relpath for the given URL.
1759 name = options.name
agable@chromium.orgf2214672015-10-27 21:02:48 +00001760 if not os.path.abspath(os.path.join(os.getcwd(), name)).startswith(
1761 os.getcwd()):
1762 parser.error('Do not pass a relative path for --name.')
1763 if any(x in ('..', '.', '/', '\\') for x in name.split(os.sep)):
1764 parser.error('Do not include relative path components in --name.')
1765
nsylvain@google.comefc80932011-05-31 21:27:56 +00001766 deps_file = options.deps_file
smutae7ea312016-07-18 11:59:41 -07001767 safesync_url = ''
1768 if len(args) > 1:
1769 safesync_url = args[1]
1770 client.SetDefaultConfig(name, deps_file, base_url, safesync_url,
1771 managed=not options.unmanaged,
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001772 cache_dir=options.cache_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001773 client.SaveConfig()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001774 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001775
1776
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001777@subcommand.epilog("""Example:
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001778 gclient pack > patch.txt
1779 generate simple patch for configured client and dependences
1780""")
1781def CMDpack(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001782 """Generates a patch which can be applied at the root of the tree.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001783
agabled437d762016-10-17 09:35:11 -07001784 Internally, runs 'git diff' on each checked out module and
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001785 dependencies, and performs minimal postprocessing of the output. The
1786 resulting patch is printed to stdout and can be applied to a freshly
1787 checked out tree via 'patch -p0 < patchfile'.
1788 """
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001789 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1790 help='override deps for the specified (comma-separated) '
1791 'platform(s); \'all\' will process all deps_os '
1792 'references')
haitao.feng@intel.com306080c2012-05-04 13:11:29 +00001793 parser.remove_option('--jobs')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001794 (options, args) = parser.parse_args(args)
iannucci@chromium.org50395ea2013-04-04 04:47:42 +00001795 # Force jobs to 1 so the stdout is not annotated with the thread ids
haitao.feng@intel.com306080c2012-05-04 13:11:29 +00001796 options.jobs = 1
kbr@google.comab318592009-09-04 00:54:55 +00001797 client = GClient.LoadCurrentConfig(options)
1798 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001799 raise gclient_utils.Error('client not configured; see \'gclient config\'')
kbr@google.comab318592009-09-04 00:54:55 +00001800 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00001801 client.PrintLocationAndContents()
kbr@google.comab318592009-09-04 00:54:55 +00001802 return client.RunOnDeps('pack', args)
1803
1804
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001805def CMDstatus(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001806 """Shows modification status for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001807 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1808 help='override deps for the specified (comma-separated) '
1809 'platform(s); \'all\' will process all deps_os '
1810 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001811 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001812 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001813 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001814 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001815 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00001816 client.PrintLocationAndContents()
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001817 return client.RunOnDeps('status', args)
1818
1819
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001820@subcommand.epilog("""Examples:
maruel@chromium.org79692d62010-05-14 18:57:13 +00001821 gclient sync
1822 update files from SCM according to current configuration,
1823 *for modules which have changed since last update or sync*
1824 gclient sync --force
1825 update files from SCM according to current configuration, for
1826 all modules (useful for recovering files deleted from local copy)
1827 gclient sync --revision src@31000
1828 update src directory to r31000
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00001829
1830JSON output format:
1831If the --output-json option is specified, the following document structure will
1832be emitted to the provided file. 'null' entries may occur for subprojects which
1833are present in the gclient solution, but were not processed (due to custom_deps,
1834os_deps, etc.)
1835
1836{
1837 "solutions" : {
1838 "<name>": { # <name> is the posix-normalized path to the solution.
agabled437d762016-10-17 09:35:11 -07001839 "revision": [<git id hex string>|null],
1840 "scm": ["git"|null],
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00001841 }
1842 }
1843}
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001844""")
1845def CMDsync(parser, args):
1846 """Checkout/update all modules."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001847 parser.add_option('-f', '--force', action='store_true',
1848 help='force update even for unchanged modules')
1849 parser.add_option('-n', '--nohooks', action='store_true',
1850 help='don\'t run hooks after the update is complete')
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001851 parser.add_option('-p', '--noprehooks', action='store_true',
1852 help='don\'t run pre-DEPS hooks', default=False)
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001853 parser.add_option('-r', '--revision', action='append',
1854 dest='revisions', metavar='REV', default=[],
1855 help='Enforces revision/hash for the solutions with the '
1856 'format src@rev. The src@ part is optional and can be '
1857 'skipped. -r can be used multiple times when .gclient '
1858 'has multiple solutions configured and will work even '
smutae7ea312016-07-18 11:59:41 -07001859 'if the src@ part is skipped. Note that specifying '
1860 '--revision means your safesync_url gets ignored.')
maruel@chromium.org794207e2013-03-08 15:29:43 +00001861 parser.add_option('--with_branch_heads', action='store_true',
1862 help='Clone git "branch_heads" refspecs in addition to '
1863 'the default refspecs. This adds about 1/2GB to a '
1864 'full checkout. (git only)')
szager@chromium.org8d3348f2014-08-19 22:49:16 +00001865 parser.add_option('--with_tags', action='store_true',
1866 help='Clone git tags in addition to the default refspecs.')
agable2697cd12016-06-28 10:23:53 -07001867 parser.add_option('-H', '--head', action='store_true',
smutae7ea312016-07-18 11:59:41 -07001868 help='skips any safesync_urls specified in '
1869 'configured solutions and sync to head instead')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001870 parser.add_option('-D', '--delete_unversioned_trees', action='store_true',
steveblock@chromium.org98e69452012-02-16 16:36:43 +00001871 help='Deletes from the working copy any dependencies that '
1872 'have been removed since the last sync, as long as '
1873 'there are no local modifications. When used with '
1874 '--force, such dependencies are removed even if they '
1875 'have local modifications. When used with --reset, '
1876 'all untracked directories are removed from the '
tnagel@chromium.orga2aaa632014-02-28 21:47:27 +00001877 'working copy, excluding those which are explicitly '
steveblock@chromium.org98e69452012-02-16 16:36:43 +00001878 'ignored in the repository.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001879 parser.add_option('-R', '--reset', action='store_true',
1880 help='resets any local changes before updating (git only)')
bauerb@chromium.org2aad1b22011-07-22 12:00:41 +00001881 parser.add_option('-M', '--merge', action='store_true',
1882 help='merge upstream changes instead of trying to '
1883 'fast-forward or rebase')
dnj@chromium.org5b23e872015-02-20 21:25:57 +00001884 parser.add_option('-A', '--auto_rebase', action='store_true',
1885 help='Automatically rebase repositories against local '
1886 'checkout during update (git only).')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001887 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1888 help='override deps for the specified (comma-separated) '
1889 'platform(s); \'all\' will process all deps_os '
1890 'references')
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +00001891 parser.add_option('--upstream', action='store_true',
1892 help='Make repo state match upstream branch.')
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00001893 parser.add_option('--output-json',
1894 help='Output a json document to this path containing '
1895 'summary information about the sync.')
primiano@chromium.org5439ea52014-08-06 17:18:18 +00001896 parser.add_option('--no-history', action='store_true',
1897 help='GIT ONLY - Reduces the size/time of the checkout at '
1898 'the cost of no history. Requires Git 1.9+')
hinoka@chromium.org46b87412014-05-15 00:42:05 +00001899 parser.add_option('--shallow', action='store_true',
1900 help='GIT ONLY - Do a shallow clone into the cache dir. '
1901 'Requires Git 1.9+')
e.hakkinen@samsung.come8bc1aa2015-04-08 08:00:37 +00001902 parser.add_option('--no_bootstrap', '--no-bootstrap',
1903 action='store_true',
1904 help='Don\'t bootstrap from Google Storage.')
hinoka@chromium.org8a10f6d2014-06-23 18:38:57 +00001905 parser.add_option('--ignore_locks', action='store_true',
1906 help='GIT ONLY - Ignore cache locks.')
iannucci@chromium.org30a07982016-04-07 21:35:19 +00001907 parser.add_option('--break_repo_locks', action='store_true',
1908 help='GIT ONLY - Forcibly remove repo locks (e.g. '
1909 'index.lock). This should only be used if you know for '
1910 'certain that this invocation of gclient is the only '
1911 'thing operating on the git repos (e.g. on a bot).')
nodir@chromium.org5b48e482016-03-18 20:27:54 +00001912 parser.add_option('--lock_timeout', type='int', default=5000,
szager@chromium.orgdbb6f822016-02-02 22:59:30 +00001913 help='GIT ONLY - Deadline (in seconds) to wait for git '
nodir@chromium.org5b48e482016-03-18 20:27:54 +00001914 'cache lock to become available. Default is %default.')
agabled437d762016-10-17 09:35:11 -07001915 # TODO(agable): Remove these when the oldest CrOS release milestone is M56.
1916 parser.add_option('-t', '--transitive', action='store_true',
1917 help='DEPRECATED: This is a no-op.')
sdefresne69b1be12016-10-18 05:48:02 -07001918 parser.add_option('-m', '--manually_grab_svn_rev', action='store_true',
agabled437d762016-10-17 09:35:11 -07001919 help='DEPRECATED: This is a no-op.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001920 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001921 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001922
1923 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001924 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001925
smutae7ea312016-07-18 11:59:41 -07001926 if options.revisions and options.head:
1927 # TODO(maruel): Make it a parser.error if it doesn't break any builder.
1928 print('Warning: you cannot use both --head and --revision')
1929
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001930 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00001931 client.PrintLocationAndContents()
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00001932 ret = client.RunOnDeps('update', args)
1933 if options.output_json:
1934 slns = {}
1935 for d in client.subtree(True):
1936 normed = d.name.replace('\\', '/').rstrip('/') + '/'
1937 slns[normed] = {
1938 'revision': d.got_revision,
1939 'scm': d.used_scm.name if d.used_scm else None,
hinoka@chromium.org17db9052014-05-10 01:11:29 +00001940 'url': str(d.url) if d.url else None,
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00001941 }
1942 with open(options.output_json, 'wb') as f:
1943 json.dump({'solutions': slns}, f)
1944 return ret
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001945
1946
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001947CMDupdate = CMDsync
1948
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001949
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001950def CMDdiff(parser, args):
1951 """Displays local diff for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001952 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1953 help='override deps for the specified (comma-separated) '
1954 'platform(s); \'all\' will process all deps_os '
1955 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001956 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001957 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001958 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001959 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001960 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00001961 client.PrintLocationAndContents()
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001962 return client.RunOnDeps('diff', args)
1963
1964
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001965def CMDrevert(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001966 """Reverts all modifications in every dependencies.
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001967
1968 That's the nuclear option to get back to a 'clean' state. It removes anything
agabled437d762016-10-17 09:35:11 -07001969 that shows up in git status."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001970 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1971 help='override deps for the specified (comma-separated) '
1972 'platform(s); \'all\' will process all deps_os '
1973 'references')
1974 parser.add_option('-n', '--nohooks', action='store_true',
1975 help='don\'t run hooks after the revert is complete')
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001976 parser.add_option('-p', '--noprehooks', action='store_true',
1977 help='don\'t run pre-DEPS hooks', default=False)
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +00001978 parser.add_option('--upstream', action='store_true',
1979 help='Make repo state match upstream branch.')
iannucci@chromium.orgbf525dc2016-04-07 22:00:28 +00001980 parser.add_option('--break_repo_locks', action='store_true',
1981 help='GIT ONLY - Forcibly remove repo locks (e.g. '
1982 'index.lock). This should only be used if you know for '
1983 'certain that this invocation of gclient is the only '
1984 'thing operating on the git repos (e.g. on a bot).')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001985 (options, args) = parser.parse_args(args)
1986 # --force is implied.
1987 options.force = True
steveblock@chromium.org98e69452012-02-16 16:36:43 +00001988 options.reset = False
1989 options.delete_unversioned_trees = False
agablec903d732016-07-26 09:07:24 -07001990 options.merge = False
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001991 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001992 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001993 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001994 return client.RunOnDeps('revert', args)
1995
1996
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001997def CMDrunhooks(parser, args):
1998 """Runs hooks for files that have been modified in the local working copy."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001999 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2000 help='override deps for the specified (comma-separated) '
2001 'platform(s); \'all\' will process all deps_os '
2002 'references')
2003 parser.add_option('-f', '--force', action='store_true', default=True,
2004 help='Deprecated. No effect.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002005 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002006 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002007 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002008 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002009 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002010 client.PrintLocationAndContents()
maruel@chromium.org5df6a462009-08-28 18:52:26 +00002011 options.force = True
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002012 options.nohooks = False
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002013 return client.RunOnDeps('runhooks', args)
2014
2015
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002016def CMDrevinfo(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002017 """Outputs revision info mapping for the client and its dependencies.
maruel@chromium.org9eda4112010-06-11 18:56:10 +00002018
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002019 This allows the capture of an overall 'revision' for the source tree that
maruel@chromium.org9eda4112010-06-11 18:56:10 +00002020 can be used to reproduce the same tree in the future. It is only useful for
agabled437d762016-10-17 09:35:11 -07002021 'unpinned dependencies', i.e. DEPS/deps references without a git hash.
2022 A git branch name isn't 'pinned' since the actual commit can change.
maruel@chromium.org9eda4112010-06-11 18:56:10 +00002023 """
2024 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2025 help='override deps for the specified (comma-separated) '
2026 'platform(s); \'all\' will process all deps_os '
2027 'references')
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00002028 parser.add_option('-a', '--actual', action='store_true',
2029 help='gets the actual checked out revisions instead of the '
2030 'ones specified in the DEPS and .gclient files')
maruel@chromium.org9eda4112010-06-11 18:56:10 +00002031 parser.add_option('-s', '--snapshot', action='store_true',
2032 help='creates a snapshot .gclient file of the current '
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00002033 'version of all repositories to reproduce the tree, '
2034 'implies -a')
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 client.PrintRevInfo()
maruel@chromium.org79692d62010-05-14 18:57:13 +00002040 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002041
2042
szager@google.comb9a78d32012-03-13 18:46:21 +00002043def CMDhookinfo(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002044 """Outputs the hooks that would be run by `gclient runhooks`."""
szager@google.comb9a78d32012-03-13 18:46:21 +00002045 (options, args) = parser.parse_args(args)
2046 options.force = True
2047 client = GClient.LoadCurrentConfig(options)
2048 if not client:
2049 raise gclient_utils.Error('client not configured; see \'gclient config\'')
2050 client.RunOnDeps(None, [])
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002051 print('; '.join(' '.join(hook) for hook in client.GetHooks(options)))
szager@google.comb9a78d32012-03-13 18:46:21 +00002052 return 0
2053
2054
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00002055def CMDverify(parser, args):
2056 """Verifies the DEPS file deps are only from allowed_hosts."""
2057 (options, args) = parser.parse_args(args)
2058 client = GClient.LoadCurrentConfig(options)
2059 if not client:
2060 raise gclient_utils.Error('client not configured; see \'gclient config\'')
2061 client.RunOnDeps(None, [])
2062 # Look at each first-level dependency of this gclient only.
2063 for dep in client.dependencies:
2064 bad_deps = dep.findDepsFromNotAllowedHosts()
2065 if not bad_deps:
2066 continue
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002067 print("There are deps from not allowed hosts in file %s" % dep.deps_file)
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00002068 for bad_dep in bad_deps:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002069 print("\t%s at %s" % (bad_dep.name, bad_dep.url))
2070 print("allowed_hosts:", ', '.join(dep.allowed_hosts))
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00002071 sys.stdout.flush()
2072 raise gclient_utils.Error(
2073 'dependencies from disallowed hosts; check your DEPS file.')
2074 return 0
2075
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002076class OptionParser(optparse.OptionParser):
szager@chromium.orge2e03202012-07-31 18:05:16 +00002077 gclientfile_default = os.environ.get('GCLIENT_FILE', '.gclient')
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002078
2079 def __init__(self, **kwargs):
2080 optparse.OptionParser.__init__(
2081 self, version='%prog ' + __version__, **kwargs)
2082
2083 # Some arm boards have issues with parallel sync.
2084 if platform.machine().startswith('arm'):
2085 jobs = 1
2086 else:
2087 jobs = max(8, gclient_utils.NumLocalCpus())
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002088
2089 self.add_option(
2090 '-j', '--jobs', default=jobs, type='int',
2091 help='Specify how many SCM commands can run in parallel; defaults to '
tnagel@chromium.orga2aaa632014-02-28 21:47:27 +00002092 '%default on this machine')
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002093 self.add_option(
2094 '-v', '--verbose', action='count', default=0,
2095 help='Produces additional output for diagnostics. Can be used up to '
2096 'three times for more logging info.')
2097 self.add_option(
2098 '--gclientfile', dest='config_filename',
2099 help='Specify an alternate %s file' % self.gclientfile_default)
2100 self.add_option(
2101 '--spec',
2102 help='create a gclient file containing the provided string. Due to '
2103 'Cygwin/Python brokenness, it can\'t contain any newlines.')
2104 self.add_option(
2105 '--no-nag-max', default=False, action='store_true',
scottmg@chromium.orgf547c802013-09-27 17:55:26 +00002106 help='Ignored for backwards compatibility.')
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002107
2108 def parse_args(self, args=None, values=None):
2109 """Integrates standard options processing."""
2110 options, args = optparse.OptionParser.parse_args(self, args, values)
2111 levels = [logging.ERROR, logging.WARNING, logging.INFO, logging.DEBUG]
2112 logging.basicConfig(
2113 level=levels[min(options.verbose, len(levels) - 1)],
maruel@chromium.org0895b752011-08-26 20:40:33 +00002114 format='%(module)s(%(lineno)d) %(funcName)s:%(message)s')
szager@chromium.orge2e03202012-07-31 18:05:16 +00002115 if options.config_filename and options.spec:
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002116 self.error('Cannot specifiy both --gclientfile and --spec')
rdsmith@chromium.orgd9591f02014-02-05 19:28:20 +00002117 if (options.config_filename and
2118 options.config_filename != os.path.basename(options.config_filename)):
2119 self.error('--gclientfile target must be a filename, not a path')
szager@chromium.orge2e03202012-07-31 18:05:16 +00002120 if not options.config_filename:
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002121 options.config_filename = self.gclientfile_default
maruel@chromium.org0895b752011-08-26 20:40:33 +00002122 options.entries_filename = options.config_filename + '_entries'
2123 if options.jobs < 1:
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002124 self.error('--jobs must be 1 or higher')
maruel@chromium.org0895b752011-08-26 20:40:33 +00002125
2126 # These hacks need to die.
2127 if not hasattr(options, 'revisions'):
2128 # GClient.RunOnDeps expects it even if not applicable.
2129 options.revisions = []
smutae7ea312016-07-18 11:59:41 -07002130 if not hasattr(options, 'head'):
2131 options.head = None
maruel@chromium.org0895b752011-08-26 20:40:33 +00002132 if not hasattr(options, 'nohooks'):
2133 options.nohooks = True
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00002134 if not hasattr(options, 'noprehooks'):
2135 options.noprehooks = True
maruel@chromium.org0895b752011-08-26 20:40:33 +00002136 if not hasattr(options, 'deps_os'):
2137 options.deps_os = None
maruel@chromium.org0895b752011-08-26 20:40:33 +00002138 if not hasattr(options, 'force'):
2139 options.force = None
2140 return (options, args)
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002141
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002142
2143def disable_buffering():
2144 # Make stdout auto-flush so buildbot doesn't kill us during lengthy
2145 # operations. Python as a strong tendency to buffer sys.stdout.
2146 sys.stdout = gclient_utils.MakeFileAutoFlush(sys.stdout)
2147 # Make stdout annotated with the thread ids.
2148 sys.stdout = gclient_utils.MakeFileAnnotated(sys.stdout)
maruel@chromium.org0895b752011-08-26 20:40:33 +00002149
2150
sbc@chromium.org013731e2015-02-26 18:28:43 +00002151def main(argv):
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002152 """Doesn't parse the arguments here, just find the right subcommand to
2153 execute."""
maruel@chromium.org82798cb2012-02-23 18:16:12 +00002154 if sys.hexversion < 0x02060000:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002155 print(
maruel@chromium.org82798cb2012-02-23 18:16:12 +00002156 '\nYour python version %s is unsupported, please upgrade.\n' %
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002157 sys.version.split(' ', 1)[0],
2158 file=sys.stderr)
maruel@chromium.org82798cb2012-02-23 18:16:12 +00002159 return 2
bcwhite@chromium.org6683ab42013-02-11 16:13:47 +00002160 if not sys.executable:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002161 print(
2162 '\nPython cannot find the location of it\'s own executable.\n',
2163 file=sys.stderr)
bcwhite@chromium.org6683ab42013-02-11 16:13:47 +00002164 return 2
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002165 fix_encoding.fix_encoding()
2166 disable_buffering()
iannucci@chromium.org596cd5c2016-04-04 21:34:39 +00002167 setup_color.init()
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002168 dispatcher = subcommand.CommandDispatcher(__name__)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00002169 try:
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002170 return dispatcher.execute(OptionParser(), argv)
xusydoc@chromium.org2fd6c3f2013-05-03 21:57:55 +00002171 except KeyboardInterrupt:
2172 gclient_utils.GClientChildren.KillAllRemainingChildren()
2173 raise
vapier@chromium.orga81a56e2015-11-11 07:56:13 +00002174 except (gclient_utils.Error, subprocess2.CalledProcessError) as e:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002175 print('Error: %s' % str(e), file=sys.stderr)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00002176 return 1
borenet@google.com6a9b1682014-03-24 18:35:23 +00002177 finally:
2178 gclient_utils.PrintWarnings()
sbc@chromium.org013731e2015-02-26 18:28:43 +00002179 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002180
2181
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00002182if '__main__' == __name__:
sbc@chromium.org013731e2015-02-26 18:28:43 +00002183 try:
2184 sys.exit(main(sys.argv[1:]))
2185 except KeyboardInterrupt:
2186 sys.stderr.write('interrupted\n')
2187 sys.exit(1)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002188
2189# vim: ts=2:sw=2:tw=80:et: