blob: 748c6dc10213ffec625684a1451f37be11cdb0d1 [file] [log] [blame]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001#!/usr/bin/python
maruel@chromium.orgba551772010-02-03 18:21:42 +00002# 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.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00005
6"""A wrapper script to manage a set of client modules in different SCM.
7
8This script is intended to be used to help basic management of client
msb@chromium.orgd6504212010-01-13 17:34:31 +00009program sources residing in one or more Subversion modules and Git
10repositories, along with other modules it depends on, also in Subversion or Git,
11but possibly on multiple respositories, making a wrapper system apparently
12necessary.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000013
14Files
15 .gclient : Current client configuration, written by 'config' command.
16 Format is a Python script defining 'solutions', a list whose
17 entries each are maps binding the strings "name" and "url"
18 to strings specifying the name and location of the client
19 module, as well as "custom_deps" to a map similar to the DEPS
20 file below.
21 .gclient_entries : A cache constructed by 'update' command. Format is a
22 Python script defining 'entries', a list of the names
23 of all modules in the client
24 <module>/DEPS : Python script defining var 'deps' as a map from each requisite
25 submodule name to a URL where it can be found (via one SCM)
26
27Hooks
28 .gclient and DEPS files may optionally contain a list named "hooks" to
29 allow custom actions to be performed based on files that have changed in the
evan@chromium.org67820ef2009-07-27 17:23:00 +000030 working copy as a result of a "sync"/"update" or "revert" operation. This
31 could be prevented by using --nohooks (hooks run by default). Hooks can also
maruel@chromium.org5df6a462009-08-28 18:52:26 +000032 be forced to run with the "runhooks" operation. If "sync" is run with
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000033 --force, all known hooks will run regardless of the state of the working
34 copy.
35
36 Each item in a "hooks" list is a dict, containing these two keys:
37 "pattern" The associated value is a string containing a regular
38 expression. When a file whose pathname matches the expression
39 is checked out, updated, or reverted, the hook's "action" will
40 run.
41 "action" A list describing a command to run along with its arguments, if
42 any. An action command will run at most one time per gclient
43 invocation, regardless of how many files matched the pattern.
44 The action is executed in the same directory as the .gclient
45 file. If the first item in the list is the string "python",
46 the current Python interpreter (sys.executable) will be used
phajdan.jr@chromium.org71b40682009-07-31 23:40:09 +000047 to run the command. If the list contains string "$matching_files"
48 it will be removed from the list and the list will be extended
49 by the list of matching files.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000050
51 Example:
52 hooks = [
53 { "pattern": "\\.(gif|jpe?g|pr0n|png)$",
54 "action": ["python", "image_indexer.py", "--all"]},
55 ]
56"""
57
maruel@chromium.org5ca27692010-05-26 19:32:41 +000058__version__ = "0.4"
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000059
60import errno
maruel@chromium.org754960e2009-09-21 12:31:05 +000061import logging
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000062import optparse
63import os
msb@chromium.org2e38de72009-09-28 17:04:47 +000064import pprint
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000065import re
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000066import sys
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000067import urlparse
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000068import urllib
69
maruel@chromium.orgada4c652009-12-03 15:32:01 +000070import breakpad
71
maruel@chromium.org5f3eee32009-09-17 00:34:30 +000072import gclient_scm
73import gclient_utils
nasser@codeaurora.org1f7a3d12010-02-04 15:11:50 +000074from third_party.repo.progress import Progress
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000075
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000076
maruel@chromium.org1f7d1182010-05-17 18:17:38 +000077def attr(attr, data):
78 """Sets an attribute on a function."""
79 def hook(fn):
80 setattr(fn, attr, data)
81 return fn
82 return hook
maruel@chromium.orge3da35f2010-03-09 21:40:45 +000083
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000084
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000085## GClient implementation.
86
87
maruel@chromium.org116704f2010-06-11 17:34:38 +000088class GClientKeywords(object):
89 class FromImpl(object):
90 """Used to implement the From() syntax."""
91
92 def __init__(self, module_name, sub_target_name=None):
93 """module_name is the dep module we want to include from. It can also be
94 the name of a subdirectory to include from.
95
96 sub_target_name is an optional parameter if the module name in the other
97 DEPS file is different. E.g., you might want to map src/net to net."""
98 self.module_name = module_name
99 self.sub_target_name = sub_target_name
100
101 def __str__(self):
102 return 'From(%s, %s)' % (repr(self.module_name),
103 repr(self.sub_target_name))
104
105 def GetUrl(self, target_name, sub_deps_base_url, root_dir, sub_deps):
106 """Resolve the URL for this From entry."""
107 sub_deps_target_name = target_name
108 if self.sub_target_name:
109 sub_deps_target_name = self.sub_target_name
110 url = sub_deps[sub_deps_target_name]
111 if url.startswith('/'):
112 # If it's a relative URL, we need to resolve the URL relative to the
113 # sub deps base URL.
114 if not isinstance(sub_deps_base_url, basestring):
115 sub_deps_base_url = sub_deps_base_url.GetPath()
116 scm = gclient_scm.CreateSCM(sub_deps_base_url, root_dir,
117 None)
118 url = scm.FullUrlForRelativeUrl(url)
119 return url
120
121 class FileImpl(object):
122 """Used to implement the File('') syntax which lets you sync a single file
123 from an SVN repo."""
124
125 def __init__(self, file_location):
126 self.file_location = file_location
127
128 def __str__(self):
129 return 'File("%s")' % self.file_location
130
131 def GetPath(self):
132 return os.path.split(self.file_location)[0]
133
134 def GetFilename(self):
135 rev_tokens = self.file_location.split('@')
136 return os.path.split(rev_tokens[0])[1]
137
138 def GetRevision(self):
139 rev_tokens = self.file_location.split('@')
140 if len(rev_tokens) > 1:
141 return rev_tokens[1]
142 return None
143
144 class VarImpl(object):
145 def __init__(self, custom_vars, local_scope):
146 self._custom_vars = custom_vars
147 self._local_scope = local_scope
148
149 def Lookup(self, var_name):
150 """Implements the Var syntax."""
151 if var_name in self._custom_vars:
152 return self._custom_vars[var_name]
153 elif var_name in self._local_scope.get("vars", {}):
154 return self._local_scope["vars"][var_name]
155 raise gclient_utils.Error("Var is not defined: %s" % var_name)
156
157
158class GClient(GClientKeywords):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000159 """Object that represent a gclient checkout."""
160
maruel@chromium.org116704f2010-06-11 17:34:38 +0000161 SUPPORTED_COMMANDS = [
kbr@google.comab318592009-09-04 00:54:55 +0000162 'cleanup', 'diff', 'export', 'pack', 'revert', 'status', 'update',
163 'runhooks'
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000164 ]
165
maruel@chromium.org116704f2010-06-11 17:34:38 +0000166 DEPS_OS_CHOICES = {
maruel@chromium.org491c04b2010-05-17 18:17:44 +0000167 "win32": "win",
168 "win": "win",
169 "cygwin": "win",
170 "darwin": "mac",
171 "mac": "mac",
172 "unix": "unix",
173 "linux": "unix",
174 "linux2": "unix",
175 }
176
maruel@chromium.org1f7d1182010-05-17 18:17:38 +0000177 DEPS_FILE = 'DEPS'
178
179 DEFAULT_CLIENT_FILE_TEXT = ("""\
180solutions = [
181 { "name" : "%(solution_name)s",
182 "url" : "%(solution_url)s",
183 "custom_deps" : {
184 },
185 "safesync_url": "%(safesync_url)s"
186 },
187]
188""")
189
190 DEFAULT_SNAPSHOT_SOLUTION_TEXT = ("""\
191 { "name" : "%(solution_name)s",
192 "url" : "%(solution_url)s",
193 "custom_deps" : {
194 %(solution_deps)s,
195 },
196 "safesync_url": "%(safesync_url)s"
197 },
198""")
199
200 DEFAULT_SNAPSHOT_FILE_TEXT = ("""\
201# Snapshot generated with gclient revinfo --snapshot
202solutions = [
203%(solution_list)s
204]
205""")
206
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000207 def __init__(self, root_dir, options):
208 self._root_dir = root_dir
209 self._options = options
maruel@chromium.org116704f2010-06-11 17:34:38 +0000210 self.config_content = None
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000211 self._config_dict = {}
212 self._deps_hooks = []
213
214 def SetConfig(self, content):
215 self._config_dict = {}
maruel@chromium.org116704f2010-06-11 17:34:38 +0000216 self.config_content = content
skylined@chromium.orgdf0032c2009-05-29 10:43:56 +0000217 try:
218 exec(content, self._config_dict)
219 except SyntaxError, e:
220 try:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000221 __pychecker__ = 'no-objattrs'
skylined@chromium.orgdf0032c2009-05-29 10:43:56 +0000222 # Try to construct a human readable error message
223 error_message = [
224 'There is a syntax error in your configuration file.',
225 'Line #%s, character %s:' % (e.lineno, e.offset),
226 '"%s"' % re.sub(r'[\r\n]*$', '', e.text) ]
227 except:
228 # Something went wrong, re-raise the original exception
229 raise e
230 else:
231 # Raise a new exception with the human readable message:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000232 raise gclient_utils.Error('\n'.join(error_message))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000233
234 def SaveConfig(self):
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000235 gclient_utils.FileWrite(os.path.join(self._root_dir,
236 self._options.config_filename),
maruel@chromium.org116704f2010-06-11 17:34:38 +0000237 self.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000238
239 def _LoadConfig(self):
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000240 client_source = gclient_utils.FileRead(
241 os.path.join(self._root_dir, self._options.config_filename))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000242 self.SetConfig(client_source)
243
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000244 def GetVar(self, key, default=None):
245 return self._config_dict.get(key, default)
246
247 @staticmethod
248 def LoadCurrentConfig(options, from_dir=None):
249 """Searches for and loads a .gclient file relative to the current working
250 dir.
251
252 Returns:
253 A dict representing the contents of the .gclient file or an empty dict if
254 the .gclient file doesn't exist.
255 """
256 if not from_dir:
257 from_dir = os.curdir
258 path = os.path.realpath(from_dir)
maruel@chromium.org0329e672009-05-13 18:41:04 +0000259 while not os.path.exists(os.path.join(path, options.config_filename)):
maruel@chromium.org55e724e2010-03-11 19:36:49 +0000260 split_path = os.path.split(path)
261 if not split_path[1]:
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000262 return None
maruel@chromium.org55e724e2010-03-11 19:36:49 +0000263 path = split_path[0]
maruel@chromium.org2806acc2009-05-15 12:33:34 +0000264 client = GClient(path, options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000265 client._LoadConfig()
266 return client
267
268 def SetDefaultConfig(self, solution_name, solution_url, safesync_url):
maruel@chromium.org1f7d1182010-05-17 18:17:38 +0000269 self.SetConfig(self.DEFAULT_CLIENT_FILE_TEXT % {
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000270 'solution_name': solution_name,
271 'solution_url': solution_url,
272 'safesync_url' : safesync_url,
273 })
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000274
275 def _SaveEntries(self, entries):
276 """Creates a .gclient_entries file to record the list of unique checkouts.
277
278 The .gclient_entries file lives in the same directory as .gclient.
279
280 Args:
281 entries: A sequence of solution names.
282 """
maruel@chromium.orge41f6822010-04-08 16:37:06 +0000283 # Sometimes pprint.pformat will use {', sometimes it'll use { ' ... It
284 # makes testing a bit too fun.
285 result = pprint.pformat(entries, 2)
286 if result.startswith('{\''):
maruel@chromium.org1edec4d2010-04-08 17:17:06 +0000287 result = '{ \'' + result[2:]
maruel@chromium.orge41f6822010-04-08 16:37:06 +0000288 text = "entries = \\\n" + result + '\n'
msb@chromium.org2e38de72009-09-28 17:04:47 +0000289 file_path = os.path.join(self._root_dir, self._options.entries_filename)
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000290 gclient_utils.FileWrite(file_path, text)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000291
292 def _ReadEntries(self):
293 """Read the .gclient_entries file for the given client.
294
295 Args:
296 client: The client for which the entries file should be read.
297
298 Returns:
299 A sequence of solution names, which will be empty if there is the
300 entries file hasn't been created yet.
301 """
302 scope = {}
303 filename = os.path.join(self._root_dir, self._options.entries_filename)
maruel@chromium.org0329e672009-05-13 18:41:04 +0000304 if not os.path.exists(filename):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000305 return []
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000306 exec(gclient_utils.FileRead(filename), scope)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000307 return scope["entries"]
308
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000309 def _ParseSolutionDeps(self, solution_name, solution_deps_content,
tony@chromium.org30ef9ae2010-04-09 02:18:05 +0000310 custom_vars, parse_hooks):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000311 """Parses the DEPS file for the specified solution.
312
313 Args:
314 solution_name: The name of the solution to query.
315 solution_deps_content: Content of the DEPS file for the solution
316 custom_vars: A dict of vars to override any vars defined in the DEPS file.
317
318 Returns:
319 A dict mapping module names (as relative paths) to URLs or an empty
320 dict if the solution does not have a DEPS file.
321 """
322 # Skip empty
323 if not solution_deps_content:
324 return {}
325 # Eval the content
326 local_scope = {}
maruel@chromium.org116704f2010-06-11 17:34:38 +0000327 var = self.VarImpl(custom_vars, local_scope)
tony@chromium.org4b5b1772010-04-08 01:52:56 +0000328 global_scope = {
329 "File": self.FileImpl,
330 "From": self.FromImpl,
331 "Var": var.Lookup,
332 "deps_os": {},
333 }
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000334 exec(solution_deps_content, global_scope, local_scope)
335 deps = local_scope.get("deps", {})
336
337 # load os specific dependencies if defined. these dependencies may
338 # override or extend the values defined by the 'deps' member.
339 if "deps_os" in local_scope:
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000340 if self._options.deps_os is not None:
341 deps_to_include = self._options.deps_os.split(",")
342 if "all" in deps_to_include:
maruel@chromium.org116704f2010-06-11 17:34:38 +0000343 deps_to_include = list(set(self.DEPS_OS_CHOICES.itervalues()))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000344 else:
maruel@chromium.org116704f2010-06-11 17:34:38 +0000345 deps_to_include = [self.DEPS_OS_CHOICES.get(sys.platform, "unix")]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000346
347 deps_to_include = set(deps_to_include)
348 for deps_os_key in deps_to_include:
349 os_deps = local_scope["deps_os"].get(deps_os_key, {})
350 if len(deps_to_include) > 1:
351 # Ignore any overrides when including deps for more than one
352 # platform, so we collect the broadest set of dependencies available.
353 # We may end up with the wrong revision of something for our
354 # platform, but this is the best we can do.
355 deps.update([x for x in os_deps.items() if not x[0] in deps])
356 else:
357 deps.update(os_deps)
358
tony@chromium.org30ef9ae2010-04-09 02:18:05 +0000359 if 'hooks' in local_scope and parse_hooks:
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000360 self._deps_hooks.extend(local_scope['hooks'])
361
362 # If use_relative_paths is set in the DEPS file, regenerate
363 # the dictionary using paths relative to the directory containing
364 # the DEPS file.
365 if local_scope.get('use_relative_paths'):
366 rel_deps = {}
367 for d, url in deps.items():
368 # normpath is required to allow DEPS to use .. in their
369 # dependency local path.
370 rel_deps[os.path.normpath(os.path.join(solution_name, d))] = url
371 return rel_deps
372 else:
373 return deps
374
375 def _ParseAllDeps(self, solution_urls, solution_deps_content):
376 """Parse the complete list of dependencies for the client.
377
378 Args:
379 solution_urls: A dict mapping module names (as relative paths) to URLs
380 corresponding to the solutions specified by the client. This parameter
381 is passed as an optimization.
382 solution_deps_content: A dict mapping module names to the content
383 of their DEPS files
384
385 Returns:
386 A dict mapping module names (as relative paths) to URLs corresponding
387 to the entire set of dependencies to checkout for the given client.
388
389 Raises:
390 Error: If a dependency conflicts with another dependency or of a solution.
391 """
392 deps = {}
393 for solution in self.GetVar("solutions"):
394 custom_vars = solution.get("custom_vars", {})
395 solution_deps = self._ParseSolutionDeps(
396 solution["name"],
397 solution_deps_content[solution["name"]],
tony@chromium.org30ef9ae2010-04-09 02:18:05 +0000398 custom_vars,
399 True)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000400
401 # If a line is in custom_deps, but not in the solution, we want to append
402 # this line to the solution.
403 if "custom_deps" in solution:
404 for d in solution["custom_deps"]:
405 if d not in solution_deps:
406 solution_deps[d] = solution["custom_deps"][d]
407
408 for d in solution_deps:
409 if "custom_deps" in solution and d in solution["custom_deps"]:
410 # Dependency is overriden.
411 url = solution["custom_deps"][d]
412 if url is None:
413 continue
414 else:
415 url = solution_deps[d]
416 # if we have a From reference dependent on another solution, then
417 # just skip the From reference. When we pull deps for the solution,
418 # we will take care of this dependency.
419 #
420 # If multiple solutions all have the same From reference, then we
421 # should only add one to our list of dependencies.
tony@chromium.org4b5b1772010-04-08 01:52:56 +0000422 if isinstance(url, self.FromImpl):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000423 if url.module_name in solution_urls:
424 # Already parsed.
425 continue
426 if d in deps and type(deps[d]) != str:
427 if url.module_name == deps[d].module_name:
428 continue
tony@chromium.org4b5b1772010-04-08 01:52:56 +0000429 elif isinstance(url, str):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000430 parsed_url = urlparse.urlparse(url)
431 scheme = parsed_url[0]
432 if not scheme:
433 # A relative url. Fetch the real base.
434 path = parsed_url[2]
435 if path[0] != "/":
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000436 raise gclient_utils.Error(
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000437 "relative DEPS entry \"%s\" must begin with a slash" % d)
msb@chromium.orge6f78352010-01-13 17:05:33 +0000438 # Create a scm just to query the full url.
439 scm = gclient_scm.CreateSCM(solution["url"], self._root_dir,
440 None)
441 url = scm.FullUrlForRelativeUrl(url)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000442 if d in deps and deps[d] != url:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000443 raise gclient_utils.Error(
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000444 "Solutions have conflicting versions of dependency \"%s\"" % d)
445 if d in solution_urls and solution_urls[d] != url:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000446 raise gclient_utils.Error(
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000447 "Dependency \"%s\" conflicts with specified solution" % d)
448 # Grab the dependency.
449 deps[d] = url
450 return deps
451
phajdan.jr@chromium.org71b40682009-07-31 23:40:09 +0000452 def _RunHookAction(self, hook_dict, matching_file_list):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000453 """Runs the action from a single hook.
454 """
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000455 logging.info(hook_dict)
456 logging.info(matching_file_list)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000457 command = hook_dict['action'][:]
458 if command[0] == 'python':
459 # If the hook specified "python" as the first item, the action is a
460 # Python script. Run it by starting a new copy of the same
461 # interpreter.
462 command[0] = sys.executable
463
phajdan.jr@chromium.org71b40682009-07-31 23:40:09 +0000464 if '$matching_files' in command:
phajdan.jr@chromium.org68f2e092009-08-06 17:05:35 +0000465 splice_index = command.index('$matching_files')
466 command[splice_index:splice_index + 1] = matching_file_list
phajdan.jr@chromium.org71b40682009-07-31 23:40:09 +0000467
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000468 # Use a discrete exit status code of 2 to indicate that a hook action
469 # failed. Users of this script may wish to treat hook action failures
470 # differently from VC failures.
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000471 gclient_utils.SubprocessCall(command, self._root_dir, fail_status=2)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000472
473 def _RunHooks(self, command, file_list, is_using_git):
474 """Evaluates all hooks, running actions as needed.
475 """
476 # Hooks only run for these command types.
477 if not command in ('update', 'revert', 'runhooks'):
478 return
479
evan@chromium.org67820ef2009-07-27 17:23:00 +0000480 # Hooks only run when --nohooks is not specified
481 if self._options.nohooks:
482 return
483
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000484 # Get any hooks from the .gclient file.
485 hooks = self.GetVar("hooks", [])
486 # Add any hooks found in DEPS files.
487 hooks.extend(self._deps_hooks)
488
489 # If "--force" was specified, run all hooks regardless of what files have
490 # changed. If the user is using git, then we don't know what files have
491 # changed so we always run all hooks.
492 if self._options.force or is_using_git:
493 for hook_dict in hooks:
phajdan.jr@chromium.org71b40682009-07-31 23:40:09 +0000494 self._RunHookAction(hook_dict, [])
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000495 return
496
497 # Run hooks on the basis of whether the files from the gclient operation
498 # match each hook's pattern.
499 for hook_dict in hooks:
500 pattern = re.compile(hook_dict['pattern'])
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000501 matching_file_list = [f for f in file_list if pattern.search(f)]
phajdan.jr@chromium.org71b40682009-07-31 23:40:09 +0000502 if matching_file_list:
503 self._RunHookAction(hook_dict, matching_file_list)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000504
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000505 def _EnforceRevisions(self, solutions):
506 """Checks for revision overrides."""
507 revision_overrides = {}
maruel@chromium.org307d1792010-05-31 20:03:13 +0000508 if self._options.head:
509 return revision_overrides
510 for s in solutions:
511 if not s.get('safesync_url', None):
512 continue
513 handle = urllib.urlopen(s['safesync_url'])
514 rev = handle.read().strip()
515 handle.close()
516 if len(rev):
517 self._options.revisions.append('%s@%s' % (s['name'], rev))
518 if not self._options.revisions:
519 return revision_overrides
520 # --revision will take over safesync_url.
521 solutions_names = [s['name'] for s in solutions]
522 index = 0
523 for revision in self._options.revisions:
524 if not '@' in revision:
525 # Support for --revision 123
526 revision = '%s@%s' % (solutions_names[index], revision)
527 sol, rev = revision.split("@", 1)
528 if not sol in solutions_names:
529 #raise gclient_utils.Error('%s is not a valid solution.' % sol)
530 print >> sys.stderr, ('Please fix your script, having invalid '
531 '--revision flags will soon considered an error.')
532 else:
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000533 revision_overrides[sol] = rev
maruel@chromium.org307d1792010-05-31 20:03:13 +0000534 index += 1
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000535 return revision_overrides
536
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000537 def RunOnDeps(self, command, args):
538 """Runs a command on each dependency in a client and its dependencies.
539
540 The module's dependencies are specified in its top-level DEPS files.
541
542 Args:
543 command: The command to use (e.g., 'status' or 'diff')
544 args: list of str - extra arguments to add to the command line.
545
546 Raises:
547 Error: If the client has conflicting entries.
548 """
maruel@chromium.org116704f2010-06-11 17:34:38 +0000549 if not command in self.SUPPORTED_COMMANDS:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000550 raise gclient_utils.Error("'%s' is an unsupported command" % command)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000551
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000552 solutions = self.GetVar("solutions")
553 if not solutions:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000554 raise gclient_utils.Error("No solution specified")
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000555 revision_overrides = self._EnforceRevisions(solutions)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000556
557 # When running runhooks --force, there's no need to consult the SCM.
558 # All known hooks are expected to run unconditionally regardless of working
559 # copy state, so skip the SCM status check.
560 run_scm = not (command == 'runhooks' and self._options.force)
561
562 entries = {}
563 entries_deps_content = {}
tony@chromium.orgd2e92562010-04-27 01:55:18 +0000564 file_list = []
565 # Run on the base solutions first.
566 for solution in solutions:
567 name = solution["name"]
maruel@chromium.org1f7d1182010-05-17 18:17:38 +0000568 deps_file = solution.get("deps_file", self.DEPS_FILE)
tony@chromium.orgd2e92562010-04-27 01:55:18 +0000569 if '/' in deps_file or '\\' in deps_file:
570 raise gclient_utils.Error('deps_file name must not be a path, just a '
571 'filename.')
572 if name in entries:
573 raise gclient_utils.Error("solution %s specified more than once" % name)
574 url = solution["url"]
575 entries[name] = url
576 if run_scm and url:
577 self._options.revision = revision_overrides.get(name)
578 scm = gclient_scm.CreateSCM(url, self._root_dir, name)
579 scm.RunCommand(command, self._options, args, file_list)
580 file_list = [os.path.join(name, f.strip()) for f in file_list]
581 self._options.revision = None
582 try:
583 deps_content = gclient_utils.FileRead(
584 os.path.join(self._root_dir, name, deps_file))
585 except IOError, e:
586 if e.errno != errno.ENOENT:
587 raise
588 deps_content = ""
589 entries_deps_content[name] = deps_content
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000590
tony@chromium.orgd2e92562010-04-27 01:55:18 +0000591 # Process the dependencies next (sort alphanumerically to ensure that
592 # containing directories get populated first and for readability)
593 deps = self._ParseAllDeps(entries, entries_deps_content)
594 deps_to_process = deps.keys()
595 deps_to_process.sort()
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000596
tony@chromium.orgd2e92562010-04-27 01:55:18 +0000597 # First pass for direct dependencies.
598 if command == 'update' and not self._options.verbose:
599 pm = Progress('Syncing projects', len(deps_to_process))
600 for d in deps_to_process:
nasser@codeaurora.org1f7a3d12010-02-04 15:11:50 +0000601 if command == 'update' and not self._options.verbose:
tony@chromium.orgd2e92562010-04-27 01:55:18 +0000602 pm.update()
603 if type(deps[d]) == str:
604 url = deps[d]
605 entries[d] = url
606 if run_scm:
607 self._options.revision = revision_overrides.get(d)
608 scm = gclient_scm.CreateSCM(url, self._root_dir, d)
609 scm.RunCommand(command, self._options, args, file_list)
610 self._options.revision = None
611 elif isinstance(deps[d], self.FileImpl):
maruel@chromium.org491c04b2010-05-17 18:17:44 +0000612 file_dep = deps[d]
613 self._options.revision = file_dep.GetRevision()
tony@chromium.orgd2e92562010-04-27 01:55:18 +0000614 if run_scm:
maruel@chromium.org491c04b2010-05-17 18:17:44 +0000615 scm = gclient_scm.CreateSCM(file_dep.GetPath(), self._root_dir, d)
tony@chromium.orgd2e92562010-04-27 01:55:18 +0000616 scm.RunCommand("updatesingle", self._options,
maruel@chromium.org491c04b2010-05-17 18:17:44 +0000617 args + [file_dep.GetFilename()], file_list)
maruel@chromium.org79692d62010-05-14 18:57:13 +0000618
tony@chromium.orgd2e92562010-04-27 01:55:18 +0000619 if command == 'update' and not self._options.verbose:
620 pm.end()
piman@chromium.org6f363722010-04-27 00:41:09 +0000621
tony@chromium.orgd2e92562010-04-27 01:55:18 +0000622 # Second pass for inherited deps (via the From keyword)
623 for d in deps_to_process:
624 if isinstance(deps[d], self.FromImpl):
625 filename = os.path.join(self._root_dir,
626 deps[d].module_name,
maruel@chromium.org1f7d1182010-05-17 18:17:38 +0000627 self.DEPS_FILE)
tony@chromium.orgd2e92562010-04-27 01:55:18 +0000628 content = gclient_utils.FileRead(filename)
629 sub_deps = self._ParseSolutionDeps(deps[d].module_name, content, {},
630 False)
631 # Getting the URL from the sub_deps file can involve having to resolve
632 # a File() or having to resolve a relative URL. To resolve relative
633 # URLs, we need to pass in the orignal sub deps URL.
634 sub_deps_base_url = deps[deps[d].module_name]
635 url = deps[d].GetUrl(d, sub_deps_base_url, self._root_dir, sub_deps)
636 entries[d] = url
637 if run_scm:
638 self._options.revision = revision_overrides.get(d)
639 scm = gclient_scm.CreateSCM(url, self._root_dir, d)
640 scm.RunCommand(command, self._options, args, file_list)
641 self._options.revision = None
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000642
phajdan.jr@chromium.orgd83b2b22009-08-11 15:30:55 +0000643 # Convert all absolute paths to relative.
644 for i in range(len(file_list)):
645 # TODO(phajdan.jr): We should know exactly when the paths are absolute.
646 # It depends on the command being executed (like runhooks vs sync).
647 if not os.path.isabs(file_list[i]):
648 continue
649
650 prefix = os.path.commonprefix([self._root_dir.lower(),
651 file_list[i].lower()])
652 file_list[i] = file_list[i][len(prefix):]
653
654 # Strip any leading path separators.
655 while file_list[i].startswith('\\') or file_list[i].startswith('/'):
656 file_list[i] = file_list[i][1:]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000657
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000658 is_using_git = gclient_utils.IsUsingGit(self._root_dir, entries.keys())
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000659 self._RunHooks(command, file_list, is_using_git)
660
661 if command == 'update':
ajwong@chromium.orgcdcee802009-06-23 15:30:42 +0000662 # Notify the user if there is an orphaned entry in their working copy.
663 # Only delete the directory if there are no changes in it, and
664 # delete_unversioned_trees is set to true.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000665 prev_entries = self._ReadEntries()
666 for entry in prev_entries:
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000667 # Fix path separator on Windows.
668 entry_fixed = entry.replace('/', os.path.sep)
669 e_dir = os.path.join(self._root_dir, entry_fixed)
670 # Use entry and not entry_fixed there.
maruel@chromium.org0329e672009-05-13 18:41:04 +0000671 if entry not in entries and os.path.exists(e_dir):
msb@chromium.org83017012009-09-28 18:52:12 +0000672 modified_files = False
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +0000673 if isinstance(prev_entries, list):
msb@chromium.org83017012009-09-28 18:52:12 +0000674 # old .gclient_entries format was list, now dict
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +0000675 modified_files = gclient_scm.scm.SVN.CaptureStatus(e_dir)
msb@chromium.org83017012009-09-28 18:52:12 +0000676 else:
677 file_list = []
678 scm = gclient_scm.CreateSCM(prev_entries[entry], self._root_dir,
679 entry_fixed)
680 scm.status(self._options, [], file_list)
681 modified_files = file_list != []
682 if not self._options.delete_unversioned_trees or modified_files:
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000683 # There are modified files in this entry. Keep warning until
684 # removed.
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000685 print(("\nWARNING: \"%s\" is no longer part of this client. "
686 "It is recommended that you manually remove it.\n") %
687 entry_fixed)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000688 else:
689 # Delete the entry
maruel@chromium.orgdf7a3132009-05-12 17:49:49 +0000690 print("\n________ deleting \'%s\' " +
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000691 "in \'%s\'") % (entry_fixed, self._root_dir)
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000692 gclient_utils.RemoveDirectory(e_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000693 # record the current list of entries for next time
694 self._SaveEntries(entries)
maruel@chromium.org17cdf762010-05-28 17:30:52 +0000695 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000696
697 def PrintRevInfo(self):
nasser@codeaurora.org5d63eb82010-03-24 23:22:09 +0000698 """Output revision info mapping for the client and its dependencies.
699
700 This allows the capture of an overall "revision" for the source tree that
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000701 can be used to reproduce the same tree in the future. It is only useful for
702 "unpinned dependencies", i.e. DEPS/deps references without a svn revision
703 number or a git hash. A git branch name isn't "pinned" since the actual
704 commit can change.
nasser@codeaurora.org5d63eb82010-03-24 23:22:09 +0000705
706 The --snapshot option allows creating a .gclient file to reproduce the tree.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000707 """
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000708 solutions = self.GetVar("solutions")
709 if not solutions:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000710 raise gclient_utils.Error("No solution specified")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000711
nasser@codeaurora.org5d63eb82010-03-24 23:22:09 +0000712 # Inner helper to generate base url and rev tuple
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000713 def GetURLAndRev(name, original_url):
nasser@codeaurora.org5d63eb82010-03-24 23:22:09 +0000714 url, _ = gclient_utils.SplitUrlRevision(original_url)
715 scm = gclient_scm.CreateSCM(original_url, self._root_dir, name)
716 return (url, scm.revinfo(self._options, [], None))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000717
maruel@chromium.orge3da35f2010-03-09 21:40:45 +0000718 # text of the snapshot gclient file
719 new_gclient = ""
720 # Dictionary of { path : SCM url } to ensure no duplicate solutions
721 solution_names = {}
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +0000722 entries = {}
723 entries_deps_content = {}
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000724 # Run on the base solutions first.
725 for solution in solutions:
maruel@chromium.orge3da35f2010-03-09 21:40:45 +0000726 # Dictionary of { path : SCM url } to describe the gclient checkout
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000727 name = solution["name"]
maruel@chromium.orge3da35f2010-03-09 21:40:45 +0000728 if name in solution_names:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000729 raise gclient_utils.Error("solution %s specified more than once" % name)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000730 (url, rev) = GetURLAndRev(name, solution["url"])
msb@chromium.org770ff9e2009-09-23 17:18:18 +0000731 entries[name] = "%s@%s" % (url, rev)
maruel@chromium.orge3da35f2010-03-09 21:40:45 +0000732 solution_names[name] = "%s@%s" % (url, rev)
maruel@chromium.org491c04b2010-05-17 18:17:44 +0000733 deps_file = solution.get("deps_file", self.DEPS_FILE)
nasser@codeaurora.org952d7c72010-03-01 20:41:01 +0000734 if '/' in deps_file or '\\' in deps_file:
735 raise gclient_utils.Error('deps_file name must not be a path, just a '
736 'filename.')
737 try:
738 deps_content = gclient_utils.FileRead(
739 os.path.join(self._root_dir, name, deps_file))
740 except IOError, e:
741 if e.errno != errno.ENOENT:
742 raise
743 deps_content = ""
744 entries_deps_content[name] = deps_content
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000745
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +0000746 # Process the dependencies next (sort alphanumerically to ensure that
747 # containing directories get populated first and for readability)
748 deps = self._ParseAllDeps(entries, entries_deps_content)
749 deps_to_process = deps.keys()
750 deps_to_process.sort()
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000751
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +0000752 # First pass for direct dependencies.
753 for d in deps_to_process:
754 if type(deps[d]) == str:
755 (url, rev) = GetURLAndRev(d, deps[d])
756 entries[d] = "%s@%s" % (url, rev)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000757
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +0000758 # Second pass for inherited deps (via the From keyword)
759 for d in deps_to_process:
tony@chromium.org4b5b1772010-04-08 01:52:56 +0000760 if isinstance(deps[d], self.FromImpl):
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +0000761 deps_parent_url = entries[deps[d].module_name]
762 if deps_parent_url.find("@") < 0:
763 raise gclient_utils.Error("From %s missing revisioned url" %
764 deps[d].module_name)
765 content = gclient_utils.FileRead(os.path.join(
766 self._root_dir,
767 deps[d].module_name,
maruel@chromium.org491c04b2010-05-17 18:17:44 +0000768 self.DEPS_FILE))
769 sub_deps = self._ParseSolutionDeps(deps[d].module_name, content, {},
770 False)
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +0000771 (url, rev) = GetURLAndRev(d, sub_deps[d])
772 entries[d] = "%s@%s" % (url, rev)
maruel@chromium.orge3da35f2010-03-09 21:40:45 +0000773
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +0000774 # Build the snapshot configuration string
775 if self._options.snapshot:
776 url = entries.pop(name)
777 custom_deps = ",\n ".join(["\"%s\": \"%s\"" % (x, entries[x])
778 for x in sorted(entries.keys())])
maruel@chromium.orge3da35f2010-03-09 21:40:45 +0000779
maruel@chromium.org1f7d1182010-05-17 18:17:38 +0000780 new_gclient += self.DEFAULT_SNAPSHOT_SOLUTION_TEXT % {
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +0000781 'solution_name': name,
782 'solution_url': url,
783 'safesync_url' : "",
784 'solution_deps': custom_deps,
785 }
786 else:
787 print(";\n".join(["%s: %s" % (x, entries[x])
788 for x in sorted(entries.keys())]))
maruel@chromium.orge3da35f2010-03-09 21:40:45 +0000789
790 # Print the snapshot configuration file
791 if self._options.snapshot:
maruel@chromium.org491c04b2010-05-17 18:17:44 +0000792 config = self.DEFAULT_SNAPSHOT_FILE_TEXT % {'solution_list': new_gclient}
maruel@chromium.orge3da35f2010-03-09 21:40:45 +0000793 snapclient = GClient(self._root_dir, self._options)
794 snapclient.SetConfig(config)
maruel@chromium.org116704f2010-06-11 17:34:38 +0000795 print(snapclient.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000796
797
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000798#### gclient commands.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000799
800
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000801def CMDcleanup(parser, args):
802 """Cleans up all working copies.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +0000803
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000804Mostly svn-specific. Simply runs 'svn cleanup' for each module.
maruel@chromium.org79692d62010-05-14 18:57:13 +0000805"""
maruel@chromium.org86c0dec2010-05-28 19:01:00 +0000806 parser.add_option("--deps", dest="deps_os", metavar="OS_LIST",
807 help="override deps for the specified (comma-separated) "
808 "platform(s); 'all' will process all deps_os "
809 "references")
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000810 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +0000811 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000812 if not client:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000813 raise gclient_utils.Error("client not configured; see 'gclient config'")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000814 if options.verbose:
815 # Print out the .gclient file. This is longer than if we just printed the
816 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +0000817 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000818 return client.RunOnDeps('cleanup', args)
819
820
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000821@attr('usage', '[url] [safesync url]')
822def CMDconfig(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +0000823 """Create a .gclient file in the current directory.
824
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000825This specifies the configuration for further commands. After update/sync,
maruel@chromium.org79692d62010-05-14 18:57:13 +0000826top-level DEPS files in each module are read to determine dependent
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000827modules to operate on as well. If optional [url] parameter is
maruel@chromium.org79692d62010-05-14 18:57:13 +0000828provided, then configuration is read from a specified Subversion server
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000829URL.
maruel@chromium.org79692d62010-05-14 18:57:13 +0000830"""
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000831 parser.add_option("--spec",
832 help="create a gclient file containing the provided "
833 "string. Due to Cygwin/Python brokenness, it "
834 "probably can't contain any newlines.")
835 parser.add_option("--name",
836 help="overrides the default name for the solution")
837 (options, args) = parser.parse_args(args)
maruel@chromium.org5fc2a332010-05-26 19:37:15 +0000838 if ((options.spec and args) or len(args) > 2 or
839 (not options.spec and not args)):
840 parser.error('Inconsistent arguments. Use either --spec or one or 2 args')
841
maruel@chromium.org0329e672009-05-13 18:41:04 +0000842 if os.path.exists(options.config_filename):
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000843 raise gclient_utils.Error("%s file already exists in the current directory"
844 % options.config_filename)
maruel@chromium.org2806acc2009-05-15 12:33:34 +0000845 client = GClient('.', options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000846 if options.spec:
847 client.SetConfig(options.spec)
848 else:
maruel@chromium.org1ab7ffc2009-06-03 17:21:37 +0000849 base_url = args[0].rstrip('/')
iposva@chromium.org8cf7a392010-04-07 17:20:26 +0000850 if not options.name:
851 name = base_url.split("/")[-1]
852 else:
853 # specify an alternate relpath for the given URL.
854 name = options.name
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000855 safesync_url = ""
856 if len(args) > 1:
857 safesync_url = args[1]
858 client.SetDefaultConfig(name, base_url, safesync_url)
859 client.SaveConfig()
maruel@chromium.org79692d62010-05-14 18:57:13 +0000860 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000861
862
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000863def CMDexport(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +0000864 """Wrapper for svn export for all managed directories."""
maruel@chromium.org86c0dec2010-05-28 19:01:00 +0000865 parser.add_option("--deps", dest="deps_os", metavar="OS_LIST",
866 help="override deps for the specified (comma-separated) "
867 "platform(s); 'all' will process all deps_os "
868 "references")
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000869 (options, args) = parser.parse_args(args)
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +0000870 if len(args) != 1:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000871 raise gclient_utils.Error("Need directory name")
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +0000872 client = GClient.LoadCurrentConfig(options)
873
874 if not client:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000875 raise gclient_utils.Error("client not configured; see 'gclient config'")
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +0000876
877 if options.verbose:
878 # Print out the .gclient file. This is longer than if we just printed the
879 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +0000880 print(client.config_content)
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +0000881 return client.RunOnDeps('export', args)
882
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000883
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000884@attr('epilog', """Example:
885 gclient pack > patch.txt
886 generate simple patch for configured client and dependences
887""")
888def CMDpack(parser, args):
maruel@chromium.org79692d62010-05-14 18:57:13 +0000889 """Generate a patch which can be applied at the root of the tree.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +0000890
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000891Internally, runs 'svn diff'/'git diff' on each checked out module and
maruel@chromium.org79692d62010-05-14 18:57:13 +0000892dependencies, and performs minimal postprocessing of the output. The
893resulting patch is printed to stdout and can be applied to a freshly
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000894checked out tree via 'patch -p0 < patchfile'.
maruel@chromium.org79692d62010-05-14 18:57:13 +0000895"""
maruel@chromium.org86c0dec2010-05-28 19:01:00 +0000896 parser.add_option("--deps", dest="deps_os", metavar="OS_LIST",
897 help="override deps for the specified (comma-separated) "
898 "platform(s); 'all' will process all deps_os "
899 "references")
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000900 (options, args) = parser.parse_args(args)
kbr@google.comab318592009-09-04 00:54:55 +0000901 client = GClient.LoadCurrentConfig(options)
902 if not client:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000903 raise gclient_utils.Error("client not configured; see 'gclient config'")
kbr@google.comab318592009-09-04 00:54:55 +0000904 if options.verbose:
905 # Print out the .gclient file. This is longer than if we just printed the
906 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +0000907 print(client.config_content)
kbr@google.comab318592009-09-04 00:54:55 +0000908 return client.RunOnDeps('pack', args)
909
910
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000911def CMDstatus(parser, args):
912 """Show modification status for every dependencies."""
maruel@chromium.org6b1d00b2010-05-26 20:11:08 +0000913 parser.add_option("--deps", dest="deps_os", metavar="OS_LIST",
maruel@chromium.org86c0dec2010-05-28 19:01:00 +0000914 help="override deps for the specified (comma-separated) "
915 "platform(s); 'all' will process all deps_os "
916 "references")
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000917 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +0000918 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000919 if not client:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000920 raise gclient_utils.Error("client not configured; see 'gclient config'")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000921 if options.verbose:
922 # Print out the .gclient file. This is longer than if we just printed the
923 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +0000924 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000925 return client.RunOnDeps('status', args)
926
927
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000928@attr('epilog', """Examples:
maruel@chromium.org79692d62010-05-14 18:57:13 +0000929 gclient sync
930 update files from SCM according to current configuration,
931 *for modules which have changed since last update or sync*
932 gclient sync --force
933 update files from SCM according to current configuration, for
934 all modules (useful for recovering files deleted from local copy)
935 gclient sync --revision src@31000
936 update src directory to r31000
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000937""")
938def CMDsync(parser, args):
939 """Checkout/update all modules."""
maruel@chromium.orgc0b8a4e2010-06-02 17:49:39 +0000940 parser.add_option("-f", "--force", action="store_true",
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000941 help="force update even for unchanged modules")
maruel@chromium.orgc0b8a4e2010-06-02 17:49:39 +0000942 parser.add_option("-n", "--nohooks", action="store_true",
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000943 help="don't run hooks after the update is complete")
944 parser.add_option("-r", "--revision", action="append",
945 dest="revisions", metavar="REV", default=[],
maruel@chromium.org307d1792010-05-31 20:03:13 +0000946 help="Enforces revision/hash for the solutions with the "
947 "format src@rev. The src@ part is optional and can be "
948 "skipped. -r can be used multiple times when .gclient "
949 "has multiple solutions configured and will work even "
950 "if the src@ part is skipped.")
maruel@chromium.orgc0b8a4e2010-06-02 17:49:39 +0000951 parser.add_option("-H", "--head", action="store_true",
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000952 help="skips any safesync_urls specified in "
953 "configured solutions and sync to head instead")
maruel@chromium.orgc0b8a4e2010-06-02 17:49:39 +0000954 parser.add_option("-D", "--delete_unversioned_trees", action="store_true",
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000955 help="delete any unexpected unversioned trees "
956 "that are in the checkout")
maruel@chromium.orgc0b8a4e2010-06-02 17:49:39 +0000957 parser.add_option("-R", "--reset", action="store_true",
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000958 help="resets any local changes before updating (git only)")
959 parser.add_option("--deps", dest="deps_os", metavar="OS_LIST",
maruel@chromium.org86c0dec2010-05-28 19:01:00 +0000960 help="override deps for the specified (comma-separated) "
961 "platform(s); 'all' will process all deps_os "
962 "references")
maruel@chromium.orgc0b8a4e2010-06-02 17:49:39 +0000963 parser.add_option("-m", "--manually_grab_svn_rev", action="store_true",
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000964 help="Skip svn up whenever possible by requesting "
965 "actual HEAD revision from the repository")
966 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +0000967 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000968
969 if not client:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000970 raise gclient_utils.Error("client not configured; see 'gclient config'")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000971
maruel@chromium.org307d1792010-05-31 20:03:13 +0000972 if options.revisions and options.head:
973 # TODO(maruel): Make it a parser.error if it doesn't break any builder.
974 print("Warning: you cannot use both --head and --revision")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000975
976 if options.verbose:
977 # Print out the .gclient file. This is longer than if we just printed the
978 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +0000979 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000980 return client.RunOnDeps('update', args)
981
982
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000983def CMDupdate(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +0000984 """Alias for the sync command. Deprecated."""
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000985 return CMDsync(parser, args)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000986
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000987def CMDdiff(parser, args):
988 """Displays local diff for every dependencies."""
maruel@chromium.org86c0dec2010-05-28 19:01:00 +0000989 parser.add_option("--deps", dest="deps_os", metavar="OS_LIST",
990 help="override deps for the specified (comma-separated) "
991 "platform(s); 'all' will process all deps_os "
992 "references")
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000993 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +0000994 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000995 if not client:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000996 raise gclient_utils.Error("client not configured; see 'gclient config'")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000997 if options.verbose:
998 # Print out the .gclient file. This is longer than if we just printed the
999 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001000 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001001 return client.RunOnDeps('diff', args)
1002
1003
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001004def CMDrevert(parser, args):
1005 """Revert all modifications in every dependencies."""
maruel@chromium.org6b1d00b2010-05-26 20:11:08 +00001006 parser.add_option("--deps", dest="deps_os", metavar="OS_LIST",
maruel@chromium.org86c0dec2010-05-28 19:01:00 +00001007 help="override deps for the specified (comma-separated) "
1008 "platform(s); 'all' will process all deps_os "
1009 "references")
maruel@chromium.orgc0b8a4e2010-06-02 17:49:39 +00001010 parser.add_option("-n", "--nohooks", action="store_true",
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001011 help="don't run hooks after the revert is complete")
1012 (options, args) = parser.parse_args(args)
1013 # --force is implied.
1014 options.force = True
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001015 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001016 if not client:
maruel@chromium.orge3608df2009-11-10 20:22:57 +00001017 raise gclient_utils.Error("client not configured; see 'gclient config'")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001018 return client.RunOnDeps('revert', args)
1019
1020
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001021def CMDrunhooks(parser, args):
1022 """Runs hooks for files that have been modified in the local working copy."""
maruel@chromium.org6b1d00b2010-05-26 20:11:08 +00001023 parser.add_option("--deps", dest="deps_os", metavar="OS_LIST",
maruel@chromium.org86c0dec2010-05-28 19:01:00 +00001024 help="override deps for the specified (comma-separated) "
1025 "platform(s); 'all' will process all deps_os "
1026 "references")
maruel@chromium.orgc0b8a4e2010-06-02 17:49:39 +00001027 parser.add_option("-f", "--force", action="store_true", default=True,
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001028 help="Deprecated. No effect.")
1029 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001030 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001031 if not client:
maruel@chromium.orge3608df2009-11-10 20:22:57 +00001032 raise gclient_utils.Error("client not configured; see 'gclient config'")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001033 if options.verbose:
1034 # Print out the .gclient file. This is longer than if we just printed the
1035 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001036 print(client.config_content)
maruel@chromium.org5df6a462009-08-28 18:52:26 +00001037 options.force = True
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001038 options.nohooks = False
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001039 return client.RunOnDeps('runhooks', args)
1040
1041
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001042def CMDrevinfo(parser, args):
1043 """Outputs details for every dependencies."""
maruel@chromium.org86c0dec2010-05-28 19:01:00 +00001044 parser.add_option("--deps", dest="deps_os", metavar="OS_LIST",
1045 help="override deps for the specified (comma-separated) "
1046 "platform(s); 'all' will process all deps_os "
1047 "references")
maruel@chromium.orgc0b8a4e2010-06-02 17:49:39 +00001048 parser.add_option("-s", "--snapshot", action="store_true",
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001049 help="create a snapshot file of the current "
1050 "version of all repositories")
1051 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001052 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001053 if not client:
maruel@chromium.orge3608df2009-11-10 20:22:57 +00001054 raise gclient_utils.Error("client not configured; see 'gclient config'")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001055 client.PrintRevInfo()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001056 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001057
1058
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001059def Command(name):
1060 return getattr(sys.modules[__name__], 'CMD' + name, None)
1061
1062
1063def CMDhelp(parser, args):
1064 """Prints list of commands or help for a specific command."""
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001065 (_, args) = parser.parse_args(args)
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001066 if len(args) == 1:
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001067 return Main(args + ['--help'])
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001068 parser.print_help()
1069 return 0
1070
1071
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001072def GenUsage(parser, command):
1073 """Modify an OptParse object with the function's documentation."""
1074 obj = Command(command)
1075 if command == 'help':
1076 command = '<command>'
1077 # OptParser.description prefer nicely non-formatted strings.
1078 parser.description = re.sub('[\r\n ]{2,}', ' ', obj.__doc__)
1079 usage = getattr(obj, 'usage', '')
1080 parser.set_usage('%%prog %s [options] %s' % (command, usage))
1081 parser.epilog = getattr(obj, 'epilog', None)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001082
1083
1084def Main(argv):
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001085 """Doesn't parse the arguments here, just find the right subcommand to
1086 execute."""
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001087 try:
1088 # Do it late so all commands are listed.
1089 CMDhelp.usage = ('\n\nCommands are:\n' + '\n'.join([
1090 ' %-10s %s' % (fn[3:], Command(fn[3:]).__doc__.split('\n')[0].strip())
1091 for fn in dir(sys.modules[__name__]) if fn.startswith('CMD')]))
1092 parser = optparse.OptionParser(version='%prog ' + __version__)
1093 parser.add_option("-v", "--verbose", action="count", default=0,
1094 help="Produces additional output for diagnostics. Can be "
1095 "used up to three times for more logging info.")
1096 parser.add_option("--gclientfile", metavar="FILENAME",
1097 dest="config_filename",
1098 default=os.environ.get("GCLIENT_FILE", ".gclient"),
1099 help="Specify an alternate .gclient file")
1100 # Integrate standard options processing.
1101 old_parser = parser.parse_args
1102 def Parse(args):
1103 (options, args) = old_parser(args)
1104 if options.verbose == 2:
1105 logging.basicConfig(level=logging.INFO)
1106 elif options.verbose > 2:
1107 logging.basicConfig(level=logging.DEBUG)
1108 options.entries_filename = options.config_filename + "_entries"
1109 if not hasattr(options, 'revisions'):
1110 # GClient.RunOnDeps expects it even if not applicable.
1111 options.revisions = []
1112 if not hasattr(options, 'head'):
1113 options.head = None
1114 return (options, args)
1115 parser.parse_args = Parse
1116 # We don't want wordwrapping in epilog (usually examples)
1117 parser.format_epilog = lambda _: parser.epilog or ''
1118 if argv:
1119 command = Command(argv[0])
1120 if command:
1121 # "fix" the usage and the description now that we know the subcommand.
1122 GenUsage(parser, argv[0])
1123 return command(parser, argv[1:])
1124 # Not a known command. Default to help.
1125 GenUsage(parser, 'help')
1126 return CMDhelp(parser, argv)
1127 except gclient_utils.Error, e:
1128 print >> sys.stderr, "Error: %s" % str(e)
1129 return 1
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001130
1131
1132if "__main__" == __name__:
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001133 sys.exit(Main(sys.argv[1:]))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001134
1135# vim: ts=2:sw=2:tw=80:et: