blob: 65666022f35396b416046f67a76efa6b160d698e [file] [log] [blame]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001#!/usr/bin/python
2#
3# Copyright 2008 Google Inc. All Rights Reserved.
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16
17"""A wrapper script to manage a set of client modules in different SCM.
18
19This script is intended to be used to help basic management of client
20program sources residing in one or more Subversion modules, along with
21other modules it depends on, also in Subversion, but possibly on
22multiple respositories, making a wrapper system apparently necessary.
23
24Files
25 .gclient : Current client configuration, written by 'config' command.
26 Format is a Python script defining 'solutions', a list whose
27 entries each are maps binding the strings "name" and "url"
28 to strings specifying the name and location of the client
29 module, as well as "custom_deps" to a map similar to the DEPS
30 file below.
31 .gclient_entries : A cache constructed by 'update' command. Format is a
32 Python script defining 'entries', a list of the names
33 of all modules in the client
34 <module>/DEPS : Python script defining var 'deps' as a map from each requisite
35 submodule name to a URL where it can be found (via one SCM)
36
37Hooks
38 .gclient and DEPS files may optionally contain a list named "hooks" to
39 allow custom actions to be performed based on files that have changed in the
evan@chromium.org67820ef2009-07-27 17:23:00 +000040 working copy as a result of a "sync"/"update" or "revert" operation. This
41 could be prevented by using --nohooks (hooks run by default). Hooks can also
maruel@chromium.org5df6a462009-08-28 18:52:26 +000042 be forced to run with the "runhooks" operation. If "sync" is run with
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000043 --force, all known hooks will run regardless of the state of the working
44 copy.
45
46 Each item in a "hooks" list is a dict, containing these two keys:
47 "pattern" The associated value is a string containing a regular
48 expression. When a file whose pathname matches the expression
49 is checked out, updated, or reverted, the hook's "action" will
50 run.
51 "action" A list describing a command to run along with its arguments, if
52 any. An action command will run at most one time per gclient
53 invocation, regardless of how many files matched the pattern.
54 The action is executed in the same directory as the .gclient
55 file. If the first item in the list is the string "python",
56 the current Python interpreter (sys.executable) will be used
phajdan.jr@chromium.org71b40682009-07-31 23:40:09 +000057 to run the command. If the list contains string "$matching_files"
58 it will be removed from the list and the list will be extended
59 by the list of matching files.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000060
61 Example:
62 hooks = [
63 { "pattern": "\\.(gif|jpe?g|pr0n|png)$",
64 "action": ["python", "image_indexer.py", "--all"]},
65 ]
66"""
67
68__author__ = "darinf@gmail.com (Darin Fisher)"
maruel@chromium.org5df6a462009-08-28 18:52:26 +000069__version__ = "0.3.3"
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000070
71import errno
maruel@chromium.org754960e2009-09-21 12:31:05 +000072import logging
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000073import optparse
74import os
msb@chromium.org2e38de72009-09-28 17:04:47 +000075import pprint
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000076import re
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000077import sys
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000078import urlparse
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000079import urllib
80
maruel@chromium.org5f3eee32009-09-17 00:34:30 +000081import gclient_scm
82import gclient_utils
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000083
84# default help text
85DEFAULT_USAGE_TEXT = (
86"""usage: %prog <subcommand> [options] [--] [svn options/args...]
87a wrapper for managing a set of client modules in svn.
88Version """ + __version__ + """
89
90subcommands:
91 cleanup
92 config
93 diff
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +000094 export
kbr@google.comab318592009-09-04 00:54:55 +000095 pack
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000096 revert
97 status
98 sync
99 update
100 runhooks
101 revinfo
102
103Options and extra arguments can be passed to invoked svn commands by
104appending them to the command line. Note that if the first such
105appended option starts with a dash (-) then the options must be
106preceded by -- to distinguish them from gclient options.
107
108For additional help on a subcommand or examples of usage, try
109 %prog help <subcommand>
110 %prog help files
111""")
112
113GENERIC_UPDATE_USAGE_TEXT = (
114 """Perform a checkout/update of the modules specified by the gclient
115configuration; see 'help config'. Unless --revision is specified,
116then the latest revision of the root solutions is checked out, with
117dependent submodule versions updated according to DEPS files.
118If --revision is specified, then the given revision is used in place
119of the latest, either for a single solution or for all solutions.
120Unless the --force option is provided, solutions and modules whose
121local revision matches the one to update (i.e., they have not changed
evan@chromium.org67820ef2009-07-27 17:23:00 +0000122in the repository) are *not* modified. Unless --nohooks is provided,
123the hooks are run.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000124This a synonym for 'gclient %(alias)s'
125
126usage: gclient %(cmd)s [options] [--] [svn update options/args]
127
128Valid options:
129 --force : force update even for unchanged modules
evan@chromium.org67820ef2009-07-27 17:23:00 +0000130 --nohooks : don't run the hooks after the update is complete
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000131 --revision REV : update/checkout all solutions with specified revision
132 --revision SOLUTION@REV : update given solution to specified revision
133 --deps PLATFORM(S) : sync deps for the given platform(s), or 'all'
134 --verbose : output additional diagnostics
maruel@chromium.orgb8b6b872009-06-30 18:50:56 +0000135 --head : update to latest revision, instead of last good revision
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000136
137Examples:
138 gclient %(cmd)s
139 update files from SVN according to current configuration,
140 *for modules which have changed since last update or sync*
141 gclient %(cmd)s --force
142 update files from SVN according to current configuration, for
143 all modules (useful for recovering files deleted from local copy)
144""")
145
146COMMAND_USAGE_TEXT = {
147 "cleanup":
148 """Clean up all working copies, using 'svn cleanup' for each module.
149Additional options and args may be passed to 'svn cleanup'.
150
151usage: cleanup [options] [--] [svn cleanup args/options]
152
153Valid options:
154 --verbose : output additional diagnostics
155""",
156 "config": """Create a .gclient file in the current directory; this
157specifies the configuration for further commands. After update/sync,
158top-level DEPS files in each module are read to determine dependent
159modules to operate on as well. If optional [url] parameter is
160provided, then configuration is read from a specified Subversion server
161URL. Otherwise, a --spec option must be provided.
162
163usage: config [option | url] [safesync url]
164
165Valid options:
166 --spec=GCLIENT_SPEC : contents of .gclient are read from string parameter.
167 *Note that due to Cygwin/Python brokenness, it
168 probably can't contain any newlines.*
169
170Examples:
171 gclient config https://gclient.googlecode.com/svn/trunk/gclient
172 configure a new client to check out gclient.py tool sources
173 gclient config --spec='solutions=[{"name":"gclient","""
174 '"url":"https://gclient.googlecode.com/svn/trunk/gclient",'
175 '"custom_deps":{}}]',
176 "diff": """Display the differences between two revisions of modules.
177(Does 'svn diff' for each checked out module and dependences.)
178Additional args and options to 'svn diff' can be passed after
179gclient options.
180
181usage: diff [options] [--] [svn args/options]
182
183Valid options:
184 --verbose : output additional diagnostics
185
186Examples:
187 gclient diff
188 simple 'svn diff' for configured client and dependences
189 gclient diff -- -x -b
190 use 'svn diff -x -b' to suppress whitespace-only differences
191 gclient diff -- -r HEAD -x -b
192 diff versus the latest version of each module
193""",
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +0000194 "export":
195 """Wrapper for svn export for all managed directories
196""",
kbr@google.comab318592009-09-04 00:54:55 +0000197 "pack":
198
199 """Generate a patch which can be applied at the root of the tree.
200Internally, runs 'svn diff' on each checked out module and
201dependencies, and performs minimal postprocessing of the output. The
202resulting patch is printed to stdout and can be applied to a freshly
203checked out tree via 'patch -p0 < patchfile'. Additional args and
204options to 'svn diff' can be passed after gclient options.
205
206usage: pack [options] [--] [svn args/options]
207
208Valid options:
209 --verbose : output additional diagnostics
210
211Examples:
212 gclient pack > patch.txt
213 generate simple patch for configured client and dependences
214 gclient pack -- -x -b > patch.txt
215 generate patch using 'svn diff -x -b' to suppress
216 whitespace-only differences
217 gclient pack -- -r HEAD -x -b > patch.txt
218 generate patch, diffing each file versus the latest version of
219 each module
220""",
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000221 "revert":
222 """Revert every file in every managed directory in the client view.
223
224usage: revert
225""",
226 "status":
227 """Show the status of client and dependent modules, using 'svn diff'
228for each module. Additional options and args may be passed to 'svn diff'.
229
230usage: status [options] [--] [svn diff args/options]
231
232Valid options:
233 --verbose : output additional diagnostics
evan@chromium.org67820ef2009-07-27 17:23:00 +0000234 --nohooks : don't run the hooks after the update is complete
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000235""",
236 "sync": GENERIC_UPDATE_USAGE_TEXT % {"cmd": "sync", "alias": "update"},
237 "update": GENERIC_UPDATE_USAGE_TEXT % {"cmd": "update", "alias": "sync"},
238 "help": """Describe the usage of this program or its subcommands.
239
240usage: help [options] [subcommand]
241
242Valid options:
243 --verbose : output additional diagnostics
244""",
245 "runhooks":
246 """Runs hooks for files that have been modified in the local working copy,
maruel@chromium.org5df6a462009-08-28 18:52:26 +0000247according to 'svn status'. Implies --force.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000248
249usage: runhooks [options]
250
251Valid options:
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000252 --verbose : output additional diagnostics
253""",
254 "revinfo":
255 """Outputs source path, server URL and revision information for every
256dependency in all solutions (no local checkout required).
257
258usage: revinfo [options]
259""",
260}
261
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000262DEFAULT_CLIENT_FILE_TEXT = ("""\
263# An element of this array (a "solution") describes a repository directory
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000264# that will be checked out into your working copy. Each solution may
265# optionally define additional dependencies (via its DEPS file) to be
266# checked out alongside the solution's directory. A solution may also
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000267# specify custom dependencies (via the "custom_deps" property) that
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000268# override or augment the dependencies specified by the DEPS file.
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000269# If a "safesync_url" is specified, it is assumed to reference the location of
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000270# a text file which contains nothing but the last known good SCM revision to
271# sync against. It is fetched if specified and used unless --head is passed
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000272
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000273solutions = [
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000274 { "name" : "%(solution_name)s",
275 "url" : "%(solution_url)s",
276 "custom_deps" : {
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000277 # To use the trunk of a component instead of what's in DEPS:
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000278 #"component": "https://svnserver/component/trunk/",
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000279 # To exclude a component from your working copy:
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000280 #"data/really_large_component": None,
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000281 },
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000282 "safesync_url": "%(safesync_url)s"
283 },
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000284]
285""")
286
287
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000288## GClient implementation.
289
290
291class GClient(object):
292 """Object that represent a gclient checkout."""
293
294 supported_commands = [
kbr@google.comab318592009-09-04 00:54:55 +0000295 'cleanup', 'diff', 'export', 'pack', 'revert', 'status', 'update',
296 'runhooks'
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000297 ]
298
299 def __init__(self, root_dir, options):
300 self._root_dir = root_dir
301 self._options = options
302 self._config_content = None
303 self._config_dict = {}
304 self._deps_hooks = []
305
306 def SetConfig(self, content):
307 self._config_dict = {}
308 self._config_content = content
skylined@chromium.orgdf0032c2009-05-29 10:43:56 +0000309 try:
310 exec(content, self._config_dict)
311 except SyntaxError, e:
312 try:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000313 __pychecker__ = 'no-objattrs'
skylined@chromium.orgdf0032c2009-05-29 10:43:56 +0000314 # Try to construct a human readable error message
315 error_message = [
316 'There is a syntax error in your configuration file.',
317 'Line #%s, character %s:' % (e.lineno, e.offset),
318 '"%s"' % re.sub(r'[\r\n]*$', '', e.text) ]
319 except:
320 # Something went wrong, re-raise the original exception
321 raise e
322 else:
323 # Raise a new exception with the human readable message:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000324 raise gclient_utils.Error('\n'.join(error_message))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000325
326 def SaveConfig(self):
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000327 gclient_utils.FileWrite(os.path.join(self._root_dir,
328 self._options.config_filename),
329 self._config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000330
331 def _LoadConfig(self):
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000332 client_source = gclient_utils.FileRead(
333 os.path.join(self._root_dir, self._options.config_filename))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000334 self.SetConfig(client_source)
335
336 def ConfigContent(self):
337 return self._config_content
338
339 def GetVar(self, key, default=None):
340 return self._config_dict.get(key, default)
341
342 @staticmethod
343 def LoadCurrentConfig(options, from_dir=None):
344 """Searches for and loads a .gclient file relative to the current working
345 dir.
346
347 Returns:
348 A dict representing the contents of the .gclient file or an empty dict if
349 the .gclient file doesn't exist.
350 """
351 if not from_dir:
352 from_dir = os.curdir
353 path = os.path.realpath(from_dir)
maruel@chromium.org0329e672009-05-13 18:41:04 +0000354 while not os.path.exists(os.path.join(path, options.config_filename)):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000355 next = os.path.split(path)
356 if not next[1]:
357 return None
358 path = next[0]
maruel@chromium.org2806acc2009-05-15 12:33:34 +0000359 client = GClient(path, options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000360 client._LoadConfig()
361 return client
362
363 def SetDefaultConfig(self, solution_name, solution_url, safesync_url):
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000364 self.SetConfig(DEFAULT_CLIENT_FILE_TEXT % {
365 'solution_name': solution_name,
366 'solution_url': solution_url,
367 'safesync_url' : safesync_url,
368 })
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000369
370 def _SaveEntries(self, entries):
371 """Creates a .gclient_entries file to record the list of unique checkouts.
372
373 The .gclient_entries file lives in the same directory as .gclient.
374
375 Args:
376 entries: A sequence of solution names.
377 """
msb@chromium.org2e38de72009-09-28 17:04:47 +0000378 text = "entries = \\\n" + pprint.pformat(entries, 2) + '\n'
379 file_path = os.path.join(self._root_dir, self._options.entries_filename)
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000380 gclient_utils.FileWrite(file_path, text)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000381
382 def _ReadEntries(self):
383 """Read the .gclient_entries file for the given client.
384
385 Args:
386 client: The client for which the entries file should be read.
387
388 Returns:
389 A sequence of solution names, which will be empty if there is the
390 entries file hasn't been created yet.
391 """
392 scope = {}
393 filename = os.path.join(self._root_dir, self._options.entries_filename)
maruel@chromium.org0329e672009-05-13 18:41:04 +0000394 if not os.path.exists(filename):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000395 return []
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000396 exec(gclient_utils.FileRead(filename), scope)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000397 return scope["entries"]
398
399 class FromImpl:
400 """Used to implement the From syntax."""
401
402 def __init__(self, module_name):
403 self.module_name = module_name
404
405 def __str__(self):
406 return 'From("%s")' % self.module_name
407
408 class _VarImpl:
409 def __init__(self, custom_vars, local_scope):
410 self._custom_vars = custom_vars
411 self._local_scope = local_scope
412
413 def Lookup(self, var_name):
414 """Implements the Var syntax."""
415 if var_name in self._custom_vars:
416 return self._custom_vars[var_name]
417 elif var_name in self._local_scope.get("vars", {}):
418 return self._local_scope["vars"][var_name]
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000419 raise gclient_utils.Error("Var is not defined: %s" % var_name)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000420
421 def _ParseSolutionDeps(self, solution_name, solution_deps_content,
422 custom_vars):
423 """Parses the DEPS file for the specified solution.
424
425 Args:
426 solution_name: The name of the solution to query.
427 solution_deps_content: Content of the DEPS file for the solution
428 custom_vars: A dict of vars to override any vars defined in the DEPS file.
429
430 Returns:
431 A dict mapping module names (as relative paths) to URLs or an empty
432 dict if the solution does not have a DEPS file.
433 """
434 # Skip empty
435 if not solution_deps_content:
436 return {}
437 # Eval the content
438 local_scope = {}
439 var = self._VarImpl(custom_vars, local_scope)
440 global_scope = {"From": self.FromImpl, "Var": var.Lookup, "deps_os": {}}
441 exec(solution_deps_content, global_scope, local_scope)
442 deps = local_scope.get("deps", {})
443
444 # load os specific dependencies if defined. these dependencies may
445 # override or extend the values defined by the 'deps' member.
446 if "deps_os" in local_scope:
447 deps_os_choices = {
448 "win32": "win",
449 "win": "win",
450 "cygwin": "win",
451 "darwin": "mac",
452 "mac": "mac",
453 "unix": "unix",
454 "linux": "unix",
455 "linux2": "unix",
456 }
457
458 if self._options.deps_os is not None:
459 deps_to_include = self._options.deps_os.split(",")
460 if "all" in deps_to_include:
461 deps_to_include = deps_os_choices.values()
462 else:
463 deps_to_include = [deps_os_choices.get(self._options.platform, "unix")]
464
465 deps_to_include = set(deps_to_include)
466 for deps_os_key in deps_to_include:
467 os_deps = local_scope["deps_os"].get(deps_os_key, {})
468 if len(deps_to_include) > 1:
469 # Ignore any overrides when including deps for more than one
470 # platform, so we collect the broadest set of dependencies available.
471 # We may end up with the wrong revision of something for our
472 # platform, but this is the best we can do.
473 deps.update([x for x in os_deps.items() if not x[0] in deps])
474 else:
475 deps.update(os_deps)
476
477 if 'hooks' in local_scope:
478 self._deps_hooks.extend(local_scope['hooks'])
479
480 # If use_relative_paths is set in the DEPS file, regenerate
481 # the dictionary using paths relative to the directory containing
482 # the DEPS file.
483 if local_scope.get('use_relative_paths'):
484 rel_deps = {}
485 for d, url in deps.items():
486 # normpath is required to allow DEPS to use .. in their
487 # dependency local path.
488 rel_deps[os.path.normpath(os.path.join(solution_name, d))] = url
489 return rel_deps
490 else:
491 return deps
492
493 def _ParseAllDeps(self, solution_urls, solution_deps_content):
494 """Parse the complete list of dependencies for the client.
495
496 Args:
497 solution_urls: A dict mapping module names (as relative paths) to URLs
498 corresponding to the solutions specified by the client. This parameter
499 is passed as an optimization.
500 solution_deps_content: A dict mapping module names to the content
501 of their DEPS files
502
503 Returns:
504 A dict mapping module names (as relative paths) to URLs corresponding
505 to the entire set of dependencies to checkout for the given client.
506
507 Raises:
508 Error: If a dependency conflicts with another dependency or of a solution.
509 """
510 deps = {}
511 for solution in self.GetVar("solutions"):
512 custom_vars = solution.get("custom_vars", {})
513 solution_deps = self._ParseSolutionDeps(
514 solution["name"],
515 solution_deps_content[solution["name"]],
516 custom_vars)
517
518 # If a line is in custom_deps, but not in the solution, we want to append
519 # this line to the solution.
520 if "custom_deps" in solution:
521 for d in solution["custom_deps"]:
522 if d not in solution_deps:
523 solution_deps[d] = solution["custom_deps"][d]
524
525 for d in solution_deps:
526 if "custom_deps" in solution and d in solution["custom_deps"]:
527 # Dependency is overriden.
528 url = solution["custom_deps"][d]
529 if url is None:
530 continue
531 else:
532 url = solution_deps[d]
533 # if we have a From reference dependent on another solution, then
534 # just skip the From reference. When we pull deps for the solution,
535 # we will take care of this dependency.
536 #
537 # If multiple solutions all have the same From reference, then we
538 # should only add one to our list of dependencies.
539 if type(url) != str:
540 if url.module_name in solution_urls:
541 # Already parsed.
542 continue
543 if d in deps and type(deps[d]) != str:
544 if url.module_name == deps[d].module_name:
545 continue
546 else:
547 parsed_url = urlparse.urlparse(url)
548 scheme = parsed_url[0]
549 if not scheme:
550 # A relative url. Fetch the real base.
551 path = parsed_url[2]
552 if path[0] != "/":
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000553 raise gclient_utils.Error(
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000554 "relative DEPS entry \"%s\" must begin with a slash" % d)
555 # Create a scm just to query the full url.
msb@chromium.orgcb5442b2009-09-22 16:51:24 +0000556 scm = gclient_scm.CreateSCM(solution["url"], self._root_dir,
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000557 None)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000558 url = scm.FullUrlForRelativeUrl(url)
559 if d in deps and deps[d] != url:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000560 raise gclient_utils.Error(
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000561 "Solutions have conflicting versions of dependency \"%s\"" % d)
562 if d in solution_urls and solution_urls[d] != url:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000563 raise gclient_utils.Error(
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000564 "Dependency \"%s\" conflicts with specified solution" % d)
565 # Grab the dependency.
566 deps[d] = url
567 return deps
568
phajdan.jr@chromium.org71b40682009-07-31 23:40:09 +0000569 def _RunHookAction(self, hook_dict, matching_file_list):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000570 """Runs the action from a single hook.
571 """
572 command = hook_dict['action'][:]
573 if command[0] == 'python':
574 # If the hook specified "python" as the first item, the action is a
575 # Python script. Run it by starting a new copy of the same
576 # interpreter.
577 command[0] = sys.executable
578
phajdan.jr@chromium.org71b40682009-07-31 23:40:09 +0000579 if '$matching_files' in command:
phajdan.jr@chromium.org68f2e092009-08-06 17:05:35 +0000580 splice_index = command.index('$matching_files')
581 command[splice_index:splice_index + 1] = matching_file_list
phajdan.jr@chromium.org71b40682009-07-31 23:40:09 +0000582
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000583 # Use a discrete exit status code of 2 to indicate that a hook action
584 # failed. Users of this script may wish to treat hook action failures
585 # differently from VC failures.
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000586 gclient_utils.SubprocessCall(command, self._root_dir, fail_status=2)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000587
588 def _RunHooks(self, command, file_list, is_using_git):
589 """Evaluates all hooks, running actions as needed.
590 """
591 # Hooks only run for these command types.
592 if not command in ('update', 'revert', 'runhooks'):
593 return
594
evan@chromium.org67820ef2009-07-27 17:23:00 +0000595 # Hooks only run when --nohooks is not specified
596 if self._options.nohooks:
597 return
598
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000599 # Get any hooks from the .gclient file.
600 hooks = self.GetVar("hooks", [])
601 # Add any hooks found in DEPS files.
602 hooks.extend(self._deps_hooks)
603
604 # If "--force" was specified, run all hooks regardless of what files have
605 # changed. If the user is using git, then we don't know what files have
606 # changed so we always run all hooks.
607 if self._options.force or is_using_git:
608 for hook_dict in hooks:
phajdan.jr@chromium.org71b40682009-07-31 23:40:09 +0000609 self._RunHookAction(hook_dict, [])
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000610 return
611
612 # Run hooks on the basis of whether the files from the gclient operation
613 # match each hook's pattern.
614 for hook_dict in hooks:
615 pattern = re.compile(hook_dict['pattern'])
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000616 matching_file_list = [f for f in file_list if pattern.search(f)]
phajdan.jr@chromium.org71b40682009-07-31 23:40:09 +0000617 if matching_file_list:
618 self._RunHookAction(hook_dict, matching_file_list)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000619
620 def RunOnDeps(self, command, args):
621 """Runs a command on each dependency in a client and its dependencies.
622
623 The module's dependencies are specified in its top-level DEPS files.
624
625 Args:
626 command: The command to use (e.g., 'status' or 'diff')
627 args: list of str - extra arguments to add to the command line.
628
629 Raises:
630 Error: If the client has conflicting entries.
631 """
632 if not command in self.supported_commands:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000633 raise gclient_utils.Error("'%s' is an unsupported command" % command)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000634
635 # Check for revision overrides.
636 revision_overrides = {}
637 for revision in self._options.revisions:
638 if revision.find("@") == -1:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000639 raise gclient_utils.Error(
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000640 "Specify the full dependency when specifying a revision number.")
641 revision_elem = revision.split("@")
642 # Disallow conflicting revs
643 if revision_overrides.has_key(revision_elem[0]) and \
644 revision_overrides[revision_elem[0]] != revision_elem[1]:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000645 raise gclient_utils.Error(
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000646 "Conflicting revision numbers specified.")
647 revision_overrides[revision_elem[0]] = revision_elem[1]
648
649 solutions = self.GetVar("solutions")
650 if not solutions:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000651 raise gclient_utils.Error("No solution specified")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000652
653 # When running runhooks --force, there's no need to consult the SCM.
654 # All known hooks are expected to run unconditionally regardless of working
655 # copy state, so skip the SCM status check.
656 run_scm = not (command == 'runhooks' and self._options.force)
657
658 entries = {}
659 entries_deps_content = {}
660 file_list = []
661 # Run on the base solutions first.
662 for solution in solutions:
663 name = solution["name"]
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000664 deps_file = solution.get("deps_file", self._options.deps_file)
665 if '/' in deps_file or '\\' in deps_file:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000666 raise gclient_utils.Error('deps_file name must not be a path, just a '
667 'filename.')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000668 if name in entries:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000669 raise gclient_utils.Error("solution %s specified more than once" % name)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000670 url = solution["url"]
671 entries[name] = url
yaar@chromium.orgf1328042009-09-22 23:14:23 +0000672 if run_scm and url:
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000673 self._options.revision = revision_overrides.get(name)
msb@chromium.orgcb5442b2009-09-22 16:51:24 +0000674 scm = gclient_scm.CreateSCM(url, self._root_dir, name)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000675 scm.RunCommand(command, self._options, args, file_list)
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000676 file_list = [os.path.join(name, f.strip()) for f in file_list]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000677 self._options.revision = None
678 try:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000679 deps_content = gclient_utils.FileRead(
680 os.path.join(self._root_dir, name, deps_file))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000681 except IOError, e:
682 if e.errno != errno.ENOENT:
683 raise
684 deps_content = ""
685 entries_deps_content[name] = deps_content
686
687 # Process the dependencies next (sort alphanumerically to ensure that
688 # containing directories get populated first and for readability)
689 deps = self._ParseAllDeps(entries, entries_deps_content)
690 deps_to_process = deps.keys()
691 deps_to_process.sort()
692
693 # First pass for direct dependencies.
694 for d in deps_to_process:
695 if type(deps[d]) == str:
696 url = deps[d]
697 entries[d] = url
698 if run_scm:
699 self._options.revision = revision_overrides.get(d)
msb@chromium.orgcb5442b2009-09-22 16:51:24 +0000700 scm = gclient_scm.CreateSCM(url, self._root_dir, d)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000701 scm.RunCommand(command, self._options, args, file_list)
702 self._options.revision = None
703
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
750 if isinstance(prev_entries,list):
751 # old .gclient_entries format was list, now dict
752 modified_files = gclient_scm.CaptureSVNStatus(e_dir)
753 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.orgac915bb2009-11-13 17:03:01 +0000812 revision, url = gclient_utils.SplitUrlRevision(original_url)
813 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.org167b9e62009-09-17 17:41:02 +0000833 entries_deps_content[name] = gclient_scm.CaptureSVN(
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"))
1135 option_parser.add_option("", "--verbose", action="store_true", default=False,
1136 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.org754960e2009-09-21 12:31:05 +00001169 if options.verbose:
1170 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: