blob: 04087100c5cd32c20308aede42d36e529279270f [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]4b90e3a2010-07-01 20:28:2660import subprocess
[email protected]fb2b8eb2009-04-23 21:03:4261import sys
[email protected]fb2b8eb2009-04-23 21:03:4262import urlparse
[email protected]fb2b8eb2009-04-23 21:03:4263import urllib
64
[email protected]ada4c652009-12-03 15:32:0165import breakpad
66
[email protected]5f3eee32009-09-17 00:34:3067import gclient_scm
68import gclient_utils
[email protected]1f7a3d12010-02-04 15:11:5069from third_party.repo.progress import Progress
[email protected]fb2b8eb2009-04-23 21:03:4270
[email protected]fb2b8eb2009-04-23 21:03:4271
[email protected]1f7d1182010-05-17 18:17:3872def attr(attr, data):
73 """Sets an attribute on a function."""
74 def hook(fn):
75 setattr(fn, attr, data)
76 return fn
77 return hook
[email protected]e3da35f2010-03-09 21:40:4578
[email protected]fb2b8eb2009-04-23 21:03:4279
[email protected]fb2b8eb2009-04-23 21:03:4280## GClient implementation.
81
82
[email protected]116704f2010-06-11 17:34:3883class GClientKeywords(object):
84 class FromImpl(object):
85 """Used to implement the From() syntax."""
86
87 def __init__(self, module_name, sub_target_name=None):
88 """module_name is the dep module we want to include from. It can also be
89 the name of a subdirectory to include from.
90
91 sub_target_name is an optional parameter if the module name in the other
92 DEPS file is different. E.g., you might want to map src/net to net."""
93 self.module_name = module_name
94 self.sub_target_name = sub_target_name
95
96 def __str__(self):
97 return 'From(%s, %s)' % (repr(self.module_name),
98 repr(self.sub_target_name))
99
100 def GetUrl(self, target_name, sub_deps_base_url, root_dir, sub_deps):
101 """Resolve the URL for this From entry."""
102 sub_deps_target_name = target_name
103 if self.sub_target_name:
104 sub_deps_target_name = self.sub_target_name
105 url = sub_deps[sub_deps_target_name]
106 if url.startswith('/'):
107 # If it's a relative URL, we need to resolve the URL relative to the
108 # sub deps base URL.
109 if not isinstance(sub_deps_base_url, basestring):
110 sub_deps_base_url = sub_deps_base_url.GetPath()
111 scm = gclient_scm.CreateSCM(sub_deps_base_url, root_dir,
112 None)
113 url = scm.FullUrlForRelativeUrl(url)
114 return url
115
116 class FileImpl(object):
117 """Used to implement the File('') syntax which lets you sync a single file
118 from an SVN repo."""
119
120 def __init__(self, file_location):
121 self.file_location = file_location
122
123 def __str__(self):
124 return 'File("%s")' % self.file_location
125
126 def GetPath(self):
127 return os.path.split(self.file_location)[0]
128
129 def GetFilename(self):
130 rev_tokens = self.file_location.split('@')
131 return os.path.split(rev_tokens[0])[1]
132
133 def GetRevision(self):
134 rev_tokens = self.file_location.split('@')
135 if len(rev_tokens) > 1:
136 return rev_tokens[1]
137 return None
138
139 class VarImpl(object):
140 def __init__(self, custom_vars, local_scope):
141 self._custom_vars = custom_vars
142 self._local_scope = local_scope
143
144 def Lookup(self, var_name):
145 """Implements the Var syntax."""
146 if var_name in self._custom_vars:
147 return self._custom_vars[var_name]
148 elif var_name in self._local_scope.get("vars", {}):
149 return self._local_scope["vars"][var_name]
150 raise gclient_utils.Error("Var is not defined: %s" % var_name)
151
152
[email protected]54a07a22010-06-14 19:07:39153class Dependency(GClientKeywords):
154 """Object that represents a dependency checkout."""
[email protected]9eda4112010-06-11 18:56:10155 DEPS_FILE = 'DEPS'
[email protected]0b6a0842010-06-15 14:34:19156
[email protected]54a07a22010-06-14 19:07:39157 def __init__(self, parent, name, url, safesync_url=None, custom_deps=None,
[email protected]0b6a0842010-06-15 14:34:19158 custom_vars=None, deps_file=None):
[email protected]54a07a22010-06-14 19:07:39159 GClientKeywords.__init__(self)
160 self.parent = parent
161 self.name = name
162 self.url = url
163 # These 2 are only set in .gclient and not in DEPS files.
164 self.safesync_url = safesync_url
165 self.custom_vars = custom_vars or {}
166 self.custom_deps = custom_deps or {}
[email protected]0b6a0842010-06-15 14:34:19167 self.deps_hooks = []
[email protected]54a07a22010-06-14 19:07:39168 self.dependencies = []
169 self.deps_file = deps_file or self.DEPS_FILE
[email protected]271375b2010-06-23 19:17:38170 self.deps_parsed = False
171 self.direct_reference = False
[email protected]fb2b8eb2009-04-23 21:03:42172
[email protected]54a07a22010-06-14 19:07:39173 # Sanity checks
174 if not self.name and self.parent:
175 raise gclient_utils.Error('Dependency without name')
176 if not isinstance(self.url,
177 (basestring, self.FromImpl, self.FileImpl, None.__class__)):
178 raise gclient_utils.Error('dependency url must be either a string, None, '
179 'File() or From() instead of %s' %
180 self.url.__class__.__name__)
181 if '/' in self.deps_file or '\\' in self.deps_file:
182 raise gclient_utils.Error('deps_file name must not be a path, just a '
183 'filename. %s' % self.deps_file)
184
[email protected]271375b2010-06-23 19:17:38185 def ParseDepsFile(self, direct_reference):
186 """Parses the DEPS file for this dependency."""
187 if direct_reference:
188 # Maybe it was referenced earlier by a From() keyword but it's now
189 # directly referenced.
190 self.direct_reference = direct_reference
191 self.deps_parsed = True
192 filepath = os.path.join(self.root_dir(), self.name, self.deps_file)
193 if not os.path.isfile(filepath):
[email protected]0d425922010-06-21 19:22:24194 return {}
[email protected]271375b2010-06-23 19:17:38195 deps_content = gclient_utils.FileRead(filepath)
[email protected]0d425922010-06-21 19:22:24196
[email protected]271375b2010-06-23 19:17:38197 # Eval the content.
198 # One thing is unintuitive, vars= {} must happen before Var() use.
199 local_scope = {}
200 var = self.VarImpl(self.custom_vars, local_scope)
201 global_scope = {
202 'File': self.FileImpl,
203 'From': self.FromImpl,
204 'Var': var.Lookup,
205 'deps_os': {},
206 }
[email protected]5990f9d2010-07-07 18:02:58207 try:
208 exec(deps_content, global_scope, local_scope)
209 except SyntaxError, e:
210 gclient_utils.SyntaxErrorToError(filepath, e)
[email protected]271375b2010-06-23 19:17:38211 deps = local_scope.get('deps', {})
[email protected]fb2b8eb2009-04-23 21:03:42212 # load os specific dependencies if defined. these dependencies may
213 # override or extend the values defined by the 'deps' member.
[email protected]271375b2010-06-23 19:17:38214 if 'deps_os' in local_scope:
215 for deps_os_key in self.enforced_os():
216 os_deps = local_scope['deps_os'].get(deps_os_key, {})
217 if len(self.enforced_os()) > 1:
218 # Ignore any conflict when including deps for more than one
[email protected]fb2b8eb2009-04-23 21:03:42219 # platform, so we collect the broadest set of dependencies available.
220 # We may end up with the wrong revision of something for our
221 # platform, but this is the best we can do.
222 deps.update([x for x in os_deps.items() if not x[0] in deps])
223 else:
224 deps.update(os_deps)
225
[email protected]271375b2010-06-23 19:17:38226 self.deps_hooks.extend(local_scope.get('hooks', []))
227
228 # If a line is in custom_deps, but not in the solution, we want to append
229 # this line to the solution.
230 for d in self.custom_deps:
231 if d not in deps:
232 deps[d] = self.custom_deps[d]
[email protected]fb2b8eb2009-04-23 21:03:42233
234 # If use_relative_paths is set in the DEPS file, regenerate
235 # the dictionary using paths relative to the directory containing
236 # the DEPS file.
[email protected]271375b2010-06-23 19:17:38237 use_relative_paths = local_scope.get('use_relative_paths', False)
238 if use_relative_paths:
[email protected]fb2b8eb2009-04-23 21:03:42239 rel_deps = {}
240 for d, url in deps.items():
241 # normpath is required to allow DEPS to use .. in their
242 # dependency local path.
[email protected]271375b2010-06-23 19:17:38243 rel_deps[os.path.normpath(os.path.join(self.name, d))] = url
244 deps = rel_deps
245 # TODO(maruel): Add these dependencies into self.dependencies.
246 return deps
[email protected]fb2b8eb2009-04-23 21:03:42247
[email protected]271375b2010-06-23 19:17:38248 def _ParseAllDeps(self, solution_urls):
[email protected]fb2b8eb2009-04-23 21:03:42249 """Parse the complete list of dependencies for the client.
250
251 Args:
252 solution_urls: A dict mapping module names (as relative paths) to URLs
253 corresponding to the solutions specified by the client. This parameter
254 is passed as an optimization.
[email protected]fb2b8eb2009-04-23 21:03:42255
256 Returns:
257 A dict mapping module names (as relative paths) to URLs corresponding
258 to the entire set of dependencies to checkout for the given client.
259
260 Raises:
261 Error: If a dependency conflicts with another dependency or of a solution.
262 """
263 deps = {}
[email protected]54a07a22010-06-14 19:07:39264 for solution in self.dependencies:
[email protected]271375b2010-06-23 19:17:38265 solution_deps = solution.ParseDepsFile(True)
[email protected]fb2b8eb2009-04-23 21:03:42266
267 # If a line is in custom_deps, but not in the solution, we want to append
268 # this line to the solution.
[email protected]54a07a22010-06-14 19:07:39269 for d in solution.custom_deps:
270 if d not in solution_deps:
271 solution_deps[d] = solution.custom_deps[d]
[email protected]fb2b8eb2009-04-23 21:03:42272
273 for d in solution_deps:
[email protected]54a07a22010-06-14 19:07:39274 if d in solution.custom_deps:
[email protected]fb2b8eb2009-04-23 21:03:42275 # Dependency is overriden.
[email protected]54a07a22010-06-14 19:07:39276 url = solution.custom_deps[d]
[email protected]fb2b8eb2009-04-23 21:03:42277 if url is None:
278 continue
279 else:
280 url = solution_deps[d]
281 # if we have a From reference dependent on another solution, then
282 # just skip the From reference. When we pull deps for the solution,
283 # we will take care of this dependency.
284 #
285 # If multiple solutions all have the same From reference, then we
286 # should only add one to our list of dependencies.
[email protected]4b5b1772010-04-08 01:52:56287 if isinstance(url, self.FromImpl):
[email protected]fb2b8eb2009-04-23 21:03:42288 if url.module_name in solution_urls:
289 # Already parsed.
290 continue
291 if d in deps and type(deps[d]) != str:
292 if url.module_name == deps[d].module_name:
293 continue
[email protected]4b5b1772010-04-08 01:52:56294 elif isinstance(url, str):
[email protected]fb2b8eb2009-04-23 21:03:42295 parsed_url = urlparse.urlparse(url)
296 scheme = parsed_url[0]
297 if not scheme:
298 # A relative url. Fetch the real base.
299 path = parsed_url[2]
300 if path[0] != "/":
[email protected]e3608df2009-11-10 20:22:57301 raise gclient_utils.Error(
[email protected]fb2b8eb2009-04-23 21:03:42302 "relative DEPS entry \"%s\" must begin with a slash" % d)
[email protected]e6f78352010-01-13 17:05:33303 # Create a scm just to query the full url.
[email protected]54a07a22010-06-14 19:07:39304 scm = gclient_scm.CreateSCM(solution.url, self.root_dir(),
305 None)
[email protected]e6f78352010-01-13 17:05:33306 url = scm.FullUrlForRelativeUrl(url)
[email protected]fb2b8eb2009-04-23 21:03:42307 if d in deps and deps[d] != url:
[email protected]e3608df2009-11-10 20:22:57308 raise gclient_utils.Error(
[email protected]fb2b8eb2009-04-23 21:03:42309 "Solutions have conflicting versions of dependency \"%s\"" % d)
310 if d in solution_urls and solution_urls[d] != url:
[email protected]e3608df2009-11-10 20:22:57311 raise gclient_utils.Error(
[email protected]fb2b8eb2009-04-23 21:03:42312 "Dependency \"%s\" conflicts with specified solution" % d)
313 # Grab the dependency.
314 deps[d] = url
315 return deps
316
[email protected]fb2b8eb2009-04-23 21:03:42317 def _RunHooks(self, command, file_list, is_using_git):
318 """Evaluates all hooks, running actions as needed.
319 """
320 # Hooks only run for these command types.
321 if not command in ('update', 'revert', 'runhooks'):
322 return
323
[email protected]67820ef2009-07-27 17:23:00324 # Hooks only run when --nohooks is not specified
325 if self._options.nohooks:
326 return
327
[email protected]fb2b8eb2009-04-23 21:03:42328 # Get any hooks from the .gclient file.
[email protected]0b6a0842010-06-15 14:34:19329 hooks = self.deps_hooks[:]
[email protected]fb2b8eb2009-04-23 21:03:42330 # Add any hooks found in DEPS files.
[email protected]54a07a22010-06-14 19:07:39331 for d in self.dependencies:
[email protected]0b6a0842010-06-15 14:34:19332 hooks.extend(d.deps_hooks)
[email protected]fb2b8eb2009-04-23 21:03:42333
334 # If "--force" was specified, run all hooks regardless of what files have
335 # changed. If the user is using git, then we don't know what files have
336 # changed so we always run all hooks.
337 if self._options.force or is_using_git:
338 for hook_dict in hooks:
[email protected]71b40682009-07-31 23:40:09339 self._RunHookAction(hook_dict, [])
[email protected]fb2b8eb2009-04-23 21:03:42340 return
341
342 # Run hooks on the basis of whether the files from the gclient operation
343 # match each hook's pattern.
344 for hook_dict in hooks:
345 pattern = re.compile(hook_dict['pattern'])
[email protected]e3608df2009-11-10 20:22:57346 matching_file_list = [f for f in file_list if pattern.search(f)]
[email protected]71b40682009-07-31 23:40:09347 if matching_file_list:
348 self._RunHookAction(hook_dict, matching_file_list)
[email protected]fb2b8eb2009-04-23 21:03:42349
[email protected]eaf61062010-07-07 18:42:39350 def _RunHookAction(self, hook_dict, matching_file_list):
351 """Runs the action from a single hook."""
352 logging.info(hook_dict)
353 logging.info(matching_file_list)
354 command = hook_dict['action'][:]
355 if command[0] == 'python':
356 # If the hook specified "python" as the first item, the action is a
357 # Python script. Run it by starting a new copy of the same
358 # interpreter.
359 command[0] = sys.executable
360
361 if '$matching_files' in command:
362 splice_index = command.index('$matching_files')
363 command[splice_index:splice_index + 1] = matching_file_list
364
365 # Use a discrete exit status code of 2 to indicate that a hook action
366 # failed. Users of this script may wish to treat hook action failures
367 # differently from VC failures.
368 return gclient_utils.SubprocessCall(command, self.root_dir(), fail_status=2)
369
[email protected]271375b2010-06-23 19:17:38370 def root_dir(self):
371 return self.parent.root_dir()
372
373 def enforced_os(self):
374 return self.parent.enforced_os()
375
[email protected]d36fba82010-06-28 16:50:40376 def recursion_limit(self):
377 return self.parent.recursion_limit() - 1
378
379 def tree(self, force_all):
380 return self.parent.tree(force_all)
381
382 def get_custom_deps(self, name, url):
383 """Returns a custom deps if applicable."""
384 if self.parent:
385 url = self.parent.get_custom_deps(name, url)
386 # None is a valid return value to disable a dependency.
387 return self.custom_deps.get(name, url)
388
389 def __str__(self):
390 out = []
391 for i in ('name', 'url', 'safesync_url', 'custom_deps', 'custom_vars',
392 'deps_hooks'):
393 # 'deps_file'
394 if self.__dict__[i]:
395 out.append('%s: %s' % (i, self.__dict__[i]))
396
397 for d in self.dependencies:
398 out.extend([' ' + x for x in str(d).splitlines()])
399 out.append('')
400 return '\n'.join(out)
401
402 def __repr__(self):
403 return '%s: %s' % (self.name, self.url)
404
[email protected]9a66ddf2010-06-16 16:54:16405
406class GClient(Dependency):
[email protected]d36fba82010-06-28 16:50:40407 """Object that represent a gclient checkout. A tree of Dependency(), one per
408 solution or DEPS entry."""
[email protected]9a66ddf2010-06-16 16:54:16409
410 DEPS_OS_CHOICES = {
411 "win32": "win",
412 "win": "win",
413 "cygwin": "win",
414 "darwin": "mac",
415 "mac": "mac",
416 "unix": "unix",
417 "linux": "unix",
418 "linux2": "unix",
419 }
420
421 DEFAULT_CLIENT_FILE_TEXT = ("""\
422solutions = [
423 { "name" : "%(solution_name)s",
424 "url" : "%(solution_url)s",
425 "custom_deps" : {
426 },
[email protected]73e21142010-07-05 13:32:01427 "safesync_url": "%(safesync_url)s",
[email protected]9a66ddf2010-06-16 16:54:16428 },
429]
430""")
431
432 DEFAULT_SNAPSHOT_SOLUTION_TEXT = ("""\
433 { "name" : "%(solution_name)s",
434 "url" : "%(solution_url)s",
435 "custom_deps" : {
[email protected]73e21142010-07-05 13:32:01436%(solution_deps)s },
437 "safesync_url": "%(safesync_url)s",
[email protected]9a66ddf2010-06-16 16:54:16438 },
439""")
440
441 DEFAULT_SNAPSHOT_FILE_TEXT = ("""\
442# Snapshot generated with gclient revinfo --snapshot
443solutions = [
[email protected]73e21142010-07-05 13:32:01444%(solution_list)s]
[email protected]9a66ddf2010-06-16 16:54:16445""")
446
447 def __init__(self, root_dir, options):
448 Dependency.__init__(self, None, None, None)
[email protected]0d425922010-06-21 19:22:24449 self._options = options
[email protected]271375b2010-06-23 19:17:38450 if options.deps_os:
451 enforced_os = options.deps_os.split(',')
452 else:
453 enforced_os = [self.DEPS_OS_CHOICES.get(sys.platform, 'unix')]
454 if 'all' in enforced_os:
455 enforced_os = self.DEPS_OS_CHOICES.itervalues()
456 self._enforced_os = list(set(enforced_os))
457 self._root_dir = root_dir
[email protected]9a66ddf2010-06-16 16:54:16458 self.config_content = None
[email protected]d36fba82010-06-28 16:50:40459 # Do not change previous behavior. Only solution level and immediate DEPS
460 # are processed.
461 self._recursion_limit = 2
[email protected]9a66ddf2010-06-16 16:54:16462
463 def SetConfig(self, content):
464 assert self.dependencies == []
465 config_dict = {}
466 self.config_content = content
467 try:
468 exec(content, config_dict)
469 except SyntaxError, e:
[email protected]5990f9d2010-07-07 18:02:58470 gclient_utils.SyntaxErrorToError('.gclient', e)
[email protected]9a66ddf2010-06-16 16:54:16471 for s in config_dict.get('solutions', []):
[email protected]81843b82010-06-28 16:49:26472 try:
473 self.dependencies.append(Dependency(
474 self, s['name'], s['url'],
475 s.get('safesync_url', None),
476 s.get('custom_deps', {}),
477 s.get('custom_vars', {})))
478 except KeyError:
479 raise gclient_utils.Error('Invalid .gclient file. Solution is '
480 'incomplete: %s' % s)
[email protected]9a66ddf2010-06-16 16:54:16481 # .gclient can have hooks.
482 self.deps_hooks = config_dict.get('hooks', [])
483
484 def SaveConfig(self):
485 gclient_utils.FileWrite(os.path.join(self.root_dir(),
486 self._options.config_filename),
487 self.config_content)
488
489 @staticmethod
490 def LoadCurrentConfig(options):
491 """Searches for and loads a .gclient file relative to the current working
492 dir. Returns a GClient object."""
493 path = gclient_utils.FindGclientRoot(os.getcwd(), options.config_filename)
494 if not path:
495 return None
496 client = GClient(path, options)
497 client.SetConfig(gclient_utils.FileRead(
498 os.path.join(path, options.config_filename)))
499 return client
500
501 def SetDefaultConfig(self, solution_name, solution_url, safesync_url):
502 self.SetConfig(self.DEFAULT_CLIENT_FILE_TEXT % {
503 'solution_name': solution_name,
504 'solution_url': solution_url,
505 'safesync_url' : safesync_url,
506 })
507
508 def _SaveEntries(self, entries):
509 """Creates a .gclient_entries file to record the list of unique checkouts.
510
511 The .gclient_entries file lives in the same directory as .gclient.
[email protected]9a66ddf2010-06-16 16:54:16512 """
513 # Sometimes pprint.pformat will use {', sometimes it'll use { ' ... It
514 # makes testing a bit too fun.
515 result = pprint.pformat(entries, 2)
516 if result.startswith('{\''):
517 result = '{ \'' + result[2:]
[email protected]73e21142010-07-05 13:32:01518 text = 'entries = \\\n' + result + '\n'
[email protected]9a66ddf2010-06-16 16:54:16519 file_path = os.path.join(self.root_dir(), self._options.entries_filename)
520 gclient_utils.FileWrite(file_path, text)
521
522 def _ReadEntries(self):
523 """Read the .gclient_entries file for the given client.
524
525 Returns:
526 A sequence of solution names, which will be empty if there is the
527 entries file hasn't been created yet.
528 """
529 scope = {}
530 filename = os.path.join(self.root_dir(), self._options.entries_filename)
531 if not os.path.exists(filename):
[email protected]73e21142010-07-05 13:32:01532 return {}
[email protected]5990f9d2010-07-07 18:02:58533 try:
534 exec(gclient_utils.FileRead(filename), scope)
535 except SyntaxError, e:
536 gclient_utils.SyntaxErrorToError(filename, e)
[email protected]9a66ddf2010-06-16 16:54:16537 return scope['entries']
538
[email protected]54a07a22010-06-14 19:07:39539 def _EnforceRevisions(self):
[email protected]918a9ae2010-05-28 15:50:30540 """Checks for revision overrides."""
541 revision_overrides = {}
[email protected]307d1792010-05-31 20:03:13542 if self._options.head:
543 return revision_overrides
[email protected]54a07a22010-06-14 19:07:39544 for s in self.dependencies:
545 if not s.safesync_url:
[email protected]307d1792010-05-31 20:03:13546 continue
[email protected]54a07a22010-06-14 19:07:39547 handle = urllib.urlopen(s.safesync_url)
[email protected]307d1792010-05-31 20:03:13548 rev = handle.read().strip()
549 handle.close()
550 if len(rev):
[email protected]54a07a22010-06-14 19:07:39551 self._options.revisions.append('%s@%s' % (s.name, rev))
[email protected]307d1792010-05-31 20:03:13552 if not self._options.revisions:
553 return revision_overrides
554 # --revision will take over safesync_url.
[email protected]54a07a22010-06-14 19:07:39555 solutions_names = [s.name for s in self.dependencies]
[email protected]307d1792010-05-31 20:03:13556 index = 0
557 for revision in self._options.revisions:
558 if not '@' in revision:
559 # Support for --revision 123
560 revision = '%s@%s' % (solutions_names[index], revision)
[email protected]54a07a22010-06-14 19:07:39561 sol, rev = revision.split('@', 1)
[email protected]307d1792010-05-31 20:03:13562 if not sol in solutions_names:
563 #raise gclient_utils.Error('%s is not a valid solution.' % sol)
564 print >> sys.stderr, ('Please fix your script, having invalid '
565 '--revision flags will soon considered an error.')
566 else:
[email protected]918a9ae2010-05-28 15:50:30567 revision_overrides[sol] = rev
[email protected]307d1792010-05-31 20:03:13568 index += 1
[email protected]918a9ae2010-05-28 15:50:30569 return revision_overrides
570
[email protected]fb2b8eb2009-04-23 21:03:42571 def RunOnDeps(self, command, args):
572 """Runs a command on each dependency in a client and its dependencies.
573
[email protected]fb2b8eb2009-04-23 21:03:42574 Args:
575 command: The command to use (e.g., 'status' or 'diff')
576 args: list of str - extra arguments to add to the command line.
[email protected]fb2b8eb2009-04-23 21:03:42577 """
[email protected]54a07a22010-06-14 19:07:39578 if not self.dependencies:
[email protected]73e21142010-07-05 13:32:01579 raise gclient_utils.Error('No solution specified')
[email protected]54a07a22010-06-14 19:07:39580 revision_overrides = self._EnforceRevisions()
[email protected]fb2b8eb2009-04-23 21:03:42581
582 # When running runhooks --force, there's no need to consult the SCM.
583 # All known hooks are expected to run unconditionally regardless of working
584 # copy state, so skip the SCM status check.
585 run_scm = not (command == 'runhooks' and self._options.force)
586
587 entries = {}
[email protected]d2e92562010-04-27 01:55:18588 file_list = []
589 # Run on the base solutions first.
[email protected]54a07a22010-06-14 19:07:39590 for solution in self.dependencies:
591 name = solution.name
[email protected]d2e92562010-04-27 01:55:18592 if name in entries:
593 raise gclient_utils.Error("solution %s specified more than once" % name)
[email protected]54a07a22010-06-14 19:07:39594 url = solution.url
[email protected]d2e92562010-04-27 01:55:18595 entries[name] = url
596 if run_scm and url:
597 self._options.revision = revision_overrides.get(name)
[email protected]75a59272010-06-11 22:34:03598 scm = gclient_scm.CreateSCM(url, self.root_dir(), name)
[email protected]d2e92562010-04-27 01:55:18599 scm.RunCommand(command, self._options, args, file_list)
600 file_list = [os.path.join(name, f.strip()) for f in file_list]
601 self._options.revision = None
[email protected]fb2b8eb2009-04-23 21:03:42602
[email protected]d2e92562010-04-27 01:55:18603 # Process the dependencies next (sort alphanumerically to ensure that
604 # containing directories get populated first and for readability)
[email protected]271375b2010-06-23 19:17:38605 deps = self._ParseAllDeps(entries)
[email protected]d2e92562010-04-27 01:55:18606 deps_to_process = deps.keys()
607 deps_to_process.sort()
[email protected]fb2b8eb2009-04-23 21:03:42608
[email protected]d2e92562010-04-27 01:55:18609 # First pass for direct dependencies.
610 if command == 'update' and not self._options.verbose:
611 pm = Progress('Syncing projects', len(deps_to_process))
612 for d in deps_to_process:
[email protected]1f7a3d12010-02-04 15:11:50613 if command == 'update' and not self._options.verbose:
[email protected]d2e92562010-04-27 01:55:18614 pm.update()
615 if type(deps[d]) == str:
616 url = deps[d]
617 entries[d] = url
618 if run_scm:
619 self._options.revision = revision_overrides.get(d)
[email protected]75a59272010-06-11 22:34:03620 scm = gclient_scm.CreateSCM(url, self.root_dir(), d)
[email protected]d2e92562010-04-27 01:55:18621 scm.RunCommand(command, self._options, args, file_list)
622 self._options.revision = None
623 elif isinstance(deps[d], self.FileImpl):
[email protected]491c04b2010-05-17 18:17:44624 file_dep = deps[d]
625 self._options.revision = file_dep.GetRevision()
[email protected]d2e92562010-04-27 01:55:18626 if run_scm:
[email protected]75a59272010-06-11 22:34:03627 scm = gclient_scm.CreateSCM(file_dep.GetPath(), self.root_dir(), d)
[email protected]d2e92562010-04-27 01:55:18628 scm.RunCommand("updatesingle", self._options,
[email protected]491c04b2010-05-17 18:17:44629 args + [file_dep.GetFilename()], file_list)
[email protected]79692d62010-05-14 18:57:13630
[email protected]d2e92562010-04-27 01:55:18631 if command == 'update' and not self._options.verbose:
632 pm.end()
[email protected]6f363722010-04-27 00:41:09633
[email protected]d2e92562010-04-27 01:55:18634 # Second pass for inherited deps (via the From keyword)
635 for d in deps_to_process:
636 if isinstance(deps[d], self.FromImpl):
[email protected]d2e92562010-04-27 01:55:18637 # Getting the URL from the sub_deps file can involve having to resolve
638 # a File() or having to resolve a relative URL. To resolve relative
639 # URLs, we need to pass in the orignal sub deps URL.
640 sub_deps_base_url = deps[deps[d].module_name]
[email protected]271375b2010-06-23 19:17:38641 sub_deps = Dependency(self, deps[d].module_name, sub_deps_base_url
642 ).ParseDepsFile(False)
[email protected]75a59272010-06-11 22:34:03643 url = deps[d].GetUrl(d, sub_deps_base_url, self.root_dir(), sub_deps)
[email protected]d2e92562010-04-27 01:55:18644 entries[d] = url
645 if run_scm:
646 self._options.revision = revision_overrides.get(d)
[email protected]75a59272010-06-11 22:34:03647 scm = gclient_scm.CreateSCM(url, self.root_dir(), d)
[email protected]d2e92562010-04-27 01:55:18648 scm.RunCommand(command, self._options, args, file_list)
649 self._options.revision = None
[email protected]df2d5902009-09-11 22:16:21650
[email protected]d83b2b22009-08-11 15:30:55651 # Convert all absolute paths to relative.
652 for i in range(len(file_list)):
653 # TODO(phajdan.jr): We should know exactly when the paths are absolute.
654 # It depends on the command being executed (like runhooks vs sync).
655 if not os.path.isabs(file_list[i]):
656 continue
657
[email protected]75a59272010-06-11 22:34:03658 prefix = os.path.commonprefix([self.root_dir().lower(),
[email protected]d83b2b22009-08-11 15:30:55659 file_list[i].lower()])
660 file_list[i] = file_list[i][len(prefix):]
661
662 # Strip any leading path separators.
663 while file_list[i].startswith('\\') or file_list[i].startswith('/'):
664 file_list[i] = file_list[i][1:]
[email protected]fb2b8eb2009-04-23 21:03:42665
[email protected]75a59272010-06-11 22:34:03666 is_using_git = gclient_utils.IsUsingGit(self.root_dir(), entries.keys())
[email protected]fb2b8eb2009-04-23 21:03:42667 self._RunHooks(command, file_list, is_using_git)
668
669 if command == 'update':
[email protected]cdcee802009-06-23 15:30:42670 # Notify the user if there is an orphaned entry in their working copy.
671 # Only delete the directory if there are no changes in it, and
672 # delete_unversioned_trees is set to true.
[email protected]fb2b8eb2009-04-23 21:03:42673 prev_entries = self._ReadEntries()
674 for entry in prev_entries:
[email protected]c5e9aec2009-08-03 18:25:56675 # Fix path separator on Windows.
676 entry_fixed = entry.replace('/', os.path.sep)
[email protected]75a59272010-06-11 22:34:03677 e_dir = os.path.join(self.root_dir(), entry_fixed)
[email protected]c5e9aec2009-08-03 18:25:56678 # Use entry and not entry_fixed there.
[email protected]0329e672009-05-13 18:41:04679 if entry not in entries and os.path.exists(e_dir):
[email protected]83017012009-09-28 18:52:12680 modified_files = False
[email protected]5aeb7dd2009-11-17 18:09:01681 if isinstance(prev_entries, list):
[email protected]83017012009-09-28 18:52:12682 # old .gclient_entries format was list, now dict
[email protected]5aeb7dd2009-11-17 18:09:01683 modified_files = gclient_scm.scm.SVN.CaptureStatus(e_dir)
[email protected]83017012009-09-28 18:52:12684 else:
685 file_list = []
[email protected]75a59272010-06-11 22:34:03686 scm = gclient_scm.CreateSCM(prev_entries[entry], self.root_dir(),
[email protected]83017012009-09-28 18:52:12687 entry_fixed)
688 scm.status(self._options, [], file_list)
689 modified_files = file_list != []
690 if not self._options.delete_unversioned_trees or modified_files:
[email protected]c5e9aec2009-08-03 18:25:56691 # There are modified files in this entry. Keep warning until
692 # removed.
[email protected]d36fba82010-06-28 16:50:40693 print(('\nWARNING: \'%s\' is no longer part of this client. '
694 'It is recommended that you manually remove it.\n') %
[email protected]c5e9aec2009-08-03 18:25:56695 entry_fixed)
[email protected]fb2b8eb2009-04-23 21:03:42696 else:
697 # Delete the entry
[email protected]73e21142010-07-05 13:32:01698 print('\n________ deleting \'%s\' in \'%s\'' % (
699 entry_fixed, self.root_dir()))
[email protected]5f3eee32009-09-17 00:34:30700 gclient_utils.RemoveDirectory(e_dir)
[email protected]fb2b8eb2009-04-23 21:03:42701 # record the current list of entries for next time
702 self._SaveEntries(entries)
[email protected]17cdf762010-05-28 17:30:52703 return 0
[email protected]fb2b8eb2009-04-23 21:03:42704
705 def PrintRevInfo(self):
[email protected]5d63eb82010-03-24 23:22:09706 """Output revision info mapping for the client and its dependencies.
707
708 This allows the capture of an overall "revision" for the source tree that
[email protected]918a9ae2010-05-28 15:50:30709 can be used to reproduce the same tree in the future. It is only useful for
710 "unpinned dependencies", i.e. DEPS/deps references without a svn revision
711 number or a git hash. A git branch name isn't "pinned" since the actual
712 commit can change.
[email protected]5d63eb82010-03-24 23:22:09713
714 The --snapshot option allows creating a .gclient file to reproduce the tree.
[email protected]fb2b8eb2009-04-23 21:03:42715 """
[email protected]54a07a22010-06-14 19:07:39716 if not self.dependencies:
[email protected]73e21142010-07-05 13:32:01717 raise gclient_utils.Error('No solution specified')
[email protected]fb2b8eb2009-04-23 21:03:42718
[email protected]5d63eb82010-03-24 23:22:09719 # Inner helper to generate base url and rev tuple
[email protected]fb2b8eb2009-04-23 21:03:42720 def GetURLAndRev(name, original_url):
[email protected]5d63eb82010-03-24 23:22:09721 url, _ = gclient_utils.SplitUrlRevision(original_url)
[email protected]75a59272010-06-11 22:34:03722 scm = gclient_scm.CreateSCM(original_url, self.root_dir(), name)
[email protected]5d63eb82010-03-24 23:22:09723 return (url, scm.revinfo(self._options, [], None))
[email protected]fb2b8eb2009-04-23 21:03:42724
[email protected]e3da35f2010-03-09 21:40:45725 # text of the snapshot gclient file
726 new_gclient = ""
727 # Dictionary of { path : SCM url } to ensure no duplicate solutions
728 solution_names = {}
[email protected]de8f3522010-03-11 23:47:44729 entries = {}
[email protected]fb2b8eb2009-04-23 21:03:42730 # Run on the base solutions first.
[email protected]54a07a22010-06-14 19:07:39731 for solution in self.dependencies:
[email protected]e3da35f2010-03-09 21:40:45732 # Dictionary of { path : SCM url } to describe the gclient checkout
[email protected]54a07a22010-06-14 19:07:39733 name = solution.name
[email protected]e3da35f2010-03-09 21:40:45734 if name in solution_names:
[email protected]e3608df2009-11-10 20:22:57735 raise gclient_utils.Error("solution %s specified more than once" % name)
[email protected]54a07a22010-06-14 19:07:39736 (url, rev) = GetURLAndRev(name, solution.url)
[email protected]770ff9e2009-09-23 17:18:18737 entries[name] = "%s@%s" % (url, rev)
[email protected]e3da35f2010-03-09 21:40:45738 solution_names[name] = "%s@%s" % (url, rev)
[email protected]fb2b8eb2009-04-23 21:03:42739
[email protected]de8f3522010-03-11 23:47:44740 # Process the dependencies next (sort alphanumerically to ensure that
741 # containing directories get populated first and for readability)
[email protected]271375b2010-06-23 19:17:38742 deps = self._ParseAllDeps(entries)
[email protected]de8f3522010-03-11 23:47:44743 deps_to_process = deps.keys()
744 deps_to_process.sort()
[email protected]fb2b8eb2009-04-23 21:03:42745
[email protected]de8f3522010-03-11 23:47:44746 # First pass for direct dependencies.
747 for d in deps_to_process:
748 if type(deps[d]) == str:
749 (url, rev) = GetURLAndRev(d, deps[d])
750 entries[d] = "%s@%s" % (url, rev)
[email protected]fb2b8eb2009-04-23 21:03:42751
[email protected]de8f3522010-03-11 23:47:44752 # Second pass for inherited deps (via the From keyword)
753 for d in deps_to_process:
[email protected]4b5b1772010-04-08 01:52:56754 if isinstance(deps[d], self.FromImpl):
[email protected]de8f3522010-03-11 23:47:44755 deps_parent_url = entries[deps[d].module_name]
756 if deps_parent_url.find("@") < 0:
757 raise gclient_utils.Error("From %s missing revisioned url" %
758 deps[d].module_name)
[email protected]271375b2010-06-23 19:17:38759 sub_deps_base_url = deps[deps[d].module_name]
760 sub_deps = Dependency(self, deps[d].module_name, sub_deps_base_url
761 ).ParseDepsFile(False)
762 url = deps[d].GetUrl(d, sub_deps_base_url, self.root_dir(), sub_deps)
763 (url, rev) = GetURLAndRev(d, url)
[email protected]de8f3522010-03-11 23:47:44764 entries[d] = "%s@%s" % (url, rev)
[email protected]e3da35f2010-03-09 21:40:45765
[email protected]de8f3522010-03-11 23:47:44766 # Build the snapshot configuration string
767 if self._options.snapshot:
768 url = entries.pop(name)
[email protected]73e21142010-07-05 13:32:01769 custom_deps = ''.join([' \"%s\": \"%s\",\n' % (x, entries[x])
770 for x in sorted(entries.keys())])
[email protected]e3da35f2010-03-09 21:40:45771
[email protected]1f7d1182010-05-17 18:17:38772 new_gclient += self.DEFAULT_SNAPSHOT_SOLUTION_TEXT % {
[email protected]de8f3522010-03-11 23:47:44773 'solution_name': name,
774 'solution_url': url,
[email protected]73e21142010-07-05 13:32:01775 'safesync_url' : '',
[email protected]de8f3522010-03-11 23:47:44776 'solution_deps': custom_deps,
777 }
778 else:
[email protected]73e21142010-07-05 13:32:01779 print(';\n'.join(['%s: %s' % (x, entries[x])
[email protected]de8f3522010-03-11 23:47:44780 for x in sorted(entries.keys())]))
[email protected]e3da35f2010-03-09 21:40:45781
782 # Print the snapshot configuration file
783 if self._options.snapshot:
[email protected]491c04b2010-05-17 18:17:44784 config = self.DEFAULT_SNAPSHOT_FILE_TEXT % {'solution_list': new_gclient}
[email protected]75a59272010-06-11 22:34:03785 snapclient = GClient(self.root_dir(), self._options)
[email protected]e3da35f2010-03-09 21:40:45786 snapclient.SetConfig(config)
[email protected]116704f2010-06-11 17:34:38787 print(snapclient.config_content)
[email protected]fb2b8eb2009-04-23 21:03:42788
[email protected]d36fba82010-06-28 16:50:40789 def ParseDepsFile(self, direct_reference):
790 """No DEPS to parse for a .gclient file."""
791 self.direct_reference = direct_reference
792 self.deps_parsed = True
793
[email protected]75a59272010-06-11 22:34:03794 def root_dir(self):
[email protected]d36fba82010-06-28 16:50:40795 """Root directory of gclient checkout."""
[email protected]75a59272010-06-11 22:34:03796 return self._root_dir
797
[email protected]271375b2010-06-23 19:17:38798 def enforced_os(self):
[email protected]d36fba82010-06-28 16:50:40799 """What deps_os entries that are to be parsed."""
[email protected]271375b2010-06-23 19:17:38800 return self._enforced_os
801
[email protected]d36fba82010-06-28 16:50:40802 def recursion_limit(self):
803 """How recursive can each dependencies in DEPS file can load DEPS file."""
804 return self._recursion_limit
805
806 def tree(self, force_all):
807 """Returns a flat list of all the dependencies."""
808 def subtree(dep):
809 if not force_all and not dep.direct_reference:
810 # Was loaded from a From() keyword in a DEPS file, don't load all its
811 # dependencies.
812 return []
813 result = dep.dependencies[:]
814 for d in dep.dependencies:
815 result.extend(subtree(d))
816 return result
817 return subtree(self)
818
[email protected]fb2b8eb2009-04-23 21:03:42819
[email protected]5ca27692010-05-26 19:32:41820#### gclient commands.
[email protected]fb2b8eb2009-04-23 21:03:42821
822
[email protected]5ca27692010-05-26 19:32:41823def CMDcleanup(parser, args):
824 """Cleans up all working copies.
[email protected]ddff62d2010-05-17 21:02:36825
[email protected]5ca27692010-05-26 19:32:41826Mostly svn-specific. Simply runs 'svn cleanup' for each module.
[email protected]79692d62010-05-14 18:57:13827"""
[email protected]0b6a0842010-06-15 14:34:19828 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
829 help='override deps for the specified (comma-separated) '
830 'platform(s); \'all\' will process all deps_os '
831 'references')
[email protected]5ca27692010-05-26 19:32:41832 (options, args) = parser.parse_args(args)
[email protected]2806acc2009-05-15 12:33:34833 client = GClient.LoadCurrentConfig(options)
[email protected]fb2b8eb2009-04-23 21:03:42834 if not client:
[email protected]0b6a0842010-06-15 14:34:19835 raise gclient_utils.Error('client not configured; see \'gclient config\'')
[email protected]fb2b8eb2009-04-23 21:03:42836 if options.verbose:
837 # Print out the .gclient file. This is longer than if we just printed the
838 # client dict, but more legible, and it might contain helpful comments.
[email protected]116704f2010-06-11 17:34:38839 print(client.config_content)
[email protected]fb2b8eb2009-04-23 21:03:42840 return client.RunOnDeps('cleanup', args)
841
842
[email protected]4b90e3a2010-07-01 20:28:26843@attr('usage', '[command] [args ...]')
844def CMDrecurse(parser, args):
845 """Operates on all the entries.
846
847 Runs a shell command on all entries.
848 """
849 # Stop parsing at the first non-arg so that these go through to the command
850 parser.disable_interspersed_args()
851 parser.add_option('-s', '--scm', action='append', default=[],
852 help='choose scm types to operate upon')
853 options, args = parser.parse_args(args)
854 root, entries = gclient_utils.GetGClientRootAndEntries()
855 scm_set = set()
856 for scm in options.scm:
857 scm_set.update(scm.split(','))
858
859 # Pass in the SCM type as an env variable
860 env = os.environ.copy()
861
862 for path, url in entries.iteritems():
863 scm = gclient_scm.GetScmName(url)
864 if scm_set and scm not in scm_set:
865 continue
866 dir = os.path.normpath(os.path.join(root, path))
867 env['GCLIENT_SCM'] = scm
868 env['GCLIENT_URL'] = url
869 subprocess.Popen(args, cwd=dir, env=env).communicate()
870
871
[email protected]5ca27692010-05-26 19:32:41872@attr('usage', '[url] [safesync url]')
873def CMDconfig(parser, args):
[email protected]ddff62d2010-05-17 21:02:36874 """Create a .gclient file in the current directory.
875
[email protected]5ca27692010-05-26 19:32:41876This specifies the configuration for further commands. After update/sync,
[email protected]79692d62010-05-14 18:57:13877top-level DEPS files in each module are read to determine dependent
[email protected]5ca27692010-05-26 19:32:41878modules to operate on as well. If optional [url] parameter is
[email protected]79692d62010-05-14 18:57:13879provided, then configuration is read from a specified Subversion server
[email protected]5ca27692010-05-26 19:32:41880URL.
[email protected]79692d62010-05-14 18:57:13881"""
[email protected]0b6a0842010-06-15 14:34:19882 parser.add_option('--spec',
883 help='create a gclient file containing the provided '
884 'string. Due to Cygwin/Python brokenness, it '
885 'probably can\'t contain any newlines.')
886 parser.add_option('--name',
887 help='overrides the default name for the solution')
[email protected]5ca27692010-05-26 19:32:41888 (options, args) = parser.parse_args(args)
[email protected]5fc2a332010-05-26 19:37:15889 if ((options.spec and args) or len(args) > 2 or
890 (not options.spec and not args)):
891 parser.error('Inconsistent arguments. Use either --spec or one or 2 args')
892
[email protected]0329e672009-05-13 18:41:04893 if os.path.exists(options.config_filename):
[email protected]0b6a0842010-06-15 14:34:19894 raise gclient_utils.Error('%s file already exists in the current directory'
[email protected]e3608df2009-11-10 20:22:57895 % options.config_filename)
[email protected]2806acc2009-05-15 12:33:34896 client = GClient('.', options)
[email protected]fb2b8eb2009-04-23 21:03:42897 if options.spec:
898 client.SetConfig(options.spec)
899 else:
[email protected]1ab7ffc2009-06-03 17:21:37900 base_url = args[0].rstrip('/')
[email protected]8cf7a392010-04-07 17:20:26901 if not options.name:
[email protected]0b6a0842010-06-15 14:34:19902 name = base_url.split('/')[-1]
[email protected]8cf7a392010-04-07 17:20:26903 else:
904 # specify an alternate relpath for the given URL.
905 name = options.name
[email protected]0b6a0842010-06-15 14:34:19906 safesync_url = ''
[email protected]fb2b8eb2009-04-23 21:03:42907 if len(args) > 1:
908 safesync_url = args[1]
909 client.SetDefaultConfig(name, base_url, safesync_url)
910 client.SaveConfig()
[email protected]79692d62010-05-14 18:57:13911 return 0
[email protected]fb2b8eb2009-04-23 21:03:42912
913
[email protected]5ca27692010-05-26 19:32:41914def CMDexport(parser, args):
[email protected]ddff62d2010-05-17 21:02:36915 """Wrapper for svn export for all managed directories."""
[email protected]0b6a0842010-06-15 14:34:19916 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
917 help='override deps for the specified (comma-separated) '
918 'platform(s); \'all\' will process all deps_os '
919 'references')
[email protected]5ca27692010-05-26 19:32:41920 (options, args) = parser.parse_args(args)
[email protected]644aa0c2009-07-17 20:20:41921 if len(args) != 1:
[email protected]0b6a0842010-06-15 14:34:19922 raise gclient_utils.Error('Need directory name')
[email protected]644aa0c2009-07-17 20:20:41923 client = GClient.LoadCurrentConfig(options)
924
925 if not client:
[email protected]0b6a0842010-06-15 14:34:19926 raise gclient_utils.Error('client not configured; see \'gclient config\'')
[email protected]644aa0c2009-07-17 20:20:41927
928 if options.verbose:
929 # Print out the .gclient file. This is longer than if we just printed the
930 # client dict, but more legible, and it might contain helpful comments.
[email protected]116704f2010-06-11 17:34:38931 print(client.config_content)
[email protected]644aa0c2009-07-17 20:20:41932 return client.RunOnDeps('export', args)
933
[email protected]fb2b8eb2009-04-23 21:03:42934
[email protected]5ca27692010-05-26 19:32:41935@attr('epilog', """Example:
936 gclient pack > patch.txt
937 generate simple patch for configured client and dependences
938""")
939def CMDpack(parser, args):
[email protected]79692d62010-05-14 18:57:13940 """Generate a patch which can be applied at the root of the tree.
[email protected]ddff62d2010-05-17 21:02:36941
[email protected]5ca27692010-05-26 19:32:41942Internally, runs 'svn diff'/'git diff' on each checked out module and
[email protected]79692d62010-05-14 18:57:13943dependencies, and performs minimal postprocessing of the output. The
944resulting patch is printed to stdout and can be applied to a freshly
[email protected]5ca27692010-05-26 19:32:41945checked out tree via 'patch -p0 < patchfile'.
[email protected]79692d62010-05-14 18:57:13946"""
[email protected]0b6a0842010-06-15 14:34:19947 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
948 help='override deps for the specified (comma-separated) '
949 'platform(s); \'all\' will process all deps_os '
950 'references')
[email protected]5ca27692010-05-26 19:32:41951 (options, args) = parser.parse_args(args)
[email protected]ab318592009-09-04 00:54:55952 client = GClient.LoadCurrentConfig(options)
953 if not client:
[email protected]0b6a0842010-06-15 14:34:19954 raise gclient_utils.Error('client not configured; see \'gclient config\'')
[email protected]ab318592009-09-04 00:54:55955 if options.verbose:
956 # Print out the .gclient file. This is longer than if we just printed the
957 # client dict, but more legible, and it might contain helpful comments.
[email protected]116704f2010-06-11 17:34:38958 print(client.config_content)
[email protected]ab318592009-09-04 00:54:55959 return client.RunOnDeps('pack', args)
960
961
[email protected]5ca27692010-05-26 19:32:41962def CMDstatus(parser, args):
963 """Show modification status for every dependencies."""
[email protected]0b6a0842010-06-15 14:34:19964 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
965 help='override deps for the specified (comma-separated) '
966 'platform(s); \'all\' will process all deps_os '
967 'references')
[email protected]5ca27692010-05-26 19:32:41968 (options, args) = parser.parse_args(args)
[email protected]2806acc2009-05-15 12:33:34969 client = GClient.LoadCurrentConfig(options)
[email protected]fb2b8eb2009-04-23 21:03:42970 if not client:
[email protected]0b6a0842010-06-15 14:34:19971 raise gclient_utils.Error('client not configured; see \'gclient config\'')
[email protected]fb2b8eb2009-04-23 21:03:42972 if options.verbose:
973 # Print out the .gclient file. This is longer than if we just printed the
974 # client dict, but more legible, and it might contain helpful comments.
[email protected]116704f2010-06-11 17:34:38975 print(client.config_content)
[email protected]fb2b8eb2009-04-23 21:03:42976 return client.RunOnDeps('status', args)
977
978
[email protected]5ca27692010-05-26 19:32:41979@attr('epilog', """Examples:
[email protected]79692d62010-05-14 18:57:13980 gclient sync
981 update files from SCM according to current configuration,
982 *for modules which have changed since last update or sync*
983 gclient sync --force
984 update files from SCM according to current configuration, for
985 all modules (useful for recovering files deleted from local copy)
986 gclient sync --revision src@31000
987 update src directory to r31000
[email protected]5ca27692010-05-26 19:32:41988""")
989def CMDsync(parser, args):
990 """Checkout/update all modules."""
[email protected]0b6a0842010-06-15 14:34:19991 parser.add_option('-f', '--force', action='store_true',
992 help='force update even for unchanged modules')
993 parser.add_option('-n', '--nohooks', action='store_true',
994 help='don\'t run hooks after the update is complete')
995 parser.add_option('-r', '--revision', action='append',
996 dest='revisions', metavar='REV', default=[],
997 help='Enforces revision/hash for the solutions with the '
998 'format src@rev. The src@ part is optional and can be '
999 'skipped. -r can be used multiple times when .gclient '
1000 'has multiple solutions configured and will work even '
1001 'if the src@ part is skipped.')
1002 parser.add_option('-H', '--head', action='store_true',
1003 help='skips any safesync_urls specified in '
1004 'configured solutions and sync to head instead')
1005 parser.add_option('-D', '--delete_unversioned_trees', action='store_true',
1006 help='delete any unexpected unversioned trees '
1007 'that are in the checkout')
1008 parser.add_option('-R', '--reset', action='store_true',
1009 help='resets any local changes before updating (git only)')
1010 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1011 help='override deps for the specified (comma-separated) '
1012 'platform(s); \'all\' will process all deps_os '
1013 'references')
1014 parser.add_option('-m', '--manually_grab_svn_rev', action='store_true',
1015 help='Skip svn up whenever possible by requesting '
1016 'actual HEAD revision from the repository')
[email protected]5ca27692010-05-26 19:32:411017 (options, args) = parser.parse_args(args)
[email protected]2806acc2009-05-15 12:33:341018 client = GClient.LoadCurrentConfig(options)
[email protected]fb2b8eb2009-04-23 21:03:421019
1020 if not client:
[email protected]0b6a0842010-06-15 14:34:191021 raise gclient_utils.Error('client not configured; see \'gclient config\'')
[email protected]fb2b8eb2009-04-23 21:03:421022
[email protected]307d1792010-05-31 20:03:131023 if options.revisions and options.head:
1024 # TODO(maruel): Make it a parser.error if it doesn't break any builder.
[email protected]0b6a0842010-06-15 14:34:191025 print('Warning: you cannot use both --head and --revision')
[email protected]fb2b8eb2009-04-23 21:03:421026
1027 if options.verbose:
1028 # Print out the .gclient file. This is longer than if we just printed the
1029 # client dict, but more legible, and it might contain helpful comments.
[email protected]116704f2010-06-11 17:34:381030 print(client.config_content)
[email protected]fb2b8eb2009-04-23 21:03:421031 return client.RunOnDeps('update', args)
1032
1033
[email protected]5ca27692010-05-26 19:32:411034def CMDupdate(parser, args):
[email protected]ddff62d2010-05-17 21:02:361035 """Alias for the sync command. Deprecated."""
[email protected]5ca27692010-05-26 19:32:411036 return CMDsync(parser, args)
[email protected]fb2b8eb2009-04-23 21:03:421037
[email protected]5ca27692010-05-26 19:32:411038def CMDdiff(parser, args):
1039 """Displays local diff for every dependencies."""
[email protected]0b6a0842010-06-15 14:34:191040 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1041 help='override deps for the specified (comma-separated) '
1042 'platform(s); \'all\' will process all deps_os '
1043 'references')
[email protected]5ca27692010-05-26 19:32:411044 (options, args) = parser.parse_args(args)
[email protected]2806acc2009-05-15 12:33:341045 client = GClient.LoadCurrentConfig(options)
[email protected]fb2b8eb2009-04-23 21:03:421046 if not client:
[email protected]0b6a0842010-06-15 14:34:191047 raise gclient_utils.Error('client not configured; see \'gclient config\'')
[email protected]fb2b8eb2009-04-23 21:03:421048 if options.verbose:
1049 # Print out the .gclient file. This is longer than if we just printed the
1050 # client dict, but more legible, and it might contain helpful comments.
[email protected]116704f2010-06-11 17:34:381051 print(client.config_content)
[email protected]fb2b8eb2009-04-23 21:03:421052 return client.RunOnDeps('diff', args)
1053
1054
[email protected]5ca27692010-05-26 19:32:411055def CMDrevert(parser, args):
1056 """Revert all modifications in every dependencies."""
[email protected]0b6a0842010-06-15 14:34:191057 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1058 help='override deps for the specified (comma-separated) '
1059 'platform(s); \'all\' will process all deps_os '
1060 'references')
1061 parser.add_option('-n', '--nohooks', action='store_true',
1062 help='don\'t run hooks after the revert is complete')
[email protected]5ca27692010-05-26 19:32:411063 (options, args) = parser.parse_args(args)
1064 # --force is implied.
1065 options.force = True
[email protected]2806acc2009-05-15 12:33:341066 client = GClient.LoadCurrentConfig(options)
[email protected]fb2b8eb2009-04-23 21:03:421067 if not client:
[email protected]0b6a0842010-06-15 14:34:191068 raise gclient_utils.Error('client not configured; see \'gclient config\'')
[email protected]fb2b8eb2009-04-23 21:03:421069 return client.RunOnDeps('revert', args)
1070
1071
[email protected]5ca27692010-05-26 19:32:411072def CMDrunhooks(parser, args):
1073 """Runs hooks for files that have been modified in the local working copy."""
[email protected]0b6a0842010-06-15 14:34:191074 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1075 help='override deps for the specified (comma-separated) '
1076 'platform(s); \'all\' will process all deps_os '
1077 'references')
1078 parser.add_option('-f', '--force', action='store_true', default=True,
1079 help='Deprecated. No effect.')
[email protected]5ca27692010-05-26 19:32:411080 (options, args) = parser.parse_args(args)
[email protected]2806acc2009-05-15 12:33:341081 client = GClient.LoadCurrentConfig(options)
[email protected]fb2b8eb2009-04-23 21:03:421082 if not client:
[email protected]0b6a0842010-06-15 14:34:191083 raise gclient_utils.Error('client not configured; see \'gclient config\'')
[email protected]fb2b8eb2009-04-23 21:03:421084 if options.verbose:
1085 # Print out the .gclient file. This is longer than if we just printed the
1086 # client dict, but more legible, and it might contain helpful comments.
[email protected]116704f2010-06-11 17:34:381087 print(client.config_content)
[email protected]5df6a462009-08-28 18:52:261088 options.force = True
[email protected]5ca27692010-05-26 19:32:411089 options.nohooks = False
[email protected]fb2b8eb2009-04-23 21:03:421090 return client.RunOnDeps('runhooks', args)
1091
1092
[email protected]5ca27692010-05-26 19:32:411093def CMDrevinfo(parser, args):
[email protected]9eda4112010-06-11 18:56:101094 """Output revision info mapping for the client and its dependencies.
1095
[email protected]0b6a0842010-06-15 14:34:191096 This allows the capture of an overall 'revision' for the source tree that
[email protected]9eda4112010-06-11 18:56:101097 can be used to reproduce the same tree in the future. It is only useful for
[email protected]0b6a0842010-06-15 14:34:191098 'unpinned dependencies', i.e. DEPS/deps references without a svn revision
1099 number or a git hash. A git branch name isn't 'pinned' since the actual
[email protected]9eda4112010-06-11 18:56:101100 commit can change.
1101 """
1102 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1103 help='override deps for the specified (comma-separated) '
1104 'platform(s); \'all\' will process all deps_os '
1105 'references')
1106 parser.add_option('-s', '--snapshot', action='store_true',
1107 help='creates a snapshot .gclient file of the current '
1108 'version of all repositories to reproduce the tree, '
1109 'implies -a')
[email protected]5ca27692010-05-26 19:32:411110 (options, args) = parser.parse_args(args)
[email protected]2806acc2009-05-15 12:33:341111 client = GClient.LoadCurrentConfig(options)
[email protected]fb2b8eb2009-04-23 21:03:421112 if not client:
[email protected]0b6a0842010-06-15 14:34:191113 raise gclient_utils.Error('client not configured; see \'gclient config\'')
[email protected]fb2b8eb2009-04-23 21:03:421114 client.PrintRevInfo()
[email protected]79692d62010-05-14 18:57:131115 return 0
[email protected]fb2b8eb2009-04-23 21:03:421116
1117
[email protected]5ca27692010-05-26 19:32:411118def Command(name):
1119 return getattr(sys.modules[__name__], 'CMD' + name, None)
1120
1121
1122def CMDhelp(parser, args):
1123 """Prints list of commands or help for a specific command."""
[email protected]6e29d572010-06-04 17:32:201124 (_, args) = parser.parse_args(args)
[email protected]ddff62d2010-05-17 21:02:361125 if len(args) == 1:
[email protected]5ca27692010-05-26 19:32:411126 return Main(args + ['--help'])
[email protected]ddff62d2010-05-17 21:02:361127 parser.print_help()
1128 return 0
1129
1130
[email protected]5ca27692010-05-26 19:32:411131def GenUsage(parser, command):
1132 """Modify an OptParse object with the function's documentation."""
1133 obj = Command(command)
1134 if command == 'help':
1135 command = '<command>'
1136 # OptParser.description prefer nicely non-formatted strings.
1137 parser.description = re.sub('[\r\n ]{2,}', ' ', obj.__doc__)
1138 usage = getattr(obj, 'usage', '')
1139 parser.set_usage('%%prog %s [options] %s' % (command, usage))
1140 parser.epilog = getattr(obj, 'epilog', None)
[email protected]fb2b8eb2009-04-23 21:03:421141
1142
1143def Main(argv):
[email protected]5ca27692010-05-26 19:32:411144 """Doesn't parse the arguments here, just find the right subcommand to
1145 execute."""
[email protected]6e29d572010-06-04 17:32:201146 try:
1147 # Do it late so all commands are listed.
1148 CMDhelp.usage = ('\n\nCommands are:\n' + '\n'.join([
1149 ' %-10s %s' % (fn[3:], Command(fn[3:]).__doc__.split('\n')[0].strip())
1150 for fn in dir(sys.modules[__name__]) if fn.startswith('CMD')]))
1151 parser = optparse.OptionParser(version='%prog ' + __version__)
[email protected]f0fc9912010-06-11 17:57:331152 parser.add_option('-v', '--verbose', action='count', default=0,
1153 help='Produces additional output for diagnostics. Can be '
1154 'used up to three times for more logging info.')
1155 parser.add_option('--gclientfile', dest='config_filename',
1156 default=os.environ.get('GCLIENT_FILE', '.gclient'),
1157 help='Specify an alternate %default file')
[email protected]6e29d572010-06-04 17:32:201158 # Integrate standard options processing.
1159 old_parser = parser.parse_args
1160 def Parse(args):
1161 (options, args) = old_parser(args)
[email protected]f0fc9912010-06-11 17:57:331162 level = None
[email protected]6e29d572010-06-04 17:32:201163 if options.verbose == 2:
[email protected]f0fc9912010-06-11 17:57:331164 level = logging.INFO
[email protected]6e29d572010-06-04 17:32:201165 elif options.verbose > 2:
[email protected]f0fc9912010-06-11 17:57:331166 level = logging.DEBUG
1167 logging.basicConfig(level=level,
1168 format='%(module)s(%(lineno)d) %(funcName)s:%(message)s')
1169 options.entries_filename = options.config_filename + '_entries'
[email protected]6e29d572010-06-04 17:32:201170 if not hasattr(options, 'revisions'):
1171 # GClient.RunOnDeps expects it even if not applicable.
1172 options.revisions = []
1173 if not hasattr(options, 'head'):
1174 options.head = None
[email protected]f0fc9912010-06-11 17:57:331175 if not hasattr(options, 'nohooks'):
1176 options.nohooks = True
1177 if not hasattr(options, 'deps_os'):
1178 options.deps_os = None
[email protected]6e29d572010-06-04 17:32:201179 return (options, args)
1180 parser.parse_args = Parse
1181 # We don't want wordwrapping in epilog (usually examples)
1182 parser.format_epilog = lambda _: parser.epilog or ''
1183 if argv:
1184 command = Command(argv[0])
1185 if command:
[email protected]f0fc9912010-06-11 17:57:331186 # 'fix' the usage and the description now that we know the subcommand.
[email protected]6e29d572010-06-04 17:32:201187 GenUsage(parser, argv[0])
1188 return command(parser, argv[1:])
1189 # Not a known command. Default to help.
1190 GenUsage(parser, 'help')
1191 return CMDhelp(parser, argv)
1192 except gclient_utils.Error, e:
[email protected]f0fc9912010-06-11 17:57:331193 print >> sys.stderr, 'Error: %s' % str(e)
[email protected]6e29d572010-06-04 17:32:201194 return 1
[email protected]fb2b8eb2009-04-23 21:03:421195
1196
[email protected]f0fc9912010-06-11 17:57:331197if '__main__' == __name__:
[email protected]6e29d572010-06-04 17:32:201198 sys.exit(Main(sys.argv[1:]))
[email protected]fb2b8eb2009-04-23 21:03:421199
1200# vim: ts=2:sw=2:tw=80:et: