blob: 1270e03e6aeee5d0cc6566bf4aacb73ed353f282 [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
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +020099import gclient_eval
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000100import gclient_scm
101import gclient_utils
szager@chromium.org848fd492014-04-09 19:06:44 +0000102import git_cache
nasser@codeaurora.org1f7a3d12010-02-04 15:11:50 +0000103from third_party.repo.progress import Progress
maruel@chromium.org39c0b222013-08-17 16:57:01 +0000104import subcommand
maruel@chromium.org31cb48a2011-04-04 18:01:36 +0000105import subprocess2
iannucci@chromium.org596cd5c2016-04-04 21:34:39 +0000106import setup_color
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000107
szager@chromium.org7b8b6de2014-08-23 00:57:31 +0000108CHROMIUM_SRC_URL = 'https://chromium.googlesource.com/chromium/src.git'
109
110
111def ast_dict_index(dnode, key):
112 """Search an ast.Dict for the argument key, and return its index."""
113 idx = [i for i in range(len(dnode.keys)) if (
114 type(dnode.keys[i]) is ast.Str and dnode.keys[i].s == key)]
115 if not idx:
116 return -1
117 elif len(idx) > 1:
118 raise gclient_utils.Error('Multiple dict entries with same key in AST')
119 return idx[-1]
120
121def ast2str(node, indent=0):
122 """Return a pretty-printed rendition of an ast.Node."""
123 t = type(node)
124 if t is ast.Module:
125 return '\n'.join([ast2str(x, indent) for x in node.body])
126 elif t is ast.Assign:
127 return ((' ' * indent) +
128 ' = '.join([ast2str(x) for x in node.targets] +
129 [ast2str(node.value, indent)]) + '\n')
130 elif t is ast.Name:
131 return node.id
132 elif t is ast.List:
133 if not node.elts:
134 return '[]'
135 elif len(node.elts) == 1:
136 return '[' + ast2str(node.elts[0], indent) + ']'
137 return ('[\n' + (' ' * (indent + 1)) +
138 (',\n' + (' ' * (indent + 1))).join(
139 [ast2str(x, indent + 1) for x in node.elts]) +
140 '\n' + (' ' * indent) + ']')
141 elif t is ast.Dict:
142 if not node.keys:
143 return '{}'
144 elif len(node.keys) == 1:
145 return '{%s: %s}' % (ast2str(node.keys[0]),
146 ast2str(node.values[0], indent + 1))
147 return ('{\n' + (' ' * (indent + 1)) +
148 (',\n' + (' ' * (indent + 1))).join(
149 ['%s: %s' % (ast2str(node.keys[i]),
150 ast2str(node.values[i], indent + 1))
151 for i in range(len(node.keys))]) +
152 '\n' + (' ' * indent) + '}')
153 elif t is ast.Str:
154 return "'%s'" % node.s
155 else:
156 raise gclient_utils.Error("Unexpected AST node at line %d, column %d: %s"
157 % (node.lineno, node.col_offset, t))
158
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000159
maruel@chromium.org116704f2010-06-11 17:34:38 +0000160class GClientKeywords(object):
161 class FromImpl(object):
162 """Used to implement the From() syntax."""
163
164 def __init__(self, module_name, sub_target_name=None):
165 """module_name is the dep module we want to include from. It can also be
166 the name of a subdirectory to include from.
167
168 sub_target_name is an optional parameter if the module name in the other
169 DEPS file is different. E.g., you might want to map src/net to net."""
170 self.module_name = module_name
171 self.sub_target_name = sub_target_name
172
173 def __str__(self):
174 return 'From(%s, %s)' % (repr(self.module_name),
175 repr(self.sub_target_name))
176
maruel@chromium.org116704f2010-06-11 17:34:38 +0000177 class VarImpl(object):
178 def __init__(self, custom_vars, local_scope):
179 self._custom_vars = custom_vars
180 self._local_scope = local_scope
181
182 def Lookup(self, var_name):
183 """Implements the Var syntax."""
184 if var_name in self._custom_vars:
185 return self._custom_vars[var_name]
186 elif var_name in self._local_scope.get("vars", {}):
187 return self._local_scope["vars"][var_name]
188 raise gclient_utils.Error("Var is not defined: %s" % var_name)
189
190
maruel@chromium.org064186c2011-09-27 23:53:33 +0000191class DependencySettings(GClientKeywords):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000192 """Immutable configuration settings."""
193 def __init__(
agablea98a6cd2016-11-15 14:30:10 -0800194 self, parent, url, managed, custom_deps, custom_vars,
agabledce6ddc2016-09-08 10:02:16 -0700195 custom_hooks, deps_file, should_process, relative):
maruel@chromium.org064186c2011-09-27 23:53:33 +0000196 GClientKeywords.__init__(self)
197
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000198 # These are not mutable:
199 self._parent = parent
mmoss@chromium.org8f93f792014-08-26 23:24:09 +0000200 self._deps_file = deps_file
maruel@chromium.org064186c2011-09-27 23:53:33 +0000201 self._url = url
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000202 # 'managed' determines whether or not this dependency is synced/updated by
203 # gclient after gclient checks it out initially. The difference between
204 # 'managed' and 'should_process' is that the user specifies 'managed' via
smutae7ea312016-07-18 11:59:41 -0700205 # the --unmanaged command-line flag or a .gclient config, where
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000206 # 'should_process' is dynamically set by gclient if it goes over its
207 # recursion limit and controls gclient's behavior so it does not misbehave.
208 self._managed = managed
209 self._should_process = should_process
agabledce6ddc2016-09-08 10:02:16 -0700210 # If this is a recursed-upon sub-dependency, and the parent has
211 # use_relative_paths set, then this dependency should check out its own
212 # dependencies relative to that parent's path for this, rather than
213 # relative to the .gclient file.
214 self._relative = relative
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +0000215 # This is a mutable value which has the list of 'target_os' OSes listed in
216 # the current deps file.
217 self.local_target_os = None
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000218
219 # These are only set in .gclient and not in DEPS files.
220 self._custom_vars = custom_vars or {}
221 self._custom_deps = custom_deps or {}
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000222 self._custom_hooks = custom_hooks or []
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000223
maruel@chromium.org064186c2011-09-27 23:53:33 +0000224 # Post process the url to remove trailing slashes.
225 if isinstance(self._url, basestring):
226 # urls are sometime incorrectly written as proto://host/path/@rev. Replace
227 # it to proto://host/path@rev.
maruel@chromium.org064186c2011-09-27 23:53:33 +0000228 self._url = self._url.replace('/@', '@')
agabled437d762016-10-17 09:35:11 -0700229 elif not isinstance(self._url, (self.FromImpl, None.__class__)):
maruel@chromium.org064186c2011-09-27 23:53:33 +0000230 raise gclient_utils.Error(
231 ('dependency url must be either a string, None, '
agabled437d762016-10-17 09:35:11 -0700232 'or From() instead of %s') % self._url.__class__.__name__)
mmoss@chromium.orgd0b272b2013-01-30 23:55:33 +0000233 # Make any deps_file path platform-appropriate.
234 for sep in ['/', '\\']:
235 self._deps_file = self._deps_file.replace(sep, os.sep)
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000236
237 @property
238 def deps_file(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000239 return self._deps_file
240
241 @property
242 def managed(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000243 return self._managed
244
245 @property
246 def parent(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000247 return self._parent
248
249 @property
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000250 def root(self):
251 """Returns the root node, a GClient object."""
252 if not self.parent:
253 # This line is to signal pylint that it could be a GClient instance.
254 return self or GClient(None, None)
255 return self.parent.root
256
257 @property
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000258 def should_process(self):
259 """True if this dependency should be processed, i.e. checked out."""
260 return self._should_process
261
262 @property
263 def custom_vars(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000264 return self._custom_vars.copy()
265
266 @property
267 def custom_deps(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000268 return self._custom_deps.copy()
269
maruel@chromium.org064186c2011-09-27 23:53:33 +0000270 @property
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000271 def custom_hooks(self):
272 return self._custom_hooks[:]
273
274 @property
maruel@chromium.org064186c2011-09-27 23:53:33 +0000275 def url(self):
276 return self._url
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000277
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000278 @property
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +0000279 def target_os(self):
280 if self.local_target_os is not None:
281 return tuple(set(self.local_target_os).union(self.parent.target_os))
282 else:
283 return self.parent.target_os
284
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000285 def get_custom_deps(self, name, url):
286 """Returns a custom deps if applicable."""
287 if self.parent:
288 url = self.parent.get_custom_deps(name, url)
289 # None is a valid return value to disable a dependency.
290 return self.custom_deps.get(name, url)
291
maruel@chromium.org064186c2011-09-27 23:53:33 +0000292
293class Dependency(gclient_utils.WorkItem, DependencySettings):
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000294 """Object that represents a dependency checkout."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000295
agablea98a6cd2016-11-15 14:30:10 -0800296 def __init__(self, parent, name, url, managed, custom_deps,
agabledce6ddc2016-09-08 10:02:16 -0700297 custom_vars, custom_hooks, deps_file, should_process,
298 relative):
maruel@chromium.org6ca8bf82011-09-19 23:04:30 +0000299 gclient_utils.WorkItem.__init__(self, name)
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000300 DependencySettings.__init__(
agablea98a6cd2016-11-15 14:30:10 -0800301 self, parent, url, managed, custom_deps, custom_vars,
agabledce6ddc2016-09-08 10:02:16 -0700302 custom_hooks, deps_file, should_process, relative)
maruel@chromium.org68988972011-09-20 14:11:42 +0000303
304 # This is in both .gclient and DEPS files:
maruel@chromium.org064186c2011-09-27 23:53:33 +0000305 self._deps_hooks = []
maruel@chromium.org68988972011-09-20 14:11:42 +0000306
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000307 self._pre_deps_hooks = []
308
maruel@chromium.org68988972011-09-20 14:11:42 +0000309 # Calculates properties:
maruel@chromium.org064186c2011-09-27 23:53:33 +0000310 self._parsed_url = None
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +0000311 self._dependencies = []
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000312 # A cache of the files affected by the current operation, necessary for
313 # hooks.
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000314 self._file_list = []
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000315 # List of host names from which dependencies are allowed.
316 # Default is an empty set, meaning unspecified in DEPS file, and hence all
317 # hosts will be allowed. Non-empty set means whitelist of hosts.
318 # allowed_hosts var is scoped to its DEPS file, and so it isn't recursive.
319 self._allowed_hosts = frozenset()
maruel@chromium.org85c2a192010-07-22 21:14:43 +0000320 # If it is not set to True, the dependency wasn't processed for its child
321 # dependency, i.e. its DEPS wasn't read.
maruel@chromium.org064186c2011-09-27 23:53:33 +0000322 self._deps_parsed = False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000323 # This dependency has been processed, i.e. checked out
maruel@chromium.org064186c2011-09-27 23:53:33 +0000324 self._processed = False
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000325 # This dependency had its pre-DEPS hooks run
326 self._pre_deps_hooks_ran = False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000327 # This dependency had its hook run
maruel@chromium.org064186c2011-09-27 23:53:33 +0000328 self._hooks_ran = False
kustermann@google.coma692e8f2013-04-18 08:32:04 +0000329 # This is the scm used to checkout self.url. It may be used by dependencies
330 # to get the datetime of the revision we checked out.
331 self._used_scm = None
szager@chromium.org4ad264b2014-05-20 04:43:47 +0000332 self._used_revision = None
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +0000333 # The actual revision we ended up getting, or None if that information is
334 # unavailable
335 self._got_revision = None
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000336
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000337 # This is a mutable value that overrides the normal recursion limit for this
338 # dependency. It is read from the actual DEPS file so cannot be set on
339 # class instantiation.
340 self.recursion_override = None
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000341 # recursedeps is a mutable value that selectively overrides the default
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000342 # 'no recursion' setting on a dep-by-dep basis. It will replace
343 # recursion_override.
iannucci@chromium.orgafa11ac2016-05-04 22:17:34 +0000344 #
345 # It will be a dictionary of {deps_name: {"deps_file": depfile_name}} or
346 # None.
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000347 self.recursedeps = None
hinoka885e5b12016-06-08 14:40:09 -0700348 # This is inherited from WorkItem. We want the URL to be a resource.
349 if url and isinstance(url, basestring):
350 # The url is usually given to gclient either as https://blah@123
qyearsley12fa6ff2016-08-24 09:18:40 -0700351 # or just https://blah. The @123 portion is irrelevant.
hinoka885e5b12016-06-08 14:40:09 -0700352 self.resources.append(url.split('@')[0])
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000353
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000354 if not self.name and self.parent:
355 raise gclient_utils.Error('Dependency without name')
356
maruel@chromium.org470b5432011-10-11 18:18:19 +0000357 @property
358 def requirements(self):
359 """Calculate the list of requirements."""
360 requirements = set()
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000361 # self.parent is implicitly a requirement. This will be recursive by
362 # definition.
363 if self.parent and self.parent.name:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000364 requirements.add(self.parent.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000365
366 # For a tree with at least 2 levels*, the leaf node needs to depend
367 # on the level higher up in an orderly way.
368 # This becomes messy for >2 depth as the DEPS file format is a dictionary,
369 # thus unsorted, while the .gclient format is a list thus sorted.
370 #
371 # * _recursion_limit is hard coded 2 and there is no hope to change this
372 # value.
373 #
374 # Interestingly enough, the following condition only works in the case we
375 # want: self is a 2nd level node. 3nd level node wouldn't need this since
376 # they already have their parent as a requirement.
maruel@chromium.org470b5432011-10-11 18:18:19 +0000377 if self.parent and self.parent.parent and not self.parent.parent.parent:
378 requirements |= set(i.name for i in self.root.dependencies if i.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000379
380 if isinstance(self.url, self.FromImpl):
maruel@chromium.org470b5432011-10-11 18:18:19 +0000381 requirements.add(self.url.module_name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000382
maruel@chromium.org470b5432011-10-11 18:18:19 +0000383 if self.name:
384 requirements |= set(
385 obj.name for obj in self.root.subtree(False)
386 if (obj is not self
387 and obj.name and
388 self.name.startswith(posixpath.join(obj.name, ''))))
389 requirements = tuple(sorted(requirements))
390 logging.info('Dependency(%s).requirements = %s' % (self.name, requirements))
391 return requirements
392
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000393 @property
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000394 def try_recursedeps(self):
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000395 """Returns False if recursion_override is ever specified."""
396 if self.recursion_override is not None:
397 return False
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000398 return self.parent.try_recursedeps
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000399
400 @property
401 def recursion_limit(self):
402 """Returns > 0 if this dependency is not too recursed to be processed."""
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000403 # We continue to support the absence of recursedeps until tools and DEPS
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000404 # using recursion_override are updated.
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000405 if self.try_recursedeps and self.parent.recursedeps != None:
406 if self.name in self.parent.recursedeps:
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000407 return 1
408
409 if self.recursion_override is not None:
410 return self.recursion_override
411 return max(self.parent.recursion_limit - 1, 0)
412
maruel@chromium.org470b5432011-10-11 18:18:19 +0000413 def verify_validity(self):
414 """Verifies that this Dependency is fine to add as a child of another one.
415
416 Returns True if this entry should be added, False if it is a duplicate of
417 another entry.
418 """
419 logging.info('Dependency(%s).verify_validity()' % self.name)
420 if self.name in [s.name for s in self.parent.dependencies]:
421 raise gclient_utils.Error(
422 'The same name "%s" appears multiple times in the deps section' %
423 self.name)
424 if not self.should_process:
425 # Return early, no need to set requirements.
426 return True
427
428 # This require a full tree traversal with locks.
429 siblings = [d for d in self.root.subtree(False) if d.name == self.name]
430 for sibling in siblings:
maruel@chromium.orgb848d5b2012-10-10 23:25:50 +0000431 self_url = self.LateOverride(self.url)
432 sibling_url = sibling.LateOverride(sibling.url)
433 # Allow to have only one to be None or ''.
434 if self_url != sibling_url and bool(self_url) == bool(sibling_url):
maruel@chromium.org470b5432011-10-11 18:18:19 +0000435 raise gclient_utils.Error(
maruel@chromium.orgb848d5b2012-10-10 23:25:50 +0000436 ('Dependency %s specified more than once:\n'
437 ' %s [%s]\n'
438 'vs\n'
439 ' %s [%s]') % (
440 self.name,
441 sibling.hierarchy(),
442 sibling_url,
443 self.hierarchy(),
444 self_url))
maruel@chromium.org470b5432011-10-11 18:18:19 +0000445 # In theory we could keep it as a shadow of the other one. In
446 # practice, simply ignore it.
447 logging.warn('Won\'t process duplicate dependency %s' % sibling)
448 return False
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000449 return True
maruel@chromium.org064186c2011-09-27 23:53:33 +0000450
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000451 def LateOverride(self, url):
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000452 """Resolves the parsed url from url.
453
454 Manages From() keyword accordingly. Do not touch self.parsed_url nor
455 self.url because it may called with other urls due to From()."""
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000456 assert self.parsed_url == None or not self.should_process, self.parsed_url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000457 parsed_url = self.get_custom_deps(self.name, url)
458 if parsed_url != url:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000459 logging.info(
460 'Dependency(%s).LateOverride(%s) -> %s' %
461 (self.name, url, parsed_url))
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000462 return parsed_url
463
464 if isinstance(url, self.FromImpl):
maruel@chromium.org470b5432011-10-11 18:18:19 +0000465 # Requires tree traversal.
maruel@chromium.org68988972011-09-20 14:11:42 +0000466 ref = [
467 dep for dep in self.root.subtree(True) if url.module_name == dep.name
468 ]
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000469 if not ref:
470 raise gclient_utils.Error('Failed to find one reference to %s. %s' % (
471 url.module_name, ref))
472 # It may happen that len(ref) > 1 but it's no big deal.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000473 ref = ref[0]
maruel@chromium.org98d05fa2010-07-22 21:58:01 +0000474 sub_target = url.sub_target_name or self.name
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000475 found_deps = [d for d in ref.dependencies if d.name == sub_target]
476 if len(found_deps) != 1:
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000477 raise gclient_utils.Error(
maruel@chromium.org98023df2011-09-07 18:44:47 +0000478 'Couldn\'t find %s in %s, referenced by %s (parent: %s)\n%s' % (
479 sub_target, ref.name, self.name, self.parent.name,
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000480 str(self.root)))
maruel@chromium.org98023df2011-09-07 18:44:47 +0000481
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000482 # Call LateOverride() again.
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000483 found_dep = found_deps[0]
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000484 parsed_url = found_dep.LateOverride(found_dep.url)
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000485 logging.info(
maruel@chromium.org470b5432011-10-11 18:18:19 +0000486 'Dependency(%s).LateOverride(%s) -> %s (From)' %
487 (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000488 return parsed_url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000489
490 if isinstance(url, basestring):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000491 parsed_url = urlparse.urlparse(url)
scr@chromium.orgf1eccaf2014-04-11 15:51:33 +0000492 if (not parsed_url[0] and
493 not re.match(r'^\w+\@[\w\.-]+\:[\w\/]+', parsed_url[2])):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000494 # A relative url. Fetch the real base.
495 path = parsed_url[2]
496 if not path.startswith('/'):
497 raise gclient_utils.Error(
498 'relative DEPS entry \'%s\' must begin with a slash' % url)
499 # Create a scm just to query the full url.
500 parent_url = self.parent.parsed_url
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000501 scm = gclient_scm.CreateSCM(
502 parent_url, self.root.root_dir, None, self.outbuf)
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000503 parsed_url = scm.FullUrlForRelativeUrl(url)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000504 else:
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000505 parsed_url = url
maruel@chromium.org470b5432011-10-11 18:18:19 +0000506 logging.info(
507 'Dependency(%s).LateOverride(%s) -> %s' %
508 (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000509 return parsed_url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000510
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000511 if url is None:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000512 logging.info(
513 'Dependency(%s).LateOverride(%s) -> %s' % (self.name, url, url))
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000514 return url
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000515
516 raise gclient_utils.Error('Unknown url type')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000517
bratell@opera.comed2b4fe2013-12-16 14:34:12 +0000518 @staticmethod
519 def MergeWithOsDeps(deps, deps_os, target_os_list):
520 """Returns a new "deps" structure that is the deps sent in updated
521 with information from deps_os (the deps_os section of the DEPS
522 file) that matches the list of target os."""
523 os_overrides = {}
524 for the_target_os in target_os_list:
525 the_target_os_deps = deps_os.get(the_target_os, {})
526 for os_dep_key, os_dep_value in the_target_os_deps.iteritems():
527 overrides = os_overrides.setdefault(os_dep_key, [])
528 overrides.append((the_target_os, os_dep_value))
529
530 # If any os didn't specify a value (we have fewer value entries
531 # than in the os list), then it wants to use the default value.
532 for os_dep_key, os_dep_value in os_overrides.iteritems():
533 if len(os_dep_value) != len(target_os_list):
qyearsley12fa6ff2016-08-24 09:18:40 -0700534 # Record the default value too so that we don't accidentally
bratell@opera.comed2b4fe2013-12-16 14:34:12 +0000535 # set it to None or miss a conflicting DEPS.
536 if os_dep_key in deps:
537 os_dep_value.append(('default', deps[os_dep_key]))
538
539 target_os_deps = {}
540 for os_dep_key, os_dep_value in os_overrides.iteritems():
541 # os_dep_value is a list of (os, value) pairs.
542 possible_values = set(x[1] for x in os_dep_value if x[1] is not None)
543 if not possible_values:
544 target_os_deps[os_dep_key] = None
545 else:
546 if len(possible_values) > 1:
547 # It would be possible to abort here but it would be
548 # unfortunate if we end up preventing any kind of checkout.
549 logging.error('Conflicting dependencies for %s: %s. (target_os=%s)',
550 os_dep_key, os_dep_value, target_os_list)
551 # Sorting to get the same result every time in case of conflicts.
552 target_os_deps[os_dep_key] = sorted(possible_values)[0]
553
554 new_deps = deps.copy()
555 new_deps.update(target_os_deps)
556 return new_deps
557
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000558 def ParseDepsFile(self):
maruel@chromium.org271375b2010-06-23 19:17:38 +0000559 """Parses the DEPS file for this dependency."""
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000560 assert not self.deps_parsed
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000561 assert not self.dependencies
iannucci@chromium.org0c3f3052014-05-22 00:29:22 +0000562
563 deps_content = None
564 use_strict = False
cmp@chromium.org76ce73c2014-07-02 00:13:18 +0000565
566 # First try to locate the configured deps file. If it's missing, fallback
567 # to DEPS.
568 deps_files = [self.deps_file]
569 if 'DEPS' not in deps_files:
570 deps_files.append('DEPS')
571 for deps_file in deps_files:
572 filepath = os.path.join(self.root.root_dir, self.name, deps_file)
573 if os.path.isfile(filepath):
574 logging.info(
575 'ParseDepsFile(%s): %s file found at %s', self.name, deps_file,
576 filepath)
577 break
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000578 logging.info(
cmp@chromium.org76ce73c2014-07-02 00:13:18 +0000579 'ParseDepsFile(%s): No %s file found at %s', self.name, deps_file,
580 filepath)
581
582 if os.path.isfile(filepath):
maruel@chromium.org46304292010-10-28 11:42:00 +0000583 deps_content = gclient_utils.FileRead(filepath)
cmp@chromium.org76ce73c2014-07-02 00:13:18 +0000584 logging.debug('ParseDepsFile(%s) read:\n%s', self.name, deps_content)
iannucci@chromium.org0c3f3052014-05-22 00:29:22 +0000585 use_strict = 'use strict' in deps_content.splitlines()[0]
586
587 local_scope = {}
588 if deps_content:
589 # One thing is unintuitive, vars = {} must happen before Var() use.
590 var = self.VarImpl(self.custom_vars, local_scope)
591 if use_strict:
592 logging.info(
593 'ParseDepsFile(%s): Strict Mode Enabled', self.name)
594 global_scope = {
595 '__builtins__': {'None': None},
596 'Var': var.Lookup,
597 'deps_os': {},
598 }
599 else:
600 global_scope = {
iannucci@chromium.org0c3f3052014-05-22 00:29:22 +0000601 'From': self.FromImpl,
602 'Var': var.Lookup,
603 'deps_os': {},
604 }
maruel@chromium.org46304292010-10-28 11:42:00 +0000605 # Eval the content.
606 try:
607 exec(deps_content, global_scope, local_scope)
vapier@chromium.orga81a56e2015-11-11 07:56:13 +0000608 except SyntaxError as e:
maruel@chromium.org46304292010-10-28 11:42:00 +0000609 gclient_utils.SyntaxErrorToError(filepath, e)
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +0200610 if self._get_option('validate_syntax', False):
611 gclient_eval.Check(deps_content, filepath, global_scope, local_scope)
iannucci@chromium.org0c3f3052014-05-22 00:29:22 +0000612 if use_strict:
613 for key, val in local_scope.iteritems():
614 if not isinstance(val, (dict, list, tuple, str)):
615 raise gclient_utils.Error(
616 'ParseDepsFile(%s): Strict mode disallows %r -> %r' %
617 (self.name, key, val))
618
maruel@chromium.org271375b2010-06-23 19:17:38 +0000619 deps = local_scope.get('deps', {})
ilevy@chromium.org27ca3a92012-10-17 18:11:02 +0000620 if 'recursion' in local_scope:
621 self.recursion_override = local_scope.get('recursion')
622 logging.warning(
623 'Setting %s recursion to %d.', self.name, self.recursion_limit)
iannucci@chromium.orgafa11ac2016-05-04 22:17:34 +0000624 self.recursedeps = None
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000625 if 'recursedeps' in local_scope:
iannucci@chromium.orgafa11ac2016-05-04 22:17:34 +0000626 self.recursedeps = {}
627 for ent in local_scope['recursedeps']:
628 if isinstance(ent, basestring):
629 self.recursedeps[ent] = {"deps_file": self.deps_file}
630 else: # (depname, depsfilename)
631 self.recursedeps[ent[0]] = {"deps_file": ent[1]}
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000632 logging.warning('Found recursedeps %r.', repr(self.recursedeps))
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +0000633 # If present, save 'target_os' in the local_target_os property.
634 if 'target_os' in local_scope:
635 self.local_target_os = local_scope['target_os']
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000636 # load os specific dependencies if defined. these dependencies may
637 # override or extend the values defined by the 'deps' member.
bratell@opera.comed2b4fe2013-12-16 14:34:12 +0000638 target_os_list = self.target_os
639 if 'deps_os' in local_scope and target_os_list:
640 deps = self.MergeWithOsDeps(deps, local_scope['deps_os'], target_os_list)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000641
maruel@chromium.org271375b2010-06-23 19:17:38 +0000642 # If a line is in custom_deps, but not in the solution, we want to append
643 # this line to the solution.
644 for d in self.custom_deps:
645 if d not in deps:
646 deps[d] = self.custom_deps[d]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000647
648 # If use_relative_paths is set in the DEPS file, regenerate
649 # the dictionary using paths relative to the directory containing
cmp@chromium.orgf2def0a2014-07-16 19:48:54 +0000650 # the DEPS file. Also update recursedeps if use_relative_paths is
651 # enabled.
agabledce6ddc2016-09-08 10:02:16 -0700652 # If the deps file doesn't set use_relative_paths, but the parent did
653 # (and therefore set self.relative on this Dependency object), then we
654 # want to modify the deps and recursedeps by prepending the parent
655 # directory of this dependency.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000656 use_relative_paths = local_scope.get('use_relative_paths', False)
agabledce6ddc2016-09-08 10:02:16 -0700657 rel_prefix = None
maruel@chromium.org271375b2010-06-23 19:17:38 +0000658 if use_relative_paths:
agabledce6ddc2016-09-08 10:02:16 -0700659 rel_prefix = self.name
660 elif self._relative:
661 rel_prefix = os.path.dirname(self.name)
662 if rel_prefix:
cmp@chromium.orgf2def0a2014-07-16 19:48:54 +0000663 logging.warning('use_relative_paths enabled.')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000664 rel_deps = {}
665 for d, url in deps.items():
666 # normpath is required to allow DEPS to use .. in their
667 # dependency local path.
agabledce6ddc2016-09-08 10:02:16 -0700668 rel_deps[os.path.normpath(os.path.join(rel_prefix, d))] = url
669 logging.warning('Updating deps by prepending %s.', rel_prefix)
maruel@chromium.org271375b2010-06-23 19:17:38 +0000670 deps = rel_deps
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000671
cmp@chromium.orgf2def0a2014-07-16 19:48:54 +0000672 # Update recursedeps if it's set.
673 if self.recursedeps is not None:
agabledce6ddc2016-09-08 10:02:16 -0700674 logging.warning('Updating recursedeps by prepending %s.', rel_prefix)
iannucci@chromium.orgafa11ac2016-05-04 22:17:34 +0000675 rel_deps = {}
676 for depname, options in self.recursedeps.iteritems():
agabledce6ddc2016-09-08 10:02:16 -0700677 rel_deps[
678 os.path.normpath(os.path.join(rel_prefix, depname))] = options
cmp@chromium.orgf2def0a2014-07-16 19:48:54 +0000679 self.recursedeps = rel_deps
680
agabledce6ddc2016-09-08 10:02:16 -0700681
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000682 if 'allowed_hosts' in local_scope:
683 try:
684 self._allowed_hosts = frozenset(local_scope.get('allowed_hosts'))
685 except TypeError: # raised if non-iterable
686 pass
687 if not self._allowed_hosts:
688 logging.warning("allowed_hosts is specified but empty %s",
689 self._allowed_hosts)
690 raise gclient_utils.Error(
691 'ParseDepsFile(%s): allowed_hosts must be absent '
692 'or a non-empty iterable' % self.name)
693
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000694 # Convert the deps into real Dependency.
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000695 deps_to_add = []
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000696 for name, url in deps.iteritems():
maruel@chromium.org68988972011-09-20 14:11:42 +0000697 should_process = self.recursion_limit and self.should_process
iannucci@chromium.orgafa11ac2016-05-04 22:17:34 +0000698 deps_file = self.deps_file
699 if self.recursedeps is not None:
700 ent = self.recursedeps.get(name)
701 if ent is not None:
702 deps_file = ent['deps_file']
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000703 deps_to_add.append(Dependency(
agablea98a6cd2016-11-15 14:30:10 -0800704 self, name, url, None, None, self.custom_vars, None,
agabledce6ddc2016-09-08 10:02:16 -0700705 deps_file, should_process, use_relative_paths))
maruel@chromium.orgb9be0652011-10-14 18:05:40 +0000706 deps_to_add.sort(key=lambda x: x.name)
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000707
708 # override named sets of hooks by the custom hooks
709 hooks_to_run = []
710 hook_names_to_suppress = [c.get('name', '') for c in self.custom_hooks]
711 for hook in local_scope.get('hooks', []):
712 if hook.get('name', '') not in hook_names_to_suppress:
713 hooks_to_run.append(hook)
714
715 # add the replacements and any additions
716 for hook in self.custom_hooks:
717 if 'action' in hook:
718 hooks_to_run.append(hook)
719
Dirk Prankeda3a29e2017-02-27 15:29:36 -0800720 if self.recursion_limit:
721 self._pre_deps_hooks = [self.GetHookAction(hook, []) for hook in
722 local_scope.get('pre_deps_hooks', [])]
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000723
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000724 self.add_dependencies_and_close(deps_to_add, hooks_to_run)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000725 logging.info('ParseDepsFile(%s) done' % self.name)
726
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +0200727 def _get_option(self, attr, default):
728 obj = self
729 while not hasattr(obj, '_options'):
730 obj = obj.parent
731 return getattr(obj._options, attr, default)
732
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000733 def add_dependencies_and_close(self, deps_to_add, hooks):
734 """Adds the dependencies, hooks and mark the parsing as done."""
maruel@chromium.orgb9be0652011-10-14 18:05:40 +0000735 for dep in deps_to_add:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000736 if dep.verify_validity():
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000737 self.add_dependency(dep)
738 self._mark_as_parsed(hooks)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000739
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000740 def findDepsFromNotAllowedHosts(self):
741 """Returns a list of depenecies from not allowed hosts.
742
743 If allowed_hosts is not set, allows all hosts and returns empty list.
744 """
745 if not self._allowed_hosts:
746 return []
747 bad_deps = []
748 for dep in self._dependencies:
szager@chromium.orgbd772dd2014-11-05 18:43:08 +0000749 # Don't enforce this for custom_deps.
750 if dep.name in self._custom_deps:
751 continue
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000752 if isinstance(dep.url, basestring):
753 parsed_url = urlparse.urlparse(dep.url)
754 if parsed_url.netloc and parsed_url.netloc not in self._allowed_hosts:
755 bad_deps.append(dep)
756 return bad_deps
757
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +0000758 # Arguments number differs from overridden method
Quinten Yearsleyb2cc4a92016-12-15 13:53:26 -0800759 # pylint: disable=arguments-differ
maruel@chromium.org3742c842010-09-09 19:27:14 +0000760 def run(self, revision_overrides, command, args, work_queue, options):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000761 """Runs |command| then parse the DEPS file."""
maruel@chromium.org470b5432011-10-11 18:18:19 +0000762 logging.info('Dependency(%s).run()' % self.name)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000763 assert self._file_list == []
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000764 if not self.should_process:
765 return
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000766 # When running runhooks, there's no need to consult the SCM.
767 # All known hooks are expected to run unconditionally regardless of working
768 # copy state, so skip the SCM status check.
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +0200769 run_scm = command not in ('runhooks', 'recurse', 'validate', None)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000770 parsed_url = self.LateOverride(self.url)
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000771 file_list = [] if not options.nohooks else None
szager@chromium.org3a3608d2014-10-22 21:13:52 +0000772 revision_override = revision_overrides.pop(self.name, None)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000773 if run_scm and parsed_url:
agabled437d762016-10-17 09:35:11 -0700774 # Create a shallow copy to mutate revision.
775 options = copy.copy(options)
776 options.revision = revision_override
777 self._used_revision = options.revision
778 self._used_scm = gclient_scm.CreateSCM(
779 parsed_url, self.root.root_dir, self.name, self.outbuf,
780 out_cb=work_queue.out_cb)
781 self._got_revision = self._used_scm.RunCommand(command, options, args,
782 file_list)
783 if file_list:
784 file_list = [os.path.join(self.name, f.strip()) for f in file_list]
maruel@chromium.org68988972011-09-20 14:11:42 +0000785
786 # TODO(phajdan.jr): We should know exactly when the paths are absolute.
787 # Convert all absolute paths to relative.
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000788 for i in range(len(file_list or [])):
maruel@chromium.org68988972011-09-20 14:11:42 +0000789 # It depends on the command being executed (like runhooks vs sync).
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000790 if not os.path.isabs(file_list[i]):
maruel@chromium.org68988972011-09-20 14:11:42 +0000791 continue
792 prefix = os.path.commonprefix(
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000793 [self.root.root_dir.lower(), file_list[i].lower()])
794 file_list[i] = file_list[i][len(prefix):]
maruel@chromium.org68988972011-09-20 14:11:42 +0000795 # Strip any leading path separators.
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000796 while file_list[i].startswith(('\\', '/')):
797 file_list[i] = file_list[i][1:]
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000798
799 # Always parse the DEPS file.
800 self.ParseDepsFile()
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000801 self._run_is_done(file_list or [], parsed_url)
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000802 if command in ('update', 'revert') and not options.noprehooks:
803 self.RunPreDepsHooks()
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000804
805 if self.recursion_limit:
806 # Parse the dependencies of this dependency.
807 for s in self.dependencies:
808 work_queue.enqueue(s)
809
810 if command == 'recurse':
agabled437d762016-10-17 09:35:11 -0700811 # Skip file only checkout.
812 scm = gclient_scm.GetScmName(parsed_url)
813 if not options.scm or scm in options.scm:
814 cwd = os.path.normpath(os.path.join(self.root.root_dir, self.name))
815 # Pass in the SCM type as an env variable. Make sure we don't put
816 # unicode strings in the environment.
817 env = os.environ.copy()
818 if scm:
819 env['GCLIENT_SCM'] = str(scm)
820 if parsed_url:
821 env['GCLIENT_URL'] = str(parsed_url)
822 env['GCLIENT_DEP_PATH'] = str(self.name)
823 if options.prepend_dir and scm == 'git':
824 print_stdout = False
825 def filter_fn(line):
826 """Git-specific path marshaling. It is optimized for git-grep."""
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000827
agabled437d762016-10-17 09:35:11 -0700828 def mod_path(git_pathspec):
829 match = re.match('^(\\S+?:)?([^\0]+)$', git_pathspec)
830 modified_path = os.path.join(self.name, match.group(2))
831 branch = match.group(1) or ''
832 return '%s%s' % (branch, modified_path)
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000833
agabled437d762016-10-17 09:35:11 -0700834 match = re.match('^Binary file ([^\0]+) matches$', line)
835 if match:
836 print('Binary file %s matches\n' % mod_path(match.group(1)))
837 return
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000838
agabled437d762016-10-17 09:35:11 -0700839 items = line.split('\0')
840 if len(items) == 2 and items[1]:
841 print('%s : %s' % (mod_path(items[0]), items[1]))
842 elif len(items) >= 2:
843 # Multiple null bytes or a single trailing null byte indicate
844 # git is likely displaying filenames only (such as with -l)
845 print('\n'.join(mod_path(path) for path in items if path))
846 else:
847 print(line)
848 else:
849 print_stdout = True
850 filter_fn = None
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000851
agabled437d762016-10-17 09:35:11 -0700852 if parsed_url is None:
853 print('Skipped omitted dependency %s' % cwd, file=sys.stderr)
854 elif os.path.isdir(cwd):
855 try:
856 gclient_utils.CheckCallAndFilter(
857 args, cwd=cwd, env=env, print_stdout=print_stdout,
858 filter_fn=filter_fn,
859 )
860 except subprocess2.CalledProcessError:
861 if not options.ignore:
862 raise
863 else:
864 print('Skipped missing %s' % cwd, file=sys.stderr)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000865
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000866
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000867 @gclient_utils.lockedmethod
868 def _run_is_done(self, file_list, parsed_url):
869 # Both these are kept for hooks that are run as a separate tree traversal.
870 self._file_list = file_list
871 self._parsed_url = parsed_url
872 self._processed = True
873
szager@google.comb9a78d32012-03-13 18:46:21 +0000874 @staticmethod
875 def GetHookAction(hook_dict, matching_file_list):
876 """Turns a parsed 'hook' dict into an executable command."""
877 logging.debug(hook_dict)
878 logging.debug(matching_file_list)
879 command = hook_dict['action'][:]
880 if command[0] == 'python':
881 # If the hook specified "python" as the first item, the action is a
882 # Python script. Run it by starting a new copy of the same
883 # interpreter.
884 command[0] = sys.executable
885 if '$matching_files' in command:
886 splice_index = command.index('$matching_files')
887 command[splice_index:splice_index + 1] = matching_file_list
888 return command
889
890 def GetHooks(self, options):
891 """Evaluates all hooks, and return them in a flat list.
892
893 RunOnDeps() must have been called before to load the DEPS.
894 """
895 result = []
maruel@chromium.org68988972011-09-20 14:11:42 +0000896 if not self.should_process or not self.recursion_limit:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000897 # Don't run the hook when it is above recursion_limit.
szager@google.comb9a78d32012-03-13 18:46:21 +0000898 return result
maruel@chromium.orgdc7445d2010-07-09 21:05:29 +0000899 # If "--force" was specified, run all hooks regardless of what files have
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000900 # changed.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000901 if self.deps_hooks:
agabled437d762016-10-17 09:35:11 -0700902 # TODO(maruel): If the user is using git, then we don't know
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000903 # what files have changed so we always run all hooks. It'd be nice to fix
904 # that.
905 if (options.force or
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000906 gclient_scm.GetScmName(self.parsed_url) in ('git', None) or
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000907 os.path.isdir(os.path.join(self.root.root_dir, self.name, '.git'))):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000908 for hook_dict in self.deps_hooks:
szager@google.comb9a78d32012-03-13 18:46:21 +0000909 result.append(self.GetHookAction(hook_dict, []))
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000910 else:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000911 # Run hooks on the basis of whether the files from the gclient operation
912 # match each hook's pattern.
913 for hook_dict in self.deps_hooks:
914 pattern = re.compile(hook_dict['pattern'])
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000915 matching_file_list = [
916 f for f in self.file_list_and_children if pattern.search(f)
917 ]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000918 if matching_file_list:
szager@google.comb9a78d32012-03-13 18:46:21 +0000919 result.append(self.GetHookAction(hook_dict, matching_file_list))
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000920 for s in self.dependencies:
szager@google.comb9a78d32012-03-13 18:46:21 +0000921 result.extend(s.GetHooks(options))
922 return result
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000923
szager@google.comb9a78d32012-03-13 18:46:21 +0000924 def RunHooksRecursively(self, options):
925 assert self.hooks_ran == False
maruel@chromium.org064186c2011-09-27 23:53:33 +0000926 self._hooks_ran = True
szager@google.comb9a78d32012-03-13 18:46:21 +0000927 for hook in self.GetHooks(options):
928 try:
ilevy@chromium.orgc28d3772013-07-12 19:42:37 +0000929 start_time = time.time()
szager@google.comb9a78d32012-03-13 18:46:21 +0000930 gclient_utils.CheckCallAndFilterAndHeader(
931 hook, cwd=self.root.root_dir, always=True)
vapier@chromium.orga81a56e2015-11-11 07:56:13 +0000932 except (gclient_utils.Error, subprocess2.CalledProcessError) as e:
szager@google.comb9a78d32012-03-13 18:46:21 +0000933 # Use a discrete exit status code of 2 to indicate that a hook action
934 # failed. Users of this script may wish to treat hook action failures
935 # differently from VC failures.
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +0000936 print('Error: %s' % str(e), file=sys.stderr)
szager@google.comb9a78d32012-03-13 18:46:21 +0000937 sys.exit(2)
ilevy@chromium.orgc28d3772013-07-12 19:42:37 +0000938 finally:
939 elapsed_time = time.time() - start_time
940 if elapsed_time > 10:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +0000941 print("Hook '%s' took %.2f secs" % (
942 gclient_utils.CommandToStr(hook), elapsed_time))
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000943
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000944 def RunPreDepsHooks(self):
945 assert self.processed
946 assert self.deps_parsed
947 assert not self.pre_deps_hooks_ran
948 assert not self.hooks_ran
949 for s in self.dependencies:
950 assert not s.processed
951 self._pre_deps_hooks_ran = True
952 for hook in self.pre_deps_hooks:
953 try:
954 start_time = time.time()
955 gclient_utils.CheckCallAndFilterAndHeader(
956 hook, cwd=self.root.root_dir, always=True)
vapier@chromium.orga81a56e2015-11-11 07:56:13 +0000957 except (gclient_utils.Error, subprocess2.CalledProcessError) as e:
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000958 # Use a discrete exit status code of 2 to indicate that a hook action
959 # failed. Users of this script may wish to treat hook action failures
960 # differently from VC failures.
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +0000961 print('Error: %s' % str(e), file=sys.stderr)
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000962 sys.exit(2)
963 finally:
964 elapsed_time = time.time() - start_time
965 if elapsed_time > 10:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +0000966 print("Hook '%s' took %.2f secs" % (
967 gclient_utils.CommandToStr(hook), elapsed_time))
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000968
969
maruel@chromium.org0d812442010-08-10 12:41:08 +0000970 def subtree(self, include_all):
maruel@chromium.orgad3287e2011-10-03 19:15:10 +0000971 """Breadth first recursion excluding root node."""
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000972 dependencies = self.dependencies
973 for d in dependencies:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000974 if d.should_process or include_all:
maruel@chromium.orgad3287e2011-10-03 19:15:10 +0000975 yield d
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000976 for d in dependencies:
maruel@chromium.orgad3287e2011-10-03 19:15:10 +0000977 for i in d.subtree(include_all):
978 yield i
979
980 def depth_first_tree(self):
981 """Depth-first recursion including the root node."""
982 yield self
983 for i in self.dependencies:
984 for j in i.depth_first_tree():
985 if j.should_process:
986 yield j
maruel@chromium.orgc57e4f22010-07-22 21:37:46 +0000987
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000988 @gclient_utils.lockedmethod
989 def add_dependency(self, new_dep):
990 self._dependencies.append(new_dep)
991
992 @gclient_utils.lockedmethod
993 def _mark_as_parsed(self, new_hooks):
994 self._deps_hooks.extend(new_hooks)
995 self._deps_parsed = True
996
maruel@chromium.org68988972011-09-20 14:11:42 +0000997 @property
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000998 @gclient_utils.lockedmethod
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +0000999 def dependencies(self):
1000 return tuple(self._dependencies)
1001
1002 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001003 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001004 def deps_hooks(self):
1005 return tuple(self._deps_hooks)
1006
1007 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001008 @gclient_utils.lockedmethod
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001009 def pre_deps_hooks(self):
1010 return tuple(self._pre_deps_hooks)
1011
1012 @property
1013 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001014 def parsed_url(self):
1015 return self._parsed_url
1016
1017 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001018 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001019 def deps_parsed(self):
maruel@chromium.org3223edd2011-10-10 23:17:39 +00001020 """This is purely for debugging purposes. It's not used anywhere."""
maruel@chromium.org064186c2011-09-27 23:53:33 +00001021 return self._deps_parsed
1022
1023 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001024 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001025 def processed(self):
1026 return self._processed
1027
1028 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001029 @gclient_utils.lockedmethod
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001030 def pre_deps_hooks_ran(self):
1031 return self._pre_deps_hooks_ran
1032
1033 @property
1034 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001035 def hooks_ran(self):
1036 return self._hooks_ran
1037
1038 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001039 @gclient_utils.lockedmethod
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00001040 def allowed_hosts(self):
1041 return self._allowed_hosts
1042
1043 @property
1044 @gclient_utils.lockedmethod
maruel@chromium.org861fd0f2010-07-23 03:05:05 +00001045 def file_list(self):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001046 return tuple(self._file_list)
1047
1048 @property
kustermann@google.coma692e8f2013-04-18 08:32:04 +00001049 def used_scm(self):
1050 """SCMWrapper instance for this dependency or None if not processed yet."""
1051 return self._used_scm
1052
1053 @property
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00001054 @gclient_utils.lockedmethod
1055 def got_revision(self):
1056 return self._got_revision
1057
1058 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001059 def file_list_and_children(self):
1060 result = list(self.file_list)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +00001061 for d in self.dependencies:
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001062 result.extend(d.file_list_and_children)
maruel@chromium.org68988972011-09-20 14:11:42 +00001063 return tuple(result)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +00001064
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001065 def __str__(self):
1066 out = []
agablea98a6cd2016-11-15 14:30:10 -08001067 for i in ('name', 'url', 'parsed_url', 'custom_deps',
maruel@chromium.org3c74bc92011-09-15 19:17:21 +00001068 'custom_vars', 'deps_hooks', 'file_list', 'should_process',
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00001069 'processed', 'hooks_ran', 'deps_parsed', 'requirements',
1070 'allowed_hosts'):
maruel@chromium.org3c74bc92011-09-15 19:17:21 +00001071 # First try the native property if it exists.
1072 if hasattr(self, '_' + i):
1073 value = getattr(self, '_' + i, False)
1074 else:
1075 value = getattr(self, i, False)
1076 if value:
1077 out.append('%s: %s' % (i, value))
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001078
1079 for d in self.dependencies:
1080 out.extend([' ' + x for x in str(d).splitlines()])
1081 out.append('')
1082 return '\n'.join(out)
1083
1084 def __repr__(self):
1085 return '%s: %s' % (self.name, self.url)
1086
maruel@chromium.orgbffb9042010-07-22 20:59:36 +00001087 def hierarchy(self):
maruel@chromium.orgbc2d2f92010-07-22 21:26:48 +00001088 """Returns a human-readable hierarchical reference to a Dependency."""
maruel@chromium.orgbffb9042010-07-22 20:59:36 +00001089 out = '%s(%s)' % (self.name, self.url)
1090 i = self.parent
1091 while i and i.name:
1092 out = '%s(%s) -> %s' % (i.name, i.url, out)
1093 i = i.parent
1094 return out
1095
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001096
1097class GClient(Dependency):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001098 """Object that represent a gclient checkout. A tree of Dependency(), one per
1099 solution or DEPS entry."""
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001100
1101 DEPS_OS_CHOICES = {
1102 "win32": "win",
1103 "win": "win",
1104 "cygwin": "win",
1105 "darwin": "mac",
1106 "mac": "mac",
1107 "unix": "unix",
1108 "linux": "unix",
1109 "linux2": "unix",
maruel@chromium.org244e3442011-06-12 15:20:55 +00001110 "linux3": "unix",
szager@chromium.orgf8c95cd2012-06-01 22:26:52 +00001111 "android": "android",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001112 }
1113
1114 DEFAULT_CLIENT_FILE_TEXT = ("""\
1115solutions = [
smutae7ea312016-07-18 11:59:41 -07001116 { "name" : "%(solution_name)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001117 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +00001118 "deps_file" : "%(deps_file)s",
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001119 "managed" : %(managed)s,
smutae7ea312016-07-18 11:59:41 -07001120 "custom_deps" : {
1121 },
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001122 },
1123]
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001124cache_dir = %(cache_dir)r
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001125""")
1126
1127 DEFAULT_SNAPSHOT_SOLUTION_TEXT = ("""\
smutae7ea312016-07-18 11:59:41 -07001128 { "name" : "%(solution_name)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001129 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +00001130 "deps_file" : "%(deps_file)s",
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001131 "managed" : %(managed)s,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001132 "custom_deps" : {
smutae7ea312016-07-18 11:59:41 -07001133%(solution_deps)s },
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001134 },
1135""")
1136
1137 DEFAULT_SNAPSHOT_FILE_TEXT = ("""\
1138# Snapshot generated with gclient revinfo --snapshot
1139solutions = [
maruel@chromium.org73e21142010-07-05 13:32:01 +00001140%(solution_list)s]
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001141""")
1142
1143 def __init__(self, root_dir, options):
maruel@chromium.org0d812442010-08-10 12:41:08 +00001144 # Do not change previous behavior. Only solution level and immediate DEPS
1145 # are processed.
1146 self._recursion_limit = 2
agablea98a6cd2016-11-15 14:30:10 -08001147 Dependency.__init__(self, None, None, None, True, None, None, None,
agabledce6ddc2016-09-08 10:02:16 -07001148 'unused', True, None)
maruel@chromium.org0d425922010-06-21 19:22:24 +00001149 self._options = options
maruel@chromium.org271375b2010-06-23 19:17:38 +00001150 if options.deps_os:
1151 enforced_os = options.deps_os.split(',')
1152 else:
1153 enforced_os = [self.DEPS_OS_CHOICES.get(sys.platform, 'unix')]
1154 if 'all' in enforced_os:
1155 enforced_os = self.DEPS_OS_CHOICES.itervalues()
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001156 self._enforced_os = tuple(set(enforced_os))
maruel@chromium.org271375b2010-06-23 19:17:38 +00001157 self._root_dir = root_dir
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001158 self.config_content = None
1159
borenet@google.com88d10082014-03-21 17:24:48 +00001160 def _CheckConfig(self):
1161 """Verify that the config matches the state of the existing checked-out
1162 solutions."""
1163 for dep in self.dependencies:
1164 if dep.managed and dep.url:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001165 scm = gclient_scm.CreateSCM(
1166 dep.url, self.root_dir, dep.name, self.outbuf)
smut@google.comd33eab32014-07-07 19:35:18 +00001167 actual_url = scm.GetActualRemoteURL(self._options)
borenet@google.com4e9be262014-04-08 19:40:30 +00001168 if actual_url and not scm.DoesRemoteURLMatch(self._options):
levarum@chromium.org27a6f9a2016-05-28 00:21:49 +00001169 mirror = scm.GetCacheMirror()
1170 if mirror:
1171 mirror_string = '%s (exists=%s)' % (mirror.mirror_path,
1172 mirror.exists())
1173 else:
1174 mirror_string = 'not used'
borenet@google.com0a427372014-04-02 19:12:13 +00001175 raise gclient_utils.Error('''
borenet@google.com88d10082014-03-21 17:24:48 +00001176Your .gclient file seems to be broken. The requested URL is different from what
borenet@google.com0a427372014-04-02 19:12:13 +00001177is actually checked out in %(checkout_path)s.
borenet@google.com88d10082014-03-21 17:24:48 +00001178
borenet@google.com97882362014-04-07 20:06:02 +00001179The .gclient file contains:
levarum@chromium.org27a6f9a2016-05-28 00:21:49 +00001180URL: %(expected_url)s (%(expected_scm)s)
1181Cache mirror: %(mirror_string)s
borenet@google.com97882362014-04-07 20:06:02 +00001182
1183The local checkout in %(checkout_path)s reports:
1184%(actual_url)s (%(actual_scm)s)
borenet@google.com88d10082014-03-21 17:24:48 +00001185
1186You should ensure that the URL listed in .gclient is correct and either change
agabled437d762016-10-17 09:35:11 -07001187it or fix the checkout.
borenet@google.com88d10082014-03-21 17:24:48 +00001188''' % {'checkout_path': os.path.join(self.root_dir, dep.name),
1189 'expected_url': dep.url,
1190 'expected_scm': gclient_scm.GetScmName(dep.url),
levarum@chromium.org27a6f9a2016-05-28 00:21:49 +00001191 'mirror_string' : mirror_string,
borenet@google.com88d10082014-03-21 17:24:48 +00001192 'actual_url': actual_url,
1193 'actual_scm': gclient_scm.GetScmName(actual_url)})
1194
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001195 def SetConfig(self, content):
maruel@chromium.orgf13a4182011-09-22 00:26:15 +00001196 assert not self.dependencies
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001197 config_dict = {}
1198 self.config_content = content
1199 try:
1200 exec(content, config_dict)
vapier@chromium.orga81a56e2015-11-11 07:56:13 +00001201 except SyntaxError as e:
maruel@chromium.org5990f9d2010-07-07 18:02:58 +00001202 gclient_utils.SyntaxErrorToError('.gclient', e)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001203
peter@chromium.org1efccc82012-04-27 16:34:38 +00001204 # Append any target OS that is not already being enforced to the tuple.
1205 target_os = config_dict.get('target_os', [])
stuartmorgan@chromium.org18a4f6a2012-11-02 13:37:24 +00001206 if config_dict.get('target_os_only', False):
1207 self._enforced_os = tuple(set(target_os))
1208 else:
1209 self._enforced_os = tuple(set(self._enforced_os).union(target_os))
1210
dyen@chromium.orgd915cca2014-08-07 21:41:37 +00001211 cache_dir = config_dict.get('cache_dir')
1212 if cache_dir:
1213 cache_dir = os.path.join(self.root_dir, cache_dir)
1214 cache_dir = os.path.abspath(cache_dir)
szager@chromium.orgcaf5bef2014-08-24 18:56:32 +00001215 # If running on a bot, force break any stale git cache locks.
dnj@chromium.orgb682b3e2014-08-25 19:17:12 +00001216 if os.path.exists(cache_dir) and os.environ.get('CHROME_HEADLESS'):
szager@chromium.org4848fb62014-08-24 19:16:31 +00001217 subprocess2.check_call(['git', 'cache', 'unlock', '--cache-dir',
1218 cache_dir, '--force', '--all'])
dyen@chromium.orgd915cca2014-08-07 21:41:37 +00001219 gclient_scm.GitWrapper.cache_dir = cache_dir
1220 git_cache.Mirror.SetCachePath(cache_dir)
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001221
stuartmorgan@chromium.org18a4f6a2012-11-02 13:37:24 +00001222 if not target_os and config_dict.get('target_os_only', False):
1223 raise gclient_utils.Error('Can\'t use target_os_only if target_os is '
1224 'not specified')
peter@chromium.org1efccc82012-04-27 16:34:38 +00001225
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001226 deps_to_add = []
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001227 for s in config_dict.get('solutions', []):
maruel@chromium.org81843b82010-06-28 16:49:26 +00001228 try:
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001229 deps_to_add.append(Dependency(
maruel@chromium.org81843b82010-06-28 16:49:26 +00001230 self, s['name'], s['url'],
smutae7ea312016-07-18 11:59:41 -07001231 s.get('managed', True),
maruel@chromium.org81843b82010-06-28 16:49:26 +00001232 s.get('custom_deps', {}),
maruel@chromium.org0d812442010-08-10 12:41:08 +00001233 s.get('custom_vars', {}),
petermayo@chromium.orge79161a2013-07-09 14:40:37 +00001234 s.get('custom_hooks', []),
nsylvain@google.comefc80932011-05-31 21:27:56 +00001235 s.get('deps_file', 'DEPS'),
agabledce6ddc2016-09-08 10:02:16 -07001236 True,
1237 None))
maruel@chromium.org81843b82010-06-28 16:49:26 +00001238 except KeyError:
1239 raise gclient_utils.Error('Invalid .gclient file. Solution is '
1240 'incomplete: %s' % s)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001241 self.add_dependencies_and_close(deps_to_add, config_dict.get('hooks', []))
1242 logging.info('SetConfig() done')
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001243
1244 def SaveConfig(self):
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001245 gclient_utils.FileWrite(os.path.join(self.root_dir,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001246 self._options.config_filename),
1247 self.config_content)
1248
1249 @staticmethod
1250 def LoadCurrentConfig(options):
1251 """Searches for and loads a .gclient file relative to the current working
1252 dir. Returns a GClient object."""
szager@chromium.orge2e03202012-07-31 18:05:16 +00001253 if options.spec:
1254 client = GClient('.', options)
1255 client.SetConfig(options.spec)
1256 else:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00001257 if options.verbose:
1258 print('Looking for %s starting from %s\n' % (
1259 options.config_filename, os.getcwd()))
szager@chromium.orge2e03202012-07-31 18:05:16 +00001260 path = gclient_utils.FindGclientRoot(os.getcwd(), options.config_filename)
1261 if not path:
1262 return None
1263 client = GClient(path, options)
1264 client.SetConfig(gclient_utils.FileRead(
1265 os.path.join(path, options.config_filename)))
maruel@chromium.org69392e72011-10-13 22:09:00 +00001266
1267 if (options.revisions and
1268 len(client.dependencies) > 1 and
1269 any('@' not in r for r in options.revisions)):
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001270 print(
1271 ('You must specify the full solution name like --revision %s@%s\n'
1272 'when you have multiple solutions setup in your .gclient file.\n'
1273 'Other solutions present are: %s.') % (
maruel@chromium.org69392e72011-10-13 22:09:00 +00001274 client.dependencies[0].name,
1275 options.revisions[0],
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001276 ', '.join(s.name for s in client.dependencies[1:])),
1277 file=sys.stderr)
maruel@chromium.org15804092010-09-02 17:07:37 +00001278 return client
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001279
nsylvain@google.comefc80932011-05-31 21:27:56 +00001280 def SetDefaultConfig(self, solution_name, deps_file, solution_url,
agablea98a6cd2016-11-15 14:30:10 -08001281 managed=True, cache_dir=None):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001282 self.SetConfig(self.DEFAULT_CLIENT_FILE_TEXT % {
1283 'solution_name': solution_name,
1284 'solution_url': solution_url,
nsylvain@google.comefc80932011-05-31 21:27:56 +00001285 'deps_file': deps_file,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001286 'managed': managed,
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001287 'cache_dir': cache_dir,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001288 })
1289
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001290 def _SaveEntries(self):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001291 """Creates a .gclient_entries file to record the list of unique checkouts.
1292
1293 The .gclient_entries file lives in the same directory as .gclient.
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001294 """
1295 # Sometimes pprint.pformat will use {', sometimes it'll use { ' ... It
1296 # makes testing a bit too fun.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001297 result = 'entries = {\n'
maruel@chromium.org68988972011-09-20 14:11:42 +00001298 for entry in self.root.subtree(False):
agabled437d762016-10-17 09:35:11 -07001299 result += ' %s: %s,\n' % (pprint.pformat(entry.name),
1300 pprint.pformat(entry.parsed_url))
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001301 result += '}\n'
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001302 file_path = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org1333cb32011-10-04 23:40:16 +00001303 logging.debug(result)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001304 gclient_utils.FileWrite(file_path, result)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001305
1306 def _ReadEntries(self):
1307 """Read the .gclient_entries file for the given client.
1308
1309 Returns:
1310 A sequence of solution names, which will be empty if there is the
1311 entries file hasn't been created yet.
1312 """
1313 scope = {}
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001314 filename = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001315 if not os.path.exists(filename):
maruel@chromium.org73e21142010-07-05 13:32:01 +00001316 return {}
maruel@chromium.org5990f9d2010-07-07 18:02:58 +00001317 try:
1318 exec(gclient_utils.FileRead(filename), scope)
vapier@chromium.orga81a56e2015-11-11 07:56:13 +00001319 except SyntaxError as e:
maruel@chromium.org5990f9d2010-07-07 18:02:58 +00001320 gclient_utils.SyntaxErrorToError(filename, e)
Aaron Gable3721ee92017-04-03 14:53:14 -07001321 return scope.get('entries', {})
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001322
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001323 def _EnforceRevisions(self):
maruel@chromium.org918a9ae2010-05-28 15:50:30 +00001324 """Checks for revision overrides."""
1325 revision_overrides = {}
smutae7ea312016-07-18 11:59:41 -07001326 if self._options.head:
1327 return revision_overrides
joi@chromium.org792ea882010-11-10 02:37:27 +00001328 if not self._options.revisions:
1329 for s in self.dependencies:
smutae7ea312016-07-18 11:59:41 -07001330 if not s.managed:
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001331 self._options.revisions.append('%s@unmanaged' % s.name)
maruel@chromium.org307d1792010-05-31 20:03:13 +00001332 if not self._options.revisions:
1333 return revision_overrides
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001334 solutions_names = [s.name for s in self.dependencies]
smutae7ea312016-07-18 11:59:41 -07001335 index = 0
1336 for revision in self._options.revisions:
1337 if not '@' in revision:
maruel@chromium.org307d1792010-05-31 20:03:13 +00001338 # Support for --revision 123
smutae7ea312016-07-18 11:59:41 -07001339 revision = '%s@%s' % (solutions_names[index], revision)
1340 name, rev = revision.split('@', 1)
szager@chromium.org4ad264b2014-05-20 04:43:47 +00001341 revision_overrides[name] = rev
smutae7ea312016-07-18 11:59:41 -07001342 index += 1
maruel@chromium.org918a9ae2010-05-28 15:50:30 +00001343 return revision_overrides
1344
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001345 def RunOnDeps(self, command, args, ignore_requirements=False, progress=True):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001346 """Runs a command on each dependency in a client and its dependencies.
1347
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001348 Args:
1349 command: The command to use (e.g., 'status' or 'diff')
1350 args: list of str - extra arguments to add to the command line.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001351 """
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001352 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +00001353 raise gclient_utils.Error('No solution specified')
borenet@google.com0a427372014-04-02 19:12:13 +00001354
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001355 revision_overrides = {}
1356 # It's unnecessary to check for revision overrides for 'recurse'.
1357 # Save a few seconds by not calling _EnforceRevisions() in that case.
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +02001358 if command not in ('diff', 'recurse', 'runhooks', 'status', 'revert',
1359 'validate'):
szager@chromium.org5273b8a2014-08-21 15:10:10 +00001360 self._CheckConfig()
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001361 revision_overrides = self._EnforceRevisions()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001362 pm = None
maruel@chromium.org5b3f8852010-09-10 16:49:54 +00001363 # Disable progress for non-tty stdout.
iannucci@chromium.org596cd5c2016-04-04 21:34:39 +00001364 if (setup_color.IS_TTY and not self._options.verbose and progress):
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001365 if command in ('update', 'revert'):
1366 pm = Progress('Syncing projects', 1)
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +02001367 elif command in ('recurse', 'validate'):
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001368 pm = Progress(' '.join(args), 1)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001369 work_queue = gclient_utils.ExecutionQueue(
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001370 self._options.jobs, pm, ignore_requirements=ignore_requirements,
1371 verbose=self._options.verbose)
maruel@chromium.org049bced2010-08-12 13:37:20 +00001372 for s in self.dependencies:
1373 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +00001374 work_queue.flush(revision_overrides, command, args, options=self._options)
szager@chromium.org4ad264b2014-05-20 04:43:47 +00001375 if revision_overrides:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001376 print('Please fix your script, having invalid --revision flags will soon '
1377 'considered an error.', file=sys.stderr)
piman@chromium.org6f363722010-04-27 00:41:09 +00001378
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001379 # Once all the dependencies have been processed, it's now safe to run the
1380 # hooks.
1381 if not self._options.nohooks:
1382 self.RunHooksRecursively(self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001383
1384 if command == 'update':
ajwong@chromium.orgcdcee802009-06-23 15:30:42 +00001385 # Notify the user if there is an orphaned entry in their working copy.
1386 # Only delete the directory if there are no changes in it, and
1387 # delete_unversioned_trees is set to true.
maruel@chromium.org68988972011-09-20 14:11:42 +00001388 entries = [i.name for i in self.root.subtree(False) if i.url]
xusydoc@chromium.org885a9602013-05-31 09:54:40 +00001389 full_entries = [os.path.join(self.root_dir, e.replace('/', os.path.sep))
1390 for e in entries]
1391
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001392 for entry, prev_url in self._ReadEntries().iteritems():
maruel@chromium.org04dd7de2010-10-14 13:25:49 +00001393 if not prev_url:
1394 # entry must have been overridden via .gclient custom_deps
1395 continue
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001396 # Fix path separator on Windows.
1397 entry_fixed = entry.replace('/', os.path.sep)
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001398 e_dir = os.path.join(self.root_dir, entry_fixed)
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001399 # Use entry and not entry_fixed there.
jochen@chromium.orga78e5532013-03-11 13:33:03 +00001400 if (entry not in entries and
1401 (not any(path.startswith(entry + '/') for path in entries)) and
jochen@chromium.orgcc475722013-03-11 13:07:40 +00001402 os.path.exists(e_dir)):
primiano@chromium.org1c127382015-02-17 11:15:40 +00001403 # The entry has been removed from DEPS.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001404 scm = gclient_scm.CreateSCM(
1405 prev_url, self.root_dir, entry_fixed, self.outbuf)
xusydoc@chromium.org885a9602013-05-31 09:54:40 +00001406
1407 # Check to see if this directory is now part of a higher-up checkout.
borenet@google.com359bb642014-05-13 17:28:19 +00001408 scm_root = None
agabled437d762016-10-17 09:35:11 -07001409 try:
1410 scm_root = gclient_scm.scm.GIT.GetCheckoutRoot(scm.checkout_path)
1411 except subprocess2.CalledProcessError:
1412 pass
1413 if not scm_root:
borenet@google.com359bb642014-05-13 17:28:19 +00001414 logging.warning('Could not find checkout root for %s. Unable to '
1415 'determine whether it is part of a higher-level '
1416 'checkout, so not removing.' % entry)
1417 continue
primiano@chromium.org1c127382015-02-17 11:15:40 +00001418
1419 # This is to handle the case of third_party/WebKit migrating from
1420 # being a DEPS entry to being part of the main project.
1421 # If the subproject is a Git project, we need to remove its .git
1422 # folder. Otherwise git operations on that folder will have different
1423 # effects depending on the current working directory.
agabled437d762016-10-17 09:35:11 -07001424 if os.path.abspath(scm_root) == os.path.abspath(e_dir):
primiano@chromium.org1c127382015-02-17 11:15:40 +00001425 e_par_dir = os.path.join(e_dir, os.pardir)
agabled437d762016-10-17 09:35:11 -07001426 if gclient_scm.scm.GIT.IsInsideWorkTree(e_par_dir):
1427 par_scm_root = gclient_scm.scm.GIT.GetCheckoutRoot(e_par_dir)
primiano@chromium.org1c127382015-02-17 11:15:40 +00001428 # rel_e_dir : relative path of entry w.r.t. its parent repo.
1429 rel_e_dir = os.path.relpath(e_dir, par_scm_root)
agabled437d762016-10-17 09:35:11 -07001430 if gclient_scm.scm.GIT.IsDirectoryVersioned(
1431 par_scm_root, rel_e_dir):
primiano@chromium.org1c127382015-02-17 11:15:40 +00001432 save_dir = scm.GetGitBackupDirPath()
1433 # Remove any eventual stale backup dir for the same project.
1434 if os.path.exists(save_dir):
1435 gclient_utils.rmtree(save_dir)
1436 os.rename(os.path.join(e_dir, '.git'), save_dir)
1437 # When switching between the two states (entry/ is a subproject
1438 # -> entry/ is part of the outer project), it is very likely
1439 # that some files are changed in the checkout, unless we are
1440 # jumping *exactly* across the commit which changed just DEPS.
1441 # In such case we want to cleanup any eventual stale files
1442 # (coming from the old subproject) in order to end up with a
1443 # clean checkout.
agabled437d762016-10-17 09:35:11 -07001444 gclient_scm.scm.GIT.CleanupDir(par_scm_root, rel_e_dir)
primiano@chromium.org1c127382015-02-17 11:15:40 +00001445 assert not os.path.exists(os.path.join(e_dir, '.git'))
1446 print(('\nWARNING: \'%s\' has been moved from DEPS to a higher '
1447 'level checkout. The git folder containing all the local'
1448 ' branches has been saved to %s.\n'
1449 'If you don\'t care about its state you can safely '
1450 'remove that folder to free up space.') %
1451 (entry, save_dir))
1452 continue
1453
borenet@google.com359bb642014-05-13 17:28:19 +00001454 if scm_root in full_entries:
primiano@chromium.org1c127382015-02-17 11:15:40 +00001455 logging.info('%s is part of a higher level checkout, not removing',
1456 scm.GetCheckoutRoot())
xusydoc@chromium.org885a9602013-05-31 09:54:40 +00001457 continue
1458
1459 file_list = []
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001460 scm.status(self._options, [], file_list)
1461 modified_files = file_list != []
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001462 if (not self._options.delete_unversioned_trees or
1463 (modified_files and not self._options.force)):
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001464 # There are modified files in this entry. Keep warning until
1465 # removed.
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001466 print(('\nWARNING: \'%s\' is no longer part of this client. '
1467 'It is recommended that you manually remove it.\n') %
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001468 entry_fixed)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001469 else:
1470 # Delete the entry
maruel@chromium.org73e21142010-07-05 13:32:01 +00001471 print('\n________ deleting \'%s\' in \'%s\'' % (
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001472 entry_fixed, self.root_dir))
digit@chromium.orgdc112ac2013-04-24 13:00:19 +00001473 gclient_utils.rmtree(e_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001474 # record the current list of entries for next time
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001475 self._SaveEntries()
maruel@chromium.org17cdf762010-05-28 17:30:52 +00001476 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001477
1478 def PrintRevInfo(self):
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001479 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +00001480 raise gclient_utils.Error('No solution specified')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001481 # Load all the settings.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001482 work_queue = gclient_utils.ExecutionQueue(
1483 self._options.jobs, None, False, verbose=self._options.verbose)
maruel@chromium.org049bced2010-08-12 13:37:20 +00001484 for s in self.dependencies:
1485 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +00001486 work_queue.flush({}, None, [], options=self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001487
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001488 def GetURLAndRev(dep):
1489 """Returns the revision-qualified SCM url for a Dependency."""
1490 if dep.parsed_url is None:
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001491 return None
agabled437d762016-10-17 09:35:11 -07001492 url, _ = gclient_utils.SplitUrlRevision(dep.parsed_url)
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001493 scm = gclient_scm.CreateSCM(
agabled437d762016-10-17 09:35:11 -07001494 dep.parsed_url, self.root_dir, dep.name, self.outbuf)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001495 if not os.path.isdir(scm.checkout_path):
1496 return None
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001497 return '%s@%s' % (url, scm.revinfo(self._options, [], None))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001498
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001499 if self._options.snapshot:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001500 new_gclient = ''
1501 # First level at .gclient
1502 for d in self.dependencies:
1503 entries = {}
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001504 def GrabDeps(dep):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001505 """Recursively grab dependencies."""
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001506 for d in dep.dependencies:
1507 entries[d.name] = GetURLAndRev(d)
1508 GrabDeps(d)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001509 GrabDeps(d)
1510 custom_deps = []
1511 for k in sorted(entries.keys()):
1512 if entries[k]:
1513 # Quotes aren't escaped...
1514 custom_deps.append(' \"%s\": \'%s\',\n' % (k, entries[k]))
1515 else:
1516 custom_deps.append(' \"%s\": None,\n' % k)
1517 new_gclient += self.DEFAULT_SNAPSHOT_SOLUTION_TEXT % {
1518 'solution_name': d.name,
1519 'solution_url': d.url,
nsylvain@google.comefc80932011-05-31 21:27:56 +00001520 'deps_file': d.deps_file,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001521 'managed': d.managed,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001522 'solution_deps': ''.join(custom_deps),
1523 }
1524 # Print the snapshot configuration file
1525 print(self.DEFAULT_SNAPSHOT_FILE_TEXT % {'solution_list': new_gclient})
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +00001526 else:
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001527 entries = {}
maruel@chromium.org68988972011-09-20 14:11:42 +00001528 for d in self.root.subtree(False):
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001529 if self._options.actual:
1530 entries[d.name] = GetURLAndRev(d)
1531 else:
1532 entries[d.name] = d.parsed_url
1533 keys = sorted(entries.keys())
1534 for x in keys:
maruel@chromium.orgce464892010-08-12 17:12:18 +00001535 print('%s: %s' % (x, entries[x]))
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +00001536 logging.info(str(self))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001537
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001538 def ParseDepsFile(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001539 """No DEPS to parse for a .gclient file."""
maruel@chromium.org049bced2010-08-12 13:37:20 +00001540 raise gclient_utils.Error('Internal error')
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001541
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00001542 def PrintLocationAndContents(self):
1543 # Print out the .gclient file. This is longer than if we just printed the
1544 # client dict, but more legible, and it might contain helpful comments.
1545 print('Loaded .gclient config in %s:\n%s' % (
1546 self.root_dir, self.config_content))
1547
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001548 @property
maruel@chromium.org75a59272010-06-11 22:34:03 +00001549 def root_dir(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001550 """Root directory of gclient checkout."""
maruel@chromium.org75a59272010-06-11 22:34:03 +00001551 return self._root_dir
1552
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001553 @property
maruel@chromium.org271375b2010-06-23 19:17:38 +00001554 def enforced_os(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001555 """What deps_os entries that are to be parsed."""
maruel@chromium.org271375b2010-06-23 19:17:38 +00001556 return self._enforced_os
1557
maruel@chromium.org68988972011-09-20 14:11:42 +00001558 @property
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001559 def recursion_limit(self):
1560 """How recursive can each dependencies in DEPS file can load DEPS file."""
1561 return self._recursion_limit
1562
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +00001563 @property
cmp@chromium.orgc401ad12014-07-02 23:20:08 +00001564 def try_recursedeps(self):
1565 """Whether to attempt using recursedeps-style recursion processing."""
cmp@chromium.orge84ac912014-06-30 23:14:35 +00001566 return True
1567
1568 @property
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +00001569 def target_os(self):
1570 return self._enforced_os
1571
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001572
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001573#### gclient commands.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001574
1575
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001576@subcommand.usage('[command] [args ...]')
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001577def CMDrecurse(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001578 """Operates [command args ...] on all the dependencies.
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001579
1580 Runs a shell command on all entries.
qyearsley12fa6ff2016-08-24 09:18:40 -07001581 Sets GCLIENT_DEP_PATH environment variable as the dep's relative location to
ilevy@chromium.org37116242012-11-28 01:32:48 +00001582 root directory of the checkout.
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001583 """
1584 # Stop parsing at the first non-arg so that these go through to the command
1585 parser.disable_interspersed_args()
1586 parser.add_option('-s', '--scm', action='append', default=[],
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001587 help='Choose scm types to operate upon.')
maruel@chromium.org288054d2012-03-05 00:43:07 +00001588 parser.add_option('-i', '--ignore', action='store_true',
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001589 help='Ignore non-zero return codes from subcommands.')
1590 parser.add_option('--prepend-dir', action='store_true',
1591 help='Prepend relative dir for use with git <cmd> --null.')
1592 parser.add_option('--no-progress', action='store_true',
1593 help='Disable progress bar that shows sub-command updates')
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001594 options, args = parser.parse_args(args)
maruel@chromium.org45e9f2d2010-10-18 13:33:46 +00001595 if not args:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001596 print('Need to supply a command!', file=sys.stderr)
maruel@chromium.org45e9f2d2010-10-18 13:33:46 +00001597 return 1
maruel@chromium.org78cba522010-10-18 13:32:05 +00001598 root_and_entries = gclient_utils.GetGClientRootAndEntries()
1599 if not root_and_entries:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001600 print(
maruel@chromium.org78cba522010-10-18 13:32:05 +00001601 'You need to run gclient sync at least once to use \'recurse\'.\n'
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001602 'This is because .gclient_entries needs to exist and be up to date.',
1603 file=sys.stderr)
maruel@chromium.org78cba522010-10-18 13:32:05 +00001604 return 1
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001605
1606 # Normalize options.scm to a set()
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001607 scm_set = set()
1608 for scm in options.scm:
1609 scm_set.update(scm.split(','))
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001610 options.scm = scm_set
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001611
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001612 options.nohooks = True
1613 client = GClient.LoadCurrentConfig(options)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001614 return client.RunOnDeps('recurse', args, ignore_requirements=True,
1615 progress=not options.no_progress)
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001616
1617
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001618@subcommand.usage('[args ...]')
davidbarr@chromium.org12f944e2012-03-01 02:18:31 +00001619def CMDfetch(parser, args):
1620 """Fetches upstream commits for all modules.
1621
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001622 Completely git-specific. Simply runs 'git fetch [args ...]' for each module.
1623 """
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001624 (options, args) = parser.parse_args(args)
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00001625 return CMDrecurse(OptionParser(), [
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001626 '--jobs=%d' % options.jobs, '--scm=git', 'git', 'fetch'] + args)
1627
1628
1629def CMDgrep(parser, args):
1630 """Greps through git repos managed by gclient.
1631
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001632 Runs 'git grep [args...]' for each module.
1633 """
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001634 # We can't use optparse because it will try to parse arguments sent
1635 # to git grep and throw an error. :-(
1636 if not args or re.match('(-h|--help)$', args[0]):
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001637 print(
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001638 'Usage: gclient grep [-j <N>] git-grep-args...\n\n'
1639 'Example: "gclient grep -j10 -A2 RefCountedBase" runs\n"git grep '
1640 '-A2 RefCountedBase" on each of gclient\'s git\nrepos with up to '
1641 '10 jobs.\n\nBonus: page output by appending "|& less -FRSX" to the'
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001642 ' end of your query.',
1643 file=sys.stderr)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001644 return 1
1645
1646 jobs_arg = ['--jobs=1']
1647 if re.match(r'(-j|--jobs=)\d+$', args[0]):
1648 jobs_arg, args = args[:1], args[1:]
1649 elif re.match(r'(-j|--jobs)$', args[0]):
1650 jobs_arg, args = args[:2], args[2:]
1651
1652 return CMDrecurse(
1653 parser,
1654 jobs_arg + ['--ignore', '--prepend-dir', '--no-progress', '--scm=git',
1655 'git', 'grep', '--null', '--color=Always'] + args)
davidbarr@chromium.org12f944e2012-03-01 02:18:31 +00001656
1657
stip@chromium.orga735da22015-04-29 23:18:20 +00001658def CMDroot(parser, args):
1659 """Outputs the solution root (or current dir if there isn't one)."""
1660 (options, args) = parser.parse_args(args)
1661 client = GClient.LoadCurrentConfig(options)
1662 if client:
1663 print(os.path.abspath(client.root_dir))
1664 else:
1665 print(os.path.abspath('.'))
1666
1667
agablea98a6cd2016-11-15 14:30:10 -08001668@subcommand.usage('[url]')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001669def CMDconfig(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001670 """Creates a .gclient file in the current directory.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001671
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001672 This specifies the configuration for further commands. After update/sync,
1673 top-level DEPS files in each module are read to determine dependent
1674 modules to operate on as well. If optional [url] parameter is
1675 provided, then configuration is read from a specified Subversion server
1676 URL.
1677 """
szager@chromium.orge2e03202012-07-31 18:05:16 +00001678 # We do a little dance with the --gclientfile option. 'gclient config' is the
1679 # only command where it's acceptable to have both '--gclientfile' and '--spec'
1680 # arguments. So, we temporarily stash any --gclientfile parameter into
1681 # options.output_config_file until after the (gclientfile xor spec) error
1682 # check.
1683 parser.remove_option('--gclientfile')
1684 parser.add_option('--gclientfile', dest='output_config_file',
1685 help='Specify an alternate .gclient file')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001686 parser.add_option('--name',
1687 help='overrides the default name for the solution')
nsylvain@google.comefc80932011-05-31 21:27:56 +00001688 parser.add_option('--deps-file', default='DEPS',
1689 help='overrides the default name for the DEPS file for the'
1690 'main solutions and all sub-dependencies')
smutae7ea312016-07-18 11:59:41 -07001691 parser.add_option('--unmanaged', action='store_true', default=False,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001692 help='overrides the default behavior to make it possible '
smutae7ea312016-07-18 11:59:41 -07001693 'to have the main solution untouched by gclient '
1694 '(gclient will check out unmanaged dependencies but '
1695 'will never sync them)')
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001696 parser.add_option('--cache-dir',
1697 help='(git only) Cache all git repos into this dir and do '
1698 'shared clones from the cache, instead of cloning '
1699 'directly from the remote. (experimental)')
szager@chromium.orge2e03202012-07-31 18:05:16 +00001700 parser.set_defaults(config_filename=None)
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001701 (options, args) = parser.parse_args(args)
szager@chromium.orge2e03202012-07-31 18:05:16 +00001702 if options.output_config_file:
1703 setattr(options, 'config_filename', getattr(options, 'output_config_file'))
maruel@chromium.org5fc2a332010-05-26 19:37:15 +00001704 if ((options.spec and args) or len(args) > 2 or
1705 (not options.spec and not args)):
1706 parser.error('Inconsistent arguments. Use either --spec or one or 2 args')
1707
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001708 client = GClient('.', options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001709 if options.spec:
1710 client.SetConfig(options.spec)
1711 else:
maruel@chromium.org1ab7ffc2009-06-03 17:21:37 +00001712 base_url = args[0].rstrip('/')
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001713 if not options.name:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001714 name = base_url.split('/')[-1]
nsylvain@google.com12649ef2011-06-01 17:11:20 +00001715 if name.endswith('.git'):
1716 name = name[:-4]
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001717 else:
1718 # specify an alternate relpath for the given URL.
1719 name = options.name
agable@chromium.orgf2214672015-10-27 21:02:48 +00001720 if not os.path.abspath(os.path.join(os.getcwd(), name)).startswith(
1721 os.getcwd()):
1722 parser.error('Do not pass a relative path for --name.')
1723 if any(x in ('..', '.', '/', '\\') for x in name.split(os.sep)):
1724 parser.error('Do not include relative path components in --name.')
1725
nsylvain@google.comefc80932011-05-31 21:27:56 +00001726 deps_file = options.deps_file
agablea98a6cd2016-11-15 14:30:10 -08001727 client.SetDefaultConfig(name, deps_file, base_url,
smutae7ea312016-07-18 11:59:41 -07001728 managed=not options.unmanaged,
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001729 cache_dir=options.cache_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001730 client.SaveConfig()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001731 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001732
1733
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001734@subcommand.epilog("""Example:
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001735 gclient pack > patch.txt
1736 generate simple patch for configured client and dependences
1737""")
1738def CMDpack(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001739 """Generates a patch which can be applied at the root of the tree.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001740
agabled437d762016-10-17 09:35:11 -07001741 Internally, runs 'git diff' on each checked out module and
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001742 dependencies, and performs minimal postprocessing of the output. The
1743 resulting patch is printed to stdout and can be applied to a freshly
1744 checked out tree via 'patch -p0 < patchfile'.
1745 """
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001746 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1747 help='override deps for the specified (comma-separated) '
1748 'platform(s); \'all\' will process all deps_os '
1749 'references')
haitao.feng@intel.com306080c2012-05-04 13:11:29 +00001750 parser.remove_option('--jobs')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001751 (options, args) = parser.parse_args(args)
iannucci@chromium.org50395ea2013-04-04 04:47:42 +00001752 # Force jobs to 1 so the stdout is not annotated with the thread ids
haitao.feng@intel.com306080c2012-05-04 13:11:29 +00001753 options.jobs = 1
kbr@google.comab318592009-09-04 00:54:55 +00001754 client = GClient.LoadCurrentConfig(options)
1755 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001756 raise gclient_utils.Error('client not configured; see \'gclient config\'')
kbr@google.comab318592009-09-04 00:54:55 +00001757 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00001758 client.PrintLocationAndContents()
kbr@google.comab318592009-09-04 00:54:55 +00001759 return client.RunOnDeps('pack', args)
1760
1761
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001762def CMDstatus(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001763 """Shows modification status for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001764 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1765 help='override deps for the specified (comma-separated) '
1766 'platform(s); \'all\' will process all deps_os '
1767 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001768 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001769 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001770 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001771 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001772 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00001773 client.PrintLocationAndContents()
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001774 return client.RunOnDeps('status', args)
1775
1776
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001777@subcommand.epilog("""Examples:
maruel@chromium.org79692d62010-05-14 18:57:13 +00001778 gclient sync
1779 update files from SCM according to current configuration,
1780 *for modules which have changed since last update or sync*
1781 gclient sync --force
1782 update files from SCM according to current configuration, for
1783 all modules (useful for recovering files deleted from local copy)
1784 gclient sync --revision src@31000
1785 update src directory to r31000
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00001786
1787JSON output format:
1788If the --output-json option is specified, the following document structure will
1789be emitted to the provided file. 'null' entries may occur for subprojects which
1790are present in the gclient solution, but were not processed (due to custom_deps,
1791os_deps, etc.)
1792
1793{
1794 "solutions" : {
1795 "<name>": { # <name> is the posix-normalized path to the solution.
agabled437d762016-10-17 09:35:11 -07001796 "revision": [<git id hex string>|null],
1797 "scm": ["git"|null],
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00001798 }
1799 }
1800}
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001801""")
1802def CMDsync(parser, args):
1803 """Checkout/update all modules."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001804 parser.add_option('-f', '--force', action='store_true',
1805 help='force update even for unchanged modules')
1806 parser.add_option('-n', '--nohooks', action='store_true',
1807 help='don\'t run hooks after the update is complete')
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001808 parser.add_option('-p', '--noprehooks', action='store_true',
1809 help='don\'t run pre-DEPS hooks', default=False)
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001810 parser.add_option('-r', '--revision', action='append',
1811 dest='revisions', metavar='REV', default=[],
1812 help='Enforces revision/hash for the solutions with the '
1813 'format src@rev. The src@ part is optional and can be '
1814 'skipped. -r can be used multiple times when .gclient '
1815 'has multiple solutions configured and will work even '
agablea98a6cd2016-11-15 14:30:10 -08001816 'if the src@ part is skipped.')
maruel@chromium.org794207e2013-03-08 15:29:43 +00001817 parser.add_option('--with_branch_heads', action='store_true',
1818 help='Clone git "branch_heads" refspecs in addition to '
1819 'the default refspecs. This adds about 1/2GB to a '
1820 'full checkout. (git only)')
szager@chromium.org8d3348f2014-08-19 22:49:16 +00001821 parser.add_option('--with_tags', action='store_true',
1822 help='Clone git tags in addition to the default refspecs.')
agable2697cd12016-06-28 10:23:53 -07001823 parser.add_option('-H', '--head', action='store_true',
agablea98a6cd2016-11-15 14:30:10 -08001824 help='DEPRECATED: only made sense with safesync urls.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001825 parser.add_option('-D', '--delete_unversioned_trees', action='store_true',
steveblock@chromium.org98e69452012-02-16 16:36:43 +00001826 help='Deletes from the working copy any dependencies that '
1827 'have been removed since the last sync, as long as '
1828 'there are no local modifications. When used with '
1829 '--force, such dependencies are removed even if they '
1830 'have local modifications. When used with --reset, '
1831 'all untracked directories are removed from the '
tnagel@chromium.orga2aaa632014-02-28 21:47:27 +00001832 'working copy, excluding those which are explicitly '
steveblock@chromium.org98e69452012-02-16 16:36:43 +00001833 'ignored in the repository.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001834 parser.add_option('-R', '--reset', action='store_true',
1835 help='resets any local changes before updating (git only)')
bauerb@chromium.org2aad1b22011-07-22 12:00:41 +00001836 parser.add_option('-M', '--merge', action='store_true',
1837 help='merge upstream changes instead of trying to '
1838 'fast-forward or rebase')
dnj@chromium.org5b23e872015-02-20 21:25:57 +00001839 parser.add_option('-A', '--auto_rebase', action='store_true',
1840 help='Automatically rebase repositories against local '
1841 'checkout during update (git only).')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001842 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1843 help='override deps for the specified (comma-separated) '
1844 'platform(s); \'all\' will process all deps_os '
1845 'references')
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +00001846 parser.add_option('--upstream', action='store_true',
1847 help='Make repo state match upstream branch.')
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00001848 parser.add_option('--output-json',
1849 help='Output a json document to this path containing '
1850 'summary information about the sync.')
primiano@chromium.org5439ea52014-08-06 17:18:18 +00001851 parser.add_option('--no-history', action='store_true',
1852 help='GIT ONLY - Reduces the size/time of the checkout at '
1853 'the cost of no history. Requires Git 1.9+')
hinoka@chromium.org46b87412014-05-15 00:42:05 +00001854 parser.add_option('--shallow', action='store_true',
1855 help='GIT ONLY - Do a shallow clone into the cache dir. '
1856 'Requires Git 1.9+')
e.hakkinen@samsung.come8bc1aa2015-04-08 08:00:37 +00001857 parser.add_option('--no_bootstrap', '--no-bootstrap',
1858 action='store_true',
1859 help='Don\'t bootstrap from Google Storage.')
hinoka@chromium.org8a10f6d2014-06-23 18:38:57 +00001860 parser.add_option('--ignore_locks', action='store_true',
1861 help='GIT ONLY - Ignore cache locks.')
iannucci@chromium.org30a07982016-04-07 21:35:19 +00001862 parser.add_option('--break_repo_locks', action='store_true',
1863 help='GIT ONLY - Forcibly remove repo locks (e.g. '
1864 'index.lock). This should only be used if you know for '
1865 'certain that this invocation of gclient is the only '
1866 'thing operating on the git repos (e.g. on a bot).')
nodir@chromium.org5b48e482016-03-18 20:27:54 +00001867 parser.add_option('--lock_timeout', type='int', default=5000,
szager@chromium.orgdbb6f822016-02-02 22:59:30 +00001868 help='GIT ONLY - Deadline (in seconds) to wait for git '
nodir@chromium.org5b48e482016-03-18 20:27:54 +00001869 'cache lock to become available. Default is %default.')
agabled437d762016-10-17 09:35:11 -07001870 # TODO(agable): Remove these when the oldest CrOS release milestone is M56.
1871 parser.add_option('-t', '--transitive', action='store_true',
1872 help='DEPRECATED: This is a no-op.')
sdefresne69b1be12016-10-18 05:48:02 -07001873 parser.add_option('-m', '--manually_grab_svn_rev', action='store_true',
agabled437d762016-10-17 09:35:11 -07001874 help='DEPRECATED: This is a no-op.')
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +02001875 parser.add_option('--validate-syntax', action='store_true',
1876 help='Validate the .gclient and DEPS syntax')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001877 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001878 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001879
1880 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001881 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001882
smutae7ea312016-07-18 11:59:41 -07001883 if options.revisions and options.head:
1884 # TODO(maruel): Make it a parser.error if it doesn't break any builder.
1885 print('Warning: you cannot use both --head and --revision')
1886
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001887 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00001888 client.PrintLocationAndContents()
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00001889 ret = client.RunOnDeps('update', args)
1890 if options.output_json:
1891 slns = {}
1892 for d in client.subtree(True):
1893 normed = d.name.replace('\\', '/').rstrip('/') + '/'
1894 slns[normed] = {
1895 'revision': d.got_revision,
1896 'scm': d.used_scm.name if d.used_scm else None,
hinoka@chromium.org17db9052014-05-10 01:11:29 +00001897 'url': str(d.url) if d.url else None,
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00001898 }
1899 with open(options.output_json, 'wb') as f:
1900 json.dump({'solutions': slns}, f)
1901 return ret
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001902
1903
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001904CMDupdate = CMDsync
1905
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001906
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +02001907def CMDvalidate(parser, args):
1908 """Validates the .gclient and DEPS syntax."""
1909 options, args = parser.parse_args(args)
1910 options.validate_syntax = True
1911 client = GClient.LoadCurrentConfig(options)
1912 rv = client.RunOnDeps('validate', args)
1913 if rv == 0:
1914 print('validate: SUCCESS')
1915 else:
1916 print('validate: FAILURE')
1917 return rv
1918
1919
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001920def CMDdiff(parser, args):
1921 """Displays local diff for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001922 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1923 help='override deps for the specified (comma-separated) '
1924 'platform(s); \'all\' will process all deps_os '
1925 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001926 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001927 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001928 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001929 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001930 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00001931 client.PrintLocationAndContents()
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001932 return client.RunOnDeps('diff', args)
1933
1934
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001935def CMDrevert(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001936 """Reverts all modifications in every dependencies.
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001937
1938 That's the nuclear option to get back to a 'clean' state. It removes anything
agabled437d762016-10-17 09:35:11 -07001939 that shows up in git status."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001940 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1941 help='override deps for the specified (comma-separated) '
1942 'platform(s); \'all\' will process all deps_os '
1943 'references')
1944 parser.add_option('-n', '--nohooks', action='store_true',
1945 help='don\'t run hooks after the revert is complete')
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001946 parser.add_option('-p', '--noprehooks', action='store_true',
1947 help='don\'t run pre-DEPS hooks', default=False)
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +00001948 parser.add_option('--upstream', action='store_true',
1949 help='Make repo state match upstream branch.')
iannucci@chromium.orgbf525dc2016-04-07 22:00:28 +00001950 parser.add_option('--break_repo_locks', action='store_true',
1951 help='GIT ONLY - Forcibly remove repo locks (e.g. '
1952 'index.lock). This should only be used if you know for '
1953 'certain that this invocation of gclient is the only '
1954 'thing operating on the git repos (e.g. on a bot).')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001955 (options, args) = parser.parse_args(args)
1956 # --force is implied.
1957 options.force = True
steveblock@chromium.org98e69452012-02-16 16:36:43 +00001958 options.reset = False
1959 options.delete_unversioned_trees = False
agablec903d732016-07-26 09:07:24 -07001960 options.merge = False
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001961 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001962 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001963 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001964 return client.RunOnDeps('revert', args)
1965
1966
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001967def CMDrunhooks(parser, args):
1968 """Runs hooks for files that have been modified in the local working copy."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001969 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1970 help='override deps for the specified (comma-separated) '
1971 'platform(s); \'all\' will process all deps_os '
1972 'references')
1973 parser.add_option('-f', '--force', action='store_true', default=True,
1974 help='Deprecated. No effect.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001975 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001976 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001977 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001978 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001979 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00001980 client.PrintLocationAndContents()
maruel@chromium.org5df6a462009-08-28 18:52:26 +00001981 options.force = True
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001982 options.nohooks = False
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001983 return client.RunOnDeps('runhooks', args)
1984
1985
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001986def CMDrevinfo(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001987 """Outputs revision info mapping for the client and its dependencies.
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001988
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001989 This allows the capture of an overall 'revision' for the source tree that
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001990 can be used to reproduce the same tree in the future. It is only useful for
agabled437d762016-10-17 09:35:11 -07001991 'unpinned dependencies', i.e. DEPS/deps references without a git hash.
1992 A git branch name isn't 'pinned' since the actual commit can change.
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001993 """
1994 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1995 help='override deps for the specified (comma-separated) '
1996 'platform(s); \'all\' will process all deps_os '
1997 'references')
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001998 parser.add_option('-a', '--actual', action='store_true',
1999 help='gets the actual checked out revisions instead of the '
2000 'ones specified in the DEPS and .gclient files')
maruel@chromium.org9eda4112010-06-11 18:56:10 +00002001 parser.add_option('-s', '--snapshot', action='store_true',
2002 help='creates a snapshot .gclient file of the current '
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00002003 'version of all repositories to reproduce the tree, '
2004 'implies -a')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002005 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002006 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002007 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002008 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002009 client.PrintRevInfo()
maruel@chromium.org79692d62010-05-14 18:57:13 +00002010 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002011
2012
szager@google.comb9a78d32012-03-13 18:46:21 +00002013def CMDhookinfo(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002014 """Outputs the hooks that would be run by `gclient runhooks`."""
szager@google.comb9a78d32012-03-13 18:46:21 +00002015 (options, args) = parser.parse_args(args)
2016 options.force = True
2017 client = GClient.LoadCurrentConfig(options)
2018 if not client:
2019 raise gclient_utils.Error('client not configured; see \'gclient config\'')
2020 client.RunOnDeps(None, [])
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002021 print('; '.join(' '.join(hook) for hook in client.GetHooks(options)))
szager@google.comb9a78d32012-03-13 18:46:21 +00002022 return 0
2023
2024
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00002025def CMDverify(parser, args):
2026 """Verifies the DEPS file deps are only from allowed_hosts."""
2027 (options, args) = parser.parse_args(args)
2028 client = GClient.LoadCurrentConfig(options)
2029 if not client:
2030 raise gclient_utils.Error('client not configured; see \'gclient config\'')
2031 client.RunOnDeps(None, [])
2032 # Look at each first-level dependency of this gclient only.
2033 for dep in client.dependencies:
2034 bad_deps = dep.findDepsFromNotAllowedHosts()
2035 if not bad_deps:
2036 continue
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002037 print("There are deps from not allowed hosts in file %s" % dep.deps_file)
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00002038 for bad_dep in bad_deps:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002039 print("\t%s at %s" % (bad_dep.name, bad_dep.url))
2040 print("allowed_hosts:", ', '.join(dep.allowed_hosts))
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00002041 sys.stdout.flush()
2042 raise gclient_utils.Error(
2043 'dependencies from disallowed hosts; check your DEPS file.')
2044 return 0
2045
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002046class OptionParser(optparse.OptionParser):
szager@chromium.orge2e03202012-07-31 18:05:16 +00002047 gclientfile_default = os.environ.get('GCLIENT_FILE', '.gclient')
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002048
2049 def __init__(self, **kwargs):
2050 optparse.OptionParser.__init__(
2051 self, version='%prog ' + __version__, **kwargs)
2052
2053 # Some arm boards have issues with parallel sync.
2054 if platform.machine().startswith('arm'):
2055 jobs = 1
2056 else:
2057 jobs = max(8, gclient_utils.NumLocalCpus())
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002058
2059 self.add_option(
2060 '-j', '--jobs', default=jobs, type='int',
2061 help='Specify how many SCM commands can run in parallel; defaults to '
tnagel@chromium.orga2aaa632014-02-28 21:47:27 +00002062 '%default on this machine')
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002063 self.add_option(
2064 '-v', '--verbose', action='count', default=0,
2065 help='Produces additional output for diagnostics. Can be used up to '
2066 'three times for more logging info.')
2067 self.add_option(
2068 '--gclientfile', dest='config_filename',
2069 help='Specify an alternate %s file' % self.gclientfile_default)
2070 self.add_option(
2071 '--spec',
2072 help='create a gclient file containing the provided string. Due to '
2073 'Cygwin/Python brokenness, it can\'t contain any newlines.')
2074 self.add_option(
2075 '--no-nag-max', default=False, action='store_true',
scottmg@chromium.orgf547c802013-09-27 17:55:26 +00002076 help='Ignored for backwards compatibility.')
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002077
2078 def parse_args(self, args=None, values=None):
2079 """Integrates standard options processing."""
2080 options, args = optparse.OptionParser.parse_args(self, args, values)
2081 levels = [logging.ERROR, logging.WARNING, logging.INFO, logging.DEBUG]
2082 logging.basicConfig(
2083 level=levels[min(options.verbose, len(levels) - 1)],
maruel@chromium.org0895b752011-08-26 20:40:33 +00002084 format='%(module)s(%(lineno)d) %(funcName)s:%(message)s')
szager@chromium.orge2e03202012-07-31 18:05:16 +00002085 if options.config_filename and options.spec:
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002086 self.error('Cannot specifiy both --gclientfile and --spec')
rdsmith@chromium.orgd9591f02014-02-05 19:28:20 +00002087 if (options.config_filename and
2088 options.config_filename != os.path.basename(options.config_filename)):
2089 self.error('--gclientfile target must be a filename, not a path')
szager@chromium.orge2e03202012-07-31 18:05:16 +00002090 if not options.config_filename:
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002091 options.config_filename = self.gclientfile_default
maruel@chromium.org0895b752011-08-26 20:40:33 +00002092 options.entries_filename = options.config_filename + '_entries'
2093 if options.jobs < 1:
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002094 self.error('--jobs must be 1 or higher')
maruel@chromium.org0895b752011-08-26 20:40:33 +00002095
2096 # These hacks need to die.
2097 if not hasattr(options, 'revisions'):
2098 # GClient.RunOnDeps expects it even if not applicable.
2099 options.revisions = []
smutae7ea312016-07-18 11:59:41 -07002100 if not hasattr(options, 'head'):
2101 options.head = None
maruel@chromium.org0895b752011-08-26 20:40:33 +00002102 if not hasattr(options, 'nohooks'):
2103 options.nohooks = True
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00002104 if not hasattr(options, 'noprehooks'):
2105 options.noprehooks = True
maruel@chromium.org0895b752011-08-26 20:40:33 +00002106 if not hasattr(options, 'deps_os'):
2107 options.deps_os = None
maruel@chromium.org0895b752011-08-26 20:40:33 +00002108 if not hasattr(options, 'force'):
2109 options.force = None
2110 return (options, args)
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002111
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002112
2113def disable_buffering():
2114 # Make stdout auto-flush so buildbot doesn't kill us during lengthy
2115 # operations. Python as a strong tendency to buffer sys.stdout.
2116 sys.stdout = gclient_utils.MakeFileAutoFlush(sys.stdout)
2117 # Make stdout annotated with the thread ids.
2118 sys.stdout = gclient_utils.MakeFileAnnotated(sys.stdout)
maruel@chromium.org0895b752011-08-26 20:40:33 +00002119
2120
sbc@chromium.org013731e2015-02-26 18:28:43 +00002121def main(argv):
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002122 """Doesn't parse the arguments here, just find the right subcommand to
2123 execute."""
maruel@chromium.org82798cb2012-02-23 18:16:12 +00002124 if sys.hexversion < 0x02060000:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002125 print(
maruel@chromium.org82798cb2012-02-23 18:16:12 +00002126 '\nYour python version %s is unsupported, please upgrade.\n' %
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002127 sys.version.split(' ', 1)[0],
2128 file=sys.stderr)
maruel@chromium.org82798cb2012-02-23 18:16:12 +00002129 return 2
bcwhite@chromium.org6683ab42013-02-11 16:13:47 +00002130 if not sys.executable:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002131 print(
2132 '\nPython cannot find the location of it\'s own executable.\n',
2133 file=sys.stderr)
bcwhite@chromium.org6683ab42013-02-11 16:13:47 +00002134 return 2
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002135 fix_encoding.fix_encoding()
2136 disable_buffering()
iannucci@chromium.org596cd5c2016-04-04 21:34:39 +00002137 setup_color.init()
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002138 dispatcher = subcommand.CommandDispatcher(__name__)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00002139 try:
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002140 return dispatcher.execute(OptionParser(), argv)
xusydoc@chromium.org2fd6c3f2013-05-03 21:57:55 +00002141 except KeyboardInterrupt:
2142 gclient_utils.GClientChildren.KillAllRemainingChildren()
2143 raise
vapier@chromium.orga81a56e2015-11-11 07:56:13 +00002144 except (gclient_utils.Error, subprocess2.CalledProcessError) as e:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002145 print('Error: %s' % str(e), file=sys.stderr)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00002146 return 1
borenet@google.com6a9b1682014-03-24 18:35:23 +00002147 finally:
2148 gclient_utils.PrintWarnings()
sbc@chromium.org013731e2015-02-26 18:28:43 +00002149 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002150
2151
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00002152if '__main__' == __name__:
sbc@chromium.org013731e2015-02-26 18:28:43 +00002153 try:
2154 sys.exit(main(sys.argv[1:]))
2155 except KeyboardInterrupt:
2156 sys.stderr.write('interrupted\n')
2157 sys.exit(1)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002158
2159# vim: ts=2:sw=2:tw=80:et: