blob: 7ecc727632a34c1ff304dca38b4ee3bbcfdff4b3 [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
Tom Andersonc31ae0b2018-02-06 14:48:56 -080078#
79# Specifying a target CPU
80# To specify a target CPU, the variables target_cpu and target_cpu_only
81# are available and are analagous to target_os and target_os_only.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000082
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +000083from __future__ import print_function
84
maruel@chromium.org39c0b222013-08-17 16:57:01 +000085__version__ = '0.7'
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000086
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +020087import collections
maruel@chromium.org9e5317a2010-08-13 20:35:11 +000088import copy
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +000089import json
maruel@chromium.org754960e2009-09-21 12:31:05 +000090import logging
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000091import optparse
92import os
bradnelson@google.com4949dab2012-04-19 16:41:07 +000093import platform
maruel@chromium.org621939b2010-08-10 20:12:00 +000094import posixpath
msb@chromium.org2e38de72009-09-28 17:04:47 +000095import pprint
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000096import re
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000097import sys
ilevy@chromium.orgc28d3772013-07-12 19:42:37 +000098import time
bradnelson@google.com4949dab2012-04-19 16:41:07 +000099import urlparse
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000100
Tom Andersonc31ae0b2018-02-06 14:48:56 -0800101import detect_host_arch
maruel@chromium.org35625c72011-03-23 17:34:02 +0000102import fix_encoding
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +0200103import gclient_eval
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000104import gclient_scm
105import gclient_utils
szager@chromium.org848fd492014-04-09 19:06:44 +0000106import git_cache
nasser@codeaurora.org1f7a3d12010-02-04 15:11:50 +0000107from third_party.repo.progress import Progress
maruel@chromium.org39c0b222013-08-17 16:57:01 +0000108import subcommand
maruel@chromium.org31cb48a2011-04-04 18:01:36 +0000109import subprocess2
iannucci@chromium.org596cd5c2016-04-04 21:34:39 +0000110import setup_color
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000111
112
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +0200113class GNException(Exception):
114 pass
115
116
117def ToGNString(value, allow_dicts = True):
118 """Returns a stringified GN equivalent of the Python value.
119
120 allow_dicts indicates if this function will allow converting dictionaries
121 to GN scopes. This is only possible at the top level, you can't nest a
122 GN scope in a list, so this should be set to False for recursive calls."""
123 if isinstance(value, basestring):
124 if value.find('\n') >= 0:
125 raise GNException("Trying to print a string with a newline in it.")
126 return '"' + \
127 value.replace('\\', '\\\\').replace('"', '\\"').replace('$', '\\$') + \
128 '"'
129
130 if isinstance(value, unicode):
131 return ToGNString(value.encode('utf-8'))
132
133 if isinstance(value, bool):
134 if value:
135 return "true"
136 return "false"
137
138 # NOTE: some type handling removed compared to chromium/src copy.
139
140 raise GNException("Unsupported type when printing to GN.")
141
142
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200143class Hook(object):
144 """Descriptor of command ran before/after sync or on demand."""
145
Paweł Hajdan, Jr032d5452017-06-22 20:43:53 +0200146 def __init__(self, action, pattern=None, name=None, cwd=None, condition=None,
Daniel Chenga0c5f082017-10-19 13:35:19 -0700147 variables=None, verbose=False):
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200148 """Constructor.
149
150 Arguments:
151 action (list of basestring): argv of the command to run
152 pattern (basestring regex): noop with git; deprecated
153 name (basestring): optional name; no effect on operation
Paweł Hajdan, Jrc9364392017-06-14 17:11:56 +0200154 cwd (basestring): working directory to use
Paweł Hajdan, Jr032d5452017-06-22 20:43:53 +0200155 condition (basestring): condition when to run the hook
156 variables (dict): variables for evaluating the condition
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200157 """
158 self._action = gclient_utils.freeze(action)
159 self._pattern = pattern
160 self._name = name
Paweł Hajdan, Jrc9364392017-06-14 17:11:56 +0200161 self._cwd = cwd
Paweł Hajdan, Jr032d5452017-06-22 20:43:53 +0200162 self._condition = condition
163 self._variables = variables
Daniel Chenga0c5f082017-10-19 13:35:19 -0700164 self._verbose = verbose
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200165
166 @staticmethod
Michael Moss42d02c22018-02-05 10:32:24 -0800167 def from_dict(d, variables=None, verbose=False, conditions=None):
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200168 """Creates a Hook instance from a dict like in the DEPS file."""
Michael Moss42d02c22018-02-05 10:32:24 -0800169 # Merge any local and inherited conditions.
Edward Lemur16f4bad2018-05-16 16:53:49 -0400170 gclient_eval.UpdateCondition(d, 'and', conditions)
Paweł Hajdan, Jr032d5452017-06-22 20:43:53 +0200171 return Hook(
172 d['action'],
173 d.get('pattern'),
174 d.get('name'),
175 d.get('cwd'),
Edward Lemur16f4bad2018-05-16 16:53:49 -0400176 d.get('condition'),
Daniel Chenga0c5f082017-10-19 13:35:19 -0700177 variables=variables,
178 # Always print the header if not printing to a TTY.
179 verbose=verbose or not setup_color.IS_TTY)
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200180
181 @property
182 def action(self):
183 return self._action
184
185 @property
186 def pattern(self):
187 return self._pattern
188
189 @property
190 def name(self):
191 return self._name
192
Paweł Hajdan, Jrecf53fe2017-09-29 18:28:49 +0200193 @property
194 def condition(self):
195 return self._condition
196
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200197 def matches(self, file_list):
198 """Returns true if the pattern matches any of files in the list."""
199 if not self._pattern:
200 return True
201 pattern = re.compile(self._pattern)
202 return bool([f for f in file_list if pattern.search(f)])
203
204 def run(self, root):
Paweł Hajdan, Jr032d5452017-06-22 20:43:53 +0200205 """Executes the hook's command (provided the condition is met)."""
206 if (self._condition and
207 not gclient_eval.EvaluateCondition(self._condition, self._variables)):
208 return
209
Edward Lemure05f18d2018-06-08 17:36:53 +0000210 cmd = [arg for arg in self._action]
Paweł Hajdan, Jrfc6196b2017-07-27 13:15:25 +0200211
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200212 if cmd[0] == 'python':
213 # If the hook specified "python" as the first item, the action is a
214 # Python script. Run it by starting a new copy of the same
215 # interpreter.
216 cmd[0] = sys.executable
Nodir Turakulov0ffcc872017-11-09 16:44:58 -0800217 elif cmd[0] == 'vpython' and _detect_host_os() == 'win':
218 cmd[0] += '.bat'
Paweł Hajdan, Jrc9364392017-06-14 17:11:56 +0200219
220 cwd = root
221 if self._cwd:
222 cwd = os.path.join(cwd, self._cwd)
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200223 try:
224 start_time = time.time()
225 gclient_utils.CheckCallAndFilterAndHeader(
Daniel Chenga0c5f082017-10-19 13:35:19 -0700226 cmd, cwd=cwd, always=self._verbose)
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200227 except (gclient_utils.Error, subprocess2.CalledProcessError) as e:
228 # Use a discrete exit status code of 2 to indicate that a hook action
229 # failed. Users of this script may wish to treat hook action failures
230 # differently from VC failures.
231 print('Error: %s' % str(e), file=sys.stderr)
232 sys.exit(2)
233 finally:
234 elapsed_time = time.time() - start_time
235 if elapsed_time > 10:
236 print("Hook '%s' took %.2f secs" % (
237 gclient_utils.CommandToStr(cmd), elapsed_time))
238
239
Paweł Hajdan, Jrfc6196b2017-07-27 13:15:25 +0200240class DependencySettings(object):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000241 """Immutable configuration settings."""
242 def __init__(
Edward Lemure05f18d2018-06-08 17:36:53 +0000243 self, parent, url, managed, custom_deps, custom_vars,
Edward Lemur0c911472018-06-14 19:39:52 +0000244 custom_hooks, deps_file, relative, condition):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000245 # These are not mutable:
246 self._parent = parent
mmoss@chromium.org8f93f792014-08-26 23:24:09 +0000247 self._deps_file = deps_file
maruel@chromium.org064186c2011-09-27 23:53:33 +0000248 self._url = url
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +0200249 # The condition as string (or None). Useful to keep e.g. for flatten.
250 self._condition = condition
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000251 # 'managed' determines whether or not this dependency is synced/updated by
Edward Lemur0c911472018-06-14 19:39:52 +0000252 # gclient after gclient checks it out initially. The user specifies
253 # 'managed' via the --unmanaged command-line flag or a .gclient config.
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000254 self._managed = managed
agabledce6ddc2016-09-08 10:02:16 -0700255 # If this is a recursed-upon sub-dependency, and the parent has
256 # use_relative_paths set, then this dependency should check out its own
257 # dependencies relative to that parent's path for this, rather than
258 # relative to the .gclient file.
259 self._relative = relative
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +0000260 # This is a mutable value which has the list of 'target_os' OSes listed in
261 # the current deps file.
262 self.local_target_os = None
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000263
264 # These are only set in .gclient and not in DEPS files.
265 self._custom_vars = custom_vars or {}
266 self._custom_deps = custom_deps or {}
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000267 self._custom_hooks = custom_hooks or []
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000268
Edward Lemur0c911472018-06-14 19:39:52 +0000269 if self.url is not None:
Michael Moss4e9b50a2018-05-23 22:35:06 -0700270 # urls are sometime incorrectly written as proto://host/path/@rev. Replace
271 # it to proto://host/path@rev.
272 self.set_url(self.url.replace('/@', '@'))
Edward Lemure7273d22018-05-10 19:13:51 -0400273
mmoss@chromium.orgd0b272b2013-01-30 23:55:33 +0000274 # Make any deps_file path platform-appropriate.
John Budorick0f7b2002018-01-19 15:46:17 -0800275 if self._deps_file:
276 for sep in ['/', '\\']:
277 self._deps_file = self._deps_file.replace(sep, os.sep)
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000278
279 @property
280 def deps_file(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000281 return self._deps_file
282
283 @property
284 def managed(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000285 return self._managed
286
287 @property
288 def parent(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000289 return self._parent
290
291 @property
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000292 def root(self):
293 """Returns the root node, a GClient object."""
294 if not self.parent:
295 # This line is to signal pylint that it could be a GClient instance.
296 return self or GClient(None, None)
297 return self.parent.root
298
299 @property
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000300 def custom_vars(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000301 return self._custom_vars.copy()
302
303 @property
304 def custom_deps(self):
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000305 return self._custom_deps.copy()
306
maruel@chromium.org064186c2011-09-27 23:53:33 +0000307 @property
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000308 def custom_hooks(self):
309 return self._custom_hooks[:]
310
311 @property
maruel@chromium.org064186c2011-09-27 23:53:33 +0000312 def url(self):
Paweł Hajdan, Jrfc6196b2017-07-27 13:15:25 +0200313 """URL after variable expansion."""
maruel@chromium.org064186c2011-09-27 23:53:33 +0000314 return self._url
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000315
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000316 @property
Paweł Hajdan, Jrf69860b2017-06-05 20:24:28 +0200317 def condition(self):
318 return self._condition
319
320 @property
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +0000321 def target_os(self):
322 if self.local_target_os is not None:
323 return tuple(set(self.local_target_os).union(self.parent.target_os))
324 else:
325 return self.parent.target_os
326
Tom Andersonc31ae0b2018-02-06 14:48:56 -0800327 @property
328 def target_cpu(self):
329 return self.parent.target_cpu
330
Edward Lemure7273d22018-05-10 19:13:51 -0400331 def set_url(self, url):
332 self._url = url
333
maruel@chromium.org8c0d9582011-10-03 21:36:01 +0000334 def get_custom_deps(self, name, url):
335 """Returns a custom deps if applicable."""
336 if self.parent:
337 url = self.parent.get_custom_deps(name, url)
338 # None is a valid return value to disable a dependency.
339 return self.custom_deps.get(name, url)
340
maruel@chromium.org064186c2011-09-27 23:53:33 +0000341
342class Dependency(gclient_utils.WorkItem, DependencySettings):
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000343 """Object that represents a dependency checkout."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000344
Edward Lemure05f18d2018-06-08 17:36:53 +0000345 def __init__(self, parent, name, url, managed, custom_deps,
Edward Lemur0c911472018-06-14 19:39:52 +0000346 custom_vars, custom_hooks, deps_file, should_recurse, relative,
347 condition, print_outbuf=False):
maruel@chromium.org6ca8bf82011-09-19 23:04:30 +0000348 gclient_utils.WorkItem.__init__(self, name)
maruel@chromium.org8ac2b272011-09-26 18:49:49 +0000349 DependencySettings.__init__(
Edward Lemur0c911472018-06-14 19:39:52 +0000350 self,
351 parent=parent,
352 url=url,
353 managed=managed,
354 custom_deps=custom_deps,
355 custom_vars=custom_vars,
356 custom_hooks=custom_hooks,
357 deps_file=deps_file,
358 relative=relative,
359 condition=condition)
maruel@chromium.org68988972011-09-20 14:11:42 +0000360
361 # This is in both .gclient and DEPS files:
maruel@chromium.org064186c2011-09-27 23:53:33 +0000362 self._deps_hooks = []
maruel@chromium.org68988972011-09-20 14:11:42 +0000363
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000364 self._pre_deps_hooks = []
365
maruel@chromium.org68988972011-09-20 14:11:42 +0000366 # Calculates properties:
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +0000367 self._dependencies = []
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +0200368 self._vars = {}
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200369
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000370 # A cache of the files affected by the current operation, necessary for
371 # hooks.
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000372 self._file_list = []
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000373 # List of host names from which dependencies are allowed.
374 # Default is an empty set, meaning unspecified in DEPS file, and hence all
375 # hosts will be allowed. Non-empty set means whitelist of hosts.
376 # allowed_hosts var is scoped to its DEPS file, and so it isn't recursive.
377 self._allowed_hosts = frozenset()
Michael Moss848c86e2018-05-03 16:05:50 -0700378 self._gn_args_from = None
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +0200379 # Spec for .gni output to write (if any).
380 self._gn_args_file = None
381 self._gn_args = []
maruel@chromium.org85c2a192010-07-22 21:14:43 +0000382 # If it is not set to True, the dependency wasn't processed for its child
383 # dependency, i.e. its DEPS wasn't read.
maruel@chromium.org064186c2011-09-27 23:53:33 +0000384 self._deps_parsed = False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000385 # This dependency has been processed, i.e. checked out
maruel@chromium.org064186c2011-09-27 23:53:33 +0000386 self._processed = False
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000387 # This dependency had its pre-DEPS hooks run
388 self._pre_deps_hooks_ran = False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000389 # This dependency had its hook run
maruel@chromium.org064186c2011-09-27 23:53:33 +0000390 self._hooks_ran = False
kustermann@google.coma692e8f2013-04-18 08:32:04 +0000391 # This is the scm used to checkout self.url. It may be used by dependencies
392 # to get the datetime of the revision we checked out.
393 self._used_scm = None
szager@chromium.org4ad264b2014-05-20 04:43:47 +0000394 self._used_revision = None
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +0000395 # The actual revision we ended up getting, or None if that information is
396 # unavailable
397 self._got_revision = None
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000398
cmp@chromium.orgc401ad12014-07-02 23:20:08 +0000399 # recursedeps is a mutable value that selectively overrides the default
Edward Lemurfbb06aa2018-06-11 20:43:06 +0000400 # 'no recursion' setting on a dep-by-dep basis.
iannucci@chromium.orgafa11ac2016-05-04 22:17:34 +0000401 #
Edward Lemurfbb06aa2018-06-11 20:43:06 +0000402 # It will be a dictionary of {deps_name: depfile_namee}
403 self.recursedeps = {}
404
405 # Whether we should process this dependency's DEPS file.
406 self._should_recurse = should_recurse
Edward Lemure7273d22018-05-10 19:13:51 -0400407
Edward Lemur0c911472018-06-14 19:39:52 +0000408 if self.url:
409 # This is inherited from WorkItem. We want the URL to be a resource.
Michael Moss4e9b50a2018-05-23 22:35:06 -0700410 # The url is usually given to gclient either as https://blah@123
411 # or just https://blah. The @123 portion is irrelevant.
412 self.resources.append(self.url.split('@')[0])
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000413
Edward Lemur231f5ea2018-01-31 19:02:36 +0100414 # Controls whether we want to print git's output when we first clone the
415 # dependency
416 self.print_outbuf = print_outbuf
417
Edward Lemure7273d22018-05-10 19:13:51 -0400418 def PinToActualRevision(self):
Edward Lemure05f18d2018-06-08 17:36:53 +0000419 """Updates self.url to the revision checked out on disk."""
Edward Lemure05f18d2018-06-08 17:36:53 +0000420 url = None
Edward Lemurbabd0982018-05-11 13:32:37 -0400421 scm = self.CreateSCM()
Edward Lemure7273d22018-05-10 19:13:51 -0400422 if os.path.isdir(scm.checkout_path):
423 revision = scm.revinfo(None, None, None)
424 url = '%s@%s' % (gclient_utils.SplitUrlRevision(self.url)[0], revision)
Edward Lemure7273d22018-05-10 19:13:51 -0400425 self.set_url(url)
Edward Lemure7273d22018-05-10 19:13:51 -0400426
John Budorick0f7b2002018-01-19 15:46:17 -0800427 def ToLines(self):
428 s = []
429 condition_part = ([' "condition": %r,' % self.condition]
430 if self.condition else [])
431 s.extend([
Michael Moss4e9b50a2018-05-23 22:35:06 -0700432 ' # %s' % self.hierarchy(include_url=False),
John Budorick0f7b2002018-01-19 15:46:17 -0800433 ' "%s": {' % (self.name,),
Edward Lemure05f18d2018-06-08 17:36:53 +0000434 ' "url": "%s",' % (self.url,),
John Budorick0f7b2002018-01-19 15:46:17 -0800435 ] + condition_part + [
436 ' },',
437 '',
438 ])
439 return s
440
maruel@chromium.org470b5432011-10-11 18:18:19 +0000441 @property
442 def requirements(self):
443 """Calculate the list of requirements."""
444 requirements = set()
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000445 # self.parent is implicitly a requirement. This will be recursive by
446 # definition.
447 if self.parent and self.parent.name:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000448 requirements.add(self.parent.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000449
450 # For a tree with at least 2 levels*, the leaf node needs to depend
451 # on the level higher up in an orderly way.
452 # This becomes messy for >2 depth as the DEPS file format is a dictionary,
453 # thus unsorted, while the .gclient format is a list thus sorted.
454 #
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000455 # Interestingly enough, the following condition only works in the case we
456 # want: self is a 2nd level node. 3nd level node wouldn't need this since
457 # they already have their parent as a requirement.
maruel@chromium.org470b5432011-10-11 18:18:19 +0000458 if self.parent and self.parent.parent and not self.parent.parent.parent:
459 requirements |= set(i.name for i in self.root.dependencies if i.name)
maruel@chromium.org118fb1c2011-09-01 20:04:24 +0000460
maruel@chromium.org470b5432011-10-11 18:18:19 +0000461 if self.name:
462 requirements |= set(
Edward Lemur0c911472018-06-14 19:39:52 +0000463 obj.name for obj in self.root.subtree()
maruel@chromium.org470b5432011-10-11 18:18:19 +0000464 if (obj is not self
465 and obj.name and
466 self.name.startswith(posixpath.join(obj.name, ''))))
467 requirements = tuple(sorted(requirements))
468 logging.info('Dependency(%s).requirements = %s' % (self.name, requirements))
469 return requirements
470
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000471 @property
Edward Lemurfbb06aa2018-06-11 20:43:06 +0000472 def should_recurse(self):
473 return self._should_recurse
cmp@chromium.orge84ac912014-06-30 23:14:35 +0000474
maruel@chromium.org470b5432011-10-11 18:18:19 +0000475 def verify_validity(self):
476 """Verifies that this Dependency is fine to add as a child of another one.
477
478 Returns True if this entry should be added, False if it is a duplicate of
479 another entry.
480 """
481 logging.info('Dependency(%s).verify_validity()' % self.name)
482 if self.name in [s.name for s in self.parent.dependencies]:
483 raise gclient_utils.Error(
484 'The same name "%s" appears multiple times in the deps section' %
485 self.name)
maruel@chromium.org470b5432011-10-11 18:18:19 +0000486
487 # This require a full tree traversal with locks.
Edward Lemur0c911472018-06-14 19:39:52 +0000488 siblings = [d for d in self.root.subtree() if d.name == self.name]
maruel@chromium.org470b5432011-10-11 18:18:19 +0000489 for sibling in siblings:
Edward Lemur0c911472018-06-14 19:39:52 +0000490 if self.url != sibling.url:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000491 raise gclient_utils.Error(
maruel@chromium.orgb848d5b2012-10-10 23:25:50 +0000492 ('Dependency %s specified more than once:\n'
493 ' %s [%s]\n'
494 'vs\n'
495 ' %s [%s]') % (
496 self.name,
497 sibling.hierarchy(),
Edward Lemure7273d22018-05-10 19:13:51 -0400498 sibling.url,
maruel@chromium.orgb848d5b2012-10-10 23:25:50 +0000499 self.hierarchy(),
Edward Lemure7273d22018-05-10 19:13:51 -0400500 self.url))
maruel@chromium.org470b5432011-10-11 18:18:19 +0000501 # In theory we could keep it as a shadow of the other one. In
502 # practice, simply ignore it.
503 logging.warn('Won\'t process duplicate dependency %s' % sibling)
504 return False
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000505 return True
maruel@chromium.org064186c2011-09-27 23:53:33 +0000506
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200507 def _postprocess_deps(self, deps, rel_prefix):
508 """Performs post-processing of deps compared to what's in the DEPS file."""
Paweł Hajdan, Jr4426eaf2017-06-13 12:25:47 +0200509 # Make sure the dict is mutable, e.g. in case it's frozen.
510 deps = dict(deps)
511
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200512 # If a line is in custom_deps, but not in the solution, we want to append
513 # this line to the solution.
Edward Lemur23a35872018-05-17 01:57:06 -0400514 for dep_name, dep_info in self.custom_deps.iteritems():
515 if dep_name not in deps:
516 deps[dep_name] = {'url': dep_info, 'dep_type': 'git'}
Edward Lemur16f4bad2018-05-16 16:53:49 -0400517
Michael Moss42d02c22018-02-05 10:32:24 -0800518 # Make child deps conditional on any parent conditions. This ensures that,
519 # when flattened, recursed entries have the correct restrictions, even if
520 # not explicitly set in the recursed DEPS file. For instance, if
521 # "src/ios_foo" is conditional on "checkout_ios=True", then anything
522 # recursively included by "src/ios_foo/DEPS" should also require
523 # "checkout_ios=True".
524 if self.condition:
Edward Lemur16f4bad2018-05-16 16:53:49 -0400525 for value in deps.itervalues():
526 gclient_eval.UpdateCondition(value, 'and', self.condition)
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200527
528 if rel_prefix:
529 logging.warning('use_relative_paths enabled.')
530 rel_deps = {}
531 for d, url in deps.items():
532 # normpath is required to allow DEPS to use .. in their
533 # dependency local path.
534 rel_deps[os.path.normpath(os.path.join(rel_prefix, d))] = url
535 logging.warning('Updating deps by prepending %s.', rel_prefix)
536 deps = rel_deps
537
538 return deps
539
Edward Lemur0c911472018-06-14 19:39:52 +0000540 def FormatUrl(self, name, url):
541 custom_deps_url = self.get_custom_deps(name, url)
542 if url != custom_deps_url:
543 return custom_deps_url
544 if url is None:
545 return None
546 if not isinstance(url, basestring):
547 raise gclient_utils.Error(
548 ('dependency url must be either string or None, '
549 'instead of %s') % self.url.__class__.__name__)
550 # For relative URLs, strip the parent url (self.url) from the last '/'
551 # and append the relative url.
552 if url[0] == '/':
553 if self.url is None:
554 raise gclient_utils.Error(
555 'Trying to set a relative url for %s, but parent has no url.' % name
556 )
557 url = self.url[:self.url.rfind('/')] + url
558 return url
559
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200560 def _deps_to_objects(self, deps, use_relative_paths):
561 """Convert a deps dict to a dict of Dependency objects."""
562 deps_to_add = []
563 for name, dep_value in deps.iteritems():
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200564 if dep_value is None:
565 continue
John Budorick0f7b2002018-01-19 15:46:17 -0800566
Edward Lemur16f4bad2018-05-16 16:53:49 -0400567 condition = dep_value.get('condition')
Edward Lemur0c911472018-06-14 19:39:52 +0000568 dep_type = dep_value.get('dep_type', 'git')
Paweł Hajdan, Jrfc6196b2017-07-27 13:15:25 +0200569
Edward Lemur0c911472018-06-14 19:39:52 +0000570 should_process = True
571 if condition and not self.get_option('process_all_deps', False):
572 should_process = gclient_eval.EvaluateCondition(
Paweł Hajdan, Jrd3790252017-07-03 21:06:24 +0200573 condition, self.get_vars())
John Budorick0f7b2002018-01-19 15:46:17 -0800574
Edward Lemur0c911472018-06-14 19:39:52 +0000575 if not should_process:
576 continue
577
John Budorick0f7b2002018-01-19 15:46:17 -0800578 if dep_type == 'cipd':
John Budorickd3ba72b2018-03-20 12:27:42 -0700579 cipd_root = self.GetCipdRoot()
John Budorick0f7b2002018-01-19 15:46:17 -0800580 for package in dep_value.get('packages', []):
581 deps_to_add.append(
582 CipdDependency(
Edward Lemure05f18d2018-06-08 17:36:53 +0000583 parent=self,
584 name=name,
585 dep_value=package,
586 cipd_root=cipd_root,
587 custom_vars=self.custom_vars,
Edward Lemure05f18d2018-06-08 17:36:53 +0000588 relative=use_relative_paths,
589 condition=condition))
John Budorick0f7b2002018-01-19 15:46:17 -0800590 else:
Edward Lemur0c911472018-06-14 19:39:52 +0000591 url = self.FormatUrl(name, dep_value.get('url'))
592 if url:
593 deps_to_add.append(
594 GitDependency(
595 parent=self,
596 name=name,
597 url=url,
598 managed=None,
599 custom_deps=None,
600 custom_vars=self.custom_vars,
601 custom_hooks=None,
602 deps_file=self.recursedeps.get(name, self.deps_file),
603 should_recurse=name in self.recursedeps,
604 relative=use_relative_paths,
605 condition=condition))
John Budorick0f7b2002018-01-19 15:46:17 -0800606
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200607 deps_to_add.sort(key=lambda x: x.name)
608 return deps_to_add
609
Edward Lemure05f18d2018-06-08 17:36:53 +0000610 def ParseDepsFile(self):
maruel@chromium.org271375b2010-06-23 19:17:38 +0000611 """Parses the DEPS file for this dependency."""
maruel@chromium.org3223edd2011-10-10 23:17:39 +0000612 assert not self.deps_parsed
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000613 assert not self.dependencies
iannucci@chromium.org0c3f3052014-05-22 00:29:22 +0000614
615 deps_content = None
cmp@chromium.org76ce73c2014-07-02 00:13:18 +0000616
617 # First try to locate the configured deps file. If it's missing, fallback
618 # to DEPS.
619 deps_files = [self.deps_file]
620 if 'DEPS' not in deps_files:
621 deps_files.append('DEPS')
622 for deps_file in deps_files:
623 filepath = os.path.join(self.root.root_dir, self.name, deps_file)
624 if os.path.isfile(filepath):
625 logging.info(
626 'ParseDepsFile(%s): %s file found at %s', self.name, deps_file,
627 filepath)
628 break
maruel@chromium.org1333cb32011-10-04 23:40:16 +0000629 logging.info(
cmp@chromium.org76ce73c2014-07-02 00:13:18 +0000630 'ParseDepsFile(%s): No %s file found at %s', self.name, deps_file,
631 filepath)
632
633 if os.path.isfile(filepath):
maruel@chromium.org46304292010-10-28 11:42:00 +0000634 deps_content = gclient_utils.FileRead(filepath)
cmp@chromium.org76ce73c2014-07-02 00:13:18 +0000635 logging.debug('ParseDepsFile(%s) read:\n%s', self.name, deps_content)
iannucci@chromium.org0c3f3052014-05-22 00:29:22 +0000636
637 local_scope = {}
638 if deps_content:
maruel@chromium.org46304292010-10-28 11:42:00 +0000639 try:
Edward Lesmes6c24d372018-03-28 12:52:29 -0400640 local_scope = gclient_eval.Parse(
Edward Lemur0c911472018-06-14 19:39:52 +0000641 deps_content, self.get_option('validate_syntax', False),
Michael Mossda55cdc2018-04-06 18:37:19 -0700642 filepath, self.get_vars())
vapier@chromium.orga81a56e2015-11-11 07:56:13 +0000643 except SyntaxError as e:
maruel@chromium.org46304292010-10-28 11:42:00 +0000644 gclient_utils.SyntaxErrorToError(filepath, e)
iannucci@chromium.org0c3f3052014-05-22 00:29:22 +0000645
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000646 if 'allowed_hosts' in local_scope:
647 try:
648 self._allowed_hosts = frozenset(local_scope.get('allowed_hosts'))
649 except TypeError: # raised if non-iterable
650 pass
651 if not self._allowed_hosts:
652 logging.warning("allowed_hosts is specified but empty %s",
653 self._allowed_hosts)
654 raise gclient_utils.Error(
655 'ParseDepsFile(%s): allowed_hosts must be absent '
656 'or a non-empty iterable' % self.name)
657
Michael Moss848c86e2018-05-03 16:05:50 -0700658 self._gn_args_from = local_scope.get('gclient_gn_args_from')
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +0200659 self._gn_args_file = local_scope.get('gclient_gn_args_file')
660 self._gn_args = local_scope.get('gclient_gn_args', [])
Michael Moss848c86e2018-05-03 16:05:50 -0700661 # It doesn't make sense to set all of these, since setting gn_args_from to
662 # another DEPS will make gclient ignore any other local gn_args* settings.
663 assert not (self._gn_args_from and self._gn_args_file), \
664 'Only specify one of "gclient_gn_args_from" or ' \
665 '"gclient_gn_args_file + gclient_gn_args".'
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +0200666
Edward Lesmes0b899352018-03-19 21:59:55 +0000667 self._vars = local_scope.get('vars', {})
Paweł Hajdan, Jr1407d002017-08-01 20:01:01 +0200668 if self.parent:
669 for key, value in self.parent.get_vars().iteritems():
670 if key in self._vars:
671 self._vars[key] = value
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200672 # Since we heavily post-process things, freeze ones which should
673 # reflect original state of DEPS.
Paweł Hajdan, Jr1407d002017-08-01 20:01:01 +0200674 self._vars = gclient_utils.freeze(self._vars)
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200675
676 # If use_relative_paths is set in the DEPS file, regenerate
677 # the dictionary using paths relative to the directory containing
678 # the DEPS file. Also update recursedeps if use_relative_paths is
679 # enabled.
680 # If the deps file doesn't set use_relative_paths, but the parent did
681 # (and therefore set self.relative on this Dependency object), then we
682 # want to modify the deps and recursedeps by prepending the parent
683 # directory of this dependency.
684 use_relative_paths = local_scope.get('use_relative_paths', False)
685 rel_prefix = None
686 if use_relative_paths:
687 rel_prefix = self.name
688 elif self._relative:
689 rel_prefix = os.path.dirname(self.name)
690
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200691 if 'recursion' in local_scope:
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200692 logging.warning(
Edward Lemurfbb06aa2018-06-11 20:43:06 +0000693 '%s: Ignoring recursion = %d.', self.name, local_scope['recursion'])
694
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200695 if 'recursedeps' in local_scope:
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200696 for ent in local_scope['recursedeps']:
697 if isinstance(ent, basestring):
Edward Lemurfbb06aa2018-06-11 20:43:06 +0000698 self.recursedeps[ent] = self.deps_file
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200699 else: # (depname, depsfilename)
Edward Lemurfbb06aa2018-06-11 20:43:06 +0000700 self.recursedeps[ent[0]] = ent[1]
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200701 logging.warning('Found recursedeps %r.', repr(self.recursedeps))
702
703 if rel_prefix:
704 logging.warning('Updating recursedeps by prepending %s.', rel_prefix)
705 rel_deps = {}
706 for depname, options in self.recursedeps.iteritems():
707 rel_deps[
708 os.path.normpath(os.path.join(rel_prefix, depname))] = options
709 self.recursedeps = rel_deps
Michael Moss848c86e2018-05-03 16:05:50 -0700710 # To get gn_args from another DEPS, that DEPS must be recursed into.
711 if self._gn_args_from:
712 assert self.recursedeps and self._gn_args_from in self.recursedeps, \
713 'The "gclient_gn_args_from" value must be in recursedeps.'
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200714
715 # If present, save 'target_os' in the local_target_os property.
716 if 'target_os' in local_scope:
717 self.local_target_os = local_scope['target_os']
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200718
Edward Lemur16f4bad2018-05-16 16:53:49 -0400719 deps = local_scope.get('deps', {})
Paweł Hajdan, Jrcd788e32017-06-12 18:42:22 +0200720 deps_to_add = self._deps_to_objects(
721 self._postprocess_deps(deps, rel_prefix), use_relative_paths)
petermayo@chromium.orge79161a2013-07-09 14:40:37 +0000722
723 # override named sets of hooks by the custom hooks
724 hooks_to_run = []
725 hook_names_to_suppress = [c.get('name', '') for c in self.custom_hooks]
726 for hook in local_scope.get('hooks', []):
727 if hook.get('name', '') not in hook_names_to_suppress:
728 hooks_to_run.append(hook)
729
730 # add the replacements and any additions
731 for hook in self.custom_hooks:
732 if 'action' in hook:
733 hooks_to_run.append(hook)
734
Edward Lemurfbb06aa2018-06-11 20:43:06 +0000735 if self.should_recurse:
Paweł Hajdan, Jr032d5452017-06-22 20:43:53 +0200736 self._pre_deps_hooks = [
Michael Moss42d02c22018-02-05 10:32:24 -0800737 Hook.from_dict(hook, variables=self.get_vars(), verbose=True,
738 conditions=self.condition)
Daniel Chenga0c5f082017-10-19 13:35:19 -0700739 for hook in local_scope.get('pre_deps_hooks', [])
740 ]
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000741
Paweł Hajdan, Jr357415c2017-07-24 14:35:28 +0200742 self.add_dependencies_and_close(deps_to_add, hooks_to_run)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000743 logging.info('ParseDepsFile(%s) done' % self.name)
744
Edward Lemur0c911472018-06-14 19:39:52 +0000745 def get_option(self, attr, default=None):
746 return getattr(self.root._options, attr, default)
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +0200747
Paweł Hajdan, Jr357415c2017-07-24 14:35:28 +0200748 def add_dependencies_and_close(self, deps_to_add, hooks):
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000749 """Adds the dependencies, hooks and mark the parsing as done."""
maruel@chromium.orgb9be0652011-10-14 18:05:40 +0000750 for dep in deps_to_add:
maruel@chromium.org470b5432011-10-11 18:18:19 +0000751 if dep.verify_validity():
maruel@chromium.org0bcfd182011-10-10 20:06:09 +0000752 self.add_dependency(dep)
Daniel Chenga0c5f082017-10-19 13:35:19 -0700753 self._mark_as_parsed([
754 Hook.from_dict(
Michael Moss42d02c22018-02-05 10:32:24 -0800755 h, variables=self.get_vars(), verbose=self.root._options.verbose,
756 conditions=self.condition)
Daniel Chenga0c5f082017-10-19 13:35:19 -0700757 for h in hooks
758 ])
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000759
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000760 def findDepsFromNotAllowedHosts(self):
761 """Returns a list of depenecies from not allowed hosts.
762
763 If allowed_hosts is not set, allows all hosts and returns empty list.
764 """
765 if not self._allowed_hosts:
766 return []
767 bad_deps = []
768 for dep in self._dependencies:
szager@chromium.orgbd772dd2014-11-05 18:43:08 +0000769 # Don't enforce this for custom_deps.
770 if dep.name in self._custom_deps:
771 continue
Edward Lemur0c911472018-06-14 19:39:52 +0000772 parsed_url = urlparse.urlparse(dep.url)
773 if parsed_url.netloc and parsed_url.netloc not in self._allowed_hosts:
774 bad_deps.append(dep)
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +0000775 return bad_deps
776
Edward Lemure7273d22018-05-10 19:13:51 -0400777 def FuzzyMatchUrl(self, candidates):
Edward Lesmesbb16e332018-03-30 17:54:51 -0400778 """Attempts to find this dependency in the list of candidates.
779
Edward Lemure7273d22018-05-10 19:13:51 -0400780 It looks first for the URL of this dependency in the list of
Edward Lesmesbb16e332018-03-30 17:54:51 -0400781 candidates. If it doesn't succeed, and the URL ends in '.git', it will try
782 looking for the URL minus '.git'. Finally it will try to look for the name
783 of the dependency.
784
785 Args:
Edward Lesmesbb16e332018-03-30 17:54:51 -0400786 candidates: list, dict. The list of candidates in which to look for this
787 dependency. It can contain URLs as above, or dependency names like
788 "src/some/dep".
789
790 Returns:
791 If this dependency is not found in the list of candidates, returns None.
792 Otherwise, it returns under which name did we find this dependency:
793 - Its parsed url: "https://example.com/src.git'
794 - Its parsed url minus '.git': "https://example.com/src"
795 - Its name: "src"
796 """
Edward Lemure7273d22018-05-10 19:13:51 -0400797 if self.url:
798 origin, _ = gclient_utils.SplitUrlRevision(self.url)
Edward Lesmesbb16e332018-03-30 17:54:51 -0400799 if origin in candidates:
800 return origin
801 if origin.endswith('.git') and origin[:-len('.git')] in candidates:
802 return origin[:-len('.git')]
Edward Lesmes990148e2018-04-26 14:56:55 -0400803 if origin + '.git' in candidates:
804 return origin + '.git'
Edward Lesmesbb16e332018-03-30 17:54:51 -0400805 if self.name in candidates:
806 return self.name
807 return None
808
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +0000809 # Arguments number differs from overridden method
Quinten Yearsleyb2cc4a92016-12-15 13:53:26 -0800810 # pylint: disable=arguments-differ
Edward Lesmesc621b212018-03-21 20:26:56 -0400811 def run(self, revision_overrides, command, args, work_queue, options,
812 patch_refs):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000813 """Runs |command| then parse the DEPS file."""
maruel@chromium.org470b5432011-10-11 18:18:19 +0000814 logging.info('Dependency(%s).run()' % self.name)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000815 assert self._file_list == []
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000816 # When running runhooks, there's no need to consult the SCM.
817 # All known hooks are expected to run unconditionally regardless of working
818 # copy state, so skip the SCM status check.
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +0200819 run_scm = command not in (
820 'flatten', 'runhooks', 'recurse', 'validate', None)
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000821 file_list = [] if not options.nohooks else None
Edward Lesmesbb16e332018-03-30 17:54:51 -0400822 revision_override = revision_overrides.pop(
Edward Lemure7273d22018-05-10 19:13:51 -0400823 self.FuzzyMatchUrl(revision_overrides), None)
Edward Lemur0c911472018-06-14 19:39:52 +0000824 if run_scm:
agabled437d762016-10-17 09:35:11 -0700825 # Create a shallow copy to mutate revision.
826 options = copy.copy(options)
827 options.revision = revision_override
828 self._used_revision = options.revision
Edward Lemurbabd0982018-05-11 13:32:37 -0400829 self._used_scm = self.CreateSCM(out_cb=work_queue.out_cb)
agabled437d762016-10-17 09:35:11 -0700830 self._got_revision = self._used_scm.RunCommand(command, options, args,
831 file_list)
Edward Lesmesc621b212018-03-21 20:26:56 -0400832
Edward Lemure7273d22018-05-10 19:13:51 -0400833 patch_repo = self.url.split('@')[0]
834 patch_ref = patch_refs.pop(self.FuzzyMatchUrl(patch_refs), None)
Edward Lesmesc621b212018-03-21 20:26:56 -0400835 if command == 'update' and patch_ref is not None:
836 self._used_scm.apply_patch_ref(patch_repo, patch_ref, options,
Edward Lesmesbb16e332018-03-30 17:54:51 -0400837 file_list)
Edward Lesmesc621b212018-03-21 20:26:56 -0400838
agabled437d762016-10-17 09:35:11 -0700839 if file_list:
840 file_list = [os.path.join(self.name, f.strip()) for f in file_list]
maruel@chromium.org68988972011-09-20 14:11:42 +0000841
842 # TODO(phajdan.jr): We should know exactly when the paths are absolute.
843 # Convert all absolute paths to relative.
iannucci@chromium.org396e1a62013-07-03 19:41:04 +0000844 for i in range(len(file_list or [])):
maruel@chromium.org68988972011-09-20 14:11:42 +0000845 # It depends on the command being executed (like runhooks vs sync).
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000846 if not os.path.isabs(file_list[i]):
maruel@chromium.org68988972011-09-20 14:11:42 +0000847 continue
848 prefix = os.path.commonprefix(
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000849 [self.root.root_dir.lower(), file_list[i].lower()])
850 file_list[i] = file_list[i][len(prefix):]
maruel@chromium.org68988972011-09-20 14:11:42 +0000851 # Strip any leading path separators.
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000852 while file_list[i].startswith(('\\', '/')):
853 file_list[i] = file_list[i][1:]
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000854
Edward Lemurfbb06aa2018-06-11 20:43:06 +0000855 if self.should_recurse:
Edward Lemure05f18d2018-06-08 17:36:53 +0000856 self.ParseDepsFile()
Edward Lesmes5d6cde32018-04-12 18:32:46 -0400857
Edward Lemure7273d22018-05-10 19:13:51 -0400858 self._run_is_done(file_list or [])
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000859
Edward Lemurfbb06aa2018-06-11 20:43:06 +0000860 if self.should_recurse:
Edward Lesmes5d6cde32018-04-12 18:32:46 -0400861 if command in ('update', 'revert') and not options.noprehooks:
862 self.RunPreDepsHooks()
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000863 # Parse the dependencies of this dependency.
864 for s in self.dependencies:
Edward Lemur0c911472018-06-14 19:39:52 +0000865 work_queue.enqueue(s)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000866
867 if command == 'recurse':
agabled437d762016-10-17 09:35:11 -0700868 # Skip file only checkout.
Edward Lemurbabd0982018-05-11 13:32:37 -0400869 scm = self.GetScmName()
agabled437d762016-10-17 09:35:11 -0700870 if not options.scm or scm in options.scm:
871 cwd = os.path.normpath(os.path.join(self.root.root_dir, self.name))
872 # Pass in the SCM type as an env variable. Make sure we don't put
873 # unicode strings in the environment.
874 env = os.environ.copy()
Edward Lemur0c911472018-06-14 19:39:52 +0000875 env['GCLIENT_SCM'] = str(scm)
876 env['GCLIENT_URL'] = str(self.url)
agabled437d762016-10-17 09:35:11 -0700877 env['GCLIENT_DEP_PATH'] = str(self.name)
878 if options.prepend_dir and scm == 'git':
879 print_stdout = False
880 def filter_fn(line):
881 """Git-specific path marshaling. It is optimized for git-grep."""
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000882
agabled437d762016-10-17 09:35:11 -0700883 def mod_path(git_pathspec):
884 match = re.match('^(\\S+?:)?([^\0]+)$', git_pathspec)
885 modified_path = os.path.join(self.name, match.group(2))
886 branch = match.group(1) or ''
887 return '%s%s' % (branch, modified_path)
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000888
agabled437d762016-10-17 09:35:11 -0700889 match = re.match('^Binary file ([^\0]+) matches$', line)
890 if match:
891 print('Binary file %s matches\n' % mod_path(match.group(1)))
892 return
ilevy@chromium.org0233ac22012-11-28 20:27:02 +0000893
agabled437d762016-10-17 09:35:11 -0700894 items = line.split('\0')
895 if len(items) == 2 and items[1]:
896 print('%s : %s' % (mod_path(items[0]), items[1]))
897 elif len(items) >= 2:
898 # Multiple null bytes or a single trailing null byte indicate
899 # git is likely displaying filenames only (such as with -l)
900 print('\n'.join(mod_path(path) for path in items if path))
901 else:
902 print(line)
903 else:
904 print_stdout = True
905 filter_fn = None
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +0000906
Edward Lemur0c911472018-06-14 19:39:52 +0000907 if os.path.isdir(cwd):
agabled437d762016-10-17 09:35:11 -0700908 try:
909 gclient_utils.CheckCallAndFilter(
910 args, cwd=cwd, env=env, print_stdout=print_stdout,
911 filter_fn=filter_fn,
912 )
913 except subprocess2.CalledProcessError:
914 if not options.ignore:
915 raise
916 else:
917 print('Skipped missing %s' % cwd, file=sys.stderr)
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000918
Edward Lemurbabd0982018-05-11 13:32:37 -0400919 def GetScmName(self):
Edward Lemurb61d3872018-05-09 18:42:47 -0400920 raise NotImplementedError()
John Budorick0f7b2002018-01-19 15:46:17 -0800921
Edward Lemurbabd0982018-05-11 13:32:37 -0400922 def CreateSCM(self, out_cb=None):
Edward Lemurb61d3872018-05-09 18:42:47 -0400923 raise NotImplementedError()
John Budorick0f7b2002018-01-19 15:46:17 -0800924
Dirk Pranke9f20d022017-10-11 18:36:54 -0700925 def HasGNArgsFile(self):
926 return self._gn_args_file is not None
927
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +0200928 def WriteGNArgsFile(self):
929 lines = ['# Generated from %r' % self.deps_file]
Paweł Hajdan, Jrb495bf52017-09-25 19:33:50 +0200930 variables = self.get_vars()
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +0200931 for arg in self._gn_args:
Paweł Hajdan, Jre0214742017-09-28 12:21:01 +0200932 value = variables[arg]
933 if isinstance(value, basestring):
934 value = gclient_eval.EvaluateCondition(value, variables)
Paweł Hajdan, Jrb495bf52017-09-25 19:33:50 +0200935 lines.append('%s = %s' % (arg, ToGNString(value)))
Paweł Hajdan, Jr57253732017-06-06 23:49:11 +0200936 with open(os.path.join(self.root.root_dir, self._gn_args_file), 'w') as f:
937 f.write('\n'.join(lines))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000938
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000939 @gclient_utils.lockedmethod
Edward Lemure7273d22018-05-10 19:13:51 -0400940 def _run_is_done(self, file_list):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000941 # Both these are kept for hooks that are run as a separate tree traversal.
942 self._file_list = file_list
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +0000943 self._processed = True
944
szager@google.comb9a78d32012-03-13 18:46:21 +0000945 def GetHooks(self, options):
946 """Evaluates all hooks, and return them in a flat list.
947
948 RunOnDeps() must have been called before to load the DEPS.
949 """
950 result = []
maruel@chromium.orgdc7445d2010-07-09 21:05:29 +0000951 # If "--force" was specified, run all hooks regardless of what files have
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000952 # changed.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000953 if self.deps_hooks:
agabled437d762016-10-17 09:35:11 -0700954 # TODO(maruel): If the user is using git, then we don't know
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000955 # what files have changed so we always run all hooks. It'd be nice to fix
956 # that.
Edward Lemurbabd0982018-05-11 13:32:37 -0400957 result.extend(self.deps_hooks)
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000958 for s in self.dependencies:
szager@google.comb9a78d32012-03-13 18:46:21 +0000959 result.extend(s.GetHooks(options))
960 return result
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000961
Daniel Chenga0c5f082017-10-19 13:35:19 -0700962 def RunHooksRecursively(self, options, progress):
szager@google.comb9a78d32012-03-13 18:46:21 +0000963 assert self.hooks_ran == False
maruel@chromium.org064186c2011-09-27 23:53:33 +0000964 self._hooks_ran = True
Daniel Chenga0c5f082017-10-19 13:35:19 -0700965 hooks = self.GetHooks(options)
966 if progress:
967 progress._total = len(hooks)
968 for hook in hooks:
Daniel Chenga0c5f082017-10-19 13:35:19 -0700969 if progress:
970 progress.update(extra=hook.name or '')
Daniel Cheng93c5d602017-10-20 11:40:17 -0700971 hook.run(self.root.root_dir)
Daniel Chenga0c5f082017-10-19 13:35:19 -0700972 if progress:
973 progress.end()
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000974
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000975 def RunPreDepsHooks(self):
976 assert self.processed
977 assert self.deps_parsed
978 assert not self.pre_deps_hooks_ran
979 assert not self.hooks_ran
980 for s in self.dependencies:
981 assert not s.processed
982 self._pre_deps_hooks_ran = True
983 for hook in self.pre_deps_hooks:
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +0200984 hook.run(self.root.root_dir)
borenet@google.com2d1ee9e2013-10-15 08:13:16 +0000985
John Budorickd3ba72b2018-03-20 12:27:42 -0700986 def GetCipdRoot(self):
987 if self.root is self:
988 # Let's not infinitely recurse. If this is root and isn't an
989 # instance of GClient, do nothing.
990 return None
991 return self.root.GetCipdRoot()
992
Edward Lemur0c911472018-06-14 19:39:52 +0000993 def subtree(self):
maruel@chromium.orgad3287e2011-10-03 19:15:10 +0000994 """Breadth first recursion excluding root node."""
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000995 dependencies = self.dependencies
996 for d in dependencies:
Edward Lemur0c911472018-06-14 19:39:52 +0000997 yield d
maruel@chromium.orgf13a4182011-09-22 00:26:15 +0000998 for d in dependencies:
Edward Lemur0c911472018-06-14 19:39:52 +0000999 for i in d.subtree():
maruel@chromium.orgad3287e2011-10-03 19:15:10 +00001000 yield i
1001
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001002 @gclient_utils.lockedmethod
1003 def add_dependency(self, new_dep):
1004 self._dependencies.append(new_dep)
1005
1006 @gclient_utils.lockedmethod
Paweł Hajdan, Jr357415c2017-07-24 14:35:28 +02001007 def _mark_as_parsed(self, new_hooks):
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001008 self._deps_hooks.extend(new_hooks)
1009 self._deps_parsed = True
1010
maruel@chromium.org68988972011-09-20 14:11:42 +00001011 @property
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001012 @gclient_utils.lockedmethod
maruel@chromium.org4bdd5fd2011-09-26 19:41:17 +00001013 def dependencies(self):
1014 return tuple(self._dependencies)
1015
1016 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001017 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001018 def deps_hooks(self):
1019 return tuple(self._deps_hooks)
1020
1021 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001022 @gclient_utils.lockedmethod
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001023 def pre_deps_hooks(self):
1024 return tuple(self._pre_deps_hooks)
1025
1026 @property
1027 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001028 def deps_parsed(self):
maruel@chromium.org3223edd2011-10-10 23:17:39 +00001029 """This is purely for debugging purposes. It's not used anywhere."""
maruel@chromium.org064186c2011-09-27 23:53:33 +00001030 return self._deps_parsed
1031
1032 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001033 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001034 def processed(self):
1035 return self._processed
1036
1037 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001038 @gclient_utils.lockedmethod
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00001039 def pre_deps_hooks_ran(self):
1040 return self._pre_deps_hooks_ran
1041
1042 @property
1043 @gclient_utils.lockedmethod
maruel@chromium.org064186c2011-09-27 23:53:33 +00001044 def hooks_ran(self):
1045 return self._hooks_ran
1046
1047 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001048 @gclient_utils.lockedmethod
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00001049 def allowed_hosts(self):
1050 return self._allowed_hosts
1051
1052 @property
1053 @gclient_utils.lockedmethod
maruel@chromium.org861fd0f2010-07-23 03:05:05 +00001054 def file_list(self):
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001055 return tuple(self._file_list)
1056
1057 @property
kustermann@google.coma692e8f2013-04-18 08:32:04 +00001058 def used_scm(self):
1059 """SCMWrapper instance for this dependency or None if not processed yet."""
1060 return self._used_scm
1061
1062 @property
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00001063 @gclient_utils.lockedmethod
1064 def got_revision(self):
1065 return self._got_revision
1066
1067 @property
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001068 def file_list_and_children(self):
1069 result = list(self.file_list)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +00001070 for d in self.dependencies:
maruel@chromium.orgbaa7be32011-10-10 20:49:47 +00001071 result.extend(d.file_list_and_children)
maruel@chromium.org68988972011-09-20 14:11:42 +00001072 return tuple(result)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +00001073
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001074 def __str__(self):
1075 out = []
Edward Lemure7273d22018-05-10 19:13:51 -04001076 for i in ('name', 'url', 'custom_deps',
Edward Lemur0c911472018-06-14 19:39:52 +00001077 'custom_vars', 'deps_hooks', 'file_list',
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00001078 'processed', 'hooks_ran', 'deps_parsed', 'requirements',
1079 'allowed_hosts'):
maruel@chromium.org3c74bc92011-09-15 19:17:21 +00001080 # First try the native property if it exists.
1081 if hasattr(self, '_' + i):
1082 value = getattr(self, '_' + i, False)
1083 else:
1084 value = getattr(self, i, False)
1085 if value:
1086 out.append('%s: %s' % (i, value))
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001087
1088 for d in self.dependencies:
1089 out.extend([' ' + x for x in str(d).splitlines()])
1090 out.append('')
1091 return '\n'.join(out)
1092
1093 def __repr__(self):
1094 return '%s: %s' % (self.name, self.url)
1095
Michael Moss4e9b50a2018-05-23 22:35:06 -07001096 def hierarchy(self, include_url=True):
maruel@chromium.orgbc2d2f92010-07-22 21:26:48 +00001097 """Returns a human-readable hierarchical reference to a Dependency."""
Michael Moss4e9b50a2018-05-23 22:35:06 -07001098 def format_name(d):
1099 if include_url:
1100 return '%s(%s)' % (d.name, d.url)
1101 return d.name
1102 out = format_name(self)
maruel@chromium.orgbffb9042010-07-22 20:59:36 +00001103 i = self.parent
1104 while i and i.name:
Michael Moss4e9b50a2018-05-23 22:35:06 -07001105 out = '%s -> %s' % (format_name(i), out)
maruel@chromium.orgbffb9042010-07-22 20:59:36 +00001106 i = i.parent
1107 return out
1108
Michael Mossfe68c912018-03-22 19:19:35 -07001109 def hierarchy_data(self):
1110 """Returns a machine-readable hierarchical reference to a Dependency."""
1111 d = self
1112 out = []
1113 while d and d.name:
1114 out.insert(0, (d.name, d.url))
1115 d = d.parent
1116 return tuple(out)
1117
Paweł Hajdan, Jrd3790252017-07-03 21:06:24 +02001118 def get_vars(self):
1119 """Returns a dictionary of effective variable values
1120 (DEPS file contents with applied custom_vars overrides)."""
Paweł Hajdan, Jra3b67ae2017-08-30 15:18:21 +02001121 # Provide some built-in variables.
1122 result = {
Paweł Hajdan, Jrd325eb32017-10-03 17:43:37 +02001123 'checkout_android': 'android' in self.target_os,
Benjamin Pastene6fe29412018-01-23 15:35:58 -08001124 'checkout_chromeos': 'chromeos' in self.target_os,
Paweł Hajdan, Jrd325eb32017-10-03 17:43:37 +02001125 'checkout_fuchsia': 'fuchsia' in self.target_os,
1126 'checkout_ios': 'ios' in self.target_os,
1127 'checkout_linux': 'unix' in self.target_os,
1128 'checkout_mac': 'mac' in self.target_os,
1129 'checkout_win': 'win' in self.target_os,
1130 'host_os': _detect_host_os(),
Tom Andersonc31ae0b2018-02-06 14:48:56 -08001131
1132 'checkout_arm': 'arm' in self.target_cpu,
1133 'checkout_arm64': 'arm64' in self.target_cpu,
1134 'checkout_x86': 'x86' in self.target_cpu,
1135 'checkout_mips': 'mips' in self.target_cpu,
1136 'checkout_ppc': 'ppc' in self.target_cpu,
1137 'checkout_s390': 's390' in self.target_cpu,
1138 'checkout_x64': 'x64' in self.target_cpu,
1139 'host_cpu': detect_host_arch.HostArch(),
Paweł Hajdan, Jra3b67ae2017-08-30 15:18:21 +02001140 }
Michael Mossda55cdc2018-04-06 18:37:19 -07001141 # Variable precedence:
1142 # - built-in
1143 # - DEPS vars
1144 # - parents, from first to last
1145 # - custom_vars overrides
Paweł Hajdan, Jra3b67ae2017-08-30 15:18:21 +02001146 result.update(self._vars)
Michael Mossda55cdc2018-04-06 18:37:19 -07001147 if self.parent:
1148 parent_vars = self.parent.get_vars()
1149 result.update(parent_vars)
Paweł Hajdan, Jrd3790252017-07-03 21:06:24 +02001150 result.update(self.custom_vars or {})
1151 return result
1152
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001153
Paweł Hajdan, Jra3b67ae2017-08-30 15:18:21 +02001154_PLATFORM_MAPPING = {
1155 'cygwin': 'win',
1156 'darwin': 'mac',
1157 'linux2': 'linux',
1158 'win32': 'win',
Jaideep Bajwad05f3582017-09-11 12:31:48 -04001159 'aix6': 'aix',
Paweł Hajdan, Jra3b67ae2017-08-30 15:18:21 +02001160}
1161
1162
1163def _detect_host_os():
1164 return _PLATFORM_MAPPING[sys.platform]
1165
1166
Edward Lemurb61d3872018-05-09 18:42:47 -04001167class GitDependency(Dependency):
1168 """A Dependency object that represents a single git checkout."""
1169
1170 #override
Edward Lemurbabd0982018-05-11 13:32:37 -04001171 def GetScmName(self):
Edward Lemurb61d3872018-05-09 18:42:47 -04001172 """Always 'git'."""
Edward Lemurb61d3872018-05-09 18:42:47 -04001173 return 'git'
1174
1175 #override
Edward Lemurbabd0982018-05-11 13:32:37 -04001176 def CreateSCM(self, out_cb=None):
Edward Lemurb61d3872018-05-09 18:42:47 -04001177 """Create a Wrapper instance suitable for handling this git dependency."""
Edward Lemurbabd0982018-05-11 13:32:37 -04001178 return gclient_scm.GitWrapper(
1179 self.url, self.root.root_dir, self.name, self.outbuf, out_cb,
1180 print_outbuf=self.print_outbuf)
Edward Lemurb61d3872018-05-09 18:42:47 -04001181
1182
1183class GClient(GitDependency):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001184 """Object that represent a gclient checkout. A tree of Dependency(), one per
1185 solution or DEPS entry."""
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001186
1187 DEPS_OS_CHOICES = {
Jaideep Bajwad05f3582017-09-11 12:31:48 -04001188 "aix6": "unix",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001189 "win32": "win",
1190 "win": "win",
1191 "cygwin": "win",
1192 "darwin": "mac",
1193 "mac": "mac",
1194 "unix": "unix",
1195 "linux": "unix",
1196 "linux2": "unix",
maruel@chromium.org244e3442011-06-12 15:20:55 +00001197 "linux3": "unix",
szager@chromium.orgf8c95cd2012-06-01 22:26:52 +00001198 "android": "android",
Michael Mossc54fa812017-08-17 11:27:58 -07001199 "ios": "ios",
Sergiy Byelozyorov518bb682018-06-03 11:25:58 +02001200 "fuchsia": "fuchsia",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001201 }
1202
1203 DEFAULT_CLIENT_FILE_TEXT = ("""\
1204solutions = [
smutae7ea312016-07-18 11:59:41 -07001205 { "name" : "%(solution_name)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001206 "url" : "%(solution_url)s",
nsylvain@google.comefc80932011-05-31 21:27:56 +00001207 "deps_file" : "%(deps_file)s",
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001208 "managed" : %(managed)s,
smutae7ea312016-07-18 11:59:41 -07001209 "custom_deps" : {
1210 },
Paweł Hajdan, Jr3ba2a7c2017-10-04 19:24:46 +02001211 "custom_vars": %(custom_vars)r,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001212 },
1213]
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001214cache_dir = %(cache_dir)r
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001215""")
1216
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001217 DEFAULT_SNAPSHOT_FILE_TEXT = ("""\
1218# Snapshot generated with gclient revinfo --snapshot
Edward Lesmesc2960242018-03-06 20:50:15 -05001219solutions = %(solution_list)s
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001220""")
1221
1222 def __init__(self, root_dir, options):
maruel@chromium.org0d812442010-08-10 12:41:08 +00001223 # Do not change previous behavior. Only solution level and immediate DEPS
1224 # are processed.
1225 self._recursion_limit = 2
Edward Lemure05f18d2018-06-08 17:36:53 +00001226 super(GClient, self).__init__(
1227 parent=None,
1228 name=None,
Edward Lemur0c911472018-06-14 19:39:52 +00001229 url='None',
Edward Lemure05f18d2018-06-08 17:36:53 +00001230 managed=True,
1231 custom_deps=None,
1232 custom_vars=None,
1233 custom_hooks=None,
1234 deps_file='unused',
Edward Lemurfbb06aa2018-06-11 20:43:06 +00001235 should_recurse=True,
Edward Lemure05f18d2018-06-08 17:36:53 +00001236 relative=None,
1237 condition=None,
1238 print_outbuf=True)
1239
maruel@chromium.org0d425922010-06-21 19:22:24 +00001240 self._options = options
maruel@chromium.org271375b2010-06-23 19:17:38 +00001241 if options.deps_os:
1242 enforced_os = options.deps_os.split(',')
1243 else:
1244 enforced_os = [self.DEPS_OS_CHOICES.get(sys.platform, 'unix')]
1245 if 'all' in enforced_os:
1246 enforced_os = self.DEPS_OS_CHOICES.itervalues()
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001247 self._enforced_os = tuple(set(enforced_os))
Tom Andersonc31ae0b2018-02-06 14:48:56 -08001248 self._enforced_cpu = detect_host_arch.HostArch(),
maruel@chromium.org271375b2010-06-23 19:17:38 +00001249 self._root_dir = root_dir
John Budorickd3ba72b2018-03-20 12:27:42 -07001250 self._cipd_root = None
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001251 self.config_content = None
1252
borenet@google.com88d10082014-03-21 17:24:48 +00001253 def _CheckConfig(self):
1254 """Verify that the config matches the state of the existing checked-out
1255 solutions."""
1256 for dep in self.dependencies:
Edward Lemur0c911472018-06-14 19:39:52 +00001257 if dep.managed:
Edward Lemurbabd0982018-05-11 13:32:37 -04001258 scm = dep.CreateSCM()
smut@google.comd33eab32014-07-07 19:35:18 +00001259 actual_url = scm.GetActualRemoteURL(self._options)
borenet@google.com4e9be262014-04-08 19:40:30 +00001260 if actual_url and not scm.DoesRemoteURLMatch(self._options):
levarum@chromium.org27a6f9a2016-05-28 00:21:49 +00001261 mirror = scm.GetCacheMirror()
1262 if mirror:
1263 mirror_string = '%s (exists=%s)' % (mirror.mirror_path,
1264 mirror.exists())
1265 else:
1266 mirror_string = 'not used'
borenet@google.com0a427372014-04-02 19:12:13 +00001267 raise gclient_utils.Error('''
borenet@google.com88d10082014-03-21 17:24:48 +00001268Your .gclient file seems to be broken. The requested URL is different from what
borenet@google.com0a427372014-04-02 19:12:13 +00001269is actually checked out in %(checkout_path)s.
borenet@google.com88d10082014-03-21 17:24:48 +00001270
borenet@google.com97882362014-04-07 20:06:02 +00001271The .gclient file contains:
levarum@chromium.org27a6f9a2016-05-28 00:21:49 +00001272URL: %(expected_url)s (%(expected_scm)s)
1273Cache mirror: %(mirror_string)s
borenet@google.com97882362014-04-07 20:06:02 +00001274
1275The local checkout in %(checkout_path)s reports:
1276%(actual_url)s (%(actual_scm)s)
borenet@google.com88d10082014-03-21 17:24:48 +00001277
1278You should ensure that the URL listed in .gclient is correct and either change
agabled437d762016-10-17 09:35:11 -07001279it or fix the checkout.
borenet@google.com88d10082014-03-21 17:24:48 +00001280''' % {'checkout_path': os.path.join(self.root_dir, dep.name),
1281 'expected_url': dep.url,
Edward Lemurbabd0982018-05-11 13:32:37 -04001282 'expected_scm': dep.GetScmName(),
levarum@chromium.org27a6f9a2016-05-28 00:21:49 +00001283 'mirror_string' : mirror_string,
borenet@google.com88d10082014-03-21 17:24:48 +00001284 'actual_url': actual_url,
Edward Lemurbabd0982018-05-11 13:32:37 -04001285 'actual_scm': dep.GetScmName()})
borenet@google.com88d10082014-03-21 17:24:48 +00001286
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001287 def SetConfig(self, content):
maruel@chromium.orgf13a4182011-09-22 00:26:15 +00001288 assert not self.dependencies
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001289 config_dict = {}
1290 self.config_content = content
1291 try:
1292 exec(content, config_dict)
vapier@chromium.orga81a56e2015-11-11 07:56:13 +00001293 except SyntaxError as e:
maruel@chromium.org5990f9d2010-07-07 18:02:58 +00001294 gclient_utils.SyntaxErrorToError('.gclient', e)
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001295
peter@chromium.org1efccc82012-04-27 16:34:38 +00001296 # Append any target OS that is not already being enforced to the tuple.
1297 target_os = config_dict.get('target_os', [])
stuartmorgan@chromium.org18a4f6a2012-11-02 13:37:24 +00001298 if config_dict.get('target_os_only', False):
1299 self._enforced_os = tuple(set(target_os))
1300 else:
1301 self._enforced_os = tuple(set(self._enforced_os).union(target_os))
1302
Tom Andersonc31ae0b2018-02-06 14:48:56 -08001303 # Append any target CPU that is not already being enforced to the tuple.
1304 target_cpu = config_dict.get('target_cpu', [])
1305 if config_dict.get('target_cpu_only', False):
1306 self._enforced_cpu = tuple(set(target_cpu))
1307 else:
1308 self._enforced_cpu = tuple(set(self._enforced_cpu).union(target_cpu))
1309
Aleksandr Derbenev9e8fb0e2017-08-01 20:18:31 +03001310 cache_dir = config_dict.get('cache_dir', self._options.cache_dir)
dyen@chromium.orgd915cca2014-08-07 21:41:37 +00001311 if cache_dir:
1312 cache_dir = os.path.join(self.root_dir, cache_dir)
1313 cache_dir = os.path.abspath(cache_dir)
Andrii Shyshkalov77ce4bd2017-11-27 12:38:18 -08001314
dyen@chromium.orgd915cca2014-08-07 21:41:37 +00001315 gclient_scm.GitWrapper.cache_dir = cache_dir
1316 git_cache.Mirror.SetCachePath(cache_dir)
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001317
stuartmorgan@chromium.org18a4f6a2012-11-02 13:37:24 +00001318 if not target_os and config_dict.get('target_os_only', False):
1319 raise gclient_utils.Error('Can\'t use target_os_only if target_os is '
1320 'not specified')
peter@chromium.org1efccc82012-04-27 16:34:38 +00001321
Tom Andersonc31ae0b2018-02-06 14:48:56 -08001322 if not target_cpu and config_dict.get('target_cpu_only', False):
1323 raise gclient_utils.Error('Can\'t use target_cpu_only if target_cpu is '
1324 'not specified')
1325
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001326 deps_to_add = []
Edward Lemur0c911472018-06-14 19:39:52 +00001327 solutions = config_dict.get('solutions', [])
1328 if len(solutions) == 1 and solutions[0].get('url') is None:
1329 s = solutions[0]
1330 # If url is not given, or is None, but a DEPS file is specified, we
1331 # treat the given DEPS file as a .gclient file and add its dependencies
1332 # instead.
1333 temp_dep = GitDependency(
1334 parent=self,
1335 name=s.get('name') or '.',
1336 url=None,
1337 managed=s.get('managed', True),
1338 custom_deps=s.get('custom_deps', {}),
1339 custom_vars=s.get('custom_vars', {}),
1340 custom_hooks=s.get('custom_hooks', []),
1341 deps_file=s.get('deps_file', 'DEPS'),
1342 should_recurse=True,
1343 relative=None,
1344 condition=None,
1345 print_outbuf=True)
1346 temp_dep.ParseDepsFile()
1347 for dep in temp_dep.dependencies:
1348 # Note that hooks are not preserved, since they might depend on the
1349 # existence of a checkout.
1350 dep._custom_vars = temp_dep.get_vars()
1351 dep._custom_deps = temp_dep.custom_deps
1352 dep._parent = self
1353 deps_to_add.extend(temp_dep.dependencies)
1354 else:
1355 for s in solutions:
1356 if not s.get('name') or not s.get('url'):
1357 raise gclient_utils.Error('Invalid .gclient file. Solution is '
1358 'incomplete: %s' % s)
Michael Moss4e9b50a2018-05-23 22:35:06 -07001359 deps_to_add.append(GitDependency(
Edward Lemure05f18d2018-06-08 17:36:53 +00001360 parent=self,
1361 name=s['name'],
1362 url=s['url'],
1363 managed=s.get('managed', True),
1364 custom_deps=s.get('custom_deps', {}),
1365 custom_vars=s.get('custom_vars', {}),
1366 custom_hooks=s.get('custom_hooks', []),
1367 deps_file=s.get('deps_file', 'DEPS'),
Edward Lemurfbb06aa2018-06-11 20:43:06 +00001368 should_recurse=True,
Edward Lemure05f18d2018-06-08 17:36:53 +00001369 relative=None,
1370 condition=None,
1371 print_outbuf=True))
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001372 self.add_dependencies_and_close(deps_to_add, config_dict.get('hooks', []))
Edward Lemur0c911472018-06-14 19:39:52 +00001373 if not self.dependencies:
1374 raise gclient_utils.Error('No solution specified')
1375
maruel@chromium.org0bcfd182011-10-10 20:06:09 +00001376 logging.info('SetConfig() done')
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001377
1378 def SaveConfig(self):
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001379 gclient_utils.FileWrite(os.path.join(self.root_dir,
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001380 self._options.config_filename),
1381 self.config_content)
1382
1383 @staticmethod
1384 def LoadCurrentConfig(options):
1385 """Searches for and loads a .gclient file relative to the current working
1386 dir. Returns a GClient object."""
szager@chromium.orge2e03202012-07-31 18:05:16 +00001387 if options.spec:
1388 client = GClient('.', options)
1389 client.SetConfig(options.spec)
1390 else:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00001391 if options.verbose:
1392 print('Looking for %s starting from %s\n' % (
1393 options.config_filename, os.getcwd()))
szager@chromium.orge2e03202012-07-31 18:05:16 +00001394 path = gclient_utils.FindGclientRoot(os.getcwd(), options.config_filename)
1395 if not path:
Michael Achenbachb3ce73d2017-10-11 16:41:27 +02001396 if options.verbose:
1397 print('Couldn\'t find configuration file.')
szager@chromium.orge2e03202012-07-31 18:05:16 +00001398 return None
1399 client = GClient(path, options)
1400 client.SetConfig(gclient_utils.FileRead(
1401 os.path.join(path, options.config_filename)))
maruel@chromium.org69392e72011-10-13 22:09:00 +00001402
1403 if (options.revisions and
1404 len(client.dependencies) > 1 and
1405 any('@' not in r for r in options.revisions)):
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001406 print(
1407 ('You must specify the full solution name like --revision %s@%s\n'
1408 'when you have multiple solutions setup in your .gclient file.\n'
1409 'Other solutions present are: %s.') % (
maruel@chromium.org69392e72011-10-13 22:09:00 +00001410 client.dependencies[0].name,
1411 options.revisions[0],
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001412 ', '.join(s.name for s in client.dependencies[1:])),
1413 file=sys.stderr)
maruel@chromium.org15804092010-09-02 17:07:37 +00001414 return client
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001415
nsylvain@google.comefc80932011-05-31 21:27:56 +00001416 def SetDefaultConfig(self, solution_name, deps_file, solution_url,
Paweł Hajdan, Jr3ba2a7c2017-10-04 19:24:46 +02001417 managed=True, cache_dir=None, custom_vars=None):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001418 self.SetConfig(self.DEFAULT_CLIENT_FILE_TEXT % {
1419 'solution_name': solution_name,
1420 'solution_url': solution_url,
nsylvain@google.comefc80932011-05-31 21:27:56 +00001421 'deps_file': deps_file,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001422 'managed': managed,
iannucci@chromium.org53456aa2013-07-03 19:38:34 +00001423 'cache_dir': cache_dir,
Paweł Hajdan, Jr3ba2a7c2017-10-04 19:24:46 +02001424 'custom_vars': custom_vars or {},
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001425 })
1426
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001427 def _SaveEntries(self):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001428 """Creates a .gclient_entries file to record the list of unique checkouts.
1429
1430 The .gclient_entries file lives in the same directory as .gclient.
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001431 """
1432 # Sometimes pprint.pformat will use {', sometimes it'll use { ' ... It
1433 # makes testing a bit too fun.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001434 result = 'entries = {\n'
Edward Lemur0c911472018-06-14 19:39:52 +00001435 for entry in self.root.subtree():
agabled437d762016-10-17 09:35:11 -07001436 result += ' %s: %s,\n' % (pprint.pformat(entry.name),
Edward Lemure7273d22018-05-10 19:13:51 -04001437 pprint.pformat(entry.url))
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001438 result += '}\n'
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001439 file_path = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org1333cb32011-10-04 23:40:16 +00001440 logging.debug(result)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001441 gclient_utils.FileWrite(file_path, result)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001442
1443 def _ReadEntries(self):
1444 """Read the .gclient_entries file for the given client.
1445
1446 Returns:
1447 A sequence of solution names, which will be empty if there is the
1448 entries file hasn't been created yet.
1449 """
1450 scope = {}
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001451 filename = os.path.join(self.root_dir, self._options.entries_filename)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001452 if not os.path.exists(filename):
maruel@chromium.org73e21142010-07-05 13:32:01 +00001453 return {}
maruel@chromium.org5990f9d2010-07-07 18:02:58 +00001454 try:
1455 exec(gclient_utils.FileRead(filename), scope)
vapier@chromium.orga81a56e2015-11-11 07:56:13 +00001456 except SyntaxError as e:
maruel@chromium.org5990f9d2010-07-07 18:02:58 +00001457 gclient_utils.SyntaxErrorToError(filename, e)
Aaron Gable3721ee92017-04-03 14:53:14 -07001458 return scope.get('entries', {})
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +00001459
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001460 def _EnforceRevisions(self):
maruel@chromium.org918a9ae2010-05-28 15:50:30 +00001461 """Checks for revision overrides."""
1462 revision_overrides = {}
smutae7ea312016-07-18 11:59:41 -07001463 if self._options.head:
1464 return revision_overrides
joi@chromium.org792ea882010-11-10 02:37:27 +00001465 if not self._options.revisions:
1466 for s in self.dependencies:
smutae7ea312016-07-18 11:59:41 -07001467 if not s.managed:
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00001468 self._options.revisions.append('%s@unmanaged' % s.name)
maruel@chromium.org307d1792010-05-31 20:03:13 +00001469 if not self._options.revisions:
1470 return revision_overrides
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001471 solutions_names = [s.name for s in self.dependencies]
smutae7ea312016-07-18 11:59:41 -07001472 index = 0
1473 for revision in self._options.revisions:
1474 if not '@' in revision:
maruel@chromium.org307d1792010-05-31 20:03:13 +00001475 # Support for --revision 123
smutae7ea312016-07-18 11:59:41 -07001476 revision = '%s@%s' % (solutions_names[index], revision)
1477 name, rev = revision.split('@', 1)
szager@chromium.org4ad264b2014-05-20 04:43:47 +00001478 revision_overrides[name] = rev
smutae7ea312016-07-18 11:59:41 -07001479 index += 1
maruel@chromium.org918a9ae2010-05-28 15:50:30 +00001480 return revision_overrides
1481
Edward Lesmesc621b212018-03-21 20:26:56 -04001482 def _EnforcePatchRefs(self):
1483 """Checks for patch refs."""
1484 patch_refs = {}
1485 if not self._options.patch_refs:
1486 return patch_refs
1487 for given_patch_ref in self._options.patch_refs:
1488 patch_repo, _, patch_ref = given_patch_ref.partition('@')
1489 if not patch_repo or not patch_ref:
1490 raise gclient_utils.Error(
1491 'Wrong revision format: %s should be of the form '
1492 'patch_repo@patch_ref.' % given_patch_ref)
1493 patch_refs[patch_repo] = patch_ref
1494 return patch_refs
1495
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001496 def RunOnDeps(self, command, args, ignore_requirements=False, progress=True):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001497 """Runs a command on each dependency in a client and its dependencies.
1498
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001499 Args:
1500 command: The command to use (e.g., 'status' or 'diff')
1501 args: list of str - extra arguments to add to the command line.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001502 """
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001503 revision_overrides = {}
Edward Lesmesc621b212018-03-21 20:26:56 -04001504 patch_refs = {}
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001505 # It's unnecessary to check for revision overrides for 'recurse'.
1506 # Save a few seconds by not calling _EnforceRevisions() in that case.
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +02001507 if command not in ('diff', 'recurse', 'runhooks', 'status', 'revert',
1508 'validate'):
szager@chromium.org5273b8a2014-08-21 15:10:10 +00001509 self._CheckConfig()
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001510 revision_overrides = self._EnforceRevisions()
Edward Lesmesc621b212018-03-21 20:26:56 -04001511
1512 if command == 'update':
1513 patch_refs = self._EnforcePatchRefs()
Daniel Chenga21b5b32017-10-19 20:07:48 +00001514 # Disable progress for non-tty stdout.
Daniel Chenga0c5f082017-10-19 13:35:19 -07001515 should_show_progress = (
1516 setup_color.IS_TTY and not self._options.verbose and progress)
1517 pm = None
1518 if should_show_progress:
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001519 if command in ('update', 'revert'):
1520 pm = Progress('Syncing projects', 1)
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +02001521 elif command in ('recurse', 'validate'):
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001522 pm = Progress(' '.join(args), 1)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001523 work_queue = gclient_utils.ExecutionQueue(
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001524 self._options.jobs, pm, ignore_requirements=ignore_requirements,
1525 verbose=self._options.verbose)
maruel@chromium.org049bced2010-08-12 13:37:20 +00001526 for s in self.dependencies:
Edward Lemur0c911472018-06-14 19:39:52 +00001527 work_queue.enqueue(s)
Edward Lesmesc621b212018-03-21 20:26:56 -04001528 work_queue.flush(revision_overrides, command, args, options=self._options,
1529 patch_refs=patch_refs)
1530
szager@chromium.org4ad264b2014-05-20 04:43:47 +00001531 if revision_overrides:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001532 print('Please fix your script, having invalid --revision flags will soon '
Edward Lesmesc621b212018-03-21 20:26:56 -04001533 'be considered an error.', file=sys.stderr)
1534
1535 if patch_refs:
1536 raise gclient_utils.Error(
1537 'The following --patch-ref flags were not used. Please fix it:\n%s' %
1538 ('\n'.join(
1539 patch_repo + '@' + patch_ref
1540 for patch_repo, patch_ref in patch_refs.iteritems())))
piman@chromium.org6f363722010-04-27 00:41:09 +00001541
John Budorickd3ba72b2018-03-20 12:27:42 -07001542 if self._cipd_root:
1543 self._cipd_root.run(command)
1544
Dirk Pranke9f20d022017-10-11 18:36:54 -07001545 # Once all the dependencies have been processed, it's now safe to write
Michael Moss848c86e2018-05-03 16:05:50 -07001546 # out the gn_args_file and run the hooks.
Dirk Pranke9f20d022017-10-11 18:36:54 -07001547 if command == 'update':
Michael Moss848c86e2018-05-03 16:05:50 -07001548 gn_args_dep = self.dependencies[0]
1549 if gn_args_dep._gn_args_from:
1550 deps_map = dict([(dep.name, dep) for dep in gn_args_dep.dependencies])
1551 gn_args_dep = deps_map.get(gn_args_dep._gn_args_from)
1552 if gn_args_dep and gn_args_dep.HasGNArgsFile():
1553 gn_args_dep.WriteGNArgsFile()
Dirk Pranke9f20d022017-10-11 18:36:54 -07001554
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001555 if not self._options.nohooks:
Daniel Chenga0c5f082017-10-19 13:35:19 -07001556 if should_show_progress:
1557 pm = Progress('Running hooks', 1)
1558 self.RunHooksRecursively(self._options, pm)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001559
1560 if command == 'update':
ajwong@chromium.orgcdcee802009-06-23 15:30:42 +00001561 # Notify the user if there is an orphaned entry in their working copy.
1562 # Only delete the directory if there are no changes in it, and
1563 # delete_unversioned_trees is set to true.
Edward Lemur0c911472018-06-14 19:39:52 +00001564 entries = [i.name for i in self.root.subtree()]
xusydoc@chromium.org885a9602013-05-31 09:54:40 +00001565 full_entries = [os.path.join(self.root_dir, e.replace('/', os.path.sep))
1566 for e in entries]
1567
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001568 for entry, prev_url in self._ReadEntries().iteritems():
maruel@chromium.org04dd7de2010-10-14 13:25:49 +00001569 if not prev_url:
1570 # entry must have been overridden via .gclient custom_deps
1571 continue
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001572 # Fix path separator on Windows.
1573 entry_fixed = entry.replace('/', os.path.sep)
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001574 e_dir = os.path.join(self.root_dir, entry_fixed)
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001575 # Use entry and not entry_fixed there.
jochen@chromium.orga78e5532013-03-11 13:33:03 +00001576 if (entry not in entries and
1577 (not any(path.startswith(entry + '/') for path in entries)) and
jochen@chromium.orgcc475722013-03-11 13:07:40 +00001578 os.path.exists(e_dir)):
primiano@chromium.org1c127382015-02-17 11:15:40 +00001579 # The entry has been removed from DEPS.
Edward Lemurbabd0982018-05-11 13:32:37 -04001580 scm = gclient_scm.GitWrapper(
1581 prev_url, self.root_dir, entry_fixed, self.outbuf)
xusydoc@chromium.org885a9602013-05-31 09:54:40 +00001582
1583 # Check to see if this directory is now part of a higher-up checkout.
borenet@google.com359bb642014-05-13 17:28:19 +00001584 scm_root = None
agabled437d762016-10-17 09:35:11 -07001585 try:
1586 scm_root = gclient_scm.scm.GIT.GetCheckoutRoot(scm.checkout_path)
1587 except subprocess2.CalledProcessError:
1588 pass
1589 if not scm_root:
borenet@google.com359bb642014-05-13 17:28:19 +00001590 logging.warning('Could not find checkout root for %s. Unable to '
1591 'determine whether it is part of a higher-level '
1592 'checkout, so not removing.' % entry)
1593 continue
primiano@chromium.org1c127382015-02-17 11:15:40 +00001594
1595 # This is to handle the case of third_party/WebKit migrating from
1596 # being a DEPS entry to being part of the main project.
1597 # If the subproject is a Git project, we need to remove its .git
1598 # folder. Otherwise git operations on that folder will have different
1599 # effects depending on the current working directory.
agabled437d762016-10-17 09:35:11 -07001600 if os.path.abspath(scm_root) == os.path.abspath(e_dir):
primiano@chromium.org1c127382015-02-17 11:15:40 +00001601 e_par_dir = os.path.join(e_dir, os.pardir)
agabled437d762016-10-17 09:35:11 -07001602 if gclient_scm.scm.GIT.IsInsideWorkTree(e_par_dir):
1603 par_scm_root = gclient_scm.scm.GIT.GetCheckoutRoot(e_par_dir)
primiano@chromium.org1c127382015-02-17 11:15:40 +00001604 # rel_e_dir : relative path of entry w.r.t. its parent repo.
1605 rel_e_dir = os.path.relpath(e_dir, par_scm_root)
agabled437d762016-10-17 09:35:11 -07001606 if gclient_scm.scm.GIT.IsDirectoryVersioned(
1607 par_scm_root, rel_e_dir):
primiano@chromium.org1c127382015-02-17 11:15:40 +00001608 save_dir = scm.GetGitBackupDirPath()
1609 # Remove any eventual stale backup dir for the same project.
1610 if os.path.exists(save_dir):
1611 gclient_utils.rmtree(save_dir)
1612 os.rename(os.path.join(e_dir, '.git'), save_dir)
1613 # When switching between the two states (entry/ is a subproject
1614 # -> entry/ is part of the outer project), it is very likely
1615 # that some files are changed in the checkout, unless we are
1616 # jumping *exactly* across the commit which changed just DEPS.
1617 # In such case we want to cleanup any eventual stale files
1618 # (coming from the old subproject) in order to end up with a
1619 # clean checkout.
agabled437d762016-10-17 09:35:11 -07001620 gclient_scm.scm.GIT.CleanupDir(par_scm_root, rel_e_dir)
primiano@chromium.org1c127382015-02-17 11:15:40 +00001621 assert not os.path.exists(os.path.join(e_dir, '.git'))
1622 print(('\nWARNING: \'%s\' has been moved from DEPS to a higher '
1623 'level checkout. The git folder containing all the local'
1624 ' branches has been saved to %s.\n'
1625 'If you don\'t care about its state you can safely '
1626 'remove that folder to free up space.') %
1627 (entry, save_dir))
1628 continue
1629
borenet@google.com359bb642014-05-13 17:28:19 +00001630 if scm_root in full_entries:
primiano@chromium.org1c127382015-02-17 11:15:40 +00001631 logging.info('%s is part of a higher level checkout, not removing',
1632 scm.GetCheckoutRoot())
xusydoc@chromium.org885a9602013-05-31 09:54:40 +00001633 continue
1634
1635 file_list = []
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001636 scm.status(self._options, [], file_list)
1637 modified_files = file_list != []
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001638 if (not self._options.delete_unversioned_trees or
1639 (modified_files and not self._options.force)):
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001640 # There are modified files in this entry. Keep warning until
1641 # removed.
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001642 print(('\nWARNING: \'%s\' is no longer part of this client. '
1643 'It is recommended that you manually remove it.\n') %
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +00001644 entry_fixed)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001645 else:
1646 # Delete the entry
maruel@chromium.org73e21142010-07-05 13:32:01 +00001647 print('\n________ deleting \'%s\' in \'%s\'' % (
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001648 entry_fixed, self.root_dir))
digit@chromium.orgdc112ac2013-04-24 13:00:19 +00001649 gclient_utils.rmtree(e_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001650 # record the current list of entries for next time
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001651 self._SaveEntries()
maruel@chromium.org17cdf762010-05-28 17:30:52 +00001652 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001653
1654 def PrintRevInfo(self):
maruel@chromium.org54a07a22010-06-14 19:07:39 +00001655 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +00001656 raise gclient_utils.Error('No solution specified')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001657 # Load all the settings.
szager@chromium.orgfe0d1902014-04-08 20:50:44 +00001658 work_queue = gclient_utils.ExecutionQueue(
1659 self._options.jobs, None, False, verbose=self._options.verbose)
maruel@chromium.org049bced2010-08-12 13:37:20 +00001660 for s in self.dependencies:
Edward Lemur0c911472018-06-14 19:39:52 +00001661 work_queue.enqueue(s)
Edward Lesmesc621b212018-03-21 20:26:56 -04001662 work_queue.flush({}, None, [], options=self._options, patch_refs=None)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001663
Edward Lemur0c911472018-06-14 19:39:52 +00001664 def ShouldPrint(dep):
Edward Lesmesbb16e332018-03-30 17:54:51 -04001665 return (not self._options.filter
Edward Lemure7273d22018-05-10 19:13:51 -04001666 or dep.FuzzyMatchUrl(self._options.filter))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001667
Edward Lemur0c911472018-06-14 19:39:52 +00001668 for dep in self.subtree():
1669 if self._options.snapshot or self._options.actual:
1670 dep.PinToActualRevision()
1671
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +00001672 if self._options.snapshot:
Edward Lemur0c911472018-06-14 19:39:52 +00001673 json_output = [
1674 {
1675 'name': d.name,
1676 'solution_url': d.url,
1677 'deps_file': d.deps_file,
1678 'managed': d.managed,
1679 'custom_deps': {
1680 subdep.name: subdep.url
1681 for subdep in d.subtree()
1682 if ShouldPrint(subdep)
1683 },
1684 }
1685 for d in self.dependencies
1686 if ShouldPrint(d)
1687 ]
1688 output = json.dumps(json_output, indent=2, separators=(',', ': '))
1689 if not self._options.output_json:
1690 output = self.DEFAULT_SNAPSHOT_FILE_TEXT % {'solution_list': output}
1691 elif self._options.output_json:
1692 json_output = {
1693 d.name: {
1694 'url': d.url.split('@')[0],
1695 'rev': d.url.split('@')[1] if '@' in d.url else None,
1696 }
1697 for d in self.subtree()
1698 if ShouldPrint(d)
1699 }
1700 output = json.dumps(json_output, indent=2, separators=(',', ': '))
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +00001701 else:
Edward Lemur0c911472018-06-14 19:39:52 +00001702 output = '\n'.join(
1703 '%s: %s' % (d.name, d.url)
1704 for d in self.subtree()
1705 if ShouldPrint(d)
1706 )
1707
1708 if self._options.output_json and self._options.output_json != '-':
1709 with open(self._options.output_json, 'w') as f:
1710 f.write(output)
1711 else:
1712 print(output)
1713
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +00001714 logging.info(str(self))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001715
Edward Lemure05f18d2018-06-08 17:36:53 +00001716 def ParseDepsFile(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001717 """No DEPS to parse for a .gclient file."""
maruel@chromium.org049bced2010-08-12 13:37:20 +00001718 raise gclient_utils.Error('Internal error')
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001719
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00001720 def PrintLocationAndContents(self):
1721 # Print out the .gclient file. This is longer than if we just printed the
1722 # client dict, but more legible, and it might contain helpful comments.
1723 print('Loaded .gclient config in %s:\n%s' % (
1724 self.root_dir, self.config_content))
1725
John Budorickd3ba72b2018-03-20 12:27:42 -07001726 def GetCipdRoot(self):
1727 if not self._cipd_root:
1728 self._cipd_root = gclient_scm.CipdRoot(
1729 self.root_dir,
1730 # TODO(jbudorick): Support other service URLs as necessary.
1731 # Service URLs should be constant over the scope of a cipd
1732 # root, so a var per DEPS file specifying the service URL
1733 # should suffice.
1734 'https://chrome-infra-packages.appspot.com')
1735 return self._cipd_root
1736
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001737 @property
maruel@chromium.org75a59272010-06-11 22:34:03 +00001738 def root_dir(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001739 """Root directory of gclient checkout."""
maruel@chromium.org75a59272010-06-11 22:34:03 +00001740 return self._root_dir
1741
maruel@chromium.orgd6db3d52011-09-20 14:00:45 +00001742 @property
maruel@chromium.org271375b2010-06-23 19:17:38 +00001743 def enforced_os(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +00001744 """What deps_os entries that are to be parsed."""
maruel@chromium.org271375b2010-06-23 19:17:38 +00001745 return self._enforced_os
1746
maruel@chromium.org68988972011-09-20 14:11:42 +00001747 @property
sivachandra@chromium.orgd45e73e2012-10-24 23:42:48 +00001748 def target_os(self):
1749 return self._enforced_os
1750
Tom Andersonc31ae0b2018-02-06 14:48:56 -08001751 @property
1752 def target_cpu(self):
1753 return self._enforced_cpu
1754
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001755
John Budorick0f7b2002018-01-19 15:46:17 -08001756class CipdDependency(Dependency):
1757 """A Dependency object that represents a single CIPD package."""
1758
Edward Lemur0c911472018-06-14 19:39:52 +00001759 def __init__(self, parent, name, dep_value, cipd_root, custom_vars, relative,
1760 condition):
John Budorick0f7b2002018-01-19 15:46:17 -08001761 package = dep_value['package']
1762 version = dep_value['version']
1763 url = urlparse.urljoin(
1764 cipd_root.service_url, '%s@%s' % (package, version))
1765 super(CipdDependency, self).__init__(
Edward Lemure05f18d2018-06-08 17:36:53 +00001766 parent=parent,
1767 name=name + ':' + package,
1768 url=url,
1769 managed=None,
1770 custom_deps=None,
1771 custom_vars=custom_vars,
1772 custom_hooks=None,
1773 deps_file=None,
Edward Lemurfbb06aa2018-06-11 20:43:06 +00001774 should_recurse=False,
Edward Lemure05f18d2018-06-08 17:36:53 +00001775 relative=relative,
1776 condition=condition)
John Budorick0f7b2002018-01-19 15:46:17 -08001777 if relative:
1778 # TODO(jbudorick): Implement relative if necessary.
1779 raise gclient_utils.Error(
1780 'Relative CIPD dependencies are not currently supported.')
John Budorickd3ba72b2018-03-20 12:27:42 -07001781 self._cipd_package = None
John Budorick0f7b2002018-01-19 15:46:17 -08001782 self._cipd_root = cipd_root
John Budorick0f7b2002018-01-19 15:46:17 -08001783 self._cipd_subdir = os.path.relpath(
Shenghua Zhang6f830312018-02-26 11:45:07 -08001784 os.path.join(self.root.root_dir, name), cipd_root.root_dir)
Edward Lemur0c911472018-06-14 19:39:52 +00001785 self._package_path = name
John Budorickd3ba72b2018-03-20 12:27:42 -07001786 self._package_name = package
1787 self._package_version = version
1788
1789 #override
Edward Lesmesc621b212018-03-21 20:26:56 -04001790 def run(self, revision_overrides, command, args, work_queue, options,
1791 patch_refs):
John Budorickd3ba72b2018-03-20 12:27:42 -07001792 """Runs |command| then parse the DEPS file."""
1793 logging.info('CipdDependency(%s).run()' % self.name)
John Budorickd3ba72b2018-03-20 12:27:42 -07001794 self._CreatePackageIfNecessary()
1795 super(CipdDependency, self).run(revision_overrides, command, args,
Edward Lesmesc621b212018-03-21 20:26:56 -04001796 work_queue, options, patch_refs)
John Budorickd3ba72b2018-03-20 12:27:42 -07001797
1798 def _CreatePackageIfNecessary(self):
1799 # We lazily create the CIPD package to make sure that only packages
1800 # that we want (as opposed to all packages defined in all DEPS files
1801 # we parse) get added to the root and subsequently ensured.
1802 if not self._cipd_package:
1803 self._cipd_package = self._cipd_root.add_package(
1804 self._cipd_subdir, self._package_name, self._package_version)
John Budorick0f7b2002018-01-19 15:46:17 -08001805
Edward Lemure05f18d2018-06-08 17:36:53 +00001806 def ParseDepsFile(self):
John Budorick0f7b2002018-01-19 15:46:17 -08001807 """CIPD dependencies are not currently allowed to have nested deps."""
1808 self.add_dependencies_and_close([], [])
1809
1810 #override
Shenghua Zhang6f830312018-02-26 11:45:07 -08001811 def verify_validity(self):
1812 """CIPD dependencies allow duplicate name for packages in same directory."""
1813 logging.info('Dependency(%s).verify_validity()' % self.name)
1814 return True
1815
1816 #override
Edward Lemurbabd0982018-05-11 13:32:37 -04001817 def GetScmName(self):
John Budorick0f7b2002018-01-19 15:46:17 -08001818 """Always 'cipd'."""
John Budorick0f7b2002018-01-19 15:46:17 -08001819 return 'cipd'
1820
1821 #override
Edward Lemurbabd0982018-05-11 13:32:37 -04001822 def CreateSCM(self, out_cb=None):
John Budorick0f7b2002018-01-19 15:46:17 -08001823 """Create a Wrapper instance suitable for handling this CIPD dependency."""
John Budorickd3ba72b2018-03-20 12:27:42 -07001824 self._CreatePackageIfNecessary()
John Budorick0f7b2002018-01-19 15:46:17 -08001825 return gclient_scm.CipdWrapper(
Edward Lemurbabd0982018-05-11 13:32:37 -04001826 self.url, self.root.root_dir, self.name, self.outbuf, out_cb,
1827 root=self._cipd_root, package=self._cipd_package)
John Budorick0f7b2002018-01-19 15:46:17 -08001828
Edward Lemur0c911472018-06-14 19:39:52 +00001829 def hierarchy(self, include_url=True):
1830 """Returns a human-readable hierarchical reference to a Dependency."""
1831 return self.parent.hierarchy(include_url) + ' -> ' + self._package_path
1832
John Budorick0f7b2002018-01-19 15:46:17 -08001833 def ToLines(self):
1834 """Return a list of lines representing this in a DEPS file."""
1835 s = []
John Budorickd3ba72b2018-03-20 12:27:42 -07001836 self._CreatePackageIfNecessary()
John Budorick0f7b2002018-01-19 15:46:17 -08001837 if self._cipd_package.authority_for_subdir:
1838 condition_part = ([' "condition": %r,' % self.condition]
1839 if self.condition else [])
1840 s.extend([
Michael Moss4e9b50a2018-05-23 22:35:06 -07001841 ' # %s' % self.hierarchy(include_url=False),
John Budorickd3ba72b2018-03-20 12:27:42 -07001842 ' "%s": {' % (self.name.split(':')[0],),
John Budorick0f7b2002018-01-19 15:46:17 -08001843 ' "packages": [',
1844 ])
1845 for p in self._cipd_root.packages(self._cipd_subdir):
1846 s.extend([
John Budorick64e33cb2018-02-20 09:40:30 -08001847 ' {',
1848 ' "package": "%s",' % p.name,
1849 ' "version": "%s",' % p.version,
1850 ' },',
John Budorick0f7b2002018-01-19 15:46:17 -08001851 ])
John Budorickd3ba72b2018-03-20 12:27:42 -07001852
John Budorick0f7b2002018-01-19 15:46:17 -08001853 s.extend([
1854 ' ],',
1855 ' "dep_type": "cipd",',
1856 ] + condition_part + [
1857 ' },',
1858 '',
1859 ])
1860 return s
1861
1862
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001863#### gclient commands.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001864
1865
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001866@subcommand.usage('[command] [args ...]')
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001867def CMDrecurse(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001868 """Operates [command args ...] on all the dependencies.
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001869
1870 Runs a shell command on all entries.
qyearsley12fa6ff2016-08-24 09:18:40 -07001871 Sets GCLIENT_DEP_PATH environment variable as the dep's relative location to
ilevy@chromium.org37116242012-11-28 01:32:48 +00001872 root directory of the checkout.
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001873 """
1874 # Stop parsing at the first non-arg so that these go through to the command
1875 parser.disable_interspersed_args()
1876 parser.add_option('-s', '--scm', action='append', default=[],
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001877 help='Choose scm types to operate upon.')
maruel@chromium.org288054d2012-03-05 00:43:07 +00001878 parser.add_option('-i', '--ignore', action='store_true',
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001879 help='Ignore non-zero return codes from subcommands.')
1880 parser.add_option('--prepend-dir', action='store_true',
1881 help='Prepend relative dir for use with git <cmd> --null.')
1882 parser.add_option('--no-progress', action='store_true',
1883 help='Disable progress bar that shows sub-command updates')
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001884 options, args = parser.parse_args(args)
maruel@chromium.org45e9f2d2010-10-18 13:33:46 +00001885 if not args:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001886 print('Need to supply a command!', file=sys.stderr)
maruel@chromium.org45e9f2d2010-10-18 13:33:46 +00001887 return 1
maruel@chromium.org78cba522010-10-18 13:32:05 +00001888 root_and_entries = gclient_utils.GetGClientRootAndEntries()
1889 if not root_and_entries:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001890 print(
maruel@chromium.org78cba522010-10-18 13:32:05 +00001891 'You need to run gclient sync at least once to use \'recurse\'.\n'
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00001892 'This is because .gclient_entries needs to exist and be up to date.',
1893 file=sys.stderr)
maruel@chromium.org78cba522010-10-18 13:32:05 +00001894 return 1
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001895
1896 # Normalize options.scm to a set()
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001897 scm_set = set()
1898 for scm in options.scm:
1899 scm_set.update(scm.split(','))
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001900 options.scm = scm_set
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001901
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001902 options.nohooks = True
1903 client = GClient.LoadCurrentConfig(options)
Marc-Antoine Ruele6e06412017-10-18 13:47:02 -04001904 if not client:
1905 raise gclient_utils.Error('client not configured; see \'gclient config\'')
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001906 return client.RunOnDeps('recurse', args, ignore_requirements=True,
1907 progress=not options.no_progress)
piman@chromium.org4b90e3a2010-07-01 20:28:26 +00001908
1909
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001910@subcommand.usage('[args ...]')
davidbarr@chromium.org12f944e2012-03-01 02:18:31 +00001911def CMDfetch(parser, args):
1912 """Fetches upstream commits for all modules.
1913
maruel@chromium.org39c0b222013-08-17 16:57:01 +00001914 Completely git-specific. Simply runs 'git fetch [args ...]' for each module.
1915 """
davidbarr@chromium.org47ca0ee2012-03-02 16:06:11 +00001916 (options, args) = parser.parse_args(args)
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00001917 return CMDrecurse(OptionParser(), [
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00001918 '--jobs=%d' % options.jobs, '--scm=git', 'git', 'fetch'] + args)
1919
1920
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02001921class Flattener(object):
1922 """Flattens a gclient solution."""
1923
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02001924 def __init__(self, client, pin_all_deps=False):
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02001925 """Constructor.
1926
1927 Arguments:
1928 client (GClient): client to flatten
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02001929 pin_all_deps (bool): whether to pin all deps, even if they're not pinned
1930 in DEPS
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02001931 """
1932 self._client = client
1933
1934 self._deps_string = None
Paweł Hajdan, Jrfeb01642017-09-12 15:50:46 +02001935 self._deps_files = set()
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02001936
1937 self._allowed_hosts = set()
1938 self._deps = {}
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02001939 self._hooks = []
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02001940 self._pre_deps_hooks = []
Paweł Hajdan, Jrfb022012017-07-06 18:00:08 +02001941 self._vars = {}
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02001942
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02001943 self._flatten(pin_all_deps=pin_all_deps)
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02001944
1945 @property
1946 def deps_string(self):
1947 assert self._deps_string is not None
1948 return self._deps_string
1949
Paweł Hajdan, Jrfeb01642017-09-12 15:50:46 +02001950 @property
1951 def deps_files(self):
1952 return self._deps_files
1953
Paweł Hajdan, Jr5ec77132017-08-16 19:21:06 +02001954 def _pin_dep(self, dep):
1955 """Pins a dependency to specific full revision sha.
1956
1957 Arguments:
1958 dep (Dependency): dependency to process
1959 """
Paweł Hajdan, Jr5ec77132017-08-16 19:21:06 +02001960 # Make sure the revision is always fully specified (a hash),
1961 # as opposed to refs or tags which might change. Similarly,
1962 # shortened shas might become ambiguous; make sure to always
1963 # use full one for pinning.
Edward Lemure7273d22018-05-10 19:13:51 -04001964 revision = gclient_utils.SplitUrlRevision(dep.url)[1]
1965 if not revision or not gclient_utils.IsFullGitSha(revision):
1966 dep.PinToActualRevision()
Paweł Hajdan, Jr5ec77132017-08-16 19:21:06 +02001967
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02001968 def _flatten(self, pin_all_deps=False):
1969 """Runs the flattener. Saves resulting DEPS string.
1970
1971 Arguments:
1972 pin_all_deps (bool): whether to pin all deps, even if they're not pinned
1973 in DEPS
1974 """
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02001975 for solution in self._client.dependencies:
Paweł Hajdan, Jr11eb7152017-08-10 12:50:11 +02001976 self._add_dep(solution)
Edward Lemur0c911472018-06-14 19:39:52 +00001977 if solution.should_recurse:
1978 self._flatten_dep(solution)
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02001979
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02001980 if pin_all_deps:
1981 for dep in self._deps.itervalues():
Paweł Hajdan, Jr5ec77132017-08-16 19:21:06 +02001982 self._pin_dep(dep)
Paweł Hajdan, Jr39300ba2017-08-11 16:52:38 +02001983
Paweł Hajdan, Jr76a9d042017-08-18 20:05:41 +02001984 def add_deps_file(dep):
Paweł Hajdan, Jr0870df22017-08-23 17:59:29 +02001985 # Only include DEPS files referenced by recursedeps.
Edward Lemurfbb06aa2018-06-11 20:43:06 +00001986 if not dep.should_recurse:
Paweł Hajdan, Jr0870df22017-08-23 17:59:29 +02001987 return
Paweł Hajdan, Jrfeb01642017-09-12 15:50:46 +02001988 deps_file = dep.deps_file
1989 deps_path = os.path.join(self._client.root_dir, dep.name, deps_file)
Paweł Hajdan, Jr76a9d042017-08-18 20:05:41 +02001990 if not os.path.exists(deps_path):
Paweł Hajdan, Jrfeb01642017-09-12 15:50:46 +02001991 # gclient has a fallback that if deps_file doesn't exist, it'll try
1992 # DEPS. Do the same here.
1993 deps_file = 'DEPS'
1994 deps_path = os.path.join(self._client.root_dir, dep.name, deps_file)
1995 if not os.path.exists(deps_path):
1996 return
Edward Lemure7273d22018-05-10 19:13:51 -04001997 self._deps_files.add((dep.url, deps_file, dep.hierarchy_data()))
Paweł Hajdan, Jr76a9d042017-08-18 20:05:41 +02001998 for dep in self._deps.itervalues():
1999 add_deps_file(dep)
Paweł Hajdan, Jr76a9d042017-08-18 20:05:41 +02002000
Michael Moss848c86e2018-05-03 16:05:50 -07002001 gn_args_dep = self._deps.get(self._client.dependencies[0]._gn_args_from,
2002 self._client.dependencies[0])
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002003 self._deps_string = '\n'.join(
Michael Moss848c86e2018-05-03 16:05:50 -07002004 _GNSettingsToLines(gn_args_dep._gn_args_file, gn_args_dep._gn_args) +
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002005 _AllowedHostsToLines(self._allowed_hosts) +
2006 _DepsToLines(self._deps) +
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002007 _HooksToLines('hooks', self._hooks) +
2008 _HooksToLines('pre_deps_hooks', self._pre_deps_hooks) +
Paweł Hajdan, Jrfb022012017-07-06 18:00:08 +02002009 _VarsToLines(self._vars) +
Paweł Hajdan, Jr76a9d042017-08-18 20:05:41 +02002010 ['# %s, %s' % (url, deps_file)
Michael Mossfe68c912018-03-22 19:19:35 -07002011 for url, deps_file, _ in sorted(self._deps_files)] +
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002012 ['']) # Ensure newline at end of file.
2013
Paweł Hajdan, Jr11eb7152017-08-10 12:50:11 +02002014 def _add_dep(self, dep):
2015 """Helper to add a dependency to flattened DEPS.
2016
2017 Arguments:
2018 dep (Dependency): dependency to add
2019 """
2020 assert dep.name not in self._deps or self._deps.get(dep.name) == dep, (
2021 dep.name, self._deps.get(dep.name))
Edward Lemur0c911472018-06-14 19:39:52 +00002022 self._deps[dep.name] = dep
Paweł Hajdan, Jr11eb7152017-08-10 12:50:11 +02002023
Edward Lemur16f4bad2018-05-16 16:53:49 -04002024 def _flatten_dep(self, dep):
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002025 """Visits a dependency in order to flatten it (see CMDflatten).
2026
2027 Arguments:
2028 dep (Dependency): dependency to process
2029 """
Edward Lemur16f4bad2018-05-16 16:53:49 -04002030 logging.debug('_flatten_dep(%s)', dep.name)
Paweł Hajdan, Jradae2a62017-08-18 16:49:57 +02002031
Edward Lemur16f4bad2018-05-16 16:53:49 -04002032 assert dep.deps_parsed, (
2033 "Attempted to flatten %s but it has not been processed." % dep.name)
Paweł Hajdan, Jrc69b32e2017-08-17 18:47:48 +02002034
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002035 self._allowed_hosts.update(dep.allowed_hosts)
2036
Michael Mossce9f17f2018-01-31 13:16:35 -08002037 # Only include vars explicitly listed in the DEPS files or gclient solution,
2038 # not automatic, local overrides (i.e. not all of dep.get_vars()).
Michael Moss4e9b50a2018-05-23 22:35:06 -07002039 hierarchy = dep.hierarchy(include_url=False)
Paweł Hajdan, Jra3b67ae2017-08-30 15:18:21 +02002040 for key, value in dep._vars.iteritems():
Paweł Hajdan, Jrc9353602017-08-02 17:52:08 +02002041 # Make sure there are no conflicting variables. It is fine however
2042 # to use same variable name, as long as the value is consistent.
2043 assert key not in self._vars or self._vars[key][1] == value
Michael Mossce9f17f2018-01-31 13:16:35 -08002044 self._vars[key] = (hierarchy, value)
2045 # Override explicit custom variables.
2046 for key, value in dep.custom_vars.iteritems():
2047 # Do custom_vars that don't correspond to DEPS vars ever make sense? DEPS
2048 # conditionals shouldn't be using vars that aren't also defined in the
2049 # DEPS (presubmit actually disallows this), so any new custom_var must be
2050 # unused in the DEPS, so no need to add it to the flattened output either.
2051 if key not in self._vars:
2052 continue
2053 # Don't "override" existing vars if it's actually the same value.
2054 elif self._vars[key][1] == value:
2055 continue
2056 # Anything else is overriding a default value from the DEPS.
2057 self._vars[key] = (hierarchy + ' [custom_var override]', value)
Paweł Hajdan, Jrfb022012017-07-06 18:00:08 +02002058
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002059 self._pre_deps_hooks.extend([(dep, hook) for hook in dep.pre_deps_hooks])
Edward Lemur16f4bad2018-05-16 16:53:49 -04002060 self._hooks.extend([(dep, hook) for hook in dep.deps_hooks])
Paweł Hajdan, Jradae2a62017-08-18 16:49:57 +02002061
Paweł Hajdan, Jrb0ad16e2017-08-03 15:33:21 +02002062 for sub_dep in dep.dependencies:
Edward Lemur16f4bad2018-05-16 16:53:49 -04002063 self._add_dep(sub_dep)
Paweł Hajdan, Jrb0ad16e2017-08-03 15:33:21 +02002064
Edward Lemurfbb06aa2018-06-11 20:43:06 +00002065 for d in dep.dependencies:
2066 if d.should_recurse:
2067 self._flatten_dep(d)
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002068
2069
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002070def CMDflatten(parser, args):
2071 """Flattens the solutions into a single DEPS file."""
2072 parser.add_option('--output-deps', help='Path to the output DEPS file')
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02002073 parser.add_option(
Paweł Hajdan, Jrfeb01642017-09-12 15:50:46 +02002074 '--output-deps-files',
2075 help=('Path to the output metadata about DEPS files referenced by '
2076 'recursedeps.'))
2077 parser.add_option(
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02002078 '--pin-all-deps', action='store_true',
2079 help=('Pin all deps, even if not pinned in DEPS. CAVEAT: only does so '
2080 'for checked out deps, NOT deps_os.'))
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002081 options, args = parser.parse_args(args)
2082
2083 options.nohooks = True
Paweł Hajdan, Jr5ec77132017-08-16 19:21:06 +02002084 options.process_all_deps = True
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002085 client = GClient.LoadCurrentConfig(options)
2086
2087 # Only print progress if we're writing to a file. Otherwise, progress updates
2088 # could obscure intended output.
2089 code = client.RunOnDeps('flatten', args, progress=options.output_deps)
2090 if code != 0:
2091 return code
2092
Paweł Hajdan, Jr271a1682017-07-06 20:54:30 +02002093 flattener = Flattener(client, pin_all_deps=options.pin_all_deps)
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002094
2095 if options.output_deps:
2096 with open(options.output_deps, 'w') as f:
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002097 f.write(flattener.deps_string)
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002098 else:
Paweł Hajdan, Jraaf93f42017-07-06 17:37:46 +02002099 print(flattener.deps_string)
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002100
Michael Mossfe68c912018-03-22 19:19:35 -07002101 deps_files = [{'url': d[0], 'deps_file': d[1], 'hierarchy': d[2]}
Paweł Hajdan, Jrfeb01642017-09-12 15:50:46 +02002102 for d in sorted(flattener.deps_files)]
2103 if options.output_deps_files:
2104 with open(options.output_deps_files, 'w') as f:
2105 json.dump(deps_files, f)
2106
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002107 return 0
2108
2109
Paweł Hajdan, Jr3c2aa832017-06-07 20:22:16 +02002110def _GNSettingsToLines(gn_args_file, gn_args):
2111 s = []
2112 if gn_args_file:
2113 s.extend([
2114 'gclient_gn_args_file = "%s"' % gn_args_file,
2115 'gclient_gn_args = %r' % gn_args,
2116 ])
2117 return s
2118
2119
Paweł Hajdan, Jr6014b562017-06-30 17:43:42 +02002120def _AllowedHostsToLines(allowed_hosts):
2121 """Converts |allowed_hosts| set to list of lines for output."""
2122 if not allowed_hosts:
2123 return []
2124 s = ['allowed_hosts = [']
2125 for h in sorted(allowed_hosts):
2126 s.append(' "%s",' % h)
2127 s.extend([']', ''])
2128 return s
2129
2130
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002131def _DepsToLines(deps):
2132 """Converts |deps| dict to list of lines for output."""
Paweł Hajdan, Jr5b593352017-06-29 18:37:45 +02002133 if not deps:
2134 return []
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002135 s = ['deps = {']
John Budorick0f7b2002018-01-19 15:46:17 -08002136 for _, dep in sorted(deps.iteritems()):
2137 s.extend(dep.ToLines())
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002138 s.extend(['}', ''])
2139 return s
2140
2141
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02002142def _DepsOsToLines(deps_os):
2143 """Converts |deps_os| dict to list of lines for output."""
Paweł Hajdan, Jr5b593352017-06-29 18:37:45 +02002144 if not deps_os:
2145 return []
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02002146 s = ['deps_os = {']
2147 for dep_os, os_deps in sorted(deps_os.iteritems()):
2148 s.append(' "%s": {' % dep_os)
Paweł Hajdan, Jrad30de62017-06-26 18:51:58 +02002149 for name, dep in sorted(os_deps.iteritems()):
Paweł Hajdan, Jr78ce24e2017-10-03 17:09:13 +02002150 condition_part = ([' "condition": %r,' % dep.condition]
Paweł Hajdan, Jrad30de62017-06-26 18:51:58 +02002151 if dep.condition else [])
2152 s.extend([
Michael Moss4e9b50a2018-05-23 22:35:06 -07002153 ' # %s' % dep.hierarchy(include_url=False),
Paweł Hajdan, Jrad30de62017-06-26 18:51:58 +02002154 ' "%s": {' % (name,),
Edward Lemure05f18d2018-06-08 17:36:53 +00002155 ' "url": "%s",' % (dep.url,),
Paweł Hajdan, Jrad30de62017-06-26 18:51:58 +02002156 ] + condition_part + [
2157 ' },',
2158 '',
2159 ])
Paweł Hajdan, Jrc9603f52017-06-13 22:14:24 +02002160 s.extend([' },', ''])
2161 s.extend(['}', ''])
2162 return s
2163
2164
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002165def _HooksToLines(name, hooks):
2166 """Converts |hooks| list to list of lines for output."""
Paweł Hajdan, Jr5b593352017-06-29 18:37:45 +02002167 if not hooks:
2168 return []
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002169 s = ['%s = [' % name]
2170 for dep, hook in hooks:
2171 s.extend([
Michael Moss4e9b50a2018-05-23 22:35:06 -07002172 ' # %s' % dep.hierarchy(include_url=False),
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002173 ' {',
2174 ])
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +02002175 if hook.name is not None:
2176 s.append(' "name": "%s",' % hook.name)
2177 if hook.pattern is not None:
2178 s.append(' "pattern": "%s",' % hook.pattern)
Paweł Hajdan, Jrecf53fe2017-09-29 18:28:49 +02002179 if hook.condition is not None:
Paweł Hajdan, Jr78ce24e2017-10-03 17:09:13 +02002180 s.append(' "condition": %r,' % hook.condition)
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002181 s.extend(
Paweł Hajdan, Jrc9364392017-06-14 17:11:56 +02002182 # Hooks run in the parent directory of their dep.
Paweł Hajdan, Jrad30de62017-06-26 18:51:58 +02002183 [' "cwd": "%s",' % os.path.normpath(os.path.dirname(dep.name))] +
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002184 [' "action": ['] +
Paweł Hajdan, Jrc10a4d82017-06-14 14:06:50 +02002185 [' "%s",' % arg for arg in hook.action] +
Paweł Hajdan, Jr064f6f42017-05-18 22:17:55 +02002186 [' ]', ' },', '']
2187 )
2188 s.extend([']', ''])
2189 return s
2190
2191
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +02002192def _HooksOsToLines(hooks_os):
2193 """Converts |hooks| list to list of lines for output."""
Paweł Hajdan, Jr5b593352017-06-29 18:37:45 +02002194 if not hooks_os:
2195 return []
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +02002196 s = ['hooks_os = {']
2197 for hook_os, os_hooks in hooks_os.iteritems():
Michael Moss017bcf62017-06-28 15:26:38 -07002198 s.append(' "%s": [' % hook_os)
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +02002199 for dep, hook in os_hooks:
2200 s.extend([
Michael Moss4e9b50a2018-05-23 22:35:06 -07002201 ' # %s' % dep.hierarchy(include_url=False),
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +02002202 ' {',
2203 ])
2204 if hook.name is not None:
2205 s.append(' "name": "%s",' % hook.name)
2206 if hook.pattern is not None:
2207 s.append(' "pattern": "%s",' % hook.pattern)
Paweł Hajdan, Jrecf53fe2017-09-29 18:28:49 +02002208 if hook.condition is not None:
Paweł Hajdan, Jr78ce24e2017-10-03 17:09:13 +02002209 s.append(' "condition": %r,' % hook.condition)
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +02002210 s.extend(
2211 # Hooks run in the parent directory of their dep.
2212 [' "cwd": "%s",' % os.path.normpath(os.path.dirname(dep.name))] +
2213 [' "action": ['] +
2214 [' "%s",' % arg for arg in hook.action] +
2215 [' ]', ' },', '']
2216 )
Michael Moss017bcf62017-06-28 15:26:38 -07002217 s.extend([' ],', ''])
Paweł Hajdan, Jr96e1d782017-06-27 11:12:25 +02002218 s.extend(['}', ''])
2219 return s
2220
2221
Paweł Hajdan, Jrfb022012017-07-06 18:00:08 +02002222def _VarsToLines(variables):
2223 """Converts |variables| dict to list of lines for output."""
2224 if not variables:
2225 return []
2226 s = ['vars = {']
2227 for key, tup in sorted(variables.iteritems()):
Michael Mossce9f17f2018-01-31 13:16:35 -08002228 hierarchy, value = tup
Paweł Hajdan, Jrfb022012017-07-06 18:00:08 +02002229 s.extend([
Michael Mossce9f17f2018-01-31 13:16:35 -08002230 ' # %s' % hierarchy,
Paweł Hajdan, Jrfb022012017-07-06 18:00:08 +02002231 ' "%s": %r,' % (key, value),
2232 '',
2233 ])
2234 s.extend(['}', ''])
2235 return s
2236
2237
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00002238def CMDgrep(parser, args):
2239 """Greps through git repos managed by gclient.
2240
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002241 Runs 'git grep [args...]' for each module.
2242 """
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00002243 # We can't use optparse because it will try to parse arguments sent
2244 # to git grep and throw an error. :-(
2245 if not args or re.match('(-h|--help)$', args[0]):
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002246 print(
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00002247 'Usage: gclient grep [-j <N>] git-grep-args...\n\n'
2248 'Example: "gclient grep -j10 -A2 RefCountedBase" runs\n"git grep '
2249 '-A2 RefCountedBase" on each of gclient\'s git\nrepos with up to '
2250 '10 jobs.\n\nBonus: page output by appending "|& less -FRSX" to the'
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002251 ' end of your query.',
2252 file=sys.stderr)
ilevy@chromium.orgf2ed3fb2012-11-09 23:39:49 +00002253 return 1
2254
2255 jobs_arg = ['--jobs=1']
2256 if re.match(r'(-j|--jobs=)\d+$', args[0]):
2257 jobs_arg, args = args[:1], args[1:]
2258 elif re.match(r'(-j|--jobs)$', args[0]):
2259 jobs_arg, args = args[:2], args[2:]
2260
2261 return CMDrecurse(
2262 parser,
2263 jobs_arg + ['--ignore', '--prepend-dir', '--no-progress', '--scm=git',
2264 'git', 'grep', '--null', '--color=Always'] + args)
davidbarr@chromium.org12f944e2012-03-01 02:18:31 +00002265
2266
stip@chromium.orga735da22015-04-29 23:18:20 +00002267def CMDroot(parser, args):
2268 """Outputs the solution root (or current dir if there isn't one)."""
2269 (options, args) = parser.parse_args(args)
2270 client = GClient.LoadCurrentConfig(options)
2271 if client:
2272 print(os.path.abspath(client.root_dir))
2273 else:
2274 print(os.path.abspath('.'))
2275
2276
agablea98a6cd2016-11-15 14:30:10 -08002277@subcommand.usage('[url]')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002278def CMDconfig(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002279 """Creates a .gclient file in the current directory.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00002280
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002281 This specifies the configuration for further commands. After update/sync,
2282 top-level DEPS files in each module are read to determine dependent
2283 modules to operate on as well. If optional [url] parameter is
2284 provided, then configuration is read from a specified Subversion server
2285 URL.
2286 """
szager@chromium.orge2e03202012-07-31 18:05:16 +00002287 # We do a little dance with the --gclientfile option. 'gclient config' is the
2288 # only command where it's acceptable to have both '--gclientfile' and '--spec'
2289 # arguments. So, we temporarily stash any --gclientfile parameter into
2290 # options.output_config_file until after the (gclientfile xor spec) error
2291 # check.
2292 parser.remove_option('--gclientfile')
2293 parser.add_option('--gclientfile', dest='output_config_file',
2294 help='Specify an alternate .gclient file')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002295 parser.add_option('--name',
2296 help='overrides the default name for the solution')
nsylvain@google.comefc80932011-05-31 21:27:56 +00002297 parser.add_option('--deps-file', default='DEPS',
David Benjamin105e11e2017-10-16 10:39:35 -04002298 help='overrides the default name for the DEPS file for the '
nsylvain@google.comefc80932011-05-31 21:27:56 +00002299 'main solutions and all sub-dependencies')
smutae7ea312016-07-18 11:59:41 -07002300 parser.add_option('--unmanaged', action='store_true', default=False,
cmp@chromium.orgeb2756d2011-09-20 20:17:51 +00002301 help='overrides the default behavior to make it possible '
smutae7ea312016-07-18 11:59:41 -07002302 'to have the main solution untouched by gclient '
2303 '(gclient will check out unmanaged dependencies but '
2304 'will never sync them)')
Paweł Hajdan, Jr3ba2a7c2017-10-04 19:24:46 +02002305 parser.add_option('--custom-var', action='append', dest='custom_vars',
2306 default=[],
2307 help='overrides variables; key=value syntax')
szager@chromium.orge2e03202012-07-31 18:05:16 +00002308 parser.set_defaults(config_filename=None)
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002309 (options, args) = parser.parse_args(args)
szager@chromium.orge2e03202012-07-31 18:05:16 +00002310 if options.output_config_file:
2311 setattr(options, 'config_filename', getattr(options, 'output_config_file'))
maruel@chromium.org5fc2a332010-05-26 19:37:15 +00002312 if ((options.spec and args) or len(args) > 2 or
2313 (not options.spec and not args)):
2314 parser.error('Inconsistent arguments. Use either --spec or one or 2 args')
2315
Paweł Hajdan, Jr3ba2a7c2017-10-04 19:24:46 +02002316 custom_vars = {}
2317 for arg in options.custom_vars:
2318 kv = arg.split('=', 1)
2319 if len(kv) != 2:
2320 parser.error('Invalid --custom-var argument: %r' % arg)
2321 custom_vars[kv[0]] = gclient_eval.EvaluateCondition(kv[1], {})
2322
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002323 client = GClient('.', options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002324 if options.spec:
2325 client.SetConfig(options.spec)
2326 else:
maruel@chromium.org1ab7ffc2009-06-03 17:21:37 +00002327 base_url = args[0].rstrip('/')
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00002328 if not options.name:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002329 name = base_url.split('/')[-1]
nsylvain@google.com12649ef2011-06-01 17:11:20 +00002330 if name.endswith('.git'):
2331 name = name[:-4]
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00002332 else:
2333 # specify an alternate relpath for the given URL.
2334 name = options.name
agable@chromium.orgf2214672015-10-27 21:02:48 +00002335 if not os.path.abspath(os.path.join(os.getcwd(), name)).startswith(
2336 os.getcwd()):
2337 parser.error('Do not pass a relative path for --name.')
2338 if any(x in ('..', '.', '/', '\\') for x in name.split(os.sep)):
2339 parser.error('Do not include relative path components in --name.')
2340
nsylvain@google.comefc80932011-05-31 21:27:56 +00002341 deps_file = options.deps_file
agablea98a6cd2016-11-15 14:30:10 -08002342 client.SetDefaultConfig(name, deps_file, base_url,
smutae7ea312016-07-18 11:59:41 -07002343 managed=not options.unmanaged,
Paweł Hajdan, Jr3ba2a7c2017-10-04 19:24:46 +02002344 cache_dir=options.cache_dir,
2345 custom_vars=custom_vars)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002346 client.SaveConfig()
maruel@chromium.org79692d62010-05-14 18:57:13 +00002347 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002348
2349
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002350@subcommand.epilog("""Example:
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002351 gclient pack > patch.txt
2352 generate simple patch for configured client and dependences
2353""")
2354def CMDpack(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002355 """Generates a patch which can be applied at the root of the tree.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00002356
agabled437d762016-10-17 09:35:11 -07002357 Internally, runs 'git diff' on each checked out module and
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002358 dependencies, and performs minimal postprocessing of the output. The
2359 resulting patch is printed to stdout and can be applied to a freshly
2360 checked out tree via 'patch -p0 < patchfile'.
2361 """
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002362 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2363 help='override deps for the specified (comma-separated) '
2364 'platform(s); \'all\' will process all deps_os '
2365 'references')
haitao.feng@intel.com306080c2012-05-04 13:11:29 +00002366 parser.remove_option('--jobs')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002367 (options, args) = parser.parse_args(args)
iannucci@chromium.org50395ea2013-04-04 04:47:42 +00002368 # Force jobs to 1 so the stdout is not annotated with the thread ids
haitao.feng@intel.com306080c2012-05-04 13:11:29 +00002369 options.jobs = 1
kbr@google.comab318592009-09-04 00:54:55 +00002370 client = GClient.LoadCurrentConfig(options)
2371 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002372 raise gclient_utils.Error('client not configured; see \'gclient config\'')
kbr@google.comab318592009-09-04 00:54:55 +00002373 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002374 client.PrintLocationAndContents()
kbr@google.comab318592009-09-04 00:54:55 +00002375 return client.RunOnDeps('pack', args)
2376
2377
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002378def CMDstatus(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002379 """Shows modification status for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002380 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2381 help='override deps for the specified (comma-separated) '
2382 'platform(s); \'all\' will process all deps_os '
2383 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002384 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002385 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002386 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002387 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002388 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002389 client.PrintLocationAndContents()
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002390 return client.RunOnDeps('status', args)
2391
2392
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002393@subcommand.epilog("""Examples:
maruel@chromium.org79692d62010-05-14 18:57:13 +00002394 gclient sync
2395 update files from SCM according to current configuration,
2396 *for modules which have changed since last update or sync*
2397 gclient sync --force
2398 update files from SCM according to current configuration, for
2399 all modules (useful for recovering files deleted from local copy)
2400 gclient sync --revision src@31000
2401 update src directory to r31000
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002402
2403JSON output format:
2404If the --output-json option is specified, the following document structure will
2405be emitted to the provided file. 'null' entries may occur for subprojects which
2406are present in the gclient solution, but were not processed (due to custom_deps,
2407os_deps, etc.)
2408
2409{
2410 "solutions" : {
2411 "<name>": { # <name> is the posix-normalized path to the solution.
agabled437d762016-10-17 09:35:11 -07002412 "revision": [<git id hex string>|null],
2413 "scm": ["git"|null],
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002414 }
2415 }
2416}
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002417""")
2418def CMDsync(parser, args):
2419 """Checkout/update all modules."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002420 parser.add_option('-f', '--force', action='store_true',
2421 help='force update even for unchanged modules')
2422 parser.add_option('-n', '--nohooks', action='store_true',
2423 help='don\'t run hooks after the update is complete')
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00002424 parser.add_option('-p', '--noprehooks', action='store_true',
2425 help='don\'t run pre-DEPS hooks', default=False)
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002426 parser.add_option('-r', '--revision', action='append',
2427 dest='revisions', metavar='REV', default=[],
2428 help='Enforces revision/hash for the solutions with the '
2429 'format src@rev. The src@ part is optional and can be '
Edward Lesmes53014652018-03-07 18:01:40 -05002430 'skipped. You can also specify URLs instead of paths '
2431 'and gclient will find the solution corresponding to '
2432 'the given URL. If a path is also specified, the URL '
2433 'takes precedence. -r can be used multiple times when '
2434 '.gclient has multiple solutions configured, and will '
2435 'work even if the src@ part is skipped.')
Edward Lesmesc621b212018-03-21 20:26:56 -04002436 parser.add_option('--patch-ref', action='append',
2437 dest='patch_refs', metavar='GERRIT_REF', default=[],
2438 help='Patches the given reference with the format dep@ref. '
2439 'For dep, you can specify URLs as well as paths, with '
2440 'URLs taking preference. The reference will be '
2441 'applied to the necessary path, will be rebased on '
2442 'top what the dep was synced to, and then will do a '
2443 'soft reset. Use --no-rebase-patch-ref and '
2444 '--reset-patch-ref to disable this behavior.')
maruel@chromium.org794207e2013-03-08 15:29:43 +00002445 parser.add_option('--with_branch_heads', action='store_true',
2446 help='Clone git "branch_heads" refspecs in addition to '
2447 'the default refspecs. This adds about 1/2GB to a '
2448 'full checkout. (git only)')
szager@chromium.org8d3348f2014-08-19 22:49:16 +00002449 parser.add_option('--with_tags', action='store_true',
2450 help='Clone git tags in addition to the default refspecs.')
agable2697cd12016-06-28 10:23:53 -07002451 parser.add_option('-H', '--head', action='store_true',
agablea98a6cd2016-11-15 14:30:10 -08002452 help='DEPRECATED: only made sense with safesync urls.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002453 parser.add_option('-D', '--delete_unversioned_trees', action='store_true',
steveblock@chromium.org98e69452012-02-16 16:36:43 +00002454 help='Deletes from the working copy any dependencies that '
2455 'have been removed since the last sync, as long as '
2456 'there are no local modifications. When used with '
2457 '--force, such dependencies are removed even if they '
2458 'have local modifications. When used with --reset, '
2459 'all untracked directories are removed from the '
tnagel@chromium.orga2aaa632014-02-28 21:47:27 +00002460 'working copy, excluding those which are explicitly '
steveblock@chromium.org98e69452012-02-16 16:36:43 +00002461 'ignored in the repository.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002462 parser.add_option('-R', '--reset', action='store_true',
2463 help='resets any local changes before updating (git only)')
bauerb@chromium.org2aad1b22011-07-22 12:00:41 +00002464 parser.add_option('-M', '--merge', action='store_true',
2465 help='merge upstream changes instead of trying to '
2466 'fast-forward or rebase')
dnj@chromium.org5b23e872015-02-20 21:25:57 +00002467 parser.add_option('-A', '--auto_rebase', action='store_true',
2468 help='Automatically rebase repositories against local '
2469 'checkout during update (git only).')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002470 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2471 help='override deps for the specified (comma-separated) '
2472 'platform(s); \'all\' will process all deps_os '
2473 'references')
Paweł Hajdan, Jr5ec77132017-08-16 19:21:06 +02002474 parser.add_option('--process-all-deps', action='store_true',
2475 help='Check out all deps, even for different OS-es, '
2476 'or with conditions evaluating to false')
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +00002477 parser.add_option('--upstream', action='store_true',
2478 help='Make repo state match upstream branch.')
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002479 parser.add_option('--output-json',
2480 help='Output a json document to this path containing '
2481 'summary information about the sync.')
primiano@chromium.org5439ea52014-08-06 17:18:18 +00002482 parser.add_option('--no-history', action='store_true',
2483 help='GIT ONLY - Reduces the size/time of the checkout at '
2484 'the cost of no history. Requires Git 1.9+')
hinoka@chromium.org46b87412014-05-15 00:42:05 +00002485 parser.add_option('--shallow', action='store_true',
2486 help='GIT ONLY - Do a shallow clone into the cache dir. '
2487 'Requires Git 1.9+')
e.hakkinen@samsung.come8bc1aa2015-04-08 08:00:37 +00002488 parser.add_option('--no_bootstrap', '--no-bootstrap',
2489 action='store_true',
2490 help='Don\'t bootstrap from Google Storage.')
Vadim Shtayura08049e22017-10-11 00:14:52 +00002491 parser.add_option('--ignore_locks', action='store_true',
2492 help='GIT ONLY - Ignore cache locks.')
iannucci@chromium.org30a07982016-04-07 21:35:19 +00002493 parser.add_option('--break_repo_locks', action='store_true',
2494 help='GIT ONLY - Forcibly remove repo locks (e.g. '
2495 'index.lock). This should only be used if you know for '
2496 'certain that this invocation of gclient is the only '
2497 'thing operating on the git repos (e.g. on a bot).')
Vadim Shtayura08049e22017-10-11 00:14:52 +00002498 parser.add_option('--lock_timeout', type='int', default=5000,
2499 help='GIT ONLY - Deadline (in seconds) to wait for git '
2500 'cache lock to become available. Default is %default.')
agabled437d762016-10-17 09:35:11 -07002501 # TODO(agable): Remove these when the oldest CrOS release milestone is M56.
2502 parser.add_option('-t', '--transitive', action='store_true',
2503 help='DEPRECATED: This is a no-op.')
sdefresne69b1be12016-10-18 05:48:02 -07002504 parser.add_option('-m', '--manually_grab_svn_rev', action='store_true',
agabled437d762016-10-17 09:35:11 -07002505 help='DEPRECATED: This is a no-op.')
Paweł Hajdan, Jr7c7b5592017-05-23 15:06:05 +02002506 # TODO(phajdan.jr): Remove validation options once default (crbug/570091).
Paweł Hajdan, Jr694773d2017-05-29 16:06:23 +02002507 parser.add_option('--validate-syntax', action='store_true', default=True,
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +02002508 help='Validate the .gclient and DEPS syntax')
Paweł Hajdan, Jr7c7b5592017-05-23 15:06:05 +02002509 parser.add_option('--disable-syntax-validation', action='store_false',
2510 dest='validate_syntax',
2511 help='Disable validation of .gclient and DEPS syntax.')
Edward Lesmesc621b212018-03-21 20:26:56 -04002512 parser.add_option('--no-rebase-patch-ref', action='store_false',
2513 dest='rebase_patch_ref', default=True,
2514 help='Bypass rebase of the patch ref after checkout.')
2515 parser.add_option('--no-reset-patch-ref', action='store_false',
2516 dest='reset_patch_ref', default=True,
2517 help='Bypass calling reset after patching the ref.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002518 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002519 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002520
2521 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002522 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002523
smutae7ea312016-07-18 11:59:41 -07002524 if options.revisions and options.head:
2525 # TODO(maruel): Make it a parser.error if it doesn't break any builder.
2526 print('Warning: you cannot use both --head and --revision')
2527
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002528 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002529 client.PrintLocationAndContents()
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002530 ret = client.RunOnDeps('update', args)
2531 if options.output_json:
2532 slns = {}
Edward Lemur0c911472018-06-14 19:39:52 +00002533 for d in client.subtree():
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002534 normed = d.name.replace('\\', '/').rstrip('/') + '/'
2535 slns[normed] = {
2536 'revision': d.got_revision,
2537 'scm': d.used_scm.name if d.used_scm else None,
Edward Lemur0c911472018-06-14 19:39:52 +00002538 'url': str(d.url),
iannucci@chromium.org2702bcd2013-09-24 19:10:07 +00002539 }
2540 with open(options.output_json, 'wb') as f:
2541 json.dump({'solutions': slns}, f)
2542 return ret
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002543
2544
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002545CMDupdate = CMDsync
2546
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002547
Paweł Hajdan, Jre2f9feec2017-05-09 10:04:02 +02002548def CMDvalidate(parser, args):
2549 """Validates the .gclient and DEPS syntax."""
2550 options, args = parser.parse_args(args)
2551 options.validate_syntax = True
2552 client = GClient.LoadCurrentConfig(options)
2553 rv = client.RunOnDeps('validate', args)
2554 if rv == 0:
2555 print('validate: SUCCESS')
2556 else:
2557 print('validate: FAILURE')
2558 return rv
2559
2560
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002561def CMDdiff(parser, args):
2562 """Displays local diff for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002563 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2564 help='override deps for the specified (comma-separated) '
2565 'platform(s); \'all\' will process all deps_os '
2566 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002567 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002568 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002569 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002570 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002571 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002572 client.PrintLocationAndContents()
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002573 return client.RunOnDeps('diff', args)
2574
2575
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002576def CMDrevert(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002577 """Reverts all modifications in every dependencies.
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00002578
2579 That's the nuclear option to get back to a 'clean' state. It removes anything
agabled437d762016-10-17 09:35:11 -07002580 that shows up in git status."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002581 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2582 help='override deps for the specified (comma-separated) '
2583 'platform(s); \'all\' will process all deps_os '
2584 'references')
2585 parser.add_option('-n', '--nohooks', action='store_true',
2586 help='don\'t run hooks after the revert is complete')
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00002587 parser.add_option('-p', '--noprehooks', action='store_true',
2588 help='don\'t run pre-DEPS hooks', default=False)
iannucci@chromium.orgd4fffee2013-06-28 00:35:26 +00002589 parser.add_option('--upstream', action='store_true',
2590 help='Make repo state match upstream branch.')
iannucci@chromium.orgbf525dc2016-04-07 22:00:28 +00002591 parser.add_option('--break_repo_locks', action='store_true',
2592 help='GIT ONLY - Forcibly remove repo locks (e.g. '
2593 'index.lock). This should only be used if you know for '
2594 'certain that this invocation of gclient is the only '
2595 'thing operating on the git repos (e.g. on a bot).')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002596 (options, args) = parser.parse_args(args)
2597 # --force is implied.
2598 options.force = True
steveblock@chromium.org98e69452012-02-16 16:36:43 +00002599 options.reset = False
2600 options.delete_unversioned_trees = False
agablec903d732016-07-26 09:07:24 -07002601 options.merge = False
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002602 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002603 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002604 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002605 return client.RunOnDeps('revert', args)
2606
2607
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002608def CMDrunhooks(parser, args):
2609 """Runs hooks for files that have been modified in the local working copy."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002610 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2611 help='override deps for the specified (comma-separated) '
2612 'platform(s); \'all\' will process all deps_os '
2613 'references')
2614 parser.add_option('-f', '--force', action='store_true', default=True,
2615 help='Deprecated. No effect.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002616 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002617 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002618 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002619 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002620 if options.verbose:
sergiyb@chromium.orgfa2707e2016-03-12 00:40:56 +00002621 client.PrintLocationAndContents()
maruel@chromium.org5df6a462009-08-28 18:52:26 +00002622 options.force = True
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002623 options.nohooks = False
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002624 return client.RunOnDeps('runhooks', args)
2625
2626
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002627def CMDrevinfo(parser, args):
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002628 """Outputs revision info mapping for the client and its dependencies.
maruel@chromium.org9eda4112010-06-11 18:56:10 +00002629
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002630 This allows the capture of an overall 'revision' for the source tree that
maruel@chromium.org9eda4112010-06-11 18:56:10 +00002631 can be used to reproduce the same tree in the future. It is only useful for
agabled437d762016-10-17 09:35:11 -07002632 'unpinned dependencies', i.e. DEPS/deps references without a git hash.
2633 A git branch name isn't 'pinned' since the actual commit can change.
maruel@chromium.org9eda4112010-06-11 18:56:10 +00002634 """
2635 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
2636 help='override deps for the specified (comma-separated) '
2637 'platform(s); \'all\' will process all deps_os '
2638 'references')
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00002639 parser.add_option('-a', '--actual', action='store_true',
2640 help='gets the actual checked out revisions instead of the '
2641 'ones specified in the DEPS and .gclient files')
maruel@chromium.org9eda4112010-06-11 18:56:10 +00002642 parser.add_option('-s', '--snapshot', action='store_true',
2643 help='creates a snapshot .gclient file of the current '
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00002644 'version of all repositories to reproduce the tree, '
2645 'implies -a')
Edward Lesmesbb16e332018-03-30 17:54:51 -04002646 parser.add_option('--filter', action='append', dest='filter',
Edward Lesmesdaa76d22018-03-06 14:56:57 -05002647 help='Display revision information only for the specified '
Edward Lesmesbb16e332018-03-30 17:54:51 -04002648 'dependencies (filtered by URL or path).')
Edward Lesmesc2960242018-03-06 20:50:15 -05002649 parser.add_option('--output-json',
2650 help='Output a json document to this path containing '
2651 'information about the revisions.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002652 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00002653 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002654 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00002655 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002656 client.PrintRevInfo()
maruel@chromium.org79692d62010-05-14 18:57:13 +00002657 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002658
2659
Edward Lesmes411041f2018-04-05 20:12:55 -04002660def CMDgetdep(parser, args):
2661 """Gets revision information and variable values from a DEPS file."""
2662 parser.add_option('--var', action='append',
2663 dest='vars', metavar='VAR', default=[],
2664 help='Gets the value of a given variable.')
2665 parser.add_option('-r', '--revision', action='append',
2666 dest='revisions', metavar='DEP', default=[],
2667 help='Gets the revision/version for the given dependency. '
2668 'If it is a git dependency, dep must be a path. If it '
2669 'is a CIPD dependency, dep must be of the form '
2670 'path:package.')
2671 parser.add_option('--deps-file', default='DEPS',
2672 # TODO(ehmaldonado): Try to find the DEPS file pointed by
2673 # .gclient first.
2674 help='The DEPS file to be edited. Defaults to the DEPS '
2675 'file in the current directory.')
2676 (options, args) = parser.parse_args(args)
2677
2678 if not os.path.isfile(options.deps_file):
2679 raise gclient_utils.Error(
2680 'DEPS file %s does not exist.' % options.deps_file)
2681 with open(options.deps_file) as f:
2682 contents = f.read()
Edward Lemure05f18d2018-06-08 17:36:53 +00002683 local_scope = gclient_eval.Exec(contents, options.deps_file)
Edward Lesmes411041f2018-04-05 20:12:55 -04002684
2685 for var in options.vars:
2686 print(gclient_eval.GetVar(local_scope, var))
2687
2688 for name in options.revisions:
2689 if ':' in name:
2690 name, _, package = name.partition(':')
2691 if not name or not package:
2692 parser.error(
2693 'Wrong CIPD format: %s:%s should be of the form path:pkg.'
2694 % (name, package))
2695 print(gclient_eval.GetCIPD(local_scope, name, package))
2696 else:
2697 print(gclient_eval.GetRevision(local_scope, name))
2698
2699
Edward Lesmes6f64a052018-03-20 17:35:49 -04002700def CMDsetdep(parser, args):
Edward Lesmes0ecf6d62018-04-05 18:28:55 -04002701 """Modifies dependency revisions and variable values in a DEPS file"""
Edward Lesmes6f64a052018-03-20 17:35:49 -04002702 parser.add_option('--var', action='append',
2703 dest='vars', metavar='VAR=VAL', default=[],
2704 help='Sets a variable to the given value with the format '
2705 'name=value.')
2706 parser.add_option('-r', '--revision', action='append',
2707 dest='revisions', metavar='DEP@REV', default=[],
2708 help='Sets the revision/version for the dependency with '
2709 'the format dep@rev. If it is a git dependency, dep '
2710 'must be a path and rev must be a git hash or '
2711 'reference (e.g. src/dep@deadbeef). If it is a CIPD '
2712 'dependency, dep must be of the form path:package and '
2713 'rev must be the package version '
2714 '(e.g. src/pkg:chromium/pkg@2.1-cr0).')
2715 parser.add_option('--deps-file', default='DEPS',
2716 # TODO(ehmaldonado): Try to find the DEPS file pointed by
2717 # .gclient first.
2718 help='The DEPS file to be edited. Defaults to the DEPS '
2719 'file in the current directory.')
2720 (options, args) = parser.parse_args(args)
Edward Lesmes0ecf6d62018-04-05 18:28:55 -04002721 if args:
2722 parser.error('Unused arguments: "%s"' % '" "'.join(args))
2723 if not options.revisions and not options.vars:
2724 parser.error(
2725 'You must specify at least one variable or revision to modify.')
Edward Lesmes6f64a052018-03-20 17:35:49 -04002726
Edward Lesmes6f64a052018-03-20 17:35:49 -04002727 if not os.path.isfile(options.deps_file):
2728 raise gclient_utils.Error(
2729 'DEPS file %s does not exist.' % options.deps_file)
2730 with open(options.deps_file) as f:
2731 contents = f.read()
Edward Lemure05f18d2018-06-08 17:36:53 +00002732 local_scope = gclient_eval.Exec(contents, options.deps_file)
Edward Lesmes6f64a052018-03-20 17:35:49 -04002733
2734 for var in options.vars:
2735 name, _, value = var.partition('=')
2736 if not name or not value:
Edward Lesmes0ecf6d62018-04-05 18:28:55 -04002737 parser.error(
Edward Lesmes6f64a052018-03-20 17:35:49 -04002738 'Wrong var format: %s should be of the form name=value.' % var)
Edward Lesmes3d993812018-04-02 12:52:49 -04002739 if name in local_scope['vars']:
2740 gclient_eval.SetVar(local_scope, name, value)
2741 else:
2742 gclient_eval.AddVar(local_scope, name, value)
Edward Lesmes6f64a052018-03-20 17:35:49 -04002743
2744 for revision in options.revisions:
2745 name, _, value = revision.partition('@')
2746 if not name or not value:
Edward Lesmes0ecf6d62018-04-05 18:28:55 -04002747 parser.error(
Edward Lesmes6f64a052018-03-20 17:35:49 -04002748 'Wrong dep format: %s should be of the form dep@rev.' % revision)
2749 if ':' in name:
2750 name, _, package = name.partition(':')
2751 if not name or not package:
Edward Lesmes0ecf6d62018-04-05 18:28:55 -04002752 parser.error(
Edward Lesmes6f64a052018-03-20 17:35:49 -04002753 'Wrong CIPD format: %s:%s should be of the form path:pkg@version.'
2754 % (name, package))
2755 gclient_eval.SetCIPD(local_scope, name, package, value)
2756 else:
Edward Lesmes9f531292018-03-20 21:27:15 -04002757 gclient_eval.SetRevision(local_scope, name, value)
Edward Lesmes6f64a052018-03-20 17:35:49 -04002758
2759 with open(options.deps_file, 'w') as f:
2760 f.write(gclient_eval.RenderDEPSFile(local_scope))
2761
2762
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00002763def CMDverify(parser, args):
2764 """Verifies the DEPS file deps are only from allowed_hosts."""
2765 (options, args) = parser.parse_args(args)
2766 client = GClient.LoadCurrentConfig(options)
2767 if not client:
2768 raise gclient_utils.Error('client not configured; see \'gclient config\'')
2769 client.RunOnDeps(None, [])
2770 # Look at each first-level dependency of this gclient only.
2771 for dep in client.dependencies:
2772 bad_deps = dep.findDepsFromNotAllowedHosts()
2773 if not bad_deps:
2774 continue
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002775 print("There are deps from not allowed hosts in file %s" % dep.deps_file)
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00002776 for bad_dep in bad_deps:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002777 print("\t%s at %s" % (bad_dep.name, bad_dep.url))
2778 print("allowed_hosts:", ', '.join(dep.allowed_hosts))
tandrii@chromium.orgc137c1a2014-09-23 11:49:52 +00002779 sys.stdout.flush()
2780 raise gclient_utils.Error(
2781 'dependencies from disallowed hosts; check your DEPS file.')
2782 return 0
2783
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002784class OptionParser(optparse.OptionParser):
szager@chromium.orge2e03202012-07-31 18:05:16 +00002785 gclientfile_default = os.environ.get('GCLIENT_FILE', '.gclient')
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002786
2787 def __init__(self, **kwargs):
2788 optparse.OptionParser.__init__(
2789 self, version='%prog ' + __version__, **kwargs)
2790
2791 # Some arm boards have issues with parallel sync.
2792 if platform.machine().startswith('arm'):
2793 jobs = 1
2794 else:
2795 jobs = max(8, gclient_utils.NumLocalCpus())
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002796
2797 self.add_option(
2798 '-j', '--jobs', default=jobs, type='int',
2799 help='Specify how many SCM commands can run in parallel; defaults to '
tnagel@chromium.orga2aaa632014-02-28 21:47:27 +00002800 '%default on this machine')
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002801 self.add_option(
2802 '-v', '--verbose', action='count', default=0,
2803 help='Produces additional output for diagnostics. Can be used up to '
2804 'three times for more logging info.')
2805 self.add_option(
2806 '--gclientfile', dest='config_filename',
2807 help='Specify an alternate %s file' % self.gclientfile_default)
2808 self.add_option(
2809 '--spec',
2810 help='create a gclient file containing the provided string. Due to '
2811 'Cygwin/Python brokenness, it can\'t contain any newlines.')
2812 self.add_option(
Aleksandr Derbenev9e8fb0e2017-08-01 20:18:31 +03002813 '--cache-dir',
2814 help='(git only) Cache all git repos into this dir and do '
2815 'shared clones from the cache, instead of cloning '
2816 'directly from the remote. (experimental)',
2817 default=os.environ.get('GCLIENT_CACHE_DIR'))
2818 self.add_option(
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002819 '--no-nag-max', default=False, action='store_true',
scottmg@chromium.orgf547c802013-09-27 17:55:26 +00002820 help='Ignored for backwards compatibility.')
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002821
2822 def parse_args(self, args=None, values=None):
2823 """Integrates standard options processing."""
2824 options, args = optparse.OptionParser.parse_args(self, args, values)
2825 levels = [logging.ERROR, logging.WARNING, logging.INFO, logging.DEBUG]
2826 logging.basicConfig(
2827 level=levels[min(options.verbose, len(levels) - 1)],
maruel@chromium.org0895b752011-08-26 20:40:33 +00002828 format='%(module)s(%(lineno)d) %(funcName)s:%(message)s')
szager@chromium.orge2e03202012-07-31 18:05:16 +00002829 if options.config_filename and options.spec:
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002830 self.error('Cannot specifiy both --gclientfile and --spec')
rdsmith@chromium.orgd9591f02014-02-05 19:28:20 +00002831 if (options.config_filename and
2832 options.config_filename != os.path.basename(options.config_filename)):
2833 self.error('--gclientfile target must be a filename, not a path')
szager@chromium.orge2e03202012-07-31 18:05:16 +00002834 if not options.config_filename:
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002835 options.config_filename = self.gclientfile_default
maruel@chromium.org0895b752011-08-26 20:40:33 +00002836 options.entries_filename = options.config_filename + '_entries'
2837 if options.jobs < 1:
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002838 self.error('--jobs must be 1 or higher')
maruel@chromium.org0895b752011-08-26 20:40:33 +00002839
2840 # These hacks need to die.
2841 if not hasattr(options, 'revisions'):
2842 # GClient.RunOnDeps expects it even if not applicable.
2843 options.revisions = []
smutae7ea312016-07-18 11:59:41 -07002844 if not hasattr(options, 'head'):
2845 options.head = None
maruel@chromium.org0895b752011-08-26 20:40:33 +00002846 if not hasattr(options, 'nohooks'):
2847 options.nohooks = True
borenet@google.com2d1ee9e2013-10-15 08:13:16 +00002848 if not hasattr(options, 'noprehooks'):
2849 options.noprehooks = True
maruel@chromium.org0895b752011-08-26 20:40:33 +00002850 if not hasattr(options, 'deps_os'):
2851 options.deps_os = None
maruel@chromium.org0895b752011-08-26 20:40:33 +00002852 if not hasattr(options, 'force'):
2853 options.force = None
2854 return (options, args)
iannucci@chromium.orgd9c1b202013-07-24 23:52:11 +00002855
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002856
2857def disable_buffering():
2858 # Make stdout auto-flush so buildbot doesn't kill us during lengthy
2859 # operations. Python as a strong tendency to buffer sys.stdout.
2860 sys.stdout = gclient_utils.MakeFileAutoFlush(sys.stdout)
2861 # Make stdout annotated with the thread ids.
2862 sys.stdout = gclient_utils.MakeFileAnnotated(sys.stdout)
maruel@chromium.org0895b752011-08-26 20:40:33 +00002863
2864
sbc@chromium.org013731e2015-02-26 18:28:43 +00002865def main(argv):
maruel@chromium.org5ca27692010-05-26 19:32:41 +00002866 """Doesn't parse the arguments here, just find the right subcommand to
2867 execute."""
maruel@chromium.org82798cb2012-02-23 18:16:12 +00002868 if sys.hexversion < 0x02060000:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002869 print(
maruel@chromium.org82798cb2012-02-23 18:16:12 +00002870 '\nYour python version %s is unsupported, please upgrade.\n' %
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002871 sys.version.split(' ', 1)[0],
2872 file=sys.stderr)
maruel@chromium.org82798cb2012-02-23 18:16:12 +00002873 return 2
bcwhite@chromium.org6683ab42013-02-11 16:13:47 +00002874 if not sys.executable:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002875 print(
2876 '\nPython cannot find the location of it\'s own executable.\n',
2877 file=sys.stderr)
bcwhite@chromium.org6683ab42013-02-11 16:13:47 +00002878 return 2
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002879 fix_encoding.fix_encoding()
2880 disable_buffering()
iannucci@chromium.org596cd5c2016-04-04 21:34:39 +00002881 setup_color.init()
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002882 dispatcher = subcommand.CommandDispatcher(__name__)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00002883 try:
maruel@chromium.org39c0b222013-08-17 16:57:01 +00002884 return dispatcher.execute(OptionParser(), argv)
xusydoc@chromium.org2fd6c3f2013-05-03 21:57:55 +00002885 except KeyboardInterrupt:
2886 gclient_utils.GClientChildren.KillAllRemainingChildren()
2887 raise
vapier@chromium.orga81a56e2015-11-11 07:56:13 +00002888 except (gclient_utils.Error, subprocess2.CalledProcessError) as e:
vapier@chromium.orgbb79bea2015-11-11 07:30:23 +00002889 print('Error: %s' % str(e), file=sys.stderr)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00002890 return 1
borenet@google.com6a9b1682014-03-24 18:35:23 +00002891 finally:
2892 gclient_utils.PrintWarnings()
sbc@chromium.org013731e2015-02-26 18:28:43 +00002893 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002894
2895
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00002896if '__main__' == __name__:
sbc@chromium.org013731e2015-02-26 18:28:43 +00002897 try:
2898 sys.exit(main(sys.argv[1:]))
2899 except KeyboardInterrupt:
2900 sys.stderr.write('interrupted\n')
2901 sys.exit(1)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00002902
2903# vim: ts=2:sw=2:tw=80:et: