blob: 586c655f912aef538963c18e246e0a7a6a2f7375 [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
[email protected]116704f2010-06-11 17:34:3888class GClientKeywords(object):
89 class FromImpl(object):
90 """Used to implement the From() syntax."""
91
92 def __init__(self, module_name, sub_target_name=None):
93 """module_name is the dep module we want to include from. It can also be
94 the name of a subdirectory to include from.
95
96 sub_target_name is an optional parameter if the module name in the other
97 DEPS file is different. E.g., you might want to map src/net to net."""
98 self.module_name = module_name
99 self.sub_target_name = sub_target_name
100
101 def __str__(self):
102 return 'From(%s, %s)' % (repr(self.module_name),
103 repr(self.sub_target_name))
104
105 def GetUrl(self, target_name, sub_deps_base_url, root_dir, sub_deps):
106 """Resolve the URL for this From entry."""
107 sub_deps_target_name = target_name
108 if self.sub_target_name:
109 sub_deps_target_name = self.sub_target_name
110 url = sub_deps[sub_deps_target_name]
111 if url.startswith('/'):
112 # If it's a relative URL, we need to resolve the URL relative to the
113 # sub deps base URL.
114 if not isinstance(sub_deps_base_url, basestring):
115 sub_deps_base_url = sub_deps_base_url.GetPath()
116 scm = gclient_scm.CreateSCM(sub_deps_base_url, root_dir,
117 None)
118 url = scm.FullUrlForRelativeUrl(url)
119 return url
120
121 class FileImpl(object):
122 """Used to implement the File('') syntax which lets you sync a single file
123 from an SVN repo."""
124
125 def __init__(self, file_location):
126 self.file_location = file_location
127
128 def __str__(self):
129 return 'File("%s")' % self.file_location
130
131 def GetPath(self):
132 return os.path.split(self.file_location)[0]
133
134 def GetFilename(self):
135 rev_tokens = self.file_location.split('@')
136 return os.path.split(rev_tokens[0])[1]
137
138 def GetRevision(self):
139 rev_tokens = self.file_location.split('@')
140 if len(rev_tokens) > 1:
141 return rev_tokens[1]
142 return None
143
144 class VarImpl(object):
145 def __init__(self, custom_vars, local_scope):
146 self._custom_vars = custom_vars
147 self._local_scope = local_scope
148
149 def Lookup(self, var_name):
150 """Implements the Var syntax."""
151 if var_name in self._custom_vars:
152 return self._custom_vars[var_name]
153 elif var_name in self._local_scope.get("vars", {}):
154 return self._local_scope["vars"][var_name]
155 raise gclient_utils.Error("Var is not defined: %s" % var_name)
156
157
158class GClient(GClientKeywords):
[email protected]fb2b8eb2009-04-23 21:03:42159 """Object that represent a gclient checkout."""
160
[email protected]116704f2010-06-11 17:34:38161 SUPPORTED_COMMANDS = [
[email protected]ab318592009-09-04 00:54:55162 'cleanup', 'diff', 'export', 'pack', 'revert', 'status', 'update',
163 'runhooks'
[email protected]fb2b8eb2009-04-23 21:03:42164 ]
165
[email protected]116704f2010-06-11 17:34:38166 DEPS_OS_CHOICES = {
[email protected]491c04b2010-05-17 18:17:44167 "win32": "win",
168 "win": "win",
169 "cygwin": "win",
170 "darwin": "mac",
171 "mac": "mac",
172 "unix": "unix",
173 "linux": "unix",
174 "linux2": "unix",
175 }
176
[email protected]1f7d1182010-05-17 18:17:38177 DEPS_FILE = 'DEPS'
178
179 DEFAULT_CLIENT_FILE_TEXT = ("""\
180solutions = [
181 { "name" : "%(solution_name)s",
182 "url" : "%(solution_url)s",
183 "custom_deps" : {
184 },
185 "safesync_url": "%(safesync_url)s"
186 },
187]
188""")
189
190 DEFAULT_SNAPSHOT_SOLUTION_TEXT = ("""\
191 { "name" : "%(solution_name)s",
192 "url" : "%(solution_url)s",
193 "custom_deps" : {
194 %(solution_deps)s,
195 },
196 "safesync_url": "%(safesync_url)s"
197 },
198""")
199
200 DEFAULT_SNAPSHOT_FILE_TEXT = ("""\
201# Snapshot generated with gclient revinfo --snapshot
202solutions = [
203%(solution_list)s
204]
205""")
206
[email protected]fb2b8eb2009-04-23 21:03:42207 def __init__(self, root_dir, options):
208 self._root_dir = root_dir
209 self._options = options
[email protected]116704f2010-06-11 17:34:38210 self.config_content = None
[email protected]fb2b8eb2009-04-23 21:03:42211 self._config_dict = {}
212 self._deps_hooks = []
213
214 def SetConfig(self, content):
215 self._config_dict = {}
[email protected]116704f2010-06-11 17:34:38216 self.config_content = content
[email protected]df0032c2009-05-29 10:43:56217 try:
218 exec(content, self._config_dict)
219 except SyntaxError, e:
220 try:
[email protected]e3608df2009-11-10 20:22:57221 __pychecker__ = 'no-objattrs'
[email protected]df0032c2009-05-29 10:43:56222 # Try to construct a human readable error message
223 error_message = [
224 'There is a syntax error in your configuration file.',
225 'Line #%s, character %s:' % (e.lineno, e.offset),
226 '"%s"' % re.sub(r'[\r\n]*$', '', e.text) ]
227 except:
228 # Something went wrong, re-raise the original exception
229 raise e
230 else:
231 # Raise a new exception with the human readable message:
[email protected]e3608df2009-11-10 20:22:57232 raise gclient_utils.Error('\n'.join(error_message))
[email protected]fb2b8eb2009-04-23 21:03:42233
234 def SaveConfig(self):
[email protected]e3608df2009-11-10 20:22:57235 gclient_utils.FileWrite(os.path.join(self._root_dir,
236 self._options.config_filename),
[email protected]116704f2010-06-11 17:34:38237 self.config_content)
[email protected]fb2b8eb2009-04-23 21:03:42238
239 def _LoadConfig(self):
[email protected]e3608df2009-11-10 20:22:57240 client_source = gclient_utils.FileRead(
241 os.path.join(self._root_dir, self._options.config_filename))
[email protected]fb2b8eb2009-04-23 21:03:42242 self.SetConfig(client_source)
243
[email protected]fb2b8eb2009-04-23 21:03:42244 def GetVar(self, key, default=None):
245 return self._config_dict.get(key, default)
246
247 @staticmethod
248 def LoadCurrentConfig(options, from_dir=None):
249 """Searches for and loads a .gclient file relative to the current working
250 dir.
251
252 Returns:
253 A dict representing the contents of the .gclient file or an empty dict if
254 the .gclient file doesn't exist.
255 """
256 if not from_dir:
257 from_dir = os.curdir
258 path = os.path.realpath(from_dir)
[email protected]0329e672009-05-13 18:41:04259 while not os.path.exists(os.path.join(path, options.config_filename)):
[email protected]55e724e2010-03-11 19:36:49260 split_path = os.path.split(path)
261 if not split_path[1]:
[email protected]fb2b8eb2009-04-23 21:03:42262 return None
[email protected]55e724e2010-03-11 19:36:49263 path = split_path[0]
[email protected]2806acc2009-05-15 12:33:34264 client = GClient(path, options)
[email protected]fb2b8eb2009-04-23 21:03:42265 client._LoadConfig()
266 return client
267
268 def SetDefaultConfig(self, solution_name, solution_url, safesync_url):
[email protected]1f7d1182010-05-17 18:17:38269 self.SetConfig(self.DEFAULT_CLIENT_FILE_TEXT % {
[email protected]df2d5902009-09-11 22:16:21270 'solution_name': solution_name,
271 'solution_url': solution_url,
272 'safesync_url' : safesync_url,
273 })
[email protected]fb2b8eb2009-04-23 21:03:42274
275 def _SaveEntries(self, entries):
276 """Creates a .gclient_entries file to record the list of unique checkouts.
277
278 The .gclient_entries file lives in the same directory as .gclient.
279
280 Args:
281 entries: A sequence of solution names.
282 """
[email protected]e41f6822010-04-08 16:37:06283 # Sometimes pprint.pformat will use {', sometimes it'll use { ' ... It
284 # makes testing a bit too fun.
285 result = pprint.pformat(entries, 2)
286 if result.startswith('{\''):
[email protected]1edec4d2010-04-08 17:17:06287 result = '{ \'' + result[2:]
[email protected]e41f6822010-04-08 16:37:06288 text = "entries = \\\n" + result + '\n'
[email protected]2e38de72009-09-28 17:04:47289 file_path = os.path.join(self._root_dir, self._options.entries_filename)
[email protected]e3608df2009-11-10 20:22:57290 gclient_utils.FileWrite(file_path, text)
[email protected]fb2b8eb2009-04-23 21:03:42291
292 def _ReadEntries(self):
293 """Read the .gclient_entries file for the given client.
294
295 Args:
296 client: The client for which the entries file should be read.
297
298 Returns:
299 A sequence of solution names, which will be empty if there is the
300 entries file hasn't been created yet.
301 """
302 scope = {}
303 filename = os.path.join(self._root_dir, self._options.entries_filename)
[email protected]0329e672009-05-13 18:41:04304 if not os.path.exists(filename):
[email protected]fb2b8eb2009-04-23 21:03:42305 return []
[email protected]e3608df2009-11-10 20:22:57306 exec(gclient_utils.FileRead(filename), scope)
[email protected]fb2b8eb2009-04-23 21:03:42307 return scope["entries"]
308
[email protected]fb2b8eb2009-04-23 21:03:42309 def _ParseSolutionDeps(self, solution_name, solution_deps_content,
[email protected]30ef9ae2010-04-09 02:18:05310 custom_vars, parse_hooks):
[email protected]fb2b8eb2009-04-23 21:03:42311 """Parses the DEPS file for the specified solution.
312
313 Args:
314 solution_name: The name of the solution to query.
315 solution_deps_content: Content of the DEPS file for the solution
316 custom_vars: A dict of vars to override any vars defined in the DEPS file.
317
318 Returns:
319 A dict mapping module names (as relative paths) to URLs or an empty
320 dict if the solution does not have a DEPS file.
321 """
322 # Skip empty
323 if not solution_deps_content:
324 return {}
325 # Eval the content
326 local_scope = {}
[email protected]116704f2010-06-11 17:34:38327 var = self.VarImpl(custom_vars, local_scope)
[email protected]4b5b1772010-04-08 01:52:56328 global_scope = {
329 "File": self.FileImpl,
330 "From": self.FromImpl,
331 "Var": var.Lookup,
332 "deps_os": {},
333 }
[email protected]fb2b8eb2009-04-23 21:03:42334 exec(solution_deps_content, global_scope, local_scope)
335 deps = local_scope.get("deps", {})
336
337 # load os specific dependencies if defined. these dependencies may
338 # override or extend the values defined by the 'deps' member.
339 if "deps_os" in local_scope:
[email protected]fb2b8eb2009-04-23 21:03:42340 if self._options.deps_os is not None:
341 deps_to_include = self._options.deps_os.split(",")
342 if "all" in deps_to_include:
[email protected]116704f2010-06-11 17:34:38343 deps_to_include = list(set(self.DEPS_OS_CHOICES.itervalues()))
[email protected]fb2b8eb2009-04-23 21:03:42344 else:
[email protected]116704f2010-06-11 17:34:38345 deps_to_include = [self.DEPS_OS_CHOICES.get(sys.platform, "unix")]
[email protected]fb2b8eb2009-04-23 21:03:42346
347 deps_to_include = set(deps_to_include)
348 for deps_os_key in deps_to_include:
349 os_deps = local_scope["deps_os"].get(deps_os_key, {})
350 if len(deps_to_include) > 1:
351 # Ignore any overrides when including deps for more than one
352 # platform, so we collect the broadest set of dependencies available.
353 # We may end up with the wrong revision of something for our
354 # platform, but this is the best we can do.
355 deps.update([x for x in os_deps.items() if not x[0] in deps])
356 else:
357 deps.update(os_deps)
358
[email protected]30ef9ae2010-04-09 02:18:05359 if 'hooks' in local_scope and parse_hooks:
[email protected]fb2b8eb2009-04-23 21:03:42360 self._deps_hooks.extend(local_scope['hooks'])
361
362 # If use_relative_paths is set in the DEPS file, regenerate
363 # the dictionary using paths relative to the directory containing
364 # the DEPS file.
365 if local_scope.get('use_relative_paths'):
366 rel_deps = {}
367 for d, url in deps.items():
368 # normpath is required to allow DEPS to use .. in their
369 # dependency local path.
370 rel_deps[os.path.normpath(os.path.join(solution_name, d))] = url
371 return rel_deps
372 else:
373 return deps
374
375 def _ParseAllDeps(self, solution_urls, solution_deps_content):
376 """Parse the complete list of dependencies for the client.
377
378 Args:
379 solution_urls: A dict mapping module names (as relative paths) to URLs
380 corresponding to the solutions specified by the client. This parameter
381 is passed as an optimization.
382 solution_deps_content: A dict mapping module names to the content
383 of their DEPS files
384
385 Returns:
386 A dict mapping module names (as relative paths) to URLs corresponding
387 to the entire set of dependencies to checkout for the given client.
388
389 Raises:
390 Error: If a dependency conflicts with another dependency or of a solution.
391 """
392 deps = {}
393 for solution in self.GetVar("solutions"):
394 custom_vars = solution.get("custom_vars", {})
395 solution_deps = self._ParseSolutionDeps(
396 solution["name"],
397 solution_deps_content[solution["name"]],
[email protected]30ef9ae2010-04-09 02:18:05398 custom_vars,
399 True)
[email protected]fb2b8eb2009-04-23 21:03:42400
401 # If a line is in custom_deps, but not in the solution, we want to append
402 # this line to the solution.
403 if "custom_deps" in solution:
404 for d in solution["custom_deps"]:
405 if d not in solution_deps:
406 solution_deps[d] = solution["custom_deps"][d]
407
408 for d in solution_deps:
409 if "custom_deps" in solution and d in solution["custom_deps"]:
410 # Dependency is overriden.
411 url = solution["custom_deps"][d]
412 if url is None:
413 continue
414 else:
415 url = solution_deps[d]
416 # if we have a From reference dependent on another solution, then
417 # just skip the From reference. When we pull deps for the solution,
418 # we will take care of this dependency.
419 #
420 # If multiple solutions all have the same From reference, then we
421 # should only add one to our list of dependencies.
[email protected]4b5b1772010-04-08 01:52:56422 if isinstance(url, self.FromImpl):
[email protected]fb2b8eb2009-04-23 21:03:42423 if url.module_name in solution_urls:
424 # Already parsed.
425 continue
426 if d in deps and type(deps[d]) != str:
427 if url.module_name == deps[d].module_name:
428 continue
[email protected]4b5b1772010-04-08 01:52:56429 elif isinstance(url, str):
[email protected]fb2b8eb2009-04-23 21:03:42430 parsed_url = urlparse.urlparse(url)
431 scheme = parsed_url[0]
432 if not scheme:
433 # A relative url. Fetch the real base.
434 path = parsed_url[2]
435 if path[0] != "/":
[email protected]e3608df2009-11-10 20:22:57436 raise gclient_utils.Error(
[email protected]fb2b8eb2009-04-23 21:03:42437 "relative DEPS entry \"%s\" must begin with a slash" % d)
[email protected]e6f78352010-01-13 17:05:33438 # Create a scm just to query the full url.
439 scm = gclient_scm.CreateSCM(solution["url"], self._root_dir,
440 None)
441 url = scm.FullUrlForRelativeUrl(url)
[email protected]fb2b8eb2009-04-23 21:03:42442 if d in deps and deps[d] != url:
[email protected]e3608df2009-11-10 20:22:57443 raise gclient_utils.Error(
[email protected]fb2b8eb2009-04-23 21:03:42444 "Solutions have conflicting versions of dependency \"%s\"" % d)
445 if d in solution_urls and solution_urls[d] != url:
[email protected]e3608df2009-11-10 20:22:57446 raise gclient_utils.Error(
[email protected]fb2b8eb2009-04-23 21:03:42447 "Dependency \"%s\" conflicts with specified solution" % d)
448 # Grab the dependency.
449 deps[d] = url
450 return deps
451
[email protected]71b40682009-07-31 23:40:09452 def _RunHookAction(self, hook_dict, matching_file_list):
[email protected]fb2b8eb2009-04-23 21:03:42453 """Runs the action from a single hook.
454 """
[email protected]5ca27692010-05-26 19:32:41455 logging.info(hook_dict)
456 logging.info(matching_file_list)
[email protected]fb2b8eb2009-04-23 21:03:42457 command = hook_dict['action'][:]
458 if command[0] == 'python':
459 # If the hook specified "python" as the first item, the action is a
460 # Python script. Run it by starting a new copy of the same
461 # interpreter.
462 command[0] = sys.executable
463
[email protected]71b40682009-07-31 23:40:09464 if '$matching_files' in command:
[email protected]68f2e092009-08-06 17:05:35465 splice_index = command.index('$matching_files')
466 command[splice_index:splice_index + 1] = matching_file_list
[email protected]71b40682009-07-31 23:40:09467
[email protected]fb2b8eb2009-04-23 21:03:42468 # Use a discrete exit status code of 2 to indicate that a hook action
469 # failed. Users of this script may wish to treat hook action failures
470 # differently from VC failures.
[email protected]5f3eee32009-09-17 00:34:30471 gclient_utils.SubprocessCall(command, self._root_dir, fail_status=2)
[email protected]fb2b8eb2009-04-23 21:03:42472
473 def _RunHooks(self, command, file_list, is_using_git):
474 """Evaluates all hooks, running actions as needed.
475 """
476 # Hooks only run for these command types.
477 if not command in ('update', 'revert', 'runhooks'):
478 return
479
[email protected]67820ef2009-07-27 17:23:00480 # Hooks only run when --nohooks is not specified
481 if self._options.nohooks:
482 return
483
[email protected]fb2b8eb2009-04-23 21:03:42484 # Get any hooks from the .gclient file.
485 hooks = self.GetVar("hooks", [])
486 # Add any hooks found in DEPS files.
487 hooks.extend(self._deps_hooks)
488
489 # If "--force" was specified, run all hooks regardless of what files have
490 # changed. If the user is using git, then we don't know what files have
491 # changed so we always run all hooks.
492 if self._options.force or is_using_git:
493 for hook_dict in hooks:
[email protected]71b40682009-07-31 23:40:09494 self._RunHookAction(hook_dict, [])
[email protected]fb2b8eb2009-04-23 21:03:42495 return
496
497 # Run hooks on the basis of whether the files from the gclient operation
498 # match each hook's pattern.
499 for hook_dict in hooks:
500 pattern = re.compile(hook_dict['pattern'])
[email protected]e3608df2009-11-10 20:22:57501 matching_file_list = [f for f in file_list if pattern.search(f)]
[email protected]71b40682009-07-31 23:40:09502 if matching_file_list:
503 self._RunHookAction(hook_dict, matching_file_list)
[email protected]fb2b8eb2009-04-23 21:03:42504
[email protected]918a9ae2010-05-28 15:50:30505 def _EnforceRevisions(self, solutions):
506 """Checks for revision overrides."""
507 revision_overrides = {}
[email protected]307d1792010-05-31 20:03:13508 if self._options.head:
509 return revision_overrides
510 for s in solutions:
511 if not s.get('safesync_url', None):
512 continue
513 handle = urllib.urlopen(s['safesync_url'])
514 rev = handle.read().strip()
515 handle.close()
516 if len(rev):
517 self._options.revisions.append('%s@%s' % (s['name'], rev))
518 if not self._options.revisions:
519 return revision_overrides
520 # --revision will take over safesync_url.
521 solutions_names = [s['name'] for s in solutions]
522 index = 0
523 for revision in self._options.revisions:
524 if not '@' in revision:
525 # Support for --revision 123
526 revision = '%s@%s' % (solutions_names[index], revision)
527 sol, rev = revision.split("@", 1)
528 if not sol in solutions_names:
529 #raise gclient_utils.Error('%s is not a valid solution.' % sol)
530 print >> sys.stderr, ('Please fix your script, having invalid '
531 '--revision flags will soon considered an error.')
532 else:
[email protected]918a9ae2010-05-28 15:50:30533 revision_overrides[sol] = rev
[email protected]307d1792010-05-31 20:03:13534 index += 1
[email protected]918a9ae2010-05-28 15:50:30535 return revision_overrides
536
[email protected]fb2b8eb2009-04-23 21:03:42537 def RunOnDeps(self, command, args):
538 """Runs a command on each dependency in a client and its dependencies.
539
540 The module's dependencies are specified in its top-level DEPS files.
541
542 Args:
543 command: The command to use (e.g., 'status' or 'diff')
544 args: list of str - extra arguments to add to the command line.
545
546 Raises:
547 Error: If the client has conflicting entries.
548 """
[email protected]116704f2010-06-11 17:34:38549 if not command in self.SUPPORTED_COMMANDS:
[email protected]e3608df2009-11-10 20:22:57550 raise gclient_utils.Error("'%s' is an unsupported command" % command)
[email protected]fb2b8eb2009-04-23 21:03:42551
[email protected]fb2b8eb2009-04-23 21:03:42552 solutions = self.GetVar("solutions")
553 if not solutions:
[email protected]e3608df2009-11-10 20:22:57554 raise gclient_utils.Error("No solution specified")
[email protected]918a9ae2010-05-28 15:50:30555 revision_overrides = self._EnforceRevisions(solutions)
[email protected]fb2b8eb2009-04-23 21:03:42556
557 # When running runhooks --force, there's no need to consult the SCM.
558 # All known hooks are expected to run unconditionally regardless of working
559 # copy state, so skip the SCM status check.
560 run_scm = not (command == 'runhooks' and self._options.force)
561
562 entries = {}
563 entries_deps_content = {}
[email protected]d2e92562010-04-27 01:55:18564 file_list = []
565 # Run on the base solutions first.
566 for solution in solutions:
567 name = solution["name"]
[email protected]1f7d1182010-05-17 18:17:38568 deps_file = solution.get("deps_file", self.DEPS_FILE)
[email protected]d2e92562010-04-27 01:55:18569 if '/' in deps_file or '\\' in deps_file:
570 raise gclient_utils.Error('deps_file name must not be a path, just a '
571 'filename.')
572 if name in entries:
573 raise gclient_utils.Error("solution %s specified more than once" % name)
574 url = solution["url"]
575 entries[name] = url
576 if run_scm and url:
577 self._options.revision = revision_overrides.get(name)
578 scm = gclient_scm.CreateSCM(url, self._root_dir, name)
579 scm.RunCommand(command, self._options, args, file_list)
580 file_list = [os.path.join(name, f.strip()) for f in file_list]
581 self._options.revision = None
582 try:
583 deps_content = gclient_utils.FileRead(
584 os.path.join(self._root_dir, name, deps_file))
585 except IOError, e:
586 if e.errno != errno.ENOENT:
587 raise
588 deps_content = ""
589 entries_deps_content[name] = deps_content
[email protected]fb2b8eb2009-04-23 21:03:42590
[email protected]d2e92562010-04-27 01:55:18591 # Process the dependencies next (sort alphanumerically to ensure that
592 # containing directories get populated first and for readability)
593 deps = self._ParseAllDeps(entries, entries_deps_content)
594 deps_to_process = deps.keys()
595 deps_to_process.sort()
[email protected]fb2b8eb2009-04-23 21:03:42596
[email protected]d2e92562010-04-27 01:55:18597 # First pass for direct dependencies.
598 if command == 'update' and not self._options.verbose:
599 pm = Progress('Syncing projects', len(deps_to_process))
600 for d in deps_to_process:
[email protected]1f7a3d12010-02-04 15:11:50601 if command == 'update' and not self._options.verbose:
[email protected]d2e92562010-04-27 01:55:18602 pm.update()
603 if type(deps[d]) == str:
604 url = deps[d]
605 entries[d] = url
606 if run_scm:
607 self._options.revision = revision_overrides.get(d)
608 scm = gclient_scm.CreateSCM(url, self._root_dir, d)
609 scm.RunCommand(command, self._options, args, file_list)
610 self._options.revision = None
611 elif isinstance(deps[d], self.FileImpl):
[email protected]491c04b2010-05-17 18:17:44612 file_dep = deps[d]
613 self._options.revision = file_dep.GetRevision()
[email protected]d2e92562010-04-27 01:55:18614 if run_scm:
[email protected]491c04b2010-05-17 18:17:44615 scm = gclient_scm.CreateSCM(file_dep.GetPath(), self._root_dir, d)
[email protected]d2e92562010-04-27 01:55:18616 scm.RunCommand("updatesingle", self._options,
[email protected]491c04b2010-05-17 18:17:44617 args + [file_dep.GetFilename()], file_list)
[email protected]79692d62010-05-14 18:57:13618
[email protected]d2e92562010-04-27 01:55:18619 if command == 'update' and not self._options.verbose:
620 pm.end()
[email protected]6f363722010-04-27 00:41:09621
[email protected]d2e92562010-04-27 01:55:18622 # Second pass for inherited deps (via the From keyword)
623 for d in deps_to_process:
624 if isinstance(deps[d], self.FromImpl):
625 filename = os.path.join(self._root_dir,
626 deps[d].module_name,
[email protected]1f7d1182010-05-17 18:17:38627 self.DEPS_FILE)
[email protected]d2e92562010-04-27 01:55:18628 content = gclient_utils.FileRead(filename)
629 sub_deps = self._ParseSolutionDeps(deps[d].module_name, content, {},
630 False)
631 # Getting the URL from the sub_deps file can involve having to resolve
632 # a File() or having to resolve a relative URL. To resolve relative
633 # URLs, we need to pass in the orignal sub deps URL.
634 sub_deps_base_url = deps[deps[d].module_name]
635 url = deps[d].GetUrl(d, sub_deps_base_url, self._root_dir, sub_deps)
636 entries[d] = url
637 if run_scm:
638 self._options.revision = revision_overrides.get(d)
639 scm = gclient_scm.CreateSCM(url, self._root_dir, d)
640 scm.RunCommand(command, self._options, args, file_list)
641 self._options.revision = None
[email protected]df2d5902009-09-11 22:16:21642
[email protected]d83b2b22009-08-11 15:30:55643 # Convert all absolute paths to relative.
644 for i in range(len(file_list)):
645 # TODO(phajdan.jr): We should know exactly when the paths are absolute.
646 # It depends on the command being executed (like runhooks vs sync).
647 if not os.path.isabs(file_list[i]):
648 continue
649
650 prefix = os.path.commonprefix([self._root_dir.lower(),
651 file_list[i].lower()])
652 file_list[i] = file_list[i][len(prefix):]
653
654 # Strip any leading path separators.
655 while file_list[i].startswith('\\') or file_list[i].startswith('/'):
656 file_list[i] = file_list[i][1:]
[email protected]fb2b8eb2009-04-23 21:03:42657
[email protected]5f3eee32009-09-17 00:34:30658 is_using_git = gclient_utils.IsUsingGit(self._root_dir, entries.keys())
[email protected]fb2b8eb2009-04-23 21:03:42659 self._RunHooks(command, file_list, is_using_git)
660
661 if command == 'update':
[email protected]cdcee802009-06-23 15:30:42662 # Notify the user if there is an orphaned entry in their working copy.
663 # Only delete the directory if there are no changes in it, and
664 # delete_unversioned_trees is set to true.
[email protected]fb2b8eb2009-04-23 21:03:42665 prev_entries = self._ReadEntries()
666 for entry in prev_entries:
[email protected]c5e9aec2009-08-03 18:25:56667 # Fix path separator on Windows.
668 entry_fixed = entry.replace('/', os.path.sep)
669 e_dir = os.path.join(self._root_dir, entry_fixed)
670 # Use entry and not entry_fixed there.
[email protected]0329e672009-05-13 18:41:04671 if entry not in entries and os.path.exists(e_dir):
[email protected]83017012009-09-28 18:52:12672 modified_files = False
[email protected]5aeb7dd2009-11-17 18:09:01673 if isinstance(prev_entries, list):
[email protected]83017012009-09-28 18:52:12674 # old .gclient_entries format was list, now dict
[email protected]5aeb7dd2009-11-17 18:09:01675 modified_files = gclient_scm.scm.SVN.CaptureStatus(e_dir)
[email protected]83017012009-09-28 18:52:12676 else:
677 file_list = []
678 scm = gclient_scm.CreateSCM(prev_entries[entry], self._root_dir,
679 entry_fixed)
680 scm.status(self._options, [], file_list)
681 modified_files = file_list != []
682 if not self._options.delete_unversioned_trees or modified_files:
[email protected]c5e9aec2009-08-03 18:25:56683 # There are modified files in this entry. Keep warning until
684 # removed.
[email protected]c5e9aec2009-08-03 18:25:56685 print(("\nWARNING: \"%s\" is no longer part of this client. "
686 "It is recommended that you manually remove it.\n") %
687 entry_fixed)
[email protected]fb2b8eb2009-04-23 21:03:42688 else:
689 # Delete the entry
[email protected]df7a3132009-05-12 17:49:49690 print("\n________ deleting \'%s\' " +
[email protected]c5e9aec2009-08-03 18:25:56691 "in \'%s\'") % (entry_fixed, self._root_dir)
[email protected]5f3eee32009-09-17 00:34:30692 gclient_utils.RemoveDirectory(e_dir)
[email protected]fb2b8eb2009-04-23 21:03:42693 # record the current list of entries for next time
694 self._SaveEntries(entries)
[email protected]17cdf762010-05-28 17:30:52695 return 0
[email protected]fb2b8eb2009-04-23 21:03:42696
697 def PrintRevInfo(self):
[email protected]5d63eb82010-03-24 23:22:09698 """Output revision info mapping for the client and its dependencies.
699
700 This allows the capture of an overall "revision" for the source tree that
[email protected]918a9ae2010-05-28 15:50:30701 can be used to reproduce the same tree in the future. It is only useful for
702 "unpinned dependencies", i.e. DEPS/deps references without a svn revision
703 number or a git hash. A git branch name isn't "pinned" since the actual
704 commit can change.
[email protected]5d63eb82010-03-24 23:22:09705
706 The --snapshot option allows creating a .gclient file to reproduce the tree.
[email protected]fb2b8eb2009-04-23 21:03:42707 """
[email protected]fb2b8eb2009-04-23 21:03:42708 solutions = self.GetVar("solutions")
709 if not solutions:
[email protected]e3608df2009-11-10 20:22:57710 raise gclient_utils.Error("No solution specified")
[email protected]fb2b8eb2009-04-23 21:03:42711
[email protected]5d63eb82010-03-24 23:22:09712 # Inner helper to generate base url and rev tuple
[email protected]fb2b8eb2009-04-23 21:03:42713 def GetURLAndRev(name, original_url):
[email protected]5d63eb82010-03-24 23:22:09714 url, _ = gclient_utils.SplitUrlRevision(original_url)
715 scm = gclient_scm.CreateSCM(original_url, self._root_dir, name)
716 return (url, scm.revinfo(self._options, [], None))
[email protected]fb2b8eb2009-04-23 21:03:42717
[email protected]e3da35f2010-03-09 21:40:45718 # text of the snapshot gclient file
719 new_gclient = ""
720 # Dictionary of { path : SCM url } to ensure no duplicate solutions
721 solution_names = {}
[email protected]de8f3522010-03-11 23:47:44722 entries = {}
723 entries_deps_content = {}
[email protected]fb2b8eb2009-04-23 21:03:42724 # Run on the base solutions first.
725 for solution in solutions:
[email protected]e3da35f2010-03-09 21:40:45726 # Dictionary of { path : SCM url } to describe the gclient checkout
[email protected]fb2b8eb2009-04-23 21:03:42727 name = solution["name"]
[email protected]e3da35f2010-03-09 21:40:45728 if name in solution_names:
[email protected]e3608df2009-11-10 20:22:57729 raise gclient_utils.Error("solution %s specified more than once" % name)
[email protected]fb2b8eb2009-04-23 21:03:42730 (url, rev) = GetURLAndRev(name, solution["url"])
[email protected]770ff9e2009-09-23 17:18:18731 entries[name] = "%s@%s" % (url, rev)
[email protected]e3da35f2010-03-09 21:40:45732 solution_names[name] = "%s@%s" % (url, rev)
[email protected]491c04b2010-05-17 18:17:44733 deps_file = solution.get("deps_file", self.DEPS_FILE)
[email protected]952d7c72010-03-01 20:41:01734 if '/' in deps_file or '\\' in deps_file:
735 raise gclient_utils.Error('deps_file name must not be a path, just a '
736 'filename.')
737 try:
738 deps_content = gclient_utils.FileRead(
739 os.path.join(self._root_dir, name, deps_file))
740 except IOError, e:
741 if e.errno != errno.ENOENT:
742 raise
743 deps_content = ""
744 entries_deps_content[name] = deps_content
[email protected]fb2b8eb2009-04-23 21:03:42745
[email protected]de8f3522010-03-11 23:47:44746 # Process the dependencies next (sort alphanumerically to ensure that
747 # containing directories get populated first and for readability)
748 deps = self._ParseAllDeps(entries, entries_deps_content)
749 deps_to_process = deps.keys()
750 deps_to_process.sort()
[email protected]fb2b8eb2009-04-23 21:03:42751
[email protected]de8f3522010-03-11 23:47:44752 # First pass for direct dependencies.
753 for d in deps_to_process:
754 if type(deps[d]) == str:
755 (url, rev) = GetURLAndRev(d, deps[d])
756 entries[d] = "%s@%s" % (url, rev)
[email protected]fb2b8eb2009-04-23 21:03:42757
[email protected]de8f3522010-03-11 23:47:44758 # Second pass for inherited deps (via the From keyword)
759 for d in deps_to_process:
[email protected]4b5b1772010-04-08 01:52:56760 if isinstance(deps[d], self.FromImpl):
[email protected]de8f3522010-03-11 23:47:44761 deps_parent_url = entries[deps[d].module_name]
762 if deps_parent_url.find("@") < 0:
763 raise gclient_utils.Error("From %s missing revisioned url" %
764 deps[d].module_name)
765 content = gclient_utils.FileRead(os.path.join(
766 self._root_dir,
767 deps[d].module_name,
[email protected]491c04b2010-05-17 18:17:44768 self.DEPS_FILE))
769 sub_deps = self._ParseSolutionDeps(deps[d].module_name, content, {},
770 False)
[email protected]de8f3522010-03-11 23:47:44771 (url, rev) = GetURLAndRev(d, sub_deps[d])
772 entries[d] = "%s@%s" % (url, rev)
[email protected]e3da35f2010-03-09 21:40:45773
[email protected]de8f3522010-03-11 23:47:44774 # Build the snapshot configuration string
775 if self._options.snapshot:
776 url = entries.pop(name)
777 custom_deps = ",\n ".join(["\"%s\": \"%s\"" % (x, entries[x])
778 for x in sorted(entries.keys())])
[email protected]e3da35f2010-03-09 21:40:45779
[email protected]1f7d1182010-05-17 18:17:38780 new_gclient += self.DEFAULT_SNAPSHOT_SOLUTION_TEXT % {
[email protected]de8f3522010-03-11 23:47:44781 'solution_name': name,
782 'solution_url': url,
783 'safesync_url' : "",
784 'solution_deps': custom_deps,
785 }
786 else:
787 print(";\n".join(["%s: %s" % (x, entries[x])
788 for x in sorted(entries.keys())]))
[email protected]e3da35f2010-03-09 21:40:45789
790 # Print the snapshot configuration file
791 if self._options.snapshot:
[email protected]491c04b2010-05-17 18:17:44792 config = self.DEFAULT_SNAPSHOT_FILE_TEXT % {'solution_list': new_gclient}
[email protected]e3da35f2010-03-09 21:40:45793 snapclient = GClient(self._root_dir, self._options)
794 snapclient.SetConfig(config)
[email protected]116704f2010-06-11 17:34:38795 print(snapclient.config_content)
[email protected]fb2b8eb2009-04-23 21:03:42796
797
[email protected]5ca27692010-05-26 19:32:41798#### gclient commands.
[email protected]fb2b8eb2009-04-23 21:03:42799
800
[email protected]5ca27692010-05-26 19:32:41801def CMDcleanup(parser, args):
802 """Cleans up all working copies.
[email protected]ddff62d2010-05-17 21:02:36803
[email protected]5ca27692010-05-26 19:32:41804Mostly svn-specific. Simply runs 'svn cleanup' for each module.
[email protected]79692d62010-05-14 18:57:13805"""
[email protected]86c0dec2010-05-28 19:01:00806 parser.add_option("--deps", dest="deps_os", metavar="OS_LIST",
807 help="override deps for the specified (comma-separated) "
808 "platform(s); 'all' will process all deps_os "
809 "references")
[email protected]5ca27692010-05-26 19:32:41810 (options, args) = parser.parse_args(args)
[email protected]2806acc2009-05-15 12:33:34811 client = GClient.LoadCurrentConfig(options)
[email protected]fb2b8eb2009-04-23 21:03:42812 if not client:
[email protected]e3608df2009-11-10 20:22:57813 raise gclient_utils.Error("client not configured; see 'gclient config'")
[email protected]fb2b8eb2009-04-23 21:03:42814 if options.verbose:
815 # Print out the .gclient file. This is longer than if we just printed the
816 # client dict, but more legible, and it might contain helpful comments.
[email protected]116704f2010-06-11 17:34:38817 print(client.config_content)
[email protected]fb2b8eb2009-04-23 21:03:42818 return client.RunOnDeps('cleanup', args)
819
820
[email protected]5ca27692010-05-26 19:32:41821@attr('usage', '[url] [safesync url]')
822def CMDconfig(parser, args):
[email protected]ddff62d2010-05-17 21:02:36823 """Create a .gclient file in the current directory.
824
[email protected]5ca27692010-05-26 19:32:41825This specifies the configuration for further commands. After update/sync,
[email protected]79692d62010-05-14 18:57:13826top-level DEPS files in each module are read to determine dependent
[email protected]5ca27692010-05-26 19:32:41827modules to operate on as well. If optional [url] parameter is
[email protected]79692d62010-05-14 18:57:13828provided, then configuration is read from a specified Subversion server
[email protected]5ca27692010-05-26 19:32:41829URL.
[email protected]79692d62010-05-14 18:57:13830"""
[email protected]5ca27692010-05-26 19:32:41831 parser.add_option("--spec",
832 help="create a gclient file containing the provided "
833 "string. Due to Cygwin/Python brokenness, it "
834 "probably can't contain any newlines.")
835 parser.add_option("--name",
836 help="overrides the default name for the solution")
837 (options, args) = parser.parse_args(args)
[email protected]5fc2a332010-05-26 19:37:15838 if ((options.spec and args) or len(args) > 2 or
839 (not options.spec and not args)):
840 parser.error('Inconsistent arguments. Use either --spec or one or 2 args')
841
[email protected]0329e672009-05-13 18:41:04842 if os.path.exists(options.config_filename):
[email protected]e3608df2009-11-10 20:22:57843 raise gclient_utils.Error("%s file already exists in the current directory"
844 % options.config_filename)
[email protected]2806acc2009-05-15 12:33:34845 client = GClient('.', options)
[email protected]fb2b8eb2009-04-23 21:03:42846 if options.spec:
847 client.SetConfig(options.spec)
848 else:
[email protected]1ab7ffc2009-06-03 17:21:37849 base_url = args[0].rstrip('/')
[email protected]8cf7a392010-04-07 17:20:26850 if not options.name:
851 name = base_url.split("/")[-1]
852 else:
853 # specify an alternate relpath for the given URL.
854 name = options.name
[email protected]fb2b8eb2009-04-23 21:03:42855 safesync_url = ""
856 if len(args) > 1:
857 safesync_url = args[1]
858 client.SetDefaultConfig(name, base_url, safesync_url)
859 client.SaveConfig()
[email protected]79692d62010-05-14 18:57:13860 return 0
[email protected]fb2b8eb2009-04-23 21:03:42861
862
[email protected]5ca27692010-05-26 19:32:41863def CMDexport(parser, args):
[email protected]ddff62d2010-05-17 21:02:36864 """Wrapper for svn export for all managed directories."""
[email protected]86c0dec2010-05-28 19:01:00865 parser.add_option("--deps", dest="deps_os", metavar="OS_LIST",
866 help="override deps for the specified (comma-separated) "
867 "platform(s); 'all' will process all deps_os "
868 "references")
[email protected]5ca27692010-05-26 19:32:41869 (options, args) = parser.parse_args(args)
[email protected]644aa0c2009-07-17 20:20:41870 if len(args) != 1:
[email protected]e3608df2009-11-10 20:22:57871 raise gclient_utils.Error("Need directory name")
[email protected]644aa0c2009-07-17 20:20:41872 client = GClient.LoadCurrentConfig(options)
873
874 if not client:
[email protected]e3608df2009-11-10 20:22:57875 raise gclient_utils.Error("client not configured; see 'gclient config'")
[email protected]644aa0c2009-07-17 20:20:41876
877 if options.verbose:
878 # Print out the .gclient file. This is longer than if we just printed the
879 # client dict, but more legible, and it might contain helpful comments.
[email protected]116704f2010-06-11 17:34:38880 print(client.config_content)
[email protected]644aa0c2009-07-17 20:20:41881 return client.RunOnDeps('export', args)
882
[email protected]fb2b8eb2009-04-23 21:03:42883
[email protected]5ca27692010-05-26 19:32:41884@attr('epilog', """Example:
885 gclient pack > patch.txt
886 generate simple patch for configured client and dependences
887""")
888def CMDpack(parser, args):
[email protected]79692d62010-05-14 18:57:13889 """Generate a patch which can be applied at the root of the tree.
[email protected]ddff62d2010-05-17 21:02:36890
[email protected]5ca27692010-05-26 19:32:41891Internally, runs 'svn diff'/'git diff' on each checked out module and
[email protected]79692d62010-05-14 18:57:13892dependencies, and performs minimal postprocessing of the output. The
893resulting patch is printed to stdout and can be applied to a freshly
[email protected]5ca27692010-05-26 19:32:41894checked out tree via 'patch -p0 < patchfile'.
[email protected]79692d62010-05-14 18:57:13895"""
[email protected]86c0dec2010-05-28 19:01:00896 parser.add_option("--deps", dest="deps_os", metavar="OS_LIST",
897 help="override deps for the specified (comma-separated) "
898 "platform(s); 'all' will process all deps_os "
899 "references")
[email protected]5ca27692010-05-26 19:32:41900 (options, args) = parser.parse_args(args)
[email protected]ab318592009-09-04 00:54:55901 client = GClient.LoadCurrentConfig(options)
902 if not client:
[email protected]e3608df2009-11-10 20:22:57903 raise gclient_utils.Error("client not configured; see 'gclient config'")
[email protected]ab318592009-09-04 00:54:55904 if options.verbose:
905 # Print out the .gclient file. This is longer than if we just printed the
906 # client dict, but more legible, and it might contain helpful comments.
[email protected]116704f2010-06-11 17:34:38907 print(client.config_content)
[email protected]ab318592009-09-04 00:54:55908 return client.RunOnDeps('pack', args)
909
910
[email protected]5ca27692010-05-26 19:32:41911def CMDstatus(parser, args):
912 """Show modification status for every dependencies."""
[email protected]6b1d00b2010-05-26 20:11:08913 parser.add_option("--deps", dest="deps_os", metavar="OS_LIST",
[email protected]86c0dec2010-05-28 19:01:00914 help="override deps for the specified (comma-separated) "
915 "platform(s); 'all' will process all deps_os "
916 "references")
[email protected]5ca27692010-05-26 19:32:41917 (options, args) = parser.parse_args(args)
[email protected]2806acc2009-05-15 12:33:34918 client = GClient.LoadCurrentConfig(options)
[email protected]fb2b8eb2009-04-23 21:03:42919 if not client:
[email protected]e3608df2009-11-10 20:22:57920 raise gclient_utils.Error("client not configured; see 'gclient config'")
[email protected]fb2b8eb2009-04-23 21:03:42921 if options.verbose:
922 # Print out the .gclient file. This is longer than if we just printed the
923 # client dict, but more legible, and it might contain helpful comments.
[email protected]116704f2010-06-11 17:34:38924 print(client.config_content)
[email protected]fb2b8eb2009-04-23 21:03:42925 return client.RunOnDeps('status', args)
926
927
[email protected]5ca27692010-05-26 19:32:41928@attr('epilog', """Examples:
[email protected]79692d62010-05-14 18:57:13929 gclient sync
930 update files from SCM according to current configuration,
931 *for modules which have changed since last update or sync*
932 gclient sync --force
933 update files from SCM according to current configuration, for
934 all modules (useful for recovering files deleted from local copy)
935 gclient sync --revision src@31000
936 update src directory to r31000
[email protected]5ca27692010-05-26 19:32:41937""")
938def CMDsync(parser, args):
939 """Checkout/update all modules."""
[email protected]c0b8a4e2010-06-02 17:49:39940 parser.add_option("-f", "--force", action="store_true",
[email protected]5ca27692010-05-26 19:32:41941 help="force update even for unchanged modules")
[email protected]c0b8a4e2010-06-02 17:49:39942 parser.add_option("-n", "--nohooks", action="store_true",
[email protected]5ca27692010-05-26 19:32:41943 help="don't run hooks after the update is complete")
944 parser.add_option("-r", "--revision", action="append",
945 dest="revisions", metavar="REV", default=[],
[email protected]307d1792010-05-31 20:03:13946 help="Enforces revision/hash for the solutions with the "
947 "format src@rev. The src@ part is optional and can be "
948 "skipped. -r can be used multiple times when .gclient "
949 "has multiple solutions configured and will work even "
950 "if the src@ part is skipped.")
[email protected]c0b8a4e2010-06-02 17:49:39951 parser.add_option("-H", "--head", action="store_true",
[email protected]5ca27692010-05-26 19:32:41952 help="skips any safesync_urls specified in "
953 "configured solutions and sync to head instead")
[email protected]c0b8a4e2010-06-02 17:49:39954 parser.add_option("-D", "--delete_unversioned_trees", action="store_true",
[email protected]5ca27692010-05-26 19:32:41955 help="delete any unexpected unversioned trees "
956 "that are in the checkout")
[email protected]c0b8a4e2010-06-02 17:49:39957 parser.add_option("-R", "--reset", action="store_true",
[email protected]5ca27692010-05-26 19:32:41958 help="resets any local changes before updating (git only)")
959 parser.add_option("--deps", dest="deps_os", metavar="OS_LIST",
[email protected]86c0dec2010-05-28 19:01:00960 help="override deps for the specified (comma-separated) "
961 "platform(s); 'all' will process all deps_os "
962 "references")
[email protected]c0b8a4e2010-06-02 17:49:39963 parser.add_option("-m", "--manually_grab_svn_rev", action="store_true",
[email protected]5ca27692010-05-26 19:32:41964 help="Skip svn up whenever possible by requesting "
965 "actual HEAD revision from the repository")
966 (options, args) = parser.parse_args(args)
[email protected]2806acc2009-05-15 12:33:34967 client = GClient.LoadCurrentConfig(options)
[email protected]fb2b8eb2009-04-23 21:03:42968
969 if not client:
[email protected]e3608df2009-11-10 20:22:57970 raise gclient_utils.Error("client not configured; see 'gclient config'")
[email protected]fb2b8eb2009-04-23 21:03:42971
[email protected]307d1792010-05-31 20:03:13972 if options.revisions and options.head:
973 # TODO(maruel): Make it a parser.error if it doesn't break any builder.
974 print("Warning: you cannot use both --head and --revision")
[email protected]fb2b8eb2009-04-23 21:03:42975
976 if options.verbose:
977 # Print out the .gclient file. This is longer than if we just printed the
978 # client dict, but more legible, and it might contain helpful comments.
[email protected]116704f2010-06-11 17:34:38979 print(client.config_content)
[email protected]fb2b8eb2009-04-23 21:03:42980 return client.RunOnDeps('update', args)
981
982
[email protected]5ca27692010-05-26 19:32:41983def CMDupdate(parser, args):
[email protected]ddff62d2010-05-17 21:02:36984 """Alias for the sync command. Deprecated."""
[email protected]5ca27692010-05-26 19:32:41985 return CMDsync(parser, args)
[email protected]fb2b8eb2009-04-23 21:03:42986
[email protected]5ca27692010-05-26 19:32:41987def CMDdiff(parser, args):
988 """Displays local diff for every dependencies."""
[email protected]86c0dec2010-05-28 19:01:00989 parser.add_option("--deps", dest="deps_os", metavar="OS_LIST",
990 help="override deps for the specified (comma-separated) "
991 "platform(s); 'all' will process all deps_os "
992 "references")
[email protected]5ca27692010-05-26 19:32:41993 (options, args) = parser.parse_args(args)
[email protected]2806acc2009-05-15 12:33:34994 client = GClient.LoadCurrentConfig(options)
[email protected]fb2b8eb2009-04-23 21:03:42995 if not client:
[email protected]e3608df2009-11-10 20:22:57996 raise gclient_utils.Error("client not configured; see 'gclient config'")
[email protected]fb2b8eb2009-04-23 21:03:42997 if options.verbose:
998 # Print out the .gclient file. This is longer than if we just printed the
999 # client dict, but more legible, and it might contain helpful comments.
[email protected]116704f2010-06-11 17:34:381000 print(client.config_content)
[email protected]fb2b8eb2009-04-23 21:03:421001 return client.RunOnDeps('diff', args)
1002
1003
[email protected]5ca27692010-05-26 19:32:411004def CMDrevert(parser, args):
1005 """Revert all modifications in every dependencies."""
[email protected]6b1d00b2010-05-26 20:11:081006 parser.add_option("--deps", dest="deps_os", metavar="OS_LIST",
[email protected]86c0dec2010-05-28 19:01:001007 help="override deps for the specified (comma-separated) "
1008 "platform(s); 'all' will process all deps_os "
1009 "references")
[email protected]c0b8a4e2010-06-02 17:49:391010 parser.add_option("-n", "--nohooks", action="store_true",
[email protected]5ca27692010-05-26 19:32:411011 help="don't run hooks after the revert is complete")
1012 (options, args) = parser.parse_args(args)
1013 # --force is implied.
1014 options.force = True
[email protected]2806acc2009-05-15 12:33:341015 client = GClient.LoadCurrentConfig(options)
[email protected]fb2b8eb2009-04-23 21:03:421016 if not client:
[email protected]e3608df2009-11-10 20:22:571017 raise gclient_utils.Error("client not configured; see 'gclient config'")
[email protected]fb2b8eb2009-04-23 21:03:421018 return client.RunOnDeps('revert', args)
1019
1020
[email protected]5ca27692010-05-26 19:32:411021def CMDrunhooks(parser, args):
1022 """Runs hooks for files that have been modified in the local working copy."""
[email protected]6b1d00b2010-05-26 20:11:081023 parser.add_option("--deps", dest="deps_os", metavar="OS_LIST",
[email protected]86c0dec2010-05-28 19:01:001024 help="override deps for the specified (comma-separated) "
1025 "platform(s); 'all' will process all deps_os "
1026 "references")
[email protected]c0b8a4e2010-06-02 17:49:391027 parser.add_option("-f", "--force", action="store_true", default=True,
[email protected]5ca27692010-05-26 19:32:411028 help="Deprecated. No effect.")
1029 (options, args) = parser.parse_args(args)
[email protected]2806acc2009-05-15 12:33:341030 client = GClient.LoadCurrentConfig(options)
[email protected]fb2b8eb2009-04-23 21:03:421031 if not client:
[email protected]e3608df2009-11-10 20:22:571032 raise gclient_utils.Error("client not configured; see 'gclient config'")
[email protected]fb2b8eb2009-04-23 21:03:421033 if options.verbose:
1034 # Print out the .gclient file. This is longer than if we just printed the
1035 # client dict, but more legible, and it might contain helpful comments.
[email protected]116704f2010-06-11 17:34:381036 print(client.config_content)
[email protected]5df6a462009-08-28 18:52:261037 options.force = True
[email protected]5ca27692010-05-26 19:32:411038 options.nohooks = False
[email protected]fb2b8eb2009-04-23 21:03:421039 return client.RunOnDeps('runhooks', args)
1040
1041
[email protected]5ca27692010-05-26 19:32:411042def CMDrevinfo(parser, args):
1043 """Outputs details for every dependencies."""
[email protected]86c0dec2010-05-28 19:01:001044 parser.add_option("--deps", dest="deps_os", metavar="OS_LIST",
1045 help="override deps for the specified (comma-separated) "
1046 "platform(s); 'all' will process all deps_os "
1047 "references")
[email protected]c0b8a4e2010-06-02 17:49:391048 parser.add_option("-s", "--snapshot", action="store_true",
[email protected]5ca27692010-05-26 19:32:411049 help="create a snapshot file of the current "
1050 "version of all repositories")
1051 (options, args) = parser.parse_args(args)
[email protected]2806acc2009-05-15 12:33:341052 client = GClient.LoadCurrentConfig(options)
[email protected]fb2b8eb2009-04-23 21:03:421053 if not client:
[email protected]e3608df2009-11-10 20:22:571054 raise gclient_utils.Error("client not configured; see 'gclient config'")
[email protected]fb2b8eb2009-04-23 21:03:421055 client.PrintRevInfo()
[email protected]79692d62010-05-14 18:57:131056 return 0
[email protected]fb2b8eb2009-04-23 21:03:421057
1058
[email protected]5ca27692010-05-26 19:32:411059def Command(name):
1060 return getattr(sys.modules[__name__], 'CMD' + name, None)
1061
1062
1063def CMDhelp(parser, args):
1064 """Prints list of commands or help for a specific command."""
[email protected]6e29d572010-06-04 17:32:201065 (_, args) = parser.parse_args(args)
[email protected]ddff62d2010-05-17 21:02:361066 if len(args) == 1:
[email protected]5ca27692010-05-26 19:32:411067 return Main(args + ['--help'])
[email protected]ddff62d2010-05-17 21:02:361068 parser.print_help()
1069 return 0
1070
1071
[email protected]5ca27692010-05-26 19:32:411072def GenUsage(parser, command):
1073 """Modify an OptParse object with the function's documentation."""
1074 obj = Command(command)
1075 if command == 'help':
1076 command = '<command>'
1077 # OptParser.description prefer nicely non-formatted strings.
1078 parser.description = re.sub('[\r\n ]{2,}', ' ', obj.__doc__)
1079 usage = getattr(obj, 'usage', '')
1080 parser.set_usage('%%prog %s [options] %s' % (command, usage))
1081 parser.epilog = getattr(obj, 'epilog', None)
[email protected]fb2b8eb2009-04-23 21:03:421082
1083
1084def Main(argv):
[email protected]5ca27692010-05-26 19:32:411085 """Doesn't parse the arguments here, just find the right subcommand to
1086 execute."""
[email protected]6e29d572010-06-04 17:32:201087 try:
1088 # Do it late so all commands are listed.
1089 CMDhelp.usage = ('\n\nCommands are:\n' + '\n'.join([
1090 ' %-10s %s' % (fn[3:], Command(fn[3:]).__doc__.split('\n')[0].strip())
1091 for fn in dir(sys.modules[__name__]) if fn.startswith('CMD')]))
1092 parser = optparse.OptionParser(version='%prog ' + __version__)
[email protected]f0fc9912010-06-11 17:57:331093 parser.add_option('-v', '--verbose', action='count', default=0,
1094 help='Produces additional output for diagnostics. Can be '
1095 'used up to three times for more logging info.')
1096 parser.add_option('--gclientfile', dest='config_filename',
1097 default=os.environ.get('GCLIENT_FILE', '.gclient'),
1098 help='Specify an alternate %default file')
[email protected]6e29d572010-06-04 17:32:201099 # Integrate standard options processing.
1100 old_parser = parser.parse_args
1101 def Parse(args):
1102 (options, args) = old_parser(args)
[email protected]f0fc9912010-06-11 17:57:331103 level = None
[email protected]6e29d572010-06-04 17:32:201104 if options.verbose == 2:
[email protected]f0fc9912010-06-11 17:57:331105 level = logging.INFO
[email protected]6e29d572010-06-04 17:32:201106 elif options.verbose > 2:
[email protected]f0fc9912010-06-11 17:57:331107 level = logging.DEBUG
1108 logging.basicConfig(level=level,
1109 format='%(module)s(%(lineno)d) %(funcName)s:%(message)s')
1110 options.entries_filename = options.config_filename + '_entries'
[email protected]6e29d572010-06-04 17:32:201111 if not hasattr(options, 'revisions'):
1112 # GClient.RunOnDeps expects it even if not applicable.
1113 options.revisions = []
1114 if not hasattr(options, 'head'):
1115 options.head = None
[email protected]f0fc9912010-06-11 17:57:331116 if not hasattr(options, 'nohooks'):
1117 options.nohooks = True
1118 if not hasattr(options, 'deps_os'):
1119 options.deps_os = None
[email protected]6e29d572010-06-04 17:32:201120 return (options, args)
1121 parser.parse_args = Parse
1122 # We don't want wordwrapping in epilog (usually examples)
1123 parser.format_epilog = lambda _: parser.epilog or ''
1124 if argv:
1125 command = Command(argv[0])
1126 if command:
[email protected]f0fc9912010-06-11 17:57:331127 # 'fix' the usage and the description now that we know the subcommand.
[email protected]6e29d572010-06-04 17:32:201128 GenUsage(parser, argv[0])
1129 return command(parser, argv[1:])
1130 # Not a known command. Default to help.
1131 GenUsage(parser, 'help')
1132 return CMDhelp(parser, argv)
1133 except gclient_utils.Error, e:
[email protected]f0fc9912010-06-11 17:57:331134 print >> sys.stderr, 'Error: %s' % str(e)
[email protected]6e29d572010-06-04 17:32:201135 return 1
[email protected]fb2b8eb2009-04-23 21:03:421136
1137
[email protected]f0fc9912010-06-11 17:57:331138if '__main__' == __name__:
[email protected]6e29d572010-06-04 17:32:201139 sys.exit(Main(sys.argv[1:]))
[email protected]fb2b8eb2009-04-23 21:03:421140
1141# vim: ts=2:sw=2:tw=80:et: