blob: df2085eb950bf3566365983fcf4d548f81baaf26 [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
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000075
76# default help text
77DEFAULT_USAGE_TEXT = (
msb@chromium.orgd6504212010-01-13 17:34:31 +000078"""usage: %prog <subcommand> [options] [--] [SCM options/args...]
79a wrapper for managing a set of svn client modules and/or git repositories.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000080Version """ + __version__ + """
81
82subcommands:
83 cleanup
84 config
85 diff
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +000086 export
kbr@google.comab318592009-09-04 00:54:55 +000087 pack
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000088 revert
89 status
90 sync
91 update
92 runhooks
93 revinfo
94
msb@chromium.orgd6504212010-01-13 17:34:31 +000095Options and extra arguments can be passed to invoked SCM commands by
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000096appending them to the command line. Note that if the first such
97appended option starts with a dash (-) then the options must be
98preceded by -- to distinguish them from gclient options.
99
100For additional help on a subcommand or examples of usage, try
101 %prog help <subcommand>
102 %prog help files
103""")
104
105GENERIC_UPDATE_USAGE_TEXT = (
106 """Perform a checkout/update of the modules specified by the gclient
107configuration; see 'help config'. Unless --revision is specified,
108then the latest revision of the root solutions is checked out, with
109dependent submodule versions updated according to DEPS files.
110If --revision is specified, then the given revision is used in place
111of the latest, either for a single solution or for all solutions.
112Unless the --force option is provided, solutions and modules whose
113local revision matches the one to update (i.e., they have not changed
evan@chromium.org67820ef2009-07-27 17:23:00 +0000114in the repository) are *not* modified. Unless --nohooks is provided,
115the hooks are run.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000116This a synonym for 'gclient %(alias)s'
117
msb@chromium.orgd6504212010-01-13 17:34:31 +0000118usage: gclient %(cmd)s [options] [--] [SCM update options/args]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000119
120Valid options:
estade@chromium.org3b5cba42009-12-01 00:37:08 +0000121 --force : force update even for unchanged modules
122 --nohooks : don't run the hooks after the update is complete
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000123 --revision SOLUTION@REV : update given solution to specified revision
estade@chromium.org3b5cba42009-12-01 00:37:08 +0000124 --deps PLATFORM(S) : sync deps for the given platform(s), or 'all'
125 --verbose : output additional diagnostics
126 --head : update to latest revision, instead of last good revision
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000127
128Examples:
129 gclient %(cmd)s
msb@chromium.orgd6504212010-01-13 17:34:31 +0000130 update files from SCM according to current configuration,
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000131 *for modules which have changed since last update or sync*
132 gclient %(cmd)s --force
msb@chromium.orgd6504212010-01-13 17:34:31 +0000133 update files from SCM according to current configuration, for
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000134 all modules (useful for recovering files deleted from local copy)
estade@chromium.org3b5cba42009-12-01 00:37:08 +0000135 gclient %(cmd)s --revision src@31000
136 update src directory to r31000
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000137""")
138
139COMMAND_USAGE_TEXT = {
140 "cleanup":
141 """Clean up all working copies, using 'svn cleanup' for each module.
142Additional options and args may be passed to 'svn cleanup'.
143
144usage: cleanup [options] [--] [svn cleanup args/options]
145
146Valid options:
147 --verbose : output additional diagnostics
148""",
149 "config": """Create a .gclient file in the current directory; this
150specifies the configuration for further commands. After update/sync,
151top-level DEPS files in each module are read to determine dependent
152modules to operate on as well. If optional [url] parameter is
153provided, then configuration is read from a specified Subversion server
154URL. Otherwise, a --spec option must be provided.
155
156usage: config [option | url] [safesync url]
157
158Valid options:
159 --spec=GCLIENT_SPEC : contents of .gclient are read from string parameter.
160 *Note that due to Cygwin/Python brokenness, it
161 probably can't contain any newlines.*
162
163Examples:
164 gclient config https://gclient.googlecode.com/svn/trunk/gclient
165 configure a new client to check out gclient.py tool sources
166 gclient config --spec='solutions=[{"name":"gclient","""
167 '"url":"https://gclient.googlecode.com/svn/trunk/gclient",'
168 '"custom_deps":{}}]',
169 "diff": """Display the differences between two revisions of modules.
170(Does 'svn diff' for each checked out module and dependences.)
171Additional args and options to 'svn diff' can be passed after
172gclient options.
173
174usage: diff [options] [--] [svn args/options]
175
176Valid options:
177 --verbose : output additional diagnostics
178
179Examples:
180 gclient diff
181 simple 'svn diff' for configured client and dependences
182 gclient diff -- -x -b
183 use 'svn diff -x -b' to suppress whitespace-only differences
184 gclient diff -- -r HEAD -x -b
185 diff versus the latest version of each module
186""",
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +0000187 "export":
188 """Wrapper for svn export for all managed directories
189""",
kbr@google.comab318592009-09-04 00:54:55 +0000190 "pack":
191
192 """Generate a patch which can be applied at the root of the tree.
193Internally, runs 'svn diff' on each checked out module and
194dependencies, and performs minimal postprocessing of the output. The
195resulting patch is printed to stdout and can be applied to a freshly
196checked out tree via 'patch -p0 < patchfile'. Additional args and
197options to 'svn diff' can be passed after gclient options.
198
199usage: pack [options] [--] [svn args/options]
200
201Valid options:
202 --verbose : output additional diagnostics
203
204Examples:
205 gclient pack > patch.txt
206 generate simple patch for configured client and dependences
207 gclient pack -- -x -b > patch.txt
208 generate patch using 'svn diff -x -b' to suppress
209 whitespace-only differences
210 gclient pack -- -r HEAD -x -b > patch.txt
211 generate patch, diffing each file versus the latest version of
212 each module
213""",
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000214 "revert":
215 """Revert every file in every managed directory in the client view.
216
217usage: revert
218""",
219 "status":
220 """Show the status of client and dependent modules, using 'svn diff'
221for each module. Additional options and args may be passed to 'svn diff'.
222
223usage: status [options] [--] [svn diff args/options]
224
225Valid options:
226 --verbose : output additional diagnostics
evan@chromium.org67820ef2009-07-27 17:23:00 +0000227 --nohooks : don't run the hooks after the update is complete
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000228""",
229 "sync": GENERIC_UPDATE_USAGE_TEXT % {"cmd": "sync", "alias": "update"},
230 "update": GENERIC_UPDATE_USAGE_TEXT % {"cmd": "update", "alias": "sync"},
231 "help": """Describe the usage of this program or its subcommands.
232
233usage: help [options] [subcommand]
234
235Valid options:
236 --verbose : output additional diagnostics
237""",
238 "runhooks":
239 """Runs hooks for files that have been modified in the local working copy,
maruel@chromium.org5df6a462009-08-28 18:52:26 +0000240according to 'svn status'. Implies --force.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000241
242usage: runhooks [options]
243
244Valid options:
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000245 --verbose : output additional diagnostics
246""",
247 "revinfo":
248 """Outputs source path, server URL and revision information for every
249dependency in all solutions (no local checkout required).
250
251usage: revinfo [options]
252""",
253}
254
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000255DEFAULT_CLIENT_FILE_TEXT = ("""\
256# An element of this array (a "solution") describes a repository directory
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000257# that will be checked out into your working copy. Each solution may
258# optionally define additional dependencies (via its DEPS file) to be
259# checked out alongside the solution's directory. A solution may also
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000260# specify custom dependencies (via the "custom_deps" property) that
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000261# override or augment the dependencies specified by the DEPS file.
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000262# If a "safesync_url" is specified, it is assumed to reference the location of
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000263# a text file which contains nothing but the last known good SCM revision to
264# sync against. It is fetched if specified and used unless --head is passed
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000265
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000266solutions = [
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000267 { "name" : "%(solution_name)s",
268 "url" : "%(solution_url)s",
269 "custom_deps" : {
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000270 # To use the trunk of a component instead of what's in DEPS:
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000271 #"component": "https://svnserver/component/trunk/",
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000272 # To exclude a component from your working copy:
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000273 #"data/really_large_component": None,
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000274 },
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000275 "safesync_url": "%(safesync_url)s"
276 },
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000277]
278""")
279
280
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000281## GClient implementation.
282
283
284class GClient(object):
285 """Object that represent a gclient checkout."""
286
287 supported_commands = [
kbr@google.comab318592009-09-04 00:54:55 +0000288 'cleanup', 'diff', 'export', 'pack', 'revert', 'status', 'update',
289 'runhooks'
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000290 ]
291
292 def __init__(self, root_dir, options):
293 self._root_dir = root_dir
294 self._options = options
295 self._config_content = None
296 self._config_dict = {}
297 self._deps_hooks = []
298
299 def SetConfig(self, content):
300 self._config_dict = {}
301 self._config_content = content
skylined@chromium.orgdf0032c2009-05-29 10:43:56 +0000302 try:
303 exec(content, self._config_dict)
304 except SyntaxError, e:
305 try:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000306 __pychecker__ = 'no-objattrs'
skylined@chromium.orgdf0032c2009-05-29 10:43:56 +0000307 # Try to construct a human readable error message
308 error_message = [
309 'There is a syntax error in your configuration file.',
310 'Line #%s, character %s:' % (e.lineno, e.offset),
311 '"%s"' % re.sub(r'[\r\n]*$', '', e.text) ]
312 except:
313 # Something went wrong, re-raise the original exception
314 raise e
315 else:
316 # Raise a new exception with the human readable message:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000317 raise gclient_utils.Error('\n'.join(error_message))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000318
319 def SaveConfig(self):
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000320 gclient_utils.FileWrite(os.path.join(self._root_dir,
321 self._options.config_filename),
322 self._config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000323
324 def _LoadConfig(self):
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000325 client_source = gclient_utils.FileRead(
326 os.path.join(self._root_dir, self._options.config_filename))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000327 self.SetConfig(client_source)
328
329 def ConfigContent(self):
330 return self._config_content
331
332 def GetVar(self, key, default=None):
333 return self._config_dict.get(key, default)
334
335 @staticmethod
336 def LoadCurrentConfig(options, from_dir=None):
337 """Searches for and loads a .gclient file relative to the current working
338 dir.
339
340 Returns:
341 A dict representing the contents of the .gclient file or an empty dict if
342 the .gclient file doesn't exist.
343 """
344 if not from_dir:
345 from_dir = os.curdir
346 path = os.path.realpath(from_dir)
maruel@chromium.org0329e672009-05-13 18:41:04 +0000347 while not os.path.exists(os.path.join(path, options.config_filename)):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000348 next = os.path.split(path)
349 if not next[1]:
350 return None
351 path = next[0]
maruel@chromium.org2806acc2009-05-15 12:33:34 +0000352 client = GClient(path, options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000353 client._LoadConfig()
354 return client
355
356 def SetDefaultConfig(self, solution_name, solution_url, safesync_url):
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000357 self.SetConfig(DEFAULT_CLIENT_FILE_TEXT % {
358 'solution_name': solution_name,
359 'solution_url': solution_url,
360 'safesync_url' : safesync_url,
361 })
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000362
363 def _SaveEntries(self, entries):
364 """Creates a .gclient_entries file to record the list of unique checkouts.
365
366 The .gclient_entries file lives in the same directory as .gclient.
367
368 Args:
369 entries: A sequence of solution names.
370 """
msb@chromium.org2e38de72009-09-28 17:04:47 +0000371 text = "entries = \\\n" + pprint.pformat(entries, 2) + '\n'
372 file_path = os.path.join(self._root_dir, self._options.entries_filename)
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000373 gclient_utils.FileWrite(file_path, text)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000374
375 def _ReadEntries(self):
376 """Read the .gclient_entries file for the given client.
377
378 Args:
379 client: The client for which the entries file should be read.
380
381 Returns:
382 A sequence of solution names, which will be empty if there is the
383 entries file hasn't been created yet.
384 """
385 scope = {}
386 filename = os.path.join(self._root_dir, self._options.entries_filename)
maruel@chromium.org0329e672009-05-13 18:41:04 +0000387 if not os.path.exists(filename):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000388 return []
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000389 exec(gclient_utils.FileRead(filename), scope)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000390 return scope["entries"]
391
392 class FromImpl:
393 """Used to implement the From syntax."""
394
395 def __init__(self, module_name):
396 self.module_name = module_name
397
398 def __str__(self):
399 return 'From("%s")' % self.module_name
400
401 class _VarImpl:
402 def __init__(self, custom_vars, local_scope):
403 self._custom_vars = custom_vars
404 self._local_scope = local_scope
405
406 def Lookup(self, var_name):
407 """Implements the Var syntax."""
408 if var_name in self._custom_vars:
409 return self._custom_vars[var_name]
410 elif var_name in self._local_scope.get("vars", {}):
411 return self._local_scope["vars"][var_name]
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000412 raise gclient_utils.Error("Var is not defined: %s" % var_name)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000413
414 def _ParseSolutionDeps(self, solution_name, solution_deps_content,
415 custom_vars):
416 """Parses the DEPS file for the specified solution.
417
418 Args:
419 solution_name: The name of the solution to query.
420 solution_deps_content: Content of the DEPS file for the solution
421 custom_vars: A dict of vars to override any vars defined in the DEPS file.
422
423 Returns:
424 A dict mapping module names (as relative paths) to URLs or an empty
425 dict if the solution does not have a DEPS file.
426 """
427 # Skip empty
428 if not solution_deps_content:
429 return {}
430 # Eval the content
431 local_scope = {}
432 var = self._VarImpl(custom_vars, local_scope)
433 global_scope = {"From": self.FromImpl, "Var": var.Lookup, "deps_os": {}}
434 exec(solution_deps_content, global_scope, local_scope)
435 deps = local_scope.get("deps", {})
436
437 # load os specific dependencies if defined. these dependencies may
438 # override or extend the values defined by the 'deps' member.
439 if "deps_os" in local_scope:
440 deps_os_choices = {
441 "win32": "win",
442 "win": "win",
443 "cygwin": "win",
444 "darwin": "mac",
445 "mac": "mac",
446 "unix": "unix",
447 "linux": "unix",
448 "linux2": "unix",
449 }
450
451 if self._options.deps_os is not None:
452 deps_to_include = self._options.deps_os.split(",")
453 if "all" in deps_to_include:
454 deps_to_include = deps_os_choices.values()
455 else:
456 deps_to_include = [deps_os_choices.get(self._options.platform, "unix")]
457
458 deps_to_include = set(deps_to_include)
459 for deps_os_key in deps_to_include:
460 os_deps = local_scope["deps_os"].get(deps_os_key, {})
461 if len(deps_to_include) > 1:
462 # Ignore any overrides when including deps for more than one
463 # platform, so we collect the broadest set of dependencies available.
464 # We may end up with the wrong revision of something for our
465 # platform, but this is the best we can do.
466 deps.update([x for x in os_deps.items() if not x[0] in deps])
467 else:
468 deps.update(os_deps)
469
470 if 'hooks' in local_scope:
471 self._deps_hooks.extend(local_scope['hooks'])
472
473 # If use_relative_paths is set in the DEPS file, regenerate
474 # the dictionary using paths relative to the directory containing
475 # the DEPS file.
476 if local_scope.get('use_relative_paths'):
477 rel_deps = {}
478 for d, url in deps.items():
479 # normpath is required to allow DEPS to use .. in their
480 # dependency local path.
481 rel_deps[os.path.normpath(os.path.join(solution_name, d))] = url
482 return rel_deps
483 else:
484 return deps
485
486 def _ParseAllDeps(self, solution_urls, solution_deps_content):
487 """Parse the complete list of dependencies for the client.
488
489 Args:
490 solution_urls: A dict mapping module names (as relative paths) to URLs
491 corresponding to the solutions specified by the client. This parameter
492 is passed as an optimization.
493 solution_deps_content: A dict mapping module names to the content
494 of their DEPS files
495
496 Returns:
497 A dict mapping module names (as relative paths) to URLs corresponding
498 to the entire set of dependencies to checkout for the given client.
499
500 Raises:
501 Error: If a dependency conflicts with another dependency or of a solution.
502 """
503 deps = {}
504 for solution in self.GetVar("solutions"):
505 custom_vars = solution.get("custom_vars", {})
506 solution_deps = self._ParseSolutionDeps(
507 solution["name"],
508 solution_deps_content[solution["name"]],
509 custom_vars)
510
511 # If a line is in custom_deps, but not in the solution, we want to append
512 # this line to the solution.
513 if "custom_deps" in solution:
514 for d in solution["custom_deps"]:
515 if d not in solution_deps:
516 solution_deps[d] = solution["custom_deps"][d]
517
518 for d in solution_deps:
519 if "custom_deps" in solution and d in solution["custom_deps"]:
520 # Dependency is overriden.
521 url = solution["custom_deps"][d]
522 if url is None:
523 continue
524 else:
525 url = solution_deps[d]
526 # if we have a From reference dependent on another solution, then
527 # just skip the From reference. When we pull deps for the solution,
528 # we will take care of this dependency.
529 #
530 # If multiple solutions all have the same From reference, then we
531 # should only add one to our list of dependencies.
532 if type(url) != str:
533 if url.module_name in solution_urls:
534 # Already parsed.
535 continue
536 if d in deps and type(deps[d]) != str:
537 if url.module_name == deps[d].module_name:
538 continue
539 else:
540 parsed_url = urlparse.urlparse(url)
541 scheme = parsed_url[0]
542 if not scheme:
543 # A relative url. Fetch the real base.
544 path = parsed_url[2]
545 if path[0] != "/":
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000546 raise gclient_utils.Error(
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000547 "relative DEPS entry \"%s\" must begin with a slash" % d)
msb@chromium.orge6f78352010-01-13 17:05:33 +0000548 # Create a scm just to query the full url.
549 scm = gclient_scm.CreateSCM(solution["url"], self._root_dir,
550 None)
551 url = scm.FullUrlForRelativeUrl(url)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000552 if d in deps and deps[d] != url:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000553 raise gclient_utils.Error(
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000554 "Solutions have conflicting versions of dependency \"%s\"" % d)
555 if d in solution_urls and solution_urls[d] != url:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000556 raise gclient_utils.Error(
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000557 "Dependency \"%s\" conflicts with specified solution" % d)
558 # Grab the dependency.
559 deps[d] = url
560 return deps
561
phajdan.jr@chromium.org71b40682009-07-31 23:40:09 +0000562 def _RunHookAction(self, hook_dict, matching_file_list):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000563 """Runs the action from a single hook.
564 """
565 command = hook_dict['action'][:]
566 if command[0] == 'python':
567 # If the hook specified "python" as the first item, the action is a
568 # Python script. Run it by starting a new copy of the same
569 # interpreter.
570 command[0] = sys.executable
571
phajdan.jr@chromium.org71b40682009-07-31 23:40:09 +0000572 if '$matching_files' in command:
phajdan.jr@chromium.org68f2e092009-08-06 17:05:35 +0000573 splice_index = command.index('$matching_files')
574 command[splice_index:splice_index + 1] = matching_file_list
phajdan.jr@chromium.org71b40682009-07-31 23:40:09 +0000575
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000576 # Use a discrete exit status code of 2 to indicate that a hook action
577 # failed. Users of this script may wish to treat hook action failures
578 # differently from VC failures.
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000579 gclient_utils.SubprocessCall(command, self._root_dir, fail_status=2)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000580
581 def _RunHooks(self, command, file_list, is_using_git):
582 """Evaluates all hooks, running actions as needed.
583 """
584 # Hooks only run for these command types.
585 if not command in ('update', 'revert', 'runhooks'):
586 return
587
evan@chromium.org67820ef2009-07-27 17:23:00 +0000588 # Hooks only run when --nohooks is not specified
589 if self._options.nohooks:
590 return
591
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000592 # Get any hooks from the .gclient file.
593 hooks = self.GetVar("hooks", [])
594 # Add any hooks found in DEPS files.
595 hooks.extend(self._deps_hooks)
596
597 # If "--force" was specified, run all hooks regardless of what files have
598 # changed. If the user is using git, then we don't know what files have
599 # changed so we always run all hooks.
600 if self._options.force or is_using_git:
601 for hook_dict in hooks:
phajdan.jr@chromium.org71b40682009-07-31 23:40:09 +0000602 self._RunHookAction(hook_dict, [])
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000603 return
604
605 # Run hooks on the basis of whether the files from the gclient operation
606 # match each hook's pattern.
607 for hook_dict in hooks:
608 pattern = re.compile(hook_dict['pattern'])
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000609 matching_file_list = [f for f in file_list if pattern.search(f)]
phajdan.jr@chromium.org71b40682009-07-31 23:40:09 +0000610 if matching_file_list:
611 self._RunHookAction(hook_dict, matching_file_list)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000612
613 def RunOnDeps(self, command, args):
614 """Runs a command on each dependency in a client and its dependencies.
615
616 The module's dependencies are specified in its top-level DEPS files.
617
618 Args:
619 command: The command to use (e.g., 'status' or 'diff')
620 args: list of str - extra arguments to add to the command line.
621
622 Raises:
623 Error: If the client has conflicting entries.
624 """
625 if not command in self.supported_commands:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000626 raise gclient_utils.Error("'%s' is an unsupported command" % command)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000627
628 # Check for revision overrides.
629 revision_overrides = {}
630 for revision in self._options.revisions:
631 if revision.find("@") == -1:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000632 raise gclient_utils.Error(
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000633 "Specify the full dependency when specifying a revision number.")
634 revision_elem = revision.split("@")
635 # Disallow conflicting revs
636 if revision_overrides.has_key(revision_elem[0]) and \
637 revision_overrides[revision_elem[0]] != revision_elem[1]:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000638 raise gclient_utils.Error(
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000639 "Conflicting revision numbers specified.")
640 revision_overrides[revision_elem[0]] = revision_elem[1]
641
642 solutions = self.GetVar("solutions")
643 if not solutions:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000644 raise gclient_utils.Error("No solution specified")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000645
646 # When running runhooks --force, there's no need to consult the SCM.
647 # All known hooks are expected to run unconditionally regardless of working
648 # copy state, so skip the SCM status check.
649 run_scm = not (command == 'runhooks' and self._options.force)
650
651 entries = {}
652 entries_deps_content = {}
653 file_list = []
654 # Run on the base solutions first.
655 for solution in solutions:
656 name = solution["name"]
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000657 deps_file = solution.get("deps_file", self._options.deps_file)
658 if '/' in deps_file or '\\' in deps_file:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000659 raise gclient_utils.Error('deps_file name must not be a path, just a '
660 'filename.')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000661 if name in entries:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000662 raise gclient_utils.Error("solution %s specified more than once" % name)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000663 url = solution["url"]
664 entries[name] = url
yaar@chromium.orgf1328042009-09-22 23:14:23 +0000665 if run_scm and url:
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000666 self._options.revision = revision_overrides.get(name)
msb@chromium.orgcb5442b2009-09-22 16:51:24 +0000667 scm = gclient_scm.CreateSCM(url, self._root_dir, name)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000668 scm.RunCommand(command, self._options, args, file_list)
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000669 file_list = [os.path.join(name, f.strip()) for f in file_list]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000670 self._options.revision = None
671 try:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000672 deps_content = gclient_utils.FileRead(
673 os.path.join(self._root_dir, name, deps_file))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000674 except IOError, e:
675 if e.errno != errno.ENOENT:
676 raise
677 deps_content = ""
678 entries_deps_content[name] = deps_content
679
680 # Process the dependencies next (sort alphanumerically to ensure that
681 # containing directories get populated first and for readability)
682 deps = self._ParseAllDeps(entries, entries_deps_content)
683 deps_to_process = deps.keys()
684 deps_to_process.sort()
685
686 # First pass for direct dependencies.
687 for d in deps_to_process:
688 if type(deps[d]) == str:
689 url = deps[d]
690 entries[d] = url
691 if run_scm:
692 self._options.revision = revision_overrides.get(d)
msb@chromium.orgcb5442b2009-09-22 16:51:24 +0000693 scm = gclient_scm.CreateSCM(url, self._root_dir, d)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000694 scm.RunCommand(command, self._options, args, file_list)
695 self._options.revision = None
696
697 # Second pass for inherited deps (via the From keyword)
698 for d in deps_to_process:
699 if type(deps[d]) != str:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000700 filename = os.path.join(self._root_dir,
701 deps[d].module_name,
702 self._options.deps_file)
703 content = gclient_utils.FileRead(filename)
704 sub_deps = self._ParseSolutionDeps(deps[d].module_name, content, {})
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000705 url = sub_deps[d]
706 entries[d] = url
707 if run_scm:
708 self._options.revision = revision_overrides.get(d)
msb@chromium.orgcb5442b2009-09-22 16:51:24 +0000709 scm = gclient_scm.CreateSCM(url, self._root_dir, d)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000710 scm.RunCommand(command, self._options, args, file_list)
711 self._options.revision = None
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000712
phajdan.jr@chromium.orgd83b2b22009-08-11 15:30:55 +0000713 # Convert all absolute paths to relative.
714 for i in range(len(file_list)):
715 # TODO(phajdan.jr): We should know exactly when the paths are absolute.
716 # It depends on the command being executed (like runhooks vs sync).
717 if not os.path.isabs(file_list[i]):
718 continue
719
720 prefix = os.path.commonprefix([self._root_dir.lower(),
721 file_list[i].lower()])
722 file_list[i] = file_list[i][len(prefix):]
723
724 # Strip any leading path separators.
725 while file_list[i].startswith('\\') or file_list[i].startswith('/'):
726 file_list[i] = file_list[i][1:]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000727
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000728 is_using_git = gclient_utils.IsUsingGit(self._root_dir, entries.keys())
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000729 self._RunHooks(command, file_list, is_using_git)
730
731 if command == 'update':
ajwong@chromium.orgcdcee802009-06-23 15:30:42 +0000732 # Notify the user if there is an orphaned entry in their working copy.
733 # Only delete the directory if there are no changes in it, and
734 # delete_unversioned_trees is set to true.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000735 prev_entries = self._ReadEntries()
736 for entry in prev_entries:
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000737 # Fix path separator on Windows.
738 entry_fixed = entry.replace('/', os.path.sep)
739 e_dir = os.path.join(self._root_dir, entry_fixed)
740 # Use entry and not entry_fixed there.
maruel@chromium.org0329e672009-05-13 18:41:04 +0000741 if entry not in entries and os.path.exists(e_dir):
msb@chromium.org83017012009-09-28 18:52:12 +0000742 modified_files = False
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +0000743 if isinstance(prev_entries, list):
msb@chromium.org83017012009-09-28 18:52:12 +0000744 # old .gclient_entries format was list, now dict
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +0000745 modified_files = gclient_scm.scm.SVN.CaptureStatus(e_dir)
msb@chromium.org83017012009-09-28 18:52:12 +0000746 else:
747 file_list = []
748 scm = gclient_scm.CreateSCM(prev_entries[entry], self._root_dir,
749 entry_fixed)
750 scm.status(self._options, [], file_list)
751 modified_files = file_list != []
752 if not self._options.delete_unversioned_trees or modified_files:
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000753 # There are modified files in this entry. Keep warning until
754 # removed.
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000755 print(("\nWARNING: \"%s\" is no longer part of this client. "
756 "It is recommended that you manually remove it.\n") %
757 entry_fixed)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000758 else:
759 # Delete the entry
maruel@chromium.orgdf7a3132009-05-12 17:49:49 +0000760 print("\n________ deleting \'%s\' " +
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000761 "in \'%s\'") % (entry_fixed, self._root_dir)
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000762 gclient_utils.RemoveDirectory(e_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000763 # record the current list of entries for next time
764 self._SaveEntries(entries)
765
766 def PrintRevInfo(self):
767 """Output revision info mapping for the client and its dependencies. This
768 allows the capture of a overall "revision" for the source tree that can
769 be used to reproduce the same tree in the future. The actual output
770 contains enough information (source paths, svn server urls and revisions)
771 that it can be used either to generate external svn commands (without
772 gclient) or as input to gclient's --rev option (with some massaging of
773 the data).
774
775 NOTE: Unlike RunOnDeps this does not require a local checkout and is run
776 on the Pulse master. It MUST NOT execute hooks.
777
778 Raises:
779 Error: If the client has conflicting entries.
780 """
781 # Check for revision overrides.
782 revision_overrides = {}
783 for revision in self._options.revisions:
784 if revision.find("@") < 0:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000785 raise gclient_utils.Error(
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000786 "Specify the full dependency when specifying a revision number.")
787 revision_elem = revision.split("@")
788 # Disallow conflicting revs
789 if revision_overrides.has_key(revision_elem[0]) and \
790 revision_overrides[revision_elem[0]] != revision_elem[1]:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000791 raise gclient_utils.Error(
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000792 "Conflicting revision numbers specified.")
793 revision_overrides[revision_elem[0]] = revision_elem[1]
794
795 solutions = self.GetVar("solutions")
796 if not solutions:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000797 raise gclient_utils.Error("No solution specified")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000798
799 entries = {}
800 entries_deps_content = {}
801
802 # Inner helper to generate base url and rev tuple (including honoring
803 # |revision_overrides|)
804 def GetURLAndRev(name, original_url):
msb@chromium.orgd5a035e2009-11-13 17:58:26 +0000805 url, revision = gclient_utils.SplitUrlRevision(original_url)
msb@chromium.orgac915bb2009-11-13 17:03:01 +0000806 if not revision:
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000807 if revision_overrides.has_key(name):
msb@chromium.orgac915bb2009-11-13 17:03:01 +0000808 return (url, revision_overrides[name])
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000809 else:
msb@chromium.org0f282062009-11-06 20:14:02 +0000810 scm = gclient_scm.CreateSCM(solution["url"], self._root_dir, name)
msb@chromium.orgac915bb2009-11-13 17:03:01 +0000811 return (url, scm.revinfo(self._options, [], None))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000812 else:
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000813 if revision_overrides.has_key(name):
msb@chromium.orgac915bb2009-11-13 17:03:01 +0000814 return (url, revision_overrides[name])
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000815 else:
msb@chromium.orgac915bb2009-11-13 17:03:01 +0000816 return (url, revision)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000817
818 # Run on the base solutions first.
819 for solution in solutions:
820 name = solution["name"]
821 if name in entries:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000822 raise gclient_utils.Error("solution %s specified more than once" % name)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000823 (url, rev) = GetURLAndRev(name, solution["url"])
msb@chromium.org770ff9e2009-09-23 17:18:18 +0000824 entries[name] = "%s@%s" % (url, rev)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000825 # TODO(aharper): SVN/SCMWrapper cleanup (non-local commandset)
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +0000826 entries_deps_content[name] = gclient_scm.scm.SVN.Capture(
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000827 ["cat",
msb@chromium.org770ff9e2009-09-23 17:18:18 +0000828 "%s/%s@%s" % (url,
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000829 self._options.deps_file,
830 rev)],
831 os.getcwd())
832
833 # Process the dependencies next (sort alphanumerically to ensure that
834 # containing directories get populated first and for readability)
835 deps = self._ParseAllDeps(entries, entries_deps_content)
836 deps_to_process = deps.keys()
837 deps_to_process.sort()
838
839 # First pass for direct dependencies.
840 for d in deps_to_process:
841 if type(deps[d]) == str:
842 (url, rev) = GetURLAndRev(d, deps[d])
msb@chromium.org770ff9e2009-09-23 17:18:18 +0000843 entries[d] = "%s@%s" % (url, rev)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000844
845 # Second pass for inherited deps (via the From keyword)
846 for d in deps_to_process:
847 if type(deps[d]) != str:
848 deps_parent_url = entries[deps[d].module_name]
849 if deps_parent_url.find("@") < 0:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000850 raise gclient_utils.Error("From %s missing revisioned url" %
851 deps[d].module_name)
852 content = gclient_utils.FileRead(os.path.join(self._root_dir,
853 deps[d].module_name,
854 self._options.deps_file))
855 sub_deps = self._ParseSolutionDeps(deps[d].module_name, content, {})
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000856 (url, rev) = GetURLAndRev(d, sub_deps[d])
msb@chromium.org770ff9e2009-09-23 17:18:18 +0000857 entries[d] = "%s@%s" % (url, rev)
maruel@chromium.org57e893e2009-08-19 18:12:09 +0000858 print(";\n\n".join(["%s: %s" % (x, entries[x])
859 for x in sorted(entries.keys())]))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000860
861
862## gclient commands.
863
864
865def DoCleanup(options, args):
866 """Handle the cleanup subcommand.
867
868 Raises:
869 Error: if client isn't configured properly.
870 """
maruel@chromium.org2806acc2009-05-15 12:33:34 +0000871 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000872 if not client:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000873 raise gclient_utils.Error("client not configured; see 'gclient config'")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000874 if options.verbose:
875 # Print out the .gclient file. This is longer than if we just printed the
876 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.orgdf7a3132009-05-12 17:49:49 +0000877 print(client.ConfigContent())
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000878 return client.RunOnDeps('cleanup', args)
879
880
881def DoConfig(options, args):
882 """Handle the config subcommand.
883
884 Args:
885 options: If options.spec set, a string providing contents of config file.
886 args: The command line args. If spec is not set,
887 then args[0] is a string URL to get for config file.
888
889 Raises:
890 Error: on usage error
891 """
892 if len(args) < 1 and not options.spec:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000893 raise gclient_utils.Error("required argument missing; see 'gclient help "
894 "config'")
maruel@chromium.org0329e672009-05-13 18:41:04 +0000895 if os.path.exists(options.config_filename):
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000896 raise gclient_utils.Error("%s file already exists in the current directory"
897 % options.config_filename)
maruel@chromium.org2806acc2009-05-15 12:33:34 +0000898 client = GClient('.', options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000899 if options.spec:
900 client.SetConfig(options.spec)
901 else:
902 # TODO(darin): it would be nice to be able to specify an alternate relpath
903 # for the given URL.
maruel@chromium.org1ab7ffc2009-06-03 17:21:37 +0000904 base_url = args[0].rstrip('/')
905 name = base_url.split("/")[-1]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000906 safesync_url = ""
907 if len(args) > 1:
908 safesync_url = args[1]
909 client.SetDefaultConfig(name, base_url, safesync_url)
910 client.SaveConfig()
911
912
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +0000913def DoExport(options, args):
914 """Handle the export subcommand.
phajdan.jr@chromium.org71b40682009-07-31 23:40:09 +0000915
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +0000916 Raises:
917 Error: on usage error
918 """
919 if len(args) != 1:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000920 raise gclient_utils.Error("Need directory name")
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +0000921 client = GClient.LoadCurrentConfig(options)
922
923 if not client:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000924 raise gclient_utils.Error("client not configured; see 'gclient config'")
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +0000925
926 if options.verbose:
927 # Print out the .gclient file. This is longer than if we just printed the
928 # client dict, but more legible, and it might contain helpful comments.
929 print(client.ConfigContent())
930 return client.RunOnDeps('export', args)
931
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000932def DoHelp(options, args):
933 """Handle the help subcommand giving help for another subcommand.
934
935 Raises:
936 Error: if the command is unknown.
937 """
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000938 __pychecker__ = 'unusednames=options'
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000939 if len(args) == 1 and args[0] in COMMAND_USAGE_TEXT:
maruel@chromium.orgdf7a3132009-05-12 17:49:49 +0000940 print(COMMAND_USAGE_TEXT[args[0]])
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000941 else:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000942 raise gclient_utils.Error("unknown subcommand '%s'; see 'gclient help'" %
943 args[0])
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000944
945
kbr@google.comab318592009-09-04 00:54:55 +0000946def DoPack(options, args):
947 """Handle the pack subcommand.
948
949 Raises:
950 Error: if client isn't configured properly.
951 """
952 client = GClient.LoadCurrentConfig(options)
953 if not client:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000954 raise gclient_utils.Error("client not configured; see 'gclient config'")
kbr@google.comab318592009-09-04 00:54:55 +0000955 if options.verbose:
956 # Print out the .gclient file. This is longer than if we just printed the
957 # client dict, but more legible, and it might contain helpful comments.
958 print(client.ConfigContent())
kbr@google.comab318592009-09-04 00:54:55 +0000959 return client.RunOnDeps('pack', args)
960
961
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000962def DoStatus(options, args):
963 """Handle the status subcommand.
964
965 Raises:
966 Error: if client isn't configured properly.
967 """
maruel@chromium.org2806acc2009-05-15 12:33:34 +0000968 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000969 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 if options.verbose:
972 # Print out the .gclient file. This is longer than if we just printed the
973 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.orgdf7a3132009-05-12 17:49:49 +0000974 print(client.ConfigContent())
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000975 return client.RunOnDeps('status', args)
976
977
978def DoUpdate(options, args):
979 """Handle the update and sync subcommands.
980
981 Raises:
982 Error: if client isn't configured properly.
983 """
maruel@chromium.org2806acc2009-05-15 12:33:34 +0000984 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000985
986 if not client:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000987 raise gclient_utils.Error("client not configured; see 'gclient config'")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000988
989 if not options.head:
990 solutions = client.GetVar('solutions')
991 if solutions:
992 for s in solutions:
993 if s.get('safesync_url', ''):
994 # rip through revisions and make sure we're not over-riding
995 # something that was explicitly passed
996 has_key = False
997 for r in options.revisions:
998 if r.split('@')[0] == s['name']:
999 has_key = True
1000 break
1001
1002 if not has_key:
1003 handle = urllib.urlopen(s['safesync_url'])
1004 rev = handle.read().strip()
1005 handle.close()
1006 if len(rev):
1007 options.revisions.append(s['name']+'@'+rev)
1008
1009 if options.verbose:
1010 # Print out the .gclient file. This is longer than if we just printed the
1011 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.orgdf7a3132009-05-12 17:49:49 +00001012 print(client.ConfigContent())
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001013 return client.RunOnDeps('update', args)
1014
1015
1016def DoDiff(options, args):
1017 """Handle the diff subcommand.
1018
1019 Raises:
1020 Error: if client isn't configured properly.
1021 """
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001022 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001023 if not client:
maruel@chromium.orge3608df2009-11-10 20:22:57 +00001024 raise gclient_utils.Error("client not configured; see 'gclient config'")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001025 if options.verbose:
1026 # Print out the .gclient file. This is longer than if we just printed the
1027 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.orgdf7a3132009-05-12 17:49:49 +00001028 print(client.ConfigContent())
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001029 return client.RunOnDeps('diff', args)
1030
1031
1032def DoRevert(options, args):
1033 """Handle the revert subcommand.
1034
1035 Raises:
1036 Error: if client isn't configured properly.
1037 """
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001038 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001039 if not client:
maruel@chromium.orge3608df2009-11-10 20:22:57 +00001040 raise gclient_utils.Error("client not configured; see 'gclient config'")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001041 return client.RunOnDeps('revert', args)
1042
1043
1044def DoRunHooks(options, args):
1045 """Handle the runhooks subcommand.
1046
1047 Raises:
1048 Error: if client isn't configured properly.
1049 """
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001050 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001051 if not client:
maruel@chromium.orge3608df2009-11-10 20:22:57 +00001052 raise gclient_utils.Error("client not configured; see 'gclient config'")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001053 if options.verbose:
1054 # Print out the .gclient file. This is longer than if we just printed the
1055 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.orgdf7a3132009-05-12 17:49:49 +00001056 print(client.ConfigContent())
maruel@chromium.org5df6a462009-08-28 18:52:26 +00001057 options.force = True
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001058 return client.RunOnDeps('runhooks', args)
1059
1060
1061def DoRevInfo(options, args):
1062 """Handle the revinfo subcommand.
1063
1064 Raises:
1065 Error: if client isn't configured properly.
1066 """
maruel@chromium.orge3608df2009-11-10 20:22:57 +00001067 __pychecker__ = 'unusednames=args'
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001068 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001069 if not client:
maruel@chromium.orge3608df2009-11-10 20:22:57 +00001070 raise gclient_utils.Error("client not configured; see 'gclient config'")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001071 client.PrintRevInfo()
1072
1073
1074gclient_command_map = {
1075 "cleanup": DoCleanup,
1076 "config": DoConfig,
1077 "diff": DoDiff,
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +00001078 "export": DoExport,
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001079 "help": DoHelp,
kbr@google.comab318592009-09-04 00:54:55 +00001080 "pack": DoPack,
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001081 "status": DoStatus,
1082 "sync": DoUpdate,
1083 "update": DoUpdate,
1084 "revert": DoRevert,
1085 "runhooks": DoRunHooks,
1086 "revinfo" : DoRevInfo,
1087}
1088
1089
1090def DispatchCommand(command, options, args, command_map=None):
1091 """Dispatches the appropriate subcommand based on command line arguments."""
1092 if command_map is None:
1093 command_map = gclient_command_map
1094
1095 if command in command_map:
1096 return command_map[command](options, args)
1097 else:
maruel@chromium.orge3608df2009-11-10 20:22:57 +00001098 raise gclient_utils.Error("unknown subcommand '%s'; see 'gclient help'" %
1099 command)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001100
1101
1102def Main(argv):
1103 """Parse command line arguments and dispatch command."""
1104
1105 option_parser = optparse.OptionParser(usage=DEFAULT_USAGE_TEXT,
1106 version=__version__)
1107 option_parser.disable_interspersed_args()
1108 option_parser.add_option("", "--force", action="store_true", default=False,
1109 help=("(update/sync only) force update even "
1110 "for modules which haven't changed"))
evan@chromium.org67820ef2009-07-27 17:23:00 +00001111 option_parser.add_option("", "--nohooks", action="store_true", default=False,
1112 help=("(update/sync/revert only) prevent the hooks from "
1113 "running"))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001114 option_parser.add_option("", "--revision", action="append", dest="revisions",
1115 metavar="REV", default=[],
1116 help=("(update/sync only) sync to a specific "
1117 "revision, can be used multiple times for "
1118 "each solution, e.g. --revision=src@123, "
1119 "--revision=internal@32"))
1120 option_parser.add_option("", "--deps", default=None, dest="deps_os",
1121 metavar="OS_LIST",
1122 help=("(update/sync only) sync deps for the "
1123 "specified (comma-separated) platform(s); "
1124 "'all' will sync all platforms"))
1125 option_parser.add_option("", "--spec", default=None,
1126 help=("(config only) create a gclient file "
1127 "containing the provided string"))
maruel@chromium.orga6220d12010-01-06 21:04:17 +00001128 option_parser.add_option("-v", "--verbose", action="count", default=0,
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001129 help="produce additional output for diagnostics")
maruel@chromium.org7753d242009-10-07 17:40:24 +00001130 option_parser.add_option("", "--manually_grab_svn_rev", action="store_true",
1131 default=False,
1132 help="Skip svn up whenever possible by requesting "
1133 "actual HEAD revision from the repository")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001134 option_parser.add_option("", "--head", action="store_true", default=False,
1135 help=("skips any safesync_urls specified in "
1136 "configured solutions"))
ajwong@chromium.orgcdcee802009-06-23 15:30:42 +00001137 option_parser.add_option("", "--delete_unversioned_trees",
1138 action="store_true", default=False,
1139 help=("on update, delete any unexpected "
1140 "unversioned trees that are in the checkout"))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001141
1142 if len(argv) < 2:
1143 # Users don't need to be told to use the 'help' command.
1144 option_parser.print_help()
1145 return 1
1146 # Add manual support for --version as first argument.
1147 if argv[1] == '--version':
1148 option_parser.print_version()
1149 return 0
1150
1151 # Add manual support for --help as first argument.
1152 if argv[1] == '--help':
1153 argv[1] = 'help'
1154
1155 command = argv[1]
1156 options, args = option_parser.parse_args(argv[2:])
1157
1158 if len(argv) < 3 and command == "help":
1159 option_parser.print_help()
1160 return 0
1161
maruel@chromium.orga6220d12010-01-06 21:04:17 +00001162 if options.verbose > 1:
maruel@chromium.org754960e2009-09-21 12:31:05 +00001163 logging.basicConfig(level=logging.DEBUG)
1164
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001165 # Files used for configuration and state saving.
1166 options.config_filename = os.environ.get("GCLIENT_FILE", ".gclient")
1167 options.entries_filename = ".gclient_entries"
1168 options.deps_file = "DEPS"
1169
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001170 options.platform = sys.platform
1171 return DispatchCommand(command, options, args)
1172
1173
1174if "__main__" == __name__:
1175 try:
1176 result = Main(sys.argv)
maruel@chromium.orge3608df2009-11-10 20:22:57 +00001177 except gclient_utils.Error, e:
maruel@chromium.orgdf7a3132009-05-12 17:49:49 +00001178 print >> sys.stderr, "Error: %s" % str(e)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001179 result = 1
1180 sys.exit(result)
1181
1182# vim: ts=2:sw=2:tw=80:et: