blob: 6d7988afaac6401c3397f04d52bfa0c20286ea3a [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__(
agablea98a6cd2016-11-15 14:30:10 -0800193 self, parent, 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
mmoss@chromium.org8f93f792014-08-26 23:24:09 +0000199 self._deps_file = deps_file
maruel@chromium.org064186c2011-09-27 23:53:33 +0000200 self._url = url
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000201 # 'managed' determines whether or not this dependency is synced/updated by
202 # gclient after gclient checks it out initially. The difference between
203 # 'managed' and 'should_process' is that the user specifies 'managed' via
smutae7ea312016-07-18 11:59:41 -0700204 # the --unmanaged command-line flag or a .gclient config, where
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000205 # 'should_process' is dynamically set by gclient if it goes over its
206 # recursion limit and controls gclient's behavior so it does not misbehave.
207 self._managed = managed
208 self._should_process = should_process
agabledce6ddc2016-09-08 10:02:16 -0700209 # If this is a recursed-upon sub-dependency, and the parent has
210 # use_relative_paths set, then this dependency should check out its own
211 # dependencies relative to that parent's path for this, rather than
212 # relative to the .gclient file.
213 self._relative = relative
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +0000214 # This is a mutable value which has the list of 'target_os' OSes listed in
215 # the current deps file.
216 self.local_target_os = None
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000217
218 # These are only set in .gclient and not in DEPS files.
219 self._custom_vars = custom_vars or {}
220 self._custom_deps = custom_deps or {}
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000221 self._custom_hooks = custom_hooks or []
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000222
maruel@chromium.org064186c2011-09-27 23:53:33 +0000223 # Post process the url to remove trailing slashes.
224 if isinstance(self._url, basestring):
225 # urls are sometime incorrectly written as proto://host/path/@rev. Replace
226 # it to proto://host/path@rev.
maruel@chromium.org064186c2011-09-27 23:53:33 +0000227 self._url = self._url.replace('/@', '@')
agabled437d762016-10-17 09:35:11 -0700228 elif not isinstance(self._url, (self.FromImpl, None.__class__)):
maruel@chromium.org064186c2011-09-27 23:53:33 +0000229 raise gclient_utils.Error(
230 ('dependency url must be either a string, None, '
agabled437d762016-10-17 09:35:11 -0700231 'or From() instead of %s') % self._url.__class__.__name__)
mmoss@chromium.orgd0b272b2013-01-30 23:55:33 +0000232 # Make any deps_file path platform-appropriate.
233 for sep in ['/', '\\']:
234 self._deps_file = self._deps_file.replace(sep, os.sep)
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000235
236 @property
237 def deps_file(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000238 return self._deps_file
239
240 @property
241 def managed(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000242 return self._managed
243
244 @property
245 def parent(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000246 return self._parent
247
248 @property
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000249 def root(self):
250 """Returns the root node, a GClient object."""
251 if not self.parent:
252 # This line is to signal pylint that it could be a GClient instance.
253 return self or GClient(None, None)
254 return self.parent.root
255
256 @property
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000257 def should_process(self):
258 """True if this dependency should be processed, i.e. checked out."""
259 return self._should_process
260
261 @property
262 def custom_vars(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000263 return self._custom_vars.copy()
264
265 @property
266 def custom_deps(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000267 return self._custom_deps.copy()
268
maruel@chromium.org064186c2011-09-27 23:53:33 +0000269 @property
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000270 def custom_hooks(self):
271 return self._custom_hooks[:]
272
273 @property
maruel@chromium.org064186c2011-09-27 23:53:33 +0000274 def url(self):
275 return self._url
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000276
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000277 @property
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +0000278 def target_os(self):
279 if self.local_target_os is not None:
280 return tuple(set(self.local_target_os).union(self.parent.target_os))
281 else:
282 return self.parent.target_os
283
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000284 def get_custom_deps(self, name, url):
285 """Returns a custom deps if applicable."""
286 if self.parent:
287 url = self.parent.get_custom_deps(name, url)
288 # None is a valid return value to disable a dependency.
289 return self.custom_deps.get(name, url)
290
maruel@chromium.org064186c2011-09-27 23:53:33 +0000291
292class Dependency(gclient_utils.WorkItem, DependencySettings):
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000293 """Object that represents a dependency checkout."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000294
agablea98a6cd2016-11-15 14:30:10 -0800295 def __init__(self, parent, name, url, managed, custom_deps,
agabledce6ddc2016-09-08 10:02:16 -0700296 custom_vars, custom_hooks, deps_file, should_process,
297 relative):
maruel@chromium.org6ca8bf82011-09-19 23:04:30 +0000298 gclient_utils.WorkItem.__init__(self, name)
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000299 DependencySettings.__init__(
agablea98a6cd2016-11-15 14:30:10 -0800300 self, parent, url, managed, custom_deps, custom_vars,
agabledce6ddc2016-09-08 10:02:16 -0700301 custom_hooks, deps_file, should_process, relative)
maruel@chromium.org68988972011-09-20 14:11:42 +0000302
303 # This is in both .gclient and DEPS files:
maruel@chromium.org064186c2011-09-27 23:53:33 +0000304 self._deps_hooks = []
maruel@chromium.org68988972011-09-20 14:11:42 +0000305
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000306 self._pre_deps_hooks = []
307
maruel@chromium.org68988972011-09-20 14:11:42 +0000308 # Calculates properties:
maruel@chromium.org064186c2011-09-27 23:53:33 +0000309 self._parsed_url = None
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +0000310 self._dependencies = []
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000311 # A cache of the files affected by the current operation, necessary for
312 # hooks.
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000313 self._file_list = []
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000314 # List of host names from which dependencies are allowed.
315 # Default is an empty set, meaning unspecified in DEPS file, and hence all
316 # hosts will be allowed. Non-empty set means whitelist of hosts.
317 # allowed_hosts var is scoped to its DEPS file, and so it isn't recursive.
318 self._allowed_hosts = frozenset()
maruel@chromium.org85c2a192010-07-22 21:14:43 +0000319 # If it is not set to True, the dependency wasn't processed for its child
320 # dependency, i.e. its DEPS wasn't read.
maruel@chromium.org064186c2011-09-27 23:53:33 +0000321 self._deps_parsed = False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000322 # This dependency has been processed, i.e. checked out
maruel@chromium.org064186c2011-09-27 23:53:33 +0000323 self._processed = False
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000324 # This dependency had its pre-DEPS hooks run
325 self._pre_deps_hooks_ran = False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000326 # This dependency had its hook run
maruel@chromium.org064186c2011-09-27 23:53:33 +0000327 self._hooks_ran = False
kustermann@google.coma692e8f2013-04-18 08:32:04 +0000328 # This is the scm used to checkout self.url. It may be used by dependencies
329 # to get the datetime of the revision we checked out.
330 self._used_scm = None
szager@chromium.org4ad264b2014-05-20 04:43:47 +0000331 self._used_revision = None
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +0000332 # The actual revision we ended up getting, or None if that information is
333 # unavailable
334 self._got_revision = None
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000335
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000336 # This is a mutable value that overrides the normal recursion limit for this
337 # dependency. It is read from the actual DEPS file so cannot be set on
338 # class instantiation.
339 self.recursion_override = None
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000340 # recursedeps is a mutable value that selectively overrides the default
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000341 # 'no recursion' setting on a dep-by-dep basis. It will replace
342 # recursion_override.
iannucci@chromium.orgafa11ac2016-05-04 22:17:34 +0000343 #
344 # It will be a dictionary of {deps_name: {"deps_file": depfile_name}} or
345 # None.
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000346 self.recursedeps = None
hinoka885e5b12016-06-08 14:40:09 -0700347 # This is inherited from WorkItem. We want the URL to be a resource.
348 if url and isinstance(url, basestring):
349 # The url is usually given to gclient either as https://blah@123
qyearsley12fa6ff2016-08-24 09:18:40 -0700350 # or just https://blah. The @123 portion is irrelevant.
hinoka885e5b12016-06-08 14:40:09 -0700351 self.resources.append(url.split('@')[0])
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000352
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000353 if not self.name and self.parent:
354 raise gclient_utils.Error('Dependency without name')
355
maruel@chromium.org470b5432011-10-11 18:18:19 +0000356 @property
357 def requirements(self):
358 """Calculate the list of requirements."""
359 requirements = set()
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000360 # self.parent is implicitly a requirement. This will be recursive by
361 # definition.
362 if self.parent and self.parent.name:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000363 requirements.add(self.parent.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000364
365 # For a tree with at least 2 levels*, the leaf node needs to depend
366 # on the level higher up in an orderly way.
367 # This becomes messy for >2 depth as the DEPS file format is a dictionary,
368 # thus unsorted, while the .gclient format is a list thus sorted.
369 #
370 # * _recursion_limit is hard coded 2 and there is no hope to change this
371 # value.
372 #
373 # Interestingly enough, the following condition only works in the case we
374 # want: self is a 2nd level node. 3nd level node wouldn't need this since
375 # they already have their parent as a requirement.
maruel@chromium.org470b5432011-10-11 18:18:19 +0000376 if self.parent and self.parent.parent and not self.parent.parent.parent:
377 requirements |= set(i.name for i in self.root.dependencies if i.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000378
379 if isinstance(self.url, self.FromImpl):
maruel@chromium.org470b5432011-10-11 18:18:19 +0000380 requirements.add(self.url.module_name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000381
maruel@chromium.org470b5432011-10-11 18:18:19 +0000382 if self.name:
383 requirements |= set(
384 obj.name for obj in self.root.subtree(False)
385 if (obj is not self
386 and obj.name and
387 self.name.startswith(posixpath.join(obj.name, ''))))
388 requirements = tuple(sorted(requirements))
389 logging.info('Dependency(%s).requirements = %s' % (self.name, requirements))
390 return requirements
391
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000392 @property
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000393 def try_recursedeps(self):
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000394 """Returns False if recursion_override is ever specified."""
395 if self.recursion_override is not None:
396 return False
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000397 return self.parent.try_recursedeps
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000398
399 @property
400 def recursion_limit(self):
401 """Returns > 0 if this dependency is not too recursed to be processed."""
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000402 # We continue to support the absence of recursedeps until tools and DEPS
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000403 # using recursion_override are updated.
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000404 if self.try_recursedeps and self.parent.recursedeps != None:
405 if self.name in self.parent.recursedeps:
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000406 return 1
407
408 if self.recursion_override is not None:
409 return self.recursion_override
410 return max(self.parent.recursion_limit - 1, 0)
411
maruel@chromium.org470b5432011-10-11 18:18:19 +0000412 def verify_validity(self):
413 """Verifies that this Dependency is fine to add as a child of another one.
414
415 Returns True if this entry should be added, False if it is a duplicate of
416 another entry.
417 """
418 logging.info('Dependency(%s).verify_validity()' % self.name)
419 if self.name in [s.name for s in self.parent.dependencies]:
420 raise gclient_utils.Error(
421 'The same name "%s" appears multiple times in the deps section' %
422 self.name)
423 if not self.should_process:
424 # Return early, no need to set requirements.
425 return True
426
427 # This require a full tree traversal with locks.
428 siblings = [d for d in self.root.subtree(False) if d.name == self.name]
429 for sibling in siblings:
maruel@chromium.orgb848d5b2012-10-10 23:25:50 +0000430 self_url = self.LateOverride(self.url)
431 sibling_url = sibling.LateOverride(sibling.url)
432 # Allow to have only one to be None or ''.
433 if self_url != sibling_url and bool(self_url) == bool(sibling_url):
maruel@chromium.org470b5432011-10-11 18:18:19 +0000434 raise gclient_utils.Error(
maruel@chromium.orgb848d5b2012-10-10 23:25:50 +0000435 ('Dependency %s specified more than once:\n'
436 ' %s [%s]\n'
437 'vs\n'
438 ' %s [%s]') % (
439 self.name,
440 sibling.hierarchy(),
441 sibling_url,
442 self.hierarchy(),
443 self_url))
maruel@chromium.org470b5432011-10-11 18:18:19 +0000444 # In theory we could keep it as a shadow of the other one. In
445 # practice, simply ignore it.
446 logging.warn('Won\'t process duplicate dependency %s' % sibling)
447 return False
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000448 return True
maruel@chromium.org064186c2011-09-27 23:53:33 +0000449
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000450 def LateOverride(self, url):
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000451 """Resolves the parsed url from url.
452
453 Manages From() keyword accordingly. Do not touch self.parsed_url nor
454 self.url because it may called with other urls due to From()."""
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000455 assert self.parsed_url == None or not self.should_process, self.parsed_url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000456 parsed_url = self.get_custom_deps(self.name, url)
457 if parsed_url != url:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000458 logging.info(
459 'Dependency(%s).LateOverride(%s) -> %s' %
460 (self.name, url, parsed_url))
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000461 return parsed_url
462
463 if isinstance(url, self.FromImpl):
maruel@chromium.org470b5432011-10-11 18:18:19 +0000464 # Requires tree traversal.
maruel@chromium.org68988972011-09-20 14:11:42 +0000465 ref = [
466 dep for dep in self.root.subtree(True) if url.module_name == dep.name
467 ]
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000468 if not ref:
469 raise gclient_utils.Error('Failed to find one reference to %s. %s' % (
470 url.module_name, ref))
471 # It may happen that len(ref) > 1 but it's no big deal.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000472 ref = ref[0]
maruel@chromium.org98d05fa2010-07-22 21:58:01 +0000473 sub_target = url.sub_target_name or self.name
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000474 found_deps = [d for d in ref.dependencies if d.name == sub_target]
475 if len(found_deps) != 1:
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000476 raise gclient_utils.Error(
maruel@chromium.org98023df2011-09-07 18:44:47 +0000477 'Couldn\'t find %s in %s, referenced by %s (parent: %s)\n%s' % (
478 sub_target, ref.name, self.name, self.parent.name,
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000479 str(self.root)))
maruel@chromium.org98023df2011-09-07 18:44:47 +0000480
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000481 # Call LateOverride() again.
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000482 found_dep = found_deps[0]
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000483 parsed_url = found_dep.LateOverride(found_dep.url)
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000484 logging.info(
maruel@chromium.org470b5432011-10-11 18:18:19 +0000485 'Dependency(%s).LateOverride(%s) -> %s (From)' %
486 (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000487 return parsed_url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000488
489 if isinstance(url, basestring):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000490 parsed_url = urlparse.urlparse(url)
scr@chromium.orgf1eccaf2014-04-11 15:51:33 +0000491 if (not parsed_url[0] and
492 not re.match(r'^\w+\@[\w\.-]+\:[\w\/]+', parsed_url[2])):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000493 # A relative url. Fetch the real base.
494 path = parsed_url[2]
495 if not path.startswith('/'):
496 raise gclient_utils.Error(
497 'relative DEPS entry \'%s\' must begin with a slash' % url)
498 # Create a scm just to query the full url.
499 parent_url = self.parent.parsed_url
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000500 scm = gclient_scm.CreateSCM(
501 parent_url, self.root.root_dir, None, self.outbuf)
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000502 parsed_url = scm.FullUrlForRelativeUrl(url)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000503 else:
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000504 parsed_url = url
maruel@chromium.org470b5432011-10-11 18:18:19 +0000505 logging.info(
506 'Dependency(%s).LateOverride(%s) -> %s' %
507 (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000508 return parsed_url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000509
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000510 if url is None:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000511 logging.info(
512 'Dependency(%s).LateOverride(%s) -> %s' % (self.name, url, url))
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000513 return url
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000514
515 raise gclient_utils.Error('Unknown url type')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000516
bratell@opera.comed2b4fe2013-12-16 14:34:12 +0000517 @staticmethod
518 def MergeWithOsDeps(deps, deps_os, target_os_list):
519 """Returns a new "deps" structure that is the deps sent in updated
520 with information from deps_os (the deps_os section of the DEPS
521 file) that matches the list of target os."""
522 os_overrides = {}
523 for the_target_os in target_os_list:
524 the_target_os_deps = deps_os.get(the_target_os, {})
525 for os_dep_key, os_dep_value in the_target_os_deps.iteritems():
526 overrides = os_overrides.setdefault(os_dep_key, [])
527 overrides.append((the_target_os, os_dep_value))
528
529 # If any os didn't specify a value (we have fewer value entries
530 # than in the os list), then it wants to use the default value.
531 for os_dep_key, os_dep_value in os_overrides.iteritems():
532 if len(os_dep_value) != len(target_os_list):
qyearsley12fa6ff2016-08-24 09:18:40 -0700533 # Record the default value too so that we don't accidentally
bratell@opera.comed2b4fe2013-12-16 14:34:12 +0000534 # set it to None or miss a conflicting DEPS.
535 if os_dep_key in deps:
536 os_dep_value.append(('default', deps[os_dep_key]))
537
538 target_os_deps = {}
539 for os_dep_key, os_dep_value in os_overrides.iteritems():
540 # os_dep_value is a list of (os, value) pairs.
541 possible_values = set(x[1] for x in os_dep_value if x[1] is not None)
542 if not possible_values:
543 target_os_deps[os_dep_key] = None
544 else:
545 if len(possible_values) > 1:
546 # It would be possible to abort here but it would be
547 # unfortunate if we end up preventing any kind of checkout.
548 logging.error('Conflicting dependencies for %s: %s. (target_os=%s)',
549 os_dep_key, os_dep_value, target_os_list)
550 # Sorting to get the same result every time in case of conflicts.
551 target_os_deps[os_dep_key] = sorted(possible_values)[0]
552
553 new_deps = deps.copy()
554 new_deps.update(target_os_deps)
555 return new_deps
556
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000557 def ParseDepsFile(self):
maruel@chromium.org271375b2010-06-23 19:17:38 +0000558 """Parses the DEPS file for this dependency."""
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000559 assert not self.deps_parsed
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000560 assert not self.dependencies
iannucci@chromium.org0c3f3052014-05-22 00:29:22 +0000561
562 deps_content = None
563 use_strict = False
cmp@chromium.org76ce73c2014-07-02 00:13:18 +0000564
565 # First try to locate the configured deps file. If it's missing, fallback
566 # to DEPS.
567 deps_files = [self.deps_file]
568 if 'DEPS' not in deps_files:
569 deps_files.append('DEPS')
570 for deps_file in deps_files:
571 filepath = os.path.join(self.root.root_dir, self.name, deps_file)
572 if os.path.isfile(filepath):
573 logging.info(
574 'ParseDepsFile(%s): %s file found at %s', self.name, deps_file,
575 filepath)
576 break
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000577 logging.info(
cmp@chromium.org76ce73c2014-07-02 00:13:18 +0000578 'ParseDepsFile(%s): No %s file found at %s', self.name, deps_file,
579 filepath)
580
581 if os.path.isfile(filepath):
maruel@chromium.org46304292010-10-28 11:42:00 +0000582 deps_content = gclient_utils.FileRead(filepath)
cmp@chromium.org76ce73c2014-07-02 00:13:18 +0000583 logging.debug('ParseDepsFile(%s) read:\n%s', self.name, deps_content)
iannucci@chromium.org0c3f3052014-05-22 00:29:22 +0000584 use_strict = 'use strict' in deps_content.splitlines()[0]
585
586 local_scope = {}
587 if deps_content:
588 # One thing is unintuitive, vars = {} must happen before Var() use.
589 var = self.VarImpl(self.custom_vars, local_scope)
590 if use_strict:
591 logging.info(
592 'ParseDepsFile(%s): Strict Mode Enabled', self.name)
593 global_scope = {
594 '__builtins__': {'None': None},
595 'Var': var.Lookup,
596 'deps_os': {},
597 }
598 else:
599 global_scope = {
iannucci@chromium.org0c3f3052014-05-22 00:29:22 +0000600 'From': self.FromImpl,
601 'Var': var.Lookup,
602 'deps_os': {},
603 }
maruel@chromium.org46304292010-10-28 11:42:00 +0000604 # Eval the content.
605 try:
606 exec(deps_content, global_scope, local_scope)
vapier@chromium.orga81a56e2015-11-11 07:56:13 +0000607 except SyntaxError as e:
maruel@chromium.org46304292010-10-28 11:42:00 +0000608 gclient_utils.SyntaxErrorToError(filepath, e)
iannucci@chromium.org0c3f3052014-05-22 00:29:22 +0000609 if use_strict:
610 for key, val in local_scope.iteritems():
611 if not isinstance(val, (dict, list, tuple, str)):
612 raise gclient_utils.Error(
613 'ParseDepsFile(%s): Strict mode disallows %r -> %r' %
614 (self.name, key, val))
615
maruel@chromium.org271375b2010-06-23 19:17:38 +0000616 deps = local_scope.get('deps', {})
ilevy@chromium.org27ca3a92012-10-17 18:11:02 +0000617 if 'recursion' in local_scope:
618 self.recursion_override = local_scope.get('recursion')
619 logging.warning(
620 'Setting %s recursion to %d.', self.name, self.recursion_limit)
iannucci@chromium.orgafa11ac2016-05-04 22:17:34 +0000621 self.recursedeps = None
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000622 if 'recursedeps' in local_scope:
iannucci@chromium.orgafa11ac2016-05-04 22:17:34 +0000623 self.recursedeps = {}
624 for ent in local_scope['recursedeps']:
625 if isinstance(ent, basestring):
626 self.recursedeps[ent] = {"deps_file": self.deps_file}
627 else: # (depname, depsfilename)
628 self.recursedeps[ent[0]] = {"deps_file": ent[1]}
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000629 logging.warning('Found recursedeps %r.', repr(self.recursedeps))
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +0000630 # If present, save 'target_os' in the local_target_os property.
631 if 'target_os' in local_scope:
632 self.local_target_os = local_scope['target_os']
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000633 # load os specific dependencies if defined. these dependencies may
634 # override or extend the values defined by the 'deps' member.
bratell@opera.comed2b4fe2013-12-16 14:34:12 +0000635 target_os_list = self.target_os
636 if 'deps_os' in local_scope and target_os_list:
637 deps = self.MergeWithOsDeps(deps, local_scope['deps_os'], target_os_list)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000638
maruel@chromium.org271375b2010-06-23 19:17:38 +0000639 # If a line is in custom_deps, but not in the solution, we want to append
640 # this line to the solution.
641 for d in self.custom_deps:
642 if d not in deps:
643 deps[d] = self.custom_deps[d]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000644
645 # If use_relative_paths is set in the DEPS file, regenerate
646 # the dictionary using paths relative to the directory containing
cmp@chromium.orgf2def0a2014-07-16 19:48:54 +0000647 # the DEPS file. Also update recursedeps if use_relative_paths is
648 # enabled.
agabledce6ddc2016-09-08 10:02:16 -0700649 # If the deps file doesn't set use_relative_paths, but the parent did
650 # (and therefore set self.relative on this Dependency object), then we
651 # want to modify the deps and recursedeps by prepending the parent
652 # directory of this dependency.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000653 use_relative_paths = local_scope.get('use_relative_paths', False)
agabledce6ddc2016-09-08 10:02:16 -0700654 rel_prefix = None
maruel@chromium.org271375b2010-06-23 19:17:38 +0000655 if use_relative_paths:
agabledce6ddc2016-09-08 10:02:16 -0700656 rel_prefix = self.name
657 elif self._relative:
658 rel_prefix = os.path.dirname(self.name)
659 if rel_prefix:
cmp@chromium.orgf2def0a2014-07-16 19:48:54 +0000660 logging.warning('use_relative_paths enabled.')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000661 rel_deps = {}
662 for d, url in deps.items():
663 # normpath is required to allow DEPS to use .. in their
664 # dependency local path.
agabledce6ddc2016-09-08 10:02:16 -0700665 rel_deps[os.path.normpath(os.path.join(rel_prefix, d))] = url
666 logging.warning('Updating deps by prepending %s.', rel_prefix)
maruel@chromium.org271375b2010-06-23 19:17:38 +0000667 deps = rel_deps
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000668
cmp@chromium.orgf2def0a2014-07-16 19:48:54 +0000669 # Update recursedeps if it's set.
670 if self.recursedeps is not None:
agabledce6ddc2016-09-08 10:02:16 -0700671 logging.warning('Updating recursedeps by prepending %s.', rel_prefix)
iannucci@chromium.orgafa11ac2016-05-04 22:17:34 +0000672 rel_deps = {}
673 for depname, options in self.recursedeps.iteritems():
agabledce6ddc2016-09-08 10:02:16 -0700674 rel_deps[
675 os.path.normpath(os.path.join(rel_prefix, depname))] = options
cmp@chromium.orgf2def0a2014-07-16 19:48:54 +0000676 self.recursedeps = rel_deps
677
agabledce6ddc2016-09-08 10:02:16 -0700678
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000679 if 'allowed_hosts' in local_scope:
680 try:
681 self._allowed_hosts = frozenset(local_scope.get('allowed_hosts'))
682 except TypeError: # raised if non-iterable
683 pass
684 if not self._allowed_hosts:
685 logging.warning("allowed_hosts is specified but empty %s",
686 self._allowed_hosts)
687 raise gclient_utils.Error(
688 'ParseDepsFile(%s): allowed_hosts must be absent '
689 'or a non-empty iterable' % self.name)
690
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000691 # Convert the deps into real Dependency.
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000692 deps_to_add = []
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000693 for name, url in deps.iteritems():
maruel@chromium.org68988972011-09-20 14:11:42 +0000694 should_process = self.recursion_limit and self.should_process
iannucci@chromium.orgafa11ac2016-05-04 22:17:34 +0000695 deps_file = self.deps_file
696 if self.recursedeps is not None:
697 ent = self.recursedeps.get(name)
698 if ent is not None:
699 deps_file = ent['deps_file']
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000700 deps_to_add.append(Dependency(
agablea98a6cd2016-11-15 14:30:10 -0800701 self, name, url, None, None, self.custom_vars, None,
agabledce6ddc2016-09-08 10:02:16 -0700702 deps_file, should_process, use_relative_paths))
maruel@chromium.orgb9be0652011-10-14 18:05:40 +0000703 deps_to_add.sort(key=lambda x: x.name)
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000704
705 # override named sets of hooks by the custom hooks
706 hooks_to_run = []
707 hook_names_to_suppress = [c.get('name', '') for c in self.custom_hooks]
708 for hook in local_scope.get('hooks', []):
709 if hook.get('name', '') not in hook_names_to_suppress:
710 hooks_to_run.append(hook)
711
712 # add the replacements and any additions
713 for hook in self.custom_hooks:
714 if 'action' in hook:
715 hooks_to_run.append(hook)
716
Dirk Prankeda3a29e2017-02-27 15:29:36 -0800717 if self.recursion_limit:
718 self._pre_deps_hooks = [self.GetHookAction(hook, []) for hook in
719 local_scope.get('pre_deps_hooks', [])]
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000720
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000721 self.add_dependencies_and_close(deps_to_add, hooks_to_run)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000722 logging.info('ParseDepsFile(%s) done' % self.name)
723
724 def add_dependencies_and_close(self, deps_to_add, hooks):
725 """Adds the dependencies, hooks and mark the parsing as done."""
maruel@chromium.orgb9be0652011-10-14 18:05:40 +0000726 for dep in deps_to_add:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000727 if dep.verify_validity():
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000728 self.add_dependency(dep)
729 self._mark_as_parsed(hooks)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000730
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000731 def findDepsFromNotAllowedHosts(self):
732 """Returns a list of depenecies from not allowed hosts.
733
734 If allowed_hosts is not set, allows all hosts and returns empty list.
735 """
736 if not self._allowed_hosts:
737 return []
738 bad_deps = []
739 for dep in self._dependencies:
szager@chromium.orgbd772dd2014-11-05 18:43:08 +0000740 # Don't enforce this for custom_deps.
741 if dep.name in self._custom_deps:
742 continue
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000743 if isinstance(dep.url, basestring):
744 parsed_url = urlparse.urlparse(dep.url)
745 if parsed_url.netloc and parsed_url.netloc not in self._allowed_hosts:
746 bad_deps.append(dep)
747 return bad_deps
748
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +0000749 # Arguments number differs from overridden method
Quinten Yearsleyb2cc4a92016-12-15 13:53:26 -0800750 # pylint: disable=arguments-differ
maruel@chromium.org3742c842010-09-09 19:27:14 +0000751 def run(self, revision_overrides, command, args, work_queue, options):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000752 """Runs |command| then parse the DEPS file."""
maruel@chromium.org470b5432011-10-11 18:18:19 +0000753 logging.info('Dependency(%s).run()' % self.name)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000754 assert self._file_list == []
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000755 if not self.should_process:
756 return
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000757 # When running runhooks, there's no need to consult the SCM.
758 # All known hooks are expected to run unconditionally regardless of working
759 # copy state, so skip the SCM status check.
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +0000760 run_scm = command not in ('runhooks', 'recurse', None)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000761 parsed_url = self.LateOverride(self.url)
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000762 file_list = [] if not options.nohooks else None
szager@chromium.org3a3608d2014-10-22 21:13:52 +0000763 revision_override = revision_overrides.pop(self.name, None)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000764 if run_scm and parsed_url:
agabled437d762016-10-17 09:35:11 -0700765 # Create a shallow copy to mutate revision.
766 options = copy.copy(options)
767 options.revision = revision_override
768 self._used_revision = options.revision
769 self._used_scm = gclient_scm.CreateSCM(
770 parsed_url, self.root.root_dir, self.name, self.outbuf,
771 out_cb=work_queue.out_cb)
772 self._got_revision = self._used_scm.RunCommand(command, options, args,
773 file_list)
774 if file_list:
775 file_list = [os.path.join(self.name, f.strip()) for f in file_list]
maruel@chromium.org68988972011-09-20 14:11:42 +0000776
777 # TODO(phajdan.jr): We should know exactly when the paths are absolute.
778 # Convert all absolute paths to relative.
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000779 for i in range(len(file_list or [])):
maruel@chromium.org68988972011-09-20 14:11:42 +0000780 # It depends on the command being executed (like runhooks vs sync).
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000781 if not os.path.isabs(file_list[i]):
maruel@chromium.org68988972011-09-20 14:11:42 +0000782 continue
783 prefix = os.path.commonprefix(
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000784 [self.root.root_dir.lower(), file_list[i].lower()])
785 file_list[i] = file_list[i][len(prefix):]
maruel@chromium.org68988972011-09-20 14:11:42 +0000786 # Strip any leading path separators.
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000787 while file_list[i].startswith(('\\', '/')):
788 file_list[i] = file_list[i][1:]
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000789
790 # Always parse the DEPS file.
791 self.ParseDepsFile()
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000792 self._run_is_done(file_list or [], parsed_url)
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000793 if command in ('update', 'revert') and not options.noprehooks:
794 self.RunPreDepsHooks()
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000795
796 if self.recursion_limit:
797 # Parse the dependencies of this dependency.
798 for s in self.dependencies:
799 work_queue.enqueue(s)
800
801 if command == 'recurse':
agabled437d762016-10-17 09:35:11 -0700802 # Skip file only checkout.
803 scm = gclient_scm.GetScmName(parsed_url)
804 if not options.scm or scm in options.scm:
805 cwd = os.path.normpath(os.path.join(self.root.root_dir, self.name))
806 # Pass in the SCM type as an env variable. Make sure we don't put
807 # unicode strings in the environment.
808 env = os.environ.copy()
809 if scm:
810 env['GCLIENT_SCM'] = str(scm)
811 if parsed_url:
812 env['GCLIENT_URL'] = str(parsed_url)
813 env['GCLIENT_DEP_PATH'] = str(self.name)
814 if options.prepend_dir and scm == 'git':
815 print_stdout = False
816 def filter_fn(line):
817 """Git-specific path marshaling. It is optimized for git-grep."""
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000818
agabled437d762016-10-17 09:35:11 -0700819 def mod_path(git_pathspec):
820 match = re.match('^(\\S+?:)?([^\0]+)$', git_pathspec)
821 modified_path = os.path.join(self.name, match.group(2))
822 branch = match.group(1) or ''
823 return '%s%s' % (branch, modified_path)
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000824
agabled437d762016-10-17 09:35:11 -0700825 match = re.match('^Binary file ([^\0]+) matches$', line)
826 if match:
827 print('Binary file %s matches\n' % mod_path(match.group(1)))
828 return
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000829
agabled437d762016-10-17 09:35:11 -0700830 items = line.split('\0')
831 if len(items) == 2 and items[1]:
832 print('%s : %s' % (mod_path(items[0]), items[1]))
833 elif len(items) >= 2:
834 # Multiple null bytes or a single trailing null byte indicate
835 # git is likely displaying filenames only (such as with -l)
836 print('\n'.join(mod_path(path) for path in items if path))
837 else:
838 print(line)
839 else:
840 print_stdout = True
841 filter_fn = None
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000842
agabled437d762016-10-17 09:35:11 -0700843 if parsed_url is None:
844 print('Skipped omitted dependency %s' % cwd, file=sys.stderr)
845 elif os.path.isdir(cwd):
846 try:
847 gclient_utils.CheckCallAndFilter(
848 args, cwd=cwd, env=env, print_stdout=print_stdout,
849 filter_fn=filter_fn,
850 )
851 except subprocess2.CalledProcessError:
852 if not options.ignore:
853 raise
854 else:
855 print('Skipped missing %s' % cwd, file=sys.stderr)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000856
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000857
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000858 @gclient_utils.lockedmethod
859 def _run_is_done(self, file_list, parsed_url):
860 # Both these are kept for hooks that are run as a separate tree traversal.
861 self._file_list = file_list
862 self._parsed_url = parsed_url
863 self._processed = True
864
szager@google.comb9a78d32012-03-13 18:46:21 +0000865 @staticmethod
866 def GetHookAction(hook_dict, matching_file_list):
867 """Turns a parsed 'hook' dict into an executable command."""
868 logging.debug(hook_dict)
869 logging.debug(matching_file_list)
870 command = hook_dict['action'][:]
871 if command[0] == 'python':
872 # If the hook specified "python" as the first item, the action is a
873 # Python script. Run it by starting a new copy of the same
874 # interpreter.
875 command[0] = sys.executable
876 if '$matching_files' in command:
877 splice_index = command.index('$matching_files')
878 command[splice_index:splice_index + 1] = matching_file_list
879 return command
880
881 def GetHooks(self, options):
882 """Evaluates all hooks, and return them in a flat list.
883
884 RunOnDeps() must have been called before to load the DEPS.
885 """
886 result = []
maruel@chromium.org68988972011-09-20 14:11:42 +0000887 if not self.should_process or not self.recursion_limit:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000888 # Don't run the hook when it is above recursion_limit.
szager@google.comb9a78d32012-03-13 18:46:21 +0000889 return result
maruel@chromium.orgdc7445d2010-07-09 21:05:29 +0000890 # If "--force" was specified, run all hooks regardless of what files have
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000891 # changed.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000892 if self.deps_hooks:
agabled437d762016-10-17 09:35:11 -0700893 # TODO(maruel): If the user is using git, then we don't know
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000894 # what files have changed so we always run all hooks. It'd be nice to fix
895 # that.
896 if (options.force or
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000897 gclient_scm.GetScmName(self.parsed_url) in ('git', None) or
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000898 os.path.isdir(os.path.join(self.root.root_dir, self.name, '.git'))):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000899 for hook_dict in self.deps_hooks:
szager@google.comb9a78d32012-03-13 18:46:21 +0000900 result.append(self.GetHookAction(hook_dict, []))
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000901 else:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000902 # Run hooks on the basis of whether the files from the gclient operation
903 # match each hook's pattern.
904 for hook_dict in self.deps_hooks:
905 pattern = re.compile(hook_dict['pattern'])
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000906 matching_file_list = [
907 f for f in self.file_list_and_children if pattern.search(f)
908 ]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000909 if matching_file_list:
szager@google.comb9a78d32012-03-13 18:46:21 +0000910 result.append(self.GetHookAction(hook_dict, matching_file_list))
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000911 for s in self.dependencies:
szager@google.comb9a78d32012-03-13 18:46:21 +0000912 result.extend(s.GetHooks(options))
913 return result
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000914
szager@google.comb9a78d32012-03-13 18:46:21 +0000915 def RunHooksRecursively(self, options):
916 assert self.hooks_ran == False
maruel@chromium.org064186c2011-09-27 23:53:33 +0000917 self._hooks_ran = True
szager@google.comb9a78d32012-03-13 18:46:21 +0000918 for hook in self.GetHooks(options):
919 try:
ilevy@chromium.orgc28d3772013-07-12 19:42:37 +0000920 start_time = time.time()
szager@google.comb9a78d32012-03-13 18:46:21 +0000921 gclient_utils.CheckCallAndFilterAndHeader(
922 hook, cwd=self.root.root_dir, always=True)
vapier@chromium.orga81a56e2015-11-11 07:56:13 +0000923 except (gclient_utils.Error, subprocess2.CalledProcessError) as e:
szager@google.comb9a78d32012-03-13 18:46:21 +0000924 # Use a discrete exit status code of 2 to indicate that a hook action
925 # failed. Users of this script may wish to treat hook action failures
926 # differently from VC failures.
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +0000927 print('Error: %s' % str(e), file=sys.stderr)
szager@google.comb9a78d32012-03-13 18:46:21 +0000928 sys.exit(2)
ilevy@chromium.orgc28d3772013-07-12 19:42:37 +0000929 finally:
930 elapsed_time = time.time() - start_time
931 if elapsed_time > 10:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +0000932 print("Hook '%s' took %.2f secs" % (
933 gclient_utils.CommandToStr(hook), elapsed_time))
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000934
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000935 def RunPreDepsHooks(self):
936 assert self.processed
937 assert self.deps_parsed
938 assert not self.pre_deps_hooks_ran
939 assert not self.hooks_ran
940 for s in self.dependencies:
941 assert not s.processed
942 self._pre_deps_hooks_ran = True
943 for hook in self.pre_deps_hooks:
944 try:
945 start_time = time.time()
946 gclient_utils.CheckCallAndFilterAndHeader(
947 hook, cwd=self.root.root_dir, always=True)
vapier@chromium.orga81a56e2015-11-11 07:56:13 +0000948 except (gclient_utils.Error, subprocess2.CalledProcessError) as e:
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000949 # Use a discrete exit status code of 2 to indicate that a hook action
950 # failed. Users of this script may wish to treat hook action failures
951 # differently from VC failures.
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +0000952 print('Error: %s' % str(e), file=sys.stderr)
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000953 sys.exit(2)
954 finally:
955 elapsed_time = time.time() - start_time
956 if elapsed_time > 10:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +0000957 print("Hook '%s' took %.2f secs" % (
958 gclient_utils.CommandToStr(hook), elapsed_time))
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000959
960
maruel@chromium.org0d812442010-08-10 12:41:08 +0000961 def subtree(self, include_all):
maruel@chromium.orgad3287e2011-10-03 19:15:10 +0000962 """Breadth first recursion excluding root node."""
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000963 dependencies = self.dependencies
964 for d in dependencies:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000965 if d.should_process or include_all:
maruel@chromium.orgad3287e2011-10-03 19:15:10 +0000966 yield d
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000967 for d in dependencies:
maruel@chromium.orgad3287e2011-10-03 19:15:10 +0000968 for i in d.subtree(include_all):
969 yield i
970
971 def depth_first_tree(self):
972 """Depth-first recursion including the root node."""
973 yield self
974 for i in self.dependencies:
975 for j in i.depth_first_tree():
976 if j.should_process:
977 yield j
maruel@chromium.orgc57e4f22010-07-22 21:37:46 +0000978
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000979 @gclient_utils.lockedmethod
980 def add_dependency(self, new_dep):
981 self._dependencies.append(new_dep)
982
983 @gclient_utils.lockedmethod
984 def _mark_as_parsed(self, new_hooks):
985 self._deps_hooks.extend(new_hooks)
986 self._deps_parsed = True
987
maruel@chromium.org68988972011-09-20 14:11:42 +0000988 @property
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000989 @gclient_utils.lockedmethod
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +0000990 def dependencies(self):
991 return tuple(self._dependencies)
992
993 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000994 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000995 def deps_hooks(self):
996 return tuple(self._deps_hooks)
997
998 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000999 @gclient_utils.lockedmethod
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001000 def pre_deps_hooks(self):
1001 return tuple(self._pre_deps_hooks)
1002
1003 @property
1004 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001005 def parsed_url(self):
1006 return self._parsed_url
1007
1008 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001009 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001010 def deps_parsed(self):
maruel@chromium.org3223edd2011-10-10 23:17:39 +00001011 """This is purely for debugging purposes. It's not used anywhere."""
maruel@chromium.org064186c2011-09-27 23:53:33 +00001012 return self._deps_parsed
1013
1014 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001015 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001016 def processed(self):
1017 return self._processed
1018
1019 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001020 @gclient_utils.lockedmethod
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001021 def pre_deps_hooks_ran(self):
1022 return self._pre_deps_hooks_ran
1023
1024 @property
1025 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001026 def hooks_ran(self):
1027 return self._hooks_ran
1028
1029 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001030 @gclient_utils.lockedmethod
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00001031 def allowed_hosts(self):
1032 return self._allowed_hosts
1033
1034 @property
1035 @gclient_utils.lockedmethod
maruel@chromium.org861fd0f2010-07-23 03:05:05 +00001036 def file_list(self):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001037 return tuple(self._file_list)
1038
1039 @property
kustermann@google.coma692e8f2013-04-18 08:32:04 +00001040 def used_scm(self):
1041 """SCMWrapper instance for this dependency or None if not processed yet."""
1042 return self._used_scm
1043
1044 @property
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00001045 @gclient_utils.lockedmethod
1046 def got_revision(self):
1047 return self._got_revision
1048
1049 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001050 def file_list_and_children(self):
1051 result = list(self.file_list)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +00001052 for d in self.dependencies:
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001053 result.extend(d.file_list_and_children)
maruel@chromium.org68988972011-09-20 14:11:42 +00001054 return tuple(result)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +00001055
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001056 def __str__(self):
1057 out = []
agablea98a6cd2016-11-15 14:30:10 -08001058 for i in ('name', 'url', 'parsed_url', 'custom_deps',
maruel@chromium.org3c74bc92011-09-15 19:17:21 +00001059 'custom_vars', 'deps_hooks', 'file_list', 'should_process',
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00001060 'processed', 'hooks_ran', 'deps_parsed', 'requirements',
1061 'allowed_hosts'):
maruel@chromium.org3c74bc92011-09-15 19:17:21 +00001062 # First try the native property if it exists.
1063 if hasattr(self, '_' + i):
1064 value = getattr(self, '_' + i, False)
1065 else:
1066 value = getattr(self, i, False)
1067 if value:
1068 out.append('%s: %s' % (i, value))
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001069
1070 for d in self.dependencies:
1071 out.extend([' ' + x for x in str(d).splitlines()])
1072 out.append('')
1073 return '\n'.join(out)
1074
1075 def __repr__(self):
1076 return '%s: %s' % (self.name, self.url)
1077
maruel@chromium.orgbffb9042010-07-22 20:59:36 +00001078 def hierarchy(self):
maruel@chromium.orgbc2d2f92010-07-22 21:26:48 +00001079 """Returns a human-readable hierarchical reference to a Dependency."""
maruel@chromium.orgbffb9042010-07-22 20:59:36 +00001080 out = '%s(%s)' % (self.name, self.url)
1081 i = self.parent
1082 while i and i.name:
1083 out = '%s(%s) -> %s' % (i.name, i.url, out)
1084 i = i.parent
1085 return out
1086
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001087
1088class GClient(Dependency):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001089 """Object that represent a gclient checkout. A tree of Dependency(), one per
1090 solution or DEPS entry."""
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001091
1092 DEPS_OS_CHOICES = {
1093 "win32": "win",
1094 "win": "win",
1095 "cygwin": "win",
1096 "darwin": "mac",
1097 "mac": "mac",
1098 "unix": "unix",
1099 "linux": "unix",
1100 "linux2": "unix",
maruel@chromium.org244e3442011-06-12 15:20:55 +00001101 "linux3": "unix",
szager@chromium.orgf8c95cd2012-06-01 22:26:52 +00001102 "android": "android",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001103 }
1104
1105 DEFAULT_CLIENT_FILE_TEXT = ("""\
1106solutions = [
smutae7ea312016-07-18 11:59:41 -07001107 { "name" : "%(solution_name)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001108 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +00001109 "deps_file" : "%(deps_file)s",
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001110 "managed" : %(managed)s,
smutae7ea312016-07-18 11:59:41 -07001111 "custom_deps" : {
1112 },
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001113 },
1114]
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001115cache_dir = %(cache_dir)r
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001116""")
1117
1118 DEFAULT_SNAPSHOT_SOLUTION_TEXT = ("""\
smutae7ea312016-07-18 11:59:41 -07001119 { "name" : "%(solution_name)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001120 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +00001121 "deps_file" : "%(deps_file)s",
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001122 "managed" : %(managed)s,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001123 "custom_deps" : {
smutae7ea312016-07-18 11:59:41 -07001124%(solution_deps)s },
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001125 },
1126""")
1127
1128 DEFAULT_SNAPSHOT_FILE_TEXT = ("""\
1129# Snapshot generated with gclient revinfo --snapshot
1130solutions = [
maruel@chromium.org73e21142010-07-05 13:32:01 +00001131%(solution_list)s]
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001132""")
1133
1134 def __init__(self, root_dir, options):
maruel@chromium.org0d812442010-08-10 12:41:08 +00001135 # Do not change previous behavior. Only solution level and immediate DEPS
1136 # are processed.
1137 self._recursion_limit = 2
agablea98a6cd2016-11-15 14:30:10 -08001138 Dependency.__init__(self, None, None, None, True, None, None, None,
agabledce6ddc2016-09-08 10:02:16 -07001139 'unused', True, None)
maruel@chromium.org0d425922010-06-21 19:22:24 +00001140 self._options = options
maruel@chromium.org271375b2010-06-23 19:17:38 +00001141 if options.deps_os:
1142 enforced_os = options.deps_os.split(',')
1143 else:
1144 enforced_os = [self.DEPS_OS_CHOICES.get(sys.platform, 'unix')]
1145 if 'all' in enforced_os:
1146 enforced_os = self.DEPS_OS_CHOICES.itervalues()
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001147 self._enforced_os = tuple(set(enforced_os))
maruel@chromium.org271375b2010-06-23 19:17:38 +00001148 self._root_dir = root_dir
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001149 self.config_content = None
1150
borenet@google.com88d10082014-03-21 17:24:48 +00001151 def _CheckConfig(self):
1152 """Verify that the config matches the state of the existing checked-out
1153 solutions."""
1154 for dep in self.dependencies:
1155 if dep.managed and dep.url:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001156 scm = gclient_scm.CreateSCM(
1157 dep.url, self.root_dir, dep.name, self.outbuf)
smut@google.comd33eab32014-07-07 19:35:18 +00001158 actual_url = scm.GetActualRemoteURL(self._options)
borenet@google.com4e9be262014-04-08 19:40:30 +00001159 if actual_url and not scm.DoesRemoteURLMatch(self._options):
levarum@chromium.org27a6f9a2016-05-28 00:21:49 +00001160 mirror = scm.GetCacheMirror()
1161 if mirror:
1162 mirror_string = '%s (exists=%s)' % (mirror.mirror_path,
1163 mirror.exists())
1164 else:
1165 mirror_string = 'not used'
borenet@google.com0a427372014-04-02 19:12:13 +00001166 raise gclient_utils.Error('''
borenet@google.com88d10082014-03-21 17:24:48 +00001167Your .gclient file seems to be broken. The requested URL is different from what
borenet@google.com0a427372014-04-02 19:12:13 +00001168is actually checked out in %(checkout_path)s.
borenet@google.com88d10082014-03-21 17:24:48 +00001169
borenet@google.com97882362014-04-07 20:06:02 +00001170The .gclient file contains:
levarum@chromium.org27a6f9a2016-05-28 00:21:49 +00001171URL: %(expected_url)s (%(expected_scm)s)
1172Cache mirror: %(mirror_string)s
borenet@google.com97882362014-04-07 20:06:02 +00001173
1174The local checkout in %(checkout_path)s reports:
1175%(actual_url)s (%(actual_scm)s)
borenet@google.com88d10082014-03-21 17:24:48 +00001176
1177You should ensure that the URL listed in .gclient is correct and either change
agabled437d762016-10-17 09:35:11 -07001178it or fix the checkout.
borenet@google.com88d10082014-03-21 17:24:48 +00001179''' % {'checkout_path': os.path.join(self.root_dir, dep.name),
1180 'expected_url': dep.url,
1181 'expected_scm': gclient_scm.GetScmName(dep.url),
levarum@chromium.org27a6f9a2016-05-28 00:21:49 +00001182 'mirror_string' : mirror_string,
borenet@google.com88d10082014-03-21 17:24:48 +00001183 'actual_url': actual_url,
1184 'actual_scm': gclient_scm.GetScmName(actual_url)})
1185
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001186 def SetConfig(self, content):
maruel@chromium.orgf13a4182011-09-22 00:26:15 +00001187 assert not self.dependencies
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001188 config_dict = {}
1189 self.config_content = content
1190 try:
1191 exec(content, config_dict)
vapier@chromium.orga81a56e2015-11-11 07:56:13 +00001192 except SyntaxError as e:
maruel@chromium.org5990f9d2010-07-07 18:02:58 +00001193 gclient_utils.SyntaxErrorToError('.gclient', e)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001194
peter@chromium.org1efccc82012-04-27 16:34:38 +00001195 # Append any target OS that is not already being enforced to the tuple.
1196 target_os = config_dict.get('target_os', [])
stuartmorgan@chromium.org18a4f6a2012-11-02 13:37:24 +00001197 if config_dict.get('target_os_only', False):
1198 self._enforced_os = tuple(set(target_os))
1199 else:
1200 self._enforced_os = tuple(set(self._enforced_os).union(target_os))
1201
dyen@chromium.orgd915cca2014-08-07 21:41:37 +00001202 cache_dir = config_dict.get('cache_dir')
1203 if cache_dir:
1204 cache_dir = os.path.join(self.root_dir, cache_dir)
1205 cache_dir = os.path.abspath(cache_dir)
szager@chromium.orgcaf5bef2014-08-24 18:56:32 +00001206 # If running on a bot, force break any stale git cache locks.
dnj@chromium.orgb682b3e2014-08-25 19:17:12 +00001207 if os.path.exists(cache_dir) and os.environ.get('CHROME_HEADLESS'):
szager@chromium.org4848fb62014-08-24 19:16:31 +00001208 subprocess2.check_call(['git', 'cache', 'unlock', '--cache-dir',
1209 cache_dir, '--force', '--all'])
dyen@chromium.orgd915cca2014-08-07 21:41:37 +00001210 gclient_scm.GitWrapper.cache_dir = cache_dir
1211 git_cache.Mirror.SetCachePath(cache_dir)
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001212
stuartmorgan@chromium.org18a4f6a2012-11-02 13:37:24 +00001213 if not target_os and config_dict.get('target_os_only', False):
1214 raise gclient_utils.Error('Can\'t use target_os_only if target_os is '
1215 'not specified')
peter@chromium.org1efccc82012-04-27 16:34:38 +00001216
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001217 deps_to_add = []
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001218 for s in config_dict.get('solutions', []):
maruel@chromium.org81843b82010-06-28 16:49:26 +00001219 try:
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001220 deps_to_add.append(Dependency(
maruel@chromium.org81843b82010-06-28 16:49:26 +00001221 self, s['name'], s['url'],
smutae7ea312016-07-18 11:59:41 -07001222 s.get('managed', True),
maruel@chromium.org81843b82010-06-28 16:49:26 +00001223 s.get('custom_deps', {}),
maruel@chromium.org0d812442010-08-10 12:41:08 +00001224 s.get('custom_vars', {}),
petermayo@chromium.orge79161a2013-07-09 14:40:37 +00001225 s.get('custom_hooks', []),
nsylvain@google.comefc80932011-05-31 21:27:56 +00001226 s.get('deps_file', 'DEPS'),
agabledce6ddc2016-09-08 10:02:16 -07001227 True,
1228 None))
maruel@chromium.org81843b82010-06-28 16:49:26 +00001229 except KeyError:
1230 raise gclient_utils.Error('Invalid .gclient file. Solution is '
1231 'incomplete: %s' % s)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001232 self.add_dependencies_and_close(deps_to_add, config_dict.get('hooks', []))
1233 logging.info('SetConfig() done')
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001234
1235 def SaveConfig(self):
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001236 gclient_utils.FileWrite(os.path.join(self.root_dir,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001237 self._options.config_filename),
1238 self.config_content)
1239
1240 @staticmethod
1241 def LoadCurrentConfig(options):
1242 """Searches for and loads a .gclient file relative to the current working
1243 dir. Returns a GClient object."""
szager@chromium.orge2e03202012-07-31 18:05:16 +00001244 if options.spec:
1245 client = GClient('.', options)
1246 client.SetConfig(options.spec)
1247 else:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00001248 if options.verbose:
1249 print('Looking for %s starting from %s\n' % (
1250 options.config_filename, os.getcwd()))
szager@chromium.orge2e03202012-07-31 18:05:16 +00001251 path = gclient_utils.FindGclientRoot(os.getcwd(), options.config_filename)
1252 if not path:
1253 return None
1254 client = GClient(path, options)
1255 client.SetConfig(gclient_utils.FileRead(
1256 os.path.join(path, options.config_filename)))
maruel@chromium.org69392e72011-10-13 22:09:00 +00001257
1258 if (options.revisions and
1259 len(client.dependencies) > 1 and
1260 any('@' not in r for r in options.revisions)):
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001261 print(
1262 ('You must specify the full solution name like --revision %s@%s\n'
1263 'when you have multiple solutions setup in your .gclient file.\n'
1264 'Other solutions present are: %s.') % (
maruel@chromium.org69392e72011-10-13 22:09:00 +00001265 client.dependencies[0].name,
1266 options.revisions[0],
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001267 ', '.join(s.name for s in client.dependencies[1:])),
1268 file=sys.stderr)
maruel@chromium.org15804092010-09-02 17:07:37 +00001269 return client
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001270
nsylvain@google.comefc80932011-05-31 21:27:56 +00001271 def SetDefaultConfig(self, solution_name, deps_file, solution_url,
agablea98a6cd2016-11-15 14:30:10 -08001272 managed=True, cache_dir=None):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001273 self.SetConfig(self.DEFAULT_CLIENT_FILE_TEXT % {
1274 'solution_name': solution_name,
1275 'solution_url': solution_url,
nsylvain@google.comefc80932011-05-31 21:27:56 +00001276 'deps_file': deps_file,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001277 'managed': managed,
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001278 'cache_dir': cache_dir,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001279 })
1280
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001281 def _SaveEntries(self):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001282 """Creates a .gclient_entries file to record the list of unique checkouts.
1283
1284 The .gclient_entries file lives in the same directory as .gclient.
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001285 """
1286 # Sometimes pprint.pformat will use {', sometimes it'll use { ' ... It
1287 # makes testing a bit too fun.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001288 result = 'entries = {\n'
maruel@chromium.org68988972011-09-20 14:11:42 +00001289 for entry in self.root.subtree(False):
agabled437d762016-10-17 09:35:11 -07001290 result += ' %s: %s,\n' % (pprint.pformat(entry.name),
1291 pprint.pformat(entry.parsed_url))
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001292 result += '}\n'
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001293 file_path = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org1333cb32011-10-04 23:40:16 +00001294 logging.debug(result)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001295 gclient_utils.FileWrite(file_path, result)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001296
1297 def _ReadEntries(self):
1298 """Read the .gclient_entries file for the given client.
1299
1300 Returns:
1301 A sequence of solution names, which will be empty if there is the
1302 entries file hasn't been created yet.
1303 """
1304 scope = {}
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001305 filename = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001306 if not os.path.exists(filename):
maruel@chromium.org73e21142010-07-05 13:32:01 +00001307 return {}
maruel@chromium.org5990f9d2010-07-07 18:02:58 +00001308 try:
1309 exec(gclient_utils.FileRead(filename), scope)
vapier@chromium.orga81a56e2015-11-11 07:56:13 +00001310 except SyntaxError as e:
maruel@chromium.org5990f9d2010-07-07 18:02:58 +00001311 gclient_utils.SyntaxErrorToError(filename, e)
Aaron Gable3721ee92017-04-03 14:53:14 -07001312 return scope.get('entries', {})
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001313
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001314 def _EnforceRevisions(self):
maruel@chromium.org918a9ae2010-05-28 15:50:30 +00001315 """Checks for revision overrides."""
1316 revision_overrides = {}
smutae7ea312016-07-18 11:59:41 -07001317 if self._options.head:
1318 return revision_overrides
joi@chromium.org792ea882010-11-10 02:37:27 +00001319 if not self._options.revisions:
1320 for s in self.dependencies:
smutae7ea312016-07-18 11:59:41 -07001321 if not s.managed:
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001322 self._options.revisions.append('%s@unmanaged' % s.name)
maruel@chromium.org307d1792010-05-31 20:03:13 +00001323 if not self._options.revisions:
1324 return revision_overrides
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001325 solutions_names = [s.name for s in self.dependencies]
smutae7ea312016-07-18 11:59:41 -07001326 index = 0
1327 for revision in self._options.revisions:
1328 if not '@' in revision:
maruel@chromium.org307d1792010-05-31 20:03:13 +00001329 # Support for --revision 123
smutae7ea312016-07-18 11:59:41 -07001330 revision = '%s@%s' % (solutions_names[index], revision)
1331 name, rev = revision.split('@', 1)
szager@chromium.org4ad264b2014-05-20 04:43:47 +00001332 revision_overrides[name] = rev
smutae7ea312016-07-18 11:59:41 -07001333 index += 1
maruel@chromium.org918a9ae2010-05-28 15:50:30 +00001334 return revision_overrides
1335
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001336 def RunOnDeps(self, command, args, ignore_requirements=False, progress=True):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001337 """Runs a command on each dependency in a client and its dependencies.
1338
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001339 Args:
1340 command: The command to use (e.g., 'status' or 'diff')
1341 args: list of str - extra arguments to add to the command line.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001342 """
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001343 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +00001344 raise gclient_utils.Error('No solution specified')
borenet@google.com0a427372014-04-02 19:12:13 +00001345
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001346 revision_overrides = {}
1347 # It's unnecessary to check for revision overrides for 'recurse'.
1348 # Save a few seconds by not calling _EnforceRevisions() in that case.
agable@chromium.org0242eb42015-06-09 00:45:31 +00001349 if command not in ('diff', 'recurse', 'runhooks', 'status', 'revert'):
szager@chromium.org5273b8a2014-08-21 15:10:10 +00001350 self._CheckConfig()
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001351 revision_overrides = self._EnforceRevisions()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001352 pm = None
maruel@chromium.org5b3f8852010-09-10 16:49:54 +00001353 # Disable progress for non-tty stdout.
iannucci@chromium.org596cd5c2016-04-04 21:34:39 +00001354 if (setup_color.IS_TTY and not self._options.verbose and progress):
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001355 if command in ('update', 'revert'):
1356 pm = Progress('Syncing projects', 1)
maruel@chromium.orgcd8d8e12012-10-03 17:16:25 +00001357 elif command == 'recurse':
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001358 pm = Progress(' '.join(args), 1)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001359 work_queue = gclient_utils.ExecutionQueue(
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001360 self._options.jobs, pm, ignore_requirements=ignore_requirements,
1361 verbose=self._options.verbose)
maruel@chromium.org049bced2010-08-12 13:37:20 +00001362 for s in self.dependencies:
1363 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +00001364 work_queue.flush(revision_overrides, command, args, options=self._options)
szager@chromium.org4ad264b2014-05-20 04:43:47 +00001365 if revision_overrides:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001366 print('Please fix your script, having invalid --revision flags will soon '
1367 'considered an error.', file=sys.stderr)
piman@chromium.org6f363722010-04-27 00:41:09 +00001368
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001369 # Once all the dependencies have been processed, it's now safe to run the
1370 # hooks.
1371 if not self._options.nohooks:
1372 self.RunHooksRecursively(self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001373
1374 if command == 'update':
ajwong@chromium.orgcdcee802009-06-23 15:30:42 +00001375 # Notify the user if there is an orphaned entry in their working copy.
1376 # Only delete the directory if there are no changes in it, and
1377 # delete_unversioned_trees is set to true.
maruel@chromium.org68988972011-09-20 14:11:42 +00001378 entries = [i.name for i in self.root.subtree(False) if i.url]
xusydoc@chromium.org885a9602013-05-31 09:54:40 +00001379 full_entries = [os.path.join(self.root_dir, e.replace('/', os.path.sep))
1380 for e in entries]
1381
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001382 for entry, prev_url in self._ReadEntries().iteritems():
maruel@chromium.org04dd7de2010-10-14 13:25:49 +00001383 if not prev_url:
1384 # entry must have been overridden via .gclient custom_deps
1385 continue
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001386 # Fix path separator on Windows.
1387 entry_fixed = entry.replace('/', os.path.sep)
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001388 e_dir = os.path.join(self.root_dir, entry_fixed)
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001389 # Use entry and not entry_fixed there.
jochen@chromium.orga78e5532013-03-11 13:33:03 +00001390 if (entry not in entries and
1391 (not any(path.startswith(entry + '/') for path in entries)) and
jochen@chromium.orgcc475722013-03-11 13:07:40 +00001392 os.path.exists(e_dir)):
primiano@chromium.org1c127382015-02-17 11:15:40 +00001393 # The entry has been removed from DEPS.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001394 scm = gclient_scm.CreateSCM(
1395 prev_url, self.root_dir, entry_fixed, self.outbuf)
xusydoc@chromium.org885a9602013-05-31 09:54:40 +00001396
1397 # Check to see if this directory is now part of a higher-up checkout.
borenet@google.com359bb642014-05-13 17:28:19 +00001398 scm_root = None
agabled437d762016-10-17 09:35:11 -07001399 try:
1400 scm_root = gclient_scm.scm.GIT.GetCheckoutRoot(scm.checkout_path)
1401 except subprocess2.CalledProcessError:
1402 pass
1403 if not scm_root:
borenet@google.com359bb642014-05-13 17:28:19 +00001404 logging.warning('Could not find checkout root for %s. Unable to '
1405 'determine whether it is part of a higher-level '
1406 'checkout, so not removing.' % entry)
1407 continue
primiano@chromium.org1c127382015-02-17 11:15:40 +00001408
1409 # This is to handle the case of third_party/WebKit migrating from
1410 # being a DEPS entry to being part of the main project.
1411 # If the subproject is a Git project, we need to remove its .git
1412 # folder. Otherwise git operations on that folder will have different
1413 # effects depending on the current working directory.
agabled437d762016-10-17 09:35:11 -07001414 if os.path.abspath(scm_root) == os.path.abspath(e_dir):
primiano@chromium.org1c127382015-02-17 11:15:40 +00001415 e_par_dir = os.path.join(e_dir, os.pardir)
agabled437d762016-10-17 09:35:11 -07001416 if gclient_scm.scm.GIT.IsInsideWorkTree(e_par_dir):
1417 par_scm_root = gclient_scm.scm.GIT.GetCheckoutRoot(e_par_dir)
primiano@chromium.org1c127382015-02-17 11:15:40 +00001418 # rel_e_dir : relative path of entry w.r.t. its parent repo.
1419 rel_e_dir = os.path.relpath(e_dir, par_scm_root)
agabled437d762016-10-17 09:35:11 -07001420 if gclient_scm.scm.GIT.IsDirectoryVersioned(
1421 par_scm_root, rel_e_dir):
primiano@chromium.org1c127382015-02-17 11:15:40 +00001422 save_dir = scm.GetGitBackupDirPath()
1423 # Remove any eventual stale backup dir for the same project.
1424 if os.path.exists(save_dir):
1425 gclient_utils.rmtree(save_dir)
1426 os.rename(os.path.join(e_dir, '.git'), save_dir)
1427 # When switching between the two states (entry/ is a subproject
1428 # -> entry/ is part of the outer project), it is very likely
1429 # that some files are changed in the checkout, unless we are
1430 # jumping *exactly* across the commit which changed just DEPS.
1431 # In such case we want to cleanup any eventual stale files
1432 # (coming from the old subproject) in order to end up with a
1433 # clean checkout.
agabled437d762016-10-17 09:35:11 -07001434 gclient_scm.scm.GIT.CleanupDir(par_scm_root, rel_e_dir)
primiano@chromium.org1c127382015-02-17 11:15:40 +00001435 assert not os.path.exists(os.path.join(e_dir, '.git'))
1436 print(('\nWARNING: \'%s\' has been moved from DEPS to a higher '
1437 'level checkout. The git folder containing all the local'
1438 ' branches has been saved to %s.\n'
1439 'If you don\'t care about its state you can safely '
1440 'remove that folder to free up space.') %
1441 (entry, save_dir))
1442 continue
1443
borenet@google.com359bb642014-05-13 17:28:19 +00001444 if scm_root in full_entries:
primiano@chromium.org1c127382015-02-17 11:15:40 +00001445 logging.info('%s is part of a higher level checkout, not removing',
1446 scm.GetCheckoutRoot())
xusydoc@chromium.org885a9602013-05-31 09:54:40 +00001447 continue
1448
1449 file_list = []
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001450 scm.status(self._options, [], file_list)
1451 modified_files = file_list != []
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001452 if (not self._options.delete_unversioned_trees or
1453 (modified_files and not self._options.force)):
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001454 # There are modified files in this entry. Keep warning until
1455 # removed.
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001456 print(('\nWARNING: \'%s\' is no longer part of this client. '
1457 'It is recommended that you manually remove it.\n') %
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001458 entry_fixed)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001459 else:
1460 # Delete the entry
maruel@chromium.org73e21142010-07-05 13:32:01 +00001461 print('\n________ deleting \'%s\' in \'%s\'' % (
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001462 entry_fixed, self.root_dir))
digit@chromium.orgdc112ac2013-04-24 13:00:19 +00001463 gclient_utils.rmtree(e_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001464 # record the current list of entries for next time
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001465 self._SaveEntries()
maruel@chromium.org17cdf762010-05-28 17:30:52 +00001466 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001467
1468 def PrintRevInfo(self):
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001469 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +00001470 raise gclient_utils.Error('No solution specified')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001471 # Load all the settings.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001472 work_queue = gclient_utils.ExecutionQueue(
1473 self._options.jobs, None, False, verbose=self._options.verbose)
maruel@chromium.org049bced2010-08-12 13:37:20 +00001474 for s in self.dependencies:
1475 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +00001476 work_queue.flush({}, None, [], options=self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001477
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001478 def GetURLAndRev(dep):
1479 """Returns the revision-qualified SCM url for a Dependency."""
1480 if dep.parsed_url is None:
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001481 return None
agabled437d762016-10-17 09:35:11 -07001482 url, _ = gclient_utils.SplitUrlRevision(dep.parsed_url)
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001483 scm = gclient_scm.CreateSCM(
agabled437d762016-10-17 09:35:11 -07001484 dep.parsed_url, self.root_dir, dep.name, self.outbuf)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001485 if not os.path.isdir(scm.checkout_path):
1486 return None
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001487 return '%s@%s' % (url, scm.revinfo(self._options, [], None))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001488
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001489 if self._options.snapshot:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001490 new_gclient = ''
1491 # First level at .gclient
1492 for d in self.dependencies:
1493 entries = {}
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001494 def GrabDeps(dep):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001495 """Recursively grab dependencies."""
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001496 for d in dep.dependencies:
1497 entries[d.name] = GetURLAndRev(d)
1498 GrabDeps(d)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001499 GrabDeps(d)
1500 custom_deps = []
1501 for k in sorted(entries.keys()):
1502 if entries[k]:
1503 # Quotes aren't escaped...
1504 custom_deps.append(' \"%s\": \'%s\',\n' % (k, entries[k]))
1505 else:
1506 custom_deps.append(' \"%s\": None,\n' % k)
1507 new_gclient += self.DEFAULT_SNAPSHOT_SOLUTION_TEXT % {
1508 'solution_name': d.name,
1509 'solution_url': d.url,
nsylvain@google.comefc80932011-05-31 21:27:56 +00001510 'deps_file': d.deps_file,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001511 'managed': d.managed,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001512 'solution_deps': ''.join(custom_deps),
1513 }
1514 # Print the snapshot configuration file
1515 print(self.DEFAULT_SNAPSHOT_FILE_TEXT % {'solution_list': new_gclient})
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +00001516 else:
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001517 entries = {}
maruel@chromium.org68988972011-09-20 14:11:42 +00001518 for d in self.root.subtree(False):
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001519 if self._options.actual:
1520 entries[d.name] = GetURLAndRev(d)
1521 else:
1522 entries[d.name] = d.parsed_url
1523 keys = sorted(entries.keys())
1524 for x in keys:
maruel@chromium.orgce464892010-08-12 17:12:18 +00001525 print('%s: %s' % (x, entries[x]))
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +00001526 logging.info(str(self))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001527
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001528 def ParseDepsFile(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001529 """No DEPS to parse for a .gclient file."""
maruel@chromium.org049bced2010-08-12 13:37:20 +00001530 raise gclient_utils.Error('Internal error')
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001531
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00001532 def PrintLocationAndContents(self):
1533 # Print out the .gclient file. This is longer than if we just printed the
1534 # client dict, but more legible, and it might contain helpful comments.
1535 print('Loaded .gclient config in %s:\n%s' % (
1536 self.root_dir, self.config_content))
1537
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001538 @property
maruel@chromium.org75a59272010-06-11 22:34:03 +00001539 def root_dir(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001540 """Root directory of gclient checkout."""
maruel@chromium.org75a59272010-06-11 22:34:03 +00001541 return self._root_dir
1542
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001543 @property
maruel@chromium.org271375b2010-06-23 19:17:38 +00001544 def enforced_os(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001545 """What deps_os entries that are to be parsed."""
maruel@chromium.org271375b2010-06-23 19:17:38 +00001546 return self._enforced_os
1547
maruel@chromium.org68988972011-09-20 14:11:42 +00001548 @property
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001549 def recursion_limit(self):
1550 """How recursive can each dependencies in DEPS file can load DEPS file."""
1551 return self._recursion_limit
1552
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +00001553 @property
cmp@chromium.orgc401ad12014-07-02 23:20:08 +00001554 def try_recursedeps(self):
1555 """Whether to attempt using recursedeps-style recursion processing."""
cmp@chromium.orge84ac912014-06-30 23:14:35 +00001556 return True
1557
1558 @property
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +00001559 def target_os(self):
1560 return self._enforced_os
1561
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001562
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001563#### gclient commands.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001564
1565
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001566@subcommand.usage('[command] [args ...]')
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001567def CMDrecurse(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001568 """Operates [command args ...] on all the dependencies.
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001569
1570 Runs a shell command on all entries.
qyearsley12fa6ff2016-08-24 09:18:40 -07001571 Sets GCLIENT_DEP_PATH environment variable as the dep's relative location to
ilevy@chromium.org37116242012-11-28 01:32:48 +00001572 root directory of the checkout.
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001573 """
1574 # Stop parsing at the first non-arg so that these go through to the command
1575 parser.disable_interspersed_args()
1576 parser.add_option('-s', '--scm', action='append', default=[],
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001577 help='Choose scm types to operate upon.')
maruel@chromium.org288054d2012-03-05 00:43:07 +00001578 parser.add_option('-i', '--ignore', action='store_true',
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001579 help='Ignore non-zero return codes from subcommands.')
1580 parser.add_option('--prepend-dir', action='store_true',
1581 help='Prepend relative dir for use with git <cmd> --null.')
1582 parser.add_option('--no-progress', action='store_true',
1583 help='Disable progress bar that shows sub-command updates')
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001584 options, args = parser.parse_args(args)
maruel@chromium.org45e9f2d2010-10-18 13:33:46 +00001585 if not args:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001586 print('Need to supply a command!', file=sys.stderr)
maruel@chromium.org45e9f2d2010-10-18 13:33:46 +00001587 return 1
maruel@chromium.org78cba522010-10-18 13:32:05 +00001588 root_and_entries = gclient_utils.GetGClientRootAndEntries()
1589 if not root_and_entries:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001590 print(
maruel@chromium.org78cba522010-10-18 13:32:05 +00001591 'You need to run gclient sync at least once to use \'recurse\'.\n'
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001592 'This is because .gclient_entries needs to exist and be up to date.',
1593 file=sys.stderr)
maruel@chromium.org78cba522010-10-18 13:32:05 +00001594 return 1
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001595
1596 # Normalize options.scm to a set()
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001597 scm_set = set()
1598 for scm in options.scm:
1599 scm_set.update(scm.split(','))
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001600 options.scm = scm_set
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001601
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001602 options.nohooks = True
1603 client = GClient.LoadCurrentConfig(options)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001604 return client.RunOnDeps('recurse', args, ignore_requirements=True,
1605 progress=not options.no_progress)
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001606
1607
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001608@subcommand.usage('[args ...]')
davidbarr@chromium.org12f944e2012-03-01 02:18:31 +00001609def CMDfetch(parser, args):
1610 """Fetches upstream commits for all modules.
1611
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001612 Completely git-specific. Simply runs 'git fetch [args ...]' for each module.
1613 """
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001614 (options, args) = parser.parse_args(args)
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00001615 return CMDrecurse(OptionParser(), [
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001616 '--jobs=%d' % options.jobs, '--scm=git', 'git', 'fetch'] + args)
1617
1618
1619def CMDgrep(parser, args):
1620 """Greps through git repos managed by gclient.
1621
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001622 Runs 'git grep [args...]' for each module.
1623 """
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001624 # We can't use optparse because it will try to parse arguments sent
1625 # to git grep and throw an error. :-(
1626 if not args or re.match('(-h|--help)$', args[0]):
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001627 print(
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001628 'Usage: gclient grep [-j <N>] git-grep-args...\n\n'
1629 'Example: "gclient grep -j10 -A2 RefCountedBase" runs\n"git grep '
1630 '-A2 RefCountedBase" on each of gclient\'s git\nrepos with up to '
1631 '10 jobs.\n\nBonus: page output by appending "|& less -FRSX" to the'
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001632 ' end of your query.',
1633 file=sys.stderr)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001634 return 1
1635
1636 jobs_arg = ['--jobs=1']
1637 if re.match(r'(-j|--jobs=)\d+$', args[0]):
1638 jobs_arg, args = args[:1], args[1:]
1639 elif re.match(r'(-j|--jobs)$', args[0]):
1640 jobs_arg, args = args[:2], args[2:]
1641
1642 return CMDrecurse(
1643 parser,
1644 jobs_arg + ['--ignore', '--prepend-dir', '--no-progress', '--scm=git',
1645 'git', 'grep', '--null', '--color=Always'] + args)
davidbarr@chromium.org12f944e2012-03-01 02:18:31 +00001646
1647
stip@chromium.orga735da22015-04-29 23:18:20 +00001648def CMDroot(parser, args):
1649 """Outputs the solution root (or current dir if there isn't one)."""
1650 (options, args) = parser.parse_args(args)
1651 client = GClient.LoadCurrentConfig(options)
1652 if client:
1653 print(os.path.abspath(client.root_dir))
1654 else:
1655 print(os.path.abspath('.'))
1656
1657
agablea98a6cd2016-11-15 14:30:10 -08001658@subcommand.usage('[url]')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001659def CMDconfig(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001660 """Creates a .gclient file in the current directory.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001661
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001662 This specifies the configuration for further commands. After update/sync,
1663 top-level DEPS files in each module are read to determine dependent
1664 modules to operate on as well. If optional [url] parameter is
1665 provided, then configuration is read from a specified Subversion server
1666 URL.
1667 """
szager@chromium.orge2e03202012-07-31 18:05:16 +00001668 # We do a little dance with the --gclientfile option. 'gclient config' is the
1669 # only command where it's acceptable to have both '--gclientfile' and '--spec'
1670 # arguments. So, we temporarily stash any --gclientfile parameter into
1671 # options.output_config_file until after the (gclientfile xor spec) error
1672 # check.
1673 parser.remove_option('--gclientfile')
1674 parser.add_option('--gclientfile', dest='output_config_file',
1675 help='Specify an alternate .gclient file')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001676 parser.add_option('--name',
1677 help='overrides the default name for the solution')
nsylvain@google.comefc80932011-05-31 21:27:56 +00001678 parser.add_option('--deps-file', default='DEPS',
1679 help='overrides the default name for the DEPS file for the'
1680 'main solutions and all sub-dependencies')
smutae7ea312016-07-18 11:59:41 -07001681 parser.add_option('--unmanaged', action='store_true', default=False,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001682 help='overrides the default behavior to make it possible '
smutae7ea312016-07-18 11:59:41 -07001683 'to have the main solution untouched by gclient '
1684 '(gclient will check out unmanaged dependencies but '
1685 'will never sync them)')
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001686 parser.add_option('--cache-dir',
1687 help='(git only) Cache all git repos into this dir and do '
1688 'shared clones from the cache, instead of cloning '
1689 'directly from the remote. (experimental)')
szager@chromium.orge2e03202012-07-31 18:05:16 +00001690 parser.set_defaults(config_filename=None)
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001691 (options, args) = parser.parse_args(args)
szager@chromium.orge2e03202012-07-31 18:05:16 +00001692 if options.output_config_file:
1693 setattr(options, 'config_filename', getattr(options, 'output_config_file'))
maruel@chromium.org5fc2a332010-05-26 19:37:15 +00001694 if ((options.spec and args) or len(args) > 2 or
1695 (not options.spec and not args)):
1696 parser.error('Inconsistent arguments. Use either --spec or one or 2 args')
1697
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001698 client = GClient('.', options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001699 if options.spec:
1700 client.SetConfig(options.spec)
1701 else:
maruel@chromium.org1ab7ffc2009-06-03 17:21:37 +00001702 base_url = args[0].rstrip('/')
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001703 if not options.name:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001704 name = base_url.split('/')[-1]
nsylvain@google.com12649ef2011-06-01 17:11:20 +00001705 if name.endswith('.git'):
1706 name = name[:-4]
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001707 else:
1708 # specify an alternate relpath for the given URL.
1709 name = options.name
agable@chromium.orgf2214672015-10-27 21:02:48 +00001710 if not os.path.abspath(os.path.join(os.getcwd(), name)).startswith(
1711 os.getcwd()):
1712 parser.error('Do not pass a relative path for --name.')
1713 if any(x in ('..', '.', '/', '\\') for x in name.split(os.sep)):
1714 parser.error('Do not include relative path components in --name.')
1715
nsylvain@google.comefc80932011-05-31 21:27:56 +00001716 deps_file = options.deps_file
agablea98a6cd2016-11-15 14:30:10 -08001717 client.SetDefaultConfig(name, deps_file, base_url,
smutae7ea312016-07-18 11:59:41 -07001718 managed=not options.unmanaged,
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001719 cache_dir=options.cache_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001720 client.SaveConfig()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001721 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001722
1723
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001724@subcommand.epilog("""Example:
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001725 gclient pack > patch.txt
1726 generate simple patch for configured client and dependences
1727""")
1728def CMDpack(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001729 """Generates a patch which can be applied at the root of the tree.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001730
agabled437d762016-10-17 09:35:11 -07001731 Internally, runs 'git diff' on each checked out module and
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001732 dependencies, and performs minimal postprocessing of the output. The
1733 resulting patch is printed to stdout and can be applied to a freshly
1734 checked out tree via 'patch -p0 < patchfile'.
1735 """
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001736 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1737 help='override deps for the specified (comma-separated) '
1738 'platform(s); \'all\' will process all deps_os '
1739 'references')
haitao.feng@intel.com306080c2012-05-04 13:11:29 +00001740 parser.remove_option('--jobs')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001741 (options, args) = parser.parse_args(args)
iannucci@chromium.org50395ea2013-04-04 04:47:42 +00001742 # Force jobs to 1 so the stdout is not annotated with the thread ids
haitao.feng@intel.com306080c2012-05-04 13:11:29 +00001743 options.jobs = 1
kbr@google.comab318592009-09-04 00:54:55 +00001744 client = GClient.LoadCurrentConfig(options)
1745 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001746 raise gclient_utils.Error('client not configured; see \'gclient config\'')
kbr@google.comab318592009-09-04 00:54:55 +00001747 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00001748 client.PrintLocationAndContents()
kbr@google.comab318592009-09-04 00:54:55 +00001749 return client.RunOnDeps('pack', args)
1750
1751
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001752def CMDstatus(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001753 """Shows modification status for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001754 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1755 help='override deps for the specified (comma-separated) '
1756 'platform(s); \'all\' will process all deps_os '
1757 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001758 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001759 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001760 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001761 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001762 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00001763 client.PrintLocationAndContents()
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001764 return client.RunOnDeps('status', args)
1765
1766
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001767@subcommand.epilog("""Examples:
maruel@chromium.org79692d62010-05-14 18:57:13 +00001768 gclient sync
1769 update files from SCM according to current configuration,
1770 *for modules which have changed since last update or sync*
1771 gclient sync --force
1772 update files from SCM according to current configuration, for
1773 all modules (useful for recovering files deleted from local copy)
1774 gclient sync --revision src@31000
1775 update src directory to r31000
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00001776
1777JSON output format:
1778If the --output-json option is specified, the following document structure will
1779be emitted to the provided file. 'null' entries may occur for subprojects which
1780are present in the gclient solution, but were not processed (due to custom_deps,
1781os_deps, etc.)
1782
1783{
1784 "solutions" : {
1785 "<name>": { # <name> is the posix-normalized path to the solution.
agabled437d762016-10-17 09:35:11 -07001786 "revision": [<git id hex string>|null],
1787 "scm": ["git"|null],
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00001788 }
1789 }
1790}
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001791""")
1792def CMDsync(parser, args):
1793 """Checkout/update all modules."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001794 parser.add_option('-f', '--force', action='store_true',
1795 help='force update even for unchanged modules')
1796 parser.add_option('-n', '--nohooks', action='store_true',
1797 help='don\'t run hooks after the update is complete')
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001798 parser.add_option('-p', '--noprehooks', action='store_true',
1799 help='don\'t run pre-DEPS hooks', default=False)
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001800 parser.add_option('-r', '--revision', action='append',
1801 dest='revisions', metavar='REV', default=[],
1802 help='Enforces revision/hash for the solutions with the '
1803 'format src@rev. The src@ part is optional and can be '
1804 'skipped. -r can be used multiple times when .gclient '
1805 'has multiple solutions configured and will work even '
agablea98a6cd2016-11-15 14:30:10 -08001806 'if the src@ part is skipped.')
maruel@chromium.org794207e2013-03-08 15:29:43 +00001807 parser.add_option('--with_branch_heads', action='store_true',
1808 help='Clone git "branch_heads" refspecs in addition to '
1809 'the default refspecs. This adds about 1/2GB to a '
1810 'full checkout. (git only)')
szager@chromium.org8d3348f2014-08-19 22:49:16 +00001811 parser.add_option('--with_tags', action='store_true',
1812 help='Clone git tags in addition to the default refspecs.')
agable2697cd12016-06-28 10:23:53 -07001813 parser.add_option('-H', '--head', action='store_true',
agablea98a6cd2016-11-15 14:30:10 -08001814 help='DEPRECATED: only made sense with safesync urls.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001815 parser.add_option('-D', '--delete_unversioned_trees', action='store_true',
steveblock@chromium.org98e69452012-02-16 16:36:43 +00001816 help='Deletes from the working copy any dependencies that '
1817 'have been removed since the last sync, as long as '
1818 'there are no local modifications. When used with '
1819 '--force, such dependencies are removed even if they '
1820 'have local modifications. When used with --reset, '
1821 'all untracked directories are removed from the '
tnagel@chromium.orga2aaa632014-02-28 21:47:27 +00001822 'working copy, excluding those which are explicitly '
steveblock@chromium.org98e69452012-02-16 16:36:43 +00001823 'ignored in the repository.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001824 parser.add_option('-R', '--reset', action='store_true',
1825 help='resets any local changes before updating (git only)')
bauerb@chromium.org2aad1b22011-07-22 12:00:41 +00001826 parser.add_option('-M', '--merge', action='store_true',
1827 help='merge upstream changes instead of trying to '
1828 'fast-forward or rebase')
dnj@chromium.org5b23e872015-02-20 21:25:57 +00001829 parser.add_option('-A', '--auto_rebase', action='store_true',
1830 help='Automatically rebase repositories against local '
1831 'checkout during update (git only).')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001832 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1833 help='override deps for the specified (comma-separated) '
1834 'platform(s); \'all\' will process all deps_os '
1835 'references')
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +00001836 parser.add_option('--upstream', action='store_true',
1837 help='Make repo state match upstream branch.')
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00001838 parser.add_option('--output-json',
1839 help='Output a json document to this path containing '
1840 'summary information about the sync.')
primiano@chromium.org5439ea52014-08-06 17:18:18 +00001841 parser.add_option('--no-history', action='store_true',
1842 help='GIT ONLY - Reduces the size/time of the checkout at '
1843 'the cost of no history. Requires Git 1.9+')
hinoka@chromium.org46b87412014-05-15 00:42:05 +00001844 parser.add_option('--shallow', action='store_true',
1845 help='GIT ONLY - Do a shallow clone into the cache dir. '
1846 'Requires Git 1.9+')
e.hakkinen@samsung.come8bc1aa2015-04-08 08:00:37 +00001847 parser.add_option('--no_bootstrap', '--no-bootstrap',
1848 action='store_true',
1849 help='Don\'t bootstrap from Google Storage.')
hinoka@chromium.org8a10f6d2014-06-23 18:38:57 +00001850 parser.add_option('--ignore_locks', action='store_true',
1851 help='GIT ONLY - Ignore cache locks.')
iannucci@chromium.org30a07982016-04-07 21:35:19 +00001852 parser.add_option('--break_repo_locks', action='store_true',
1853 help='GIT ONLY - Forcibly remove repo locks (e.g. '
1854 'index.lock). This should only be used if you know for '
1855 'certain that this invocation of gclient is the only '
1856 'thing operating on the git repos (e.g. on a bot).')
nodir@chromium.org5b48e482016-03-18 20:27:54 +00001857 parser.add_option('--lock_timeout', type='int', default=5000,
szager@chromium.orgdbb6f822016-02-02 22:59:30 +00001858 help='GIT ONLY - Deadline (in seconds) to wait for git '
nodir@chromium.org5b48e482016-03-18 20:27:54 +00001859 'cache lock to become available. Default is %default.')
agabled437d762016-10-17 09:35:11 -07001860 # TODO(agable): Remove these when the oldest CrOS release milestone is M56.
1861 parser.add_option('-t', '--transitive', action='store_true',
1862 help='DEPRECATED: This is a no-op.')
sdefresne69b1be12016-10-18 05:48:02 -07001863 parser.add_option('-m', '--manually_grab_svn_rev', action='store_true',
agabled437d762016-10-17 09:35:11 -07001864 help='DEPRECATED: This is a no-op.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001865 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001866 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001867
1868 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001869 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001870
smutae7ea312016-07-18 11:59:41 -07001871 if options.revisions and options.head:
1872 # TODO(maruel): Make it a parser.error if it doesn't break any builder.
1873 print('Warning: you cannot use both --head and --revision')
1874
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001875 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00001876 client.PrintLocationAndContents()
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00001877 ret = client.RunOnDeps('update', args)
1878 if options.output_json:
1879 slns = {}
1880 for d in client.subtree(True):
1881 normed = d.name.replace('\\', '/').rstrip('/') + '/'
1882 slns[normed] = {
1883 'revision': d.got_revision,
1884 'scm': d.used_scm.name if d.used_scm else None,
hinoka@chromium.org17db9052014-05-10 01:11:29 +00001885 'url': str(d.url) if d.url else None,
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00001886 }
1887 with open(options.output_json, 'wb') as f:
1888 json.dump({'solutions': slns}, f)
1889 return ret
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001890
1891
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001892CMDupdate = CMDsync
1893
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001894
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001895def CMDdiff(parser, args):
1896 """Displays local diff for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001897 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1898 help='override deps for the specified (comma-separated) '
1899 'platform(s); \'all\' will process all deps_os '
1900 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001901 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001902 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001903 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001904 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001905 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00001906 client.PrintLocationAndContents()
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001907 return client.RunOnDeps('diff', args)
1908
1909
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001910def CMDrevert(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001911 """Reverts all modifications in every dependencies.
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001912
1913 That's the nuclear option to get back to a 'clean' state. It removes anything
agabled437d762016-10-17 09:35:11 -07001914 that shows up in git status."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001915 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1916 help='override deps for the specified (comma-separated) '
1917 'platform(s); \'all\' will process all deps_os '
1918 'references')
1919 parser.add_option('-n', '--nohooks', action='store_true',
1920 help='don\'t run hooks after the revert is complete')
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001921 parser.add_option('-p', '--noprehooks', action='store_true',
1922 help='don\'t run pre-DEPS hooks', default=False)
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +00001923 parser.add_option('--upstream', action='store_true',
1924 help='Make repo state match upstream branch.')
iannucci@chromium.orgbf525dc2016-04-07 22:00:28 +00001925 parser.add_option('--break_repo_locks', action='store_true',
1926 help='GIT ONLY - Forcibly remove repo locks (e.g. '
1927 'index.lock). This should only be used if you know for '
1928 'certain that this invocation of gclient is the only '
1929 'thing operating on the git repos (e.g. on a bot).')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001930 (options, args) = parser.parse_args(args)
1931 # --force is implied.
1932 options.force = True
steveblock@chromium.org98e69452012-02-16 16:36:43 +00001933 options.reset = False
1934 options.delete_unversioned_trees = False
agablec903d732016-07-26 09:07:24 -07001935 options.merge = False
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001936 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001937 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001938 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001939 return client.RunOnDeps('revert', args)
1940
1941
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001942def CMDrunhooks(parser, args):
1943 """Runs hooks for files that have been modified in the local working copy."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001944 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1945 help='override deps for the specified (comma-separated) '
1946 'platform(s); \'all\' will process all deps_os '
1947 'references')
1948 parser.add_option('-f', '--force', action='store_true', default=True,
1949 help='Deprecated. No effect.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001950 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001951 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001952 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001953 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001954 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00001955 client.PrintLocationAndContents()
maruel@chromium.org5df6a462009-08-28 18:52:26 +00001956 options.force = True
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001957 options.nohooks = False
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001958 return client.RunOnDeps('runhooks', args)
1959
1960
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001961def CMDrevinfo(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001962 """Outputs revision info mapping for the client and its dependencies.
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001963
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001964 This allows the capture of an overall 'revision' for the source tree that
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001965 can be used to reproduce the same tree in the future. It is only useful for
agabled437d762016-10-17 09:35:11 -07001966 'unpinned dependencies', i.e. DEPS/deps references without a git hash.
1967 A git branch name isn't 'pinned' since the actual commit can change.
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001968 """
1969 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1970 help='override deps for the specified (comma-separated) '
1971 'platform(s); \'all\' will process all deps_os '
1972 'references')
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001973 parser.add_option('-a', '--actual', action='store_true',
1974 help='gets the actual checked out revisions instead of the '
1975 'ones specified in the DEPS and .gclient files')
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001976 parser.add_option('-s', '--snapshot', action='store_true',
1977 help='creates a snapshot .gclient file of the current '
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001978 'version of all repositories to reproduce the tree, '
1979 'implies -a')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001980 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001981 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001982 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001983 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001984 client.PrintRevInfo()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001985 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001986
1987
szager@google.comb9a78d32012-03-13 18:46:21 +00001988def CMDhookinfo(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001989 """Outputs the hooks that would be run by `gclient runhooks`."""
szager@google.comb9a78d32012-03-13 18:46:21 +00001990 (options, args) = parser.parse_args(args)
1991 options.force = True
1992 client = GClient.LoadCurrentConfig(options)
1993 if not client:
1994 raise gclient_utils.Error('client not configured; see \'gclient config\'')
1995 client.RunOnDeps(None, [])
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001996 print('; '.join(' '.join(hook) for hook in client.GetHooks(options)))
szager@google.comb9a78d32012-03-13 18:46:21 +00001997 return 0
1998
1999
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00002000def CMDverify(parser, args):
2001 """Verifies the DEPS file deps are only from allowed_hosts."""
2002 (options, args) = parser.parse_args(args)
2003 client = GClient.LoadCurrentConfig(options)
2004 if not client:
2005 raise gclient_utils.Error('client not configured; see \'gclient config\'')
2006 client.RunOnDeps(None, [])
2007 # Look at each first-level dependency of this gclient only.
2008 for dep in client.dependencies:
2009 bad_deps = dep.findDepsFromNotAllowedHosts()
2010 if not bad_deps:
2011 continue
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002012 print("There are deps from not allowed hosts in file %s" % dep.deps_file)
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00002013 for bad_dep in bad_deps:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002014 print("\t%s at %s" % (bad_dep.name, bad_dep.url))
2015 print("allowed_hosts:", ', '.join(dep.allowed_hosts))
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00002016 sys.stdout.flush()
2017 raise gclient_utils.Error(
2018 'dependencies from disallowed hosts; check your DEPS file.')
2019 return 0
2020
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002021class OptionParser(optparse.OptionParser):
szager@chromium.orge2e03202012-07-31 18:05:16 +00002022 gclientfile_default = os.environ.get('GCLIENT_FILE', '.gclient')
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002023
2024 def __init__(self, **kwargs):
2025 optparse.OptionParser.__init__(
2026 self, version='%prog ' + __version__, **kwargs)
2027
2028 # Some arm boards have issues with parallel sync.
2029 if platform.machine().startswith('arm'):
2030 jobs = 1
2031 else:
2032 jobs = max(8, gclient_utils.NumLocalCpus())
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002033
2034 self.add_option(
2035 '-j', '--jobs', default=jobs, type='int',
2036 help='Specify how many SCM commands can run in parallel; defaults to '
tnagel@chromium.orga2aaa632014-02-28 21:47:27 +00002037 '%default on this machine')
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002038 self.add_option(
2039 '-v', '--verbose', action='count', default=0,
2040 help='Produces additional output for diagnostics. Can be used up to '
2041 'three times for more logging info.')
2042 self.add_option(
2043 '--gclientfile', dest='config_filename',
2044 help='Specify an alternate %s file' % self.gclientfile_default)
2045 self.add_option(
2046 '--spec',
2047 help='create a gclient file containing the provided string. Due to '
2048 'Cygwin/Python brokenness, it can\'t contain any newlines.')
2049 self.add_option(
2050 '--no-nag-max', default=False, action='store_true',
scottmg@chromium.orgf547c802013-09-27 17:55:26 +00002051 help='Ignored for backwards compatibility.')
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002052
2053 def parse_args(self, args=None, values=None):
2054 """Integrates standard options processing."""
2055 options, args = optparse.OptionParser.parse_args(self, args, values)
2056 levels = [logging.ERROR, logging.WARNING, logging.INFO, logging.DEBUG]
2057 logging.basicConfig(
2058 level=levels[min(options.verbose, len(levels) - 1)],
maruel@chromium.org0895b752011-08-26 20:40:33 +00002059 format='%(module)s(%(lineno)d) %(funcName)s:%(message)s')
szager@chromium.orge2e03202012-07-31 18:05:16 +00002060 if options.config_filename and options.spec:
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002061 self.error('Cannot specifiy both --gclientfile and --spec')
rdsmith@chromium.orgd9591f02014-02-05 19:28:20 +00002062 if (options.config_filename and
2063 options.config_filename != os.path.basename(options.config_filename)):
2064 self.error('--gclientfile target must be a filename, not a path')
szager@chromium.orge2e03202012-07-31 18:05:16 +00002065 if not options.config_filename:
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002066 options.config_filename = self.gclientfile_default
maruel@chromium.org0895b752011-08-26 20:40:33 +00002067 options.entries_filename = options.config_filename + '_entries'
2068 if options.jobs < 1:
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002069 self.error('--jobs must be 1 or higher')
maruel@chromium.org0895b752011-08-26 20:40:33 +00002070
2071 # These hacks need to die.
2072 if not hasattr(options, 'revisions'):
2073 # GClient.RunOnDeps expects it even if not applicable.
2074 options.revisions = []
smutae7ea312016-07-18 11:59:41 -07002075 if not hasattr(options, 'head'):
2076 options.head = None
maruel@chromium.org0895b752011-08-26 20:40:33 +00002077 if not hasattr(options, 'nohooks'):
2078 options.nohooks = True
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00002079 if not hasattr(options, 'noprehooks'):
2080 options.noprehooks = True
maruel@chromium.org0895b752011-08-26 20:40:33 +00002081 if not hasattr(options, 'deps_os'):
2082 options.deps_os = None
maruel@chromium.org0895b752011-08-26 20:40:33 +00002083 if not hasattr(options, 'force'):
2084 options.force = None
2085 return (options, args)
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002086
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002087
2088def disable_buffering():
2089 # Make stdout auto-flush so buildbot doesn't kill us during lengthy
2090 # operations. Python as a strong tendency to buffer sys.stdout.
2091 sys.stdout = gclient_utils.MakeFileAutoFlush(sys.stdout)
2092 # Make stdout annotated with the thread ids.
2093 sys.stdout = gclient_utils.MakeFileAnnotated(sys.stdout)
maruel@chromium.org0895b752011-08-26 20:40:33 +00002094
2095
sbc@chromium.org013731e2015-02-26 18:28:43 +00002096def main(argv):
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002097 """Doesn't parse the arguments here, just find the right subcommand to
2098 execute."""
maruel@chromium.org82798cb2012-02-23 18:16:12 +00002099 if sys.hexversion < 0x02060000:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002100 print(
maruel@chromium.org82798cb2012-02-23 18:16:12 +00002101 '\nYour python version %s is unsupported, please upgrade.\n' %
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002102 sys.version.split(' ', 1)[0],
2103 file=sys.stderr)
maruel@chromium.org82798cb2012-02-23 18:16:12 +00002104 return 2
bcwhite@chromium.org6683ab42013-02-11 16:13:47 +00002105 if not sys.executable:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002106 print(
2107 '\nPython cannot find the location of it\'s own executable.\n',
2108 file=sys.stderr)
bcwhite@chromium.org6683ab42013-02-11 16:13:47 +00002109 return 2
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002110 fix_encoding.fix_encoding()
2111 disable_buffering()
iannucci@chromium.org596cd5c2016-04-04 21:34:39 +00002112 setup_color.init()
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002113 dispatcher = subcommand.CommandDispatcher(__name__)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00002114 try:
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002115 return dispatcher.execute(OptionParser(), argv)
xusydoc@chromium.org2fd6c3f2013-05-03 21:57:55 +00002116 except KeyboardInterrupt:
2117 gclient_utils.GClientChildren.KillAllRemainingChildren()
2118 raise
vapier@chromium.orga81a56e2015-11-11 07:56:13 +00002119 except (gclient_utils.Error, subprocess2.CalledProcessError) as e:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002120 print('Error: %s' % str(e), file=sys.stderr)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00002121 return 1
borenet@google.com6a9b1682014-03-24 18:35:23 +00002122 finally:
2123 gclient_utils.PrintWarnings()
sbc@chromium.org013731e2015-02-26 18:28:43 +00002124 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002125
2126
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00002127if '__main__' == __name__:
sbc@chromium.org013731e2015-02-26 18:28:43 +00002128 try:
2129 sys.exit(main(sys.argv[1:]))
2130 except KeyboardInterrupt:
2131 sys.stderr.write('interrupted\n')
2132 sys.exit(1)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002133
2134# vim: ts=2:sw=2:tw=80:et: