blob: cb93162a291d65c7afa22352aae3febeb522a248 [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.org5aeb7dd2009-11-17 18:09:01 +000069__version__ = "0.3.4"
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:
estade@chromium.org3b5cba42009-12-01 00:37:08 +0000129 --force : force update even for unchanged modules
130 --nohooks : don't run the hooks after the update is complete
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000131 --revision SOLUTION@REV : update given solution to specified revision
estade@chromium.org3b5cba42009-12-01 00:37:08 +0000132 --deps PLATFORM(S) : sync deps for the given platform(s), or 'all'
133 --verbose : output additional diagnostics
134 --head : update to latest revision, instead of last good revision
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000135
136Examples:
137 gclient %(cmd)s
138 update files from SVN according to current configuration,
139 *for modules which have changed since last update or sync*
140 gclient %(cmd)s --force
141 update files from SVN according to current configuration, for
142 all modules (useful for recovering files deleted from local copy)
estade@chromium.org3b5cba42009-12-01 00:37:08 +0000143 gclient %(cmd)s --revision src@31000
144 update src directory to r31000
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000145""")
146
147COMMAND_USAGE_TEXT = {
148 "cleanup":
149 """Clean up all working copies, using 'svn cleanup' for each module.
150Additional options and args may be passed to 'svn cleanup'.
151
152usage: cleanup [options] [--] [svn cleanup args/options]
153
154Valid options:
155 --verbose : output additional diagnostics
156""",
157 "config": """Create a .gclient file in the current directory; this
158specifies the configuration for further commands. After update/sync,
159top-level DEPS files in each module are read to determine dependent
160modules to operate on as well. If optional [url] parameter is
161provided, then configuration is read from a specified Subversion server
162URL. Otherwise, a --spec option must be provided.
163
164usage: config [option | url] [safesync url]
165
166Valid options:
167 --spec=GCLIENT_SPEC : contents of .gclient are read from string parameter.
168 *Note that due to Cygwin/Python brokenness, it
169 probably can't contain any newlines.*
170
171Examples:
172 gclient config https://gclient.googlecode.com/svn/trunk/gclient
173 configure a new client to check out gclient.py tool sources
174 gclient config --spec='solutions=[{"name":"gclient","""
175 '"url":"https://gclient.googlecode.com/svn/trunk/gclient",'
176 '"custom_deps":{}}]',
177 "diff": """Display the differences between two revisions of modules.
178(Does 'svn diff' for each checked out module and dependences.)
179Additional args and options to 'svn diff' can be passed after
180gclient options.
181
182usage: diff [options] [--] [svn args/options]
183
184Valid options:
185 --verbose : output additional diagnostics
186
187Examples:
188 gclient diff
189 simple 'svn diff' for configured client and dependences
190 gclient diff -- -x -b
191 use 'svn diff -x -b' to suppress whitespace-only differences
192 gclient diff -- -r HEAD -x -b
193 diff versus the latest version of each module
194""",
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +0000195 "export":
196 """Wrapper for svn export for all managed directories
197""",
kbr@google.comab318592009-09-04 00:54:55 +0000198 "pack":
199
200 """Generate a patch which can be applied at the root of the tree.
201Internally, runs 'svn diff' on each checked out module and
202dependencies, and performs minimal postprocessing of the output. The
203resulting patch is printed to stdout and can be applied to a freshly
204checked out tree via 'patch -p0 < patchfile'. Additional args and
205options to 'svn diff' can be passed after gclient options.
206
207usage: pack [options] [--] [svn args/options]
208
209Valid options:
210 --verbose : output additional diagnostics
211
212Examples:
213 gclient pack > patch.txt
214 generate simple patch for configured client and dependences
215 gclient pack -- -x -b > patch.txt
216 generate patch using 'svn diff -x -b' to suppress
217 whitespace-only differences
218 gclient pack -- -r HEAD -x -b > patch.txt
219 generate patch, diffing each file versus the latest version of
220 each module
221""",
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000222 "revert":
223 """Revert every file in every managed directory in the client view.
224
225usage: revert
226""",
227 "status":
228 """Show the status of client and dependent modules, using 'svn diff'
229for each module. Additional options and args may be passed to 'svn diff'.
230
231usage: status [options] [--] [svn diff args/options]
232
233Valid options:
234 --verbose : output additional diagnostics
evan@chromium.org67820ef2009-07-27 17:23:00 +0000235 --nohooks : don't run the hooks after the update is complete
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000236""",
237 "sync": GENERIC_UPDATE_USAGE_TEXT % {"cmd": "sync", "alias": "update"},
238 "update": GENERIC_UPDATE_USAGE_TEXT % {"cmd": "update", "alias": "sync"},
239 "help": """Describe the usage of this program or its subcommands.
240
241usage: help [options] [subcommand]
242
243Valid options:
244 --verbose : output additional diagnostics
245""",
246 "runhooks":
247 """Runs hooks for files that have been modified in the local working copy,
maruel@chromium.org5df6a462009-08-28 18:52:26 +0000248according to 'svn status'. Implies --force.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000249
250usage: runhooks [options]
251
252Valid options:
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000253 --verbose : output additional diagnostics
254""",
255 "revinfo":
256 """Outputs source path, server URL and revision information for every
257dependency in all solutions (no local checkout required).
258
259usage: revinfo [options]
260""",
261}
262
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000263DEFAULT_CLIENT_FILE_TEXT = ("""\
264# An element of this array (a "solution") describes a repository directory
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000265# that will be checked out into your working copy. Each solution may
266# optionally define additional dependencies (via its DEPS file) to be
267# checked out alongside the solution's directory. A solution may also
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000268# specify custom dependencies (via the "custom_deps" property) that
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000269# override or augment the dependencies specified by the DEPS file.
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000270# If a "safesync_url" is specified, it is assumed to reference the location of
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000271# a text file which contains nothing but the last known good SCM revision to
272# sync against. It is fetched if specified and used unless --head is passed
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000273
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000274solutions = [
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000275 { "name" : "%(solution_name)s",
276 "url" : "%(solution_url)s",
277 "custom_deps" : {
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000278 # To use the trunk of a component instead of what's in DEPS:
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000279 #"component": "https://svnserver/component/trunk/",
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000280 # To exclude a component from your working copy:
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000281 #"data/really_large_component": None,
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000282 },
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000283 "safesync_url": "%(safesync_url)s"
284 },
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000285]
286""")
287
288
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000289## GClient implementation.
290
291
292class GClient(object):
293 """Object that represent a gclient checkout."""
294
295 supported_commands = [
kbr@google.comab318592009-09-04 00:54:55 +0000296 'cleanup', 'diff', 'export', 'pack', 'revert', 'status', 'update',
297 'runhooks'
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000298 ]
299
300 def __init__(self, root_dir, options):
301 self._root_dir = root_dir
302 self._options = options
303 self._config_content = None
304 self._config_dict = {}
305 self._deps_hooks = []
306
307 def SetConfig(self, content):
308 self._config_dict = {}
309 self._config_content = content
skylined@chromium.orgdf0032c2009-05-29 10:43:56 +0000310 try:
311 exec(content, self._config_dict)
312 except SyntaxError, e:
313 try:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000314 __pychecker__ = 'no-objattrs'
skylined@chromium.orgdf0032c2009-05-29 10:43:56 +0000315 # Try to construct a human readable error message
316 error_message = [
317 'There is a syntax error in your configuration file.',
318 'Line #%s, character %s:' % (e.lineno, e.offset),
319 '"%s"' % re.sub(r'[\r\n]*$', '', e.text) ]
320 except:
321 # Something went wrong, re-raise the original exception
322 raise e
323 else:
324 # Raise a new exception with the human readable message:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000325 raise gclient_utils.Error('\n'.join(error_message))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000326
327 def SaveConfig(self):
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000328 gclient_utils.FileWrite(os.path.join(self._root_dir,
329 self._options.config_filename),
330 self._config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000331
332 def _LoadConfig(self):
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000333 client_source = gclient_utils.FileRead(
334 os.path.join(self._root_dir, self._options.config_filename))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000335 self.SetConfig(client_source)
336
337 def ConfigContent(self):
338 return self._config_content
339
340 def GetVar(self, key, default=None):
341 return self._config_dict.get(key, default)
342
343 @staticmethod
344 def LoadCurrentConfig(options, from_dir=None):
345 """Searches for and loads a .gclient file relative to the current working
346 dir.
347
348 Returns:
349 A dict representing the contents of the .gclient file or an empty dict if
350 the .gclient file doesn't exist.
351 """
352 if not from_dir:
353 from_dir = os.curdir
354 path = os.path.realpath(from_dir)
maruel@chromium.org0329e672009-05-13 18:41:04 +0000355 while not os.path.exists(os.path.join(path, options.config_filename)):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000356 next = os.path.split(path)
357 if not next[1]:
358 return None
359 path = next[0]
maruel@chromium.org2806acc2009-05-15 12:33:34 +0000360 client = GClient(path, options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000361 client._LoadConfig()
362 return client
363
364 def SetDefaultConfig(self, solution_name, solution_url, safesync_url):
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000365 self.SetConfig(DEFAULT_CLIENT_FILE_TEXT % {
366 'solution_name': solution_name,
367 'solution_url': solution_url,
368 'safesync_url' : safesync_url,
369 })
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000370
371 def _SaveEntries(self, entries):
372 """Creates a .gclient_entries file to record the list of unique checkouts.
373
374 The .gclient_entries file lives in the same directory as .gclient.
375
376 Args:
377 entries: A sequence of solution names.
378 """
msb@chromium.org2e38de72009-09-28 17:04:47 +0000379 text = "entries = \\\n" + pprint.pformat(entries, 2) + '\n'
380 file_path = os.path.join(self._root_dir, self._options.entries_filename)
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000381 gclient_utils.FileWrite(file_path, text)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000382
383 def _ReadEntries(self):
384 """Read the .gclient_entries file for the given client.
385
386 Args:
387 client: The client for which the entries file should be read.
388
389 Returns:
390 A sequence of solution names, which will be empty if there is the
391 entries file hasn't been created yet.
392 """
393 scope = {}
394 filename = os.path.join(self._root_dir, self._options.entries_filename)
maruel@chromium.org0329e672009-05-13 18:41:04 +0000395 if not os.path.exists(filename):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000396 return []
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000397 exec(gclient_utils.FileRead(filename), scope)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000398 return scope["entries"]
399
400 class FromImpl:
401 """Used to implement the From syntax."""
402
403 def __init__(self, module_name):
404 self.module_name = module_name
405
406 def __str__(self):
407 return 'From("%s")' % self.module_name
408
409 class _VarImpl:
410 def __init__(self, custom_vars, local_scope):
411 self._custom_vars = custom_vars
412 self._local_scope = local_scope
413
414 def Lookup(self, var_name):
415 """Implements the Var syntax."""
416 if var_name in self._custom_vars:
417 return self._custom_vars[var_name]
418 elif var_name in self._local_scope.get("vars", {}):
419 return self._local_scope["vars"][var_name]
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000420 raise gclient_utils.Error("Var is not defined: %s" % var_name)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000421
422 def _ParseSolutionDeps(self, solution_name, solution_deps_content,
423 custom_vars):
424 """Parses the DEPS file for the specified solution.
425
426 Args:
427 solution_name: The name of the solution to query.
428 solution_deps_content: Content of the DEPS file for the solution
429 custom_vars: A dict of vars to override any vars defined in the DEPS file.
430
431 Returns:
432 A dict mapping module names (as relative paths) to URLs or an empty
433 dict if the solution does not have a DEPS file.
434 """
435 # Skip empty
436 if not solution_deps_content:
437 return {}
438 # Eval the content
439 local_scope = {}
440 var = self._VarImpl(custom_vars, local_scope)
441 global_scope = {"From": self.FromImpl, "Var": var.Lookup, "deps_os": {}}
442 exec(solution_deps_content, global_scope, local_scope)
443 deps = local_scope.get("deps", {})
444
445 # load os specific dependencies if defined. these dependencies may
446 # override or extend the values defined by the 'deps' member.
447 if "deps_os" in local_scope:
448 deps_os_choices = {
449 "win32": "win",
450 "win": "win",
451 "cygwin": "win",
452 "darwin": "mac",
453 "mac": "mac",
454 "unix": "unix",
455 "linux": "unix",
456 "linux2": "unix",
457 }
458
459 if self._options.deps_os is not None:
460 deps_to_include = self._options.deps_os.split(",")
461 if "all" in deps_to_include:
462 deps_to_include = deps_os_choices.values()
463 else:
464 deps_to_include = [deps_os_choices.get(self._options.platform, "unix")]
465
466 deps_to_include = set(deps_to_include)
467 for deps_os_key in deps_to_include:
468 os_deps = local_scope["deps_os"].get(deps_os_key, {})
469 if len(deps_to_include) > 1:
470 # Ignore any overrides when including deps for more than one
471 # platform, so we collect the broadest set of dependencies available.
472 # We may end up with the wrong revision of something for our
473 # platform, but this is the best we can do.
474 deps.update([x for x in os_deps.items() if not x[0] in deps])
475 else:
476 deps.update(os_deps)
477
478 if 'hooks' in local_scope:
479 self._deps_hooks.extend(local_scope['hooks'])
480
481 # If use_relative_paths is set in the DEPS file, regenerate
482 # the dictionary using paths relative to the directory containing
483 # the DEPS file.
484 if local_scope.get('use_relative_paths'):
485 rel_deps = {}
486 for d, url in deps.items():
487 # normpath is required to allow DEPS to use .. in their
488 # dependency local path.
489 rel_deps[os.path.normpath(os.path.join(solution_name, d))] = url
490 return rel_deps
491 else:
492 return deps
493
494 def _ParseAllDeps(self, solution_urls, solution_deps_content):
495 """Parse the complete list of dependencies for the client.
496
497 Args:
498 solution_urls: A dict mapping module names (as relative paths) to URLs
499 corresponding to the solutions specified by the client. This parameter
500 is passed as an optimization.
501 solution_deps_content: A dict mapping module names to the content
502 of their DEPS files
503
504 Returns:
505 A dict mapping module names (as relative paths) to URLs corresponding
506 to the entire set of dependencies to checkout for the given client.
507
508 Raises:
509 Error: If a dependency conflicts with another dependency or of a solution.
510 """
511 deps = {}
512 for solution in self.GetVar("solutions"):
513 custom_vars = solution.get("custom_vars", {})
514 solution_deps = self._ParseSolutionDeps(
515 solution["name"],
516 solution_deps_content[solution["name"]],
517 custom_vars)
518
519 # If a line is in custom_deps, but not in the solution, we want to append
520 # this line to the solution.
521 if "custom_deps" in solution:
522 for d in solution["custom_deps"]:
523 if d not in solution_deps:
524 solution_deps[d] = solution["custom_deps"][d]
525
526 for d in solution_deps:
527 if "custom_deps" in solution and d in solution["custom_deps"]:
528 # Dependency is overriden.
529 url = solution["custom_deps"][d]
530 if url is None:
531 continue
532 else:
533 url = solution_deps[d]
534 # if we have a From reference dependent on another solution, then
535 # just skip the From reference. When we pull deps for the solution,
536 # we will take care of this dependency.
537 #
538 # If multiple solutions all have the same From reference, then we
539 # should only add one to our list of dependencies.
540 if type(url) != str:
541 if url.module_name in solution_urls:
542 # Already parsed.
543 continue
544 if d in deps and type(deps[d]) != str:
545 if url.module_name == deps[d].module_name:
546 continue
547 else:
548 parsed_url = urlparse.urlparse(url)
549 scheme = parsed_url[0]
550 if not scheme:
551 # A relative url. Fetch the real base.
552 path = parsed_url[2]
553 if path[0] != "/":
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000554 raise gclient_utils.Error(
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000555 "relative DEPS entry \"%s\" must begin with a slash" % d)
556 # Create a scm just to query the full url.
msb@chromium.orgcb5442b2009-09-22 16:51:24 +0000557 scm = gclient_scm.CreateSCM(solution["url"], self._root_dir,
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000558 None)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000559 url = scm.FullUrlForRelativeUrl(url)
560 if d in deps and deps[d] != url:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000561 raise gclient_utils.Error(
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000562 "Solutions have conflicting versions of dependency \"%s\"" % d)
563 if d in solution_urls and solution_urls[d] != url:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000564 raise gclient_utils.Error(
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000565 "Dependency \"%s\" conflicts with specified solution" % d)
566 # Grab the dependency.
567 deps[d] = url
568 return deps
569
phajdan.jr@chromium.org71b40682009-07-31 23:40:09 +0000570 def _RunHookAction(self, hook_dict, matching_file_list):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000571 """Runs the action from a single hook.
572 """
573 command = hook_dict['action'][:]
574 if command[0] == 'python':
575 # If the hook specified "python" as the first item, the action is a
576 # Python script. Run it by starting a new copy of the same
577 # interpreter.
578 command[0] = sys.executable
579
phajdan.jr@chromium.org71b40682009-07-31 23:40:09 +0000580 if '$matching_files' in command:
phajdan.jr@chromium.org68f2e092009-08-06 17:05:35 +0000581 splice_index = command.index('$matching_files')
582 command[splice_index:splice_index + 1] = matching_file_list
phajdan.jr@chromium.org71b40682009-07-31 23:40:09 +0000583
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000584 # Use a discrete exit status code of 2 to indicate that a hook action
585 # failed. Users of this script may wish to treat hook action failures
586 # differently from VC failures.
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000587 gclient_utils.SubprocessCall(command, self._root_dir, fail_status=2)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000588
589 def _RunHooks(self, command, file_list, is_using_git):
590 """Evaluates all hooks, running actions as needed.
591 """
592 # Hooks only run for these command types.
593 if not command in ('update', 'revert', 'runhooks'):
594 return
595
evan@chromium.org67820ef2009-07-27 17:23:00 +0000596 # Hooks only run when --nohooks is not specified
597 if self._options.nohooks:
598 return
599
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000600 # Get any hooks from the .gclient file.
601 hooks = self.GetVar("hooks", [])
602 # Add any hooks found in DEPS files.
603 hooks.extend(self._deps_hooks)
604
605 # If "--force" was specified, run all hooks regardless of what files have
606 # changed. If the user is using git, then we don't know what files have
607 # changed so we always run all hooks.
608 if self._options.force or is_using_git:
609 for hook_dict in hooks:
phajdan.jr@chromium.org71b40682009-07-31 23:40:09 +0000610 self._RunHookAction(hook_dict, [])
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000611 return
612
613 # Run hooks on the basis of whether the files from the gclient operation
614 # match each hook's pattern.
615 for hook_dict in hooks:
616 pattern = re.compile(hook_dict['pattern'])
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000617 matching_file_list = [f for f in file_list if pattern.search(f)]
phajdan.jr@chromium.org71b40682009-07-31 23:40:09 +0000618 if matching_file_list:
619 self._RunHookAction(hook_dict, matching_file_list)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000620
621 def RunOnDeps(self, command, args):
622 """Runs a command on each dependency in a client and its dependencies.
623
624 The module's dependencies are specified in its top-level DEPS files.
625
626 Args:
627 command: The command to use (e.g., 'status' or 'diff')
628 args: list of str - extra arguments to add to the command line.
629
630 Raises:
631 Error: If the client has conflicting entries.
632 """
633 if not command in self.supported_commands:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000634 raise gclient_utils.Error("'%s' is an unsupported command" % command)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000635
636 # Check for revision overrides.
637 revision_overrides = {}
638 for revision in self._options.revisions:
639 if revision.find("@") == -1:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000640 raise gclient_utils.Error(
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000641 "Specify the full dependency when specifying a revision number.")
642 revision_elem = revision.split("@")
643 # Disallow conflicting revs
644 if revision_overrides.has_key(revision_elem[0]) and \
645 revision_overrides[revision_elem[0]] != revision_elem[1]:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000646 raise gclient_utils.Error(
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000647 "Conflicting revision numbers specified.")
648 revision_overrides[revision_elem[0]] = revision_elem[1]
649
650 solutions = self.GetVar("solutions")
651 if not solutions:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000652 raise gclient_utils.Error("No solution specified")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000653
654 # When running runhooks --force, there's no need to consult the SCM.
655 # All known hooks are expected to run unconditionally regardless of working
656 # copy state, so skip the SCM status check.
657 run_scm = not (command == 'runhooks' and self._options.force)
658
659 entries = {}
660 entries_deps_content = {}
661 file_list = []
662 # Run on the base solutions first.
663 for solution in solutions:
664 name = solution["name"]
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000665 deps_file = solution.get("deps_file", self._options.deps_file)
666 if '/' in deps_file or '\\' in deps_file:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000667 raise gclient_utils.Error('deps_file name must not be a path, just a '
668 'filename.')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000669 if name in entries:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000670 raise gclient_utils.Error("solution %s specified more than once" % name)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000671 url = solution["url"]
672 entries[name] = url
yaar@chromium.orgf1328042009-09-22 23:14:23 +0000673 if run_scm and url:
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000674 self._options.revision = revision_overrides.get(name)
msb@chromium.orgcb5442b2009-09-22 16:51:24 +0000675 scm = gclient_scm.CreateSCM(url, self._root_dir, name)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000676 scm.RunCommand(command, self._options, args, file_list)
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000677 file_list = [os.path.join(name, f.strip()) for f in file_list]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000678 self._options.revision = None
679 try:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000680 deps_content = gclient_utils.FileRead(
681 os.path.join(self._root_dir, name, deps_file))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000682 except IOError, e:
683 if e.errno != errno.ENOENT:
684 raise
685 deps_content = ""
686 entries_deps_content[name] = deps_content
687
688 # Process the dependencies next (sort alphanumerically to ensure that
689 # containing directories get populated first and for readability)
690 deps = self._ParseAllDeps(entries, entries_deps_content)
691 deps_to_process = deps.keys()
692 deps_to_process.sort()
693
694 # First pass for direct dependencies.
695 for d in deps_to_process:
696 if type(deps[d]) == str:
697 url = deps[d]
698 entries[d] = url
699 if run_scm:
700 self._options.revision = revision_overrides.get(d)
msb@chromium.orgcb5442b2009-09-22 16:51:24 +0000701 scm = gclient_scm.CreateSCM(url, self._root_dir, d)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000702 scm.RunCommand(command, self._options, args, file_list)
703 self._options.revision = None
704
705 # Second pass for inherited deps (via the From keyword)
706 for d in deps_to_process:
707 if type(deps[d]) != str:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000708 filename = os.path.join(self._root_dir,
709 deps[d].module_name,
710 self._options.deps_file)
711 content = gclient_utils.FileRead(filename)
712 sub_deps = self._ParseSolutionDeps(deps[d].module_name, content, {})
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000713 url = sub_deps[d]
714 entries[d] = url
715 if run_scm:
716 self._options.revision = revision_overrides.get(d)
msb@chromium.orgcb5442b2009-09-22 16:51:24 +0000717 scm = gclient_scm.CreateSCM(url, self._root_dir, d)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000718 scm.RunCommand(command, self._options, args, file_list)
719 self._options.revision = None
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000720
phajdan.jr@chromium.orgd83b2b22009-08-11 15:30:55 +0000721 # Convert all absolute paths to relative.
722 for i in range(len(file_list)):
723 # TODO(phajdan.jr): We should know exactly when the paths are absolute.
724 # It depends on the command being executed (like runhooks vs sync).
725 if not os.path.isabs(file_list[i]):
726 continue
727
728 prefix = os.path.commonprefix([self._root_dir.lower(),
729 file_list[i].lower()])
730 file_list[i] = file_list[i][len(prefix):]
731
732 # Strip any leading path separators.
733 while file_list[i].startswith('\\') or file_list[i].startswith('/'):
734 file_list[i] = file_list[i][1:]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000735
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000736 is_using_git = gclient_utils.IsUsingGit(self._root_dir, entries.keys())
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000737 self._RunHooks(command, file_list, is_using_git)
738
739 if command == 'update':
ajwong@chromium.orgcdcee802009-06-23 15:30:42 +0000740 # Notify the user if there is an orphaned entry in their working copy.
741 # Only delete the directory if there are no changes in it, and
742 # delete_unversioned_trees is set to true.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000743 prev_entries = self._ReadEntries()
744 for entry in prev_entries:
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000745 # Fix path separator on Windows.
746 entry_fixed = entry.replace('/', os.path.sep)
747 e_dir = os.path.join(self._root_dir, entry_fixed)
748 # Use entry and not entry_fixed there.
maruel@chromium.org0329e672009-05-13 18:41:04 +0000749 if entry not in entries and os.path.exists(e_dir):
msb@chromium.org83017012009-09-28 18:52:12 +0000750 modified_files = False
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +0000751 if isinstance(prev_entries, list):
msb@chromium.org83017012009-09-28 18:52:12 +0000752 # old .gclient_entries format was list, now dict
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +0000753 modified_files = gclient_scm.scm.SVN.CaptureStatus(e_dir)
msb@chromium.org83017012009-09-28 18:52:12 +0000754 else:
755 file_list = []
756 scm = gclient_scm.CreateSCM(prev_entries[entry], self._root_dir,
757 entry_fixed)
758 scm.status(self._options, [], file_list)
759 modified_files = file_list != []
760 if not self._options.delete_unversioned_trees or modified_files:
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000761 # There are modified files in this entry. Keep warning until
762 # removed.
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000763 print(("\nWARNING: \"%s\" is no longer part of this client. "
764 "It is recommended that you manually remove it.\n") %
765 entry_fixed)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000766 else:
767 # Delete the entry
maruel@chromium.orgdf7a3132009-05-12 17:49:49 +0000768 print("\n________ deleting \'%s\' " +
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000769 "in \'%s\'") % (entry_fixed, self._root_dir)
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000770 gclient_utils.RemoveDirectory(e_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000771 # record the current list of entries for next time
772 self._SaveEntries(entries)
773
774 def PrintRevInfo(self):
775 """Output revision info mapping for the client and its dependencies. This
776 allows the capture of a overall "revision" for the source tree that can
777 be used to reproduce the same tree in the future. The actual output
778 contains enough information (source paths, svn server urls and revisions)
779 that it can be used either to generate external svn commands (without
780 gclient) or as input to gclient's --rev option (with some massaging of
781 the data).
782
783 NOTE: Unlike RunOnDeps this does not require a local checkout and is run
784 on the Pulse master. It MUST NOT execute hooks.
785
786 Raises:
787 Error: If the client has conflicting entries.
788 """
789 # Check for revision overrides.
790 revision_overrides = {}
791 for revision in self._options.revisions:
792 if revision.find("@") < 0:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000793 raise gclient_utils.Error(
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000794 "Specify the full dependency when specifying a revision number.")
795 revision_elem = revision.split("@")
796 # Disallow conflicting revs
797 if revision_overrides.has_key(revision_elem[0]) and \
798 revision_overrides[revision_elem[0]] != revision_elem[1]:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000799 raise gclient_utils.Error(
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000800 "Conflicting revision numbers specified.")
801 revision_overrides[revision_elem[0]] = revision_elem[1]
802
803 solutions = self.GetVar("solutions")
804 if not solutions:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000805 raise gclient_utils.Error("No solution specified")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000806
807 entries = {}
808 entries_deps_content = {}
809
810 # Inner helper to generate base url and rev tuple (including honoring
811 # |revision_overrides|)
812 def GetURLAndRev(name, original_url):
msb@chromium.orgd5a035e2009-11-13 17:58:26 +0000813 url, revision = gclient_utils.SplitUrlRevision(original_url)
msb@chromium.orgac915bb2009-11-13 17:03:01 +0000814 if not revision:
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000815 if revision_overrides.has_key(name):
msb@chromium.orgac915bb2009-11-13 17:03:01 +0000816 return (url, revision_overrides[name])
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000817 else:
msb@chromium.org0f282062009-11-06 20:14:02 +0000818 scm = gclient_scm.CreateSCM(solution["url"], self._root_dir, name)
msb@chromium.orgac915bb2009-11-13 17:03:01 +0000819 return (url, scm.revinfo(self._options, [], None))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000820 else:
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000821 if revision_overrides.has_key(name):
msb@chromium.orgac915bb2009-11-13 17:03:01 +0000822 return (url, revision_overrides[name])
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000823 else:
msb@chromium.orgac915bb2009-11-13 17:03:01 +0000824 return (url, revision)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000825
826 # Run on the base solutions first.
827 for solution in solutions:
828 name = solution["name"]
829 if name in entries:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000830 raise gclient_utils.Error("solution %s specified more than once" % name)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000831 (url, rev) = GetURLAndRev(name, solution["url"])
msb@chromium.org770ff9e2009-09-23 17:18:18 +0000832 entries[name] = "%s@%s" % (url, rev)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000833 # TODO(aharper): SVN/SCMWrapper cleanup (non-local commandset)
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +0000834 entries_deps_content[name] = gclient_scm.scm.SVN.Capture(
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000835 ["cat",
msb@chromium.org770ff9e2009-09-23 17:18:18 +0000836 "%s/%s@%s" % (url,
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000837 self._options.deps_file,
838 rev)],
839 os.getcwd())
840
841 # Process the dependencies next (sort alphanumerically to ensure that
842 # containing directories get populated first and for readability)
843 deps = self._ParseAllDeps(entries, entries_deps_content)
844 deps_to_process = deps.keys()
845 deps_to_process.sort()
846
847 # First pass for direct dependencies.
848 for d in deps_to_process:
849 if type(deps[d]) == str:
850 (url, rev) = GetURLAndRev(d, deps[d])
msb@chromium.org770ff9e2009-09-23 17:18:18 +0000851 entries[d] = "%s@%s" % (url, rev)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000852
853 # Second pass for inherited deps (via the From keyword)
854 for d in deps_to_process:
855 if type(deps[d]) != str:
856 deps_parent_url = entries[deps[d].module_name]
857 if deps_parent_url.find("@") < 0:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000858 raise gclient_utils.Error("From %s missing revisioned url" %
859 deps[d].module_name)
860 content = gclient_utils.FileRead(os.path.join(self._root_dir,
861 deps[d].module_name,
862 self._options.deps_file))
863 sub_deps = self._ParseSolutionDeps(deps[d].module_name, content, {})
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000864 (url, rev) = GetURLAndRev(d, sub_deps[d])
msb@chromium.org770ff9e2009-09-23 17:18:18 +0000865 entries[d] = "%s@%s" % (url, rev)
maruel@chromium.org57e893e2009-08-19 18:12:09 +0000866 print(";\n\n".join(["%s: %s" % (x, entries[x])
867 for x in sorted(entries.keys())]))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000868
869
870## gclient commands.
871
872
873def DoCleanup(options, args):
874 """Handle the cleanup subcommand.
875
876 Raises:
877 Error: if client isn't configured properly.
878 """
maruel@chromium.org2806acc2009-05-15 12:33:34 +0000879 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000880 if not client:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000881 raise gclient_utils.Error("client not configured; see 'gclient config'")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000882 if options.verbose:
883 # Print out the .gclient file. This is longer than if we just printed the
884 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.orgdf7a3132009-05-12 17:49:49 +0000885 print(client.ConfigContent())
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000886 return client.RunOnDeps('cleanup', args)
887
888
889def DoConfig(options, args):
890 """Handle the config subcommand.
891
892 Args:
893 options: If options.spec set, a string providing contents of config file.
894 args: The command line args. If spec is not set,
895 then args[0] is a string URL to get for config file.
896
897 Raises:
898 Error: on usage error
899 """
900 if len(args) < 1 and not options.spec:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000901 raise gclient_utils.Error("required argument missing; see 'gclient help "
902 "config'")
maruel@chromium.org0329e672009-05-13 18:41:04 +0000903 if os.path.exists(options.config_filename):
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000904 raise gclient_utils.Error("%s file already exists in the current directory"
905 % options.config_filename)
maruel@chromium.org2806acc2009-05-15 12:33:34 +0000906 client = GClient('.', options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000907 if options.spec:
908 client.SetConfig(options.spec)
909 else:
910 # TODO(darin): it would be nice to be able to specify an alternate relpath
911 # for the given URL.
maruel@chromium.org1ab7ffc2009-06-03 17:21:37 +0000912 base_url = args[0].rstrip('/')
913 name = base_url.split("/")[-1]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000914 safesync_url = ""
915 if len(args) > 1:
916 safesync_url = args[1]
917 client.SetDefaultConfig(name, base_url, safesync_url)
918 client.SaveConfig()
919
920
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +0000921def DoExport(options, args):
922 """Handle the export subcommand.
phajdan.jr@chromium.org71b40682009-07-31 23:40:09 +0000923
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +0000924 Raises:
925 Error: on usage error
926 """
927 if len(args) != 1:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000928 raise gclient_utils.Error("Need directory name")
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +0000929 client = GClient.LoadCurrentConfig(options)
930
931 if not client:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000932 raise gclient_utils.Error("client not configured; see 'gclient config'")
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +0000933
934 if options.verbose:
935 # Print out the .gclient file. This is longer than if we just printed the
936 # client dict, but more legible, and it might contain helpful comments.
937 print(client.ConfigContent())
938 return client.RunOnDeps('export', args)
939
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000940def DoHelp(options, args):
941 """Handle the help subcommand giving help for another subcommand.
942
943 Raises:
944 Error: if the command is unknown.
945 """
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000946 __pychecker__ = 'unusednames=options'
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000947 if len(args) == 1 and args[0] in COMMAND_USAGE_TEXT:
maruel@chromium.orgdf7a3132009-05-12 17:49:49 +0000948 print(COMMAND_USAGE_TEXT[args[0]])
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000949 else:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000950 raise gclient_utils.Error("unknown subcommand '%s'; see 'gclient help'" %
951 args[0])
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000952
953
kbr@google.comab318592009-09-04 00:54:55 +0000954def DoPack(options, args):
955 """Handle the pack subcommand.
956
957 Raises:
958 Error: if client isn't configured properly.
959 """
960 client = GClient.LoadCurrentConfig(options)
961 if not client:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000962 raise gclient_utils.Error("client not configured; see 'gclient config'")
kbr@google.comab318592009-09-04 00:54:55 +0000963 if options.verbose:
964 # Print out the .gclient file. This is longer than if we just printed the
965 # client dict, but more legible, and it might contain helpful comments.
966 print(client.ConfigContent())
kbr@google.comab318592009-09-04 00:54:55 +0000967 return client.RunOnDeps('pack', args)
968
969
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000970def DoStatus(options, args):
971 """Handle the status subcommand.
972
973 Raises:
974 Error: if client isn't configured properly.
975 """
maruel@chromium.org2806acc2009-05-15 12:33:34 +0000976 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000977 if not client:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000978 raise gclient_utils.Error("client not configured; see 'gclient config'")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000979 if options.verbose:
980 # Print out the .gclient file. This is longer than if we just printed the
981 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.orgdf7a3132009-05-12 17:49:49 +0000982 print(client.ConfigContent())
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000983 return client.RunOnDeps('status', args)
984
985
986def DoUpdate(options, args):
987 """Handle the update and sync subcommands.
988
989 Raises:
990 Error: if client isn't configured properly.
991 """
maruel@chromium.org2806acc2009-05-15 12:33:34 +0000992 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000993
994 if not client:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000995 raise gclient_utils.Error("client not configured; see 'gclient config'")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000996
997 if not options.head:
998 solutions = client.GetVar('solutions')
999 if solutions:
1000 for s in solutions:
1001 if s.get('safesync_url', ''):
1002 # rip through revisions and make sure we're not over-riding
1003 # something that was explicitly passed
1004 has_key = False
1005 for r in options.revisions:
1006 if r.split('@')[0] == s['name']:
1007 has_key = True
1008 break
1009
1010 if not has_key:
1011 handle = urllib.urlopen(s['safesync_url'])
1012 rev = handle.read().strip()
1013 handle.close()
1014 if len(rev):
1015 options.revisions.append(s['name']+'@'+rev)
1016
1017 if options.verbose:
1018 # Print out the .gclient file. This is longer than if we just printed the
1019 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.orgdf7a3132009-05-12 17:49:49 +00001020 print(client.ConfigContent())
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001021 return client.RunOnDeps('update', args)
1022
1023
1024def DoDiff(options, args):
1025 """Handle the diff subcommand.
1026
1027 Raises:
1028 Error: if client isn't configured properly.
1029 """
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001030 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001031 if not client:
maruel@chromium.orge3608df2009-11-10 20:22:57 +00001032 raise gclient_utils.Error("client not configured; see 'gclient config'")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001033 if options.verbose:
1034 # Print out the .gclient file. This is longer than if we just printed the
1035 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.orgdf7a3132009-05-12 17:49:49 +00001036 print(client.ConfigContent())
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001037 return client.RunOnDeps('diff', args)
1038
1039
1040def DoRevert(options, args):
1041 """Handle the revert subcommand.
1042
1043 Raises:
1044 Error: if client isn't configured properly.
1045 """
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001046 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001047 if not client:
maruel@chromium.orge3608df2009-11-10 20:22:57 +00001048 raise gclient_utils.Error("client not configured; see 'gclient config'")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001049 return client.RunOnDeps('revert', args)
1050
1051
1052def DoRunHooks(options, args):
1053 """Handle the runhooks subcommand.
1054
1055 Raises:
1056 Error: if client isn't configured properly.
1057 """
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001058 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001059 if not client:
maruel@chromium.orge3608df2009-11-10 20:22:57 +00001060 raise gclient_utils.Error("client not configured; see 'gclient config'")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001061 if options.verbose:
1062 # Print out the .gclient file. This is longer than if we just printed the
1063 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.orgdf7a3132009-05-12 17:49:49 +00001064 print(client.ConfigContent())
maruel@chromium.org5df6a462009-08-28 18:52:26 +00001065 options.force = True
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001066 return client.RunOnDeps('runhooks', args)
1067
1068
1069def DoRevInfo(options, args):
1070 """Handle the revinfo subcommand.
1071
1072 Raises:
1073 Error: if client isn't configured properly.
1074 """
maruel@chromium.orge3608df2009-11-10 20:22:57 +00001075 __pychecker__ = 'unusednames=args'
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001076 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001077 if not client:
maruel@chromium.orge3608df2009-11-10 20:22:57 +00001078 raise gclient_utils.Error("client not configured; see 'gclient config'")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001079 client.PrintRevInfo()
1080
1081
1082gclient_command_map = {
1083 "cleanup": DoCleanup,
1084 "config": DoConfig,
1085 "diff": DoDiff,
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +00001086 "export": DoExport,
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001087 "help": DoHelp,
kbr@google.comab318592009-09-04 00:54:55 +00001088 "pack": DoPack,
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001089 "status": DoStatus,
1090 "sync": DoUpdate,
1091 "update": DoUpdate,
1092 "revert": DoRevert,
1093 "runhooks": DoRunHooks,
1094 "revinfo" : DoRevInfo,
1095}
1096
1097
1098def DispatchCommand(command, options, args, command_map=None):
1099 """Dispatches the appropriate subcommand based on command line arguments."""
1100 if command_map is None:
1101 command_map = gclient_command_map
1102
1103 if command in command_map:
1104 return command_map[command](options, args)
1105 else:
maruel@chromium.orge3608df2009-11-10 20:22:57 +00001106 raise gclient_utils.Error("unknown subcommand '%s'; see 'gclient help'" %
1107 command)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001108
1109
1110def Main(argv):
1111 """Parse command line arguments and dispatch command."""
1112
1113 option_parser = optparse.OptionParser(usage=DEFAULT_USAGE_TEXT,
1114 version=__version__)
1115 option_parser.disable_interspersed_args()
1116 option_parser.add_option("", "--force", action="store_true", default=False,
1117 help=("(update/sync only) force update even "
1118 "for modules which haven't changed"))
evan@chromium.org67820ef2009-07-27 17:23:00 +00001119 option_parser.add_option("", "--nohooks", action="store_true", default=False,
1120 help=("(update/sync/revert only) prevent the hooks from "
1121 "running"))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001122 option_parser.add_option("", "--revision", action="append", dest="revisions",
1123 metavar="REV", default=[],
1124 help=("(update/sync only) sync to a specific "
1125 "revision, can be used multiple times for "
1126 "each solution, e.g. --revision=src@123, "
1127 "--revision=internal@32"))
1128 option_parser.add_option("", "--deps", default=None, dest="deps_os",
1129 metavar="OS_LIST",
1130 help=("(update/sync only) sync deps for the "
1131 "specified (comma-separated) platform(s); "
1132 "'all' will sync all platforms"))
1133 option_parser.add_option("", "--spec", default=None,
1134 help=("(config only) create a gclient file "
1135 "containing the provided string"))
1136 option_parser.add_option("", "--verbose", action="store_true", default=False,
1137 help="produce additional output for diagnostics")
maruel@chromium.org7753d242009-10-07 17:40:24 +00001138 option_parser.add_option("", "--manually_grab_svn_rev", action="store_true",
1139 default=False,
1140 help="Skip svn up whenever possible by requesting "
1141 "actual HEAD revision from the repository")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001142 option_parser.add_option("", "--head", action="store_true", default=False,
1143 help=("skips any safesync_urls specified in "
1144 "configured solutions"))
ajwong@chromium.orgcdcee802009-06-23 15:30:42 +00001145 option_parser.add_option("", "--delete_unversioned_trees",
1146 action="store_true", default=False,
1147 help=("on update, delete any unexpected "
1148 "unversioned trees that are in the checkout"))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001149
1150 if len(argv) < 2:
1151 # Users don't need to be told to use the 'help' command.
1152 option_parser.print_help()
1153 return 1
1154 # Add manual support for --version as first argument.
1155 if argv[1] == '--version':
1156 option_parser.print_version()
1157 return 0
1158
1159 # Add manual support for --help as first argument.
1160 if argv[1] == '--help':
1161 argv[1] = 'help'
1162
1163 command = argv[1]
1164 options, args = option_parser.parse_args(argv[2:])
1165
1166 if len(argv) < 3 and command == "help":
1167 option_parser.print_help()
1168 return 0
1169
maruel@chromium.org754960e2009-09-21 12:31:05 +00001170 if options.verbose:
1171 logging.basicConfig(level=logging.DEBUG)
1172
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001173 # Files used for configuration and state saving.
1174 options.config_filename = os.environ.get("GCLIENT_FILE", ".gclient")
1175 options.entries_filename = ".gclient_entries"
1176 options.deps_file = "DEPS"
1177
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001178 options.platform = sys.platform
1179 return DispatchCommand(command, options, args)
1180
1181
1182if "__main__" == __name__:
1183 try:
1184 result = Main(sys.argv)
maruel@chromium.orge3608df2009-11-10 20:22:57 +00001185 except gclient_utils.Error, e:
maruel@chromium.orgdf7a3132009-05-12 17:49:49 +00001186 print >> sys.stderr, "Error: %s" % str(e)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001187 result = 1
1188 sys.exit(result)
1189
1190# vim: ts=2:sw=2:tw=80:et: