blob: aae1ac06735f5e9fe996ba3024023ce28c935434 [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
58__author__ = "darinf@gmail.com (Darin Fisher)"
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +000059__version__ = "0.3.4"
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000060
61import errno
maruel@chromium.org754960e2009-09-21 12:31:05 +000062import logging
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000063import optparse
64import os
msb@chromium.org2e38de72009-09-28 17:04:47 +000065import pprint
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000066import re
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000067import sys
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000068import urlparse
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000069import urllib
70
maruel@chromium.orgada4c652009-12-03 15:32:01 +000071import breakpad
72
maruel@chromium.org5f3eee32009-09-17 00:34:30 +000073import gclient_scm
74import gclient_utils
nasser@codeaurora.org1f7a3d12010-02-04 15:11:50 +000075from third_party.repo.progress import Progress
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000076
77# default help text
78DEFAULT_USAGE_TEXT = (
msb@chromium.orgd6504212010-01-13 17:34:31 +000079"""usage: %prog <subcommand> [options] [--] [SCM options/args...]
80a wrapper for managing a set of svn client modules and/or git repositories.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000081Version """ + __version__ + """
82
83subcommands:
84 cleanup
85 config
86 diff
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +000087 export
kbr@google.comab318592009-09-04 00:54:55 +000088 pack
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000089 revert
90 status
91 sync
92 update
93 runhooks
94 revinfo
95
msb@chromium.orgd6504212010-01-13 17:34:31 +000096Options and extra arguments can be passed to invoked SCM commands by
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000097appending them to the command line. Note that if the first such
98appended option starts with a dash (-) then the options must be
99preceded by -- to distinguish them from gclient options.
100
101For additional help on a subcommand or examples of usage, try
102 %prog help <subcommand>
103 %prog help files
104""")
105
106GENERIC_UPDATE_USAGE_TEXT = (
107 """Perform a checkout/update of the modules specified by the gclient
108configuration; see 'help config'. Unless --revision is specified,
109then the latest revision of the root solutions is checked out, with
110dependent submodule versions updated according to DEPS files.
111If --revision is specified, then the given revision is used in place
112of the latest, either for a single solution or for all solutions.
113Unless the --force option is provided, solutions and modules whose
114local revision matches the one to update (i.e., they have not changed
evan@chromium.org67820ef2009-07-27 17:23:00 +0000115in the repository) are *not* modified. Unless --nohooks is provided,
116the hooks are run.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000117This a synonym for 'gclient %(alias)s'
118
msb@chromium.orgd6504212010-01-13 17:34:31 +0000119usage: gclient %(cmd)s [options] [--] [SCM update options/args]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000120
121Valid options:
estade@chromium.org3b5cba42009-12-01 00:37:08 +0000122 --force : force update even for unchanged modules
123 --nohooks : don't run the hooks after the update is complete
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000124 --revision SOLUTION@REV : update given solution to specified revision
estade@chromium.org3b5cba42009-12-01 00:37:08 +0000125 --deps PLATFORM(S) : sync deps for the given platform(s), or 'all'
126 --verbose : output additional diagnostics
127 --head : update to latest revision, instead of last good revision
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000128
129Examples:
130 gclient %(cmd)s
msb@chromium.orgd6504212010-01-13 17:34:31 +0000131 update files from SCM according to current configuration,
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000132 *for modules which have changed since last update or sync*
133 gclient %(cmd)s --force
msb@chromium.orgd6504212010-01-13 17:34:31 +0000134 update files from SCM according to current configuration, for
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000135 all modules (useful for recovering files deleted from local copy)
estade@chromium.org3b5cba42009-12-01 00:37:08 +0000136 gclient %(cmd)s --revision src@31000
137 update src directory to r31000
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000138""")
139
140COMMAND_USAGE_TEXT = {
141 "cleanup":
142 """Clean up all working copies, using 'svn cleanup' for each module.
143Additional options and args may be passed to 'svn cleanup'.
144
145usage: cleanup [options] [--] [svn cleanup args/options]
146
147Valid options:
148 --verbose : output additional diagnostics
149""",
150 "config": """Create a .gclient file in the current directory; this
151specifies the configuration for further commands. After update/sync,
152top-level DEPS files in each module are read to determine dependent
153modules to operate on as well. If optional [url] parameter is
154provided, then configuration is read from a specified Subversion server
155URL. Otherwise, a --spec option must be provided.
156
157usage: config [option | url] [safesync url]
158
159Valid options:
160 --spec=GCLIENT_SPEC : contents of .gclient are read from string parameter.
161 *Note that due to Cygwin/Python brokenness, it
162 probably can't contain any newlines.*
163
164Examples:
165 gclient config https://gclient.googlecode.com/svn/trunk/gclient
166 configure a new client to check out gclient.py tool sources
167 gclient config --spec='solutions=[{"name":"gclient","""
168 '"url":"https://gclient.googlecode.com/svn/trunk/gclient",'
169 '"custom_deps":{}}]',
170 "diff": """Display the differences between two revisions of modules.
171(Does 'svn diff' for each checked out module and dependences.)
172Additional args and options to 'svn diff' can be passed after
173gclient options.
174
175usage: diff [options] [--] [svn args/options]
176
177Valid options:
178 --verbose : output additional diagnostics
179
180Examples:
181 gclient diff
182 simple 'svn diff' for configured client and dependences
183 gclient diff -- -x -b
184 use 'svn diff -x -b' to suppress whitespace-only differences
185 gclient diff -- -r HEAD -x -b
186 diff versus the latest version of each module
187""",
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +0000188 "export":
189 """Wrapper for svn export for all managed directories
190""",
kbr@google.comab318592009-09-04 00:54:55 +0000191 "pack":
192
193 """Generate a patch which can be applied at the root of the tree.
194Internally, runs 'svn diff' on each checked out module and
195dependencies, and performs minimal postprocessing of the output. The
196resulting patch is printed to stdout and can be applied to a freshly
197checked out tree via 'patch -p0 < patchfile'. Additional args and
198options to 'svn diff' can be passed after gclient options.
199
200usage: pack [options] [--] [svn args/options]
201
202Valid options:
203 --verbose : output additional diagnostics
204
205Examples:
206 gclient pack > patch.txt
207 generate simple patch for configured client and dependences
208 gclient pack -- -x -b > patch.txt
209 generate patch using 'svn diff -x -b' to suppress
210 whitespace-only differences
211 gclient pack -- -r HEAD -x -b > patch.txt
212 generate patch, diffing each file versus the latest version of
213 each module
214""",
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000215 "revert":
216 """Revert every file in every managed directory in the client view.
217
218usage: revert
219""",
220 "status":
221 """Show the status of client and dependent modules, using 'svn diff'
222for each module. Additional options and args may be passed to 'svn diff'.
223
224usage: status [options] [--] [svn diff args/options]
225
226Valid options:
227 --verbose : output additional diagnostics
evan@chromium.org67820ef2009-07-27 17:23:00 +0000228 --nohooks : don't run the hooks after the update is complete
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000229""",
230 "sync": GENERIC_UPDATE_USAGE_TEXT % {"cmd": "sync", "alias": "update"},
231 "update": GENERIC_UPDATE_USAGE_TEXT % {"cmd": "update", "alias": "sync"},
232 "help": """Describe the usage of this program or its subcommands.
233
234usage: help [options] [subcommand]
235
236Valid options:
237 --verbose : output additional diagnostics
238""",
239 "runhooks":
240 """Runs hooks for files that have been modified in the local working copy,
maruel@chromium.org5df6a462009-08-28 18:52:26 +0000241according to 'svn status'. Implies --force.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000242
243usage: runhooks [options]
244
245Valid options:
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000246 --verbose : output additional diagnostics
247""",
248 "revinfo":
249 """Outputs source path, server URL and revision information for every
250dependency in all solutions (no local checkout required).
251
252usage: revinfo [options]
253""",
254}
255
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000256DEFAULT_CLIENT_FILE_TEXT = ("""\
257# An element of this array (a "solution") describes a repository directory
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000258# that will be checked out into your working copy. Each solution may
259# optionally define additional dependencies (via its DEPS file) to be
260# checked out alongside the solution's directory. A solution may also
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000261# specify custom dependencies (via the "custom_deps" property) that
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000262# override or augment the dependencies specified by the DEPS file.
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000263# If a "safesync_url" is specified, it is assumed to reference the location of
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000264# a text file which contains nothing but the last known good SCM revision to
265# sync against. It is fetched if specified and used unless --head is passed
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000266
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000267solutions = [
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000268 { "name" : "%(solution_name)s",
269 "url" : "%(solution_url)s",
270 "custom_deps" : {
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000271 # To use the trunk of a component instead of what's in DEPS:
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000272 #"component": "https://svnserver/component/trunk/",
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000273 # To exclude a component from your working copy:
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000274 #"data/really_large_component": None,
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000275 },
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000276 "safesync_url": "%(safesync_url)s"
277 },
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000278]
279""")
280
281
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000282## GClient implementation.
283
284
285class GClient(object):
286 """Object that represent a gclient checkout."""
287
288 supported_commands = [
kbr@google.comab318592009-09-04 00:54:55 +0000289 'cleanup', 'diff', 'export', 'pack', 'revert', 'status', 'update',
290 'runhooks'
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000291 ]
292
293 def __init__(self, root_dir, options):
294 self._root_dir = root_dir
295 self._options = options
296 self._config_content = None
297 self._config_dict = {}
298 self._deps_hooks = []
299
300 def SetConfig(self, content):
301 self._config_dict = {}
302 self._config_content = content
skylined@chromium.orgdf0032c2009-05-29 10:43:56 +0000303 try:
304 exec(content, self._config_dict)
305 except SyntaxError, e:
306 try:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000307 __pychecker__ = 'no-objattrs'
skylined@chromium.orgdf0032c2009-05-29 10:43:56 +0000308 # Try to construct a human readable error message
309 error_message = [
310 'There is a syntax error in your configuration file.',
311 'Line #%s, character %s:' % (e.lineno, e.offset),
312 '"%s"' % re.sub(r'[\r\n]*$', '', e.text) ]
313 except:
314 # Something went wrong, re-raise the original exception
315 raise e
316 else:
317 # Raise a new exception with the human readable message:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000318 raise gclient_utils.Error('\n'.join(error_message))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000319
320 def SaveConfig(self):
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000321 gclient_utils.FileWrite(os.path.join(self._root_dir,
322 self._options.config_filename),
323 self._config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000324
325 def _LoadConfig(self):
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000326 client_source = gclient_utils.FileRead(
327 os.path.join(self._root_dir, self._options.config_filename))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000328 self.SetConfig(client_source)
329
330 def ConfigContent(self):
331 return self._config_content
332
333 def GetVar(self, key, default=None):
334 return self._config_dict.get(key, default)
335
336 @staticmethod
337 def LoadCurrentConfig(options, from_dir=None):
338 """Searches for and loads a .gclient file relative to the current working
339 dir.
340
341 Returns:
342 A dict representing the contents of the .gclient file or an empty dict if
343 the .gclient file doesn't exist.
344 """
345 if not from_dir:
346 from_dir = os.curdir
347 path = os.path.realpath(from_dir)
maruel@chromium.org0329e672009-05-13 18:41:04 +0000348 while not os.path.exists(os.path.join(path, options.config_filename)):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000349 next = os.path.split(path)
350 if not next[1]:
351 return None
352 path = next[0]
maruel@chromium.org2806acc2009-05-15 12:33:34 +0000353 client = GClient(path, options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000354 client._LoadConfig()
355 return client
356
357 def SetDefaultConfig(self, solution_name, solution_url, safesync_url):
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000358 self.SetConfig(DEFAULT_CLIENT_FILE_TEXT % {
359 'solution_name': solution_name,
360 'solution_url': solution_url,
361 'safesync_url' : safesync_url,
362 })
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000363
364 def _SaveEntries(self, entries):
365 """Creates a .gclient_entries file to record the list of unique checkouts.
366
367 The .gclient_entries file lives in the same directory as .gclient.
368
369 Args:
370 entries: A sequence of solution names.
371 """
msb@chromium.org2e38de72009-09-28 17:04:47 +0000372 text = "entries = \\\n" + pprint.pformat(entries, 2) + '\n'
373 file_path = os.path.join(self._root_dir, self._options.entries_filename)
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000374 gclient_utils.FileWrite(file_path, text)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000375
376 def _ReadEntries(self):
377 """Read the .gclient_entries file for the given client.
378
379 Args:
380 client: The client for which the entries file should be read.
381
382 Returns:
383 A sequence of solution names, which will be empty if there is the
384 entries file hasn't been created yet.
385 """
386 scope = {}
387 filename = os.path.join(self._root_dir, self._options.entries_filename)
maruel@chromium.org0329e672009-05-13 18:41:04 +0000388 if not os.path.exists(filename):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000389 return []
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000390 exec(gclient_utils.FileRead(filename), scope)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000391 return scope["entries"]
392
393 class FromImpl:
394 """Used to implement the From syntax."""
395
396 def __init__(self, module_name):
397 self.module_name = module_name
398
399 def __str__(self):
400 return 'From("%s")' % self.module_name
401
402 class _VarImpl:
403 def __init__(self, custom_vars, local_scope):
404 self._custom_vars = custom_vars
405 self._local_scope = local_scope
406
407 def Lookup(self, var_name):
408 """Implements the Var syntax."""
409 if var_name in self._custom_vars:
410 return self._custom_vars[var_name]
411 elif var_name in self._local_scope.get("vars", {}):
412 return self._local_scope["vars"][var_name]
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000413 raise gclient_utils.Error("Var is not defined: %s" % var_name)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000414
415 def _ParseSolutionDeps(self, solution_name, solution_deps_content,
416 custom_vars):
417 """Parses the DEPS file for the specified solution.
418
419 Args:
420 solution_name: The name of the solution to query.
421 solution_deps_content: Content of the DEPS file for the solution
422 custom_vars: A dict of vars to override any vars defined in the DEPS file.
423
424 Returns:
425 A dict mapping module names (as relative paths) to URLs or an empty
426 dict if the solution does not have a DEPS file.
427 """
428 # Skip empty
429 if not solution_deps_content:
430 return {}
431 # Eval the content
432 local_scope = {}
433 var = self._VarImpl(custom_vars, local_scope)
434 global_scope = {"From": self.FromImpl, "Var": var.Lookup, "deps_os": {}}
435 exec(solution_deps_content, global_scope, local_scope)
436 deps = local_scope.get("deps", {})
437
438 # load os specific dependencies if defined. these dependencies may
439 # override or extend the values defined by the 'deps' member.
440 if "deps_os" in local_scope:
441 deps_os_choices = {
442 "win32": "win",
443 "win": "win",
444 "cygwin": "win",
445 "darwin": "mac",
446 "mac": "mac",
447 "unix": "unix",
448 "linux": "unix",
449 "linux2": "unix",
450 }
451
452 if self._options.deps_os is not None:
453 deps_to_include = self._options.deps_os.split(",")
454 if "all" in deps_to_include:
455 deps_to_include = deps_os_choices.values()
456 else:
457 deps_to_include = [deps_os_choices.get(self._options.platform, "unix")]
458
459 deps_to_include = set(deps_to_include)
460 for deps_os_key in deps_to_include:
461 os_deps = local_scope["deps_os"].get(deps_os_key, {})
462 if len(deps_to_include) > 1:
463 # Ignore any overrides when including deps for more than one
464 # platform, so we collect the broadest set of dependencies available.
465 # We may end up with the wrong revision of something for our
466 # platform, but this is the best we can do.
467 deps.update([x for x in os_deps.items() if not x[0] in deps])
468 else:
469 deps.update(os_deps)
470
471 if 'hooks' in local_scope:
472 self._deps_hooks.extend(local_scope['hooks'])
473
474 # If use_relative_paths is set in the DEPS file, regenerate
475 # the dictionary using paths relative to the directory containing
476 # the DEPS file.
477 if local_scope.get('use_relative_paths'):
478 rel_deps = {}
479 for d, url in deps.items():
480 # normpath is required to allow DEPS to use .. in their
481 # dependency local path.
482 rel_deps[os.path.normpath(os.path.join(solution_name, d))] = url
483 return rel_deps
484 else:
485 return deps
486
487 def _ParseAllDeps(self, solution_urls, solution_deps_content):
488 """Parse the complete list of dependencies for the client.
489
490 Args:
491 solution_urls: A dict mapping module names (as relative paths) to URLs
492 corresponding to the solutions specified by the client. This parameter
493 is passed as an optimization.
494 solution_deps_content: A dict mapping module names to the content
495 of their DEPS files
496
497 Returns:
498 A dict mapping module names (as relative paths) to URLs corresponding
499 to the entire set of dependencies to checkout for the given client.
500
501 Raises:
502 Error: If a dependency conflicts with another dependency or of a solution.
503 """
504 deps = {}
505 for solution in self.GetVar("solutions"):
506 custom_vars = solution.get("custom_vars", {})
507 solution_deps = self._ParseSolutionDeps(
508 solution["name"],
509 solution_deps_content[solution["name"]],
510 custom_vars)
511
512 # If a line is in custom_deps, but not in the solution, we want to append
513 # this line to the solution.
514 if "custom_deps" in solution:
515 for d in solution["custom_deps"]:
516 if d not in solution_deps:
517 solution_deps[d] = solution["custom_deps"][d]
518
519 for d in solution_deps:
520 if "custom_deps" in solution and d in solution["custom_deps"]:
521 # Dependency is overriden.
522 url = solution["custom_deps"][d]
523 if url is None:
524 continue
525 else:
526 url = solution_deps[d]
527 # if we have a From reference dependent on another solution, then
528 # just skip the From reference. When we pull deps for the solution,
529 # we will take care of this dependency.
530 #
531 # If multiple solutions all have the same From reference, then we
532 # should only add one to our list of dependencies.
533 if type(url) != str:
534 if url.module_name in solution_urls:
535 # Already parsed.
536 continue
537 if d in deps and type(deps[d]) != str:
538 if url.module_name == deps[d].module_name:
539 continue
540 else:
541 parsed_url = urlparse.urlparse(url)
542 scheme = parsed_url[0]
543 if not scheme:
544 # A relative url. Fetch the real base.
545 path = parsed_url[2]
546 if path[0] != "/":
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000547 raise gclient_utils.Error(
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000548 "relative DEPS entry \"%s\" must begin with a slash" % d)
msb@chromium.orge6f78352010-01-13 17:05:33 +0000549 # Create a scm just to query the full url.
550 scm = gclient_scm.CreateSCM(solution["url"], self._root_dir,
551 None)
552 url = scm.FullUrlForRelativeUrl(url)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000553 if d in deps and deps[d] != url:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000554 raise gclient_utils.Error(
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000555 "Solutions have conflicting versions of dependency \"%s\"" % d)
556 if d in solution_urls and solution_urls[d] != url:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000557 raise gclient_utils.Error(
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000558 "Dependency \"%s\" conflicts with specified solution" % d)
559 # Grab the dependency.
560 deps[d] = url
561 return deps
562
phajdan.jr@chromium.org71b40682009-07-31 23:40:09 +0000563 def _RunHookAction(self, hook_dict, matching_file_list):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000564 """Runs the action from a single hook.
565 """
566 command = hook_dict['action'][:]
567 if command[0] == 'python':
568 # If the hook specified "python" as the first item, the action is a
569 # Python script. Run it by starting a new copy of the same
570 # interpreter.
571 command[0] = sys.executable
572
phajdan.jr@chromium.org71b40682009-07-31 23:40:09 +0000573 if '$matching_files' in command:
phajdan.jr@chromium.org68f2e092009-08-06 17:05:35 +0000574 splice_index = command.index('$matching_files')
575 command[splice_index:splice_index + 1] = matching_file_list
phajdan.jr@chromium.org71b40682009-07-31 23:40:09 +0000576
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000577 # Use a discrete exit status code of 2 to indicate that a hook action
578 # failed. Users of this script may wish to treat hook action failures
579 # differently from VC failures.
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000580 gclient_utils.SubprocessCall(command, self._root_dir, fail_status=2)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000581
582 def _RunHooks(self, command, file_list, is_using_git):
583 """Evaluates all hooks, running actions as needed.
584 """
585 # Hooks only run for these command types.
586 if not command in ('update', 'revert', 'runhooks'):
587 return
588
evan@chromium.org67820ef2009-07-27 17:23:00 +0000589 # Hooks only run when --nohooks is not specified
590 if self._options.nohooks:
591 return
592
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000593 # Get any hooks from the .gclient file.
594 hooks = self.GetVar("hooks", [])
595 # Add any hooks found in DEPS files.
596 hooks.extend(self._deps_hooks)
597
598 # If "--force" was specified, run all hooks regardless of what files have
599 # changed. If the user is using git, then we don't know what files have
600 # changed so we always run all hooks.
601 if self._options.force or is_using_git:
602 for hook_dict in hooks:
phajdan.jr@chromium.org71b40682009-07-31 23:40:09 +0000603 self._RunHookAction(hook_dict, [])
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000604 return
605
606 # Run hooks on the basis of whether the files from the gclient operation
607 # match each hook's pattern.
608 for hook_dict in hooks:
609 pattern = re.compile(hook_dict['pattern'])
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000610 matching_file_list = [f for f in file_list if pattern.search(f)]
phajdan.jr@chromium.org71b40682009-07-31 23:40:09 +0000611 if matching_file_list:
612 self._RunHookAction(hook_dict, matching_file_list)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000613
614 def RunOnDeps(self, command, args):
615 """Runs a command on each dependency in a client and its dependencies.
616
617 The module's dependencies are specified in its top-level DEPS files.
618
619 Args:
620 command: The command to use (e.g., 'status' or 'diff')
621 args: list of str - extra arguments to add to the command line.
622
623 Raises:
624 Error: If the client has conflicting entries.
625 """
626 if not command in self.supported_commands:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000627 raise gclient_utils.Error("'%s' is an unsupported command" % command)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000628
629 # Check for revision overrides.
630 revision_overrides = {}
631 for revision in self._options.revisions:
632 if revision.find("@") == -1:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000633 raise gclient_utils.Error(
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000634 "Specify the full dependency when specifying a revision number.")
635 revision_elem = revision.split("@")
636 # Disallow conflicting revs
637 if revision_overrides.has_key(revision_elem[0]) and \
638 revision_overrides[revision_elem[0]] != revision_elem[1]:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000639 raise gclient_utils.Error(
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000640 "Conflicting revision numbers specified.")
641 revision_overrides[revision_elem[0]] = revision_elem[1]
642
643 solutions = self.GetVar("solutions")
644 if not solutions:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000645 raise gclient_utils.Error("No solution specified")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000646
647 # When running runhooks --force, there's no need to consult the SCM.
648 # All known hooks are expected to run unconditionally regardless of working
649 # copy state, so skip the SCM status check.
650 run_scm = not (command == 'runhooks' and self._options.force)
651
652 entries = {}
653 entries_deps_content = {}
654 file_list = []
655 # Run on the base solutions first.
656 for solution in solutions:
657 name = solution["name"]
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000658 deps_file = solution.get("deps_file", self._options.deps_file)
659 if '/' in deps_file or '\\' in deps_file:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000660 raise gclient_utils.Error('deps_file name must not be a path, just a '
661 'filename.')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000662 if name in entries:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000663 raise gclient_utils.Error("solution %s specified more than once" % name)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000664 url = solution["url"]
665 entries[name] = url
yaar@chromium.orgf1328042009-09-22 23:14:23 +0000666 if run_scm and url:
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000667 self._options.revision = revision_overrides.get(name)
msb@chromium.orgcb5442b2009-09-22 16:51:24 +0000668 scm = gclient_scm.CreateSCM(url, self._root_dir, name)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000669 scm.RunCommand(command, self._options, args, file_list)
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000670 file_list = [os.path.join(name, f.strip()) for f in file_list]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000671 self._options.revision = None
672 try:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000673 deps_content = gclient_utils.FileRead(
674 os.path.join(self._root_dir, name, deps_file))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000675 except IOError, e:
676 if e.errno != errno.ENOENT:
677 raise
678 deps_content = ""
679 entries_deps_content[name] = deps_content
680
681 # Process the dependencies next (sort alphanumerically to ensure that
682 # containing directories get populated first and for readability)
683 deps = self._ParseAllDeps(entries, entries_deps_content)
684 deps_to_process = deps.keys()
685 deps_to_process.sort()
686
687 # First pass for direct dependencies.
nasser@codeaurora.org1f7a3d12010-02-04 15:11:50 +0000688 if command == 'update' and not self._options.verbose:
689 pm = Progress('Syncing projects', len(deps_to_process))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000690 for d in deps_to_process:
nasser@codeaurora.org1f7a3d12010-02-04 15:11:50 +0000691 if command == 'update' and not self._options.verbose:
692 pm.update()
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000693 if type(deps[d]) == str:
694 url = deps[d]
695 entries[d] = url
696 if run_scm:
697 self._options.revision = revision_overrides.get(d)
msb@chromium.orgcb5442b2009-09-22 16:51:24 +0000698 scm = gclient_scm.CreateSCM(url, self._root_dir, d)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000699 scm.RunCommand(command, self._options, args, file_list)
700 self._options.revision = None
nasser@codeaurora.org1f7a3d12010-02-04 15:11:50 +0000701 if command == 'update' and not self._options.verbose:
702 pm.end()
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000703
704 # Second pass for inherited deps (via the From keyword)
705 for d in deps_to_process:
706 if type(deps[d]) != str:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000707 filename = os.path.join(self._root_dir,
708 deps[d].module_name,
709 self._options.deps_file)
710 content = gclient_utils.FileRead(filename)
711 sub_deps = self._ParseSolutionDeps(deps[d].module_name, content, {})
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000712 url = sub_deps[d]
713 entries[d] = url
714 if run_scm:
715 self._options.revision = revision_overrides.get(d)
msb@chromium.orgcb5442b2009-09-22 16:51:24 +0000716 scm = gclient_scm.CreateSCM(url, self._root_dir, d)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000717 scm.RunCommand(command, self._options, args, file_list)
718 self._options.revision = None
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000719
phajdan.jr@chromium.orgd83b2b22009-08-11 15:30:55 +0000720 # Convert all absolute paths to relative.
721 for i in range(len(file_list)):
722 # TODO(phajdan.jr): We should know exactly when the paths are absolute.
723 # It depends on the command being executed (like runhooks vs sync).
724 if not os.path.isabs(file_list[i]):
725 continue
726
727 prefix = os.path.commonprefix([self._root_dir.lower(),
728 file_list[i].lower()])
729 file_list[i] = file_list[i][len(prefix):]
730
731 # Strip any leading path separators.
732 while file_list[i].startswith('\\') or file_list[i].startswith('/'):
733 file_list[i] = file_list[i][1:]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000734
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000735 is_using_git = gclient_utils.IsUsingGit(self._root_dir, entries.keys())
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000736 self._RunHooks(command, file_list, is_using_git)
737
738 if command == 'update':
ajwong@chromium.orgcdcee802009-06-23 15:30:42 +0000739 # Notify the user if there is an orphaned entry in their working copy.
740 # Only delete the directory if there are no changes in it, and
741 # delete_unversioned_trees is set to true.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000742 prev_entries = self._ReadEntries()
743 for entry in prev_entries:
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000744 # Fix path separator on Windows.
745 entry_fixed = entry.replace('/', os.path.sep)
746 e_dir = os.path.join(self._root_dir, entry_fixed)
747 # Use entry and not entry_fixed there.
maruel@chromium.org0329e672009-05-13 18:41:04 +0000748 if entry not in entries and os.path.exists(e_dir):
msb@chromium.org83017012009-09-28 18:52:12 +0000749 modified_files = False
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +0000750 if isinstance(prev_entries, list):
msb@chromium.org83017012009-09-28 18:52:12 +0000751 # old .gclient_entries format was list, now dict
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +0000752 modified_files = gclient_scm.scm.SVN.CaptureStatus(e_dir)
msb@chromium.org83017012009-09-28 18:52:12 +0000753 else:
754 file_list = []
755 scm = gclient_scm.CreateSCM(prev_entries[entry], self._root_dir,
756 entry_fixed)
757 scm.status(self._options, [], file_list)
758 modified_files = file_list != []
759 if not self._options.delete_unversioned_trees or modified_files:
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000760 # There are modified files in this entry. Keep warning until
761 # removed.
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000762 print(("\nWARNING: \"%s\" is no longer part of this client. "
763 "It is recommended that you manually remove it.\n") %
764 entry_fixed)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000765 else:
766 # Delete the entry
maruel@chromium.orgdf7a3132009-05-12 17:49:49 +0000767 print("\n________ deleting \'%s\' " +
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000768 "in \'%s\'") % (entry_fixed, self._root_dir)
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000769 gclient_utils.RemoveDirectory(e_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000770 # record the current list of entries for next time
771 self._SaveEntries(entries)
772
773 def PrintRevInfo(self):
774 """Output revision info mapping for the client and its dependencies. This
775 allows the capture of a overall "revision" for the source tree that can
776 be used to reproduce the same tree in the future. The actual output
777 contains enough information (source paths, svn server urls and revisions)
778 that it can be used either to generate external svn commands (without
779 gclient) or as input to gclient's --rev option (with some massaging of
780 the data).
781
782 NOTE: Unlike RunOnDeps this does not require a local checkout and is run
783 on the Pulse master. It MUST NOT execute hooks.
784
785 Raises:
786 Error: If the client has conflicting entries.
787 """
788 # Check for revision overrides.
789 revision_overrides = {}
790 for revision in self._options.revisions:
791 if revision.find("@") < 0:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000792 raise gclient_utils.Error(
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000793 "Specify the full dependency when specifying a revision number.")
794 revision_elem = revision.split("@")
795 # Disallow conflicting revs
796 if revision_overrides.has_key(revision_elem[0]) and \
797 revision_overrides[revision_elem[0]] != revision_elem[1]:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000798 raise gclient_utils.Error(
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000799 "Conflicting revision numbers specified.")
800 revision_overrides[revision_elem[0]] = revision_elem[1]
801
802 solutions = self.GetVar("solutions")
803 if not solutions:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000804 raise gclient_utils.Error("No solution specified")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000805
806 entries = {}
807 entries_deps_content = {}
808
809 # Inner helper to generate base url and rev tuple (including honoring
810 # |revision_overrides|)
811 def GetURLAndRev(name, original_url):
msb@chromium.orgd5a035e2009-11-13 17:58:26 +0000812 url, revision = gclient_utils.SplitUrlRevision(original_url)
msb@chromium.orgac915bb2009-11-13 17:03:01 +0000813 if not revision:
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000814 if revision_overrides.has_key(name):
msb@chromium.orgac915bb2009-11-13 17:03:01 +0000815 return (url, revision_overrides[name])
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000816 else:
msb@chromium.org0f282062009-11-06 20:14:02 +0000817 scm = gclient_scm.CreateSCM(solution["url"], self._root_dir, name)
msb@chromium.orgac915bb2009-11-13 17:03:01 +0000818 return (url, scm.revinfo(self._options, [], None))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000819 else:
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000820 if revision_overrides.has_key(name):
msb@chromium.orgac915bb2009-11-13 17:03:01 +0000821 return (url, revision_overrides[name])
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000822 else:
msb@chromium.orgac915bb2009-11-13 17:03:01 +0000823 return (url, revision)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000824
825 # Run on the base solutions first.
826 for solution in solutions:
827 name = solution["name"]
828 if name in entries:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000829 raise gclient_utils.Error("solution %s specified more than once" % name)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000830 (url, rev) = GetURLAndRev(name, solution["url"])
msb@chromium.org770ff9e2009-09-23 17:18:18 +0000831 entries[name] = "%s@%s" % (url, rev)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000832 # TODO(aharper): SVN/SCMWrapper cleanup (non-local commandset)
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +0000833 entries_deps_content[name] = gclient_scm.scm.SVN.Capture(
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000834 ["cat",
msb@chromium.org770ff9e2009-09-23 17:18:18 +0000835 "%s/%s@%s" % (url,
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000836 self._options.deps_file,
837 rev)],
838 os.getcwd())
839
840 # Process the dependencies next (sort alphanumerically to ensure that
841 # containing directories get populated first and for readability)
842 deps = self._ParseAllDeps(entries, entries_deps_content)
843 deps_to_process = deps.keys()
844 deps_to_process.sort()
845
846 # First pass for direct dependencies.
847 for d in deps_to_process:
848 if type(deps[d]) == str:
849 (url, rev) = GetURLAndRev(d, deps[d])
msb@chromium.org770ff9e2009-09-23 17:18:18 +0000850 entries[d] = "%s@%s" % (url, rev)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000851
852 # Second pass for inherited deps (via the From keyword)
853 for d in deps_to_process:
854 if type(deps[d]) != str:
855 deps_parent_url = entries[deps[d].module_name]
856 if deps_parent_url.find("@") < 0:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000857 raise gclient_utils.Error("From %s missing revisioned url" %
858 deps[d].module_name)
859 content = gclient_utils.FileRead(os.path.join(self._root_dir,
860 deps[d].module_name,
861 self._options.deps_file))
862 sub_deps = self._ParseSolutionDeps(deps[d].module_name, content, {})
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000863 (url, rev) = GetURLAndRev(d, sub_deps[d])
msb@chromium.org770ff9e2009-09-23 17:18:18 +0000864 entries[d] = "%s@%s" % (url, rev)
maruel@chromium.org57e893e2009-08-19 18:12:09 +0000865 print(";\n\n".join(["%s: %s" % (x, entries[x])
866 for x in sorted(entries.keys())]))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000867
868
869## gclient commands.
870
871
872def DoCleanup(options, args):
873 """Handle the cleanup subcommand.
874
875 Raises:
876 Error: if client isn't configured properly.
877 """
maruel@chromium.org2806acc2009-05-15 12:33:34 +0000878 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000879 if not client:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000880 raise gclient_utils.Error("client not configured; see 'gclient config'")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000881 if options.verbose:
882 # Print out the .gclient file. This is longer than if we just printed the
883 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.orgdf7a3132009-05-12 17:49:49 +0000884 print(client.ConfigContent())
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000885 return client.RunOnDeps('cleanup', args)
886
887
888def DoConfig(options, args):
889 """Handle the config subcommand.
890
891 Args:
892 options: If options.spec set, a string providing contents of config file.
893 args: The command line args. If spec is not set,
894 then args[0] is a string URL to get for config file.
895
896 Raises:
897 Error: on usage error
898 """
899 if len(args) < 1 and not options.spec:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000900 raise gclient_utils.Error("required argument missing; see 'gclient help "
901 "config'")
maruel@chromium.org0329e672009-05-13 18:41:04 +0000902 if os.path.exists(options.config_filename):
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000903 raise gclient_utils.Error("%s file already exists in the current directory"
904 % options.config_filename)
maruel@chromium.org2806acc2009-05-15 12:33:34 +0000905 client = GClient('.', options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000906 if options.spec:
907 client.SetConfig(options.spec)
908 else:
909 # TODO(darin): it would be nice to be able to specify an alternate relpath
910 # for the given URL.
maruel@chromium.org1ab7ffc2009-06-03 17:21:37 +0000911 base_url = args[0].rstrip('/')
912 name = base_url.split("/")[-1]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000913 safesync_url = ""
914 if len(args) > 1:
915 safesync_url = args[1]
916 client.SetDefaultConfig(name, base_url, safesync_url)
917 client.SaveConfig()
918
919
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +0000920def DoExport(options, args):
921 """Handle the export subcommand.
phajdan.jr@chromium.org71b40682009-07-31 23:40:09 +0000922
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +0000923 Raises:
924 Error: on usage error
925 """
926 if len(args) != 1:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000927 raise gclient_utils.Error("Need directory name")
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +0000928 client = GClient.LoadCurrentConfig(options)
929
930 if not client:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000931 raise gclient_utils.Error("client not configured; see 'gclient config'")
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +0000932
933 if options.verbose:
934 # Print out the .gclient file. This is longer than if we just printed the
935 # client dict, but more legible, and it might contain helpful comments.
936 print(client.ConfigContent())
937 return client.RunOnDeps('export', args)
938
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000939def DoHelp(options, args):
940 """Handle the help subcommand giving help for another subcommand.
941
942 Raises:
943 Error: if the command is unknown.
944 """
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000945 __pychecker__ = 'unusednames=options'
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000946 if len(args) == 1 and args[0] in COMMAND_USAGE_TEXT:
maruel@chromium.orgdf7a3132009-05-12 17:49:49 +0000947 print(COMMAND_USAGE_TEXT[args[0]])
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000948 else:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000949 raise gclient_utils.Error("unknown subcommand '%s'; see 'gclient help'" %
950 args[0])
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000951
952
kbr@google.comab318592009-09-04 00:54:55 +0000953def DoPack(options, args):
954 """Handle the pack subcommand.
955
956 Raises:
957 Error: if client isn't configured properly.
958 """
959 client = GClient.LoadCurrentConfig(options)
960 if not client:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000961 raise gclient_utils.Error("client not configured; see 'gclient config'")
kbr@google.comab318592009-09-04 00:54:55 +0000962 if options.verbose:
963 # Print out the .gclient file. This is longer than if we just printed the
964 # client dict, but more legible, and it might contain helpful comments.
965 print(client.ConfigContent())
kbr@google.comab318592009-09-04 00:54:55 +0000966 return client.RunOnDeps('pack', args)
967
968
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000969def DoStatus(options, args):
970 """Handle the status subcommand.
971
972 Raises:
973 Error: if client isn't configured properly.
974 """
maruel@chromium.org2806acc2009-05-15 12:33:34 +0000975 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000976 if not client:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000977 raise gclient_utils.Error("client not configured; see 'gclient config'")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000978 if options.verbose:
979 # Print out the .gclient file. This is longer than if we just printed the
980 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.orgdf7a3132009-05-12 17:49:49 +0000981 print(client.ConfigContent())
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000982 return client.RunOnDeps('status', args)
983
984
985def DoUpdate(options, args):
986 """Handle the update and sync subcommands.
987
988 Raises:
989 Error: if client isn't configured properly.
990 """
maruel@chromium.org2806acc2009-05-15 12:33:34 +0000991 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000992
993 if not client:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000994 raise gclient_utils.Error("client not configured; see 'gclient config'")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000995
996 if not options.head:
997 solutions = client.GetVar('solutions')
998 if solutions:
999 for s in solutions:
1000 if s.get('safesync_url', ''):
1001 # rip through revisions and make sure we're not over-riding
1002 # something that was explicitly passed
1003 has_key = False
1004 for r in options.revisions:
1005 if r.split('@')[0] == s['name']:
1006 has_key = True
1007 break
1008
1009 if not has_key:
1010 handle = urllib.urlopen(s['safesync_url'])
1011 rev = handle.read().strip()
1012 handle.close()
1013 if len(rev):
1014 options.revisions.append(s['name']+'@'+rev)
1015
1016 if options.verbose:
1017 # Print out the .gclient file. This is longer than if we just printed the
1018 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.orgdf7a3132009-05-12 17:49:49 +00001019 print(client.ConfigContent())
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001020 return client.RunOnDeps('update', args)
1021
1022
1023def DoDiff(options, args):
1024 """Handle the diff subcommand.
1025
1026 Raises:
1027 Error: if client isn't configured properly.
1028 """
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001029 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001030 if not client:
maruel@chromium.orge3608df2009-11-10 20:22:57 +00001031 raise gclient_utils.Error("client not configured; see 'gclient config'")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001032 if options.verbose:
1033 # Print out the .gclient file. This is longer than if we just printed the
1034 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.orgdf7a3132009-05-12 17:49:49 +00001035 print(client.ConfigContent())
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001036 return client.RunOnDeps('diff', args)
1037
1038
1039def DoRevert(options, args):
1040 """Handle the revert subcommand.
1041
1042 Raises:
1043 Error: if client isn't configured properly.
1044 """
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001045 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001046 if not client:
maruel@chromium.orge3608df2009-11-10 20:22:57 +00001047 raise gclient_utils.Error("client not configured; see 'gclient config'")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001048 return client.RunOnDeps('revert', args)
1049
1050
1051def DoRunHooks(options, args):
1052 """Handle the runhooks subcommand.
1053
1054 Raises:
1055 Error: if client isn't configured properly.
1056 """
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001057 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001058 if not client:
maruel@chromium.orge3608df2009-11-10 20:22:57 +00001059 raise gclient_utils.Error("client not configured; see 'gclient config'")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001060 if options.verbose:
1061 # Print out the .gclient file. This is longer than if we just printed the
1062 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.orgdf7a3132009-05-12 17:49:49 +00001063 print(client.ConfigContent())
maruel@chromium.org5df6a462009-08-28 18:52:26 +00001064 options.force = True
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001065 return client.RunOnDeps('runhooks', args)
1066
1067
1068def DoRevInfo(options, args):
1069 """Handle the revinfo subcommand.
1070
1071 Raises:
1072 Error: if client isn't configured properly.
1073 """
maruel@chromium.orge3608df2009-11-10 20:22:57 +00001074 __pychecker__ = 'unusednames=args'
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001075 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001076 if not client:
maruel@chromium.orge3608df2009-11-10 20:22:57 +00001077 raise gclient_utils.Error("client not configured; see 'gclient config'")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001078 client.PrintRevInfo()
1079
1080
1081gclient_command_map = {
1082 "cleanup": DoCleanup,
1083 "config": DoConfig,
1084 "diff": DoDiff,
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +00001085 "export": DoExport,
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001086 "help": DoHelp,
kbr@google.comab318592009-09-04 00:54:55 +00001087 "pack": DoPack,
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001088 "status": DoStatus,
1089 "sync": DoUpdate,
1090 "update": DoUpdate,
1091 "revert": DoRevert,
1092 "runhooks": DoRunHooks,
1093 "revinfo" : DoRevInfo,
1094}
1095
1096
1097def DispatchCommand(command, options, args, command_map=None):
1098 """Dispatches the appropriate subcommand based on command line arguments."""
1099 if command_map is None:
1100 command_map = gclient_command_map
1101
1102 if command in command_map:
1103 return command_map[command](options, args)
1104 else:
maruel@chromium.orge3608df2009-11-10 20:22:57 +00001105 raise gclient_utils.Error("unknown subcommand '%s'; see 'gclient help'" %
1106 command)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001107
1108
1109def Main(argv):
1110 """Parse command line arguments and dispatch command."""
1111
1112 option_parser = optparse.OptionParser(usage=DEFAULT_USAGE_TEXT,
1113 version=__version__)
1114 option_parser.disable_interspersed_args()
1115 option_parser.add_option("", "--force", action="store_true", default=False,
1116 help=("(update/sync only) force update even "
1117 "for modules which haven't changed"))
evan@chromium.org67820ef2009-07-27 17:23:00 +00001118 option_parser.add_option("", "--nohooks", action="store_true", default=False,
1119 help=("(update/sync/revert only) prevent the hooks from "
1120 "running"))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001121 option_parser.add_option("", "--revision", action="append", dest="revisions",
1122 metavar="REV", default=[],
1123 help=("(update/sync only) sync to a specific "
1124 "revision, can be used multiple times for "
1125 "each solution, e.g. --revision=src@123, "
1126 "--revision=internal@32"))
1127 option_parser.add_option("", "--deps", default=None, dest="deps_os",
1128 metavar="OS_LIST",
1129 help=("(update/sync only) sync deps for the "
1130 "specified (comma-separated) platform(s); "
1131 "'all' will sync all platforms"))
1132 option_parser.add_option("", "--spec", default=None,
1133 help=("(config only) create a gclient file "
1134 "containing the provided string"))
maruel@chromium.orga6220d12010-01-06 21:04:17 +00001135 option_parser.add_option("-v", "--verbose", action="count", default=0,
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001136 help="produce additional output for diagnostics")
maruel@chromium.org7753d242009-10-07 17:40:24 +00001137 option_parser.add_option("", "--manually_grab_svn_rev", action="store_true",
1138 default=False,
1139 help="Skip svn up whenever possible by requesting "
1140 "actual HEAD revision from the repository")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001141 option_parser.add_option("", "--head", action="store_true", default=False,
1142 help=("skips any safesync_urls specified in "
1143 "configured solutions"))
ajwong@chromium.orgcdcee802009-06-23 15:30:42 +00001144 option_parser.add_option("", "--delete_unversioned_trees",
1145 action="store_true", default=False,
1146 help=("on update, delete any unexpected "
1147 "unversioned trees that are in the checkout"))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001148
1149 if len(argv) < 2:
1150 # Users don't need to be told to use the 'help' command.
1151 option_parser.print_help()
1152 return 1
1153 # Add manual support for --version as first argument.
1154 if argv[1] == '--version':
1155 option_parser.print_version()
1156 return 0
1157
1158 # Add manual support for --help as first argument.
1159 if argv[1] == '--help':
1160 argv[1] = 'help'
1161
1162 command = argv[1]
1163 options, args = option_parser.parse_args(argv[2:])
1164
1165 if len(argv) < 3 and command == "help":
1166 option_parser.print_help()
1167 return 0
1168
maruel@chromium.orga6220d12010-01-06 21:04:17 +00001169 if options.verbose > 1:
maruel@chromium.org754960e2009-09-21 12:31:05 +00001170 logging.basicConfig(level=logging.DEBUG)
1171
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001172 # Files used for configuration and state saving.
1173 options.config_filename = os.environ.get("GCLIENT_FILE", ".gclient")
1174 options.entries_filename = ".gclient_entries"
1175 options.deps_file = "DEPS"
1176
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001177 options.platform = sys.platform
1178 return DispatchCommand(command, options, args)
1179
1180
1181if "__main__" == __name__:
1182 try:
1183 result = Main(sys.argv)
maruel@chromium.orge3608df2009-11-10 20:22:57 +00001184 except gclient_utils.Error, e:
maruel@chromium.orgdf7a3132009-05-12 17:49:49 +00001185 print >> sys.stderr, "Error: %s" % str(e)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001186 result = 1
1187 sys.exit(result)
1188
1189# vim: ts=2:sw=2:tw=80:et: