blob: 5813029865cb85dbd9a7acd99ba20c6199dd38de [file] [log] [blame]
[email protected]fb2b8eb2009-04-23 21:03:421#!/usr/bin/python
[email protected]ba551772010-02-03 18:21:422# Copyright (c) 2010 The Chromium Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
[email protected]fb2b8eb2009-04-23 21:03:425
6"""A wrapper script to manage a set of client modules in different SCM.
7
8This script is intended to be used to help basic management of client
[email protected]d6504212010-01-13 17:34:319program sources residing in one or more Subversion modules and Git
10repositories, along with other modules it depends on, also in Subversion or Git,
11but possibly on multiple respositories, making a wrapper system apparently
12necessary.
[email protected]fb2b8eb2009-04-23 21:03:4213
14Files
15 .gclient : Current client configuration, written by 'config' command.
16 Format is a Python script defining 'solutions', a list whose
17 entries each are maps binding the strings "name" and "url"
18 to strings specifying the name and location of the client
19 module, as well as "custom_deps" to a map similar to the DEPS
20 file below.
21 .gclient_entries : A cache constructed by 'update' command. Format is a
22 Python script defining 'entries', a list of the names
23 of all modules in the client
24 <module>/DEPS : Python script defining var 'deps' as a map from each requisite
25 submodule name to a URL where it can be found (via one SCM)
26
27Hooks
28 .gclient and DEPS files may optionally contain a list named "hooks" to
29 allow custom actions to be performed based on files that have changed in the
[email protected]67820ef2009-07-27 17:23:0030 working copy as a result of a "sync"/"update" or "revert" operation. This
31 could be prevented by using --nohooks (hooks run by default). Hooks can also
[email protected]5df6a462009-08-28 18:52:2632 be forced to run with the "runhooks" operation. If "sync" is run with
[email protected]fb2b8eb2009-04-23 21:03:4233 --force, all known hooks will run regardless of the state of the working
34 copy.
35
36 Each item in a "hooks" list is a dict, containing these two keys:
37 "pattern" The associated value is a string containing a regular
38 expression. When a file whose pathname matches the expression
39 is checked out, updated, or reverted, the hook's "action" will
40 run.
41 "action" A list describing a command to run along with its arguments, if
42 any. An action command will run at most one time per gclient
43 invocation, regardless of how many files matched the pattern.
44 The action is executed in the same directory as the .gclient
45 file. If the first item in the list is the string "python",
46 the current Python interpreter (sys.executable) will be used
[email protected]71b40682009-07-31 23:40:0947 to run the command. If the list contains string "$matching_files"
48 it will be removed from the list and the list will be extended
49 by the list of matching files.
[email protected]fb2b8eb2009-04-23 21:03:4250
51 Example:
52 hooks = [
53 { "pattern": "\\.(gif|jpe?g|pr0n|png)$",
54 "action": ["python", "image_indexer.py", "--all"]},
55 ]
56"""
57
[email protected]5ca27692010-05-26 19:32:4158__version__ = "0.4"
[email protected]fb2b8eb2009-04-23 21:03:4259
60import errno
[email protected]754960e2009-09-21 12:31:0561import logging
[email protected]fb2b8eb2009-04-23 21:03:4262import optparse
63import os
[email protected]2e38de72009-09-28 17:04:4764import pprint
[email protected]fb2b8eb2009-04-23 21:03:4265import re
[email protected]fb2b8eb2009-04-23 21:03:4266import sys
[email protected]fb2b8eb2009-04-23 21:03:4267import urlparse
[email protected]fb2b8eb2009-04-23 21:03:4268import urllib
69
[email protected]ada4c652009-12-03 15:32:0170import breakpad
71
[email protected]5f3eee32009-09-17 00:34:3072import gclient_scm
73import gclient_utils
[email protected]1f7a3d12010-02-04 15:11:5074from third_party.repo.progress import Progress
[email protected]fb2b8eb2009-04-23 21:03:4275
[email protected]fb2b8eb2009-04-23 21:03:4276
[email protected]1f7d1182010-05-17 18:17:3877def attr(attr, data):
78 """Sets an attribute on a function."""
79 def hook(fn):
80 setattr(fn, attr, data)
81 return fn
82 return hook
[email protected]e3da35f2010-03-09 21:40:4583
[email protected]fb2b8eb2009-04-23 21:03:4284
[email protected]fb2b8eb2009-04-23 21:03:4285## GClient implementation.
86
87
88class GClient(object):
89 """Object that represent a gclient checkout."""
90
91 supported_commands = [
[email protected]ab318592009-09-04 00:54:5592 'cleanup', 'diff', 'export', 'pack', 'revert', 'status', 'update',
93 'runhooks'
[email protected]fb2b8eb2009-04-23 21:03:4294 ]
95
[email protected]491c04b2010-05-17 18:17:4496 deps_os_choices = {
97 "win32": "win",
98 "win": "win",
99 "cygwin": "win",
100 "darwin": "mac",
101 "mac": "mac",
102 "unix": "unix",
103 "linux": "unix",
104 "linux2": "unix",
105 }
106
[email protected]1f7d1182010-05-17 18:17:38107 DEPS_FILE = 'DEPS'
108
109 DEFAULT_CLIENT_FILE_TEXT = ("""\
110solutions = [
111 { "name" : "%(solution_name)s",
112 "url" : "%(solution_url)s",
113 "custom_deps" : {
114 },
115 "safesync_url": "%(safesync_url)s"
116 },
117]
118""")
119
120 DEFAULT_SNAPSHOT_SOLUTION_TEXT = ("""\
121 { "name" : "%(solution_name)s",
122 "url" : "%(solution_url)s",
123 "custom_deps" : {
124 %(solution_deps)s,
125 },
126 "safesync_url": "%(safesync_url)s"
127 },
128""")
129
130 DEFAULT_SNAPSHOT_FILE_TEXT = ("""\
131# Snapshot generated with gclient revinfo --snapshot
132solutions = [
133%(solution_list)s
134]
135""")
136
[email protected]fb2b8eb2009-04-23 21:03:42137 def __init__(self, root_dir, options):
138 self._root_dir = root_dir
139 self._options = options
140 self._config_content = None
141 self._config_dict = {}
142 self._deps_hooks = []
143
144 def SetConfig(self, content):
145 self._config_dict = {}
146 self._config_content = content
[email protected]df0032c2009-05-29 10:43:56147 try:
148 exec(content, self._config_dict)
149 except SyntaxError, e:
150 try:
[email protected]e3608df2009-11-10 20:22:57151 __pychecker__ = 'no-objattrs'
[email protected]df0032c2009-05-29 10:43:56152 # Try to construct a human readable error message
153 error_message = [
154 'There is a syntax error in your configuration file.',
155 'Line #%s, character %s:' % (e.lineno, e.offset),
156 '"%s"' % re.sub(r'[\r\n]*$', '', e.text) ]
157 except:
158 # Something went wrong, re-raise the original exception
159 raise e
160 else:
161 # Raise a new exception with the human readable message:
[email protected]e3608df2009-11-10 20:22:57162 raise gclient_utils.Error('\n'.join(error_message))
[email protected]fb2b8eb2009-04-23 21:03:42163
164 def SaveConfig(self):
[email protected]e3608df2009-11-10 20:22:57165 gclient_utils.FileWrite(os.path.join(self._root_dir,
166 self._options.config_filename),
167 self._config_content)
[email protected]fb2b8eb2009-04-23 21:03:42168
169 def _LoadConfig(self):
[email protected]e3608df2009-11-10 20:22:57170 client_source = gclient_utils.FileRead(
171 os.path.join(self._root_dir, self._options.config_filename))
[email protected]fb2b8eb2009-04-23 21:03:42172 self.SetConfig(client_source)
173
174 def ConfigContent(self):
175 return self._config_content
176
177 def GetVar(self, key, default=None):
178 return self._config_dict.get(key, default)
179
180 @staticmethod
181 def LoadCurrentConfig(options, from_dir=None):
182 """Searches for and loads a .gclient file relative to the current working
183 dir.
184
185 Returns:
186 A dict representing the contents of the .gclient file or an empty dict if
187 the .gclient file doesn't exist.
188 """
189 if not from_dir:
190 from_dir = os.curdir
191 path = os.path.realpath(from_dir)
[email protected]0329e672009-05-13 18:41:04192 while not os.path.exists(os.path.join(path, options.config_filename)):
[email protected]55e724e2010-03-11 19:36:49193 split_path = os.path.split(path)
194 if not split_path[1]:
[email protected]fb2b8eb2009-04-23 21:03:42195 return None
[email protected]55e724e2010-03-11 19:36:49196 path = split_path[0]
[email protected]2806acc2009-05-15 12:33:34197 client = GClient(path, options)
[email protected]fb2b8eb2009-04-23 21:03:42198 client._LoadConfig()
199 return client
200
201 def SetDefaultConfig(self, solution_name, solution_url, safesync_url):
[email protected]1f7d1182010-05-17 18:17:38202 self.SetConfig(self.DEFAULT_CLIENT_FILE_TEXT % {
[email protected]df2d5902009-09-11 22:16:21203 'solution_name': solution_name,
204 'solution_url': solution_url,
205 'safesync_url' : safesync_url,
206 })
[email protected]fb2b8eb2009-04-23 21:03:42207
208 def _SaveEntries(self, entries):
209 """Creates a .gclient_entries file to record the list of unique checkouts.
210
211 The .gclient_entries file lives in the same directory as .gclient.
212
213 Args:
214 entries: A sequence of solution names.
215 """
[email protected]e41f6822010-04-08 16:37:06216 # Sometimes pprint.pformat will use {', sometimes it'll use { ' ... It
217 # makes testing a bit too fun.
218 result = pprint.pformat(entries, 2)
219 if result.startswith('{\''):
[email protected]1edec4d2010-04-08 17:17:06220 result = '{ \'' + result[2:]
[email protected]e41f6822010-04-08 16:37:06221 text = "entries = \\\n" + result + '\n'
[email protected]2e38de72009-09-28 17:04:47222 file_path = os.path.join(self._root_dir, self._options.entries_filename)
[email protected]e3608df2009-11-10 20:22:57223 gclient_utils.FileWrite(file_path, text)
[email protected]fb2b8eb2009-04-23 21:03:42224
225 def _ReadEntries(self):
226 """Read the .gclient_entries file for the given client.
227
228 Args:
229 client: The client for which the entries file should be read.
230
231 Returns:
232 A sequence of solution names, which will be empty if there is the
233 entries file hasn't been created yet.
234 """
235 scope = {}
236 filename = os.path.join(self._root_dir, self._options.entries_filename)
[email protected]0329e672009-05-13 18:41:04237 if not os.path.exists(filename):
[email protected]fb2b8eb2009-04-23 21:03:42238 return []
[email protected]e3608df2009-11-10 20:22:57239 exec(gclient_utils.FileRead(filename), scope)
[email protected]fb2b8eb2009-04-23 21:03:42240 return scope["entries"]
241
242 class FromImpl:
243 """Used to implement the From syntax."""
244
[email protected]30ef9ae2010-04-09 02:18:05245 def __init__(self, module_name, sub_target_name=None):
246 """module_name is the dep module we want to include from. It can also be
247 the name of a subdirectory to include from.
248
249 sub_target_name is an optional parameter if the module name in the other
250 DEPS file is different. E.g., you might want to map src/net to net."""
[email protected]fb2b8eb2009-04-23 21:03:42251 self.module_name = module_name
[email protected]30ef9ae2010-04-09 02:18:05252 self.sub_target_name = sub_target_name
[email protected]fb2b8eb2009-04-23 21:03:42253
254 def __str__(self):
[email protected]30ef9ae2010-04-09 02:18:05255 return 'From(%s, %s)' % (repr(self.module_name),
256 repr(self.sub_target_name))
257
258 def GetUrl(self, target_name, sub_deps_base_url, root_dir, sub_deps):
259 """Resolve the URL for this From entry."""
260 sub_deps_target_name = target_name
261 if self.sub_target_name:
262 sub_deps_target_name = self.sub_target_name
263 url = sub_deps[sub_deps_target_name]
264 if url.startswith('/'):
265 # If it's a relative URL, we need to resolve the URL relative to the
266 # sub deps base URL.
267 if not isinstance(sub_deps_base_url, basestring):
268 sub_deps_base_url = sub_deps_base_url.GetPath()
269 scm = gclient_scm.CreateSCM(sub_deps_base_url, root_dir,
270 None)
271 url = scm.FullUrlForRelativeUrl(url)
272 return url
[email protected]fb2b8eb2009-04-23 21:03:42273
[email protected]4b5b1772010-04-08 01:52:56274 class FileImpl:
275 """Used to implement the File('') syntax which lets you sync a single file
276 from an SVN repo."""
277
278 def __init__(self, file_location):
279 self.file_location = file_location
280
281 def __str__(self):
282 return 'File("%s")' % self.file_location
283
284 def GetPath(self):
285 return os.path.split(self.file_location)[0]
286
287 def GetFilename(self):
288 rev_tokens = self.file_location.split('@')
289 return os.path.split(rev_tokens[0])[1]
290
291 def GetRevision(self):
292 rev_tokens = self.file_location.split('@')
293 if len(rev_tokens) > 1:
294 return rev_tokens[1]
295 return None
296
[email protected]fb2b8eb2009-04-23 21:03:42297 class _VarImpl:
298 def __init__(self, custom_vars, local_scope):
299 self._custom_vars = custom_vars
300 self._local_scope = local_scope
301
302 def Lookup(self, var_name):
303 """Implements the Var syntax."""
304 if var_name in self._custom_vars:
305 return self._custom_vars[var_name]
306 elif var_name in self._local_scope.get("vars", {}):
307 return self._local_scope["vars"][var_name]
[email protected]e3608df2009-11-10 20:22:57308 raise gclient_utils.Error("Var is not defined: %s" % var_name)
[email protected]fb2b8eb2009-04-23 21:03:42309
310 def _ParseSolutionDeps(self, solution_name, solution_deps_content,
[email protected]30ef9ae2010-04-09 02:18:05311 custom_vars, parse_hooks):
[email protected]fb2b8eb2009-04-23 21:03:42312 """Parses the DEPS file for the specified solution.
313
314 Args:
315 solution_name: The name of the solution to query.
316 solution_deps_content: Content of the DEPS file for the solution
317 custom_vars: A dict of vars to override any vars defined in the DEPS file.
318
319 Returns:
320 A dict mapping module names (as relative paths) to URLs or an empty
321 dict if the solution does not have a DEPS file.
322 """
323 # Skip empty
324 if not solution_deps_content:
325 return {}
326 # Eval the content
327 local_scope = {}
328 var = self._VarImpl(custom_vars, local_scope)
[email protected]4b5b1772010-04-08 01:52:56329 global_scope = {
330 "File": self.FileImpl,
331 "From": self.FromImpl,
332 "Var": var.Lookup,
333 "deps_os": {},
334 }
[email protected]fb2b8eb2009-04-23 21:03:42335 exec(solution_deps_content, global_scope, local_scope)
336 deps = local_scope.get("deps", {})
337
338 # load os specific dependencies if defined. these dependencies may
339 # override or extend the values defined by the 'deps' member.
340 if "deps_os" in local_scope:
[email protected]fb2b8eb2009-04-23 21:03:42341 if self._options.deps_os is not None:
342 deps_to_include = self._options.deps_os.split(",")
343 if "all" in deps_to_include:
[email protected]491c04b2010-05-17 18:17:44344 deps_to_include = list(set(self.deps_os_choices.itervalues()))
[email protected]fb2b8eb2009-04-23 21:03:42345 else:
[email protected]491c04b2010-05-17 18:17:44346 deps_to_include = [self.deps_os_choices.get(sys.platform, "unix")]
[email protected]fb2b8eb2009-04-23 21:03:42347
348 deps_to_include = set(deps_to_include)
349 for deps_os_key in deps_to_include:
350 os_deps = local_scope["deps_os"].get(deps_os_key, {})
351 if len(deps_to_include) > 1:
352 # Ignore any overrides when including deps for more than one
353 # platform, so we collect the broadest set of dependencies available.
354 # We may end up with the wrong revision of something for our
355 # platform, but this is the best we can do.
356 deps.update([x for x in os_deps.items() if not x[0] in deps])
357 else:
358 deps.update(os_deps)
359
[email protected]30ef9ae2010-04-09 02:18:05360 if 'hooks' in local_scope and parse_hooks:
[email protected]fb2b8eb2009-04-23 21:03:42361 self._deps_hooks.extend(local_scope['hooks'])
362
363 # If use_relative_paths is set in the DEPS file, regenerate
364 # the dictionary using paths relative to the directory containing
365 # the DEPS file.
366 if local_scope.get('use_relative_paths'):
367 rel_deps = {}
368 for d, url in deps.items():
369 # normpath is required to allow DEPS to use .. in their
370 # dependency local path.
371 rel_deps[os.path.normpath(os.path.join(solution_name, d))] = url
372 return rel_deps
373 else:
374 return deps
375
376 def _ParseAllDeps(self, solution_urls, solution_deps_content):
377 """Parse the complete list of dependencies for the client.
378
379 Args:
380 solution_urls: A dict mapping module names (as relative paths) to URLs
381 corresponding to the solutions specified by the client. This parameter
382 is passed as an optimization.
383 solution_deps_content: A dict mapping module names to the content
384 of their DEPS files
385
386 Returns:
387 A dict mapping module names (as relative paths) to URLs corresponding
388 to the entire set of dependencies to checkout for the given client.
389
390 Raises:
391 Error: If a dependency conflicts with another dependency or of a solution.
392 """
393 deps = {}
394 for solution in self.GetVar("solutions"):
395 custom_vars = solution.get("custom_vars", {})
396 solution_deps = self._ParseSolutionDeps(
397 solution["name"],
398 solution_deps_content[solution["name"]],
[email protected]30ef9ae2010-04-09 02:18:05399 custom_vars,
400 True)
[email protected]fb2b8eb2009-04-23 21:03:42401
402 # If a line is in custom_deps, but not in the solution, we want to append
403 # this line to the solution.
404 if "custom_deps" in solution:
405 for d in solution["custom_deps"]:
406 if d not in solution_deps:
407 solution_deps[d] = solution["custom_deps"][d]
408
409 for d in solution_deps:
410 if "custom_deps" in solution and d in solution["custom_deps"]:
411 # Dependency is overriden.
412 url = solution["custom_deps"][d]
413 if url is None:
414 continue
415 else:
416 url = solution_deps[d]
417 # if we have a From reference dependent on another solution, then
418 # just skip the From reference. When we pull deps for the solution,
419 # we will take care of this dependency.
420 #
421 # If multiple solutions all have the same From reference, then we
422 # should only add one to our list of dependencies.
[email protected]4b5b1772010-04-08 01:52:56423 if isinstance(url, self.FromImpl):
[email protected]fb2b8eb2009-04-23 21:03:42424 if url.module_name in solution_urls:
425 # Already parsed.
426 continue
427 if d in deps and type(deps[d]) != str:
428 if url.module_name == deps[d].module_name:
429 continue
[email protected]4b5b1772010-04-08 01:52:56430 elif isinstance(url, str):
[email protected]fb2b8eb2009-04-23 21:03:42431 parsed_url = urlparse.urlparse(url)
432 scheme = parsed_url[0]
433 if not scheme:
434 # A relative url. Fetch the real base.
435 path = parsed_url[2]
436 if path[0] != "/":
[email protected]e3608df2009-11-10 20:22:57437 raise gclient_utils.Error(
[email protected]fb2b8eb2009-04-23 21:03:42438 "relative DEPS entry \"%s\" must begin with a slash" % d)
[email protected]e6f78352010-01-13 17:05:33439 # Create a scm just to query the full url.
440 scm = gclient_scm.CreateSCM(solution["url"], self._root_dir,
441 None)
442 url = scm.FullUrlForRelativeUrl(url)
[email protected]fb2b8eb2009-04-23 21:03:42443 if d in deps and deps[d] != url:
[email protected]e3608df2009-11-10 20:22:57444 raise gclient_utils.Error(
[email protected]fb2b8eb2009-04-23 21:03:42445 "Solutions have conflicting versions of dependency \"%s\"" % d)
446 if d in solution_urls and solution_urls[d] != url:
[email protected]e3608df2009-11-10 20:22:57447 raise gclient_utils.Error(
[email protected]fb2b8eb2009-04-23 21:03:42448 "Dependency \"%s\" conflicts with specified solution" % d)
449 # Grab the dependency.
450 deps[d] = url
451 return deps
452
[email protected]71b40682009-07-31 23:40:09453 def _RunHookAction(self, hook_dict, matching_file_list):
[email protected]fb2b8eb2009-04-23 21:03:42454 """Runs the action from a single hook.
455 """
[email protected]5ca27692010-05-26 19:32:41456 logging.info(hook_dict)
457 logging.info(matching_file_list)
[email protected]fb2b8eb2009-04-23 21:03:42458 command = hook_dict['action'][:]
459 if command[0] == 'python':
460 # If the hook specified "python" as the first item, the action is a
461 # Python script. Run it by starting a new copy of the same
462 # interpreter.
463 command[0] = sys.executable
464
[email protected]71b40682009-07-31 23:40:09465 if '$matching_files' in command:
[email protected]68f2e092009-08-06 17:05:35466 splice_index = command.index('$matching_files')
467 command[splice_index:splice_index + 1] = matching_file_list
[email protected]71b40682009-07-31 23:40:09468
[email protected]fb2b8eb2009-04-23 21:03:42469 # Use a discrete exit status code of 2 to indicate that a hook action
470 # failed. Users of this script may wish to treat hook action failures
471 # differently from VC failures.
[email protected]5f3eee32009-09-17 00:34:30472 gclient_utils.SubprocessCall(command, self._root_dir, fail_status=2)
[email protected]fb2b8eb2009-04-23 21:03:42473
474 def _RunHooks(self, command, file_list, is_using_git):
475 """Evaluates all hooks, running actions as needed.
476 """
477 # Hooks only run for these command types.
478 if not command in ('update', 'revert', 'runhooks'):
479 return
480
[email protected]67820ef2009-07-27 17:23:00481 # Hooks only run when --nohooks is not specified
482 if self._options.nohooks:
483 return
484
[email protected]fb2b8eb2009-04-23 21:03:42485 # Get any hooks from the .gclient file.
486 hooks = self.GetVar("hooks", [])
487 # Add any hooks found in DEPS files.
488 hooks.extend(self._deps_hooks)
489
490 # If "--force" was specified, run all hooks regardless of what files have
491 # changed. If the user is using git, then we don't know what files have
492 # changed so we always run all hooks.
493 if self._options.force or is_using_git:
494 for hook_dict in hooks:
[email protected]71b40682009-07-31 23:40:09495 self._RunHookAction(hook_dict, [])
[email protected]fb2b8eb2009-04-23 21:03:42496 return
497
498 # Run hooks on the basis of whether the files from the gclient operation
499 # match each hook's pattern.
500 for hook_dict in hooks:
501 pattern = re.compile(hook_dict['pattern'])
[email protected]e3608df2009-11-10 20:22:57502 matching_file_list = [f for f in file_list if pattern.search(f)]
[email protected]71b40682009-07-31 23:40:09503 if matching_file_list:
504 self._RunHookAction(hook_dict, matching_file_list)
[email protected]fb2b8eb2009-04-23 21:03:42505
[email protected]918a9ae2010-05-28 15:50:30506 def _EnforceRevisions(self, solutions):
507 """Checks for revision overrides."""
508 revision_overrides = {}
509 solutions = [s['name'] for s in solutions]
510 if self._options.revisions:
511 revision = self._options.revisions[0]
512 # Ignore solution@
513 rev = revision
514 if '@' in revision:
515 rev = revision.split('@', 1)[1]
516 revision_overrides[solutions[0]] = rev
517
518 if len(self._options.revisions) > 1:
519 # Enforce solution@rev format for following params.
520 for revision in self._options.revisions[1:]:
521 if not '@' in revision:
522 raise gclient_utils.Error(
523 'Specify the full dependency when specifying a revision number '
524 'for non-primary solution.')
525 sol, rev = revision.split("@", 1)
526 # Disallow conflicting revs
527 if revision_overrides.get(sol, rev) != rev:
528 raise gclient_utils.Error(
529 'Conflicting revision numbers specified for %s: %s and %s.' %
530 (sol, revision_overrides[sol], rev))
531 if not sol in solutions:
532 raise gclient_utils.Error('%s is not a valid solution.' % sol)
533 revision_overrides[sol] = rev
534 return revision_overrides
535
[email protected]fb2b8eb2009-04-23 21:03:42536 def RunOnDeps(self, command, args):
537 """Runs a command on each dependency in a client and its dependencies.
538
539 The module's dependencies are specified in its top-level DEPS files.
540
541 Args:
542 command: The command to use (e.g., 'status' or 'diff')
543 args: list of str - extra arguments to add to the command line.
544
545 Raises:
546 Error: If the client has conflicting entries.
547 """
548 if not command in self.supported_commands:
[email protected]e3608df2009-11-10 20:22:57549 raise gclient_utils.Error("'%s' is an unsupported command" % command)
[email protected]fb2b8eb2009-04-23 21:03:42550
[email protected]fb2b8eb2009-04-23 21:03:42551 solutions = self.GetVar("solutions")
552 if not solutions:
[email protected]e3608df2009-11-10 20:22:57553 raise gclient_utils.Error("No solution specified")
[email protected]918a9ae2010-05-28 15:50:30554 revision_overrides = self._EnforceRevisions(solutions)
[email protected]fb2b8eb2009-04-23 21:03:42555
556 # When running runhooks --force, there's no need to consult the SCM.
557 # All known hooks are expected to run unconditionally regardless of working
558 # copy state, so skip the SCM status check.
559 run_scm = not (command == 'runhooks' and self._options.force)
560
561 entries = {}
562 entries_deps_content = {}
[email protected]d2e92562010-04-27 01:55:18563 file_list = []
564 # Run on the base solutions first.
565 for solution in solutions:
566 name = solution["name"]
[email protected]1f7d1182010-05-17 18:17:38567 deps_file = solution.get("deps_file", self.DEPS_FILE)
[email protected]d2e92562010-04-27 01:55:18568 if '/' in deps_file or '\\' in deps_file:
569 raise gclient_utils.Error('deps_file name must not be a path, just a '
570 'filename.')
571 if name in entries:
572 raise gclient_utils.Error("solution %s specified more than once" % name)
573 url = solution["url"]
574 entries[name] = url
575 if run_scm and url:
576 self._options.revision = revision_overrides.get(name)
577 scm = gclient_scm.CreateSCM(url, self._root_dir, name)
578 scm.RunCommand(command, self._options, args, file_list)
579 file_list = [os.path.join(name, f.strip()) for f in file_list]
580 self._options.revision = None
581 try:
582 deps_content = gclient_utils.FileRead(
583 os.path.join(self._root_dir, name, deps_file))
584 except IOError, e:
585 if e.errno != errno.ENOENT:
586 raise
587 deps_content = ""
588 entries_deps_content[name] = deps_content
[email protected]fb2b8eb2009-04-23 21:03:42589
[email protected]d2e92562010-04-27 01:55:18590 # Process the dependencies next (sort alphanumerically to ensure that
591 # containing directories get populated first and for readability)
592 deps = self._ParseAllDeps(entries, entries_deps_content)
593 deps_to_process = deps.keys()
594 deps_to_process.sort()
[email protected]fb2b8eb2009-04-23 21:03:42595
[email protected]d2e92562010-04-27 01:55:18596 # First pass for direct dependencies.
597 if command == 'update' and not self._options.verbose:
598 pm = Progress('Syncing projects', len(deps_to_process))
599 for d in deps_to_process:
[email protected]1f7a3d12010-02-04 15:11:50600 if command == 'update' and not self._options.verbose:
[email protected]d2e92562010-04-27 01:55:18601 pm.update()
602 if type(deps[d]) == str:
603 url = deps[d]
604 entries[d] = url
605 if run_scm:
606 self._options.revision = revision_overrides.get(d)
607 scm = gclient_scm.CreateSCM(url, self._root_dir, d)
608 scm.RunCommand(command, self._options, args, file_list)
609 self._options.revision = None
610 elif isinstance(deps[d], self.FileImpl):
[email protected]491c04b2010-05-17 18:17:44611 file_dep = deps[d]
612 self._options.revision = file_dep.GetRevision()
[email protected]d2e92562010-04-27 01:55:18613 if run_scm:
[email protected]491c04b2010-05-17 18:17:44614 scm = gclient_scm.CreateSCM(file_dep.GetPath(), self._root_dir, d)
[email protected]d2e92562010-04-27 01:55:18615 scm.RunCommand("updatesingle", self._options,
[email protected]491c04b2010-05-17 18:17:44616 args + [file_dep.GetFilename()], file_list)
[email protected]79692d62010-05-14 18:57:13617
[email protected]d2e92562010-04-27 01:55:18618 if command == 'update' and not self._options.verbose:
619 pm.end()
[email protected]6f363722010-04-27 00:41:09620
[email protected]d2e92562010-04-27 01:55:18621 # Second pass for inherited deps (via the From keyword)
622 for d in deps_to_process:
623 if isinstance(deps[d], self.FromImpl):
624 filename = os.path.join(self._root_dir,
625 deps[d].module_name,
[email protected]1f7d1182010-05-17 18:17:38626 self.DEPS_FILE)
[email protected]d2e92562010-04-27 01:55:18627 content = gclient_utils.FileRead(filename)
628 sub_deps = self._ParseSolutionDeps(deps[d].module_name, content, {},
629 False)
630 # Getting the URL from the sub_deps file can involve having to resolve
631 # a File() or having to resolve a relative URL. To resolve relative
632 # URLs, we need to pass in the orignal sub deps URL.
633 sub_deps_base_url = deps[deps[d].module_name]
634 url = deps[d].GetUrl(d, sub_deps_base_url, self._root_dir, sub_deps)
635 entries[d] = url
636 if run_scm:
637 self._options.revision = revision_overrides.get(d)
638 scm = gclient_scm.CreateSCM(url, self._root_dir, d)
639 scm.RunCommand(command, self._options, args, file_list)
640 self._options.revision = None
[email protected]df2d5902009-09-11 22:16:21641
[email protected]d83b2b22009-08-11 15:30:55642 # Convert all absolute paths to relative.
643 for i in range(len(file_list)):
644 # TODO(phajdan.jr): We should know exactly when the paths are absolute.
645 # It depends on the command being executed (like runhooks vs sync).
646 if not os.path.isabs(file_list[i]):
647 continue
648
649 prefix = os.path.commonprefix([self._root_dir.lower(),
650 file_list[i].lower()])
651 file_list[i] = file_list[i][len(prefix):]
652
653 # Strip any leading path separators.
654 while file_list[i].startswith('\\') or file_list[i].startswith('/'):
655 file_list[i] = file_list[i][1:]
[email protected]fb2b8eb2009-04-23 21:03:42656
[email protected]5f3eee32009-09-17 00:34:30657 is_using_git = gclient_utils.IsUsingGit(self._root_dir, entries.keys())
[email protected]fb2b8eb2009-04-23 21:03:42658 self._RunHooks(command, file_list, is_using_git)
659
660 if command == 'update':
[email protected]cdcee802009-06-23 15:30:42661 # Notify the user if there is an orphaned entry in their working copy.
662 # Only delete the directory if there are no changes in it, and
663 # delete_unversioned_trees is set to true.
[email protected]fb2b8eb2009-04-23 21:03:42664 prev_entries = self._ReadEntries()
665 for entry in prev_entries:
[email protected]c5e9aec2009-08-03 18:25:56666 # Fix path separator on Windows.
667 entry_fixed = entry.replace('/', os.path.sep)
668 e_dir = os.path.join(self._root_dir, entry_fixed)
669 # Use entry and not entry_fixed there.
[email protected]0329e672009-05-13 18:41:04670 if entry not in entries and os.path.exists(e_dir):
[email protected]83017012009-09-28 18:52:12671 modified_files = False
[email protected]5aeb7dd2009-11-17 18:09:01672 if isinstance(prev_entries, list):
[email protected]83017012009-09-28 18:52:12673 # old .gclient_entries format was list, now dict
[email protected]5aeb7dd2009-11-17 18:09:01674 modified_files = gclient_scm.scm.SVN.CaptureStatus(e_dir)
[email protected]83017012009-09-28 18:52:12675 else:
676 file_list = []
677 scm = gclient_scm.CreateSCM(prev_entries[entry], self._root_dir,
678 entry_fixed)
679 scm.status(self._options, [], file_list)
680 modified_files = file_list != []
681 if not self._options.delete_unversioned_trees or modified_files:
[email protected]c5e9aec2009-08-03 18:25:56682 # There are modified files in this entry. Keep warning until
683 # removed.
[email protected]c5e9aec2009-08-03 18:25:56684 print(("\nWARNING: \"%s\" is no longer part of this client. "
685 "It is recommended that you manually remove it.\n") %
686 entry_fixed)
[email protected]fb2b8eb2009-04-23 21:03:42687 else:
688 # Delete the entry
[email protected]df7a3132009-05-12 17:49:49689 print("\n________ deleting \'%s\' " +
[email protected]c5e9aec2009-08-03 18:25:56690 "in \'%s\'") % (entry_fixed, self._root_dir)
[email protected]5f3eee32009-09-17 00:34:30691 gclient_utils.RemoveDirectory(e_dir)
[email protected]fb2b8eb2009-04-23 21:03:42692 # record the current list of entries for next time
693 self._SaveEntries(entries)
694
695 def PrintRevInfo(self):
[email protected]5d63eb82010-03-24 23:22:09696 """Output revision info mapping for the client and its dependencies.
697
698 This allows the capture of an overall "revision" for the source tree that
[email protected]918a9ae2010-05-28 15:50:30699 can be used to reproduce the same tree in the future. It is only useful for
700 "unpinned dependencies", i.e. DEPS/deps references without a svn revision
701 number or a git hash. A git branch name isn't "pinned" since the actual
702 commit can change.
[email protected]5d63eb82010-03-24 23:22:09703
704 The --snapshot option allows creating a .gclient file to reproduce the tree.
[email protected]fb2b8eb2009-04-23 21:03:42705 """
[email protected]fb2b8eb2009-04-23 21:03:42706 solutions = self.GetVar("solutions")
707 if not solutions:
[email protected]e3608df2009-11-10 20:22:57708 raise gclient_utils.Error("No solution specified")
[email protected]fb2b8eb2009-04-23 21:03:42709
[email protected]5d63eb82010-03-24 23:22:09710 # Inner helper to generate base url and rev tuple
[email protected]fb2b8eb2009-04-23 21:03:42711 def GetURLAndRev(name, original_url):
[email protected]5d63eb82010-03-24 23:22:09712 url, _ = gclient_utils.SplitUrlRevision(original_url)
713 scm = gclient_scm.CreateSCM(original_url, self._root_dir, name)
714 return (url, scm.revinfo(self._options, [], None))
[email protected]fb2b8eb2009-04-23 21:03:42715
[email protected]e3da35f2010-03-09 21:40:45716 # text of the snapshot gclient file
717 new_gclient = ""
718 # Dictionary of { path : SCM url } to ensure no duplicate solutions
719 solution_names = {}
[email protected]de8f3522010-03-11 23:47:44720 entries = {}
721 entries_deps_content = {}
[email protected]fb2b8eb2009-04-23 21:03:42722 # Run on the base solutions first.
723 for solution in solutions:
[email protected]e3da35f2010-03-09 21:40:45724 # Dictionary of { path : SCM url } to describe the gclient checkout
[email protected]fb2b8eb2009-04-23 21:03:42725 name = solution["name"]
[email protected]e3da35f2010-03-09 21:40:45726 if name in solution_names:
[email protected]e3608df2009-11-10 20:22:57727 raise gclient_utils.Error("solution %s specified more than once" % name)
[email protected]fb2b8eb2009-04-23 21:03:42728 (url, rev) = GetURLAndRev(name, solution["url"])
[email protected]770ff9e2009-09-23 17:18:18729 entries[name] = "%s@%s" % (url, rev)
[email protected]e3da35f2010-03-09 21:40:45730 solution_names[name] = "%s@%s" % (url, rev)
[email protected]491c04b2010-05-17 18:17:44731 deps_file = solution.get("deps_file", self.DEPS_FILE)
[email protected]952d7c72010-03-01 20:41:01732 if '/' in deps_file or '\\' in deps_file:
733 raise gclient_utils.Error('deps_file name must not be a path, just a '
734 'filename.')
735 try:
736 deps_content = gclient_utils.FileRead(
737 os.path.join(self._root_dir, name, deps_file))
738 except IOError, e:
739 if e.errno != errno.ENOENT:
740 raise
741 deps_content = ""
742 entries_deps_content[name] = deps_content
[email protected]fb2b8eb2009-04-23 21:03:42743
[email protected]de8f3522010-03-11 23:47:44744 # Process the dependencies next (sort alphanumerically to ensure that
745 # containing directories get populated first and for readability)
746 deps = self._ParseAllDeps(entries, entries_deps_content)
747 deps_to_process = deps.keys()
748 deps_to_process.sort()
[email protected]fb2b8eb2009-04-23 21:03:42749
[email protected]de8f3522010-03-11 23:47:44750 # First pass for direct dependencies.
751 for d in deps_to_process:
752 if type(deps[d]) == str:
753 (url, rev) = GetURLAndRev(d, deps[d])
754 entries[d] = "%s@%s" % (url, rev)
[email protected]fb2b8eb2009-04-23 21:03:42755
[email protected]de8f3522010-03-11 23:47:44756 # Second pass for inherited deps (via the From keyword)
757 for d in deps_to_process:
[email protected]4b5b1772010-04-08 01:52:56758 if isinstance(deps[d], self.FromImpl):
[email protected]de8f3522010-03-11 23:47:44759 deps_parent_url = entries[deps[d].module_name]
760 if deps_parent_url.find("@") < 0:
761 raise gclient_utils.Error("From %s missing revisioned url" %
762 deps[d].module_name)
763 content = gclient_utils.FileRead(os.path.join(
764 self._root_dir,
765 deps[d].module_name,
[email protected]491c04b2010-05-17 18:17:44766 self.DEPS_FILE))
767 sub_deps = self._ParseSolutionDeps(deps[d].module_name, content, {},
768 False)
[email protected]de8f3522010-03-11 23:47:44769 (url, rev) = GetURLAndRev(d, sub_deps[d])
770 entries[d] = "%s@%s" % (url, rev)
[email protected]e3da35f2010-03-09 21:40:45771
[email protected]de8f3522010-03-11 23:47:44772 # Build the snapshot configuration string
773 if self._options.snapshot:
774 url = entries.pop(name)
775 custom_deps = ",\n ".join(["\"%s\": \"%s\"" % (x, entries[x])
776 for x in sorted(entries.keys())])
[email protected]e3da35f2010-03-09 21:40:45777
[email protected]1f7d1182010-05-17 18:17:38778 new_gclient += self.DEFAULT_SNAPSHOT_SOLUTION_TEXT % {
[email protected]de8f3522010-03-11 23:47:44779 'solution_name': name,
780 'solution_url': url,
781 'safesync_url' : "",
782 'solution_deps': custom_deps,
783 }
784 else:
785 print(";\n".join(["%s: %s" % (x, entries[x])
786 for x in sorted(entries.keys())]))
[email protected]e3da35f2010-03-09 21:40:45787
788 # Print the snapshot configuration file
789 if self._options.snapshot:
[email protected]491c04b2010-05-17 18:17:44790 config = self.DEFAULT_SNAPSHOT_FILE_TEXT % {'solution_list': new_gclient}
[email protected]e3da35f2010-03-09 21:40:45791 snapclient = GClient(self._root_dir, self._options)
792 snapclient.SetConfig(config)
793 print(snapclient._config_content)
[email protected]fb2b8eb2009-04-23 21:03:42794
795
[email protected]5ca27692010-05-26 19:32:41796#### gclient commands.
[email protected]fb2b8eb2009-04-23 21:03:42797
798
[email protected]5ca27692010-05-26 19:32:41799def CMDcleanup(parser, args):
800 """Cleans up all working copies.
[email protected]ddff62d2010-05-17 21:02:36801
[email protected]5ca27692010-05-26 19:32:41802Mostly svn-specific. Simply runs 'svn cleanup' for each module.
[email protected]79692d62010-05-14 18:57:13803"""
[email protected]5ca27692010-05-26 19:32:41804 (options, args) = parser.parse_args(args)
[email protected]2806acc2009-05-15 12:33:34805 client = GClient.LoadCurrentConfig(options)
[email protected]fb2b8eb2009-04-23 21:03:42806 if not client:
[email protected]e3608df2009-11-10 20:22:57807 raise gclient_utils.Error("client not configured; see 'gclient config'")
[email protected]fb2b8eb2009-04-23 21:03:42808 if options.verbose:
809 # Print out the .gclient file. This is longer than if we just printed the
810 # client dict, but more legible, and it might contain helpful comments.
[email protected]df7a3132009-05-12 17:49:49811 print(client.ConfigContent())
[email protected]fb2b8eb2009-04-23 21:03:42812 return client.RunOnDeps('cleanup', args)
813
814
[email protected]5ca27692010-05-26 19:32:41815@attr('usage', '[url] [safesync url]')
816def CMDconfig(parser, args):
[email protected]ddff62d2010-05-17 21:02:36817 """Create a .gclient file in the current directory.
818
[email protected]5ca27692010-05-26 19:32:41819This specifies the configuration for further commands. After update/sync,
[email protected]79692d62010-05-14 18:57:13820top-level DEPS files in each module are read to determine dependent
[email protected]5ca27692010-05-26 19:32:41821modules to operate on as well. If optional [url] parameter is
[email protected]79692d62010-05-14 18:57:13822provided, then configuration is read from a specified Subversion server
[email protected]5ca27692010-05-26 19:32:41823URL.
[email protected]79692d62010-05-14 18:57:13824"""
[email protected]5ca27692010-05-26 19:32:41825 parser.add_option("--spec",
826 help="create a gclient file containing the provided "
827 "string. Due to Cygwin/Python brokenness, it "
828 "probably can't contain any newlines.")
829 parser.add_option("--name",
830 help="overrides the default name for the solution")
831 (options, args) = parser.parse_args(args)
[email protected]5fc2a332010-05-26 19:37:15832 if ((options.spec and args) or len(args) > 2 or
833 (not options.spec and not args)):
834 parser.error('Inconsistent arguments. Use either --spec or one or 2 args')
835
[email protected]0329e672009-05-13 18:41:04836 if os.path.exists(options.config_filename):
[email protected]e3608df2009-11-10 20:22:57837 raise gclient_utils.Error("%s file already exists in the current directory"
838 % options.config_filename)
[email protected]2806acc2009-05-15 12:33:34839 client = GClient('.', options)
[email protected]fb2b8eb2009-04-23 21:03:42840 if options.spec:
841 client.SetConfig(options.spec)
842 else:
[email protected]1ab7ffc2009-06-03 17:21:37843 base_url = args[0].rstrip('/')
[email protected]8cf7a392010-04-07 17:20:26844 if not options.name:
845 name = base_url.split("/")[-1]
846 else:
847 # specify an alternate relpath for the given URL.
848 name = options.name
[email protected]fb2b8eb2009-04-23 21:03:42849 safesync_url = ""
850 if len(args) > 1:
851 safesync_url = args[1]
852 client.SetDefaultConfig(name, base_url, safesync_url)
853 client.SaveConfig()
[email protected]79692d62010-05-14 18:57:13854 return 0
[email protected]fb2b8eb2009-04-23 21:03:42855
856
[email protected]5ca27692010-05-26 19:32:41857def CMDexport(parser, args):
[email protected]ddff62d2010-05-17 21:02:36858 """Wrapper for svn export for all managed directories."""
[email protected]5ca27692010-05-26 19:32:41859 (options, args) = parser.parse_args(args)
[email protected]644aa0c2009-07-17 20:20:41860 if len(args) != 1:
[email protected]e3608df2009-11-10 20:22:57861 raise gclient_utils.Error("Need directory name")
[email protected]644aa0c2009-07-17 20:20:41862 client = GClient.LoadCurrentConfig(options)
863
864 if not client:
[email protected]e3608df2009-11-10 20:22:57865 raise gclient_utils.Error("client not configured; see 'gclient config'")
[email protected]644aa0c2009-07-17 20:20:41866
867 if options.verbose:
868 # Print out the .gclient file. This is longer than if we just printed the
869 # client dict, but more legible, and it might contain helpful comments.
870 print(client.ConfigContent())
871 return client.RunOnDeps('export', args)
872
[email protected]fb2b8eb2009-04-23 21:03:42873
[email protected]5ca27692010-05-26 19:32:41874@attr('epilog', """Example:
875 gclient pack > patch.txt
876 generate simple patch for configured client and dependences
877""")
878def CMDpack(parser, args):
[email protected]79692d62010-05-14 18:57:13879 """Generate a patch which can be applied at the root of the tree.
[email protected]ddff62d2010-05-17 21:02:36880
[email protected]5ca27692010-05-26 19:32:41881Internally, runs 'svn diff'/'git diff' on each checked out module and
[email protected]79692d62010-05-14 18:57:13882dependencies, and performs minimal postprocessing of the output. The
883resulting patch is printed to stdout and can be applied to a freshly
[email protected]5ca27692010-05-26 19:32:41884checked out tree via 'patch -p0 < patchfile'.
[email protected]79692d62010-05-14 18:57:13885"""
[email protected]5ca27692010-05-26 19:32:41886 (options, args) = parser.parse_args(args)
[email protected]ab318592009-09-04 00:54:55887 client = GClient.LoadCurrentConfig(options)
888 if not client:
[email protected]e3608df2009-11-10 20:22:57889 raise gclient_utils.Error("client not configured; see 'gclient config'")
[email protected]ab318592009-09-04 00:54:55890 if options.verbose:
891 # Print out the .gclient file. This is longer than if we just printed the
892 # client dict, but more legible, and it might contain helpful comments.
893 print(client.ConfigContent())
[email protected]ab318592009-09-04 00:54:55894 return client.RunOnDeps('pack', args)
895
896
[email protected]5ca27692010-05-26 19:32:41897def CMDstatus(parser, args):
898 """Show modification status for every dependencies."""
[email protected]6b1d00b2010-05-26 20:11:08899 parser.add_option("--deps", dest="deps_os", metavar="OS_LIST",
900 help="sync deps for the specified (comma-separated) "
901 "platform(s); 'all' will sync all platforms")
[email protected]5ca27692010-05-26 19:32:41902 (options, args) = parser.parse_args(args)
[email protected]2806acc2009-05-15 12:33:34903 client = GClient.LoadCurrentConfig(options)
[email protected]fb2b8eb2009-04-23 21:03:42904 if not client:
[email protected]e3608df2009-11-10 20:22:57905 raise gclient_utils.Error("client not configured; see 'gclient config'")
[email protected]fb2b8eb2009-04-23 21:03:42906 if options.verbose:
907 # Print out the .gclient file. This is longer than if we just printed the
908 # client dict, but more legible, and it might contain helpful comments.
[email protected]df7a3132009-05-12 17:49:49909 print(client.ConfigContent())
[email protected]fb2b8eb2009-04-23 21:03:42910 return client.RunOnDeps('status', args)
911
912
[email protected]5ca27692010-05-26 19:32:41913@attr('epilog', """Examples:
[email protected]79692d62010-05-14 18:57:13914 gclient sync
915 update files from SCM according to current configuration,
916 *for modules which have changed since last update or sync*
917 gclient sync --force
918 update files from SCM according to current configuration, for
919 all modules (useful for recovering files deleted from local copy)
920 gclient sync --revision src@31000
921 update src directory to r31000
[email protected]5ca27692010-05-26 19:32:41922""")
923def CMDsync(parser, args):
924 """Checkout/update all modules."""
925 parser.add_option("--force", action="store_true",
926 help="force update even for unchanged modules")
927 parser.add_option("--nohooks", action="store_true",
928 help="don't run hooks after the update is complete")
929 parser.add_option("-r", "--revision", action="append",
930 dest="revisions", metavar="REV", default=[],
[email protected]918a9ae2010-05-28 15:50:30931 help="Enforces revision/hash for the primary solution. "
932 "To modify a secondary solution, use it at least once "
933 "for the primary solution and then use the format "
934 "solution@rev/hash, e.g. -r src@123")
[email protected]5ca27692010-05-26 19:32:41935 parser.add_option("--head", action="store_true",
936 help="skips any safesync_urls specified in "
937 "configured solutions and sync to head instead")
938 parser.add_option("--delete_unversioned_trees", action="store_true",
939 help="delete any unexpected unversioned trees "
940 "that are in the checkout")
941 parser.add_option("--reset", action="store_true",
942 help="resets any local changes before updating (git only)")
943 parser.add_option("--deps", dest="deps_os", metavar="OS_LIST",
944 help="sync deps for the specified (comma-separated) "
945 "platform(s); 'all' will sync all platforms")
946 parser.add_option("--manually_grab_svn_rev", action="store_true",
947 help="Skip svn up whenever possible by requesting "
948 "actual HEAD revision from the repository")
949 (options, args) = parser.parse_args(args)
[email protected]2806acc2009-05-15 12:33:34950 client = GClient.LoadCurrentConfig(options)
[email protected]fb2b8eb2009-04-23 21:03:42951
952 if not client:
[email protected]e3608df2009-11-10 20:22:57953 raise gclient_utils.Error("client not configured; see 'gclient config'")
[email protected]fb2b8eb2009-04-23 21:03:42954
955 if not options.head:
956 solutions = client.GetVar('solutions')
957 if solutions:
[email protected]918a9ae2010-05-28 15:50:30958 first = True
[email protected]fb2b8eb2009-04-23 21:03:42959 for s in solutions:
[email protected]918a9ae2010-05-28 15:50:30960 if s.get('safesync_url', None):
[email protected]fb2b8eb2009-04-23 21:03:42961 # rip through revisions and make sure we're not over-riding
962 # something that was explicitly passed
963 has_key = False
[email protected]918a9ae2010-05-28 15:50:30964 r_first = True
[email protected]fb2b8eb2009-04-23 21:03:42965 for r in options.revisions:
[email protected]918a9ae2010-05-28 15:50:30966 if (first and r_first) or r.split('@', 1)[0] == s['name']:
[email protected]fb2b8eb2009-04-23 21:03:42967 has_key = True
968 break
[email protected]918a9ae2010-05-28 15:50:30969 r_first = False
[email protected]fb2b8eb2009-04-23 21:03:42970
971 if not has_key:
972 handle = urllib.urlopen(s['safesync_url'])
973 rev = handle.read().strip()
974 handle.close()
975 if len(rev):
976 options.revisions.append(s['name']+'@'+rev)
[email protected]918a9ae2010-05-28 15:50:30977 first = False
[email protected]fb2b8eb2009-04-23 21:03:42978
979 if options.verbose:
980 # Print out the .gclient file. This is longer than if we just printed the
981 # client dict, but more legible, and it might contain helpful comments.
[email protected]df7a3132009-05-12 17:49:49982 print(client.ConfigContent())
[email protected]fb2b8eb2009-04-23 21:03:42983 return client.RunOnDeps('update', args)
984
985
[email protected]5ca27692010-05-26 19:32:41986def CMDupdate(parser, args):
[email protected]ddff62d2010-05-17 21:02:36987 """Alias for the sync command. Deprecated."""
[email protected]5ca27692010-05-26 19:32:41988 return CMDsync(parser, args)
[email protected]fb2b8eb2009-04-23 21:03:42989
[email protected]5ca27692010-05-26 19:32:41990def CMDdiff(parser, args):
991 """Displays local diff for every dependencies."""
992 (options, args) = parser.parse_args(args)
[email protected]2806acc2009-05-15 12:33:34993 client = GClient.LoadCurrentConfig(options)
[email protected]fb2b8eb2009-04-23 21:03:42994 if not client:
[email protected]e3608df2009-11-10 20:22:57995 raise gclient_utils.Error("client not configured; see 'gclient config'")
[email protected]fb2b8eb2009-04-23 21:03:42996 if options.verbose:
997 # Print out the .gclient file. This is longer than if we just printed the
998 # client dict, but more legible, and it might contain helpful comments.
[email protected]df7a3132009-05-12 17:49:49999 print(client.ConfigContent())
[email protected]fb2b8eb2009-04-23 21:03:421000 return client.RunOnDeps('diff', args)
1001
1002
[email protected]5ca27692010-05-26 19:32:411003def CMDrevert(parser, args):
1004 """Revert all modifications in every dependencies."""
[email protected]6b1d00b2010-05-26 20:11:081005 parser.add_option("--deps", dest="deps_os", metavar="OS_LIST",
1006 help="sync deps for the specified (comma-separated) "
1007 "platform(s); 'all' will sync all platforms")
[email protected]5ca27692010-05-26 19:32:411008 parser.add_option("--nohooks", action="store_true",
1009 help="don't run hooks after the revert is complete")
1010 (options, args) = parser.parse_args(args)
1011 # --force is implied.
1012 options.force = True
[email protected]2806acc2009-05-15 12:33:341013 client = GClient.LoadCurrentConfig(options)
[email protected]fb2b8eb2009-04-23 21:03:421014 if not client:
[email protected]e3608df2009-11-10 20:22:571015 raise gclient_utils.Error("client not configured; see 'gclient config'")
[email protected]fb2b8eb2009-04-23 21:03:421016 return client.RunOnDeps('revert', args)
1017
1018
[email protected]5ca27692010-05-26 19:32:411019def CMDrunhooks(parser, args):
1020 """Runs hooks for files that have been modified in the local working copy."""
[email protected]6b1d00b2010-05-26 20:11:081021 parser.add_option("--deps", dest="deps_os", metavar="OS_LIST",
1022 help="sync deps for the specified (comma-separated) "
1023 "platform(s); 'all' will sync all platforms")
[email protected]5ca27692010-05-26 19:32:411024 parser.add_option("--force", action="store_true", default=True,
1025 help="Deprecated. No effect.")
1026 (options, args) = parser.parse_args(args)
[email protected]2806acc2009-05-15 12:33:341027 client = GClient.LoadCurrentConfig(options)
[email protected]fb2b8eb2009-04-23 21:03:421028 if not client:
[email protected]e3608df2009-11-10 20:22:571029 raise gclient_utils.Error("client not configured; see 'gclient config'")
[email protected]fb2b8eb2009-04-23 21:03:421030 if options.verbose:
1031 # Print out the .gclient file. This is longer than if we just printed the
1032 # client dict, but more legible, and it might contain helpful comments.
[email protected]df7a3132009-05-12 17:49:491033 print(client.ConfigContent())
[email protected]5df6a462009-08-28 18:52:261034 options.force = True
[email protected]5ca27692010-05-26 19:32:411035 options.nohooks = False
[email protected]fb2b8eb2009-04-23 21:03:421036 return client.RunOnDeps('runhooks', args)
1037
1038
[email protected]5ca27692010-05-26 19:32:411039def CMDrevinfo(parser, args):
1040 """Outputs details for every dependencies."""
1041 parser.add_option("--snapshot", action="store_true",
1042 help="create a snapshot file of the current "
1043 "version of all repositories")
1044 (options, args) = parser.parse_args(args)
[email protected]2806acc2009-05-15 12:33:341045 client = GClient.LoadCurrentConfig(options)
[email protected]d0fd5f02010-05-28 15:49:341046 options.deps_os = None
[email protected]fb2b8eb2009-04-23 21:03:421047 if not client:
[email protected]e3608df2009-11-10 20:22:571048 raise gclient_utils.Error("client not configured; see 'gclient config'")
[email protected]fb2b8eb2009-04-23 21:03:421049 client.PrintRevInfo()
[email protected]79692d62010-05-14 18:57:131050 return 0
[email protected]fb2b8eb2009-04-23 21:03:421051
1052
[email protected]5ca27692010-05-26 19:32:411053def Command(name):
1054 return getattr(sys.modules[__name__], 'CMD' + name, None)
1055
1056
1057def CMDhelp(parser, args):
1058 """Prints list of commands or help for a specific command."""
1059 (options, args) = parser.parse_args(args)
[email protected]ddff62d2010-05-17 21:02:361060 if len(args) == 1:
[email protected]5ca27692010-05-26 19:32:411061 return Main(args + ['--help'])
[email protected]ddff62d2010-05-17 21:02:361062 parser.print_help()
1063 return 0
1064
1065
[email protected]5ca27692010-05-26 19:32:411066def GenUsage(parser, command):
1067 """Modify an OptParse object with the function's documentation."""
1068 obj = Command(command)
1069 if command == 'help':
1070 command = '<command>'
1071 # OptParser.description prefer nicely non-formatted strings.
1072 parser.description = re.sub('[\r\n ]{2,}', ' ', obj.__doc__)
1073 usage = getattr(obj, 'usage', '')
1074 parser.set_usage('%%prog %s [options] %s' % (command, usage))
1075 parser.epilog = getattr(obj, 'epilog', None)
[email protected]fb2b8eb2009-04-23 21:03:421076
1077
1078def Main(argv):
[email protected]5ca27692010-05-26 19:32:411079 """Doesn't parse the arguments here, just find the right subcommand to
1080 execute."""
1081 # Do it late so all commands are listed.
1082 CMDhelp.usage = ('\n\nCommands are:\n' + '\n'.join([
1083 ' %-10s %s' % (fn[3:], Command(fn[3:]).__doc__.split('\n')[0].strip())
1084 for fn in dir(sys.modules[__name__]) if fn.startswith('CMD')]))
1085 parser = optparse.OptionParser(version='%prog ' + __version__)
[email protected]ddff62d2010-05-17 21:02:361086 parser.add_option("-v", "--verbose", action="count", default=0,
1087 help="Produces additional output for diagnostics. Can be "
1088 "used up to three times for more logging info.")
1089 parser.add_option("--gclientfile", metavar="FILENAME", dest="config_filename",
1090 default=os.environ.get("GCLIENT_FILE", ".gclient"),
1091 help="Specify an alternate .gclient file")
[email protected]ddff62d2010-05-17 21:02:361092 # Integrate standard options processing.
1093 old_parser = parser.parse_args
1094 def Parse(args):
1095 (options, args) = old_parser(args)
1096 if options.verbose == 2:
1097 logging.basicConfig(level=logging.INFO)
1098 elif options.verbose > 2:
1099 logging.basicConfig(level=logging.DEBUG)
1100 options.entries_filename = options.config_filename + "_entries"
[email protected]5ca27692010-05-26 19:32:411101 if not hasattr(options, 'revisions'):
1102 # GClient.RunOnDeps expects it even if not applicable.
1103 options.revisions = []
[email protected]ddff62d2010-05-17 21:02:361104 return (options, args)
1105 parser.parse_args = Parse
1106 # We don't want wordwrapping in epilog (usually examples)
1107 parser.format_epilog = lambda _: parser.epilog or ''
[email protected]5ca27692010-05-26 19:32:411108 if argv:
1109 command = Command(argv[0])
1110 if command:
1111 # "fix" the usage and the description now that we know the subcommand.
1112 GenUsage(parser, argv[0])
1113 return command(parser, argv[1:])
1114 # Not a known command. Default to help.
1115 GenUsage(parser, 'help')
1116 return CMDhelp(parser, argv)
[email protected]fb2b8eb2009-04-23 21:03:421117
1118
1119if "__main__" == __name__:
1120 try:
[email protected]ddff62d2010-05-17 21:02:361121 sys.exit(Main(sys.argv[1:]))
[email protected]e3608df2009-11-10 20:22:571122 except gclient_utils.Error, e:
[email protected]df7a3132009-05-12 17:49:491123 print >> sys.stderr, "Error: %s" % str(e)
[email protected]ddff62d2010-05-17 21:02:361124 sys.exit(1)
[email protected]fb2b8eb2009-04-23 21:03:421125
1126# vim: ts=2:sw=2:tw=80:et: