blob: 55305bd8c4b2493766dfa995b9ad735a50613da0 [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
[email protected]0b6a0842010-06-15 14:34:196"""Meta checkout manager supporting both Subversion and GIT.
[email protected]fb2b8eb2009-04-23 21:03:427
8Files
9 .gclient : Current client configuration, written by 'config' command.
10 Format is a Python script defining 'solutions', a list whose
11 entries each are maps binding the strings "name" and "url"
12 to strings specifying the name and location of the client
13 module, as well as "custom_deps" to a map similar to the DEPS
14 file below.
15 .gclient_entries : A cache constructed by 'update' command. Format is a
16 Python script defining 'entries', a list of the names
17 of all modules in the client
18 <module>/DEPS : Python script defining var 'deps' as a map from each requisite
19 submodule name to a URL where it can be found (via one SCM)
20
21Hooks
22 .gclient and DEPS files may optionally contain a list named "hooks" to
23 allow custom actions to be performed based on files that have changed in the
[email protected]67820ef2009-07-27 17:23:0024 working copy as a result of a "sync"/"update" or "revert" operation. This
[email protected]0b6a0842010-06-15 14:34:1925 can be prevented by using --nohooks (hooks run by default). Hooks can also
[email protected]5df6a462009-08-28 18:52:2626 be forced to run with the "runhooks" operation. If "sync" is run with
[email protected]fb2b8eb2009-04-23 21:03:4227 --force, all known hooks will run regardless of the state of the working
28 copy.
29
30 Each item in a "hooks" list is a dict, containing these two keys:
31 "pattern" The associated value is a string containing a regular
32 expression. When a file whose pathname matches the expression
33 is checked out, updated, or reverted, the hook's "action" will
34 run.
35 "action" A list describing a command to run along with its arguments, if
36 any. An action command will run at most one time per gclient
37 invocation, regardless of how many files matched the pattern.
38 The action is executed in the same directory as the .gclient
39 file. If the first item in the list is the string "python",
40 the current Python interpreter (sys.executable) will be used
[email protected]71b40682009-07-31 23:40:0941 to run the command. If the list contains string "$matching_files"
42 it will be removed from the list and the list will be extended
43 by the list of matching files.
[email protected]fb2b8eb2009-04-23 21:03:4244
45 Example:
46 hooks = [
47 { "pattern": "\\.(gif|jpe?g|pr0n|png)$",
48 "action": ["python", "image_indexer.py", "--all"]},
49 ]
50"""
51
[email protected]0b6a0842010-06-15 14:34:1952__version__ = "0.4.1"
[email protected]fb2b8eb2009-04-23 21:03:4253
54import errno
[email protected]754960e2009-09-21 12:31:0555import logging
[email protected]fb2b8eb2009-04-23 21:03:4256import optparse
57import os
[email protected]2e38de72009-09-28 17:04:4758import pprint
[email protected]fb2b8eb2009-04-23 21:03:4259import re
[email protected]fb2b8eb2009-04-23 21:03:4260import sys
[email protected]fb2b8eb2009-04-23 21:03:4261import urlparse
[email protected]fb2b8eb2009-04-23 21:03:4262import urllib
63
[email protected]ada4c652009-12-03 15:32:0164import breakpad
65
[email protected]5f3eee32009-09-17 00:34:3066import gclient_scm
67import gclient_utils
[email protected]1f7a3d12010-02-04 15:11:5068from third_party.repo.progress import Progress
[email protected]fb2b8eb2009-04-23 21:03:4269
[email protected]fb2b8eb2009-04-23 21:03:4270
[email protected]1f7d1182010-05-17 18:17:3871def attr(attr, data):
72 """Sets an attribute on a function."""
73 def hook(fn):
74 setattr(fn, attr, data)
75 return fn
76 return hook
[email protected]e3da35f2010-03-09 21:40:4577
[email protected]fb2b8eb2009-04-23 21:03:4278
[email protected]fb2b8eb2009-04-23 21:03:4279## GClient implementation.
80
81
[email protected]116704f2010-06-11 17:34:3882class GClientKeywords(object):
83 class FromImpl(object):
84 """Used to implement the From() syntax."""
85
86 def __init__(self, module_name, sub_target_name=None):
87 """module_name is the dep module we want to include from. It can also be
88 the name of a subdirectory to include from.
89
90 sub_target_name is an optional parameter if the module name in the other
91 DEPS file is different. E.g., you might want to map src/net to net."""
92 self.module_name = module_name
93 self.sub_target_name = sub_target_name
94
95 def __str__(self):
96 return 'From(%s, %s)' % (repr(self.module_name),
97 repr(self.sub_target_name))
98
99 def GetUrl(self, target_name, sub_deps_base_url, root_dir, sub_deps):
100 """Resolve the URL for this From entry."""
101 sub_deps_target_name = target_name
102 if self.sub_target_name:
103 sub_deps_target_name = self.sub_target_name
104 url = sub_deps[sub_deps_target_name]
105 if url.startswith('/'):
106 # If it's a relative URL, we need to resolve the URL relative to the
107 # sub deps base URL.
108 if not isinstance(sub_deps_base_url, basestring):
109 sub_deps_base_url = sub_deps_base_url.GetPath()
110 scm = gclient_scm.CreateSCM(sub_deps_base_url, root_dir,
111 None)
112 url = scm.FullUrlForRelativeUrl(url)
113 return url
114
115 class FileImpl(object):
116 """Used to implement the File('') syntax which lets you sync a single file
117 from an SVN repo."""
118
119 def __init__(self, file_location):
120 self.file_location = file_location
121
122 def __str__(self):
123 return 'File("%s")' % self.file_location
124
125 def GetPath(self):
126 return os.path.split(self.file_location)[0]
127
128 def GetFilename(self):
129 rev_tokens = self.file_location.split('@')
130 return os.path.split(rev_tokens[0])[1]
131
132 def GetRevision(self):
133 rev_tokens = self.file_location.split('@')
134 if len(rev_tokens) > 1:
135 return rev_tokens[1]
136 return None
137
138 class VarImpl(object):
139 def __init__(self, custom_vars, local_scope):
140 self._custom_vars = custom_vars
141 self._local_scope = local_scope
142
143 def Lookup(self, var_name):
144 """Implements the Var syntax."""
145 if var_name in self._custom_vars:
146 return self._custom_vars[var_name]
147 elif var_name in self._local_scope.get("vars", {}):
148 return self._local_scope["vars"][var_name]
149 raise gclient_utils.Error("Var is not defined: %s" % var_name)
150
151
[email protected]54a07a22010-06-14 19:07:39152class Dependency(GClientKeywords):
153 """Object that represents a dependency checkout."""
[email protected]9eda4112010-06-11 18:56:10154 DEPS_FILE = 'DEPS'
[email protected]0b6a0842010-06-15 14:34:19155
[email protected]54a07a22010-06-14 19:07:39156 def __init__(self, parent, name, url, safesync_url=None, custom_deps=None,
[email protected]0b6a0842010-06-15 14:34:19157 custom_vars=None, deps_file=None):
[email protected]54a07a22010-06-14 19:07:39158 GClientKeywords.__init__(self)
159 self.parent = parent
160 self.name = name
161 self.url = url
162 # These 2 are only set in .gclient and not in DEPS files.
163 self.safesync_url = safesync_url
164 self.custom_vars = custom_vars or {}
165 self.custom_deps = custom_deps or {}
[email protected]0b6a0842010-06-15 14:34:19166 self.deps_hooks = []
[email protected]54a07a22010-06-14 19:07:39167 self.dependencies = []
168 self.deps_file = deps_file or self.DEPS_FILE
[email protected]271375b2010-06-23 19:17:38169 self.deps_parsed = False
170 self.direct_reference = False
[email protected]fb2b8eb2009-04-23 21:03:42171
[email protected]54a07a22010-06-14 19:07:39172 # Sanity checks
173 if not self.name and self.parent:
174 raise gclient_utils.Error('Dependency without name')
175 if not isinstance(self.url,
176 (basestring, self.FromImpl, self.FileImpl, None.__class__)):
177 raise gclient_utils.Error('dependency url must be either a string, None, '
178 'File() or From() instead of %s' %
179 self.url.__class__.__name__)
180 if '/' in self.deps_file or '\\' in self.deps_file:
181 raise gclient_utils.Error('deps_file name must not be a path, just a '
182 'filename. %s' % self.deps_file)
183
[email protected]271375b2010-06-23 19:17:38184 def ParseDepsFile(self, direct_reference):
185 """Parses the DEPS file for this dependency."""
186 if direct_reference:
187 # Maybe it was referenced earlier by a From() keyword but it's now
188 # directly referenced.
189 self.direct_reference = direct_reference
190 self.deps_parsed = True
191 filepath = os.path.join(self.root_dir(), self.name, self.deps_file)
192 if not os.path.isfile(filepath):
[email protected]0d425922010-06-21 19:22:24193 return {}
[email protected]271375b2010-06-23 19:17:38194 deps_content = gclient_utils.FileRead(filepath)
[email protected]0d425922010-06-21 19:22:24195
[email protected]271375b2010-06-23 19:17:38196 # Eval the content.
197 # One thing is unintuitive, vars= {} must happen before Var() use.
198 local_scope = {}
199 var = self.VarImpl(self.custom_vars, local_scope)
200 global_scope = {
201 'File': self.FileImpl,
202 'From': self.FromImpl,
203 'Var': var.Lookup,
204 'deps_os': {},
205 }
206 exec(deps_content, global_scope, local_scope)
207 deps = local_scope.get('deps', {})
[email protected]fb2b8eb2009-04-23 21:03:42208 # load os specific dependencies if defined. these dependencies may
209 # override or extend the values defined by the 'deps' member.
[email protected]271375b2010-06-23 19:17:38210 if 'deps_os' in local_scope:
211 for deps_os_key in self.enforced_os():
212 os_deps = local_scope['deps_os'].get(deps_os_key, {})
213 if len(self.enforced_os()) > 1:
214 # Ignore any conflict when including deps for more than one
[email protected]fb2b8eb2009-04-23 21:03:42215 # platform, so we collect the broadest set of dependencies available.
216 # We may end up with the wrong revision of something for our
217 # platform, but this is the best we can do.
218 deps.update([x for x in os_deps.items() if not x[0] in deps])
219 else:
220 deps.update(os_deps)
221
[email protected]271375b2010-06-23 19:17:38222 self.deps_hooks.extend(local_scope.get('hooks', []))
223
224 # If a line is in custom_deps, but not in the solution, we want to append
225 # this line to the solution.
226 for d in self.custom_deps:
227 if d not in deps:
228 deps[d] = self.custom_deps[d]
[email protected]fb2b8eb2009-04-23 21:03:42229
230 # If use_relative_paths is set in the DEPS file, regenerate
231 # the dictionary using paths relative to the directory containing
232 # the DEPS file.
[email protected]271375b2010-06-23 19:17:38233 use_relative_paths = local_scope.get('use_relative_paths', False)
234 if use_relative_paths:
[email protected]fb2b8eb2009-04-23 21:03:42235 rel_deps = {}
236 for d, url in deps.items():
237 # normpath is required to allow DEPS to use .. in their
238 # dependency local path.
[email protected]271375b2010-06-23 19:17:38239 rel_deps[os.path.normpath(os.path.join(self.name, d))] = url
240 deps = rel_deps
241 # TODO(maruel): Add these dependencies into self.dependencies.
242 return deps
[email protected]fb2b8eb2009-04-23 21:03:42243
[email protected]271375b2010-06-23 19:17:38244 def _ParseAllDeps(self, solution_urls):
[email protected]fb2b8eb2009-04-23 21:03:42245 """Parse the complete list of dependencies for the client.
246
247 Args:
248 solution_urls: A dict mapping module names (as relative paths) to URLs
249 corresponding to the solutions specified by the client. This parameter
250 is passed as an optimization.
[email protected]fb2b8eb2009-04-23 21:03:42251
252 Returns:
253 A dict mapping module names (as relative paths) to URLs corresponding
254 to the entire set of dependencies to checkout for the given client.
255
256 Raises:
257 Error: If a dependency conflicts with another dependency or of a solution.
258 """
259 deps = {}
[email protected]54a07a22010-06-14 19:07:39260 for solution in self.dependencies:
[email protected]271375b2010-06-23 19:17:38261 solution_deps = solution.ParseDepsFile(True)
[email protected]fb2b8eb2009-04-23 21:03:42262
263 # If a line is in custom_deps, but not in the solution, we want to append
264 # this line to the solution.
[email protected]54a07a22010-06-14 19:07:39265 for d in solution.custom_deps:
266 if d not in solution_deps:
267 solution_deps[d] = solution.custom_deps[d]
[email protected]fb2b8eb2009-04-23 21:03:42268
269 for d in solution_deps:
[email protected]54a07a22010-06-14 19:07:39270 if d in solution.custom_deps:
[email protected]fb2b8eb2009-04-23 21:03:42271 # Dependency is overriden.
[email protected]54a07a22010-06-14 19:07:39272 url = solution.custom_deps[d]
[email protected]fb2b8eb2009-04-23 21:03:42273 if url is None:
274 continue
275 else:
276 url = solution_deps[d]
277 # if we have a From reference dependent on another solution, then
278 # just skip the From reference. When we pull deps for the solution,
279 # we will take care of this dependency.
280 #
281 # If multiple solutions all have the same From reference, then we
282 # should only add one to our list of dependencies.
[email protected]4b5b1772010-04-08 01:52:56283 if isinstance(url, self.FromImpl):
[email protected]fb2b8eb2009-04-23 21:03:42284 if url.module_name in solution_urls:
285 # Already parsed.
286 continue
287 if d in deps and type(deps[d]) != str:
288 if url.module_name == deps[d].module_name:
289 continue
[email protected]4b5b1772010-04-08 01:52:56290 elif isinstance(url, str):
[email protected]fb2b8eb2009-04-23 21:03:42291 parsed_url = urlparse.urlparse(url)
292 scheme = parsed_url[0]
293 if not scheme:
294 # A relative url. Fetch the real base.
295 path = parsed_url[2]
296 if path[0] != "/":
[email protected]e3608df2009-11-10 20:22:57297 raise gclient_utils.Error(
[email protected]fb2b8eb2009-04-23 21:03:42298 "relative DEPS entry \"%s\" must begin with a slash" % d)
[email protected]e6f78352010-01-13 17:05:33299 # Create a scm just to query the full url.
[email protected]54a07a22010-06-14 19:07:39300 scm = gclient_scm.CreateSCM(solution.url, self.root_dir(),
301 None)
[email protected]e6f78352010-01-13 17:05:33302 url = scm.FullUrlForRelativeUrl(url)
[email protected]fb2b8eb2009-04-23 21:03:42303 if d in deps and deps[d] != url:
[email protected]e3608df2009-11-10 20:22:57304 raise gclient_utils.Error(
[email protected]fb2b8eb2009-04-23 21:03:42305 "Solutions have conflicting versions of dependency \"%s\"" % d)
306 if d in solution_urls and solution_urls[d] != url:
[email protected]e3608df2009-11-10 20:22:57307 raise gclient_utils.Error(
[email protected]fb2b8eb2009-04-23 21:03:42308 "Dependency \"%s\" conflicts with specified solution" % d)
309 # Grab the dependency.
310 deps[d] = url
311 return deps
312
[email protected]71b40682009-07-31 23:40:09313 def _RunHookAction(self, hook_dict, matching_file_list):
[email protected]fb2b8eb2009-04-23 21:03:42314 """Runs the action from a single hook.
315 """
[email protected]5ca27692010-05-26 19:32:41316 logging.info(hook_dict)
317 logging.info(matching_file_list)
[email protected]fb2b8eb2009-04-23 21:03:42318 command = hook_dict['action'][:]
319 if command[0] == 'python':
320 # If the hook specified "python" as the first item, the action is a
321 # Python script. Run it by starting a new copy of the same
322 # interpreter.
323 command[0] = sys.executable
324
[email protected]71b40682009-07-31 23:40:09325 if '$matching_files' in command:
[email protected]68f2e092009-08-06 17:05:35326 splice_index = command.index('$matching_files')
327 command[splice_index:splice_index + 1] = matching_file_list
[email protected]71b40682009-07-31 23:40:09328
[email protected]fb2b8eb2009-04-23 21:03:42329 # Use a discrete exit status code of 2 to indicate that a hook action
330 # failed. Users of this script may wish to treat hook action failures
331 # differently from VC failures.
[email protected]75a59272010-06-11 22:34:03332 gclient_utils.SubprocessCall(command, self.root_dir(), fail_status=2)
[email protected]fb2b8eb2009-04-23 21:03:42333
334 def _RunHooks(self, command, file_list, is_using_git):
335 """Evaluates all hooks, running actions as needed.
336 """
337 # Hooks only run for these command types.
338 if not command in ('update', 'revert', 'runhooks'):
339 return
340
[email protected]67820ef2009-07-27 17:23:00341 # Hooks only run when --nohooks is not specified
342 if self._options.nohooks:
343 return
344
[email protected]fb2b8eb2009-04-23 21:03:42345 # Get any hooks from the .gclient file.
[email protected]0b6a0842010-06-15 14:34:19346 hooks = self.deps_hooks[:]
[email protected]fb2b8eb2009-04-23 21:03:42347 # Add any hooks found in DEPS files.
[email protected]54a07a22010-06-14 19:07:39348 for d in self.dependencies:
[email protected]0b6a0842010-06-15 14:34:19349 hooks.extend(d.deps_hooks)
[email protected]fb2b8eb2009-04-23 21:03:42350
351 # If "--force" was specified, run all hooks regardless of what files have
352 # changed. If the user is using git, then we don't know what files have
353 # changed so we always run all hooks.
354 if self._options.force or is_using_git:
355 for hook_dict in hooks:
[email protected]71b40682009-07-31 23:40:09356 self._RunHookAction(hook_dict, [])
[email protected]fb2b8eb2009-04-23 21:03:42357 return
358
359 # Run hooks on the basis of whether the files from the gclient operation
360 # match each hook's pattern.
361 for hook_dict in hooks:
362 pattern = re.compile(hook_dict['pattern'])
[email protected]e3608df2009-11-10 20:22:57363 matching_file_list = [f for f in file_list if pattern.search(f)]
[email protected]71b40682009-07-31 23:40:09364 if matching_file_list:
365 self._RunHookAction(hook_dict, matching_file_list)
[email protected]fb2b8eb2009-04-23 21:03:42366
[email protected]271375b2010-06-23 19:17:38367 def root_dir(self):
368 return self.parent.root_dir()
369
370 def enforced_os(self):
371 return self.parent.enforced_os()
372
[email protected]9a66ddf2010-06-16 16:54:16373
374class GClient(Dependency):
375 """Main gclient checkout root where .gclient resides."""
376 SUPPORTED_COMMANDS = [
377 'cleanup', 'diff', 'export', 'pack', 'revert', 'status', 'update',
378 'runhooks'
379 ]
380
381 DEPS_OS_CHOICES = {
382 "win32": "win",
383 "win": "win",
384 "cygwin": "win",
385 "darwin": "mac",
386 "mac": "mac",
387 "unix": "unix",
388 "linux": "unix",
389 "linux2": "unix",
390 }
391
392 DEFAULT_CLIENT_FILE_TEXT = ("""\
393solutions = [
394 { "name" : "%(solution_name)s",
395 "url" : "%(solution_url)s",
396 "custom_deps" : {
397 },
398 "safesync_url": "%(safesync_url)s"
399 },
400]
401""")
402
403 DEFAULT_SNAPSHOT_SOLUTION_TEXT = ("""\
404 { "name" : "%(solution_name)s",
405 "url" : "%(solution_url)s",
406 "custom_deps" : {
407 %(solution_deps)s,
408 },
409 "safesync_url": "%(safesync_url)s"
410 },
411""")
412
413 DEFAULT_SNAPSHOT_FILE_TEXT = ("""\
414# Snapshot generated with gclient revinfo --snapshot
415solutions = [
416%(solution_list)s
417]
418""")
419
420 def __init__(self, root_dir, options):
421 Dependency.__init__(self, None, None, None)
[email protected]0d425922010-06-21 19:22:24422 self._options = options
[email protected]271375b2010-06-23 19:17:38423 if options.deps_os:
424 enforced_os = options.deps_os.split(',')
425 else:
426 enforced_os = [self.DEPS_OS_CHOICES.get(sys.platform, 'unix')]
427 if 'all' in enforced_os:
428 enforced_os = self.DEPS_OS_CHOICES.itervalues()
429 self._enforced_os = list(set(enforced_os))
430 self._root_dir = root_dir
[email protected]9a66ddf2010-06-16 16:54:16431 self.config_content = None
432
433 def SetConfig(self, content):
434 assert self.dependencies == []
435 config_dict = {}
436 self.config_content = content
437 try:
438 exec(content, config_dict)
439 except SyntaxError, e:
440 try:
441 # Try to construct a human readable error message
442 error_message = [
443 'There is a syntax error in your configuration file.',
444 'Line #%s, character %s:' % (e.lineno, e.offset),
445 '"%s"' % re.sub(r'[\r\n]*$', '', e.text) ]
446 except:
447 # Something went wrong, re-raise the original exception
448 raise e
449 else:
450 # Raise a new exception with the human readable message:
451 raise gclient_utils.Error('\n'.join(error_message))
452 for s in config_dict.get('solutions', []):
453 self.dependencies.append(Dependency(
454 self, s['name'], s['url'],
455 s.get('safesync_url', None),
456 s.get('custom_deps', {}),
457 s.get('custom_vars', {})))
458 # .gclient can have hooks.
459 self.deps_hooks = config_dict.get('hooks', [])
460
461 def SaveConfig(self):
462 gclient_utils.FileWrite(os.path.join(self.root_dir(),
463 self._options.config_filename),
464 self.config_content)
465
466 @staticmethod
467 def LoadCurrentConfig(options):
468 """Searches for and loads a .gclient file relative to the current working
469 dir. Returns a GClient object."""
470 path = gclient_utils.FindGclientRoot(os.getcwd(), options.config_filename)
471 if not path:
472 return None
473 client = GClient(path, options)
474 client.SetConfig(gclient_utils.FileRead(
475 os.path.join(path, options.config_filename)))
476 return client
477
478 def SetDefaultConfig(self, solution_name, solution_url, safesync_url):
479 self.SetConfig(self.DEFAULT_CLIENT_FILE_TEXT % {
480 'solution_name': solution_name,
481 'solution_url': solution_url,
482 'safesync_url' : safesync_url,
483 })
484
485 def _SaveEntries(self, entries):
486 """Creates a .gclient_entries file to record the list of unique checkouts.
487
488 The .gclient_entries file lives in the same directory as .gclient.
489
490 Args:
491 entries: A sequence of solution names.
492 """
493 # Sometimes pprint.pformat will use {', sometimes it'll use { ' ... It
494 # makes testing a bit too fun.
495 result = pprint.pformat(entries, 2)
496 if result.startswith('{\''):
497 result = '{ \'' + result[2:]
498 text = "entries = \\\n" + result + '\n'
499 file_path = os.path.join(self.root_dir(), self._options.entries_filename)
500 gclient_utils.FileWrite(file_path, text)
501
502 def _ReadEntries(self):
503 """Read the .gclient_entries file for the given client.
504
505 Returns:
506 A sequence of solution names, which will be empty if there is the
507 entries file hasn't been created yet.
508 """
509 scope = {}
510 filename = os.path.join(self.root_dir(), self._options.entries_filename)
511 if not os.path.exists(filename):
512 return []
513 exec(gclient_utils.FileRead(filename), scope)
514 return scope['entries']
515
[email protected]54a07a22010-06-14 19:07:39516 def _EnforceRevisions(self):
[email protected]918a9ae2010-05-28 15:50:30517 """Checks for revision overrides."""
518 revision_overrides = {}
[email protected]307d1792010-05-31 20:03:13519 if self._options.head:
520 return revision_overrides
[email protected]54a07a22010-06-14 19:07:39521 for s in self.dependencies:
522 if not s.safesync_url:
[email protected]307d1792010-05-31 20:03:13523 continue
[email protected]54a07a22010-06-14 19:07:39524 handle = urllib.urlopen(s.safesync_url)
[email protected]307d1792010-05-31 20:03:13525 rev = handle.read().strip()
526 handle.close()
527 if len(rev):
[email protected]54a07a22010-06-14 19:07:39528 self._options.revisions.append('%s@%s' % (s.name, rev))
[email protected]307d1792010-05-31 20:03:13529 if not self._options.revisions:
530 return revision_overrides
531 # --revision will take over safesync_url.
[email protected]54a07a22010-06-14 19:07:39532 solutions_names = [s.name for s in self.dependencies]
[email protected]307d1792010-05-31 20:03:13533 index = 0
534 for revision in self._options.revisions:
535 if not '@' in revision:
536 # Support for --revision 123
537 revision = '%s@%s' % (solutions_names[index], revision)
[email protected]54a07a22010-06-14 19:07:39538 sol, rev = revision.split('@', 1)
[email protected]307d1792010-05-31 20:03:13539 if not sol in solutions_names:
540 #raise gclient_utils.Error('%s is not a valid solution.' % sol)
541 print >> sys.stderr, ('Please fix your script, having invalid '
542 '--revision flags will soon considered an error.')
543 else:
[email protected]918a9ae2010-05-28 15:50:30544 revision_overrides[sol] = rev
[email protected]307d1792010-05-31 20:03:13545 index += 1
[email protected]918a9ae2010-05-28 15:50:30546 return revision_overrides
547
[email protected]fb2b8eb2009-04-23 21:03:42548 def RunOnDeps(self, command, args):
549 """Runs a command on each dependency in a client and its dependencies.
550
[email protected]fb2b8eb2009-04-23 21:03:42551 Args:
552 command: The command to use (e.g., 'status' or 'diff')
553 args: list of str - extra arguments to add to the command line.
[email protected]fb2b8eb2009-04-23 21:03:42554 """
[email protected]116704f2010-06-11 17:34:38555 if not command in self.SUPPORTED_COMMANDS:
[email protected]e3608df2009-11-10 20:22:57556 raise gclient_utils.Error("'%s' is an unsupported command" % command)
[email protected]fb2b8eb2009-04-23 21:03:42557
[email protected]54a07a22010-06-14 19:07:39558 if not self.dependencies:
[email protected]e3608df2009-11-10 20:22:57559 raise gclient_utils.Error("No solution specified")
[email protected]54a07a22010-06-14 19:07:39560 revision_overrides = self._EnforceRevisions()
[email protected]fb2b8eb2009-04-23 21:03:42561
562 # When running runhooks --force, there's no need to consult the SCM.
563 # All known hooks are expected to run unconditionally regardless of working
564 # copy state, so skip the SCM status check.
565 run_scm = not (command == 'runhooks' and self._options.force)
566
567 entries = {}
[email protected]d2e92562010-04-27 01:55:18568 file_list = []
569 # Run on the base solutions first.
[email protected]54a07a22010-06-14 19:07:39570 for solution in self.dependencies:
571 name = solution.name
[email protected]d2e92562010-04-27 01:55:18572 if name in entries:
573 raise gclient_utils.Error("solution %s specified more than once" % name)
[email protected]54a07a22010-06-14 19:07:39574 url = solution.url
[email protected]d2e92562010-04-27 01:55:18575 entries[name] = url
576 if run_scm and url:
577 self._options.revision = revision_overrides.get(name)
[email protected]75a59272010-06-11 22:34:03578 scm = gclient_scm.CreateSCM(url, self.root_dir(), name)
[email protected]d2e92562010-04-27 01:55:18579 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
[email protected]fb2b8eb2009-04-23 21:03:42582
[email protected]d2e92562010-04-27 01:55:18583 # Process the dependencies next (sort alphanumerically to ensure that
584 # containing directories get populated first and for readability)
[email protected]271375b2010-06-23 19:17:38585 deps = self._ParseAllDeps(entries)
[email protected]d2e92562010-04-27 01:55:18586 deps_to_process = deps.keys()
587 deps_to_process.sort()
[email protected]fb2b8eb2009-04-23 21:03:42588
[email protected]d2e92562010-04-27 01:55:18589 # First pass for direct dependencies.
590 if command == 'update' and not self._options.verbose:
591 pm = Progress('Syncing projects', len(deps_to_process))
592 for d in deps_to_process:
[email protected]1f7a3d12010-02-04 15:11:50593 if command == 'update' and not self._options.verbose:
[email protected]d2e92562010-04-27 01:55:18594 pm.update()
595 if type(deps[d]) == str:
596 url = deps[d]
597 entries[d] = url
598 if run_scm:
599 self._options.revision = revision_overrides.get(d)
[email protected]75a59272010-06-11 22:34:03600 scm = gclient_scm.CreateSCM(url, self.root_dir(), d)
[email protected]d2e92562010-04-27 01:55:18601 scm.RunCommand(command, self._options, args, file_list)
602 self._options.revision = None
603 elif isinstance(deps[d], self.FileImpl):
[email protected]491c04b2010-05-17 18:17:44604 file_dep = deps[d]
605 self._options.revision = file_dep.GetRevision()
[email protected]d2e92562010-04-27 01:55:18606 if run_scm:
[email protected]75a59272010-06-11 22:34:03607 scm = gclient_scm.CreateSCM(file_dep.GetPath(), self.root_dir(), d)
[email protected]d2e92562010-04-27 01:55:18608 scm.RunCommand("updatesingle", self._options,
[email protected]491c04b2010-05-17 18:17:44609 args + [file_dep.GetFilename()], file_list)
[email protected]79692d62010-05-14 18:57:13610
[email protected]d2e92562010-04-27 01:55:18611 if command == 'update' and not self._options.verbose:
612 pm.end()
[email protected]6f363722010-04-27 00:41:09613
[email protected]d2e92562010-04-27 01:55:18614 # Second pass for inherited deps (via the From keyword)
615 for d in deps_to_process:
616 if isinstance(deps[d], self.FromImpl):
[email protected]d2e92562010-04-27 01:55:18617 # Getting the URL from the sub_deps file can involve having to resolve
618 # a File() or having to resolve a relative URL. To resolve relative
619 # URLs, we need to pass in the orignal sub deps URL.
620 sub_deps_base_url = deps[deps[d].module_name]
[email protected]271375b2010-06-23 19:17:38621 sub_deps = Dependency(self, deps[d].module_name, sub_deps_base_url
622 ).ParseDepsFile(False)
[email protected]75a59272010-06-11 22:34:03623 url = deps[d].GetUrl(d, sub_deps_base_url, self.root_dir(), sub_deps)
[email protected]d2e92562010-04-27 01:55:18624 entries[d] = url
625 if run_scm:
626 self._options.revision = revision_overrides.get(d)
[email protected]75a59272010-06-11 22:34:03627 scm = gclient_scm.CreateSCM(url, self.root_dir(), d)
[email protected]d2e92562010-04-27 01:55:18628 scm.RunCommand(command, self._options, args, file_list)
629 self._options.revision = None
[email protected]df2d5902009-09-11 22:16:21630
[email protected]d83b2b22009-08-11 15:30:55631 # Convert all absolute paths to relative.
632 for i in range(len(file_list)):
633 # TODO(phajdan.jr): We should know exactly when the paths are absolute.
634 # It depends on the command being executed (like runhooks vs sync).
635 if not os.path.isabs(file_list[i]):
636 continue
637
[email protected]75a59272010-06-11 22:34:03638 prefix = os.path.commonprefix([self.root_dir().lower(),
[email protected]d83b2b22009-08-11 15:30:55639 file_list[i].lower()])
640 file_list[i] = file_list[i][len(prefix):]
641
642 # Strip any leading path separators.
643 while file_list[i].startswith('\\') or file_list[i].startswith('/'):
644 file_list[i] = file_list[i][1:]
[email protected]fb2b8eb2009-04-23 21:03:42645
[email protected]75a59272010-06-11 22:34:03646 is_using_git = gclient_utils.IsUsingGit(self.root_dir(), entries.keys())
[email protected]fb2b8eb2009-04-23 21:03:42647 self._RunHooks(command, file_list, is_using_git)
648
649 if command == 'update':
[email protected]cdcee802009-06-23 15:30:42650 # Notify the user if there is an orphaned entry in their working copy.
651 # Only delete the directory if there are no changes in it, and
652 # delete_unversioned_trees is set to true.
[email protected]fb2b8eb2009-04-23 21:03:42653 prev_entries = self._ReadEntries()
654 for entry in prev_entries:
[email protected]c5e9aec2009-08-03 18:25:56655 # Fix path separator on Windows.
656 entry_fixed = entry.replace('/', os.path.sep)
[email protected]75a59272010-06-11 22:34:03657 e_dir = os.path.join(self.root_dir(), entry_fixed)
[email protected]c5e9aec2009-08-03 18:25:56658 # Use entry and not entry_fixed there.
[email protected]0329e672009-05-13 18:41:04659 if entry not in entries and os.path.exists(e_dir):
[email protected]83017012009-09-28 18:52:12660 modified_files = False
[email protected]5aeb7dd2009-11-17 18:09:01661 if isinstance(prev_entries, list):
[email protected]83017012009-09-28 18:52:12662 # old .gclient_entries format was list, now dict
[email protected]5aeb7dd2009-11-17 18:09:01663 modified_files = gclient_scm.scm.SVN.CaptureStatus(e_dir)
[email protected]83017012009-09-28 18:52:12664 else:
665 file_list = []
[email protected]75a59272010-06-11 22:34:03666 scm = gclient_scm.CreateSCM(prev_entries[entry], self.root_dir(),
[email protected]83017012009-09-28 18:52:12667 entry_fixed)
668 scm.status(self._options, [], file_list)
669 modified_files = file_list != []
670 if not self._options.delete_unversioned_trees or modified_files:
[email protected]c5e9aec2009-08-03 18:25:56671 # There are modified files in this entry. Keep warning until
672 # removed.
[email protected]c5e9aec2009-08-03 18:25:56673 print(("\nWARNING: \"%s\" is no longer part of this client. "
674 "It is recommended that you manually remove it.\n") %
675 entry_fixed)
[email protected]fb2b8eb2009-04-23 21:03:42676 else:
677 # Delete the entry
[email protected]df7a3132009-05-12 17:49:49678 print("\n________ deleting \'%s\' " +
[email protected]75a59272010-06-11 22:34:03679 "in \'%s\'") % (entry_fixed, self.root_dir())
[email protected]5f3eee32009-09-17 00:34:30680 gclient_utils.RemoveDirectory(e_dir)
[email protected]fb2b8eb2009-04-23 21:03:42681 # record the current list of entries for next time
682 self._SaveEntries(entries)
[email protected]17cdf762010-05-28 17:30:52683 return 0
[email protected]fb2b8eb2009-04-23 21:03:42684
685 def PrintRevInfo(self):
[email protected]5d63eb82010-03-24 23:22:09686 """Output revision info mapping for the client and its dependencies.
687
688 This allows the capture of an overall "revision" for the source tree that
[email protected]918a9ae2010-05-28 15:50:30689 can be used to reproduce the same tree in the future. It is only useful for
690 "unpinned dependencies", i.e. DEPS/deps references without a svn revision
691 number or a git hash. A git branch name isn't "pinned" since the actual
692 commit can change.
[email protected]5d63eb82010-03-24 23:22:09693
694 The --snapshot option allows creating a .gclient file to reproduce the tree.
[email protected]fb2b8eb2009-04-23 21:03:42695 """
[email protected]54a07a22010-06-14 19:07:39696 if not self.dependencies:
[email protected]e3608df2009-11-10 20:22:57697 raise gclient_utils.Error("No solution specified")
[email protected]fb2b8eb2009-04-23 21:03:42698
[email protected]5d63eb82010-03-24 23:22:09699 # Inner helper to generate base url and rev tuple
[email protected]fb2b8eb2009-04-23 21:03:42700 def GetURLAndRev(name, original_url):
[email protected]5d63eb82010-03-24 23:22:09701 url, _ = gclient_utils.SplitUrlRevision(original_url)
[email protected]75a59272010-06-11 22:34:03702 scm = gclient_scm.CreateSCM(original_url, self.root_dir(), name)
[email protected]5d63eb82010-03-24 23:22:09703 return (url, scm.revinfo(self._options, [], None))
[email protected]fb2b8eb2009-04-23 21:03:42704
[email protected]e3da35f2010-03-09 21:40:45705 # text of the snapshot gclient file
706 new_gclient = ""
707 # Dictionary of { path : SCM url } to ensure no duplicate solutions
708 solution_names = {}
[email protected]de8f3522010-03-11 23:47:44709 entries = {}
[email protected]fb2b8eb2009-04-23 21:03:42710 # Run on the base solutions first.
[email protected]54a07a22010-06-14 19:07:39711 for solution in self.dependencies:
[email protected]e3da35f2010-03-09 21:40:45712 # Dictionary of { path : SCM url } to describe the gclient checkout
[email protected]54a07a22010-06-14 19:07:39713 name = solution.name
[email protected]e3da35f2010-03-09 21:40:45714 if name in solution_names:
[email protected]e3608df2009-11-10 20:22:57715 raise gclient_utils.Error("solution %s specified more than once" % name)
[email protected]54a07a22010-06-14 19:07:39716 (url, rev) = GetURLAndRev(name, solution.url)
[email protected]770ff9e2009-09-23 17:18:18717 entries[name] = "%s@%s" % (url, rev)
[email protected]e3da35f2010-03-09 21:40:45718 solution_names[name] = "%s@%s" % (url, rev)
[email protected]fb2b8eb2009-04-23 21:03:42719
[email protected]de8f3522010-03-11 23:47:44720 # Process the dependencies next (sort alphanumerically to ensure that
721 # containing directories get populated first and for readability)
[email protected]271375b2010-06-23 19:17:38722 deps = self._ParseAllDeps(entries)
[email protected]de8f3522010-03-11 23:47:44723 deps_to_process = deps.keys()
724 deps_to_process.sort()
[email protected]fb2b8eb2009-04-23 21:03:42725
[email protected]de8f3522010-03-11 23:47:44726 # First pass for direct dependencies.
727 for d in deps_to_process:
728 if type(deps[d]) == str:
729 (url, rev) = GetURLAndRev(d, deps[d])
730 entries[d] = "%s@%s" % (url, rev)
[email protected]fb2b8eb2009-04-23 21:03:42731
[email protected]de8f3522010-03-11 23:47:44732 # Second pass for inherited deps (via the From keyword)
733 for d in deps_to_process:
[email protected]4b5b1772010-04-08 01:52:56734 if isinstance(deps[d], self.FromImpl):
[email protected]de8f3522010-03-11 23:47:44735 deps_parent_url = entries[deps[d].module_name]
736 if deps_parent_url.find("@") < 0:
737 raise gclient_utils.Error("From %s missing revisioned url" %
738 deps[d].module_name)
[email protected]271375b2010-06-23 19:17:38739 sub_deps_base_url = deps[deps[d].module_name]
740 sub_deps = Dependency(self, deps[d].module_name, sub_deps_base_url
741 ).ParseDepsFile(False)
742 url = deps[d].GetUrl(d, sub_deps_base_url, self.root_dir(), sub_deps)
743 (url, rev) = GetURLAndRev(d, url)
[email protected]de8f3522010-03-11 23:47:44744 entries[d] = "%s@%s" % (url, rev)
[email protected]e3da35f2010-03-09 21:40:45745
[email protected]de8f3522010-03-11 23:47:44746 # Build the snapshot configuration string
747 if self._options.snapshot:
748 url = entries.pop(name)
749 custom_deps = ",\n ".join(["\"%s\": \"%s\"" % (x, entries[x])
750 for x in sorted(entries.keys())])
[email protected]e3da35f2010-03-09 21:40:45751
[email protected]1f7d1182010-05-17 18:17:38752 new_gclient += self.DEFAULT_SNAPSHOT_SOLUTION_TEXT % {
[email protected]de8f3522010-03-11 23:47:44753 'solution_name': name,
754 'solution_url': url,
755 'safesync_url' : "",
756 'solution_deps': custom_deps,
757 }
758 else:
759 print(";\n".join(["%s: %s" % (x, entries[x])
760 for x in sorted(entries.keys())]))
[email protected]e3da35f2010-03-09 21:40:45761
762 # Print the snapshot configuration file
763 if self._options.snapshot:
[email protected]491c04b2010-05-17 18:17:44764 config = self.DEFAULT_SNAPSHOT_FILE_TEXT % {'solution_list': new_gclient}
[email protected]75a59272010-06-11 22:34:03765 snapclient = GClient(self.root_dir(), self._options)
[email protected]e3da35f2010-03-09 21:40:45766 snapclient.SetConfig(config)
[email protected]116704f2010-06-11 17:34:38767 print(snapclient.config_content)
[email protected]fb2b8eb2009-04-23 21:03:42768
[email protected]75a59272010-06-11 22:34:03769 def root_dir(self):
770 return self._root_dir
771
[email protected]271375b2010-06-23 19:17:38772 def enforced_os(self):
773 return self._enforced_os
774
[email protected]fb2b8eb2009-04-23 21:03:42775
[email protected]5ca27692010-05-26 19:32:41776#### gclient commands.
[email protected]fb2b8eb2009-04-23 21:03:42777
778
[email protected]5ca27692010-05-26 19:32:41779def CMDcleanup(parser, args):
780 """Cleans up all working copies.
[email protected]ddff62d2010-05-17 21:02:36781
[email protected]5ca27692010-05-26 19:32:41782Mostly svn-specific. Simply runs 'svn cleanup' for each module.
[email protected]79692d62010-05-14 18:57:13783"""
[email protected]0b6a0842010-06-15 14:34:19784 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
785 help='override deps for the specified (comma-separated) '
786 'platform(s); \'all\' will process all deps_os '
787 'references')
[email protected]5ca27692010-05-26 19:32:41788 (options, args) = parser.parse_args(args)
[email protected]2806acc2009-05-15 12:33:34789 client = GClient.LoadCurrentConfig(options)
[email protected]fb2b8eb2009-04-23 21:03:42790 if not client:
[email protected]0b6a0842010-06-15 14:34:19791 raise gclient_utils.Error('client not configured; see \'gclient config\'')
[email protected]fb2b8eb2009-04-23 21:03:42792 if options.verbose:
793 # Print out the .gclient file. This is longer than if we just printed the
794 # client dict, but more legible, and it might contain helpful comments.
[email protected]116704f2010-06-11 17:34:38795 print(client.config_content)
[email protected]fb2b8eb2009-04-23 21:03:42796 return client.RunOnDeps('cleanup', args)
797
798
[email protected]5ca27692010-05-26 19:32:41799@attr('usage', '[url] [safesync url]')
800def CMDconfig(parser, args):
[email protected]ddff62d2010-05-17 21:02:36801 """Create a .gclient file in the current directory.
802
[email protected]5ca27692010-05-26 19:32:41803This specifies the configuration for further commands. After update/sync,
[email protected]79692d62010-05-14 18:57:13804top-level DEPS files in each module are read to determine dependent
[email protected]5ca27692010-05-26 19:32:41805modules to operate on as well. If optional [url] parameter is
[email protected]79692d62010-05-14 18:57:13806provided, then configuration is read from a specified Subversion server
[email protected]5ca27692010-05-26 19:32:41807URL.
[email protected]79692d62010-05-14 18:57:13808"""
[email protected]0b6a0842010-06-15 14:34:19809 parser.add_option('--spec',
810 help='create a gclient file containing the provided '
811 'string. Due to Cygwin/Python brokenness, it '
812 'probably can\'t contain any newlines.')
813 parser.add_option('--name',
814 help='overrides the default name for the solution')
[email protected]5ca27692010-05-26 19:32:41815 (options, args) = parser.parse_args(args)
[email protected]5fc2a332010-05-26 19:37:15816 if ((options.spec and args) or len(args) > 2 or
817 (not options.spec and not args)):
818 parser.error('Inconsistent arguments. Use either --spec or one or 2 args')
819
[email protected]0329e672009-05-13 18:41:04820 if os.path.exists(options.config_filename):
[email protected]0b6a0842010-06-15 14:34:19821 raise gclient_utils.Error('%s file already exists in the current directory'
[email protected]e3608df2009-11-10 20:22:57822 % options.config_filename)
[email protected]2806acc2009-05-15 12:33:34823 client = GClient('.', options)
[email protected]fb2b8eb2009-04-23 21:03:42824 if options.spec:
825 client.SetConfig(options.spec)
826 else:
[email protected]1ab7ffc2009-06-03 17:21:37827 base_url = args[0].rstrip('/')
[email protected]8cf7a392010-04-07 17:20:26828 if not options.name:
[email protected]0b6a0842010-06-15 14:34:19829 name = base_url.split('/')[-1]
[email protected]8cf7a392010-04-07 17:20:26830 else:
831 # specify an alternate relpath for the given URL.
832 name = options.name
[email protected]0b6a0842010-06-15 14:34:19833 safesync_url = ''
[email protected]fb2b8eb2009-04-23 21:03:42834 if len(args) > 1:
835 safesync_url = args[1]
836 client.SetDefaultConfig(name, base_url, safesync_url)
837 client.SaveConfig()
[email protected]79692d62010-05-14 18:57:13838 return 0
[email protected]fb2b8eb2009-04-23 21:03:42839
840
[email protected]5ca27692010-05-26 19:32:41841def CMDexport(parser, args):
[email protected]ddff62d2010-05-17 21:02:36842 """Wrapper for svn export for all managed directories."""
[email protected]0b6a0842010-06-15 14:34:19843 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
844 help='override deps for the specified (comma-separated) '
845 'platform(s); \'all\' will process all deps_os '
846 'references')
[email protected]5ca27692010-05-26 19:32:41847 (options, args) = parser.parse_args(args)
[email protected]644aa0c2009-07-17 20:20:41848 if len(args) != 1:
[email protected]0b6a0842010-06-15 14:34:19849 raise gclient_utils.Error('Need directory name')
[email protected]644aa0c2009-07-17 20:20:41850 client = GClient.LoadCurrentConfig(options)
851
852 if not client:
[email protected]0b6a0842010-06-15 14:34:19853 raise gclient_utils.Error('client not configured; see \'gclient config\'')
[email protected]644aa0c2009-07-17 20:20:41854
855 if options.verbose:
856 # Print out the .gclient file. This is longer than if we just printed the
857 # client dict, but more legible, and it might contain helpful comments.
[email protected]116704f2010-06-11 17:34:38858 print(client.config_content)
[email protected]644aa0c2009-07-17 20:20:41859 return client.RunOnDeps('export', args)
860
[email protected]fb2b8eb2009-04-23 21:03:42861
[email protected]5ca27692010-05-26 19:32:41862@attr('epilog', """Example:
863 gclient pack > patch.txt
864 generate simple patch for configured client and dependences
865""")
866def CMDpack(parser, args):
[email protected]79692d62010-05-14 18:57:13867 """Generate a patch which can be applied at the root of the tree.
[email protected]ddff62d2010-05-17 21:02:36868
[email protected]5ca27692010-05-26 19:32:41869Internally, runs 'svn diff'/'git diff' on each checked out module and
[email protected]79692d62010-05-14 18:57:13870dependencies, and performs minimal postprocessing of the output. The
871resulting patch is printed to stdout and can be applied to a freshly
[email protected]5ca27692010-05-26 19:32:41872checked out tree via 'patch -p0 < patchfile'.
[email protected]79692d62010-05-14 18:57:13873"""
[email protected]0b6a0842010-06-15 14:34:19874 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
875 help='override deps for the specified (comma-separated) '
876 'platform(s); \'all\' will process all deps_os '
877 'references')
[email protected]5ca27692010-05-26 19:32:41878 (options, args) = parser.parse_args(args)
[email protected]ab318592009-09-04 00:54:55879 client = GClient.LoadCurrentConfig(options)
880 if not client:
[email protected]0b6a0842010-06-15 14:34:19881 raise gclient_utils.Error('client not configured; see \'gclient config\'')
[email protected]ab318592009-09-04 00:54:55882 if options.verbose:
883 # Print out the .gclient file. This is longer than if we just printed the
884 # client dict, but more legible, and it might contain helpful comments.
[email protected]116704f2010-06-11 17:34:38885 print(client.config_content)
[email protected]ab318592009-09-04 00:54:55886 return client.RunOnDeps('pack', args)
887
888
[email protected]5ca27692010-05-26 19:32:41889def CMDstatus(parser, args):
890 """Show modification status for every dependencies."""
[email protected]0b6a0842010-06-15 14:34:19891 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
892 help='override deps for the specified (comma-separated) '
893 'platform(s); \'all\' will process all deps_os '
894 'references')
[email protected]5ca27692010-05-26 19:32:41895 (options, args) = parser.parse_args(args)
[email protected]2806acc2009-05-15 12:33:34896 client = GClient.LoadCurrentConfig(options)
[email protected]fb2b8eb2009-04-23 21:03:42897 if not client:
[email protected]0b6a0842010-06-15 14:34:19898 raise gclient_utils.Error('client not configured; see \'gclient config\'')
[email protected]fb2b8eb2009-04-23 21:03:42899 if options.verbose:
900 # Print out the .gclient file. This is longer than if we just printed the
901 # client dict, but more legible, and it might contain helpful comments.
[email protected]116704f2010-06-11 17:34:38902 print(client.config_content)
[email protected]fb2b8eb2009-04-23 21:03:42903 return client.RunOnDeps('status', args)
904
905
[email protected]5ca27692010-05-26 19:32:41906@attr('epilog', """Examples:
[email protected]79692d62010-05-14 18:57:13907 gclient sync
908 update files from SCM according to current configuration,
909 *for modules which have changed since last update or sync*
910 gclient sync --force
911 update files from SCM according to current configuration, for
912 all modules (useful for recovering files deleted from local copy)
913 gclient sync --revision src@31000
914 update src directory to r31000
[email protected]5ca27692010-05-26 19:32:41915""")
916def CMDsync(parser, args):
917 """Checkout/update all modules."""
[email protected]0b6a0842010-06-15 14:34:19918 parser.add_option('-f', '--force', action='store_true',
919 help='force update even for unchanged modules')
920 parser.add_option('-n', '--nohooks', action='store_true',
921 help='don\'t run hooks after the update is complete')
922 parser.add_option('-r', '--revision', action='append',
923 dest='revisions', metavar='REV', default=[],
924 help='Enforces revision/hash for the solutions with the '
925 'format src@rev. The src@ part is optional and can be '
926 'skipped. -r can be used multiple times when .gclient '
927 'has multiple solutions configured and will work even '
928 'if the src@ part is skipped.')
929 parser.add_option('-H', '--head', action='store_true',
930 help='skips any safesync_urls specified in '
931 'configured solutions and sync to head instead')
932 parser.add_option('-D', '--delete_unversioned_trees', action='store_true',
933 help='delete any unexpected unversioned trees '
934 'that are in the checkout')
935 parser.add_option('-R', '--reset', action='store_true',
936 help='resets any local changes before updating (git only)')
937 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
938 help='override deps for the specified (comma-separated) '
939 'platform(s); \'all\' will process all deps_os '
940 'references')
941 parser.add_option('-m', '--manually_grab_svn_rev', action='store_true',
942 help='Skip svn up whenever possible by requesting '
943 'actual HEAD revision from the repository')
[email protected]5ca27692010-05-26 19:32:41944 (options, args) = parser.parse_args(args)
[email protected]2806acc2009-05-15 12:33:34945 client = GClient.LoadCurrentConfig(options)
[email protected]fb2b8eb2009-04-23 21:03:42946
947 if not client:
[email protected]0b6a0842010-06-15 14:34:19948 raise gclient_utils.Error('client not configured; see \'gclient config\'')
[email protected]fb2b8eb2009-04-23 21:03:42949
[email protected]307d1792010-05-31 20:03:13950 if options.revisions and options.head:
951 # TODO(maruel): Make it a parser.error if it doesn't break any builder.
[email protected]0b6a0842010-06-15 14:34:19952 print('Warning: you cannot use both --head and --revision')
[email protected]fb2b8eb2009-04-23 21:03:42953
954 if options.verbose:
955 # Print out the .gclient file. This is longer than if we just printed the
956 # client dict, but more legible, and it might contain helpful comments.
[email protected]116704f2010-06-11 17:34:38957 print(client.config_content)
[email protected]fb2b8eb2009-04-23 21:03:42958 return client.RunOnDeps('update', args)
959
960
[email protected]5ca27692010-05-26 19:32:41961def CMDupdate(parser, args):
[email protected]ddff62d2010-05-17 21:02:36962 """Alias for the sync command. Deprecated."""
[email protected]5ca27692010-05-26 19:32:41963 return CMDsync(parser, args)
[email protected]fb2b8eb2009-04-23 21:03:42964
[email protected]5ca27692010-05-26 19:32:41965def CMDdiff(parser, args):
966 """Displays local diff for every dependencies."""
[email protected]0b6a0842010-06-15 14:34:19967 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
968 help='override deps for the specified (comma-separated) '
969 'platform(s); \'all\' will process all deps_os '
970 'references')
[email protected]5ca27692010-05-26 19:32:41971 (options, args) = parser.parse_args(args)
[email protected]2806acc2009-05-15 12:33:34972 client = GClient.LoadCurrentConfig(options)
[email protected]fb2b8eb2009-04-23 21:03:42973 if not client:
[email protected]0b6a0842010-06-15 14:34:19974 raise gclient_utils.Error('client not configured; see \'gclient config\'')
[email protected]fb2b8eb2009-04-23 21:03:42975 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('diff', args)
980
981
[email protected]5ca27692010-05-26 19:32:41982def CMDrevert(parser, args):
983 """Revert all modifications in every dependencies."""
[email protected]0b6a0842010-06-15 14:34:19984 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
985 help='override deps for the specified (comma-separated) '
986 'platform(s); \'all\' will process all deps_os '
987 'references')
988 parser.add_option('-n', '--nohooks', action='store_true',
989 help='don\'t run hooks after the revert is complete')
[email protected]5ca27692010-05-26 19:32:41990 (options, args) = parser.parse_args(args)
991 # --force is implied.
992 options.force = True
[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]0b6a0842010-06-15 14:34:19995 raise gclient_utils.Error('client not configured; see \'gclient config\'')
[email protected]fb2b8eb2009-04-23 21:03:42996 return client.RunOnDeps('revert', args)
997
998
[email protected]5ca27692010-05-26 19:32:41999def CMDrunhooks(parser, args):
1000 """Runs hooks for files that have been modified in the local working copy."""
[email protected]0b6a0842010-06-15 14:34:191001 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1002 help='override deps for the specified (comma-separated) '
1003 'platform(s); \'all\' will process all deps_os '
1004 'references')
1005 parser.add_option('-f', '--force', action='store_true', default=True,
1006 help='Deprecated. No effect.')
[email protected]5ca27692010-05-26 19:32:411007 (options, args) = parser.parse_args(args)
[email protected]2806acc2009-05-15 12:33:341008 client = GClient.LoadCurrentConfig(options)
[email protected]fb2b8eb2009-04-23 21:03:421009 if not client:
[email protected]0b6a0842010-06-15 14:34:191010 raise gclient_utils.Error('client not configured; see \'gclient config\'')
[email protected]fb2b8eb2009-04-23 21:03:421011 if options.verbose:
1012 # Print out the .gclient file. This is longer than if we just printed the
1013 # client dict, but more legible, and it might contain helpful comments.
[email protected]116704f2010-06-11 17:34:381014 print(client.config_content)
[email protected]5df6a462009-08-28 18:52:261015 options.force = True
[email protected]5ca27692010-05-26 19:32:411016 options.nohooks = False
[email protected]fb2b8eb2009-04-23 21:03:421017 return client.RunOnDeps('runhooks', args)
1018
1019
[email protected]5ca27692010-05-26 19:32:411020def CMDrevinfo(parser, args):
[email protected]9eda4112010-06-11 18:56:101021 """Output revision info mapping for the client and its dependencies.
1022
[email protected]0b6a0842010-06-15 14:34:191023 This allows the capture of an overall 'revision' for the source tree that
[email protected]9eda4112010-06-11 18:56:101024 can be used to reproduce the same tree in the future. It is only useful for
[email protected]0b6a0842010-06-15 14:34:191025 'unpinned dependencies', i.e. DEPS/deps references without a svn revision
1026 number or a git hash. A git branch name isn't 'pinned' since the actual
[email protected]9eda4112010-06-11 18:56:101027 commit can change.
1028 """
1029 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1030 help='override deps for the specified (comma-separated) '
1031 'platform(s); \'all\' will process all deps_os '
1032 'references')
1033 parser.add_option('-s', '--snapshot', action='store_true',
1034 help='creates a snapshot .gclient file of the current '
1035 'version of all repositories to reproduce the tree, '
1036 'implies -a')
[email protected]5ca27692010-05-26 19:32:411037 (options, args) = parser.parse_args(args)
[email protected]2806acc2009-05-15 12:33:341038 client = GClient.LoadCurrentConfig(options)
[email protected]fb2b8eb2009-04-23 21:03:421039 if not client:
[email protected]0b6a0842010-06-15 14:34:191040 raise gclient_utils.Error('client not configured; see \'gclient config\'')
[email protected]fb2b8eb2009-04-23 21:03:421041 client.PrintRevInfo()
[email protected]79692d62010-05-14 18:57:131042 return 0
[email protected]fb2b8eb2009-04-23 21:03:421043
1044
[email protected]5ca27692010-05-26 19:32:411045def Command(name):
1046 return getattr(sys.modules[__name__], 'CMD' + name, None)
1047
1048
1049def CMDhelp(parser, args):
1050 """Prints list of commands or help for a specific command."""
[email protected]6e29d572010-06-04 17:32:201051 (_, args) = parser.parse_args(args)
[email protected]ddff62d2010-05-17 21:02:361052 if len(args) == 1:
[email protected]5ca27692010-05-26 19:32:411053 return Main(args + ['--help'])
[email protected]ddff62d2010-05-17 21:02:361054 parser.print_help()
1055 return 0
1056
1057
[email protected]5ca27692010-05-26 19:32:411058def GenUsage(parser, command):
1059 """Modify an OptParse object with the function's documentation."""
1060 obj = Command(command)
1061 if command == 'help':
1062 command = '<command>'
1063 # OptParser.description prefer nicely non-formatted strings.
1064 parser.description = re.sub('[\r\n ]{2,}', ' ', obj.__doc__)
1065 usage = getattr(obj, 'usage', '')
1066 parser.set_usage('%%prog %s [options] %s' % (command, usage))
1067 parser.epilog = getattr(obj, 'epilog', None)
[email protected]fb2b8eb2009-04-23 21:03:421068
1069
1070def Main(argv):
[email protected]5ca27692010-05-26 19:32:411071 """Doesn't parse the arguments here, just find the right subcommand to
1072 execute."""
[email protected]6e29d572010-06-04 17:32:201073 try:
1074 # Do it late so all commands are listed.
1075 CMDhelp.usage = ('\n\nCommands are:\n' + '\n'.join([
1076 ' %-10s %s' % (fn[3:], Command(fn[3:]).__doc__.split('\n')[0].strip())
1077 for fn in dir(sys.modules[__name__]) if fn.startswith('CMD')]))
1078 parser = optparse.OptionParser(version='%prog ' + __version__)
[email protected]f0fc9912010-06-11 17:57:331079 parser.add_option('-v', '--verbose', action='count', default=0,
1080 help='Produces additional output for diagnostics. Can be '
1081 'used up to three times for more logging info.')
1082 parser.add_option('--gclientfile', dest='config_filename',
1083 default=os.environ.get('GCLIENT_FILE', '.gclient'),
1084 help='Specify an alternate %default file')
[email protected]6e29d572010-06-04 17:32:201085 # Integrate standard options processing.
1086 old_parser = parser.parse_args
1087 def Parse(args):
1088 (options, args) = old_parser(args)
[email protected]f0fc9912010-06-11 17:57:331089 level = None
[email protected]6e29d572010-06-04 17:32:201090 if options.verbose == 2:
[email protected]f0fc9912010-06-11 17:57:331091 level = logging.INFO
[email protected]6e29d572010-06-04 17:32:201092 elif options.verbose > 2:
[email protected]f0fc9912010-06-11 17:57:331093 level = logging.DEBUG
1094 logging.basicConfig(level=level,
1095 format='%(module)s(%(lineno)d) %(funcName)s:%(message)s')
1096 options.entries_filename = options.config_filename + '_entries'
[email protected]6e29d572010-06-04 17:32:201097 if not hasattr(options, 'revisions'):
1098 # GClient.RunOnDeps expects it even if not applicable.
1099 options.revisions = []
1100 if not hasattr(options, 'head'):
1101 options.head = None
[email protected]f0fc9912010-06-11 17:57:331102 if not hasattr(options, 'nohooks'):
1103 options.nohooks = True
1104 if not hasattr(options, 'deps_os'):
1105 options.deps_os = None
[email protected]6e29d572010-06-04 17:32:201106 return (options, args)
1107 parser.parse_args = Parse
1108 # We don't want wordwrapping in epilog (usually examples)
1109 parser.format_epilog = lambda _: parser.epilog or ''
1110 if argv:
1111 command = Command(argv[0])
1112 if command:
[email protected]f0fc9912010-06-11 17:57:331113 # 'fix' the usage and the description now that we know the subcommand.
[email protected]6e29d572010-06-04 17:32:201114 GenUsage(parser, argv[0])
1115 return command(parser, argv[1:])
1116 # Not a known command. Default to help.
1117 GenUsage(parser, 'help')
1118 return CMDhelp(parser, argv)
1119 except gclient_utils.Error, e:
[email protected]f0fc9912010-06-11 17:57:331120 print >> sys.stderr, 'Error: %s' % str(e)
[email protected]6e29d572010-06-04 17:32:201121 return 1
[email protected]fb2b8eb2009-04-23 21:03:421122
1123
[email protected]f0fc9912010-06-11 17:57:331124if '__main__' == __name__:
[email protected]6e29d572010-06-04 17:32:201125 sys.exit(Main(sys.argv[1:]))
[email protected]fb2b8eb2009-04-23 21:03:421126
1127# vim: ts=2:sw=2:tw=80:et: