blob: ddf376c6d07a3d348584a12e119ec159336df31e [file] [log] [blame]
iannucci@chromium.org405b87e2015-11-12 18:08:34 +00001#!/usr/bin/env python
thakis@chromium.org4f474b62012-01-18 01:31:29 +00002# Copyright (c) 2012 The Chromium Authors. All rights reserved.
maruel@chromium.orgba551772010-02-03 18:21:42 +00003# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00005
agabled437d762016-10-17 09:35:11 -07006"""Meta checkout dependency manager for Git."""
maruel@chromium.org39c0b222013-08-17 16:57:01 +00007# Files
8# .gclient : Current client configuration, written by 'config' command.
9# Format is a Python script defining 'solutions', a list whose
10# entries each are maps binding the strings "name" and "url"
11# to strings specifying the name and location of the client
12# module, as well as "custom_deps" to a map similar to the
13# deps section of the DEPS file below, as well as
14# "custom_hooks" to a list similar to the hooks sections of
15# the DEPS file below.
16# .gclient_entries : A cache constructed by 'update' command. Format is a
17# Python script defining 'entries', a list of the names
18# of all modules in the client
19# <module>/DEPS : Python script defining var 'deps' as a map from each
20# requisite submodule name to a URL where it can be found (via
21# one SCM)
22#
23# Hooks
24# .gclient and DEPS files may optionally contain a list named "hooks" to
25# allow custom actions to be performed based on files that have changed in the
26# working copy as a result of a "sync"/"update" or "revert" operation. This
27# can be prevented by using --nohooks (hooks run by default). Hooks can also
28# be forced to run with the "runhooks" operation. If "sync" is run with
29# --force, all known but not suppressed hooks will run regardless of the state
30# of the working copy.
31#
32# Each item in a "hooks" list is a dict, containing these two keys:
33# "pattern" The associated value is a string containing a regular
34# expression. When a file whose pathname matches the expression
35# is checked out, updated, or reverted, the hook's "action" will
36# run.
37# "action" A list describing a command to run along with its arguments, if
38# any. An action command will run at most one time per gclient
39# invocation, regardless of how many files matched the pattern.
40# The action is executed in the same directory as the .gclient
41# file. If the first item in the list is the string "python",
42# the current Python interpreter (sys.executable) will be used
43# to run the command. If the list contains string
44# "$matching_files" it will be removed from the list and the list
45# will be extended by the list of matching files.
46# "name" An optional string specifying the group to which a hook belongs
47# for overriding and organizing.
48#
49# Example:
50# hooks = [
51# { "pattern": "\\.(gif|jpe?g|pr0n|png)$",
52# "action": ["python", "image_indexer.py", "--all"]},
53# { "pattern": ".",
54# "name": "gyp",
55# "action": ["python", "src/build/gyp_chromium"]},
56# ]
57#
borenet@google.com2d1ee9e2013-10-15 08:13:16 +000058# Pre-DEPS Hooks
59# DEPS files may optionally contain a list named "pre_deps_hooks". These are
60# the same as normal hooks, except that they run before the DEPS are
61# processed. Pre-DEPS run with "sync" and "revert" unless the --noprehooks
62# flag is used.
rdsmith@chromium.orgd9591f02014-02-05 19:28:20 +000063#
maruel@chromium.org39c0b222013-08-17 16:57:01 +000064# Specifying a target OS
65# An optional key named "target_os" may be added to a gclient file to specify
66# one or more additional operating systems that should be considered when
Scott Grahamc4826742017-05-11 16:59:23 -070067# processing the deps_os/hooks_os dict of a DEPS file.
maruel@chromium.org39c0b222013-08-17 16:57:01 +000068#
69# Example:
70# target_os = [ "android" ]
71#
72# If the "target_os_only" key is also present and true, then *only* the
73# operating systems listed in "target_os" will be used.
74#
75# Example:
76# target_os = [ "ios" ]
77# target_os_only = True
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000078
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +000079from __future__ import print_function
80
maruel@chromium.org39c0b222013-08-17 16:57:01 +000081__version__ = '0.7'
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000082
szager@chromium.org7b8b6de2014-08-23 00:57:31 +000083import ast
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +020084import collections
maruel@chromium.org9e5317a2010-08-13 20:35:11 +000085import copy
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +000086import json
maruel@chromium.org754960e2009-09-21 12:31:05 +000087import logging
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000088import optparse
89import os
bradnelson@google.com4949dab2012-04-19 16:41:07 +000090import platform
maruel@chromium.org621939b2010-08-10 20:12:00 +000091import posixpath
msb@chromium.org2e38de72009-09-28 17:04:47 +000092import pprint
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000093import re
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000094import sys
ilevy@chromium.orgc28d3772013-07-12 19:42:37 +000095import time
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000096import urllib
bradnelson@google.com4949dab2012-04-19 16:41:07 +000097import urlparse
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000098
maruel@chromium.org35625c72011-03-23 17:34:02 +000099import fix_encoding
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +0200100import gclient_eval
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000101import gclient_scm
102import gclient_utils
szager@chromium.org848fd492014-04-09 19:06:44 +0000103import git_cache
nasser@codeaurora.org1f7a3d12010-02-04 15:11:50 +0000104from third_party.repo.progress import Progress
maruel@chromium.org39c0b222013-08-17 16:57:01 +0000105import subcommand
maruel@chromium.org31cb48a2011-04-04 18:01:36 +0000106import subprocess2
iannucci@chromium.org596cd5c2016-04-04 21:34:39 +0000107import setup_color
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000108
szager@chromium.org7b8b6de2014-08-23 00:57:31 +0000109CHROMIUM_SRC_URL = 'https://chromium.googlesource.com/chromium/src.git'
110
111
112def ast_dict_index(dnode, key):
113 """Search an ast.Dict for the argument key, and return its index."""
114 idx = [i for i in range(len(dnode.keys)) if (
115 type(dnode.keys[i]) is ast.Str and dnode.keys[i].s == key)]
116 if not idx:
117 return -1
118 elif len(idx) > 1:
119 raise gclient_utils.Error('Multiple dict entries with same key in AST')
120 return idx[-1]
121
122def ast2str(node, indent=0):
123 """Return a pretty-printed rendition of an ast.Node."""
124 t = type(node)
125 if t is ast.Module:
126 return '\n'.join([ast2str(x, indent) for x in node.body])
127 elif t is ast.Assign:
128 return ((' ' * indent) +
129 ' = '.join([ast2str(x) for x in node.targets] +
130 [ast2str(node.value, indent)]) + '\n')
131 elif t is ast.Name:
132 return node.id
133 elif t is ast.List:
134 if not node.elts:
135 return '[]'
136 elif len(node.elts) == 1:
137 return '[' + ast2str(node.elts[0], indent) + ']'
138 return ('[\n' + (' ' * (indent + 1)) +
139 (',\n' + (' ' * (indent + 1))).join(
140 [ast2str(x, indent + 1) for x in node.elts]) +
141 '\n' + (' ' * indent) + ']')
142 elif t is ast.Dict:
143 if not node.keys:
144 return '{}'
145 elif len(node.keys) == 1:
146 return '{%s: %s}' % (ast2str(node.keys[0]),
147 ast2str(node.values[0], indent + 1))
148 return ('{\n' + (' ' * (indent + 1)) +
149 (',\n' + (' ' * (indent + 1))).join(
150 ['%s: %s' % (ast2str(node.keys[i]),
151 ast2str(node.values[i], indent + 1))
152 for i in range(len(node.keys))]) +
153 '\n' + (' ' * indent) + '}')
154 elif t is ast.Str:
155 return "'%s'" % node.s
156 else:
157 raise gclient_utils.Error("Unexpected AST node at line %d, column %d: %s"
158 % (node.lineno, node.col_offset, t))
159
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000160
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +0200161class GNException(Exception):
162 pass
163
164
165def ToGNString(value, allow_dicts = True):
166 """Returns a stringified GN equivalent of the Python value.
167
168 allow_dicts indicates if this function will allow converting dictionaries
169 to GN scopes. This is only possible at the top level, you can't nest a
170 GN scope in a list, so this should be set to False for recursive calls."""
171 if isinstance(value, basestring):
172 if value.find('\n') >= 0:
173 raise GNException("Trying to print a string with a newline in it.")
174 return '"' + \
175 value.replace('\\', '\\\\').replace('"', '\\"').replace('$', '\\$') + \
176 '"'
177
178 if isinstance(value, unicode):
179 return ToGNString(value.encode('utf-8'))
180
181 if isinstance(value, bool):
182 if value:
183 return "true"
184 return "false"
185
186 # NOTE: some type handling removed compared to chromium/src copy.
187
188 raise GNException("Unsupported type when printing to GN.")
189
190
maruel@chromium.org116704f2010-06-11 17:34:38 +0000191class GClientKeywords(object):
maruel@chromium.org116704f2010-06-11 17:34:38 +0000192 class VarImpl(object):
193 def __init__(self, custom_vars, local_scope):
194 self._custom_vars = custom_vars
195 self._local_scope = local_scope
196
197 def Lookup(self, var_name):
198 """Implements the Var syntax."""
199 if var_name in self._custom_vars:
200 return self._custom_vars[var_name]
201 elif var_name in self._local_scope.get("vars", {}):
202 return self._local_scope["vars"][var_name]
203 raise gclient_utils.Error("Var is not defined: %s" % var_name)
204
205
maruel@chromium.org064186c2011-09-27 23:53:33 +0000206class DependencySettings(GClientKeywords):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000207 """Immutable configuration settings."""
208 def __init__(
agablea98a6cd2016-11-15 14:30:10 -0800209 self, parent, url, managed, custom_deps, custom_vars,
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +0200210 custom_hooks, deps_file, should_process, relative,
211 condition, condition_value):
maruel@chromium.org064186c2011-09-27 23:53:33 +0000212 GClientKeywords.__init__(self)
213
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000214 # These are not mutable:
215 self._parent = parent
mmoss@chromium.org8f93f792014-08-26 23:24:09 +0000216 self._deps_file = deps_file
maruel@chromium.org064186c2011-09-27 23:53:33 +0000217 self._url = url
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +0200218 # The condition as string (or None). Useful to keep e.g. for flatten.
219 self._condition = condition
220 # Boolean value of the condition. If there's no condition, just True.
221 self._condition_value = condition_value
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000222 # 'managed' determines whether or not this dependency is synced/updated by
223 # gclient after gclient checks it out initially. The difference between
224 # 'managed' and 'should_process' is that the user specifies 'managed' via
smutae7ea312016-07-18 11:59:41 -0700225 # the --unmanaged command-line flag or a .gclient config, where
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000226 # 'should_process' is dynamically set by gclient if it goes over its
227 # recursion limit and controls gclient's behavior so it does not misbehave.
228 self._managed = managed
229 self._should_process = should_process
agabledce6ddc2016-09-08 10:02:16 -0700230 # If this is a recursed-upon sub-dependency, and the parent has
231 # use_relative_paths set, then this dependency should check out its own
232 # dependencies relative to that parent's path for this, rather than
233 # relative to the .gclient file.
234 self._relative = relative
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +0000235 # This is a mutable value which has the list of 'target_os' OSes listed in
236 # the current deps file.
237 self.local_target_os = None
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000238
239 # These are only set in .gclient and not in DEPS files.
240 self._custom_vars = custom_vars or {}
241 self._custom_deps = custom_deps or {}
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000242 self._custom_hooks = custom_hooks or []
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000243
maruel@chromium.org064186c2011-09-27 23:53:33 +0000244 # Post process the url to remove trailing slashes.
245 if isinstance(self._url, basestring):
246 # urls are sometime incorrectly written as proto://host/path/@rev. Replace
247 # it to proto://host/path@rev.
maruel@chromium.org064186c2011-09-27 23:53:33 +0000248 self._url = self._url.replace('/@', '@')
Paweł Hajdan, Jr7e9303b2017-05-23 14:38:27 +0200249 elif not isinstance(self._url, (None.__class__)):
maruel@chromium.org064186c2011-09-27 23:53:33 +0000250 raise gclient_utils.Error(
Paweł Hajdan, Jr7e9303b2017-05-23 14:38:27 +0200251 ('dependency url must be either string or None, '
252 'instead of %s') % self._url.__class__.__name__)
mmoss@chromium.orgd0b272b2013-01-30 23:55:33 +0000253 # Make any deps_file path platform-appropriate.
254 for sep in ['/', '\\']:
255 self._deps_file = self._deps_file.replace(sep, os.sep)
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000256
257 @property
258 def deps_file(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000259 return self._deps_file
260
261 @property
262 def managed(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000263 return self._managed
264
265 @property
266 def parent(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000267 return self._parent
268
269 @property
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000270 def root(self):
271 """Returns the root node, a GClient object."""
272 if not self.parent:
273 # This line is to signal pylint that it could be a GClient instance.
274 return self or GClient(None, None)
275 return self.parent.root
276
277 @property
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000278 def should_process(self):
279 """True if this dependency should be processed, i.e. checked out."""
280 return self._should_process
281
282 @property
283 def custom_vars(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000284 return self._custom_vars.copy()
285
286 @property
287 def custom_deps(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000288 return self._custom_deps.copy()
289
maruel@chromium.org064186c2011-09-27 23:53:33 +0000290 @property
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000291 def custom_hooks(self):
292 return self._custom_hooks[:]
293
294 @property
maruel@chromium.org064186c2011-09-27 23:53:33 +0000295 def url(self):
296 return self._url
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000297
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000298 @property
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +0200299 def condition(self):
300 return self._condition
301
302 @property
303 def condition_value(self):
304 return self._condition_value
305
306 @property
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +0000307 def target_os(self):
308 if self.local_target_os is not None:
309 return tuple(set(self.local_target_os).union(self.parent.target_os))
310 else:
311 return self.parent.target_os
312
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000313 def get_custom_deps(self, name, url):
314 """Returns a custom deps if applicable."""
315 if self.parent:
316 url = self.parent.get_custom_deps(name, url)
317 # None is a valid return value to disable a dependency.
318 return self.custom_deps.get(name, url)
319
maruel@chromium.org064186c2011-09-27 23:53:33 +0000320
321class Dependency(gclient_utils.WorkItem, DependencySettings):
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000322 """Object that represents a dependency checkout."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000323
agablea98a6cd2016-11-15 14:30:10 -0800324 def __init__(self, parent, name, url, managed, custom_deps,
agabledce6ddc2016-09-08 10:02:16 -0700325 custom_vars, custom_hooks, deps_file, should_process,
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +0200326 relative, condition, condition_value):
maruel@chromium.org6ca8bf82011-09-19 23:04:30 +0000327 gclient_utils.WorkItem.__init__(self, name)
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000328 DependencySettings.__init__(
agablea98a6cd2016-11-15 14:30:10 -0800329 self, parent, url, managed, custom_deps, custom_vars,
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +0200330 custom_hooks, deps_file, should_process, relative,
331 condition, condition_value)
maruel@chromium.org68988972011-09-20 14:11:42 +0000332
333 # This is in both .gclient and DEPS files:
maruel@chromium.org064186c2011-09-27 23:53:33 +0000334 self._deps_hooks = []
maruel@chromium.org68988972011-09-20 14:11:42 +0000335
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000336 self._pre_deps_hooks = []
337
maruel@chromium.org68988972011-09-20 14:11:42 +0000338 # Calculates properties:
maruel@chromium.org064186c2011-09-27 23:53:33 +0000339 self._parsed_url = None
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +0000340 self._dependencies = []
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200341 # Keep track of original values, before post-processing (e.g. deps_os).
342 self._orig_dependencies = []
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +0200343 self._vars = {}
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +0200344 self._os_dependencies = {}
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200345
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000346 # A cache of the files affected by the current operation, necessary for
347 # hooks.
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000348 self._file_list = []
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000349 # List of host names from which dependencies are allowed.
350 # Default is an empty set, meaning unspecified in DEPS file, and hence all
351 # hosts will be allowed. Non-empty set means whitelist of hosts.
352 # allowed_hosts var is scoped to its DEPS file, and so it isn't recursive.
353 self._allowed_hosts = frozenset()
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +0200354 # Spec for .gni output to write (if any).
355 self._gn_args_file = None
356 self._gn_args = []
maruel@chromium.org85c2a192010-07-22 21:14:43 +0000357 # If it is not set to True, the dependency wasn't processed for its child
358 # dependency, i.e. its DEPS wasn't read.
maruel@chromium.org064186c2011-09-27 23:53:33 +0000359 self._deps_parsed = False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000360 # This dependency has been processed, i.e. checked out
maruel@chromium.org064186c2011-09-27 23:53:33 +0000361 self._processed = False
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000362 # This dependency had its pre-DEPS hooks run
363 self._pre_deps_hooks_ran = False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000364 # This dependency had its hook run
maruel@chromium.org064186c2011-09-27 23:53:33 +0000365 self._hooks_ran = False
kustermann@google.coma692e8f2013-04-18 08:32:04 +0000366 # This is the scm used to checkout self.url. It may be used by dependencies
367 # to get the datetime of the revision we checked out.
368 self._used_scm = None
szager@chromium.org4ad264b2014-05-20 04:43:47 +0000369 self._used_revision = None
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +0000370 # The actual revision we ended up getting, or None if that information is
371 # unavailable
372 self._got_revision = None
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000373
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000374 # This is a mutable value that overrides the normal recursion limit for this
375 # dependency. It is read from the actual DEPS file so cannot be set on
376 # class instantiation.
377 self.recursion_override = None
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000378 # recursedeps is a mutable value that selectively overrides the default
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000379 # 'no recursion' setting on a dep-by-dep basis. It will replace
380 # recursion_override.
iannucci@chromium.orgafa11ac2016-05-04 22:17:34 +0000381 #
382 # It will be a dictionary of {deps_name: {"deps_file": depfile_name}} or
383 # None.
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000384 self.recursedeps = None
hinoka885e5b12016-06-08 14:40:09 -0700385 # This is inherited from WorkItem. We want the URL to be a resource.
386 if url and isinstance(url, basestring):
387 # The url is usually given to gclient either as https://blah@123
qyearsley12fa6ff2016-08-24 09:18:40 -0700388 # or just https://blah. The @123 portion is irrelevant.
hinoka885e5b12016-06-08 14:40:09 -0700389 self.resources.append(url.split('@')[0])
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000390
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000391 if not self.name and self.parent:
392 raise gclient_utils.Error('Dependency without name')
393
maruel@chromium.org470b5432011-10-11 18:18:19 +0000394 @property
395 def requirements(self):
396 """Calculate the list of requirements."""
397 requirements = set()
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000398 # self.parent is implicitly a requirement. This will be recursive by
399 # definition.
400 if self.parent and self.parent.name:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000401 requirements.add(self.parent.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000402
403 # For a tree with at least 2 levels*, the leaf node needs to depend
404 # on the level higher up in an orderly way.
405 # This becomes messy for >2 depth as the DEPS file format is a dictionary,
406 # thus unsorted, while the .gclient format is a list thus sorted.
407 #
408 # * _recursion_limit is hard coded 2 and there is no hope to change this
409 # value.
410 #
411 # Interestingly enough, the following condition only works in the case we
412 # want: self is a 2nd level node. 3nd level node wouldn't need this since
413 # they already have their parent as a requirement.
maruel@chromium.org470b5432011-10-11 18:18:19 +0000414 if self.parent and self.parent.parent and not self.parent.parent.parent:
415 requirements |= set(i.name for i in self.root.dependencies if i.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000416
maruel@chromium.org470b5432011-10-11 18:18:19 +0000417 if self.name:
418 requirements |= set(
419 obj.name for obj in self.root.subtree(False)
420 if (obj is not self
421 and obj.name and
422 self.name.startswith(posixpath.join(obj.name, ''))))
423 requirements = tuple(sorted(requirements))
424 logging.info('Dependency(%s).requirements = %s' % (self.name, requirements))
425 return requirements
426
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000427 @property
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000428 def try_recursedeps(self):
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000429 """Returns False if recursion_override is ever specified."""
430 if self.recursion_override is not None:
431 return False
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000432 return self.parent.try_recursedeps
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000433
434 @property
435 def recursion_limit(self):
436 """Returns > 0 if this dependency is not too recursed to be processed."""
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000437 # We continue to support the absence of recursedeps until tools and DEPS
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000438 # using recursion_override are updated.
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000439 if self.try_recursedeps and self.parent.recursedeps != None:
440 if self.name in self.parent.recursedeps:
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000441 return 1
442
443 if self.recursion_override is not None:
444 return self.recursion_override
445 return max(self.parent.recursion_limit - 1, 0)
446
maruel@chromium.org470b5432011-10-11 18:18:19 +0000447 def verify_validity(self):
448 """Verifies that this Dependency is fine to add as a child of another one.
449
450 Returns True if this entry should be added, False if it is a duplicate of
451 another entry.
452 """
453 logging.info('Dependency(%s).verify_validity()' % self.name)
454 if self.name in [s.name for s in self.parent.dependencies]:
455 raise gclient_utils.Error(
456 'The same name "%s" appears multiple times in the deps section' %
457 self.name)
458 if not self.should_process:
459 # Return early, no need to set requirements.
460 return True
461
462 # This require a full tree traversal with locks.
463 siblings = [d for d in self.root.subtree(False) if d.name == self.name]
464 for sibling in siblings:
maruel@chromium.orgb848d5b2012-10-10 23:25:50 +0000465 self_url = self.LateOverride(self.url)
466 sibling_url = sibling.LateOverride(sibling.url)
467 # Allow to have only one to be None or ''.
468 if self_url != sibling_url and bool(self_url) == bool(sibling_url):
maruel@chromium.org470b5432011-10-11 18:18:19 +0000469 raise gclient_utils.Error(
maruel@chromium.orgb848d5b2012-10-10 23:25:50 +0000470 ('Dependency %s specified more than once:\n'
471 ' %s [%s]\n'
472 'vs\n'
473 ' %s [%s]') % (
474 self.name,
475 sibling.hierarchy(),
476 sibling_url,
477 self.hierarchy(),
478 self_url))
maruel@chromium.org470b5432011-10-11 18:18:19 +0000479 # In theory we could keep it as a shadow of the other one. In
480 # practice, simply ignore it.
481 logging.warn('Won\'t process duplicate dependency %s' % sibling)
482 return False
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000483 return True
maruel@chromium.org064186c2011-09-27 23:53:33 +0000484
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000485 def LateOverride(self, url):
Paweł Hajdan, Jr7e9303b2017-05-23 14:38:27 +0200486 """Resolves the parsed url from url."""
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000487 assert self.parsed_url == None or not self.should_process, self.parsed_url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000488 parsed_url = self.get_custom_deps(self.name, url)
489 if parsed_url != url:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000490 logging.info(
491 'Dependency(%s).LateOverride(%s) -> %s' %
492 (self.name, url, parsed_url))
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000493 return parsed_url
494
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000495 if isinstance(url, basestring):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000496 parsed_url = urlparse.urlparse(url)
scr@chromium.orgf1eccaf2014-04-11 15:51:33 +0000497 if (not parsed_url[0] and
498 not re.match(r'^\w+\@[\w\.-]+\:[\w\/]+', parsed_url[2])):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000499 # A relative url. Fetch the real base.
500 path = parsed_url[2]
501 if not path.startswith('/'):
502 raise gclient_utils.Error(
503 'relative DEPS entry \'%s\' must begin with a slash' % url)
504 # Create a scm just to query the full url.
505 parent_url = self.parent.parsed_url
szager@chromium.orgfe0d1902014-04-08 20:50:44 +0000506 scm = gclient_scm.CreateSCM(
507 parent_url, self.root.root_dir, None, self.outbuf)
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000508 parsed_url = scm.FullUrlForRelativeUrl(url)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000509 else:
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000510 parsed_url = url
maruel@chromium.org470b5432011-10-11 18:18:19 +0000511 logging.info(
512 'Dependency(%s).LateOverride(%s) -> %s' %
513 (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000514 return parsed_url
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000515
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000516 if url is None:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000517 logging.info(
518 'Dependency(%s).LateOverride(%s) -> %s' % (self.name, url, url))
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000519 return url
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000520
521 raise gclient_utils.Error('Unknown url type')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000522
bratell@opera.comed2b4fe2013-12-16 14:34:12 +0000523 @staticmethod
524 def MergeWithOsDeps(deps, deps_os, target_os_list):
525 """Returns a new "deps" structure that is the deps sent in updated
526 with information from deps_os (the deps_os section of the DEPS
527 file) that matches the list of target os."""
528 os_overrides = {}
529 for the_target_os in target_os_list:
530 the_target_os_deps = deps_os.get(the_target_os, {})
531 for os_dep_key, os_dep_value in the_target_os_deps.iteritems():
532 overrides = os_overrides.setdefault(os_dep_key, [])
533 overrides.append((the_target_os, os_dep_value))
534
535 # If any os didn't specify a value (we have fewer value entries
536 # than in the os list), then it wants to use the default value.
537 for os_dep_key, os_dep_value in os_overrides.iteritems():
538 if len(os_dep_value) != len(target_os_list):
qyearsley12fa6ff2016-08-24 09:18:40 -0700539 # Record the default value too so that we don't accidentally
bratell@opera.comed2b4fe2013-12-16 14:34:12 +0000540 # set it to None or miss a conflicting DEPS.
541 if os_dep_key in deps:
542 os_dep_value.append(('default', deps[os_dep_key]))
543
544 target_os_deps = {}
545 for os_dep_key, os_dep_value in os_overrides.iteritems():
546 # os_dep_value is a list of (os, value) pairs.
547 possible_values = set(x[1] for x in os_dep_value if x[1] is not None)
548 if not possible_values:
549 target_os_deps[os_dep_key] = None
550 else:
551 if len(possible_values) > 1:
552 # It would be possible to abort here but it would be
553 # unfortunate if we end up preventing any kind of checkout.
554 logging.error('Conflicting dependencies for %s: %s. (target_os=%s)',
555 os_dep_key, os_dep_value, target_os_list)
556 # Sorting to get the same result every time in case of conflicts.
557 target_os_deps[os_dep_key] = sorted(possible_values)[0]
558
559 new_deps = deps.copy()
560 new_deps.update(target_os_deps)
561 return new_deps
562
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200563 def _postprocess_deps(self, deps, rel_prefix):
564 """Performs post-processing of deps compared to what's in the DEPS file."""
Paweł Hajdan, Jr4426eaf2017-06-13 12:25:47 +0200565 # Make sure the dict is mutable, e.g. in case it's frozen.
566 deps = dict(deps)
567
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200568 # If a line is in custom_deps, but not in the solution, we want to append
569 # this line to the solution.
570 for d in self.custom_deps:
571 if d not in deps:
572 deps[d] = self.custom_deps[d]
573
574 if rel_prefix:
575 logging.warning('use_relative_paths enabled.')
576 rel_deps = {}
577 for d, url in deps.items():
578 # normpath is required to allow DEPS to use .. in their
579 # dependency local path.
580 rel_deps[os.path.normpath(os.path.join(rel_prefix, d))] = url
581 logging.warning('Updating deps by prepending %s.', rel_prefix)
582 deps = rel_deps
583
584 return deps
585
586 def _deps_to_objects(self, deps, use_relative_paths):
587 """Convert a deps dict to a dict of Dependency objects."""
588 deps_to_add = []
589 for name, dep_value in deps.iteritems():
590 should_process = self.recursion_limit and self.should_process
591 deps_file = self.deps_file
592 if self.recursedeps is not None:
593 ent = self.recursedeps.get(name)
594 if ent is not None:
595 deps_file = ent['deps_file']
596 if dep_value is None:
597 continue
598 condition = None
599 condition_value = True
600 if isinstance(dep_value, basestring):
601 url = dep_value
602 else:
603 # This should be guaranteed by schema checking in gclient_eval.
604 assert isinstance(dep_value, collections.Mapping)
605 url = dep_value['url']
606 condition = dep_value.get('condition')
607 if condition:
608 # TODO(phajdan.jr): should we also take custom vars into account?
609 condition_value = gclient_eval.EvaluateCondition(condition, self._vars)
610 should_process = should_process and condition_value
611 deps_to_add.append(Dependency(
612 self, name, url, None, None, self.custom_vars, None,
613 deps_file, should_process, use_relative_paths, condition,
614 condition_value))
615 deps_to_add.sort(key=lambda x: x.name)
616 return deps_to_add
617
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000618 def ParseDepsFile(self):
maruel@chromium.org271375b2010-06-23 19:17:38 +0000619 """Parses the DEPS file for this dependency."""
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000620 assert not self.deps_parsed
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000621 assert not self.dependencies
iannucci@chromium.org0c3f3052014-05-22 00:29:22 +0000622
623 deps_content = None
624 use_strict = False
cmp@chromium.org76ce73c2014-07-02 00:13:18 +0000625
626 # First try to locate the configured deps file. If it's missing, fallback
627 # to DEPS.
628 deps_files = [self.deps_file]
629 if 'DEPS' not in deps_files:
630 deps_files.append('DEPS')
631 for deps_file in deps_files:
632 filepath = os.path.join(self.root.root_dir, self.name, deps_file)
633 if os.path.isfile(filepath):
634 logging.info(
635 'ParseDepsFile(%s): %s file found at %s', self.name, deps_file,
636 filepath)
637 break
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000638 logging.info(
cmp@chromium.org76ce73c2014-07-02 00:13:18 +0000639 'ParseDepsFile(%s): No %s file found at %s', self.name, deps_file,
640 filepath)
641
642 if os.path.isfile(filepath):
maruel@chromium.org46304292010-10-28 11:42:00 +0000643 deps_content = gclient_utils.FileRead(filepath)
cmp@chromium.org76ce73c2014-07-02 00:13:18 +0000644 logging.debug('ParseDepsFile(%s) read:\n%s', self.name, deps_content)
iannucci@chromium.org0c3f3052014-05-22 00:29:22 +0000645 use_strict = 'use strict' in deps_content.splitlines()[0]
646
647 local_scope = {}
648 if deps_content:
649 # One thing is unintuitive, vars = {} must happen before Var() use.
650 var = self.VarImpl(self.custom_vars, local_scope)
651 if use_strict:
652 logging.info(
653 'ParseDepsFile(%s): Strict Mode Enabled', self.name)
654 global_scope = {
655 '__builtins__': {'None': None},
656 'Var': var.Lookup,
657 'deps_os': {},
658 }
659 else:
660 global_scope = {
iannucci@chromium.org0c3f3052014-05-22 00:29:22 +0000661 'Var': var.Lookup,
662 'deps_os': {},
663 }
maruel@chromium.org46304292010-10-28 11:42:00 +0000664 # Eval the content.
665 try:
Paweł Hajdan, Jrc485d5a2017-06-02 12:08:09 +0200666 if self._get_option('validate_syntax', False):
667 gclient_eval.Exec(deps_content, global_scope, local_scope, filepath)
668 else:
669 exec(deps_content, global_scope, local_scope)
vapier@chromium.orga81a56e2015-11-11 07:56:13 +0000670 except SyntaxError as e:
maruel@chromium.org46304292010-10-28 11:42:00 +0000671 gclient_utils.SyntaxErrorToError(filepath, e)
iannucci@chromium.org0c3f3052014-05-22 00:29:22 +0000672 if use_strict:
673 for key, val in local_scope.iteritems():
674 if not isinstance(val, (dict, list, tuple, str)):
675 raise gclient_utils.Error(
676 'ParseDepsFile(%s): Strict mode disallows %r -> %r' %
677 (self.name, key, val))
678
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000679 if 'allowed_hosts' in local_scope:
680 try:
681 self._allowed_hosts = frozenset(local_scope.get('allowed_hosts'))
682 except TypeError: # raised if non-iterable
683 pass
684 if not self._allowed_hosts:
685 logging.warning("allowed_hosts is specified but empty %s",
686 self._allowed_hosts)
687 raise gclient_utils.Error(
688 'ParseDepsFile(%s): allowed_hosts must be absent '
689 'or a non-empty iterable' % self.name)
690
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +0200691 self._gn_args_file = local_scope.get('gclient_gn_args_file')
692 self._gn_args = local_scope.get('gclient_gn_args', [])
693
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200694 # Since we heavily post-process things, freeze ones which should
695 # reflect original state of DEPS.
696 self._vars = gclient_utils.freeze(local_scope.get('vars', {}))
697
698 # If use_relative_paths is set in the DEPS file, regenerate
699 # the dictionary using paths relative to the directory containing
700 # the DEPS file. Also update recursedeps if use_relative_paths is
701 # enabled.
702 # If the deps file doesn't set use_relative_paths, but the parent did
703 # (and therefore set self.relative on this Dependency object), then we
704 # want to modify the deps and recursedeps by prepending the parent
705 # directory of this dependency.
706 use_relative_paths = local_scope.get('use_relative_paths', False)
707 rel_prefix = None
708 if use_relative_paths:
709 rel_prefix = self.name
710 elif self._relative:
711 rel_prefix = os.path.dirname(self.name)
712
713 deps = local_scope.get('deps', {})
714 orig_deps = gclient_utils.freeze(deps)
715 if 'recursion' in local_scope:
716 self.recursion_override = local_scope.get('recursion')
717 logging.warning(
718 'Setting %s recursion to %d.', self.name, self.recursion_limit)
719 self.recursedeps = None
720 if 'recursedeps' in local_scope:
721 self.recursedeps = {}
722 for ent in local_scope['recursedeps']:
723 if isinstance(ent, basestring):
724 self.recursedeps[ent] = {"deps_file": self.deps_file}
725 else: # (depname, depsfilename)
726 self.recursedeps[ent[0]] = {"deps_file": ent[1]}
727 logging.warning('Found recursedeps %r.', repr(self.recursedeps))
728
729 if rel_prefix:
730 logging.warning('Updating recursedeps by prepending %s.', rel_prefix)
731 rel_deps = {}
732 for depname, options in self.recursedeps.iteritems():
733 rel_deps[
734 os.path.normpath(os.path.join(rel_prefix, depname))] = options
735 self.recursedeps = rel_deps
736
737 # If present, save 'target_os' in the local_target_os property.
738 if 'target_os' in local_scope:
739 self.local_target_os = local_scope['target_os']
740 # load os specific dependencies if defined. these dependencies may
741 # override or extend the values defined by the 'deps' member.
742 target_os_list = self.target_os
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +0200743 if 'deps_os' in local_scope:
744 for dep_os, os_deps in local_scope['deps_os'].iteritems():
745 self._os_dependencies[dep_os] = self._deps_to_objects(
746 self._postprocess_deps(os_deps, rel_prefix), use_relative_paths)
747 if target_os_list:
748 deps = self.MergeWithOsDeps(
749 deps, local_scope['deps_os'], target_os_list)
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200750
751 deps_to_add = self._deps_to_objects(
752 self._postprocess_deps(deps, rel_prefix), use_relative_paths)
753 orig_deps_to_add = self._deps_to_objects(
754 self._postprocess_deps(orig_deps, rel_prefix), use_relative_paths)
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000755
756 # override named sets of hooks by the custom hooks
757 hooks_to_run = []
758 hook_names_to_suppress = [c.get('name', '') for c in self.custom_hooks]
759 for hook in local_scope.get('hooks', []):
760 if hook.get('name', '') not in hook_names_to_suppress:
761 hooks_to_run.append(hook)
Scott Grahamc4826742017-05-11 16:59:23 -0700762 if 'hooks_os' in local_scope and target_os_list:
763 hooks_os = local_scope['hooks_os']
764 # Specifically append these to ensure that hooks_os run after hooks.
765 for the_target_os in target_os_list:
766 the_target_os_hooks = hooks_os.get(the_target_os, [])
767 hooks_to_run.extend(the_target_os_hooks)
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000768
769 # add the replacements and any additions
770 for hook in self.custom_hooks:
771 if 'action' in hook:
772 hooks_to_run.append(hook)
773
Dirk Prankeda3a29e2017-02-27 15:29:36 -0800774 if self.recursion_limit:
Paweł Hajdan, Jr35b298f2017-05-23 14:37:05 +0200775 self._pre_deps_hooks = [self.GetHookAction(hook) for hook in
Dirk Prankeda3a29e2017-02-27 15:29:36 -0800776 local_scope.get('pre_deps_hooks', [])]
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000777
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200778 self.add_dependencies_and_close(
779 deps_to_add, hooks_to_run, orig_deps_to_add=orig_deps_to_add)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000780 logging.info('ParseDepsFile(%s) done' % self.name)
781
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +0200782 def _get_option(self, attr, default):
783 obj = self
784 while not hasattr(obj, '_options'):
785 obj = obj.parent
786 return getattr(obj._options, attr, default)
787
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200788 def add_dependencies_and_close(
789 self, deps_to_add, hooks, orig_deps_to_add=None):
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000790 """Adds the dependencies, hooks and mark the parsing as done."""
maruel@chromium.orgb9be0652011-10-14 18:05:40 +0000791 for dep in deps_to_add:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000792 if dep.verify_validity():
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000793 self.add_dependency(dep)
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200794 for dep in (orig_deps_to_add or deps_to_add):
795 self.add_orig_dependency(dep)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000796 self._mark_as_parsed(hooks)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000797
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000798 def findDepsFromNotAllowedHosts(self):
799 """Returns a list of depenecies from not allowed hosts.
800
801 If allowed_hosts is not set, allows all hosts and returns empty list.
802 """
803 if not self._allowed_hosts:
804 return []
805 bad_deps = []
806 for dep in self._dependencies:
szager@chromium.orgbd772dd2014-11-05 18:43:08 +0000807 # Don't enforce this for custom_deps.
808 if dep.name in self._custom_deps:
809 continue
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000810 if isinstance(dep.url, basestring):
811 parsed_url = urlparse.urlparse(dep.url)
812 if parsed_url.netloc and parsed_url.netloc not in self._allowed_hosts:
813 bad_deps.append(dep)
814 return bad_deps
815
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +0000816 # Arguments number differs from overridden method
Quinten Yearsleyb2cc4a92016-12-15 13:53:26 -0800817 # pylint: disable=arguments-differ
maruel@chromium.org3742c842010-09-09 19:27:14 +0000818 def run(self, revision_overrides, command, args, work_queue, options):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000819 """Runs |command| then parse the DEPS file."""
maruel@chromium.org470b5432011-10-11 18:18:19 +0000820 logging.info('Dependency(%s).run()' % self.name)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000821 assert self._file_list == []
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000822 if not self.should_process:
823 return
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000824 # When running runhooks, there's no need to consult the SCM.
825 # All known hooks are expected to run unconditionally regardless of working
826 # copy state, so skip the SCM status check.
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +0200827 run_scm = command not in (
828 'flatten', 'runhooks', 'recurse', 'validate', None)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000829 parsed_url = self.LateOverride(self.url)
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000830 file_list = [] if not options.nohooks else None
szager@chromium.org3a3608d2014-10-22 21:13:52 +0000831 revision_override = revision_overrides.pop(self.name, None)
Dave Tubbda9712017-06-01 15:10:53 -0700832 if not revision_override and parsed_url:
833 revision_override = revision_overrides.get(parsed_url.split('@')[0], None)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000834 if run_scm and parsed_url:
agabled437d762016-10-17 09:35:11 -0700835 # Create a shallow copy to mutate revision.
836 options = copy.copy(options)
837 options.revision = revision_override
838 self._used_revision = options.revision
839 self._used_scm = gclient_scm.CreateSCM(
840 parsed_url, self.root.root_dir, self.name, self.outbuf,
841 out_cb=work_queue.out_cb)
842 self._got_revision = self._used_scm.RunCommand(command, options, args,
843 file_list)
844 if file_list:
845 file_list = [os.path.join(self.name, f.strip()) for f in file_list]
maruel@chromium.org68988972011-09-20 14:11:42 +0000846
847 # TODO(phajdan.jr): We should know exactly when the paths are absolute.
848 # Convert all absolute paths to relative.
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000849 for i in range(len(file_list or [])):
maruel@chromium.org68988972011-09-20 14:11:42 +0000850 # It depends on the command being executed (like runhooks vs sync).
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000851 if not os.path.isabs(file_list[i]):
maruel@chromium.org68988972011-09-20 14:11:42 +0000852 continue
853 prefix = os.path.commonprefix(
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000854 [self.root.root_dir.lower(), file_list[i].lower()])
855 file_list[i] = file_list[i][len(prefix):]
maruel@chromium.org68988972011-09-20 14:11:42 +0000856 # Strip any leading path separators.
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000857 while file_list[i].startswith(('\\', '/')):
858 file_list[i] = file_list[i][1:]
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000859
860 # Always parse the DEPS file.
861 self.ParseDepsFile()
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +0200862 if self._gn_args_file and command == 'update':
863 self.WriteGNArgsFile()
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000864 self._run_is_done(file_list or [], parsed_url)
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000865 if command in ('update', 'revert') and not options.noprehooks:
866 self.RunPreDepsHooks()
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000867
868 if self.recursion_limit:
869 # Parse the dependencies of this dependency.
870 for s in self.dependencies:
871 work_queue.enqueue(s)
872
873 if command == 'recurse':
agabled437d762016-10-17 09:35:11 -0700874 # Skip file only checkout.
875 scm = gclient_scm.GetScmName(parsed_url)
876 if not options.scm or scm in options.scm:
877 cwd = os.path.normpath(os.path.join(self.root.root_dir, self.name))
878 # Pass in the SCM type as an env variable. Make sure we don't put
879 # unicode strings in the environment.
880 env = os.environ.copy()
881 if scm:
882 env['GCLIENT_SCM'] = str(scm)
883 if parsed_url:
884 env['GCLIENT_URL'] = str(parsed_url)
885 env['GCLIENT_DEP_PATH'] = str(self.name)
886 if options.prepend_dir and scm == 'git':
887 print_stdout = False
888 def filter_fn(line):
889 """Git-specific path marshaling. It is optimized for git-grep."""
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000890
agabled437d762016-10-17 09:35:11 -0700891 def mod_path(git_pathspec):
892 match = re.match('^(\\S+?:)?([^\0]+)$', git_pathspec)
893 modified_path = os.path.join(self.name, match.group(2))
894 branch = match.group(1) or ''
895 return '%s%s' % (branch, modified_path)
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000896
agabled437d762016-10-17 09:35:11 -0700897 match = re.match('^Binary file ([^\0]+) matches$', line)
898 if match:
899 print('Binary file %s matches\n' % mod_path(match.group(1)))
900 return
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000901
agabled437d762016-10-17 09:35:11 -0700902 items = line.split('\0')
903 if len(items) == 2 and items[1]:
904 print('%s : %s' % (mod_path(items[0]), items[1]))
905 elif len(items) >= 2:
906 # Multiple null bytes or a single trailing null byte indicate
907 # git is likely displaying filenames only (such as with -l)
908 print('\n'.join(mod_path(path) for path in items if path))
909 else:
910 print(line)
911 else:
912 print_stdout = True
913 filter_fn = None
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000914
agabled437d762016-10-17 09:35:11 -0700915 if parsed_url is None:
916 print('Skipped omitted dependency %s' % cwd, file=sys.stderr)
917 elif os.path.isdir(cwd):
918 try:
919 gclient_utils.CheckCallAndFilter(
920 args, cwd=cwd, env=env, print_stdout=print_stdout,
921 filter_fn=filter_fn,
922 )
923 except subprocess2.CalledProcessError:
924 if not options.ignore:
925 raise
926 else:
927 print('Skipped missing %s' % cwd, file=sys.stderr)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000928
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +0200929 def WriteGNArgsFile(self):
930 lines = ['# Generated from %r' % self.deps_file]
931 for arg in self._gn_args:
932 lines.append('%s = %s' % (arg, ToGNString(self._vars[arg])))
933 with open(os.path.join(self.root.root_dir, self._gn_args_file), 'w') as f:
934 f.write('\n'.join(lines))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000935
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000936 @gclient_utils.lockedmethod
937 def _run_is_done(self, file_list, parsed_url):
938 # Both these are kept for hooks that are run as a separate tree traversal.
939 self._file_list = file_list
940 self._parsed_url = parsed_url
941 self._processed = True
942
szager@google.comb9a78d32012-03-13 18:46:21 +0000943 @staticmethod
Paweł Hajdan, Jr35b298f2017-05-23 14:37:05 +0200944 def GetHookAction(hook_dict):
szager@google.comb9a78d32012-03-13 18:46:21 +0000945 """Turns a parsed 'hook' dict into an executable command."""
946 logging.debug(hook_dict)
szager@google.comb9a78d32012-03-13 18:46:21 +0000947 command = hook_dict['action'][:]
948 if command[0] == 'python':
949 # If the hook specified "python" as the first item, the action is a
950 # Python script. Run it by starting a new copy of the same
951 # interpreter.
952 command[0] = sys.executable
szager@google.comb9a78d32012-03-13 18:46:21 +0000953 return command
954
955 def GetHooks(self, options):
956 """Evaluates all hooks, and return them in a flat list.
957
958 RunOnDeps() must have been called before to load the DEPS.
959 """
960 result = []
maruel@chromium.org68988972011-09-20 14:11:42 +0000961 if not self.should_process or not self.recursion_limit:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000962 # Don't run the hook when it is above recursion_limit.
szager@google.comb9a78d32012-03-13 18:46:21 +0000963 return result
maruel@chromium.orgdc7445d2010-07-09 21:05:29 +0000964 # If "--force" was specified, run all hooks regardless of what files have
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000965 # changed.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000966 if self.deps_hooks:
agabled437d762016-10-17 09:35:11 -0700967 # TODO(maruel): If the user is using git, then we don't know
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000968 # what files have changed so we always run all hooks. It'd be nice to fix
969 # that.
970 if (options.force or
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000971 gclient_scm.GetScmName(self.parsed_url) in ('git', None) or
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +0000972 os.path.isdir(os.path.join(self.root.root_dir, self.name, '.git'))):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000973 for hook_dict in self.deps_hooks:
Paweł Hajdan, Jr35b298f2017-05-23 14:37:05 +0200974 result.append(self.GetHookAction(hook_dict))
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000975 else:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000976 # Run hooks on the basis of whether the files from the gclient operation
977 # match each hook's pattern.
978 for hook_dict in self.deps_hooks:
979 pattern = re.compile(hook_dict['pattern'])
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000980 matching_file_list = [
981 f for f in self.file_list_and_children if pattern.search(f)
982 ]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000983 if matching_file_list:
Paweł Hajdan, Jr35b298f2017-05-23 14:37:05 +0200984 result.append(self.GetHookAction(hook_dict))
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000985 for s in self.dependencies:
szager@google.comb9a78d32012-03-13 18:46:21 +0000986 result.extend(s.GetHooks(options))
987 return result
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000988
szager@google.comb9a78d32012-03-13 18:46:21 +0000989 def RunHooksRecursively(self, options):
990 assert self.hooks_ran == False
maruel@chromium.org064186c2011-09-27 23:53:33 +0000991 self._hooks_ran = True
szager@google.comb9a78d32012-03-13 18:46:21 +0000992 for hook in self.GetHooks(options):
993 try:
ilevy@chromium.orgc28d3772013-07-12 19:42:37 +0000994 start_time = time.time()
szager@google.comb9a78d32012-03-13 18:46:21 +0000995 gclient_utils.CheckCallAndFilterAndHeader(
996 hook, cwd=self.root.root_dir, always=True)
vapier@chromium.orga81a56e2015-11-11 07:56:13 +0000997 except (gclient_utils.Error, subprocess2.CalledProcessError) as e:
szager@google.comb9a78d32012-03-13 18:46:21 +0000998 # Use a discrete exit status code of 2 to indicate that a hook action
999 # failed. Users of this script may wish to treat hook action failures
1000 # differently from VC failures.
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001001 print('Error: %s' % str(e), file=sys.stderr)
szager@google.comb9a78d32012-03-13 18:46:21 +00001002 sys.exit(2)
ilevy@chromium.orgc28d3772013-07-12 19:42:37 +00001003 finally:
1004 elapsed_time = time.time() - start_time
1005 if elapsed_time > 10:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001006 print("Hook '%s' took %.2f secs" % (
1007 gclient_utils.CommandToStr(hook), elapsed_time))
maruel@chromium.orgeaf61062010-07-07 18:42:39 +00001008
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001009 def RunPreDepsHooks(self):
1010 assert self.processed
1011 assert self.deps_parsed
1012 assert not self.pre_deps_hooks_ran
1013 assert not self.hooks_ran
1014 for s in self.dependencies:
1015 assert not s.processed
1016 self._pre_deps_hooks_ran = True
1017 for hook in self.pre_deps_hooks:
1018 try:
1019 start_time = time.time()
1020 gclient_utils.CheckCallAndFilterAndHeader(
1021 hook, cwd=self.root.root_dir, always=True)
vapier@chromium.orga81a56e2015-11-11 07:56:13 +00001022 except (gclient_utils.Error, subprocess2.CalledProcessError) as e:
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001023 # Use a discrete exit status code of 2 to indicate that a hook action
1024 # failed. Users of this script may wish to treat hook action failures
1025 # differently from VC failures.
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001026 print('Error: %s' % str(e), file=sys.stderr)
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001027 sys.exit(2)
1028 finally:
1029 elapsed_time = time.time() - start_time
1030 if elapsed_time > 10:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001031 print("Hook '%s' took %.2f secs" % (
1032 gclient_utils.CommandToStr(hook), elapsed_time))
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001033
1034
maruel@chromium.org0d812442010-08-10 12:41:08 +00001035 def subtree(self, include_all):
maruel@chromium.orgad3287e2011-10-03 19:15:10 +00001036 """Breadth first recursion excluding root node."""
maruel@chromium.orgf13a4182011-09-22 00:26:15 +00001037 dependencies = self.dependencies
1038 for d in dependencies:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001039 if d.should_process or include_all:
maruel@chromium.orgad3287e2011-10-03 19:15:10 +00001040 yield d
maruel@chromium.orgf13a4182011-09-22 00:26:15 +00001041 for d in dependencies:
maruel@chromium.orgad3287e2011-10-03 19:15:10 +00001042 for i in d.subtree(include_all):
1043 yield i
1044
1045 def depth_first_tree(self):
1046 """Depth-first recursion including the root node."""
1047 yield self
1048 for i in self.dependencies:
1049 for j in i.depth_first_tree():
1050 if j.should_process:
1051 yield j
maruel@chromium.orgc57e4f22010-07-22 21:37:46 +00001052
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001053 @gclient_utils.lockedmethod
1054 def add_dependency(self, new_dep):
1055 self._dependencies.append(new_dep)
1056
1057 @gclient_utils.lockedmethod
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +02001058 def add_orig_dependency(self, new_dep):
1059 self._orig_dependencies.append(new_dep)
1060
1061 @gclient_utils.lockedmethod
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001062 def _mark_as_parsed(self, new_hooks):
1063 self._deps_hooks.extend(new_hooks)
1064 self._deps_parsed = True
1065
maruel@chromium.org68988972011-09-20 14:11:42 +00001066 @property
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001067 @gclient_utils.lockedmethod
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +00001068 def dependencies(self):
1069 return tuple(self._dependencies)
1070
1071 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001072 @gclient_utils.lockedmethod
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +02001073 def orig_dependencies(self):
1074 return tuple(self._orig_dependencies)
1075
1076 @property
1077 @gclient_utils.lockedmethod
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02001078 def os_dependencies(self):
1079 return dict(self._os_dependencies)
1080
1081 @property
1082 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001083 def deps_hooks(self):
1084 return tuple(self._deps_hooks)
1085
1086 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001087 @gclient_utils.lockedmethod
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001088 def pre_deps_hooks(self):
1089 return tuple(self._pre_deps_hooks)
1090
1091 @property
1092 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001093 def parsed_url(self):
1094 return self._parsed_url
1095
1096 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001097 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001098 def deps_parsed(self):
maruel@chromium.org3223edd2011-10-10 23:17:39 +00001099 """This is purely for debugging purposes. It's not used anywhere."""
maruel@chromium.org064186c2011-09-27 23:53:33 +00001100 return self._deps_parsed
1101
1102 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001103 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001104 def processed(self):
1105 return self._processed
1106
1107 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001108 @gclient_utils.lockedmethod
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001109 def pre_deps_hooks_ran(self):
1110 return self._pre_deps_hooks_ran
1111
1112 @property
1113 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001114 def hooks_ran(self):
1115 return self._hooks_ran
1116
1117 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001118 @gclient_utils.lockedmethod
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00001119 def allowed_hosts(self):
1120 return self._allowed_hosts
1121
1122 @property
1123 @gclient_utils.lockedmethod
maruel@chromium.org861fd0f2010-07-23 03:05:05 +00001124 def file_list(self):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001125 return tuple(self._file_list)
1126
1127 @property
kustermann@google.coma692e8f2013-04-18 08:32:04 +00001128 def used_scm(self):
1129 """SCMWrapper instance for this dependency or None if not processed yet."""
1130 return self._used_scm
1131
1132 @property
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00001133 @gclient_utils.lockedmethod
1134 def got_revision(self):
1135 return self._got_revision
1136
1137 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001138 def file_list_and_children(self):
1139 result = list(self.file_list)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +00001140 for d in self.dependencies:
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001141 result.extend(d.file_list_and_children)
maruel@chromium.org68988972011-09-20 14:11:42 +00001142 return tuple(result)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +00001143
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001144 def __str__(self):
1145 out = []
agablea98a6cd2016-11-15 14:30:10 -08001146 for i in ('name', 'url', 'parsed_url', 'custom_deps',
maruel@chromium.org3c74bc92011-09-15 19:17:21 +00001147 'custom_vars', 'deps_hooks', 'file_list', 'should_process',
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00001148 'processed', 'hooks_ran', 'deps_parsed', 'requirements',
1149 'allowed_hosts'):
maruel@chromium.org3c74bc92011-09-15 19:17:21 +00001150 # First try the native property if it exists.
1151 if hasattr(self, '_' + i):
1152 value = getattr(self, '_' + i, False)
1153 else:
1154 value = getattr(self, i, False)
1155 if value:
1156 out.append('%s: %s' % (i, value))
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001157
1158 for d in self.dependencies:
1159 out.extend([' ' + x for x in str(d).splitlines()])
1160 out.append('')
1161 return '\n'.join(out)
1162
1163 def __repr__(self):
1164 return '%s: %s' % (self.name, self.url)
1165
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001166 def hierarchy(self, include_url=True):
maruel@chromium.orgbc2d2f92010-07-22 21:26:48 +00001167 """Returns a human-readable hierarchical reference to a Dependency."""
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001168 def format_name(d):
1169 if include_url:
1170 return '%s(%s)' % (d.name, d.url)
1171 return d.name
1172 out = format_name(self)
maruel@chromium.orgbffb9042010-07-22 20:59:36 +00001173 i = self.parent
1174 while i and i.name:
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001175 out = '%s -> %s' % (format_name(i), out)
maruel@chromium.orgbffb9042010-07-22 20:59:36 +00001176 i = i.parent
1177 return out
1178
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001179
1180class GClient(Dependency):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001181 """Object that represent a gclient checkout. A tree of Dependency(), one per
1182 solution or DEPS entry."""
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001183
1184 DEPS_OS_CHOICES = {
1185 "win32": "win",
1186 "win": "win",
1187 "cygwin": "win",
1188 "darwin": "mac",
1189 "mac": "mac",
1190 "unix": "unix",
1191 "linux": "unix",
1192 "linux2": "unix",
maruel@chromium.org244e3442011-06-12 15:20:55 +00001193 "linux3": "unix",
szager@chromium.orgf8c95cd2012-06-01 22:26:52 +00001194 "android": "android",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001195 }
1196
1197 DEFAULT_CLIENT_FILE_TEXT = ("""\
1198solutions = [
smutae7ea312016-07-18 11:59:41 -07001199 { "name" : "%(solution_name)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001200 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +00001201 "deps_file" : "%(deps_file)s",
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001202 "managed" : %(managed)s,
smutae7ea312016-07-18 11:59:41 -07001203 "custom_deps" : {
1204 },
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001205 },
1206]
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001207cache_dir = %(cache_dir)r
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001208""")
1209
1210 DEFAULT_SNAPSHOT_SOLUTION_TEXT = ("""\
smutae7ea312016-07-18 11:59:41 -07001211 { "name" : "%(solution_name)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001212 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +00001213 "deps_file" : "%(deps_file)s",
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001214 "managed" : %(managed)s,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001215 "custom_deps" : {
smutae7ea312016-07-18 11:59:41 -07001216%(solution_deps)s },
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001217 },
1218""")
1219
1220 DEFAULT_SNAPSHOT_FILE_TEXT = ("""\
1221# Snapshot generated with gclient revinfo --snapshot
1222solutions = [
maruel@chromium.org73e21142010-07-05 13:32:01 +00001223%(solution_list)s]
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001224""")
1225
1226 def __init__(self, root_dir, options):
maruel@chromium.org0d812442010-08-10 12:41:08 +00001227 # Do not change previous behavior. Only solution level and immediate DEPS
1228 # are processed.
1229 self._recursion_limit = 2
agablea98a6cd2016-11-15 14:30:10 -08001230 Dependency.__init__(self, None, None, None, True, None, None, None,
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +02001231 'unused', True, None, None, True)
maruel@chromium.org0d425922010-06-21 19:22:24 +00001232 self._options = options
maruel@chromium.org271375b2010-06-23 19:17:38 +00001233 if options.deps_os:
1234 enforced_os = options.deps_os.split(',')
1235 else:
1236 enforced_os = [self.DEPS_OS_CHOICES.get(sys.platform, 'unix')]
1237 if 'all' in enforced_os:
1238 enforced_os = self.DEPS_OS_CHOICES.itervalues()
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001239 self._enforced_os = tuple(set(enforced_os))
maruel@chromium.org271375b2010-06-23 19:17:38 +00001240 self._root_dir = root_dir
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001241 self.config_content = None
1242
borenet@google.com88d10082014-03-21 17:24:48 +00001243 def _CheckConfig(self):
1244 """Verify that the config matches the state of the existing checked-out
1245 solutions."""
1246 for dep in self.dependencies:
1247 if dep.managed and dep.url:
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001248 scm = gclient_scm.CreateSCM(
1249 dep.url, self.root_dir, dep.name, self.outbuf)
smut@google.comd33eab32014-07-07 19:35:18 +00001250 actual_url = scm.GetActualRemoteURL(self._options)
borenet@google.com4e9be262014-04-08 19:40:30 +00001251 if actual_url and not scm.DoesRemoteURLMatch(self._options):
levarum@chromium.org27a6f9a2016-05-28 00:21:49 +00001252 mirror = scm.GetCacheMirror()
1253 if mirror:
1254 mirror_string = '%s (exists=%s)' % (mirror.mirror_path,
1255 mirror.exists())
1256 else:
1257 mirror_string = 'not used'
borenet@google.com0a427372014-04-02 19:12:13 +00001258 raise gclient_utils.Error('''
borenet@google.com88d10082014-03-21 17:24:48 +00001259Your .gclient file seems to be broken. The requested URL is different from what
borenet@google.com0a427372014-04-02 19:12:13 +00001260is actually checked out in %(checkout_path)s.
borenet@google.com88d10082014-03-21 17:24:48 +00001261
borenet@google.com97882362014-04-07 20:06:02 +00001262The .gclient file contains:
levarum@chromium.org27a6f9a2016-05-28 00:21:49 +00001263URL: %(expected_url)s (%(expected_scm)s)
1264Cache mirror: %(mirror_string)s
borenet@google.com97882362014-04-07 20:06:02 +00001265
1266The local checkout in %(checkout_path)s reports:
1267%(actual_url)s (%(actual_scm)s)
borenet@google.com88d10082014-03-21 17:24:48 +00001268
1269You should ensure that the URL listed in .gclient is correct and either change
agabled437d762016-10-17 09:35:11 -07001270it or fix the checkout.
borenet@google.com88d10082014-03-21 17:24:48 +00001271''' % {'checkout_path': os.path.join(self.root_dir, dep.name),
1272 'expected_url': dep.url,
1273 'expected_scm': gclient_scm.GetScmName(dep.url),
levarum@chromium.org27a6f9a2016-05-28 00:21:49 +00001274 'mirror_string' : mirror_string,
borenet@google.com88d10082014-03-21 17:24:48 +00001275 'actual_url': actual_url,
1276 'actual_scm': gclient_scm.GetScmName(actual_url)})
1277
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001278 def SetConfig(self, content):
maruel@chromium.orgf13a4182011-09-22 00:26:15 +00001279 assert not self.dependencies
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001280 config_dict = {}
1281 self.config_content = content
1282 try:
1283 exec(content, config_dict)
vapier@chromium.orga81a56e2015-11-11 07:56:13 +00001284 except SyntaxError as e:
maruel@chromium.org5990f9d2010-07-07 18:02:58 +00001285 gclient_utils.SyntaxErrorToError('.gclient', e)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001286
peter@chromium.org1efccc82012-04-27 16:34:38 +00001287 # Append any target OS that is not already being enforced to the tuple.
1288 target_os = config_dict.get('target_os', [])
stuartmorgan@chromium.org18a4f6a2012-11-02 13:37:24 +00001289 if config_dict.get('target_os_only', False):
1290 self._enforced_os = tuple(set(target_os))
1291 else:
1292 self._enforced_os = tuple(set(self._enforced_os).union(target_os))
1293
dyen@chromium.orgd915cca2014-08-07 21:41:37 +00001294 cache_dir = config_dict.get('cache_dir')
1295 if cache_dir:
1296 cache_dir = os.path.join(self.root_dir, cache_dir)
1297 cache_dir = os.path.abspath(cache_dir)
szager@chromium.orgcaf5bef2014-08-24 18:56:32 +00001298 # If running on a bot, force break any stale git cache locks.
dnj@chromium.orgb682b3e2014-08-25 19:17:12 +00001299 if os.path.exists(cache_dir) and os.environ.get('CHROME_HEADLESS'):
szager@chromium.org4848fb62014-08-24 19:16:31 +00001300 subprocess2.check_call(['git', 'cache', 'unlock', '--cache-dir',
1301 cache_dir, '--force', '--all'])
dyen@chromium.orgd915cca2014-08-07 21:41:37 +00001302 gclient_scm.GitWrapper.cache_dir = cache_dir
1303 git_cache.Mirror.SetCachePath(cache_dir)
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001304
stuartmorgan@chromium.org18a4f6a2012-11-02 13:37:24 +00001305 if not target_os and config_dict.get('target_os_only', False):
1306 raise gclient_utils.Error('Can\'t use target_os_only if target_os is '
1307 'not specified')
peter@chromium.org1efccc82012-04-27 16:34:38 +00001308
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001309 deps_to_add = []
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001310 for s in config_dict.get('solutions', []):
maruel@chromium.org81843b82010-06-28 16:49:26 +00001311 try:
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001312 deps_to_add.append(Dependency(
maruel@chromium.org81843b82010-06-28 16:49:26 +00001313 self, s['name'], s['url'],
smutae7ea312016-07-18 11:59:41 -07001314 s.get('managed', True),
maruel@chromium.org81843b82010-06-28 16:49:26 +00001315 s.get('custom_deps', {}),
maruel@chromium.org0d812442010-08-10 12:41:08 +00001316 s.get('custom_vars', {}),
petermayo@chromium.orge79161a2013-07-09 14:40:37 +00001317 s.get('custom_hooks', []),
nsylvain@google.comefc80932011-05-31 21:27:56 +00001318 s.get('deps_file', 'DEPS'),
agabledce6ddc2016-09-08 10:02:16 -07001319 True,
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +02001320 None,
1321 None,
1322 True))
maruel@chromium.org81843b82010-06-28 16:49:26 +00001323 except KeyError:
1324 raise gclient_utils.Error('Invalid .gclient file. Solution is '
1325 'incomplete: %s' % s)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001326 self.add_dependencies_and_close(deps_to_add, config_dict.get('hooks', []))
1327 logging.info('SetConfig() done')
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001328
1329 def SaveConfig(self):
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001330 gclient_utils.FileWrite(os.path.join(self.root_dir,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001331 self._options.config_filename),
1332 self.config_content)
1333
1334 @staticmethod
1335 def LoadCurrentConfig(options):
1336 """Searches for and loads a .gclient file relative to the current working
1337 dir. Returns a GClient object."""
szager@chromium.orge2e03202012-07-31 18:05:16 +00001338 if options.spec:
1339 client = GClient('.', options)
1340 client.SetConfig(options.spec)
1341 else:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00001342 if options.verbose:
1343 print('Looking for %s starting from %s\n' % (
1344 options.config_filename, os.getcwd()))
szager@chromium.orge2e03202012-07-31 18:05:16 +00001345 path = gclient_utils.FindGclientRoot(os.getcwd(), options.config_filename)
1346 if not path:
1347 return None
1348 client = GClient(path, options)
1349 client.SetConfig(gclient_utils.FileRead(
1350 os.path.join(path, options.config_filename)))
maruel@chromium.org69392e72011-10-13 22:09:00 +00001351
1352 if (options.revisions and
1353 len(client.dependencies) > 1 and
1354 any('@' not in r for r in options.revisions)):
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001355 print(
1356 ('You must specify the full solution name like --revision %s@%s\n'
1357 'when you have multiple solutions setup in your .gclient file.\n'
1358 'Other solutions present are: %s.') % (
maruel@chromium.org69392e72011-10-13 22:09:00 +00001359 client.dependencies[0].name,
1360 options.revisions[0],
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001361 ', '.join(s.name for s in client.dependencies[1:])),
1362 file=sys.stderr)
maruel@chromium.org15804092010-09-02 17:07:37 +00001363 return client
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001364
nsylvain@google.comefc80932011-05-31 21:27:56 +00001365 def SetDefaultConfig(self, solution_name, deps_file, solution_url,
agablea98a6cd2016-11-15 14:30:10 -08001366 managed=True, cache_dir=None):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001367 self.SetConfig(self.DEFAULT_CLIENT_FILE_TEXT % {
1368 'solution_name': solution_name,
1369 'solution_url': solution_url,
nsylvain@google.comefc80932011-05-31 21:27:56 +00001370 'deps_file': deps_file,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001371 'managed': managed,
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001372 'cache_dir': cache_dir,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001373 })
1374
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001375 def _SaveEntries(self):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001376 """Creates a .gclient_entries file to record the list of unique checkouts.
1377
1378 The .gclient_entries file lives in the same directory as .gclient.
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001379 """
1380 # Sometimes pprint.pformat will use {', sometimes it'll use { ' ... It
1381 # makes testing a bit too fun.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001382 result = 'entries = {\n'
maruel@chromium.org68988972011-09-20 14:11:42 +00001383 for entry in self.root.subtree(False):
agabled437d762016-10-17 09:35:11 -07001384 result += ' %s: %s,\n' % (pprint.pformat(entry.name),
1385 pprint.pformat(entry.parsed_url))
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001386 result += '}\n'
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001387 file_path = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org1333cb32011-10-04 23:40:16 +00001388 logging.debug(result)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001389 gclient_utils.FileWrite(file_path, result)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001390
1391 def _ReadEntries(self):
1392 """Read the .gclient_entries file for the given client.
1393
1394 Returns:
1395 A sequence of solution names, which will be empty if there is the
1396 entries file hasn't been created yet.
1397 """
1398 scope = {}
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001399 filename = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001400 if not os.path.exists(filename):
maruel@chromium.org73e21142010-07-05 13:32:01 +00001401 return {}
maruel@chromium.org5990f9d2010-07-07 18:02:58 +00001402 try:
1403 exec(gclient_utils.FileRead(filename), scope)
vapier@chromium.orga81a56e2015-11-11 07:56:13 +00001404 except SyntaxError as e:
maruel@chromium.org5990f9d2010-07-07 18:02:58 +00001405 gclient_utils.SyntaxErrorToError(filename, e)
Aaron Gable3721ee92017-04-03 14:53:14 -07001406 return scope.get('entries', {})
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001407
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001408 def _EnforceRevisions(self):
maruel@chromium.org918a9ae2010-05-28 15:50:30 +00001409 """Checks for revision overrides."""
1410 revision_overrides = {}
smutae7ea312016-07-18 11:59:41 -07001411 if self._options.head:
1412 return revision_overrides
joi@chromium.org792ea882010-11-10 02:37:27 +00001413 if not self._options.revisions:
1414 for s in self.dependencies:
smutae7ea312016-07-18 11:59:41 -07001415 if not s.managed:
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001416 self._options.revisions.append('%s@unmanaged' % s.name)
maruel@chromium.org307d1792010-05-31 20:03:13 +00001417 if not self._options.revisions:
1418 return revision_overrides
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001419 solutions_names = [s.name for s in self.dependencies]
smutae7ea312016-07-18 11:59:41 -07001420 index = 0
1421 for revision in self._options.revisions:
1422 if not '@' in revision:
maruel@chromium.org307d1792010-05-31 20:03:13 +00001423 # Support for --revision 123
smutae7ea312016-07-18 11:59:41 -07001424 revision = '%s@%s' % (solutions_names[index], revision)
1425 name, rev = revision.split('@', 1)
szager@chromium.org4ad264b2014-05-20 04:43:47 +00001426 revision_overrides[name] = rev
smutae7ea312016-07-18 11:59:41 -07001427 index += 1
maruel@chromium.org918a9ae2010-05-28 15:50:30 +00001428 return revision_overrides
1429
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001430 def RunOnDeps(self, command, args, ignore_requirements=False, progress=True):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001431 """Runs a command on each dependency in a client and its dependencies.
1432
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001433 Args:
1434 command: The command to use (e.g., 'status' or 'diff')
1435 args: list of str - extra arguments to add to the command line.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001436 """
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001437 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +00001438 raise gclient_utils.Error('No solution specified')
borenet@google.com0a427372014-04-02 19:12:13 +00001439
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001440 revision_overrides = {}
1441 # It's unnecessary to check for revision overrides for 'recurse'.
1442 # Save a few seconds by not calling _EnforceRevisions() in that case.
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +02001443 if command not in ('diff', 'recurse', 'runhooks', 'status', 'revert',
1444 'validate'):
szager@chromium.org5273b8a2014-08-21 15:10:10 +00001445 self._CheckConfig()
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001446 revision_overrides = self._EnforceRevisions()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001447 pm = None
maruel@chromium.org5b3f8852010-09-10 16:49:54 +00001448 # Disable progress for non-tty stdout.
iannucci@chromium.org596cd5c2016-04-04 21:34:39 +00001449 if (setup_color.IS_TTY and not self._options.verbose and progress):
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001450 if command in ('update', 'revert'):
1451 pm = Progress('Syncing projects', 1)
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +02001452 elif command in ('recurse', 'validate'):
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001453 pm = Progress(' '.join(args), 1)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001454 work_queue = gclient_utils.ExecutionQueue(
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001455 self._options.jobs, pm, ignore_requirements=ignore_requirements,
1456 verbose=self._options.verbose)
maruel@chromium.org049bced2010-08-12 13:37:20 +00001457 for s in self.dependencies:
1458 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +00001459 work_queue.flush(revision_overrides, command, args, options=self._options)
szager@chromium.org4ad264b2014-05-20 04:43:47 +00001460 if revision_overrides:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001461 print('Please fix your script, having invalid --revision flags will soon '
1462 'considered an error.', file=sys.stderr)
piman@chromium.org6f363722010-04-27 00:41:09 +00001463
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001464 # Once all the dependencies have been processed, it's now safe to run the
1465 # hooks.
1466 if not self._options.nohooks:
1467 self.RunHooksRecursively(self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001468
1469 if command == 'update':
ajwong@chromium.orgcdcee802009-06-23 15:30:42 +00001470 # Notify the user if there is an orphaned entry in their working copy.
1471 # Only delete the directory if there are no changes in it, and
1472 # delete_unversioned_trees is set to true.
maruel@chromium.org68988972011-09-20 14:11:42 +00001473 entries = [i.name for i in self.root.subtree(False) if i.url]
xusydoc@chromium.org885a9602013-05-31 09:54:40 +00001474 full_entries = [os.path.join(self.root_dir, e.replace('/', os.path.sep))
1475 for e in entries]
1476
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001477 for entry, prev_url in self._ReadEntries().iteritems():
maruel@chromium.org04dd7de2010-10-14 13:25:49 +00001478 if not prev_url:
1479 # entry must have been overridden via .gclient custom_deps
1480 continue
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001481 # Fix path separator on Windows.
1482 entry_fixed = entry.replace('/', os.path.sep)
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001483 e_dir = os.path.join(self.root_dir, entry_fixed)
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001484 # Use entry and not entry_fixed there.
jochen@chromium.orga78e5532013-03-11 13:33:03 +00001485 if (entry not in entries and
1486 (not any(path.startswith(entry + '/') for path in entries)) and
jochen@chromium.orgcc475722013-03-11 13:07:40 +00001487 os.path.exists(e_dir)):
primiano@chromium.org1c127382015-02-17 11:15:40 +00001488 # The entry has been removed from DEPS.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001489 scm = gclient_scm.CreateSCM(
1490 prev_url, self.root_dir, entry_fixed, self.outbuf)
xusydoc@chromium.org885a9602013-05-31 09:54:40 +00001491
1492 # Check to see if this directory is now part of a higher-up checkout.
borenet@google.com359bb642014-05-13 17:28:19 +00001493 scm_root = None
agabled437d762016-10-17 09:35:11 -07001494 try:
1495 scm_root = gclient_scm.scm.GIT.GetCheckoutRoot(scm.checkout_path)
1496 except subprocess2.CalledProcessError:
1497 pass
1498 if not scm_root:
borenet@google.com359bb642014-05-13 17:28:19 +00001499 logging.warning('Could not find checkout root for %s. Unable to '
1500 'determine whether it is part of a higher-level '
1501 'checkout, so not removing.' % entry)
1502 continue
primiano@chromium.org1c127382015-02-17 11:15:40 +00001503
1504 # This is to handle the case of third_party/WebKit migrating from
1505 # being a DEPS entry to being part of the main project.
1506 # If the subproject is a Git project, we need to remove its .git
1507 # folder. Otherwise git operations on that folder will have different
1508 # effects depending on the current working directory.
agabled437d762016-10-17 09:35:11 -07001509 if os.path.abspath(scm_root) == os.path.abspath(e_dir):
primiano@chromium.org1c127382015-02-17 11:15:40 +00001510 e_par_dir = os.path.join(e_dir, os.pardir)
agabled437d762016-10-17 09:35:11 -07001511 if gclient_scm.scm.GIT.IsInsideWorkTree(e_par_dir):
1512 par_scm_root = gclient_scm.scm.GIT.GetCheckoutRoot(e_par_dir)
primiano@chromium.org1c127382015-02-17 11:15:40 +00001513 # rel_e_dir : relative path of entry w.r.t. its parent repo.
1514 rel_e_dir = os.path.relpath(e_dir, par_scm_root)
agabled437d762016-10-17 09:35:11 -07001515 if gclient_scm.scm.GIT.IsDirectoryVersioned(
1516 par_scm_root, rel_e_dir):
primiano@chromium.org1c127382015-02-17 11:15:40 +00001517 save_dir = scm.GetGitBackupDirPath()
1518 # Remove any eventual stale backup dir for the same project.
1519 if os.path.exists(save_dir):
1520 gclient_utils.rmtree(save_dir)
1521 os.rename(os.path.join(e_dir, '.git'), save_dir)
1522 # When switching between the two states (entry/ is a subproject
1523 # -> entry/ is part of the outer project), it is very likely
1524 # that some files are changed in the checkout, unless we are
1525 # jumping *exactly* across the commit which changed just DEPS.
1526 # In such case we want to cleanup any eventual stale files
1527 # (coming from the old subproject) in order to end up with a
1528 # clean checkout.
agabled437d762016-10-17 09:35:11 -07001529 gclient_scm.scm.GIT.CleanupDir(par_scm_root, rel_e_dir)
primiano@chromium.org1c127382015-02-17 11:15:40 +00001530 assert not os.path.exists(os.path.join(e_dir, '.git'))
1531 print(('\nWARNING: \'%s\' has been moved from DEPS to a higher '
1532 'level checkout. The git folder containing all the local'
1533 ' branches has been saved to %s.\n'
1534 'If you don\'t care about its state you can safely '
1535 'remove that folder to free up space.') %
1536 (entry, save_dir))
1537 continue
1538
borenet@google.com359bb642014-05-13 17:28:19 +00001539 if scm_root in full_entries:
primiano@chromium.org1c127382015-02-17 11:15:40 +00001540 logging.info('%s is part of a higher level checkout, not removing',
1541 scm.GetCheckoutRoot())
xusydoc@chromium.org885a9602013-05-31 09:54:40 +00001542 continue
1543
1544 file_list = []
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001545 scm.status(self._options, [], file_list)
1546 modified_files = file_list != []
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001547 if (not self._options.delete_unversioned_trees or
1548 (modified_files and not self._options.force)):
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001549 # There are modified files in this entry. Keep warning until
1550 # removed.
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001551 print(('\nWARNING: \'%s\' is no longer part of this client. '
1552 'It is recommended that you manually remove it.\n') %
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001553 entry_fixed)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001554 else:
1555 # Delete the entry
maruel@chromium.org73e21142010-07-05 13:32:01 +00001556 print('\n________ deleting \'%s\' in \'%s\'' % (
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001557 entry_fixed, self.root_dir))
digit@chromium.orgdc112ac2013-04-24 13:00:19 +00001558 gclient_utils.rmtree(e_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001559 # record the current list of entries for next time
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001560 self._SaveEntries()
maruel@chromium.org17cdf762010-05-28 17:30:52 +00001561 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001562
1563 def PrintRevInfo(self):
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001564 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +00001565 raise gclient_utils.Error('No solution specified')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001566 # Load all the settings.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001567 work_queue = gclient_utils.ExecutionQueue(
1568 self._options.jobs, None, False, verbose=self._options.verbose)
maruel@chromium.org049bced2010-08-12 13:37:20 +00001569 for s in self.dependencies:
1570 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +00001571 work_queue.flush({}, None, [], options=self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001572
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001573 def GetURLAndRev(dep):
1574 """Returns the revision-qualified SCM url for a Dependency."""
1575 if dep.parsed_url is None:
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001576 return None
agabled437d762016-10-17 09:35:11 -07001577 url, _ = gclient_utils.SplitUrlRevision(dep.parsed_url)
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001578 scm = gclient_scm.CreateSCM(
agabled437d762016-10-17 09:35:11 -07001579 dep.parsed_url, self.root_dir, dep.name, self.outbuf)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001580 if not os.path.isdir(scm.checkout_path):
1581 return None
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001582 return '%s@%s' % (url, scm.revinfo(self._options, [], None))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001583
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001584 if self._options.snapshot:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001585 new_gclient = ''
1586 # First level at .gclient
1587 for d in self.dependencies:
1588 entries = {}
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001589 def GrabDeps(dep):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001590 """Recursively grab dependencies."""
maruel@chromium.org6da25d02010-08-11 17:32:55 +00001591 for d in dep.dependencies:
1592 entries[d.name] = GetURLAndRev(d)
1593 GrabDeps(d)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001594 GrabDeps(d)
1595 custom_deps = []
1596 for k in sorted(entries.keys()):
1597 if entries[k]:
1598 # Quotes aren't escaped...
1599 custom_deps.append(' \"%s\": \'%s\',\n' % (k, entries[k]))
1600 else:
1601 custom_deps.append(' \"%s\": None,\n' % k)
1602 new_gclient += self.DEFAULT_SNAPSHOT_SOLUTION_TEXT % {
1603 'solution_name': d.name,
1604 'solution_url': d.url,
nsylvain@google.comefc80932011-05-31 21:27:56 +00001605 'deps_file': d.deps_file,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001606 'managed': d.managed,
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001607 'solution_deps': ''.join(custom_deps),
1608 }
1609 # Print the snapshot configuration file
1610 print(self.DEFAULT_SNAPSHOT_FILE_TEXT % {'solution_list': new_gclient})
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +00001611 else:
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001612 entries = {}
maruel@chromium.org68988972011-09-20 14:11:42 +00001613 for d in self.root.subtree(False):
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001614 if self._options.actual:
1615 entries[d.name] = GetURLAndRev(d)
1616 else:
1617 entries[d.name] = d.parsed_url
1618 keys = sorted(entries.keys())
1619 for x in keys:
maruel@chromium.orgce464892010-08-12 17:12:18 +00001620 print('%s: %s' % (x, entries[x]))
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +00001621 logging.info(str(self))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001622
maruel@chromium.orgf50907b2010-08-12 17:05:48 +00001623 def ParseDepsFile(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001624 """No DEPS to parse for a .gclient file."""
maruel@chromium.org049bced2010-08-12 13:37:20 +00001625 raise gclient_utils.Error('Internal error')
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001626
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00001627 def PrintLocationAndContents(self):
1628 # Print out the .gclient file. This is longer than if we just printed the
1629 # client dict, but more legible, and it might contain helpful comments.
1630 print('Loaded .gclient config in %s:\n%s' % (
1631 self.root_dir, self.config_content))
1632
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001633 @property
maruel@chromium.org75a59272010-06-11 22:34:03 +00001634 def root_dir(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001635 """Root directory of gclient checkout."""
maruel@chromium.org75a59272010-06-11 22:34:03 +00001636 return self._root_dir
1637
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001638 @property
maruel@chromium.org271375b2010-06-23 19:17:38 +00001639 def enforced_os(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001640 """What deps_os entries that are to be parsed."""
maruel@chromium.org271375b2010-06-23 19:17:38 +00001641 return self._enforced_os
1642
maruel@chromium.org68988972011-09-20 14:11:42 +00001643 @property
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001644 def recursion_limit(self):
1645 """How recursive can each dependencies in DEPS file can load DEPS file."""
1646 return self._recursion_limit
1647
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +00001648 @property
cmp@chromium.orgc401ad12014-07-02 23:20:08 +00001649 def try_recursedeps(self):
1650 """Whether to attempt using recursedeps-style recursion processing."""
cmp@chromium.orge84ac912014-06-30 23:14:35 +00001651 return True
1652
1653 @property
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +00001654 def target_os(self):
1655 return self._enforced_os
1656
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001657
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001658#### gclient commands.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001659
1660
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001661@subcommand.usage('[command] [args ...]')
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001662def CMDrecurse(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001663 """Operates [command args ...] on all the dependencies.
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001664
1665 Runs a shell command on all entries.
qyearsley12fa6ff2016-08-24 09:18:40 -07001666 Sets GCLIENT_DEP_PATH environment variable as the dep's relative location to
ilevy@chromium.org37116242012-11-28 01:32:48 +00001667 root directory of the checkout.
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001668 """
1669 # Stop parsing at the first non-arg so that these go through to the command
1670 parser.disable_interspersed_args()
1671 parser.add_option('-s', '--scm', action='append', default=[],
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001672 help='Choose scm types to operate upon.')
maruel@chromium.org288054d2012-03-05 00:43:07 +00001673 parser.add_option('-i', '--ignore', action='store_true',
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001674 help='Ignore non-zero return codes from subcommands.')
1675 parser.add_option('--prepend-dir', action='store_true',
1676 help='Prepend relative dir for use with git <cmd> --null.')
1677 parser.add_option('--no-progress', action='store_true',
1678 help='Disable progress bar that shows sub-command updates')
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001679 options, args = parser.parse_args(args)
maruel@chromium.org45e9f2d2010-10-18 13:33:46 +00001680 if not args:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001681 print('Need to supply a command!', file=sys.stderr)
maruel@chromium.org45e9f2d2010-10-18 13:33:46 +00001682 return 1
maruel@chromium.org78cba522010-10-18 13:32:05 +00001683 root_and_entries = gclient_utils.GetGClientRootAndEntries()
1684 if not root_and_entries:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001685 print(
maruel@chromium.org78cba522010-10-18 13:32:05 +00001686 'You need to run gclient sync at least once to use \'recurse\'.\n'
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001687 'This is because .gclient_entries needs to exist and be up to date.',
1688 file=sys.stderr)
maruel@chromium.org78cba522010-10-18 13:32:05 +00001689 return 1
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001690
1691 # Normalize options.scm to a set()
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001692 scm_set = set()
1693 for scm in options.scm:
1694 scm_set.update(scm.split(','))
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001695 options.scm = scm_set
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001696
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001697 options.nohooks = True
1698 client = GClient.LoadCurrentConfig(options)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001699 return client.RunOnDeps('recurse', args, ignore_requirements=True,
1700 progress=not options.no_progress)
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001701
1702
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001703@subcommand.usage('[args ...]')
davidbarr@chromium.org12f944e2012-03-01 02:18:31 +00001704def CMDfetch(parser, args):
1705 """Fetches upstream commits for all modules.
1706
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001707 Completely git-specific. Simply runs 'git fetch [args ...]' for each module.
1708 """
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001709 (options, args) = parser.parse_args(args)
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00001710 return CMDrecurse(OptionParser(), [
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001711 '--jobs=%d' % options.jobs, '--scm=git', 'git', 'fetch'] + args)
1712
1713
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001714def CMDflatten(parser, args):
1715 """Flattens the solutions into a single DEPS file."""
1716 parser.add_option('--output-deps', help='Path to the output DEPS file')
1717 parser.add_option(
1718 '--require-pinned-revisions', action='store_true',
1719 help='Fail if any of the dependencies uses unpinned revision.')
1720 options, args = parser.parse_args(args)
1721
1722 options.nohooks = True
1723 client = GClient.LoadCurrentConfig(options)
1724
1725 # Only print progress if we're writing to a file. Otherwise, progress updates
1726 # could obscure intended output.
1727 code = client.RunOnDeps('flatten', args, progress=options.output_deps)
1728 if code != 0:
1729 return code
1730
1731 deps = {}
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02001732 deps_os = {}
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001733 hooks = []
1734 pre_deps_hooks = []
1735 unpinned_deps = {}
1736
1737 for solution in client.dependencies:
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02001738 _FlattenSolution(
1739 solution, deps, deps_os, hooks, pre_deps_hooks, unpinned_deps)
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001740
1741 if options.require_pinned_revisions and unpinned_deps:
1742 sys.stderr.write('The following dependencies are not pinned:\n')
1743 sys.stderr.write('\n'.join(sorted(unpinned_deps)))
1744 return 1
1745
1746 flattened_deps = '\n'.join(
Paweł Hajdan, Jr3c2aa832017-06-07 20:22:16 +02001747 _GNSettingsToLines(
1748 client.dependencies[0]._gn_args_file,
1749 client.dependencies[0]._gn_args) +
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001750 _DepsToLines(deps) +
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02001751 _DepsOsToLines(deps_os) +
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001752 _HooksToLines('hooks', hooks) +
1753 _HooksToLines('pre_deps_hooks', pre_deps_hooks) +
1754 [''] # Ensure newline at end of file.
1755 )
1756
1757 if options.output_deps:
1758 with open(options.output_deps, 'w') as f:
1759 f.write(flattened_deps)
1760 else:
1761 print(flattened_deps)
1762
1763 return 0
1764
1765
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02001766def _FlattenSolution(
1767 solution, deps, deps_os, hooks, pre_deps_hooks, unpinned_deps):
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001768 """Visits a solution in order to flatten it (see CMDflatten).
1769
1770 Arguments:
1771 solution (Dependency): one of top-level solutions in .gclient
1772
1773 Out-parameters:
1774 deps (dict of name -> Dependency): will be filled with all Dependency
1775 objects indexed by their name
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02001776 deps_os (dict of os name -> dep name -> Dependency): same as above,
1777 for OS-specific deps
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001778 hooks (list of (Dependency, hook)): will be filled with flattened hooks
1779 pre_deps_hooks (list of (Dependency, hook)): will be filled with flattened
1780 pre_deps_hooks
1781 unpinned_deps (dict of name -> Dependency): will be filled with unpinned
1782 deps
1783 """
1784 logging.debug('_FlattenSolution(%r)', solution)
1785
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02001786 _FlattenDep(solution, deps, deps_os, hooks, pre_deps_hooks, unpinned_deps)
1787 _FlattenRecurse(solution, deps, deps_os, hooks, pre_deps_hooks, unpinned_deps)
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001788
1789
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02001790def _FlattenDep(dep, deps, deps_os, hooks, pre_deps_hooks, unpinned_deps):
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001791 """Visits a dependency in order to flatten it (see CMDflatten).
1792
1793 Arguments:
1794 dep (Dependency): dependency to process
1795
1796 Out-parameters:
1797 deps (dict): will be filled with flattened deps
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02001798 deps_os (dict): will be filled with flattened deps_os
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001799 hooks (list): will be filled with flattened hooks
1800 pre_deps_hooks (list): will be filled with flattened pre_deps_hooks
1801 unpinned_deps (dict): will be filled with unpinned deps
1802 """
1803 logging.debug('_FlattenDep(%r)', dep)
1804
1805 _AddDep(dep, deps, unpinned_deps)
1806
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02001807 for dep_os, os_deps in dep.os_dependencies.iteritems():
1808 for os_dep in os_deps:
1809 deps_os.setdefault(dep_os, {})[os_dep.name] = os_dep
1810
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001811 deps_by_name = dict((d.name, d) for d in dep.dependencies)
1812 for recurse_dep_name in (dep.recursedeps or []):
1813 _FlattenRecurse(
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02001814 deps_by_name[recurse_dep_name], deps, deps_os, hooks, pre_deps_hooks,
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001815 unpinned_deps)
1816
1817 # TODO(phajdan.jr): also handle hooks_os.
1818 hooks.extend([(dep, hook) for hook in dep.deps_hooks])
1819 pre_deps_hooks.extend(
1820 [(dep, {'action': hook}) for hook in dep.pre_deps_hooks])
1821
1822
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02001823def _FlattenRecurse(dep, deps, deps_os, hooks, pre_deps_hooks, unpinned_deps):
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001824 """Helper for flatten that recurses into |dep|'s dependencies.
1825
1826 Arguments:
1827 dep (Dependency): dependency to process
1828
1829 Out-parameters:
1830 deps (dict): will be filled with flattened deps
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02001831 deps_os (dict): will be filled with flattened deps_os
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001832 hooks (list): will be filled with flattened hooks
1833 pre_deps_hooks (list): will be filled with flattened pre_deps_hooks
1834 unpinned_deps (dict): will be filled with unpinned deps
1835 """
1836 logging.debug('_FlattenRecurse(%r)', dep)
1837
1838 # TODO(phajdan.jr): also handle deps_os.
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +02001839 for sub_dep in dep.orig_dependencies:
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02001840 _FlattenDep(sub_dep, deps, deps_os, hooks, pre_deps_hooks, unpinned_deps)
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001841
1842
1843def _AddDep(dep, deps, unpinned_deps):
1844 """Helper to add a dependency to flattened lists.
1845
1846 Arguments:
1847 dep (Dependency): dependency to process
1848
1849 Out-parameters:
1850 deps (dict): will be filled with flattened deps
1851 unpinned_deps (dict): will be filled with unpinned deps
1852 """
1853 logging.debug('_AddDep(%r)', dep)
1854
1855 assert dep.name not in deps
1856 deps[dep.name] = dep
1857
1858 # Detect unpinned deps.
1859 _, revision = gclient_utils.SplitUrlRevision(dep.url)
1860 if not revision or not gclient_utils.IsGitSha(revision):
1861 unpinned_deps[dep.name] = dep
1862
1863
Paweł Hajdan, Jr3c2aa832017-06-07 20:22:16 +02001864def _GNSettingsToLines(gn_args_file, gn_args):
1865 s = []
1866 if gn_args_file:
1867 s.extend([
1868 'gclient_gn_args_file = "%s"' % gn_args_file,
1869 'gclient_gn_args = %r' % gn_args,
1870 ])
1871 return s
1872
1873
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001874def _DepsToLines(deps):
1875 """Converts |deps| dict to list of lines for output."""
1876 s = ['deps = {']
1877 for name, dep in sorted(deps.iteritems()):
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +02001878 condition_part = ([' "condition": "%s",' % dep.condition]
1879 if dep.condition else [])
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001880 s.extend([
1881 ' # %s' % dep.hierarchy(include_url=False),
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +02001882 ' "%s": {' % (name,),
1883 ' "url": "%s",' % (dep.url,),
1884 ] + condition_part + [
1885 ' },',
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001886 '',
1887 ])
1888 s.extend(['}', ''])
1889 return s
1890
1891
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02001892def _DepsOsToLines(deps_os):
1893 """Converts |deps_os| dict to list of lines for output."""
1894 s = ['deps_os = {']
1895 for dep_os, os_deps in sorted(deps_os.iteritems()):
1896 s.append(' "%s": {' % dep_os)
1897 s.extend([' %s' % l for l in _DepsToLines(os_deps)])
1898 s.extend([' },', ''])
1899 s.extend(['}', ''])
1900 return s
1901
1902
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02001903def _HooksToLines(name, hooks):
1904 """Converts |hooks| list to list of lines for output."""
1905 s = ['%s = [' % name]
1906 for dep, hook in hooks:
1907 s.extend([
1908 ' # %s' % dep.hierarchy(include_url=False),
1909 ' {',
1910 ])
1911 if 'name' in hook:
1912 s.append(' "name": "%s",' % hook['name'])
1913 if 'pattern' in hook:
1914 s.append(' "pattern": "%s",' % hook['pattern'])
1915 # TODO(phajdan.jr): actions may contain paths that need to be adjusted,
1916 # i.e. they may be relative to the dependency path, not solution root.
1917 s.extend(
1918 [' "action": ['] +
1919 [' "%s",' % arg for arg in hook['action']] +
1920 [' ]', ' },', '']
1921 )
1922 s.extend([']', ''])
1923 return s
1924
1925
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001926def CMDgrep(parser, args):
1927 """Greps through git repos managed by gclient.
1928
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001929 Runs 'git grep [args...]' for each module.
1930 """
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001931 # We can't use optparse because it will try to parse arguments sent
1932 # to git grep and throw an error. :-(
1933 if not args or re.match('(-h|--help)$', args[0]):
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001934 print(
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001935 'Usage: gclient grep [-j <N>] git-grep-args...\n\n'
1936 'Example: "gclient grep -j10 -A2 RefCountedBase" runs\n"git grep '
1937 '-A2 RefCountedBase" on each of gclient\'s git\nrepos with up to '
1938 '10 jobs.\n\nBonus: page output by appending "|& less -FRSX" to the'
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001939 ' end of your query.',
1940 file=sys.stderr)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001941 return 1
1942
1943 jobs_arg = ['--jobs=1']
1944 if re.match(r'(-j|--jobs=)\d+$', args[0]):
1945 jobs_arg, args = args[:1], args[1:]
1946 elif re.match(r'(-j|--jobs)$', args[0]):
1947 jobs_arg, args = args[:2], args[2:]
1948
1949 return CMDrecurse(
1950 parser,
1951 jobs_arg + ['--ignore', '--prepend-dir', '--no-progress', '--scm=git',
1952 'git', 'grep', '--null', '--color=Always'] + args)
davidbarr@chromium.org12f944e2012-03-01 02:18:31 +00001953
1954
stip@chromium.orga735da22015-04-29 23:18:20 +00001955def CMDroot(parser, args):
1956 """Outputs the solution root (or current dir if there isn't one)."""
1957 (options, args) = parser.parse_args(args)
1958 client = GClient.LoadCurrentConfig(options)
1959 if client:
1960 print(os.path.abspath(client.root_dir))
1961 else:
1962 print(os.path.abspath('.'))
1963
1964
agablea98a6cd2016-11-15 14:30:10 -08001965@subcommand.usage('[url]')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001966def CMDconfig(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001967 """Creates a .gclient file in the current directory.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001968
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001969 This specifies the configuration for further commands. After update/sync,
1970 top-level DEPS files in each module are read to determine dependent
1971 modules to operate on as well. If optional [url] parameter is
1972 provided, then configuration is read from a specified Subversion server
1973 URL.
1974 """
szager@chromium.orge2e03202012-07-31 18:05:16 +00001975 # We do a little dance with the --gclientfile option. 'gclient config' is the
1976 # only command where it's acceptable to have both '--gclientfile' and '--spec'
1977 # arguments. So, we temporarily stash any --gclientfile parameter into
1978 # options.output_config_file until after the (gclientfile xor spec) error
1979 # check.
1980 parser.remove_option('--gclientfile')
1981 parser.add_option('--gclientfile', dest='output_config_file',
1982 help='Specify an alternate .gclient file')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001983 parser.add_option('--name',
1984 help='overrides the default name for the solution')
nsylvain@google.comefc80932011-05-31 21:27:56 +00001985 parser.add_option('--deps-file', default='DEPS',
1986 help='overrides the default name for the DEPS file for the'
1987 'main solutions and all sub-dependencies')
smutae7ea312016-07-18 11:59:41 -07001988 parser.add_option('--unmanaged', action='store_true', default=False,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001989 help='overrides the default behavior to make it possible '
smutae7ea312016-07-18 11:59:41 -07001990 'to have the main solution untouched by gclient '
1991 '(gclient will check out unmanaged dependencies but '
1992 'will never sync them)')
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001993 parser.add_option('--cache-dir',
1994 help='(git only) Cache all git repos into this dir and do '
1995 'shared clones from the cache, instead of cloning '
1996 'directly from the remote. (experimental)')
szager@chromium.orge2e03202012-07-31 18:05:16 +00001997 parser.set_defaults(config_filename=None)
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001998 (options, args) = parser.parse_args(args)
szager@chromium.orge2e03202012-07-31 18:05:16 +00001999 if options.output_config_file:
2000 setattr(options, 'config_filename', getattr(options, 'output_config_file'))
maruel@chromium.org5fc2a332010-05-26 19:37:15 +00002001 if ((options.spec and args) or len(args) > 2 or
2002 (not options.spec and not args)):
2003 parser.error('Inconsistent arguments. Use either --spec or one or 2 args')
2004
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002005 client = GClient('.', options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002006 if options.spec:
2007 client.SetConfig(options.spec)
2008 else:
maruel@chromium.org1ab7ffc2009-06-03 17:21:37 +00002009 base_url = args[0].rstrip('/')
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00002010 if not options.name:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002011 name = base_url.split('/')[-1]
nsylvain@google.com12649ef2011-06-01 17:11:20 +00002012 if name.endswith('.git'):
2013 name = name[:-4]
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00002014 else:
2015 # specify an alternate relpath for the given URL.
2016 name = options.name
agable@chromium.orgf2214672015-10-27 21:02:48 +00002017 if not os.path.abspath(os.path.join(os.getcwd(), name)).startswith(
2018 os.getcwd()):
2019 parser.error('Do not pass a relative path for --name.')
2020 if any(x in ('..', '.', '/', '\\') for x in name.split(os.sep)):
2021 parser.error('Do not include relative path components in --name.')
2022
nsylvain@google.comefc80932011-05-31 21:27:56 +00002023 deps_file = options.deps_file
agablea98a6cd2016-11-15 14:30:10 -08002024 client.SetDefaultConfig(name, deps_file, base_url,
smutae7ea312016-07-18 11:59:41 -07002025 managed=not options.unmanaged,
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00002026 cache_dir=options.cache_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002027 client.SaveConfig()
maruel@chromium.org79692d62010-05-14 18:57:13 +00002028 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002029
2030
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002031@subcommand.epilog("""Example:
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002032 gclient pack > patch.txt
2033 generate simple patch for configured client and dependences
2034""")
2035def CMDpack(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002036 """Generates a patch which can be applied at the root of the tree.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00002037
agabled437d762016-10-17 09:35:11 -07002038 Internally, runs 'git diff' on each checked out module and
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002039 dependencies, and performs minimal postprocessing of the output. The
2040 resulting patch is printed to stdout and can be applied to a freshly
2041 checked out tree via 'patch -p0 < patchfile'.
2042 """
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002043 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2044 help='override deps for the specified (comma-separated) '
2045 'platform(s); \'all\' will process all deps_os '
2046 'references')
haitao.feng@intel.com306080c2012-05-04 13:11:29 +00002047 parser.remove_option('--jobs')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002048 (options, args) = parser.parse_args(args)
iannucci@chromium.org50395ea2013-04-04 04:47:42 +00002049 # Force jobs to 1 so the stdout is not annotated with the thread ids
haitao.feng@intel.com306080c2012-05-04 13:11:29 +00002050 options.jobs = 1
kbr@google.comab318592009-09-04 00:54:55 +00002051 client = GClient.LoadCurrentConfig(options)
2052 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002053 raise gclient_utils.Error('client not configured; see \'gclient config\'')
kbr@google.comab318592009-09-04 00:54:55 +00002054 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002055 client.PrintLocationAndContents()
kbr@google.comab318592009-09-04 00:54:55 +00002056 return client.RunOnDeps('pack', args)
2057
2058
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002059def CMDstatus(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002060 """Shows modification status for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002061 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2062 help='override deps for the specified (comma-separated) '
2063 'platform(s); \'all\' will process all deps_os '
2064 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002065 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002066 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002067 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002068 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002069 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002070 client.PrintLocationAndContents()
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002071 return client.RunOnDeps('status', args)
2072
2073
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002074@subcommand.epilog("""Examples:
maruel@chromium.org79692d62010-05-14 18:57:13 +00002075 gclient sync
2076 update files from SCM according to current configuration,
2077 *for modules which have changed since last update or sync*
2078 gclient sync --force
2079 update files from SCM according to current configuration, for
2080 all modules (useful for recovering files deleted from local copy)
2081 gclient sync --revision src@31000
2082 update src directory to r31000
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002083
2084JSON output format:
2085If the --output-json option is specified, the following document structure will
2086be emitted to the provided file. 'null' entries may occur for subprojects which
2087are present in the gclient solution, but were not processed (due to custom_deps,
2088os_deps, etc.)
2089
2090{
2091 "solutions" : {
2092 "<name>": { # <name> is the posix-normalized path to the solution.
agabled437d762016-10-17 09:35:11 -07002093 "revision": [<git id hex string>|null],
2094 "scm": ["git"|null],
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002095 }
2096 }
2097}
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002098""")
2099def CMDsync(parser, args):
2100 """Checkout/update all modules."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002101 parser.add_option('-f', '--force', action='store_true',
2102 help='force update even for unchanged modules')
2103 parser.add_option('-n', '--nohooks', action='store_true',
2104 help='don\'t run hooks after the update is complete')
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00002105 parser.add_option('-p', '--noprehooks', action='store_true',
2106 help='don\'t run pre-DEPS hooks', default=False)
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002107 parser.add_option('-r', '--revision', action='append',
2108 dest='revisions', metavar='REV', default=[],
2109 help='Enforces revision/hash for the solutions with the '
2110 'format src@rev. The src@ part is optional and can be '
2111 'skipped. -r can be used multiple times when .gclient '
2112 'has multiple solutions configured and will work even '
agablea98a6cd2016-11-15 14:30:10 -08002113 'if the src@ part is skipped.')
maruel@chromium.org794207e2013-03-08 15:29:43 +00002114 parser.add_option('--with_branch_heads', action='store_true',
2115 help='Clone git "branch_heads" refspecs in addition to '
2116 'the default refspecs. This adds about 1/2GB to a '
2117 'full checkout. (git only)')
szager@chromium.org8d3348f2014-08-19 22:49:16 +00002118 parser.add_option('--with_tags', action='store_true',
2119 help='Clone git tags in addition to the default refspecs.')
agable2697cd12016-06-28 10:23:53 -07002120 parser.add_option('-H', '--head', action='store_true',
agablea98a6cd2016-11-15 14:30:10 -08002121 help='DEPRECATED: only made sense with safesync urls.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002122 parser.add_option('-D', '--delete_unversioned_trees', action='store_true',
steveblock@chromium.org98e69452012-02-16 16:36:43 +00002123 help='Deletes from the working copy any dependencies that '
2124 'have been removed since the last sync, as long as '
2125 'there are no local modifications. When used with '
2126 '--force, such dependencies are removed even if they '
2127 'have local modifications. When used with --reset, '
2128 'all untracked directories are removed from the '
tnagel@chromium.orga2aaa632014-02-28 21:47:27 +00002129 'working copy, excluding those which are explicitly '
steveblock@chromium.org98e69452012-02-16 16:36:43 +00002130 'ignored in the repository.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002131 parser.add_option('-R', '--reset', action='store_true',
2132 help='resets any local changes before updating (git only)')
bauerb@chromium.org2aad1b22011-07-22 12:00:41 +00002133 parser.add_option('-M', '--merge', action='store_true',
2134 help='merge upstream changes instead of trying to '
2135 'fast-forward or rebase')
dnj@chromium.org5b23e872015-02-20 21:25:57 +00002136 parser.add_option('-A', '--auto_rebase', action='store_true',
2137 help='Automatically rebase repositories against local '
2138 'checkout during update (git only).')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002139 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2140 help='override deps for the specified (comma-separated) '
2141 'platform(s); \'all\' will process all deps_os '
2142 'references')
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +00002143 parser.add_option('--upstream', action='store_true',
2144 help='Make repo state match upstream branch.')
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002145 parser.add_option('--output-json',
2146 help='Output a json document to this path containing '
2147 'summary information about the sync.')
primiano@chromium.org5439ea52014-08-06 17:18:18 +00002148 parser.add_option('--no-history', action='store_true',
2149 help='GIT ONLY - Reduces the size/time of the checkout at '
2150 'the cost of no history. Requires Git 1.9+')
hinoka@chromium.org46b87412014-05-15 00:42:05 +00002151 parser.add_option('--shallow', action='store_true',
2152 help='GIT ONLY - Do a shallow clone into the cache dir. '
2153 'Requires Git 1.9+')
e.hakkinen@samsung.come8bc1aa2015-04-08 08:00:37 +00002154 parser.add_option('--no_bootstrap', '--no-bootstrap',
2155 action='store_true',
2156 help='Don\'t bootstrap from Google Storage.')
hinoka@chromium.org8a10f6d2014-06-23 18:38:57 +00002157 parser.add_option('--ignore_locks', action='store_true',
2158 help='GIT ONLY - Ignore cache locks.')
iannucci@chromium.org30a07982016-04-07 21:35:19 +00002159 parser.add_option('--break_repo_locks', action='store_true',
2160 help='GIT ONLY - Forcibly remove repo locks (e.g. '
2161 'index.lock). This should only be used if you know for '
2162 'certain that this invocation of gclient is the only '
2163 'thing operating on the git repos (e.g. on a bot).')
nodir@chromium.org5b48e482016-03-18 20:27:54 +00002164 parser.add_option('--lock_timeout', type='int', default=5000,
szager@chromium.orgdbb6f822016-02-02 22:59:30 +00002165 help='GIT ONLY - Deadline (in seconds) to wait for git '
nodir@chromium.org5b48e482016-03-18 20:27:54 +00002166 'cache lock to become available. Default is %default.')
agabled437d762016-10-17 09:35:11 -07002167 # TODO(agable): Remove these when the oldest CrOS release milestone is M56.
2168 parser.add_option('-t', '--transitive', action='store_true',
2169 help='DEPRECATED: This is a no-op.')
sdefresne69b1be12016-10-18 05:48:02 -07002170 parser.add_option('-m', '--manually_grab_svn_rev', action='store_true',
agabled437d762016-10-17 09:35:11 -07002171 help='DEPRECATED: This is a no-op.')
Paweł Hajdan, Jr7c7b5592017-05-23 15:06:05 +02002172 # TODO(phajdan.jr): Remove validation options once default (crbug/570091).
Paweł Hajdan, Jr694773d2017-05-29 16:06:23 +02002173 parser.add_option('--validate-syntax', action='store_true', default=True,
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +02002174 help='Validate the .gclient and DEPS syntax')
Paweł Hajdan, Jr7c7b5592017-05-23 15:06:05 +02002175 parser.add_option('--disable-syntax-validation', action='store_false',
2176 dest='validate_syntax',
2177 help='Disable validation of .gclient and DEPS syntax.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002178 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002179 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002180
2181 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002182 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002183
smutae7ea312016-07-18 11:59:41 -07002184 if options.revisions and options.head:
2185 # TODO(maruel): Make it a parser.error if it doesn't break any builder.
2186 print('Warning: you cannot use both --head and --revision')
2187
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002188 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002189 client.PrintLocationAndContents()
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002190 ret = client.RunOnDeps('update', args)
2191 if options.output_json:
2192 slns = {}
2193 for d in client.subtree(True):
2194 normed = d.name.replace('\\', '/').rstrip('/') + '/'
2195 slns[normed] = {
2196 'revision': d.got_revision,
2197 'scm': d.used_scm.name if d.used_scm else None,
hinoka@chromium.org17db9052014-05-10 01:11:29 +00002198 'url': str(d.url) if d.url else None,
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002199 }
2200 with open(options.output_json, 'wb') as f:
2201 json.dump({'solutions': slns}, f)
2202 return ret
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002203
2204
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002205CMDupdate = CMDsync
2206
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002207
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +02002208def CMDvalidate(parser, args):
2209 """Validates the .gclient and DEPS syntax."""
2210 options, args = parser.parse_args(args)
2211 options.validate_syntax = True
2212 client = GClient.LoadCurrentConfig(options)
2213 rv = client.RunOnDeps('validate', args)
2214 if rv == 0:
2215 print('validate: SUCCESS')
2216 else:
2217 print('validate: FAILURE')
2218 return rv
2219
2220
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002221def CMDdiff(parser, args):
2222 """Displays local diff for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002223 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2224 help='override deps for the specified (comma-separated) '
2225 'platform(s); \'all\' will process all deps_os '
2226 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002227 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002228 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002229 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002230 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002231 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002232 client.PrintLocationAndContents()
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002233 return client.RunOnDeps('diff', args)
2234
2235
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002236def CMDrevert(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002237 """Reverts all modifications in every dependencies.
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00002238
2239 That's the nuclear option to get back to a 'clean' state. It removes anything
agabled437d762016-10-17 09:35:11 -07002240 that shows up in git status."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002241 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2242 help='override deps for the specified (comma-separated) '
2243 'platform(s); \'all\' will process all deps_os '
2244 'references')
2245 parser.add_option('-n', '--nohooks', action='store_true',
2246 help='don\'t run hooks after the revert is complete')
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00002247 parser.add_option('-p', '--noprehooks', action='store_true',
2248 help='don\'t run pre-DEPS hooks', default=False)
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +00002249 parser.add_option('--upstream', action='store_true',
2250 help='Make repo state match upstream branch.')
iannucci@chromium.orgbf525dc2016-04-07 22:00:28 +00002251 parser.add_option('--break_repo_locks', action='store_true',
2252 help='GIT ONLY - Forcibly remove repo locks (e.g. '
2253 'index.lock). This should only be used if you know for '
2254 'certain that this invocation of gclient is the only '
2255 'thing operating on the git repos (e.g. on a bot).')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002256 (options, args) = parser.parse_args(args)
2257 # --force is implied.
2258 options.force = True
steveblock@chromium.org98e69452012-02-16 16:36:43 +00002259 options.reset = False
2260 options.delete_unversioned_trees = False
agablec903d732016-07-26 09:07:24 -07002261 options.merge = False
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002262 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002263 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002264 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002265 return client.RunOnDeps('revert', args)
2266
2267
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002268def CMDrunhooks(parser, args):
2269 """Runs hooks for files that have been modified in the local working copy."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002270 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2271 help='override deps for the specified (comma-separated) '
2272 'platform(s); \'all\' will process all deps_os '
2273 'references')
2274 parser.add_option('-f', '--force', action='store_true', default=True,
2275 help='Deprecated. No effect.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002276 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002277 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002278 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002279 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002280 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002281 client.PrintLocationAndContents()
maruel@chromium.org5df6a462009-08-28 18:52:26 +00002282 options.force = True
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002283 options.nohooks = False
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002284 return client.RunOnDeps('runhooks', args)
2285
2286
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002287def CMDrevinfo(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002288 """Outputs revision info mapping for the client and its dependencies.
maruel@chromium.org9eda4112010-06-11 18:56:10 +00002289
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002290 This allows the capture of an overall 'revision' for the source tree that
maruel@chromium.org9eda4112010-06-11 18:56:10 +00002291 can be used to reproduce the same tree in the future. It is only useful for
agabled437d762016-10-17 09:35:11 -07002292 'unpinned dependencies', i.e. DEPS/deps references without a git hash.
2293 A git branch name isn't 'pinned' since the actual commit can change.
maruel@chromium.org9eda4112010-06-11 18:56:10 +00002294 """
2295 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2296 help='override deps for the specified (comma-separated) '
2297 'platform(s); \'all\' will process all deps_os '
2298 'references')
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00002299 parser.add_option('-a', '--actual', action='store_true',
2300 help='gets the actual checked out revisions instead of the '
2301 'ones specified in the DEPS and .gclient files')
maruel@chromium.org9eda4112010-06-11 18:56:10 +00002302 parser.add_option('-s', '--snapshot', action='store_true',
2303 help='creates a snapshot .gclient file of the current '
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00002304 'version of all repositories to reproduce the tree, '
2305 'implies -a')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002306 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002307 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002308 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002309 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002310 client.PrintRevInfo()
maruel@chromium.org79692d62010-05-14 18:57:13 +00002311 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002312
2313
szager@google.comb9a78d32012-03-13 18:46:21 +00002314def CMDhookinfo(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002315 """Outputs the hooks that would be run by `gclient runhooks`."""
szager@google.comb9a78d32012-03-13 18:46:21 +00002316 (options, args) = parser.parse_args(args)
2317 options.force = True
2318 client = GClient.LoadCurrentConfig(options)
2319 if not client:
2320 raise gclient_utils.Error('client not configured; see \'gclient config\'')
2321 client.RunOnDeps(None, [])
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002322 print('; '.join(' '.join(hook) for hook in client.GetHooks(options)))
szager@google.comb9a78d32012-03-13 18:46:21 +00002323 return 0
2324
2325
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00002326def CMDverify(parser, args):
2327 """Verifies the DEPS file deps are only from allowed_hosts."""
2328 (options, args) = parser.parse_args(args)
2329 client = GClient.LoadCurrentConfig(options)
2330 if not client:
2331 raise gclient_utils.Error('client not configured; see \'gclient config\'')
2332 client.RunOnDeps(None, [])
2333 # Look at each first-level dependency of this gclient only.
2334 for dep in client.dependencies:
2335 bad_deps = dep.findDepsFromNotAllowedHosts()
2336 if not bad_deps:
2337 continue
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002338 print("There are deps from not allowed hosts in file %s" % dep.deps_file)
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00002339 for bad_dep in bad_deps:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002340 print("\t%s at %s" % (bad_dep.name, bad_dep.url))
2341 print("allowed_hosts:", ', '.join(dep.allowed_hosts))
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00002342 sys.stdout.flush()
2343 raise gclient_utils.Error(
2344 'dependencies from disallowed hosts; check your DEPS file.')
2345 return 0
2346
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002347class OptionParser(optparse.OptionParser):
szager@chromium.orge2e03202012-07-31 18:05:16 +00002348 gclientfile_default = os.environ.get('GCLIENT_FILE', '.gclient')
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002349
2350 def __init__(self, **kwargs):
2351 optparse.OptionParser.__init__(
2352 self, version='%prog ' + __version__, **kwargs)
2353
2354 # Some arm boards have issues with parallel sync.
2355 if platform.machine().startswith('arm'):
2356 jobs = 1
2357 else:
2358 jobs = max(8, gclient_utils.NumLocalCpus())
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002359
2360 self.add_option(
2361 '-j', '--jobs', default=jobs, type='int',
2362 help='Specify how many SCM commands can run in parallel; defaults to '
tnagel@chromium.orga2aaa632014-02-28 21:47:27 +00002363 '%default on this machine')
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002364 self.add_option(
2365 '-v', '--verbose', action='count', default=0,
2366 help='Produces additional output for diagnostics. Can be used up to '
2367 'three times for more logging info.')
2368 self.add_option(
2369 '--gclientfile', dest='config_filename',
2370 help='Specify an alternate %s file' % self.gclientfile_default)
2371 self.add_option(
2372 '--spec',
2373 help='create a gclient file containing the provided string. Due to '
2374 'Cygwin/Python brokenness, it can\'t contain any newlines.')
2375 self.add_option(
2376 '--no-nag-max', default=False, action='store_true',
scottmg@chromium.orgf547c802013-09-27 17:55:26 +00002377 help='Ignored for backwards compatibility.')
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002378
2379 def parse_args(self, args=None, values=None):
2380 """Integrates standard options processing."""
2381 options, args = optparse.OptionParser.parse_args(self, args, values)
2382 levels = [logging.ERROR, logging.WARNING, logging.INFO, logging.DEBUG]
2383 logging.basicConfig(
2384 level=levels[min(options.verbose, len(levels) - 1)],
maruel@chromium.org0895b752011-08-26 20:40:33 +00002385 format='%(module)s(%(lineno)d) %(funcName)s:%(message)s')
szager@chromium.orge2e03202012-07-31 18:05:16 +00002386 if options.config_filename and options.spec:
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002387 self.error('Cannot specifiy both --gclientfile and --spec')
rdsmith@chromium.orgd9591f02014-02-05 19:28:20 +00002388 if (options.config_filename and
2389 options.config_filename != os.path.basename(options.config_filename)):
2390 self.error('--gclientfile target must be a filename, not a path')
szager@chromium.orge2e03202012-07-31 18:05:16 +00002391 if not options.config_filename:
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002392 options.config_filename = self.gclientfile_default
maruel@chromium.org0895b752011-08-26 20:40:33 +00002393 options.entries_filename = options.config_filename + '_entries'
2394 if options.jobs < 1:
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002395 self.error('--jobs must be 1 or higher')
maruel@chromium.org0895b752011-08-26 20:40:33 +00002396
2397 # These hacks need to die.
2398 if not hasattr(options, 'revisions'):
2399 # GClient.RunOnDeps expects it even if not applicable.
2400 options.revisions = []
smutae7ea312016-07-18 11:59:41 -07002401 if not hasattr(options, 'head'):
2402 options.head = None
maruel@chromium.org0895b752011-08-26 20:40:33 +00002403 if not hasattr(options, 'nohooks'):
2404 options.nohooks = True
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00002405 if not hasattr(options, 'noprehooks'):
2406 options.noprehooks = True
maruel@chromium.org0895b752011-08-26 20:40:33 +00002407 if not hasattr(options, 'deps_os'):
2408 options.deps_os = None
maruel@chromium.org0895b752011-08-26 20:40:33 +00002409 if not hasattr(options, 'force'):
2410 options.force = None
2411 return (options, args)
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002412
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002413
2414def disable_buffering():
2415 # Make stdout auto-flush so buildbot doesn't kill us during lengthy
2416 # operations. Python as a strong tendency to buffer sys.stdout.
2417 sys.stdout = gclient_utils.MakeFileAutoFlush(sys.stdout)
2418 # Make stdout annotated with the thread ids.
2419 sys.stdout = gclient_utils.MakeFileAnnotated(sys.stdout)
maruel@chromium.org0895b752011-08-26 20:40:33 +00002420
2421
sbc@chromium.org013731e2015-02-26 18:28:43 +00002422def main(argv):
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002423 """Doesn't parse the arguments here, just find the right subcommand to
2424 execute."""
maruel@chromium.org82798cb2012-02-23 18:16:12 +00002425 if sys.hexversion < 0x02060000:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002426 print(
maruel@chromium.org82798cb2012-02-23 18:16:12 +00002427 '\nYour python version %s is unsupported, please upgrade.\n' %
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002428 sys.version.split(' ', 1)[0],
2429 file=sys.stderr)
maruel@chromium.org82798cb2012-02-23 18:16:12 +00002430 return 2
bcwhite@chromium.org6683ab42013-02-11 16:13:47 +00002431 if not sys.executable:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002432 print(
2433 '\nPython cannot find the location of it\'s own executable.\n',
2434 file=sys.stderr)
bcwhite@chromium.org6683ab42013-02-11 16:13:47 +00002435 return 2
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002436 fix_encoding.fix_encoding()
2437 disable_buffering()
iannucci@chromium.org596cd5c2016-04-04 21:34:39 +00002438 setup_color.init()
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002439 dispatcher = subcommand.CommandDispatcher(__name__)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00002440 try:
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002441 return dispatcher.execute(OptionParser(), argv)
xusydoc@chromium.org2fd6c3f2013-05-03 21:57:55 +00002442 except KeyboardInterrupt:
2443 gclient_utils.GClientChildren.KillAllRemainingChildren()
2444 raise
vapier@chromium.orga81a56e2015-11-11 07:56:13 +00002445 except (gclient_utils.Error, subprocess2.CalledProcessError) as e:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002446 print('Error: %s' % str(e), file=sys.stderr)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00002447 return 1
borenet@google.com6a9b1682014-03-24 18:35:23 +00002448 finally:
2449 gclient_utils.PrintWarnings()
sbc@chromium.org013731e2015-02-26 18:28:43 +00002450 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002451
2452
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00002453if '__main__' == __name__:
sbc@chromium.org013731e2015-02-26 18:28:43 +00002454 try:
2455 sys.exit(main(sys.argv[1:]))
2456 except KeyboardInterrupt:
2457 sys.stderr.write('interrupted\n')
2458 sys.exit(1)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002459
2460# vim: ts=2:sw=2:tw=80:et: