blob: 058865db9ecd7efd026c3362ac1f1ed01e04bbdc [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."""
[email protected]9eda4112010-06-11 18:56:10160 DEPS_FILE = 'DEPS'
[email protected]fb2b8eb2009-04-23 21:03:42161
[email protected]116704f2010-06-11 17:34:38162 SUPPORTED_COMMANDS = [
[email protected]ab318592009-09-04 00:54:55163 'cleanup', 'diff', 'export', 'pack', 'revert', 'status', 'update',
164 'runhooks'
[email protected]fb2b8eb2009-04-23 21:03:42165 ]
166
[email protected]116704f2010-06-11 17:34:38167 DEPS_OS_CHOICES = {
[email protected]491c04b2010-05-17 18:17:44168 "win32": "win",
169 "win": "win",
170 "cygwin": "win",
171 "darwin": "mac",
172 "mac": "mac",
173 "unix": "unix",
174 "linux": "unix",
175 "linux2": "unix",
176 }
177
[email protected]1f7d1182010-05-17 18:17:38178 DEFAULT_CLIENT_FILE_TEXT = ("""\
179solutions = [
180 { "name" : "%(solution_name)s",
181 "url" : "%(solution_url)s",
182 "custom_deps" : {
183 },
184 "safesync_url": "%(safesync_url)s"
185 },
186]
187""")
188
189 DEFAULT_SNAPSHOT_SOLUTION_TEXT = ("""\
190 { "name" : "%(solution_name)s",
191 "url" : "%(solution_url)s",
192 "custom_deps" : {
193 %(solution_deps)s,
194 },
195 "safesync_url": "%(safesync_url)s"
196 },
197""")
198
199 DEFAULT_SNAPSHOT_FILE_TEXT = ("""\
200# Snapshot generated with gclient revinfo --snapshot
201solutions = [
202%(solution_list)s
203]
204""")
205
[email protected]fb2b8eb2009-04-23 21:03:42206 def __init__(self, root_dir, options):
207 self._root_dir = root_dir
208 self._options = options
[email protected]116704f2010-06-11 17:34:38209 self.config_content = None
[email protected]fb2b8eb2009-04-23 21:03:42210 self._config_dict = {}
211 self._deps_hooks = []
212
213 def SetConfig(self, content):
214 self._config_dict = {}
[email protected]116704f2010-06-11 17:34:38215 self.config_content = content
[email protected]df0032c2009-05-29 10:43:56216 try:
217 exec(content, self._config_dict)
218 except SyntaxError, e:
219 try:
[email protected]e3608df2009-11-10 20:22:57220 __pychecker__ = 'no-objattrs'
[email protected]df0032c2009-05-29 10:43:56221 # Try to construct a human readable error message
222 error_message = [
223 'There is a syntax error in your configuration file.',
224 'Line #%s, character %s:' % (e.lineno, e.offset),
225 '"%s"' % re.sub(r'[\r\n]*$', '', e.text) ]
226 except:
227 # Something went wrong, re-raise the original exception
228 raise e
229 else:
230 # Raise a new exception with the human readable message:
[email protected]e3608df2009-11-10 20:22:57231 raise gclient_utils.Error('\n'.join(error_message))
[email protected]fb2b8eb2009-04-23 21:03:42232
233 def SaveConfig(self):
[email protected]e3608df2009-11-10 20:22:57234 gclient_utils.FileWrite(os.path.join(self._root_dir,
235 self._options.config_filename),
[email protected]116704f2010-06-11 17:34:38236 self.config_content)
[email protected]fb2b8eb2009-04-23 21:03:42237
238 def _LoadConfig(self):
[email protected]e3608df2009-11-10 20:22:57239 client_source = gclient_utils.FileRead(
240 os.path.join(self._root_dir, self._options.config_filename))
[email protected]fb2b8eb2009-04-23 21:03:42241 self.SetConfig(client_source)
242
[email protected]fb2b8eb2009-04-23 21:03:42243 def GetVar(self, key, default=None):
244 return self._config_dict.get(key, default)
245
246 @staticmethod
247 def LoadCurrentConfig(options, from_dir=None):
248 """Searches for and loads a .gclient file relative to the current working
249 dir.
250
251 Returns:
252 A dict representing the contents of the .gclient file or an empty dict if
253 the .gclient file doesn't exist.
254 """
255 if not from_dir:
256 from_dir = os.curdir
257 path = os.path.realpath(from_dir)
[email protected]0329e672009-05-13 18:41:04258 while not os.path.exists(os.path.join(path, options.config_filename)):
[email protected]55e724e2010-03-11 19:36:49259 split_path = os.path.split(path)
260 if not split_path[1]:
[email protected]fb2b8eb2009-04-23 21:03:42261 return None
[email protected]55e724e2010-03-11 19:36:49262 path = split_path[0]
[email protected]2806acc2009-05-15 12:33:34263 client = GClient(path, options)
[email protected]fb2b8eb2009-04-23 21:03:42264 client._LoadConfig()
265 return client
266
267 def SetDefaultConfig(self, solution_name, solution_url, safesync_url):
[email protected]1f7d1182010-05-17 18:17:38268 self.SetConfig(self.DEFAULT_CLIENT_FILE_TEXT % {
[email protected]df2d5902009-09-11 22:16:21269 'solution_name': solution_name,
270 'solution_url': solution_url,
271 'safesync_url' : safesync_url,
272 })
[email protected]fb2b8eb2009-04-23 21:03:42273
274 def _SaveEntries(self, entries):
275 """Creates a .gclient_entries file to record the list of unique checkouts.
276
277 The .gclient_entries file lives in the same directory as .gclient.
278
279 Args:
280 entries: A sequence of solution names.
281 """
[email protected]e41f6822010-04-08 16:37:06282 # Sometimes pprint.pformat will use {', sometimes it'll use { ' ... It
283 # makes testing a bit too fun.
284 result = pprint.pformat(entries, 2)
285 if result.startswith('{\''):
[email protected]1edec4d2010-04-08 17:17:06286 result = '{ \'' + result[2:]
[email protected]e41f6822010-04-08 16:37:06287 text = "entries = \\\n" + result + '\n'
[email protected]2e38de72009-09-28 17:04:47288 file_path = os.path.join(self._root_dir, self._options.entries_filename)
[email protected]e3608df2009-11-10 20:22:57289 gclient_utils.FileWrite(file_path, text)
[email protected]fb2b8eb2009-04-23 21:03:42290
291 def _ReadEntries(self):
292 """Read the .gclient_entries file for the given client.
293
294 Args:
295 client: The client for which the entries file should be read.
296
297 Returns:
298 A sequence of solution names, which will be empty if there is the
299 entries file hasn't been created yet.
300 """
301 scope = {}
302 filename = os.path.join(self._root_dir, self._options.entries_filename)
[email protected]0329e672009-05-13 18:41:04303 if not os.path.exists(filename):
[email protected]fb2b8eb2009-04-23 21:03:42304 return []
[email protected]e3608df2009-11-10 20:22:57305 exec(gclient_utils.FileRead(filename), scope)
[email protected]fb2b8eb2009-04-23 21:03:42306 return scope["entries"]
307
[email protected]fb2b8eb2009-04-23 21:03:42308 def _ParseSolutionDeps(self, solution_name, solution_deps_content,
[email protected]30ef9ae2010-04-09 02:18:05309 custom_vars, parse_hooks):
[email protected]fb2b8eb2009-04-23 21:03:42310 """Parses the DEPS file for the specified solution.
311
312 Args:
313 solution_name: The name of the solution to query.
314 solution_deps_content: Content of the DEPS file for the solution
315 custom_vars: A dict of vars to override any vars defined in the DEPS file.
316
317 Returns:
318 A dict mapping module names (as relative paths) to URLs or an empty
319 dict if the solution does not have a DEPS file.
320 """
321 # Skip empty
322 if not solution_deps_content:
323 return {}
324 # Eval the content
325 local_scope = {}
[email protected]116704f2010-06-11 17:34:38326 var = self.VarImpl(custom_vars, local_scope)
[email protected]4b5b1772010-04-08 01:52:56327 global_scope = {
328 "File": self.FileImpl,
329 "From": self.FromImpl,
330 "Var": var.Lookup,
331 "deps_os": {},
332 }
[email protected]fb2b8eb2009-04-23 21:03:42333 exec(solution_deps_content, global_scope, local_scope)
334 deps = local_scope.get("deps", {})
335
336 # load os specific dependencies if defined. these dependencies may
337 # override or extend the values defined by the 'deps' member.
338 if "deps_os" in local_scope:
[email protected]fb2b8eb2009-04-23 21:03:42339 if self._options.deps_os is not None:
340 deps_to_include = self._options.deps_os.split(",")
341 if "all" in deps_to_include:
[email protected]116704f2010-06-11 17:34:38342 deps_to_include = list(set(self.DEPS_OS_CHOICES.itervalues()))
[email protected]fb2b8eb2009-04-23 21:03:42343 else:
[email protected]116704f2010-06-11 17:34:38344 deps_to_include = [self.DEPS_OS_CHOICES.get(sys.platform, "unix")]
[email protected]fb2b8eb2009-04-23 21:03:42345
346 deps_to_include = set(deps_to_include)
347 for deps_os_key in deps_to_include:
348 os_deps = local_scope["deps_os"].get(deps_os_key, {})
349 if len(deps_to_include) > 1:
350 # Ignore any overrides when including deps for more than one
351 # platform, so we collect the broadest set of dependencies available.
352 # We may end up with the wrong revision of something for our
353 # platform, but this is the best we can do.
354 deps.update([x for x in os_deps.items() if not x[0] in deps])
355 else:
356 deps.update(os_deps)
357
[email protected]30ef9ae2010-04-09 02:18:05358 if 'hooks' in local_scope and parse_hooks:
[email protected]fb2b8eb2009-04-23 21:03:42359 self._deps_hooks.extend(local_scope['hooks'])
360
361 # If use_relative_paths is set in the DEPS file, regenerate
362 # the dictionary using paths relative to the directory containing
363 # the DEPS file.
364 if local_scope.get('use_relative_paths'):
365 rel_deps = {}
366 for d, url in deps.items():
367 # normpath is required to allow DEPS to use .. in their
368 # dependency local path.
369 rel_deps[os.path.normpath(os.path.join(solution_name, d))] = url
370 return rel_deps
371 else:
372 return deps
373
374 def _ParseAllDeps(self, solution_urls, solution_deps_content):
375 """Parse the complete list of dependencies for the client.
376
377 Args:
378 solution_urls: A dict mapping module names (as relative paths) to URLs
379 corresponding to the solutions specified by the client. This parameter
380 is passed as an optimization.
381 solution_deps_content: A dict mapping module names to the content
382 of their DEPS files
383
384 Returns:
385 A dict mapping module names (as relative paths) to URLs corresponding
386 to the entire set of dependencies to checkout for the given client.
387
388 Raises:
389 Error: If a dependency conflicts with another dependency or of a solution.
390 """
391 deps = {}
392 for solution in self.GetVar("solutions"):
393 custom_vars = solution.get("custom_vars", {})
394 solution_deps = self._ParseSolutionDeps(
395 solution["name"],
396 solution_deps_content[solution["name"]],
[email protected]30ef9ae2010-04-09 02:18:05397 custom_vars,
398 True)
[email protected]fb2b8eb2009-04-23 21:03:42399
400 # If a line is in custom_deps, but not in the solution, we want to append
401 # this line to the solution.
402 if "custom_deps" in solution:
403 for d in solution["custom_deps"]:
404 if d not in solution_deps:
405 solution_deps[d] = solution["custom_deps"][d]
406
407 for d in solution_deps:
408 if "custom_deps" in solution and d in solution["custom_deps"]:
409 # Dependency is overriden.
410 url = solution["custom_deps"][d]
411 if url is None:
412 continue
413 else:
414 url = solution_deps[d]
415 # if we have a From reference dependent on another solution, then
416 # just skip the From reference. When we pull deps for the solution,
417 # we will take care of this dependency.
418 #
419 # If multiple solutions all have the same From reference, then we
420 # should only add one to our list of dependencies.
[email protected]4b5b1772010-04-08 01:52:56421 if isinstance(url, self.FromImpl):
[email protected]fb2b8eb2009-04-23 21:03:42422 if url.module_name in solution_urls:
423 # Already parsed.
424 continue
425 if d in deps and type(deps[d]) != str:
426 if url.module_name == deps[d].module_name:
427 continue
[email protected]4b5b1772010-04-08 01:52:56428 elif isinstance(url, str):
[email protected]fb2b8eb2009-04-23 21:03:42429 parsed_url = urlparse.urlparse(url)
430 scheme = parsed_url[0]
431 if not scheme:
432 # A relative url. Fetch the real base.
433 path = parsed_url[2]
434 if path[0] != "/":
[email protected]e3608df2009-11-10 20:22:57435 raise gclient_utils.Error(
[email protected]fb2b8eb2009-04-23 21:03:42436 "relative DEPS entry \"%s\" must begin with a slash" % d)
[email protected]e6f78352010-01-13 17:05:33437 # Create a scm just to query the full url.
438 scm = gclient_scm.CreateSCM(solution["url"], self._root_dir,
439 None)
440 url = scm.FullUrlForRelativeUrl(url)
[email protected]fb2b8eb2009-04-23 21:03:42441 if d in deps and deps[d] != url:
[email protected]e3608df2009-11-10 20:22:57442 raise gclient_utils.Error(
[email protected]fb2b8eb2009-04-23 21:03:42443 "Solutions have conflicting versions of dependency \"%s\"" % d)
444 if d in solution_urls and solution_urls[d] != url:
[email protected]e3608df2009-11-10 20:22:57445 raise gclient_utils.Error(
[email protected]fb2b8eb2009-04-23 21:03:42446 "Dependency \"%s\" conflicts with specified solution" % d)
447 # Grab the dependency.
448 deps[d] = url
449 return deps
450
[email protected]71b40682009-07-31 23:40:09451 def _RunHookAction(self, hook_dict, matching_file_list):
[email protected]fb2b8eb2009-04-23 21:03:42452 """Runs the action from a single hook.
453 """
[email protected]5ca27692010-05-26 19:32:41454 logging.info(hook_dict)
455 logging.info(matching_file_list)
[email protected]fb2b8eb2009-04-23 21:03:42456 command = hook_dict['action'][:]
457 if command[0] == 'python':
458 # If the hook specified "python" as the first item, the action is a
459 # Python script. Run it by starting a new copy of the same
460 # interpreter.
461 command[0] = sys.executable
462
[email protected]71b40682009-07-31 23:40:09463 if '$matching_files' in command:
[email protected]68f2e092009-08-06 17:05:35464 splice_index = command.index('$matching_files')
465 command[splice_index:splice_index + 1] = matching_file_list
[email protected]71b40682009-07-31 23:40:09466
[email protected]fb2b8eb2009-04-23 21:03:42467 # Use a discrete exit status code of 2 to indicate that a hook action
468 # failed. Users of this script may wish to treat hook action failures
469 # differently from VC failures.
[email protected]5f3eee32009-09-17 00:34:30470 gclient_utils.SubprocessCall(command, self._root_dir, fail_status=2)
[email protected]fb2b8eb2009-04-23 21:03:42471
472 def _RunHooks(self, command, file_list, is_using_git):
473 """Evaluates all hooks, running actions as needed.
474 """
475 # Hooks only run for these command types.
476 if not command in ('update', 'revert', 'runhooks'):
477 return
478
[email protected]67820ef2009-07-27 17:23:00479 # Hooks only run when --nohooks is not specified
480 if self._options.nohooks:
481 return
482
[email protected]fb2b8eb2009-04-23 21:03:42483 # Get any hooks from the .gclient file.
484 hooks = self.GetVar("hooks", [])
485 # Add any hooks found in DEPS files.
486 hooks.extend(self._deps_hooks)
487
488 # If "--force" was specified, run all hooks regardless of what files have
489 # changed. If the user is using git, then we don't know what files have
490 # changed so we always run all hooks.
491 if self._options.force or is_using_git:
492 for hook_dict in hooks:
[email protected]71b40682009-07-31 23:40:09493 self._RunHookAction(hook_dict, [])
[email protected]fb2b8eb2009-04-23 21:03:42494 return
495
496 # Run hooks on the basis of whether the files from the gclient operation
497 # match each hook's pattern.
498 for hook_dict in hooks:
499 pattern = re.compile(hook_dict['pattern'])
[email protected]e3608df2009-11-10 20:22:57500 matching_file_list = [f for f in file_list if pattern.search(f)]
[email protected]71b40682009-07-31 23:40:09501 if matching_file_list:
502 self._RunHookAction(hook_dict, matching_file_list)
[email protected]fb2b8eb2009-04-23 21:03:42503
[email protected]918a9ae2010-05-28 15:50:30504 def _EnforceRevisions(self, solutions):
505 """Checks for revision overrides."""
506 revision_overrides = {}
[email protected]307d1792010-05-31 20:03:13507 if self._options.head:
508 return revision_overrides
509 for s in solutions:
510 if not s.get('safesync_url', None):
511 continue
512 handle = urllib.urlopen(s['safesync_url'])
513 rev = handle.read().strip()
514 handle.close()
515 if len(rev):
516 self._options.revisions.append('%s@%s' % (s['name'], rev))
517 if not self._options.revisions:
518 return revision_overrides
519 # --revision will take over safesync_url.
520 solutions_names = [s['name'] for s in solutions]
521 index = 0
522 for revision in self._options.revisions:
523 if not '@' in revision:
524 # Support for --revision 123
525 revision = '%s@%s' % (solutions_names[index], revision)
526 sol, rev = revision.split("@", 1)
527 if not sol in solutions_names:
528 #raise gclient_utils.Error('%s is not a valid solution.' % sol)
529 print >> sys.stderr, ('Please fix your script, having invalid '
530 '--revision flags will soon considered an error.')
531 else:
[email protected]918a9ae2010-05-28 15:50:30532 revision_overrides[sol] = rev
[email protected]307d1792010-05-31 20:03:13533 index += 1
[email protected]918a9ae2010-05-28 15:50:30534 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 """
[email protected]116704f2010-06-11 17:34:38548 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)
[email protected]17cdf762010-05-28 17:30:52694 return 0
[email protected]fb2b8eb2009-04-23 21:03:42695
696 def PrintRevInfo(self):
[email protected]5d63eb82010-03-24 23:22:09697 """Output revision info mapping for the client and its dependencies.
698
699 This allows the capture of an overall "revision" for the source tree that
[email protected]918a9ae2010-05-28 15:50:30700 can be used to reproduce the same tree in the future. It is only useful for
701 "unpinned dependencies", i.e. DEPS/deps references without a svn revision
702 number or a git hash. A git branch name isn't "pinned" since the actual
703 commit can change.
[email protected]5d63eb82010-03-24 23:22:09704
705 The --snapshot option allows creating a .gclient file to reproduce the tree.
[email protected]fb2b8eb2009-04-23 21:03:42706 """
[email protected]fb2b8eb2009-04-23 21:03:42707 solutions = self.GetVar("solutions")
708 if not solutions:
[email protected]e3608df2009-11-10 20:22:57709 raise gclient_utils.Error("No solution specified")
[email protected]fb2b8eb2009-04-23 21:03:42710
[email protected]5d63eb82010-03-24 23:22:09711 # Inner helper to generate base url and rev tuple
[email protected]fb2b8eb2009-04-23 21:03:42712 def GetURLAndRev(name, original_url):
[email protected]5d63eb82010-03-24 23:22:09713 url, _ = gclient_utils.SplitUrlRevision(original_url)
714 scm = gclient_scm.CreateSCM(original_url, self._root_dir, name)
715 return (url, scm.revinfo(self._options, [], None))
[email protected]fb2b8eb2009-04-23 21:03:42716
[email protected]e3da35f2010-03-09 21:40:45717 # text of the snapshot gclient file
718 new_gclient = ""
719 # Dictionary of { path : SCM url } to ensure no duplicate solutions
720 solution_names = {}
[email protected]de8f3522010-03-11 23:47:44721 entries = {}
722 entries_deps_content = {}
[email protected]fb2b8eb2009-04-23 21:03:42723 # Run on the base solutions first.
724 for solution in solutions:
[email protected]e3da35f2010-03-09 21:40:45725 # Dictionary of { path : SCM url } to describe the gclient checkout
[email protected]fb2b8eb2009-04-23 21:03:42726 name = solution["name"]
[email protected]e3da35f2010-03-09 21:40:45727 if name in solution_names:
[email protected]e3608df2009-11-10 20:22:57728 raise gclient_utils.Error("solution %s specified more than once" % name)
[email protected]fb2b8eb2009-04-23 21:03:42729 (url, rev) = GetURLAndRev(name, solution["url"])
[email protected]770ff9e2009-09-23 17:18:18730 entries[name] = "%s@%s" % (url, rev)
[email protected]e3da35f2010-03-09 21:40:45731 solution_names[name] = "%s@%s" % (url, rev)
[email protected]491c04b2010-05-17 18:17:44732 deps_file = solution.get("deps_file", self.DEPS_FILE)
[email protected]952d7c72010-03-01 20:41:01733 if '/' in deps_file or '\\' in deps_file:
734 raise gclient_utils.Error('deps_file name must not be a path, just a '
735 'filename.')
736 try:
737 deps_content = gclient_utils.FileRead(
738 os.path.join(self._root_dir, name, deps_file))
739 except IOError, e:
740 if e.errno != errno.ENOENT:
741 raise
742 deps_content = ""
743 entries_deps_content[name] = deps_content
[email protected]fb2b8eb2009-04-23 21:03:42744
[email protected]de8f3522010-03-11 23:47:44745 # Process the dependencies next (sort alphanumerically to ensure that
746 # containing directories get populated first and for readability)
747 deps = self._ParseAllDeps(entries, entries_deps_content)
748 deps_to_process = deps.keys()
749 deps_to_process.sort()
[email protected]fb2b8eb2009-04-23 21:03:42750
[email protected]de8f3522010-03-11 23:47:44751 # First pass for direct dependencies.
752 for d in deps_to_process:
753 if type(deps[d]) == str:
754 (url, rev) = GetURLAndRev(d, deps[d])
755 entries[d] = "%s@%s" % (url, rev)
[email protected]fb2b8eb2009-04-23 21:03:42756
[email protected]de8f3522010-03-11 23:47:44757 # Second pass for inherited deps (via the From keyword)
758 for d in deps_to_process:
[email protected]4b5b1772010-04-08 01:52:56759 if isinstance(deps[d], self.FromImpl):
[email protected]de8f3522010-03-11 23:47:44760 deps_parent_url = entries[deps[d].module_name]
761 if deps_parent_url.find("@") < 0:
762 raise gclient_utils.Error("From %s missing revisioned url" %
763 deps[d].module_name)
764 content = gclient_utils.FileRead(os.path.join(
765 self._root_dir,
766 deps[d].module_name,
[email protected]491c04b2010-05-17 18:17:44767 self.DEPS_FILE))
768 sub_deps = self._ParseSolutionDeps(deps[d].module_name, content, {},
769 False)
[email protected]de8f3522010-03-11 23:47:44770 (url, rev) = GetURLAndRev(d, sub_deps[d])
771 entries[d] = "%s@%s" % (url, rev)
[email protected]e3da35f2010-03-09 21:40:45772
[email protected]de8f3522010-03-11 23:47:44773 # Build the snapshot configuration string
774 if self._options.snapshot:
775 url = entries.pop(name)
776 custom_deps = ",\n ".join(["\"%s\": \"%s\"" % (x, entries[x])
777 for x in sorted(entries.keys())])
[email protected]e3da35f2010-03-09 21:40:45778
[email protected]1f7d1182010-05-17 18:17:38779 new_gclient += self.DEFAULT_SNAPSHOT_SOLUTION_TEXT % {
[email protected]de8f3522010-03-11 23:47:44780 'solution_name': name,
781 'solution_url': url,
782 'safesync_url' : "",
783 'solution_deps': custom_deps,
784 }
785 else:
786 print(";\n".join(["%s: %s" % (x, entries[x])
787 for x in sorted(entries.keys())]))
[email protected]e3da35f2010-03-09 21:40:45788
789 # Print the snapshot configuration file
790 if self._options.snapshot:
[email protected]491c04b2010-05-17 18:17:44791 config = self.DEFAULT_SNAPSHOT_FILE_TEXT % {'solution_list': new_gclient}
[email protected]e3da35f2010-03-09 21:40:45792 snapclient = GClient(self._root_dir, self._options)
793 snapclient.SetConfig(config)
[email protected]116704f2010-06-11 17:34:38794 print(snapclient.config_content)
[email protected]fb2b8eb2009-04-23 21:03:42795
796
[email protected]5ca27692010-05-26 19:32:41797#### gclient commands.
[email protected]fb2b8eb2009-04-23 21:03:42798
799
[email protected]5ca27692010-05-26 19:32:41800def CMDcleanup(parser, args):
801 """Cleans up all working copies.
[email protected]ddff62d2010-05-17 21:02:36802
[email protected]5ca27692010-05-26 19:32:41803Mostly svn-specific. Simply runs 'svn cleanup' for each module.
[email protected]79692d62010-05-14 18:57:13804"""
[email protected]86c0dec2010-05-28 19:01:00805 parser.add_option("--deps", dest="deps_os", metavar="OS_LIST",
806 help="override deps for the specified (comma-separated) "
807 "platform(s); 'all' will process all deps_os "
808 "references")
[email protected]5ca27692010-05-26 19:32:41809 (options, args) = parser.parse_args(args)
[email protected]2806acc2009-05-15 12:33:34810 client = GClient.LoadCurrentConfig(options)
[email protected]fb2b8eb2009-04-23 21:03:42811 if not client:
[email protected]e3608df2009-11-10 20:22:57812 raise gclient_utils.Error("client not configured; see 'gclient config'")
[email protected]fb2b8eb2009-04-23 21:03:42813 if options.verbose:
814 # Print out the .gclient file. This is longer than if we just printed the
815 # client dict, but more legible, and it might contain helpful comments.
[email protected]116704f2010-06-11 17:34:38816 print(client.config_content)
[email protected]fb2b8eb2009-04-23 21:03:42817 return client.RunOnDeps('cleanup', args)
818
819
[email protected]5ca27692010-05-26 19:32:41820@attr('usage', '[url] [safesync url]')
821def CMDconfig(parser, args):
[email protected]ddff62d2010-05-17 21:02:36822 """Create a .gclient file in the current directory.
823
[email protected]5ca27692010-05-26 19:32:41824This specifies the configuration for further commands. After update/sync,
[email protected]79692d62010-05-14 18:57:13825top-level DEPS files in each module are read to determine dependent
[email protected]5ca27692010-05-26 19:32:41826modules to operate on as well. If optional [url] parameter is
[email protected]79692d62010-05-14 18:57:13827provided, then configuration is read from a specified Subversion server
[email protected]5ca27692010-05-26 19:32:41828URL.
[email protected]79692d62010-05-14 18:57:13829"""
[email protected]5ca27692010-05-26 19:32:41830 parser.add_option("--spec",
831 help="create a gclient file containing the provided "
832 "string. Due to Cygwin/Python brokenness, it "
833 "probably can't contain any newlines.")
834 parser.add_option("--name",
835 help="overrides the default name for the solution")
836 (options, args) = parser.parse_args(args)
[email protected]5fc2a332010-05-26 19:37:15837 if ((options.spec and args) or len(args) > 2 or
838 (not options.spec and not args)):
839 parser.error('Inconsistent arguments. Use either --spec or one or 2 args')
840
[email protected]0329e672009-05-13 18:41:04841 if os.path.exists(options.config_filename):
[email protected]e3608df2009-11-10 20:22:57842 raise gclient_utils.Error("%s file already exists in the current directory"
843 % options.config_filename)
[email protected]2806acc2009-05-15 12:33:34844 client = GClient('.', options)
[email protected]fb2b8eb2009-04-23 21:03:42845 if options.spec:
846 client.SetConfig(options.spec)
847 else:
[email protected]1ab7ffc2009-06-03 17:21:37848 base_url = args[0].rstrip('/')
[email protected]8cf7a392010-04-07 17:20:26849 if not options.name:
850 name = base_url.split("/")[-1]
851 else:
852 # specify an alternate relpath for the given URL.
853 name = options.name
[email protected]fb2b8eb2009-04-23 21:03:42854 safesync_url = ""
855 if len(args) > 1:
856 safesync_url = args[1]
857 client.SetDefaultConfig(name, base_url, safesync_url)
858 client.SaveConfig()
[email protected]79692d62010-05-14 18:57:13859 return 0
[email protected]fb2b8eb2009-04-23 21:03:42860
861
[email protected]5ca27692010-05-26 19:32:41862def CMDexport(parser, args):
[email protected]ddff62d2010-05-17 21:02:36863 """Wrapper for svn export for all managed directories."""
[email protected]86c0dec2010-05-28 19:01:00864 parser.add_option("--deps", dest="deps_os", metavar="OS_LIST",
865 help="override deps for the specified (comma-separated) "
866 "platform(s); 'all' will process all deps_os "
867 "references")
[email protected]5ca27692010-05-26 19:32:41868 (options, args) = parser.parse_args(args)
[email protected]644aa0c2009-07-17 20:20:41869 if len(args) != 1:
[email protected]e3608df2009-11-10 20:22:57870 raise gclient_utils.Error("Need directory name")
[email protected]644aa0c2009-07-17 20:20:41871 client = GClient.LoadCurrentConfig(options)
872
873 if not client:
[email protected]e3608df2009-11-10 20:22:57874 raise gclient_utils.Error("client not configured; see 'gclient config'")
[email protected]644aa0c2009-07-17 20:20:41875
876 if options.verbose:
877 # Print out the .gclient file. This is longer than if we just printed the
878 # client dict, but more legible, and it might contain helpful comments.
[email protected]116704f2010-06-11 17:34:38879 print(client.config_content)
[email protected]644aa0c2009-07-17 20:20:41880 return client.RunOnDeps('export', args)
881
[email protected]fb2b8eb2009-04-23 21:03:42882
[email protected]5ca27692010-05-26 19:32:41883@attr('epilog', """Example:
884 gclient pack > patch.txt
885 generate simple patch for configured client and dependences
886""")
887def CMDpack(parser, args):
[email protected]79692d62010-05-14 18:57:13888 """Generate a patch which can be applied at the root of the tree.
[email protected]ddff62d2010-05-17 21:02:36889
[email protected]5ca27692010-05-26 19:32:41890Internally, runs 'svn diff'/'git diff' on each checked out module and
[email protected]79692d62010-05-14 18:57:13891dependencies, and performs minimal postprocessing of the output. The
892resulting patch is printed to stdout and can be applied to a freshly
[email protected]5ca27692010-05-26 19:32:41893checked out tree via 'patch -p0 < patchfile'.
[email protected]79692d62010-05-14 18:57:13894"""
[email protected]86c0dec2010-05-28 19:01:00895 parser.add_option("--deps", dest="deps_os", metavar="OS_LIST",
896 help="override deps for the specified (comma-separated) "
897 "platform(s); 'all' will process all deps_os "
898 "references")
[email protected]5ca27692010-05-26 19:32:41899 (options, args) = parser.parse_args(args)
[email protected]ab318592009-09-04 00:54:55900 client = GClient.LoadCurrentConfig(options)
901 if not client:
[email protected]e3608df2009-11-10 20:22:57902 raise gclient_utils.Error("client not configured; see 'gclient config'")
[email protected]ab318592009-09-04 00:54:55903 if options.verbose:
904 # Print out the .gclient file. This is longer than if we just printed the
905 # client dict, but more legible, and it might contain helpful comments.
[email protected]116704f2010-06-11 17:34:38906 print(client.config_content)
[email protected]ab318592009-09-04 00:54:55907 return client.RunOnDeps('pack', args)
908
909
[email protected]5ca27692010-05-26 19:32:41910def CMDstatus(parser, args):
911 """Show modification status for every dependencies."""
[email protected]6b1d00b2010-05-26 20:11:08912 parser.add_option("--deps", dest="deps_os", metavar="OS_LIST",
[email protected]86c0dec2010-05-28 19:01:00913 help="override deps for the specified (comma-separated) "
914 "platform(s); 'all' will process all deps_os "
915 "references")
[email protected]5ca27692010-05-26 19:32:41916 (options, args) = parser.parse_args(args)
[email protected]2806acc2009-05-15 12:33:34917 client = GClient.LoadCurrentConfig(options)
[email protected]fb2b8eb2009-04-23 21:03:42918 if not client:
[email protected]e3608df2009-11-10 20:22:57919 raise gclient_utils.Error("client not configured; see 'gclient config'")
[email protected]fb2b8eb2009-04-23 21:03:42920 if options.verbose:
921 # Print out the .gclient file. This is longer than if we just printed the
922 # client dict, but more legible, and it might contain helpful comments.
[email protected]116704f2010-06-11 17:34:38923 print(client.config_content)
[email protected]fb2b8eb2009-04-23 21:03:42924 return client.RunOnDeps('status', args)
925
926
[email protected]5ca27692010-05-26 19:32:41927@attr('epilog', """Examples:
[email protected]79692d62010-05-14 18:57:13928 gclient sync
929 update files from SCM according to current configuration,
930 *for modules which have changed since last update or sync*
931 gclient sync --force
932 update files from SCM according to current configuration, for
933 all modules (useful for recovering files deleted from local copy)
934 gclient sync --revision src@31000
935 update src directory to r31000
[email protected]5ca27692010-05-26 19:32:41936""")
937def CMDsync(parser, args):
938 """Checkout/update all modules."""
[email protected]c0b8a4e2010-06-02 17:49:39939 parser.add_option("-f", "--force", action="store_true",
[email protected]5ca27692010-05-26 19:32:41940 help="force update even for unchanged modules")
[email protected]c0b8a4e2010-06-02 17:49:39941 parser.add_option("-n", "--nohooks", action="store_true",
[email protected]5ca27692010-05-26 19:32:41942 help="don't run hooks after the update is complete")
943 parser.add_option("-r", "--revision", action="append",
944 dest="revisions", metavar="REV", default=[],
[email protected]307d1792010-05-31 20:03:13945 help="Enforces revision/hash for the solutions with the "
946 "format src@rev. The src@ part is optional and can be "
947 "skipped. -r can be used multiple times when .gclient "
948 "has multiple solutions configured and will work even "
949 "if the src@ part is skipped.")
[email protected]c0b8a4e2010-06-02 17:49:39950 parser.add_option("-H", "--head", action="store_true",
[email protected]5ca27692010-05-26 19:32:41951 help="skips any safesync_urls specified in "
952 "configured solutions and sync to head instead")
[email protected]c0b8a4e2010-06-02 17:49:39953 parser.add_option("-D", "--delete_unversioned_trees", action="store_true",
[email protected]5ca27692010-05-26 19:32:41954 help="delete any unexpected unversioned trees "
955 "that are in the checkout")
[email protected]c0b8a4e2010-06-02 17:49:39956 parser.add_option("-R", "--reset", action="store_true",
[email protected]5ca27692010-05-26 19:32:41957 help="resets any local changes before updating (git only)")
958 parser.add_option("--deps", dest="deps_os", metavar="OS_LIST",
[email protected]86c0dec2010-05-28 19:01:00959 help="override deps for the specified (comma-separated) "
960 "platform(s); 'all' will process all deps_os "
961 "references")
[email protected]c0b8a4e2010-06-02 17:49:39962 parser.add_option("-m", "--manually_grab_svn_rev", action="store_true",
[email protected]5ca27692010-05-26 19:32:41963 help="Skip svn up whenever possible by requesting "
964 "actual HEAD revision from the repository")
965 (options, args) = parser.parse_args(args)
[email protected]2806acc2009-05-15 12:33:34966 client = GClient.LoadCurrentConfig(options)
[email protected]fb2b8eb2009-04-23 21:03:42967
968 if not client:
[email protected]e3608df2009-11-10 20:22:57969 raise gclient_utils.Error("client not configured; see 'gclient config'")
[email protected]fb2b8eb2009-04-23 21:03:42970
[email protected]307d1792010-05-31 20:03:13971 if options.revisions and options.head:
972 # TODO(maruel): Make it a parser.error if it doesn't break any builder.
973 print("Warning: you cannot use both --head and --revision")
[email protected]fb2b8eb2009-04-23 21:03:42974
975 if options.verbose:
976 # Print out the .gclient file. This is longer than if we just printed the
977 # client dict, but more legible, and it might contain helpful comments.
[email protected]116704f2010-06-11 17:34:38978 print(client.config_content)
[email protected]fb2b8eb2009-04-23 21:03:42979 return client.RunOnDeps('update', args)
980
981
[email protected]5ca27692010-05-26 19:32:41982def CMDupdate(parser, args):
[email protected]ddff62d2010-05-17 21:02:36983 """Alias for the sync command. Deprecated."""
[email protected]5ca27692010-05-26 19:32:41984 return CMDsync(parser, args)
[email protected]fb2b8eb2009-04-23 21:03:42985
[email protected]5ca27692010-05-26 19:32:41986def CMDdiff(parser, args):
987 """Displays local diff for every dependencies."""
[email protected]86c0dec2010-05-28 19:01:00988 parser.add_option("--deps", dest="deps_os", metavar="OS_LIST",
989 help="override deps for the specified (comma-separated) "
990 "platform(s); 'all' will process all deps_os "
991 "references")
[email protected]5ca27692010-05-26 19:32:41992 (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]116704f2010-06-11 17:34:38999 print(client.config_content)
[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",
[email protected]86c0dec2010-05-28 19:01:001006 help="override deps for the specified (comma-separated) "
1007 "platform(s); 'all' will process all deps_os "
1008 "references")
[email protected]c0b8a4e2010-06-02 17:49:391009 parser.add_option("-n", "--nohooks", action="store_true",
[email protected]5ca27692010-05-26 19:32:411010 help="don't run hooks after the revert is complete")
1011 (options, args) = parser.parse_args(args)
1012 # --force is implied.
1013 options.force = True
[email protected]2806acc2009-05-15 12:33:341014 client = GClient.LoadCurrentConfig(options)
[email protected]fb2b8eb2009-04-23 21:03:421015 if not client:
[email protected]e3608df2009-11-10 20:22:571016 raise gclient_utils.Error("client not configured; see 'gclient config'")
[email protected]fb2b8eb2009-04-23 21:03:421017 return client.RunOnDeps('revert', args)
1018
1019
[email protected]5ca27692010-05-26 19:32:411020def CMDrunhooks(parser, args):
1021 """Runs hooks for files that have been modified in the local working copy."""
[email protected]6b1d00b2010-05-26 20:11:081022 parser.add_option("--deps", dest="deps_os", metavar="OS_LIST",
[email protected]86c0dec2010-05-28 19:01:001023 help="override deps for the specified (comma-separated) "
1024 "platform(s); 'all' will process all deps_os "
1025 "references")
[email protected]c0b8a4e2010-06-02 17:49:391026 parser.add_option("-f", "--force", action="store_true", default=True,
[email protected]5ca27692010-05-26 19:32:411027 help="Deprecated. No effect.")
1028 (options, args) = parser.parse_args(args)
[email protected]2806acc2009-05-15 12:33:341029 client = GClient.LoadCurrentConfig(options)
[email protected]fb2b8eb2009-04-23 21:03:421030 if not client:
[email protected]e3608df2009-11-10 20:22:571031 raise gclient_utils.Error("client not configured; see 'gclient config'")
[email protected]fb2b8eb2009-04-23 21:03:421032 if options.verbose:
1033 # Print out the .gclient file. This is longer than if we just printed the
1034 # client dict, but more legible, and it might contain helpful comments.
[email protected]116704f2010-06-11 17:34:381035 print(client.config_content)
[email protected]5df6a462009-08-28 18:52:261036 options.force = True
[email protected]5ca27692010-05-26 19:32:411037 options.nohooks = False
[email protected]fb2b8eb2009-04-23 21:03:421038 return client.RunOnDeps('runhooks', args)
1039
1040
[email protected]5ca27692010-05-26 19:32:411041def CMDrevinfo(parser, args):
[email protected]9eda4112010-06-11 18:56:101042 """Output revision info mapping for the client and its dependencies.
1043
1044 This allows the capture of an overall "revision" for the source tree that
1045 can be used to reproduce the same tree in the future. It is only useful for
1046 "unpinned dependencies", i.e. DEPS/deps references without a svn revision
1047 number or a git hash. A git branch name isn't "pinned" since the actual
1048 commit can change.
1049 """
1050 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1051 help='override deps for the specified (comma-separated) '
1052 'platform(s); \'all\' will process all deps_os '
1053 'references')
1054 parser.add_option('-s', '--snapshot', action='store_true',
1055 help='creates a snapshot .gclient file of the current '
1056 'version of all repositories to reproduce the tree, '
1057 'implies -a')
[email protected]5ca27692010-05-26 19:32:411058 (options, args) = parser.parse_args(args)
[email protected]2806acc2009-05-15 12:33:341059 client = GClient.LoadCurrentConfig(options)
[email protected]fb2b8eb2009-04-23 21:03:421060 if not client:
[email protected]e3608df2009-11-10 20:22:571061 raise gclient_utils.Error("client not configured; see 'gclient config'")
[email protected]fb2b8eb2009-04-23 21:03:421062 client.PrintRevInfo()
[email protected]79692d62010-05-14 18:57:131063 return 0
[email protected]fb2b8eb2009-04-23 21:03:421064
1065
[email protected]5ca27692010-05-26 19:32:411066def Command(name):
1067 return getattr(sys.modules[__name__], 'CMD' + name, None)
1068
1069
1070def CMDhelp(parser, args):
1071 """Prints list of commands or help for a specific command."""
[email protected]6e29d572010-06-04 17:32:201072 (_, args) = parser.parse_args(args)
[email protected]ddff62d2010-05-17 21:02:361073 if len(args) == 1:
[email protected]5ca27692010-05-26 19:32:411074 return Main(args + ['--help'])
[email protected]ddff62d2010-05-17 21:02:361075 parser.print_help()
1076 return 0
1077
1078
[email protected]5ca27692010-05-26 19:32:411079def GenUsage(parser, command):
1080 """Modify an OptParse object with the function's documentation."""
1081 obj = Command(command)
1082 if command == 'help':
1083 command = '<command>'
1084 # OptParser.description prefer nicely non-formatted strings.
1085 parser.description = re.sub('[\r\n ]{2,}', ' ', obj.__doc__)
1086 usage = getattr(obj, 'usage', '')
1087 parser.set_usage('%%prog %s [options] %s' % (command, usage))
1088 parser.epilog = getattr(obj, 'epilog', None)
[email protected]fb2b8eb2009-04-23 21:03:421089
1090
1091def Main(argv):
[email protected]5ca27692010-05-26 19:32:411092 """Doesn't parse the arguments here, just find the right subcommand to
1093 execute."""
[email protected]6e29d572010-06-04 17:32:201094 try:
1095 # Do it late so all commands are listed.
1096 CMDhelp.usage = ('\n\nCommands are:\n' + '\n'.join([
1097 ' %-10s %s' % (fn[3:], Command(fn[3:]).__doc__.split('\n')[0].strip())
1098 for fn in dir(sys.modules[__name__]) if fn.startswith('CMD')]))
1099 parser = optparse.OptionParser(version='%prog ' + __version__)
[email protected]f0fc9912010-06-11 17:57:331100 parser.add_option('-v', '--verbose', action='count', default=0,
1101 help='Produces additional output for diagnostics. Can be '
1102 'used up to three times for more logging info.')
1103 parser.add_option('--gclientfile', dest='config_filename',
1104 default=os.environ.get('GCLIENT_FILE', '.gclient'),
1105 help='Specify an alternate %default file')
[email protected]6e29d572010-06-04 17:32:201106 # Integrate standard options processing.
1107 old_parser = parser.parse_args
1108 def Parse(args):
1109 (options, args) = old_parser(args)
[email protected]f0fc9912010-06-11 17:57:331110 level = None
[email protected]6e29d572010-06-04 17:32:201111 if options.verbose == 2:
[email protected]f0fc9912010-06-11 17:57:331112 level = logging.INFO
[email protected]6e29d572010-06-04 17:32:201113 elif options.verbose > 2:
[email protected]f0fc9912010-06-11 17:57:331114 level = logging.DEBUG
1115 logging.basicConfig(level=level,
1116 format='%(module)s(%(lineno)d) %(funcName)s:%(message)s')
1117 options.entries_filename = options.config_filename + '_entries'
[email protected]6e29d572010-06-04 17:32:201118 if not hasattr(options, 'revisions'):
1119 # GClient.RunOnDeps expects it even if not applicable.
1120 options.revisions = []
1121 if not hasattr(options, 'head'):
1122 options.head = None
[email protected]f0fc9912010-06-11 17:57:331123 if not hasattr(options, 'nohooks'):
1124 options.nohooks = True
1125 if not hasattr(options, 'deps_os'):
1126 options.deps_os = None
[email protected]6e29d572010-06-04 17:32:201127 return (options, args)
1128 parser.parse_args = Parse
1129 # We don't want wordwrapping in epilog (usually examples)
1130 parser.format_epilog = lambda _: parser.epilog or ''
1131 if argv:
1132 command = Command(argv[0])
1133 if command:
[email protected]f0fc9912010-06-11 17:57:331134 # 'fix' the usage and the description now that we know the subcommand.
[email protected]6e29d572010-06-04 17:32:201135 GenUsage(parser, argv[0])
1136 return command(parser, argv[1:])
1137 # Not a known command. Default to help.
1138 GenUsage(parser, 'help')
1139 return CMDhelp(parser, argv)
1140 except gclient_utils.Error, e:
[email protected]f0fc9912010-06-11 17:57:331141 print >> sys.stderr, 'Error: %s' % str(e)
[email protected]6e29d572010-06-04 17:32:201142 return 1
[email protected]fb2b8eb2009-04-23 21:03:421143
1144
[email protected]f0fc9912010-06-11 17:57:331145if '__main__' == __name__:
[email protected]6e29d572010-06-04 17:32:201146 sys.exit(Main(sys.argv[1:]))
[email protected]fb2b8eb2009-04-23 21:03:421147
1148# vim: ts=2:sw=2:tw=80:et: