blob: 0ee95e1f3f536a88736034c595ada16d386012ea [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
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000717 self._pre_deps_hooks = [self.GetHookAction(hook, []) for hook in
718 local_scope.get('pre_deps_hooks', [])]
719
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000720 self.add_dependencies_and_close(deps_to_add, hooks_to_run)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000721 logging.info('ParseDepsFile(%s) done' % self.name)
722
723 def add_dependencies_and_close(self, deps_to_add, hooks):
724 """Adds the dependencies, hooks and mark the parsing as done."""
maruel@chromium.orgb9be0652011-10-14 18:05:40 +0000725 for dep in deps_to_add:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000726 if dep.verify_validity():
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000727 self.add_dependency(dep)
728 self._mark_as_parsed(hooks)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000729
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000730 def findDepsFromNotAllowedHosts(self):
731 """Returns a list of depenecies from not allowed hosts.
732
733 If allowed_hosts is not set, allows all hosts and returns empty list.
734 """
735 if not self._allowed_hosts:
736 return []
737 bad_deps = []
738 for dep in self._dependencies:
szager@chromium.orgbd772dd2014-11-05 18:43:08 +0000739 # Don't enforce this for custom_deps.
740 if dep.name in self._custom_deps:
741 continue
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000742 if isinstance(dep.url, basestring):
743 parsed_url = urlparse.urlparse(dep.url)
744 if parsed_url.netloc and parsed_url.netloc not in self._allowed_hosts:
745 bad_deps.append(dep)
746 return bad_deps
747
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +0000748 # Arguments number differs from overridden method
Quinten Yearsleyb2cc4a92016-12-15 13:53:26 -0800749 # pylint: disable=arguments-differ
maruel@chromium.org3742c842010-09-09 19:27:14 +0000750 def run(self, revision_overrides, command, args, work_queue, options):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000751 """Runs |command| then parse the DEPS file."""
maruel@chromium.org470b5432011-10-11 18:18:19 +0000752 logging.info('Dependency(%s).run()' % self.name)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000753 assert self._file_list == []
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000754 if not self.should_process:
755 return
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000756 # When running runhooks, there's no need to consult the SCM.
757 # All known hooks are expected to run unconditionally regardless of working
758 # copy state, so skip the SCM status check.
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +0000759 run_scm = command not in ('runhooks', 'recurse', None)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000760 parsed_url = self.LateOverride(self.url)
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000761 file_list = [] if not options.nohooks else None
szager@chromium.org3a3608d2014-10-22 21:13:52 +0000762 revision_override = revision_overrides.pop(self.name, None)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000763 if run_scm and parsed_url:
agabled437d762016-10-17 09:35:11 -0700764 # Create a shallow copy to mutate revision.
765 options = copy.copy(options)
766 options.revision = revision_override
767 self._used_revision = options.revision
768 self._used_scm = gclient_scm.CreateSCM(
769 parsed_url, self.root.root_dir, self.name, self.outbuf,
770 out_cb=work_queue.out_cb)
771 self._got_revision = self._used_scm.RunCommand(command, options, args,
772 file_list)
773 if file_list:
774 file_list = [os.path.join(self.name, f.strip()) for f in file_list]
maruel@chromium.org68988972011-09-20 14:11:42 +0000775
776 # TODO(phajdan.jr): We should know exactly when the paths are absolute.
777 # Convert all absolute paths to relative.
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000778 for i in range(len(file_list or [])):
maruel@chromium.org68988972011-09-20 14:11:42 +0000779 # It depends on the command being executed (like runhooks vs sync).
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000780 if not os.path.isabs(file_list[i]):
maruel@chromium.org68988972011-09-20 14:11:42 +0000781 continue
782 prefix = os.path.commonprefix(
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000783 [self.root.root_dir.lower(), file_list[i].lower()])
784 file_list[i] = file_list[i][len(prefix):]
maruel@chromium.org68988972011-09-20 14:11:42 +0000785 # Strip any leading path separators.
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000786 while file_list[i].startswith(('\\', '/')):
787 file_list[i] = file_list[i][1:]
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000788
789 # Always parse the DEPS file.
790 self.ParseDepsFile()
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000791 self._run_is_done(file_list or [], parsed_url)
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000792 if command in ('update', 'revert') and not options.noprehooks:
793 self.RunPreDepsHooks()
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000794
795 if self.recursion_limit:
796 # Parse the dependencies of this dependency.
797 for s in self.dependencies:
798 work_queue.enqueue(s)
799
800 if command == 'recurse':
agabled437d762016-10-17 09:35:11 -0700801 # Skip file only checkout.
802 scm = gclient_scm.GetScmName(parsed_url)
803 if not options.scm or scm in options.scm:
804 cwd = os.path.normpath(os.path.join(self.root.root_dir, self.name))
805 # Pass in the SCM type as an env variable. Make sure we don't put
806 # unicode strings in the environment.
807 env = os.environ.copy()
808 if scm:
809 env['GCLIENT_SCM'] = str(scm)
810 if parsed_url:
811 env['GCLIENT_URL'] = str(parsed_url)
812 env['GCLIENT_DEP_PATH'] = str(self.name)
813 if options.prepend_dir and scm == 'git':
814 print_stdout = False
815 def filter_fn(line):
816 """Git-specific path marshaling. It is optimized for git-grep."""
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000817
agabled437d762016-10-17 09:35:11 -0700818 def mod_path(git_pathspec):
819 match = re.match('^(\\S+?:)?([^\0]+)$', git_pathspec)
820 modified_path = os.path.join(self.name, match.group(2))
821 branch = match.group(1) or ''
822 return '%s%s' % (branch, modified_path)
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000823
agabled437d762016-10-17 09:35:11 -0700824 match = re.match('^Binary file ([^\0]+) matches$', line)
825 if match:
826 print('Binary file %s matches\n' % mod_path(match.group(1)))
827 return
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000828
agabled437d762016-10-17 09:35:11 -0700829 items = line.split('\0')
830 if len(items) == 2 and items[1]:
831 print('%s : %s' % (mod_path(items[0]), items[1]))
832 elif len(items) >= 2:
833 # Multiple null bytes or a single trailing null byte indicate
834 # git is likely displaying filenames only (such as with -l)
835 print('\n'.join(mod_path(path) for path in items if path))
836 else:
837 print(line)
838 else:
839 print_stdout = True
840 filter_fn = None
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000841
agabled437d762016-10-17 09:35:11 -0700842 if parsed_url is None:
843 print('Skipped omitted dependency %s' % cwd, file=sys.stderr)
844 elif os.path.isdir(cwd):
845 try:
846 gclient_utils.CheckCallAndFilter(
847 args, cwd=cwd, env=env, print_stdout=print_stdout,
848 filter_fn=filter_fn,
849 )
850 except subprocess2.CalledProcessError:
851 if not options.ignore:
852 raise
853 else:
854 print('Skipped missing %s' % cwd, file=sys.stderr)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000855
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000856
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000857 @gclient_utils.lockedmethod
858 def _run_is_done(self, file_list, parsed_url):
859 # Both these are kept for hooks that are run as a separate tree traversal.
860 self._file_list = file_list
861 self._parsed_url = parsed_url
862 self._processed = True
863
szager@google.comb9a78d32012-03-13 18:46:21 +0000864 @staticmethod
865 def GetHookAction(hook_dict, matching_file_list):
866 """Turns a parsed 'hook' dict into an executable command."""
867 logging.debug(hook_dict)
868 logging.debug(matching_file_list)
869 command = hook_dict['action'][:]
870 if command[0] == 'python':
871 # If the hook specified "python" as the first item, the action is a
872 # Python script. Run it by starting a new copy of the same
873 # interpreter.
874 command[0] = sys.executable
875 if '$matching_files' in command:
876 splice_index = command.index('$matching_files')
877 command[splice_index:splice_index + 1] = matching_file_list
878 return command
879
880 def GetHooks(self, options):
881 """Evaluates all hooks, and return them in a flat list.
882
883 RunOnDeps() must have been called before to load the DEPS.
884 """
885 result = []
maruel@chromium.org68988972011-09-20 14:11:42 +0000886 if not self.should_process or not self.recursion_limit:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000887 # Don't run the hook when it is above recursion_limit.
szager@google.comb9a78d32012-03-13 18:46:21 +0000888 return result
maruel@chromium.orgdc7445d2010-07-09 21:05:29 +0000889 # If "--force" was specified, run all hooks regardless of what files have
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000890 # changed.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000891 if self.deps_hooks:
agabled437d762016-10-17 09:35:11 -0700892 # TODO(maruel): If the user is using git, then we don't know
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000893 # what files have changed so we always run all hooks. It'd be nice to fix
894 # that.
895 if (options.force or
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000896 gclient_scm.GetScmName(self.parsed_url) in ('git', None) or
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000897 os.path.isdir(os.path.join(self.root.root_dir, self.name, '.git'))):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000898 for hook_dict in self.deps_hooks:
szager@google.comb9a78d32012-03-13 18:46:21 +0000899 result.append(self.GetHookAction(hook_dict, []))
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000900 else:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000901 # Run hooks on the basis of whether the files from the gclient operation
902 # match each hook's pattern.
903 for hook_dict in self.deps_hooks:
904 pattern = re.compile(hook_dict['pattern'])
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000905 matching_file_list = [
906 f for f in self.file_list_and_children if pattern.search(f)
907 ]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000908 if matching_file_list:
szager@google.comb9a78d32012-03-13 18:46:21 +0000909 result.append(self.GetHookAction(hook_dict, matching_file_list))
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000910 for s in self.dependencies:
szager@google.comb9a78d32012-03-13 18:46:21 +0000911 result.extend(s.GetHooks(options))
912 return result
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000913
szager@google.comb9a78d32012-03-13 18:46:21 +0000914 def RunHooksRecursively(self, options):
915 assert self.hooks_ran == False
maruel@chromium.org064186c2011-09-27 23:53:33 +0000916 self._hooks_ran = True
szager@google.comb9a78d32012-03-13 18:46:21 +0000917 for hook in self.GetHooks(options):
918 try:
ilevy@chromium.orgc28d3772013-07-12 19:42:37 +0000919 start_time = time.time()
szager@google.comb9a78d32012-03-13 18:46:21 +0000920 gclient_utils.CheckCallAndFilterAndHeader(
921 hook, cwd=self.root.root_dir, always=True)
vapier@chromium.orga81a56e2015-11-11 07:56:13 +0000922 except (gclient_utils.Error, subprocess2.CalledProcessError) as e:
szager@google.comb9a78d32012-03-13 18:46:21 +0000923 # Use a discrete exit status code of 2 to indicate that a hook action
924 # failed. Users of this script may wish to treat hook action failures
925 # differently from VC failures.
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +0000926 print('Error: %s' % str(e), file=sys.stderr)
szager@google.comb9a78d32012-03-13 18:46:21 +0000927 sys.exit(2)
ilevy@chromium.orgc28d3772013-07-12 19:42:37 +0000928 finally:
929 elapsed_time = time.time() - start_time
930 if elapsed_time > 10:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +0000931 print("Hook '%s' took %.2f secs" % (
932 gclient_utils.CommandToStr(hook), elapsed_time))
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000933
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000934 def RunPreDepsHooks(self):
935 assert self.processed
936 assert self.deps_parsed
937 assert not self.pre_deps_hooks_ran
938 assert not self.hooks_ran
939 for s in self.dependencies:
940 assert not s.processed
941 self._pre_deps_hooks_ran = True
942 for hook in self.pre_deps_hooks:
943 try:
944 start_time = time.time()
945 gclient_utils.CheckCallAndFilterAndHeader(
946 hook, cwd=self.root.root_dir, always=True)
vapier@chromium.orga81a56e2015-11-11 07:56:13 +0000947 except (gclient_utils.Error, subprocess2.CalledProcessError) as e:
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000948 # Use a discrete exit status code of 2 to indicate that a hook action
949 # failed. Users of this script may wish to treat hook action failures
950 # differently from VC failures.
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +0000951 print('Error: %s' % str(e), file=sys.stderr)
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000952 sys.exit(2)
953 finally:
954 elapsed_time = time.time() - start_time
955 if elapsed_time > 10:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +0000956 print("Hook '%s' took %.2f secs" % (
957 gclient_utils.CommandToStr(hook), elapsed_time))
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000958
959
maruel@chromium.org0d812442010-08-10 12:41:08 +0000960 def subtree(self, include_all):
maruel@chromium.orgad3287e2011-10-03 19:15:10 +0000961 """Breadth first recursion excluding root node."""
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000962 dependencies = self.dependencies
963 for d in dependencies:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000964 if d.should_process or include_all:
maruel@chromium.orgad3287e2011-10-03 19:15:10 +0000965 yield d
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000966 for d in dependencies:
maruel@chromium.orgad3287e2011-10-03 19:15:10 +0000967 for i in d.subtree(include_all):
968 yield i
969
970 def depth_first_tree(self):
971 """Depth-first recursion including the root node."""
972 yield self
973 for i in self.dependencies:
974 for j in i.depth_first_tree():
975 if j.should_process:
976 yield j
maruel@chromium.orgc57e4f22010-07-22 21:37:46 +0000977
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000978 @gclient_utils.lockedmethod
979 def add_dependency(self, new_dep):
980 self._dependencies.append(new_dep)
981
982 @gclient_utils.lockedmethod
983 def _mark_as_parsed(self, new_hooks):
984 self._deps_hooks.extend(new_hooks)
985 self._deps_parsed = True
986
maruel@chromium.org68988972011-09-20 14:11:42 +0000987 @property
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000988 @gclient_utils.lockedmethod
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +0000989 def dependencies(self):
990 return tuple(self._dependencies)
991
992 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000993 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +0000994 def deps_hooks(self):
995 return tuple(self._deps_hooks)
996
997 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000998 @gclient_utils.lockedmethod
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000999 def pre_deps_hooks(self):
1000 return tuple(self._pre_deps_hooks)
1001
1002 @property
1003 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001004 def parsed_url(self):
1005 return self._parsed_url
1006
1007 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001008 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001009 def deps_parsed(self):
maruel@chromium.org3223edd2011-10-10 23:17:39 +00001010 """This is purely for debugging purposes. It's not used anywhere."""
maruel@chromium.org064186c2011-09-27 23:53:33 +00001011 return self._deps_parsed
1012
1013 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001014 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001015 def processed(self):
1016 return self._processed
1017
1018 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001019 @gclient_utils.lockedmethod
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001020 def pre_deps_hooks_ran(self):
1021 return self._pre_deps_hooks_ran
1022
1023 @property
1024 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001025 def hooks_ran(self):
1026 return self._hooks_ran
1027
1028 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001029 @gclient_utils.lockedmethod
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00001030 def allowed_hosts(self):
1031 return self._allowed_hosts
1032
1033 @property
1034 @gclient_utils.lockedmethod
maruel@chromium.org861fd0f2010-07-23 03:05:05 +00001035 def file_list(self):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001036 return tuple(self._file_list)
1037
1038 @property
kustermann@google.coma692e8f2013-04-18 08:32:04 +00001039 def used_scm(self):
1040 """SCMWrapper instance for this dependency or None if not processed yet."""
1041 return self._used_scm
1042
1043 @property
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00001044 @gclient_utils.lockedmethod
1045 def got_revision(self):
1046 return self._got_revision
1047
1048 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001049 def file_list_and_children(self):
1050 result = list(self.file_list)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +00001051 for d in self.dependencies:
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001052 result.extend(d.file_list_and_children)
maruel@chromium.org68988972011-09-20 14:11:42 +00001053 return tuple(result)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +00001054
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001055 def __str__(self):
1056 out = []
agablea98a6cd2016-11-15 14:30:10 -08001057 for i in ('name', 'url', 'parsed_url', 'custom_deps',
maruel@chromium.org3c74bc92011-09-15 19:17:21 +00001058 'custom_vars', 'deps_hooks', 'file_list', 'should_process',
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00001059 'processed', 'hooks_ran', 'deps_parsed', 'requirements',
1060 'allowed_hosts'):
maruel@chromium.org3c74bc92011-09-15 19:17:21 +00001061 # First try the native property if it exists.
1062 if hasattr(self, '_' + i):
1063 value = getattr(self, '_' + i, False)
1064 else:
1065 value = getattr(self, i, False)
1066 if value:
1067 out.append('%s: %s' % (i, value))
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001068
1069 for d in self.dependencies:
1070 out.extend([' ' + x for x in str(d).splitlines()])
1071 out.append('')
1072 return '\n'.join(out)
1073
1074 def __repr__(self):
1075 return '%s: %s' % (self.name, self.url)
1076
maruel@chromium.orgbffb9042010-07-22 20:59:36 +00001077 def hierarchy(self):
maruel@chromium.orgbc2d2f92010-07-22 21:26:48 +00001078 """Returns a human-readable hierarchical reference to a Dependency."""
maruel@chromium.orgbffb9042010-07-22 20:59:36 +00001079 out = '%s(%s)' % (self.name, self.url)
1080 i = self.parent
1081 while i and i.name:
1082 out = '%s(%s) -> %s' % (i.name, i.url, out)
1083 i = i.parent
1084 return out
1085
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001086
1087class GClient(Dependency):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001088 """Object that represent a gclient checkout. A tree of Dependency(), one per
1089 solution or DEPS entry."""
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001090
1091 DEPS_OS_CHOICES = {
1092 "win32": "win",
1093 "win": "win",
1094 "cygwin": "win",
1095 "darwin": "mac",
1096 "mac": "mac",
1097 "unix": "unix",
1098 "linux": "unix",
1099 "linux2": "unix",
maruel@chromium.org244e3442011-06-12 15:20:55 +00001100 "linux3": "unix",
szager@chromium.orgf8c95cd2012-06-01 22:26:52 +00001101 "android": "android",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001102 }
1103
1104 DEFAULT_CLIENT_FILE_TEXT = ("""\
1105solutions = [
smutae7ea312016-07-18 11:59:41 -07001106 { "name" : "%(solution_name)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001107 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +00001108 "deps_file" : "%(deps_file)s",
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001109 "managed" : %(managed)s,
smutae7ea312016-07-18 11:59:41 -07001110 "custom_deps" : {
1111 },
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001112 },
1113]
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001114cache_dir = %(cache_dir)r
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001115""")
1116
1117 DEFAULT_SNAPSHOT_SOLUTION_TEXT = ("""\
smutae7ea312016-07-18 11:59:41 -07001118 { "name" : "%(solution_name)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001119 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +00001120 "deps_file" : "%(deps_file)s",
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001121 "managed" : %(managed)s,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001122 "custom_deps" : {
smutae7ea312016-07-18 11:59:41 -07001123%(solution_deps)s },
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001124 },
1125""")
1126
1127 DEFAULT_SNAPSHOT_FILE_TEXT = ("""\
1128# Snapshot generated with gclient revinfo --snapshot
1129solutions = [
maruel@chromium.org73e21142010-07-05 13:32:01 +00001130%(solution_list)s]
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001131""")
1132
1133 def __init__(self, root_dir, options):
maruel@chromium.org0d812442010-08-10 12:41:08 +00001134 # Do not change previous behavior. Only solution level and immediate DEPS
1135 # are processed.
1136 self._recursion_limit = 2
agablea98a6cd2016-11-15 14:30:10 -08001137 Dependency.__init__(self, None, None, None, True, None, None, None,
agabledce6ddc2016-09-08 10:02:16 -07001138 'unused', True, None)
maruel@chromium.org0d425922010-06-21 19:22:24 +00001139 self._options = options
maruel@chromium.org271375b2010-06-23 19:17:38 +00001140 if options.deps_os:
1141 enforced_os = options.deps_os.split(',')
1142 else:
1143 enforced_os = [self.DEPS_OS_CHOICES.get(sys.platform, 'unix')]
1144 if 'all' in enforced_os:
1145 enforced_os = self.DEPS_OS_CHOICES.itervalues()
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001146 self._enforced_os = tuple(set(enforced_os))
maruel@chromium.org271375b2010-06-23 19:17:38 +00001147 self._root_dir = root_dir
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001148 self.config_content = None
1149
borenet@google.com88d10082014-03-21 17:24:48 +00001150 def _CheckConfig(self):
1151 """Verify that the config matches the state of the existing checked-out
1152 solutions."""
1153 for dep in self.dependencies:
1154 if dep.managed and dep.url:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001155 scm = gclient_scm.CreateSCM(
1156 dep.url, self.root_dir, dep.name, self.outbuf)
smut@google.comd33eab32014-07-07 19:35:18 +00001157 actual_url = scm.GetActualRemoteURL(self._options)
borenet@google.com4e9be262014-04-08 19:40:30 +00001158 if actual_url and not scm.DoesRemoteURLMatch(self._options):
levarum@chromium.org27a6f9a2016-05-28 00:21:49 +00001159 mirror = scm.GetCacheMirror()
1160 if mirror:
1161 mirror_string = '%s (exists=%s)' % (mirror.mirror_path,
1162 mirror.exists())
1163 else:
1164 mirror_string = 'not used'
borenet@google.com0a427372014-04-02 19:12:13 +00001165 raise gclient_utils.Error('''
borenet@google.com88d10082014-03-21 17:24:48 +00001166Your .gclient file seems to be broken. The requested URL is different from what
borenet@google.com0a427372014-04-02 19:12:13 +00001167is actually checked out in %(checkout_path)s.
borenet@google.com88d10082014-03-21 17:24:48 +00001168
borenet@google.com97882362014-04-07 20:06:02 +00001169The .gclient file contains:
levarum@chromium.org27a6f9a2016-05-28 00:21:49 +00001170URL: %(expected_url)s (%(expected_scm)s)
1171Cache mirror: %(mirror_string)s
borenet@google.com97882362014-04-07 20:06:02 +00001172
1173The local checkout in %(checkout_path)s reports:
1174%(actual_url)s (%(actual_scm)s)
borenet@google.com88d10082014-03-21 17:24:48 +00001175
1176You should ensure that the URL listed in .gclient is correct and either change
agabled437d762016-10-17 09:35:11 -07001177it or fix the checkout.
borenet@google.com88d10082014-03-21 17:24:48 +00001178''' % {'checkout_path': os.path.join(self.root_dir, dep.name),
1179 'expected_url': dep.url,
1180 'expected_scm': gclient_scm.GetScmName(dep.url),
levarum@chromium.org27a6f9a2016-05-28 00:21:49 +00001181 'mirror_string' : mirror_string,
borenet@google.com88d10082014-03-21 17:24:48 +00001182 'actual_url': actual_url,
1183 'actual_scm': gclient_scm.GetScmName(actual_url)})
1184
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001185 def SetConfig(self, content):
maruel@chromium.orgf13a4182011-09-22 00:26:15 +00001186 assert not self.dependencies
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001187 config_dict = {}
1188 self.config_content = content
1189 try:
1190 exec(content, config_dict)
vapier@chromium.orga81a56e2015-11-11 07:56:13 +00001191 except SyntaxError as e:
maruel@chromium.org5990f9d2010-07-07 18:02:58 +00001192 gclient_utils.SyntaxErrorToError('.gclient', e)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001193
peter@chromium.org1efccc82012-04-27 16:34:38 +00001194 # Append any target OS that is not already being enforced to the tuple.
1195 target_os = config_dict.get('target_os', [])
stuartmorgan@chromium.org18a4f6a2012-11-02 13:37:24 +00001196 if config_dict.get('target_os_only', False):
1197 self._enforced_os = tuple(set(target_os))
1198 else:
1199 self._enforced_os = tuple(set(self._enforced_os).union(target_os))
1200
dyen@chromium.orgd915cca2014-08-07 21:41:37 +00001201 cache_dir = config_dict.get('cache_dir')
1202 if cache_dir:
1203 cache_dir = os.path.join(self.root_dir, cache_dir)
1204 cache_dir = os.path.abspath(cache_dir)
szager@chromium.orgcaf5bef2014-08-24 18:56:32 +00001205 # If running on a bot, force break any stale git cache locks.
dnj@chromium.orgb682b3e2014-08-25 19:17:12 +00001206 if os.path.exists(cache_dir) and os.environ.get('CHROME_HEADLESS'):
szager@chromium.org4848fb62014-08-24 19:16:31 +00001207 subprocess2.check_call(['git', 'cache', 'unlock', '--cache-dir',
1208 cache_dir, '--force', '--all'])
dyen@chromium.orgd915cca2014-08-07 21:41:37 +00001209 gclient_scm.GitWrapper.cache_dir = cache_dir
1210 git_cache.Mirror.SetCachePath(cache_dir)
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001211
stuartmorgan@chromium.org18a4f6a2012-11-02 13:37:24 +00001212 if not target_os and config_dict.get('target_os_only', False):
1213 raise gclient_utils.Error('Can\'t use target_os_only if target_os is '
1214 'not specified')
peter@chromium.org1efccc82012-04-27 16:34:38 +00001215
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001216 deps_to_add = []
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001217 for s in config_dict.get('solutions', []):
maruel@chromium.org81843b82010-06-28 16:49:26 +00001218 try:
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001219 deps_to_add.append(Dependency(
maruel@chromium.org81843b82010-06-28 16:49:26 +00001220 self, s['name'], s['url'],
smutae7ea312016-07-18 11:59:41 -07001221 s.get('managed', True),
maruel@chromium.org81843b82010-06-28 16:49:26 +00001222 s.get('custom_deps', {}),
maruel@chromium.org0d812442010-08-10 12:41:08 +00001223 s.get('custom_vars', {}),
petermayo@chromium.orge79161a2013-07-09 14:40:37 +00001224 s.get('custom_hooks', []),
nsylvain@google.comefc80932011-05-31 21:27:56 +00001225 s.get('deps_file', 'DEPS'),
agabledce6ddc2016-09-08 10:02:16 -07001226 True,
1227 None))
maruel@chromium.org81843b82010-06-28 16:49:26 +00001228 except KeyError:
1229 raise gclient_utils.Error('Invalid .gclient file. Solution is '
1230 'incomplete: %s' % s)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001231 self.add_dependencies_and_close(deps_to_add, config_dict.get('hooks', []))
1232 logging.info('SetConfig() done')
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001233
1234 def SaveConfig(self):
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001235 gclient_utils.FileWrite(os.path.join(self.root_dir,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001236 self._options.config_filename),
1237 self.config_content)
1238
1239 @staticmethod
1240 def LoadCurrentConfig(options):
1241 """Searches for and loads a .gclient file relative to the current working
1242 dir. Returns a GClient object."""
szager@chromium.orge2e03202012-07-31 18:05:16 +00001243 if options.spec:
1244 client = GClient('.', options)
1245 client.SetConfig(options.spec)
1246 else:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00001247 if options.verbose:
1248 print('Looking for %s starting from %s\n' % (
1249 options.config_filename, os.getcwd()))
szager@chromium.orge2e03202012-07-31 18:05:16 +00001250 path = gclient_utils.FindGclientRoot(os.getcwd(), options.config_filename)
1251 if not path:
1252 return None
1253 client = GClient(path, options)
1254 client.SetConfig(gclient_utils.FileRead(
1255 os.path.join(path, options.config_filename)))
maruel@chromium.org69392e72011-10-13 22:09:00 +00001256
1257 if (options.revisions and
1258 len(client.dependencies) > 1 and
1259 any('@' not in r for r in options.revisions)):
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001260 print(
1261 ('You must specify the full solution name like --revision %s@%s\n'
1262 'when you have multiple solutions setup in your .gclient file.\n'
1263 'Other solutions present are: %s.') % (
maruel@chromium.org69392e72011-10-13 22:09:00 +00001264 client.dependencies[0].name,
1265 options.revisions[0],
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001266 ', '.join(s.name for s in client.dependencies[1:])),
1267 file=sys.stderr)
maruel@chromium.org15804092010-09-02 17:07:37 +00001268 return client
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001269
nsylvain@google.comefc80932011-05-31 21:27:56 +00001270 def SetDefaultConfig(self, solution_name, deps_file, solution_url,
agablea98a6cd2016-11-15 14:30:10 -08001271 managed=True, cache_dir=None):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001272 self.SetConfig(self.DEFAULT_CLIENT_FILE_TEXT % {
1273 'solution_name': solution_name,
1274 'solution_url': solution_url,
nsylvain@google.comefc80932011-05-31 21:27:56 +00001275 'deps_file': deps_file,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001276 'managed': managed,
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001277 'cache_dir': cache_dir,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001278 })
1279
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001280 def _SaveEntries(self):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001281 """Creates a .gclient_entries file to record the list of unique checkouts.
1282
1283 The .gclient_entries file lives in the same directory as .gclient.
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001284 """
1285 # Sometimes pprint.pformat will use {', sometimes it'll use { ' ... It
1286 # makes testing a bit too fun.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001287 result = 'entries = {\n'
maruel@chromium.org68988972011-09-20 14:11:42 +00001288 for entry in self.root.subtree(False):
agabled437d762016-10-17 09:35:11 -07001289 result += ' %s: %s,\n' % (pprint.pformat(entry.name),
1290 pprint.pformat(entry.parsed_url))
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001291 result += '}\n'
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001292 file_path = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org1333cb32011-10-04 23:40:16 +00001293 logging.debug(result)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001294 gclient_utils.FileWrite(file_path, result)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001295
1296 def _ReadEntries(self):
1297 """Read the .gclient_entries file for the given client.
1298
1299 Returns:
1300 A sequence of solution names, which will be empty if there is the
1301 entries file hasn't been created yet.
1302 """
1303 scope = {}
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001304 filename = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001305 if not os.path.exists(filename):
maruel@chromium.org73e21142010-07-05 13:32:01 +00001306 return {}
maruel@chromium.org5990f9d2010-07-07 18:02:58 +00001307 try:
1308 exec(gclient_utils.FileRead(filename), scope)
vapier@chromium.orga81a56e2015-11-11 07:56:13 +00001309 except SyntaxError as e:
maruel@chromium.org5990f9d2010-07-07 18:02:58 +00001310 gclient_utils.SyntaxErrorToError(filename, e)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001311 return scope['entries']
1312
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001313 def _EnforceRevisions(self):
maruel@chromium.org918a9ae2010-05-28 15:50:30 +00001314 """Checks for revision overrides."""
1315 revision_overrides = {}
smutae7ea312016-07-18 11:59:41 -07001316 if self._options.head:
1317 return revision_overrides
joi@chromium.org792ea882010-11-10 02:37:27 +00001318 if not self._options.revisions:
1319 for s in self.dependencies:
smutae7ea312016-07-18 11:59:41 -07001320 if not s.managed:
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001321 self._options.revisions.append('%s@unmanaged' % s.name)
maruel@chromium.org307d1792010-05-31 20:03:13 +00001322 if not self._options.revisions:
1323 return revision_overrides
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001324 solutions_names = [s.name for s in self.dependencies]
smutae7ea312016-07-18 11:59:41 -07001325 index = 0
1326 for revision in self._options.revisions:
1327 if not '@' in revision:
maruel@chromium.org307d1792010-05-31 20:03:13 +00001328 # Support for --revision 123
smutae7ea312016-07-18 11:59:41 -07001329 revision = '%s@%s' % (solutions_names[index], revision)
1330 name, rev = revision.split('@', 1)
szager@chromium.org4ad264b2014-05-20 04:43:47 +00001331 revision_overrides[name] = rev
smutae7ea312016-07-18 11:59:41 -07001332 index += 1
maruel@chromium.org918a9ae2010-05-28 15:50:30 +00001333 return revision_overrides
1334
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001335 def RunOnDeps(self, command, args, ignore_requirements=False, progress=True):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001336 """Runs a command on each dependency in a client and its dependencies.
1337
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001338 Args:
1339 command: The command to use (e.g., 'status' or 'diff')
1340 args: list of str - extra arguments to add to the command line.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001341 """
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001342 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +00001343 raise gclient_utils.Error('No solution specified')
borenet@google.com0a427372014-04-02 19:12:13 +00001344
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001345 revision_overrides = {}
1346 # It's unnecessary to check for revision overrides for 'recurse'.
1347 # Save a few seconds by not calling _EnforceRevisions() in that case.
agable@chromium.org0242eb42015-06-09 00:45:31 +00001348 if command not in ('diff', 'recurse', 'runhooks', 'status', 'revert'):
szager@chromium.org5273b8a2014-08-21 15:10:10 +00001349 self._CheckConfig()
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001350 revision_overrides = self._EnforceRevisions()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001351 pm = None
maruel@chromium.org5b3f8852010-09-10 16:49:54 +00001352 # Disable progress for non-tty stdout.
iannucci@chromium.org596cd5c2016-04-04 21:34:39 +00001353 if (setup_color.IS_TTY and not self._options.verbose and progress):
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001354 if command in ('update', 'revert'):
1355 pm = Progress('Syncing projects', 1)
maruel@chromium.orgcd8d8e12012-10-03 17:16:25 +00001356 elif command == 'recurse':
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001357 pm = Progress(' '.join(args), 1)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001358 work_queue = gclient_utils.ExecutionQueue(
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001359 self._options.jobs, pm, ignore_requirements=ignore_requirements,
1360 verbose=self._options.verbose)
maruel@chromium.org049bced2010-08-12 13:37:20 +00001361 for s in self.dependencies:
1362 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +00001363 work_queue.flush(revision_overrides, command, args, options=self._options)
szager@chromium.org4ad264b2014-05-20 04:43:47 +00001364 if revision_overrides:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001365 print('Please fix your script, having invalid --revision flags will soon '
1366 'considered an error.', file=sys.stderr)
piman@chromium.org6f363722010-04-27 00:41:09 +00001367
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001368 # Once all the dependencies have been processed, it's now safe to run the
1369 # hooks.
1370 if not self._options.nohooks:
1371 self.RunHooksRecursively(self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001372
1373 if command == 'update':
ajwong@chromium.orgcdcee802009-06-23 15:30:42 +00001374 # Notify the user if there is an orphaned entry in their working copy.
1375 # Only delete the directory if there are no changes in it, and
1376 # delete_unversioned_trees is set to true.
maruel@chromium.org68988972011-09-20 14:11:42 +00001377 entries = [i.name for i in self.root.subtree(False) if i.url]
xusydoc@chromium.org885a9602013-05-31 09:54:40 +00001378 full_entries = [os.path.join(self.root_dir, e.replace('/', os.path.sep))
1379 for e in entries]
1380
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001381 for entry, prev_url in self._ReadEntries().iteritems():
maruel@chromium.org04dd7de2010-10-14 13:25:49 +00001382 if not prev_url:
1383 # entry must have been overridden via .gclient custom_deps
1384 continue
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001385 # Fix path separator on Windows.
1386 entry_fixed = entry.replace('/', os.path.sep)
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001387 e_dir = os.path.join(self.root_dir, entry_fixed)
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001388 # Use entry and not entry_fixed there.
jochen@chromium.orga78e5532013-03-11 13:33:03 +00001389 if (entry not in entries and
1390 (not any(path.startswith(entry + '/') for path in entries)) and
jochen@chromium.orgcc475722013-03-11 13:07:40 +00001391 os.path.exists(e_dir)):
primiano@chromium.org1c127382015-02-17 11:15:40 +00001392 # The entry has been removed from DEPS.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001393 scm = gclient_scm.CreateSCM(
1394 prev_url, self.root_dir, entry_fixed, self.outbuf)
xusydoc@chromium.org885a9602013-05-31 09:54:40 +00001395
1396 # Check to see if this directory is now part of a higher-up checkout.
borenet@google.com359bb642014-05-13 17:28:19 +00001397 scm_root = None
agabled437d762016-10-17 09:35:11 -07001398 try:
1399 scm_root = gclient_scm.scm.GIT.GetCheckoutRoot(scm.checkout_path)
1400 except subprocess2.CalledProcessError:
1401 pass
1402 if not scm_root:
borenet@google.com359bb642014-05-13 17:28:19 +00001403 logging.warning('Could not find checkout root for %s. Unable to '
1404 'determine whether it is part of a higher-level '
1405 'checkout, so not removing.' % entry)
1406 continue
primiano@chromium.org1c127382015-02-17 11:15:40 +00001407
1408 # This is to handle the case of third_party/WebKit migrating from
1409 # being a DEPS entry to being part of the main project.
1410 # If the subproject is a Git project, we need to remove its .git
1411 # folder. Otherwise git operations on that folder will have different
1412 # effects depending on the current working directory.
agabled437d762016-10-17 09:35:11 -07001413 if os.path.abspath(scm_root) == os.path.abspath(e_dir):
primiano@chromium.org1c127382015-02-17 11:15:40 +00001414 e_par_dir = os.path.join(e_dir, os.pardir)
agabled437d762016-10-17 09:35:11 -07001415 if gclient_scm.scm.GIT.IsInsideWorkTree(e_par_dir):
1416 par_scm_root = gclient_scm.scm.GIT.GetCheckoutRoot(e_par_dir)
primiano@chromium.org1c127382015-02-17 11:15:40 +00001417 # rel_e_dir : relative path of entry w.r.t. its parent repo.
1418 rel_e_dir = os.path.relpath(e_dir, par_scm_root)
agabled437d762016-10-17 09:35:11 -07001419 if gclient_scm.scm.GIT.IsDirectoryVersioned(
1420 par_scm_root, rel_e_dir):
primiano@chromium.org1c127382015-02-17 11:15:40 +00001421 save_dir = scm.GetGitBackupDirPath()
1422 # Remove any eventual stale backup dir for the same project.
1423 if os.path.exists(save_dir):
1424 gclient_utils.rmtree(save_dir)
1425 os.rename(os.path.join(e_dir, '.git'), save_dir)
1426 # When switching between the two states (entry/ is a subproject
1427 # -> entry/ is part of the outer project), it is very likely
1428 # that some files are changed in the checkout, unless we are
1429 # jumping *exactly* across the commit which changed just DEPS.
1430 # In such case we want to cleanup any eventual stale files
1431 # (coming from the old subproject) in order to end up with a
1432 # clean checkout.
agabled437d762016-10-17 09:35:11 -07001433 gclient_scm.scm.GIT.CleanupDir(par_scm_root, rel_e_dir)
primiano@chromium.org1c127382015-02-17 11:15:40 +00001434 assert not os.path.exists(os.path.join(e_dir, '.git'))
1435 print(('\nWARNING: \'%s\' has been moved from DEPS to a higher '
1436 'level checkout. The git folder containing all the local'
1437 ' branches has been saved to %s.\n'
1438 'If you don\'t care about its state you can safely '
1439 'remove that folder to free up space.') %
1440 (entry, save_dir))
1441 continue
1442
borenet@google.com359bb642014-05-13 17:28:19 +00001443 if scm_root in full_entries:
primiano@chromium.org1c127382015-02-17 11:15:40 +00001444 logging.info('%s is part of a higher level checkout, not removing',
1445 scm.GetCheckoutRoot())
xusydoc@chromium.org885a9602013-05-31 09:54:40 +00001446 continue
1447
1448 file_list = []
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001449 scm.status(self._options, [], file_list)
1450 modified_files = file_list != []
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001451 if (not self._options.delete_unversioned_trees or
1452 (modified_files and not self._options.force)):
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001453 # There are modified files in this entry. Keep warning until
1454 # removed.
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001455 print(('\nWARNING: \'%s\' is no longer part of this client. '
1456 'It is recommended that you manually remove it.\n') %
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001457 entry_fixed)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001458 else:
1459 # Delete the entry
maruel@chromium.org73e21142010-07-05 13:32:01 +00001460 print('\n________ deleting \'%s\' in \'%s\'' % (
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001461 entry_fixed, self.root_dir))
digit@chromium.orgdc112ac2013-04-24 13:00:19 +00001462 gclient_utils.rmtree(e_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001463 # record the current list of entries for next time
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001464 self._SaveEntries()
maruel@chromium.org17cdf762010-05-28 17:30:52 +00001465 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001466
1467 def PrintRevInfo(self):
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001468 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +00001469 raise gclient_utils.Error('No solution specified')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001470 # Load all the settings.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001471 work_queue = gclient_utils.ExecutionQueue(
1472 self._options.jobs, None, False, verbose=self._options.verbose)
maruel@chromium.org049bced2010-08-12 13:37:20 +00001473 for s in self.dependencies:
1474 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +00001475 work_queue.flush({}, None, [], options=self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001476
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001477 def GetURLAndRev(dep):
1478 """Returns the revision-qualified SCM url for a Dependency."""
1479 if dep.parsed_url is None:
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001480 return None
agabled437d762016-10-17 09:35:11 -07001481 url, _ = gclient_utils.SplitUrlRevision(dep.parsed_url)
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001482 scm = gclient_scm.CreateSCM(
agabled437d762016-10-17 09:35:11 -07001483 dep.parsed_url, self.root_dir, dep.name, self.outbuf)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001484 if not os.path.isdir(scm.checkout_path):
1485 return None
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001486 return '%s@%s' % (url, scm.revinfo(self._options, [], None))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001487
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001488 if self._options.snapshot:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001489 new_gclient = ''
1490 # First level at .gclient
1491 for d in self.dependencies:
1492 entries = {}
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001493 def GrabDeps(dep):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001494 """Recursively grab dependencies."""
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001495 for d in dep.dependencies:
1496 entries[d.name] = GetURLAndRev(d)
1497 GrabDeps(d)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001498 GrabDeps(d)
1499 custom_deps = []
1500 for k in sorted(entries.keys()):
1501 if entries[k]:
1502 # Quotes aren't escaped...
1503 custom_deps.append(' \"%s\": \'%s\',\n' % (k, entries[k]))
1504 else:
1505 custom_deps.append(' \"%s\": None,\n' % k)
1506 new_gclient += self.DEFAULT_SNAPSHOT_SOLUTION_TEXT % {
1507 'solution_name': d.name,
1508 'solution_url': d.url,
nsylvain@google.comefc80932011-05-31 21:27:56 +00001509 'deps_file': d.deps_file,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001510 'managed': d.managed,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001511 'solution_deps': ''.join(custom_deps),
1512 }
1513 # Print the snapshot configuration file
1514 print(self.DEFAULT_SNAPSHOT_FILE_TEXT % {'solution_list': new_gclient})
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +00001515 else:
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001516 entries = {}
maruel@chromium.org68988972011-09-20 14:11:42 +00001517 for d in self.root.subtree(False):
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001518 if self._options.actual:
1519 entries[d.name] = GetURLAndRev(d)
1520 else:
1521 entries[d.name] = d.parsed_url
1522 keys = sorted(entries.keys())
1523 for x in keys:
maruel@chromium.orgce464892010-08-12 17:12:18 +00001524 print('%s: %s' % (x, entries[x]))
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +00001525 logging.info(str(self))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001526
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001527 def ParseDepsFile(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001528 """No DEPS to parse for a .gclient file."""
maruel@chromium.org049bced2010-08-12 13:37:20 +00001529 raise gclient_utils.Error('Internal error')
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001530
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00001531 def PrintLocationAndContents(self):
1532 # Print out the .gclient file. This is longer than if we just printed the
1533 # client dict, but more legible, and it might contain helpful comments.
1534 print('Loaded .gclient config in %s:\n%s' % (
1535 self.root_dir, self.config_content))
1536
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001537 @property
maruel@chromium.org75a59272010-06-11 22:34:03 +00001538 def root_dir(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001539 """Root directory of gclient checkout."""
maruel@chromium.org75a59272010-06-11 22:34:03 +00001540 return self._root_dir
1541
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001542 @property
maruel@chromium.org271375b2010-06-23 19:17:38 +00001543 def enforced_os(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001544 """What deps_os entries that are to be parsed."""
maruel@chromium.org271375b2010-06-23 19:17:38 +00001545 return self._enforced_os
1546
maruel@chromium.org68988972011-09-20 14:11:42 +00001547 @property
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001548 def recursion_limit(self):
1549 """How recursive can each dependencies in DEPS file can load DEPS file."""
1550 return self._recursion_limit
1551
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +00001552 @property
cmp@chromium.orgc401ad12014-07-02 23:20:08 +00001553 def try_recursedeps(self):
1554 """Whether to attempt using recursedeps-style recursion processing."""
cmp@chromium.orge84ac912014-06-30 23:14:35 +00001555 return True
1556
1557 @property
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +00001558 def target_os(self):
1559 return self._enforced_os
1560
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001561
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001562#### gclient commands.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001563
1564
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001565@subcommand.usage('[command] [args ...]')
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001566def CMDrecurse(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001567 """Operates [command args ...] on all the dependencies.
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001568
1569 Runs a shell command on all entries.
qyearsley12fa6ff2016-08-24 09:18:40 -07001570 Sets GCLIENT_DEP_PATH environment variable as the dep's relative location to
ilevy@chromium.org37116242012-11-28 01:32:48 +00001571 root directory of the checkout.
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001572 """
1573 # Stop parsing at the first non-arg so that these go through to the command
1574 parser.disable_interspersed_args()
1575 parser.add_option('-s', '--scm', action='append', default=[],
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001576 help='Choose scm types to operate upon.')
maruel@chromium.org288054d2012-03-05 00:43:07 +00001577 parser.add_option('-i', '--ignore', action='store_true',
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001578 help='Ignore non-zero return codes from subcommands.')
1579 parser.add_option('--prepend-dir', action='store_true',
1580 help='Prepend relative dir for use with git <cmd> --null.')
1581 parser.add_option('--no-progress', action='store_true',
1582 help='Disable progress bar that shows sub-command updates')
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001583 options, args = parser.parse_args(args)
maruel@chromium.org45e9f2d2010-10-18 13:33:46 +00001584 if not args:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001585 print('Need to supply a command!', file=sys.stderr)
maruel@chromium.org45e9f2d2010-10-18 13:33:46 +00001586 return 1
maruel@chromium.org78cba522010-10-18 13:32:05 +00001587 root_and_entries = gclient_utils.GetGClientRootAndEntries()
1588 if not root_and_entries:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001589 print(
maruel@chromium.org78cba522010-10-18 13:32:05 +00001590 'You need to run gclient sync at least once to use \'recurse\'.\n'
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001591 'This is because .gclient_entries needs to exist and be up to date.',
1592 file=sys.stderr)
maruel@chromium.org78cba522010-10-18 13:32:05 +00001593 return 1
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001594
1595 # Normalize options.scm to a set()
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001596 scm_set = set()
1597 for scm in options.scm:
1598 scm_set.update(scm.split(','))
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001599 options.scm = scm_set
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001600
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001601 options.nohooks = True
1602 client = GClient.LoadCurrentConfig(options)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001603 return client.RunOnDeps('recurse', args, ignore_requirements=True,
1604 progress=not options.no_progress)
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001605
1606
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001607@subcommand.usage('[args ...]')
davidbarr@chromium.org12f944e2012-03-01 02:18:31 +00001608def CMDfetch(parser, args):
1609 """Fetches upstream commits for all modules.
1610
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001611 Completely git-specific. Simply runs 'git fetch [args ...]' for each module.
1612 """
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001613 (options, args) = parser.parse_args(args)
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00001614 return CMDrecurse(OptionParser(), [
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001615 '--jobs=%d' % options.jobs, '--scm=git', 'git', 'fetch'] + args)
1616
1617
1618def CMDgrep(parser, args):
1619 """Greps through git repos managed by gclient.
1620
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001621 Runs 'git grep [args...]' for each module.
1622 """
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001623 # We can't use optparse because it will try to parse arguments sent
1624 # to git grep and throw an error. :-(
1625 if not args or re.match('(-h|--help)$', args[0]):
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001626 print(
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001627 'Usage: gclient grep [-j <N>] git-grep-args...\n\n'
1628 'Example: "gclient grep -j10 -A2 RefCountedBase" runs\n"git grep '
1629 '-A2 RefCountedBase" on each of gclient\'s git\nrepos with up to '
1630 '10 jobs.\n\nBonus: page output by appending "|& less -FRSX" to the'
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001631 ' end of your query.',
1632 file=sys.stderr)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001633 return 1
1634
1635 jobs_arg = ['--jobs=1']
1636 if re.match(r'(-j|--jobs=)\d+$', args[0]):
1637 jobs_arg, args = args[:1], args[1:]
1638 elif re.match(r'(-j|--jobs)$', args[0]):
1639 jobs_arg, args = args[:2], args[2:]
1640
1641 return CMDrecurse(
1642 parser,
1643 jobs_arg + ['--ignore', '--prepend-dir', '--no-progress', '--scm=git',
1644 'git', 'grep', '--null', '--color=Always'] + args)
davidbarr@chromium.org12f944e2012-03-01 02:18:31 +00001645
1646
stip@chromium.orga735da22015-04-29 23:18:20 +00001647def CMDroot(parser, args):
1648 """Outputs the solution root (or current dir if there isn't one)."""
1649 (options, args) = parser.parse_args(args)
1650 client = GClient.LoadCurrentConfig(options)
1651 if client:
1652 print(os.path.abspath(client.root_dir))
1653 else:
1654 print(os.path.abspath('.'))
1655
1656
agablea98a6cd2016-11-15 14:30:10 -08001657@subcommand.usage('[url]')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001658def CMDconfig(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001659 """Creates a .gclient file in the current directory.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001660
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001661 This specifies the configuration for further commands. After update/sync,
1662 top-level DEPS files in each module are read to determine dependent
1663 modules to operate on as well. If optional [url] parameter is
1664 provided, then configuration is read from a specified Subversion server
1665 URL.
1666 """
szager@chromium.orge2e03202012-07-31 18:05:16 +00001667 # We do a little dance with the --gclientfile option. 'gclient config' is the
1668 # only command where it's acceptable to have both '--gclientfile' and '--spec'
1669 # arguments. So, we temporarily stash any --gclientfile parameter into
1670 # options.output_config_file until after the (gclientfile xor spec) error
1671 # check.
1672 parser.remove_option('--gclientfile')
1673 parser.add_option('--gclientfile', dest='output_config_file',
1674 help='Specify an alternate .gclient file')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001675 parser.add_option('--name',
1676 help='overrides the default name for the solution')
nsylvain@google.comefc80932011-05-31 21:27:56 +00001677 parser.add_option('--deps-file', default='DEPS',
1678 help='overrides the default name for the DEPS file for the'
1679 'main solutions and all sub-dependencies')
smutae7ea312016-07-18 11:59:41 -07001680 parser.add_option('--unmanaged', action='store_true', default=False,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001681 help='overrides the default behavior to make it possible '
smutae7ea312016-07-18 11:59:41 -07001682 'to have the main solution untouched by gclient '
1683 '(gclient will check out unmanaged dependencies but '
1684 'will never sync them)')
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001685 parser.add_option('--cache-dir',
1686 help='(git only) Cache all git repos into this dir and do '
1687 'shared clones from the cache, instead of cloning '
1688 'directly from the remote. (experimental)')
szager@chromium.orge2e03202012-07-31 18:05:16 +00001689 parser.set_defaults(config_filename=None)
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001690 (options, args) = parser.parse_args(args)
szager@chromium.orge2e03202012-07-31 18:05:16 +00001691 if options.output_config_file:
1692 setattr(options, 'config_filename', getattr(options, 'output_config_file'))
maruel@chromium.org5fc2a332010-05-26 19:37:15 +00001693 if ((options.spec and args) or len(args) > 2 or
1694 (not options.spec and not args)):
1695 parser.error('Inconsistent arguments. Use either --spec or one or 2 args')
1696
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001697 client = GClient('.', options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001698 if options.spec:
1699 client.SetConfig(options.spec)
1700 else:
maruel@chromium.org1ab7ffc2009-06-03 17:21:37 +00001701 base_url = args[0].rstrip('/')
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001702 if not options.name:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001703 name = base_url.split('/')[-1]
nsylvain@google.com12649ef2011-06-01 17:11:20 +00001704 if name.endswith('.git'):
1705 name = name[:-4]
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001706 else:
1707 # specify an alternate relpath for the given URL.
1708 name = options.name
agable@chromium.orgf2214672015-10-27 21:02:48 +00001709 if not os.path.abspath(os.path.join(os.getcwd(), name)).startswith(
1710 os.getcwd()):
1711 parser.error('Do not pass a relative path for --name.')
1712 if any(x in ('..', '.', '/', '\\') for x in name.split(os.sep)):
1713 parser.error('Do not include relative path components in --name.')
1714
nsylvain@google.comefc80932011-05-31 21:27:56 +00001715 deps_file = options.deps_file
agablea98a6cd2016-11-15 14:30:10 -08001716 client.SetDefaultConfig(name, deps_file, base_url,
smutae7ea312016-07-18 11:59:41 -07001717 managed=not options.unmanaged,
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001718 cache_dir=options.cache_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001719 client.SaveConfig()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001720 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001721
1722
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001723@subcommand.epilog("""Example:
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001724 gclient pack > patch.txt
1725 generate simple patch for configured client and dependences
1726""")
1727def CMDpack(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001728 """Generates a patch which can be applied at the root of the tree.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001729
agabled437d762016-10-17 09:35:11 -07001730 Internally, runs 'git diff' on each checked out module and
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001731 dependencies, and performs minimal postprocessing of the output. The
1732 resulting patch is printed to stdout and can be applied to a freshly
1733 checked out tree via 'patch -p0 < patchfile'.
1734 """
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001735 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1736 help='override deps for the specified (comma-separated) '
1737 'platform(s); \'all\' will process all deps_os '
1738 'references')
haitao.feng@intel.com306080c2012-05-04 13:11:29 +00001739 parser.remove_option('--jobs')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001740 (options, args) = parser.parse_args(args)
iannucci@chromium.org50395ea2013-04-04 04:47:42 +00001741 # Force jobs to 1 so the stdout is not annotated with the thread ids
haitao.feng@intel.com306080c2012-05-04 13:11:29 +00001742 options.jobs = 1
kbr@google.comab318592009-09-04 00:54:55 +00001743 client = GClient.LoadCurrentConfig(options)
1744 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001745 raise gclient_utils.Error('client not configured; see \'gclient config\'')
kbr@google.comab318592009-09-04 00:54:55 +00001746 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00001747 client.PrintLocationAndContents()
kbr@google.comab318592009-09-04 00:54:55 +00001748 return client.RunOnDeps('pack', args)
1749
1750
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001751def CMDstatus(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001752 """Shows modification status for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001753 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1754 help='override deps for the specified (comma-separated) '
1755 'platform(s); \'all\' will process all deps_os '
1756 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001757 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001758 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001759 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001760 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001761 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00001762 client.PrintLocationAndContents()
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001763 return client.RunOnDeps('status', args)
1764
1765
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001766@subcommand.epilog("""Examples:
maruel@chromium.org79692d62010-05-14 18:57:13 +00001767 gclient sync
1768 update files from SCM according to current configuration,
1769 *for modules which have changed since last update or sync*
1770 gclient sync --force
1771 update files from SCM according to current configuration, for
1772 all modules (useful for recovering files deleted from local copy)
1773 gclient sync --revision src@31000
1774 update src directory to r31000
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00001775
1776JSON output format:
1777If the --output-json option is specified, the following document structure will
1778be emitted to the provided file. 'null' entries may occur for subprojects which
1779are present in the gclient solution, but were not processed (due to custom_deps,
1780os_deps, etc.)
1781
1782{
1783 "solutions" : {
1784 "<name>": { # <name> is the posix-normalized path to the solution.
agabled437d762016-10-17 09:35:11 -07001785 "revision": [<git id hex string>|null],
1786 "scm": ["git"|null],
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00001787 }
1788 }
1789}
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001790""")
1791def CMDsync(parser, args):
1792 """Checkout/update all modules."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001793 parser.add_option('-f', '--force', action='store_true',
1794 help='force update even for unchanged modules')
1795 parser.add_option('-n', '--nohooks', action='store_true',
1796 help='don\'t run hooks after the update is complete')
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001797 parser.add_option('-p', '--noprehooks', action='store_true',
1798 help='don\'t run pre-DEPS hooks', default=False)
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001799 parser.add_option('-r', '--revision', action='append',
1800 dest='revisions', metavar='REV', default=[],
1801 help='Enforces revision/hash for the solutions with the '
1802 'format src@rev. The src@ part is optional and can be '
1803 'skipped. -r can be used multiple times when .gclient '
1804 'has multiple solutions configured and will work even '
agablea98a6cd2016-11-15 14:30:10 -08001805 'if the src@ part is skipped.')
maruel@chromium.org794207e2013-03-08 15:29:43 +00001806 parser.add_option('--with_branch_heads', action='store_true',
1807 help='Clone git "branch_heads" refspecs in addition to '
1808 'the default refspecs. This adds about 1/2GB to a '
1809 'full checkout. (git only)')
szager@chromium.org8d3348f2014-08-19 22:49:16 +00001810 parser.add_option('--with_tags', action='store_true',
1811 help='Clone git tags in addition to the default refspecs.')
agable2697cd12016-06-28 10:23:53 -07001812 parser.add_option('-H', '--head', action='store_true',
agablea98a6cd2016-11-15 14:30:10 -08001813 help='DEPRECATED: only made sense with safesync urls.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001814 parser.add_option('-D', '--delete_unversioned_trees', action='store_true',
steveblock@chromium.org98e69452012-02-16 16:36:43 +00001815 help='Deletes from the working copy any dependencies that '
1816 'have been removed since the last sync, as long as '
1817 'there are no local modifications. When used with '
1818 '--force, such dependencies are removed even if they '
1819 'have local modifications. When used with --reset, '
1820 'all untracked directories are removed from the '
tnagel@chromium.orga2aaa632014-02-28 21:47:27 +00001821 'working copy, excluding those which are explicitly '
steveblock@chromium.org98e69452012-02-16 16:36:43 +00001822 'ignored in the repository.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001823 parser.add_option('-R', '--reset', action='store_true',
1824 help='resets any local changes before updating (git only)')
bauerb@chromium.org2aad1b22011-07-22 12:00:41 +00001825 parser.add_option('-M', '--merge', action='store_true',
1826 help='merge upstream changes instead of trying to '
1827 'fast-forward or rebase')
dnj@chromium.org5b23e872015-02-20 21:25:57 +00001828 parser.add_option('-A', '--auto_rebase', action='store_true',
1829 help='Automatically rebase repositories against local '
1830 'checkout during update (git only).')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001831 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1832 help='override deps for the specified (comma-separated) '
1833 'platform(s); \'all\' will process all deps_os '
1834 'references')
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +00001835 parser.add_option('--upstream', action='store_true',
1836 help='Make repo state match upstream branch.')
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00001837 parser.add_option('--output-json',
1838 help='Output a json document to this path containing '
1839 'summary information about the sync.')
primiano@chromium.org5439ea52014-08-06 17:18:18 +00001840 parser.add_option('--no-history', action='store_true',
1841 help='GIT ONLY - Reduces the size/time of the checkout at '
1842 'the cost of no history. Requires Git 1.9+')
hinoka@chromium.org46b87412014-05-15 00:42:05 +00001843 parser.add_option('--shallow', action='store_true',
1844 help='GIT ONLY - Do a shallow clone into the cache dir. '
1845 'Requires Git 1.9+')
e.hakkinen@samsung.come8bc1aa2015-04-08 08:00:37 +00001846 parser.add_option('--no_bootstrap', '--no-bootstrap',
1847 action='store_true',
1848 help='Don\'t bootstrap from Google Storage.')
hinoka@chromium.org8a10f6d2014-06-23 18:38:57 +00001849 parser.add_option('--ignore_locks', action='store_true',
1850 help='GIT ONLY - Ignore cache locks.')
iannucci@chromium.org30a07982016-04-07 21:35:19 +00001851 parser.add_option('--break_repo_locks', action='store_true',
1852 help='GIT ONLY - Forcibly remove repo locks (e.g. '
1853 'index.lock). This should only be used if you know for '
1854 'certain that this invocation of gclient is the only '
1855 'thing operating on the git repos (e.g. on a bot).')
nodir@chromium.org5b48e482016-03-18 20:27:54 +00001856 parser.add_option('--lock_timeout', type='int', default=5000,
szager@chromium.orgdbb6f822016-02-02 22:59:30 +00001857 help='GIT ONLY - Deadline (in seconds) to wait for git '
nodir@chromium.org5b48e482016-03-18 20:27:54 +00001858 'cache lock to become available. Default is %default.')
agabled437d762016-10-17 09:35:11 -07001859 # TODO(agable): Remove these when the oldest CrOS release milestone is M56.
1860 parser.add_option('-t', '--transitive', action='store_true',
1861 help='DEPRECATED: This is a no-op.')
sdefresne69b1be12016-10-18 05:48:02 -07001862 parser.add_option('-m', '--manually_grab_svn_rev', action='store_true',
agabled437d762016-10-17 09:35:11 -07001863 help='DEPRECATED: This is a no-op.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001864 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001865 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001866
1867 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001868 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001869
smutae7ea312016-07-18 11:59:41 -07001870 if options.revisions and options.head:
1871 # TODO(maruel): Make it a parser.error if it doesn't break any builder.
1872 print('Warning: you cannot use both --head and --revision')
1873
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001874 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00001875 client.PrintLocationAndContents()
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00001876 ret = client.RunOnDeps('update', args)
1877 if options.output_json:
1878 slns = {}
1879 for d in client.subtree(True):
1880 normed = d.name.replace('\\', '/').rstrip('/') + '/'
1881 slns[normed] = {
1882 'revision': d.got_revision,
1883 'scm': d.used_scm.name if d.used_scm else None,
hinoka@chromium.org17db9052014-05-10 01:11:29 +00001884 'url': str(d.url) if d.url else None,
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00001885 }
1886 with open(options.output_json, 'wb') as f:
1887 json.dump({'solutions': slns}, f)
1888 return ret
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001889
1890
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001891CMDupdate = CMDsync
1892
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001893
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001894def CMDdiff(parser, args):
1895 """Displays local diff for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001896 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1897 help='override deps for the specified (comma-separated) '
1898 'platform(s); \'all\' will process all deps_os '
1899 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001900 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001901 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001902 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001903 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001904 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00001905 client.PrintLocationAndContents()
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001906 return client.RunOnDeps('diff', args)
1907
1908
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001909def CMDrevert(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001910 """Reverts all modifications in every dependencies.
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001911
1912 That's the nuclear option to get back to a 'clean' state. It removes anything
agabled437d762016-10-17 09:35:11 -07001913 that shows up in git status."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001914 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1915 help='override deps for the specified (comma-separated) '
1916 'platform(s); \'all\' will process all deps_os '
1917 'references')
1918 parser.add_option('-n', '--nohooks', action='store_true',
1919 help='don\'t run hooks after the revert is complete')
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001920 parser.add_option('-p', '--noprehooks', action='store_true',
1921 help='don\'t run pre-DEPS hooks', default=False)
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +00001922 parser.add_option('--upstream', action='store_true',
1923 help='Make repo state match upstream branch.')
iannucci@chromium.orgbf525dc2016-04-07 22:00:28 +00001924 parser.add_option('--break_repo_locks', action='store_true',
1925 help='GIT ONLY - Forcibly remove repo locks (e.g. '
1926 'index.lock). This should only be used if you know for '
1927 'certain that this invocation of gclient is the only '
1928 'thing operating on the git repos (e.g. on a bot).')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001929 (options, args) = parser.parse_args(args)
1930 # --force is implied.
1931 options.force = True
steveblock@chromium.org98e69452012-02-16 16:36:43 +00001932 options.reset = False
1933 options.delete_unversioned_trees = False
agablec903d732016-07-26 09:07:24 -07001934 options.merge = False
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001935 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001936 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001937 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001938 return client.RunOnDeps('revert', args)
1939
1940
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001941def CMDrunhooks(parser, args):
1942 """Runs hooks for files that have been modified in the local working copy."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001943 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1944 help='override deps for the specified (comma-separated) '
1945 'platform(s); \'all\' will process all deps_os '
1946 'references')
1947 parser.add_option('-f', '--force', action='store_true', default=True,
1948 help='Deprecated. No effect.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001949 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001950 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001951 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001952 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001953 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00001954 client.PrintLocationAndContents()
maruel@chromium.org5df6a462009-08-28 18:52:26 +00001955 options.force = True
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001956 options.nohooks = False
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001957 return client.RunOnDeps('runhooks', args)
1958
1959
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001960def CMDrevinfo(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001961 """Outputs revision info mapping for the client and its dependencies.
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001962
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001963 This allows the capture of an overall 'revision' for the source tree that
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001964 can be used to reproduce the same tree in the future. It is only useful for
agabled437d762016-10-17 09:35:11 -07001965 'unpinned dependencies', i.e. DEPS/deps references without a git hash.
1966 A git branch name isn't 'pinned' since the actual commit can change.
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001967 """
1968 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1969 help='override deps for the specified (comma-separated) '
1970 'platform(s); \'all\' will process all deps_os '
1971 'references')
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001972 parser.add_option('-a', '--actual', action='store_true',
1973 help='gets the actual checked out revisions instead of the '
1974 'ones specified in the DEPS and .gclient files')
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001975 parser.add_option('-s', '--snapshot', action='store_true',
1976 help='creates a snapshot .gclient file of the current '
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001977 'version of all repositories to reproduce the tree, '
1978 'implies -a')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001979 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001980 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001981 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001982 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001983 client.PrintRevInfo()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001984 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001985
1986
szager@google.comb9a78d32012-03-13 18:46:21 +00001987def CMDhookinfo(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001988 """Outputs the hooks that would be run by `gclient runhooks`."""
szager@google.comb9a78d32012-03-13 18:46:21 +00001989 (options, args) = parser.parse_args(args)
1990 options.force = True
1991 client = GClient.LoadCurrentConfig(options)
1992 if not client:
1993 raise gclient_utils.Error('client not configured; see \'gclient config\'')
1994 client.RunOnDeps(None, [])
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001995 print('; '.join(' '.join(hook) for hook in client.GetHooks(options)))
szager@google.comb9a78d32012-03-13 18:46:21 +00001996 return 0
1997
1998
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00001999def CMDverify(parser, args):
2000 """Verifies the DEPS file deps are only from allowed_hosts."""
2001 (options, args) = parser.parse_args(args)
2002 client = GClient.LoadCurrentConfig(options)
2003 if not client:
2004 raise gclient_utils.Error('client not configured; see \'gclient config\'')
2005 client.RunOnDeps(None, [])
2006 # Look at each first-level dependency of this gclient only.
2007 for dep in client.dependencies:
2008 bad_deps = dep.findDepsFromNotAllowedHosts()
2009 if not bad_deps:
2010 continue
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002011 print("There are deps from not allowed hosts in file %s" % dep.deps_file)
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00002012 for bad_dep in bad_deps:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002013 print("\t%s at %s" % (bad_dep.name, bad_dep.url))
2014 print("allowed_hosts:", ', '.join(dep.allowed_hosts))
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00002015 sys.stdout.flush()
2016 raise gclient_utils.Error(
2017 'dependencies from disallowed hosts; check your DEPS file.')
2018 return 0
2019
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002020class OptionParser(optparse.OptionParser):
szager@chromium.orge2e03202012-07-31 18:05:16 +00002021 gclientfile_default = os.environ.get('GCLIENT_FILE', '.gclient')
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002022
2023 def __init__(self, **kwargs):
2024 optparse.OptionParser.__init__(
2025 self, version='%prog ' + __version__, **kwargs)
2026
2027 # Some arm boards have issues with parallel sync.
2028 if platform.machine().startswith('arm'):
2029 jobs = 1
2030 else:
2031 jobs = max(8, gclient_utils.NumLocalCpus())
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002032
2033 self.add_option(
2034 '-j', '--jobs', default=jobs, type='int',
2035 help='Specify how many SCM commands can run in parallel; defaults to '
tnagel@chromium.orga2aaa632014-02-28 21:47:27 +00002036 '%default on this machine')
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002037 self.add_option(
2038 '-v', '--verbose', action='count', default=0,
2039 help='Produces additional output for diagnostics. Can be used up to '
2040 'three times for more logging info.')
2041 self.add_option(
2042 '--gclientfile', dest='config_filename',
2043 help='Specify an alternate %s file' % self.gclientfile_default)
2044 self.add_option(
2045 '--spec',
2046 help='create a gclient file containing the provided string. Due to '
2047 'Cygwin/Python brokenness, it can\'t contain any newlines.')
2048 self.add_option(
2049 '--no-nag-max', default=False, action='store_true',
scottmg@chromium.orgf547c802013-09-27 17:55:26 +00002050 help='Ignored for backwards compatibility.')
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002051
2052 def parse_args(self, args=None, values=None):
2053 """Integrates standard options processing."""
2054 options, args = optparse.OptionParser.parse_args(self, args, values)
2055 levels = [logging.ERROR, logging.WARNING, logging.INFO, logging.DEBUG]
2056 logging.basicConfig(
2057 level=levels[min(options.verbose, len(levels) - 1)],
maruel@chromium.org0895b752011-08-26 20:40:33 +00002058 format='%(module)s(%(lineno)d) %(funcName)s:%(message)s')
szager@chromium.orge2e03202012-07-31 18:05:16 +00002059 if options.config_filename and options.spec:
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002060 self.error('Cannot specifiy both --gclientfile and --spec')
rdsmith@chromium.orgd9591f02014-02-05 19:28:20 +00002061 if (options.config_filename and
2062 options.config_filename != os.path.basename(options.config_filename)):
2063 self.error('--gclientfile target must be a filename, not a path')
szager@chromium.orge2e03202012-07-31 18:05:16 +00002064 if not options.config_filename:
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002065 options.config_filename = self.gclientfile_default
maruel@chromium.org0895b752011-08-26 20:40:33 +00002066 options.entries_filename = options.config_filename + '_entries'
2067 if options.jobs < 1:
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002068 self.error('--jobs must be 1 or higher')
maruel@chromium.org0895b752011-08-26 20:40:33 +00002069
2070 # These hacks need to die.
2071 if not hasattr(options, 'revisions'):
2072 # GClient.RunOnDeps expects it even if not applicable.
2073 options.revisions = []
smutae7ea312016-07-18 11:59:41 -07002074 if not hasattr(options, 'head'):
2075 options.head = None
maruel@chromium.org0895b752011-08-26 20:40:33 +00002076 if not hasattr(options, 'nohooks'):
2077 options.nohooks = True
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00002078 if not hasattr(options, 'noprehooks'):
2079 options.noprehooks = True
maruel@chromium.org0895b752011-08-26 20:40:33 +00002080 if not hasattr(options, 'deps_os'):
2081 options.deps_os = None
maruel@chromium.org0895b752011-08-26 20:40:33 +00002082 if not hasattr(options, 'force'):
2083 options.force = None
2084 return (options, args)
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002085
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002086
2087def disable_buffering():
2088 # Make stdout auto-flush so buildbot doesn't kill us during lengthy
2089 # operations. Python as a strong tendency to buffer sys.stdout.
2090 sys.stdout = gclient_utils.MakeFileAutoFlush(sys.stdout)
2091 # Make stdout annotated with the thread ids.
2092 sys.stdout = gclient_utils.MakeFileAnnotated(sys.stdout)
maruel@chromium.org0895b752011-08-26 20:40:33 +00002093
2094
sbc@chromium.org013731e2015-02-26 18:28:43 +00002095def main(argv):
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002096 """Doesn't parse the arguments here, just find the right subcommand to
2097 execute."""
maruel@chromium.org82798cb2012-02-23 18:16:12 +00002098 if sys.hexversion < 0x02060000:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002099 print(
maruel@chromium.org82798cb2012-02-23 18:16:12 +00002100 '\nYour python version %s is unsupported, please upgrade.\n' %
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002101 sys.version.split(' ', 1)[0],
2102 file=sys.stderr)
maruel@chromium.org82798cb2012-02-23 18:16:12 +00002103 return 2
bcwhite@chromium.org6683ab42013-02-11 16:13:47 +00002104 if not sys.executable:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002105 print(
2106 '\nPython cannot find the location of it\'s own executable.\n',
2107 file=sys.stderr)
bcwhite@chromium.org6683ab42013-02-11 16:13:47 +00002108 return 2
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002109 fix_encoding.fix_encoding()
2110 disable_buffering()
iannucci@chromium.org596cd5c2016-04-04 21:34:39 +00002111 setup_color.init()
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002112 dispatcher = subcommand.CommandDispatcher(__name__)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00002113 try:
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002114 return dispatcher.execute(OptionParser(), argv)
xusydoc@chromium.org2fd6c3f2013-05-03 21:57:55 +00002115 except KeyboardInterrupt:
2116 gclient_utils.GClientChildren.KillAllRemainingChildren()
2117 raise
vapier@chromium.orga81a56e2015-11-11 07:56:13 +00002118 except (gclient_utils.Error, subprocess2.CalledProcessError) as e:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002119 print('Error: %s' % str(e), file=sys.stderr)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00002120 return 1
borenet@google.com6a9b1682014-03-24 18:35:23 +00002121 finally:
2122 gclient_utils.PrintWarnings()
sbc@chromium.org013731e2015-02-26 18:28:43 +00002123 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002124
2125
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00002126if '__main__' == __name__:
sbc@chromium.org013731e2015-02-26 18:28:43 +00002127 try:
2128 sys.exit(main(sys.argv[1:]))
2129 except KeyboardInterrupt:
2130 sys.stderr.write('interrupted\n')
2131 sys.exit(1)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002132
2133# vim: ts=2:sw=2:tw=80:et: