blob: 8090d202f20c897306bebe06942cd8b1d8587556 [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.orgada4c652009-12-03 15:32:01 +000081import breakpad
82
maruel@chromium.org5f3eee32009-09-17 00:34:30 +000083import gclient_scm
84import gclient_utils
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000085
86# default help text
87DEFAULT_USAGE_TEXT = (
88"""usage: %prog <subcommand> [options] [--] [svn options/args...]
89a wrapper for managing a set of client modules in svn.
90Version """ + __version__ + """
91
92subcommands:
93 cleanup
94 config
95 diff
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +000096 export
kbr@google.comab318592009-09-04 00:54:55 +000097 pack
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000098 revert
99 status
100 sync
101 update
102 runhooks
103 revinfo
104
105Options and extra arguments can be passed to invoked svn commands by
106appending them to the command line. Note that if the first such
107appended option starts with a dash (-) then the options must be
108preceded by -- to distinguish them from gclient options.
109
110For additional help on a subcommand or examples of usage, try
111 %prog help <subcommand>
112 %prog help files
113""")
114
115GENERIC_UPDATE_USAGE_TEXT = (
116 """Perform a checkout/update of the modules specified by the gclient
117configuration; see 'help config'. Unless --revision is specified,
118then the latest revision of the root solutions is checked out, with
119dependent submodule versions updated according to DEPS files.
120If --revision is specified, then the given revision is used in place
121of the latest, either for a single solution or for all solutions.
122Unless the --force option is provided, solutions and modules whose
123local revision matches the one to update (i.e., they have not changed
evan@chromium.org67820ef2009-07-27 17:23:00 +0000124in the repository) are *not* modified. Unless --nohooks is provided,
125the hooks are run.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000126This a synonym for 'gclient %(alias)s'
127
128usage: gclient %(cmd)s [options] [--] [svn update options/args]
129
130Valid options:
estade@chromium.org3b5cba42009-12-01 00:37:08 +0000131 --force : force update even for unchanged modules
132 --nohooks : don't run the hooks after the update is complete
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000133 --revision SOLUTION@REV : update given solution to specified revision
estade@chromium.org3b5cba42009-12-01 00:37:08 +0000134 --deps PLATFORM(S) : sync deps for the given platform(s), or 'all'
135 --verbose : output additional diagnostics
136 --head : update to latest revision, instead of last good revision
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000137
138Examples:
139 gclient %(cmd)s
140 update files from SVN according to current configuration,
141 *for modules which have changed since last update or sync*
142 gclient %(cmd)s --force
143 update files from SVN according to current configuration, for
144 all modules (useful for recovering files deleted from local copy)
estade@chromium.org3b5cba42009-12-01 00:37:08 +0000145 gclient %(cmd)s --revision src@31000
146 update src directory to r31000
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000147""")
148
149COMMAND_USAGE_TEXT = {
150 "cleanup":
151 """Clean up all working copies, using 'svn cleanup' for each module.
152Additional options and args may be passed to 'svn cleanup'.
153
154usage: cleanup [options] [--] [svn cleanup args/options]
155
156Valid options:
157 --verbose : output additional diagnostics
158""",
159 "config": """Create a .gclient file in the current directory; this
160specifies the configuration for further commands. After update/sync,
161top-level DEPS files in each module are read to determine dependent
162modules to operate on as well. If optional [url] parameter is
163provided, then configuration is read from a specified Subversion server
164URL. Otherwise, a --spec option must be provided.
165
166usage: config [option | url] [safesync url]
167
168Valid options:
169 --spec=GCLIENT_SPEC : contents of .gclient are read from string parameter.
170 *Note that due to Cygwin/Python brokenness, it
171 probably can't contain any newlines.*
172
173Examples:
174 gclient config https://gclient.googlecode.com/svn/trunk/gclient
175 configure a new client to check out gclient.py tool sources
176 gclient config --spec='solutions=[{"name":"gclient","""
177 '"url":"https://gclient.googlecode.com/svn/trunk/gclient",'
178 '"custom_deps":{}}]',
179 "diff": """Display the differences between two revisions of modules.
180(Does 'svn diff' for each checked out module and dependences.)
181Additional args and options to 'svn diff' can be passed after
182gclient options.
183
184usage: diff [options] [--] [svn args/options]
185
186Valid options:
187 --verbose : output additional diagnostics
188
189Examples:
190 gclient diff
191 simple 'svn diff' for configured client and dependences
192 gclient diff -- -x -b
193 use 'svn diff -x -b' to suppress whitespace-only differences
194 gclient diff -- -r HEAD -x -b
195 diff versus the latest version of each module
196""",
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +0000197 "export":
198 """Wrapper for svn export for all managed directories
199""",
kbr@google.comab318592009-09-04 00:54:55 +0000200 "pack":
201
202 """Generate a patch which can be applied at the root of the tree.
203Internally, runs 'svn diff' on each checked out module and
204dependencies, and performs minimal postprocessing of the output. The
205resulting patch is printed to stdout and can be applied to a freshly
206checked out tree via 'patch -p0 < patchfile'. Additional args and
207options to 'svn diff' can be passed after gclient options.
208
209usage: pack [options] [--] [svn args/options]
210
211Valid options:
212 --verbose : output additional diagnostics
213
214Examples:
215 gclient pack > patch.txt
216 generate simple patch for configured client and dependences
217 gclient pack -- -x -b > patch.txt
218 generate patch using 'svn diff -x -b' to suppress
219 whitespace-only differences
220 gclient pack -- -r HEAD -x -b > patch.txt
221 generate patch, diffing each file versus the latest version of
222 each module
223""",
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000224 "revert":
225 """Revert every file in every managed directory in the client view.
226
227usage: revert
228""",
229 "status":
230 """Show the status of client and dependent modules, using 'svn diff'
231for each module. Additional options and args may be passed to 'svn diff'.
232
233usage: status [options] [--] [svn diff args/options]
234
235Valid options:
236 --verbose : output additional diagnostics
evan@chromium.org67820ef2009-07-27 17:23:00 +0000237 --nohooks : don't run the hooks after the update is complete
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000238""",
239 "sync": GENERIC_UPDATE_USAGE_TEXT % {"cmd": "sync", "alias": "update"},
240 "update": GENERIC_UPDATE_USAGE_TEXT % {"cmd": "update", "alias": "sync"},
241 "help": """Describe the usage of this program or its subcommands.
242
243usage: help [options] [subcommand]
244
245Valid options:
246 --verbose : output additional diagnostics
247""",
248 "runhooks":
249 """Runs hooks for files that have been modified in the local working copy,
maruel@chromium.org5df6a462009-08-28 18:52:26 +0000250according to 'svn status'. Implies --force.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000251
252usage: runhooks [options]
253
254Valid options:
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000255 --verbose : output additional diagnostics
256""",
257 "revinfo":
258 """Outputs source path, server URL and revision information for every
259dependency in all solutions (no local checkout required).
260
261usage: revinfo [options]
262""",
263}
264
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000265DEFAULT_CLIENT_FILE_TEXT = ("""\
266# An element of this array (a "solution") describes a repository directory
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000267# that will be checked out into your working copy. Each solution may
268# optionally define additional dependencies (via its DEPS file) to be
269# checked out alongside the solution's directory. A solution may also
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000270# specify custom dependencies (via the "custom_deps" property) that
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000271# override or augment the dependencies specified by the DEPS file.
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000272# If a "safesync_url" is specified, it is assumed to reference the location of
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000273# a text file which contains nothing but the last known good SCM revision to
274# sync against. It is fetched if specified and used unless --head is passed
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000275
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000276solutions = [
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000277 { "name" : "%(solution_name)s",
278 "url" : "%(solution_url)s",
279 "custom_deps" : {
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000280 # To use the trunk of a component instead of what's in DEPS:
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000281 #"component": "https://svnserver/component/trunk/",
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000282 # To exclude a component from your working copy:
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000283 #"data/really_large_component": None,
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000284 },
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000285 "safesync_url": "%(safesync_url)s"
286 },
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000287]
288""")
289
290
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000291## GClient implementation.
292
293
294class GClient(object):
295 """Object that represent a gclient checkout."""
296
297 supported_commands = [
kbr@google.comab318592009-09-04 00:54:55 +0000298 'cleanup', 'diff', 'export', 'pack', 'revert', 'status', 'update',
299 'runhooks'
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000300 ]
301
302 def __init__(self, root_dir, options):
303 self._root_dir = root_dir
304 self._options = options
305 self._config_content = None
306 self._config_dict = {}
307 self._deps_hooks = []
308
309 def SetConfig(self, content):
310 self._config_dict = {}
311 self._config_content = content
skylined@chromium.orgdf0032c2009-05-29 10:43:56 +0000312 try:
313 exec(content, self._config_dict)
314 except SyntaxError, e:
315 try:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000316 __pychecker__ = 'no-objattrs'
skylined@chromium.orgdf0032c2009-05-29 10:43:56 +0000317 # Try to construct a human readable error message
318 error_message = [
319 'There is a syntax error in your configuration file.',
320 'Line #%s, character %s:' % (e.lineno, e.offset),
321 '"%s"' % re.sub(r'[\r\n]*$', '', e.text) ]
322 except:
323 # Something went wrong, re-raise the original exception
324 raise e
325 else:
326 # Raise a new exception with the human readable message:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000327 raise gclient_utils.Error('\n'.join(error_message))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000328
329 def SaveConfig(self):
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000330 gclient_utils.FileWrite(os.path.join(self._root_dir,
331 self._options.config_filename),
332 self._config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000333
334 def _LoadConfig(self):
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000335 client_source = gclient_utils.FileRead(
336 os.path.join(self._root_dir, self._options.config_filename))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000337 self.SetConfig(client_source)
338
339 def ConfigContent(self):
340 return self._config_content
341
342 def GetVar(self, key, default=None):
343 return self._config_dict.get(key, default)
344
345 @staticmethod
346 def LoadCurrentConfig(options, from_dir=None):
347 """Searches for and loads a .gclient file relative to the current working
348 dir.
349
350 Returns:
351 A dict representing the contents of the .gclient file or an empty dict if
352 the .gclient file doesn't exist.
353 """
354 if not from_dir:
355 from_dir = os.curdir
356 path = os.path.realpath(from_dir)
maruel@chromium.org0329e672009-05-13 18:41:04 +0000357 while not os.path.exists(os.path.join(path, options.config_filename)):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000358 next = os.path.split(path)
359 if not next[1]:
360 return None
361 path = next[0]
maruel@chromium.org2806acc2009-05-15 12:33:34 +0000362 client = GClient(path, options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000363 client._LoadConfig()
364 return client
365
366 def SetDefaultConfig(self, solution_name, solution_url, safesync_url):
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000367 self.SetConfig(DEFAULT_CLIENT_FILE_TEXT % {
368 'solution_name': solution_name,
369 'solution_url': solution_url,
370 'safesync_url' : safesync_url,
371 })
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000372
373 def _SaveEntries(self, entries):
374 """Creates a .gclient_entries file to record the list of unique checkouts.
375
376 The .gclient_entries file lives in the same directory as .gclient.
377
378 Args:
379 entries: A sequence of solution names.
380 """
msb@chromium.org2e38de72009-09-28 17:04:47 +0000381 text = "entries = \\\n" + pprint.pformat(entries, 2) + '\n'
382 file_path = os.path.join(self._root_dir, self._options.entries_filename)
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000383 gclient_utils.FileWrite(file_path, text)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000384
385 def _ReadEntries(self):
386 """Read the .gclient_entries file for the given client.
387
388 Args:
389 client: The client for which the entries file should be read.
390
391 Returns:
392 A sequence of solution names, which will be empty if there is the
393 entries file hasn't been created yet.
394 """
395 scope = {}
396 filename = os.path.join(self._root_dir, self._options.entries_filename)
maruel@chromium.org0329e672009-05-13 18:41:04 +0000397 if not os.path.exists(filename):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000398 return []
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000399 exec(gclient_utils.FileRead(filename), scope)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000400 return scope["entries"]
401
402 class FromImpl:
403 """Used to implement the From syntax."""
404
405 def __init__(self, module_name):
406 self.module_name = module_name
407
408 def __str__(self):
409 return 'From("%s")' % self.module_name
410
411 class _VarImpl:
412 def __init__(self, custom_vars, local_scope):
413 self._custom_vars = custom_vars
414 self._local_scope = local_scope
415
416 def Lookup(self, var_name):
417 """Implements the Var syntax."""
418 if var_name in self._custom_vars:
419 return self._custom_vars[var_name]
420 elif var_name in self._local_scope.get("vars", {}):
421 return self._local_scope["vars"][var_name]
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000422 raise gclient_utils.Error("Var is not defined: %s" % var_name)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000423
424 def _ParseSolutionDeps(self, solution_name, solution_deps_content,
425 custom_vars):
426 """Parses the DEPS file for the specified solution.
427
428 Args:
429 solution_name: The name of the solution to query.
430 solution_deps_content: Content of the DEPS file for the solution
431 custom_vars: A dict of vars to override any vars defined in the DEPS file.
432
433 Returns:
434 A dict mapping module names (as relative paths) to URLs or an empty
435 dict if the solution does not have a DEPS file.
436 """
437 # Skip empty
438 if not solution_deps_content:
439 return {}
440 # Eval the content
441 local_scope = {}
442 var = self._VarImpl(custom_vars, local_scope)
443 global_scope = {"From": self.FromImpl, "Var": var.Lookup, "deps_os": {}}
444 exec(solution_deps_content, global_scope, local_scope)
445 deps = local_scope.get("deps", {})
446
447 # load os specific dependencies if defined. these dependencies may
448 # override or extend the values defined by the 'deps' member.
449 if "deps_os" in local_scope:
450 deps_os_choices = {
451 "win32": "win",
452 "win": "win",
453 "cygwin": "win",
454 "darwin": "mac",
455 "mac": "mac",
456 "unix": "unix",
457 "linux": "unix",
458 "linux2": "unix",
459 }
460
461 if self._options.deps_os is not None:
462 deps_to_include = self._options.deps_os.split(",")
463 if "all" in deps_to_include:
464 deps_to_include = deps_os_choices.values()
465 else:
466 deps_to_include = [deps_os_choices.get(self._options.platform, "unix")]
467
468 deps_to_include = set(deps_to_include)
469 for deps_os_key in deps_to_include:
470 os_deps = local_scope["deps_os"].get(deps_os_key, {})
471 if len(deps_to_include) > 1:
472 # Ignore any overrides when including deps for more than one
473 # platform, so we collect the broadest set of dependencies available.
474 # We may end up with the wrong revision of something for our
475 # platform, but this is the best we can do.
476 deps.update([x for x in os_deps.items() if not x[0] in deps])
477 else:
478 deps.update(os_deps)
479
480 if 'hooks' in local_scope:
481 self._deps_hooks.extend(local_scope['hooks'])
482
483 # If use_relative_paths is set in the DEPS file, regenerate
484 # the dictionary using paths relative to the directory containing
485 # the DEPS file.
486 if local_scope.get('use_relative_paths'):
487 rel_deps = {}
488 for d, url in deps.items():
489 # normpath is required to allow DEPS to use .. in their
490 # dependency local path.
491 rel_deps[os.path.normpath(os.path.join(solution_name, d))] = url
492 return rel_deps
493 else:
494 return deps
495
496 def _ParseAllDeps(self, solution_urls, solution_deps_content):
497 """Parse the complete list of dependencies for the client.
498
499 Args:
500 solution_urls: A dict mapping module names (as relative paths) to URLs
501 corresponding to the solutions specified by the client. This parameter
502 is passed as an optimization.
503 solution_deps_content: A dict mapping module names to the content
504 of their DEPS files
505
506 Returns:
507 A dict mapping module names (as relative paths) to URLs corresponding
508 to the entire set of dependencies to checkout for the given client.
509
510 Raises:
511 Error: If a dependency conflicts with another dependency or of a solution.
512 """
513 deps = {}
514 for solution in self.GetVar("solutions"):
515 custom_vars = solution.get("custom_vars", {})
516 solution_deps = self._ParseSolutionDeps(
517 solution["name"],
518 solution_deps_content[solution["name"]],
519 custom_vars)
520
521 # If a line is in custom_deps, but not in the solution, we want to append
522 # this line to the solution.
523 if "custom_deps" in solution:
524 for d in solution["custom_deps"]:
525 if d not in solution_deps:
526 solution_deps[d] = solution["custom_deps"][d]
527
528 for d in solution_deps:
529 if "custom_deps" in solution and d in solution["custom_deps"]:
530 # Dependency is overriden.
531 url = solution["custom_deps"][d]
532 if url is None:
533 continue
534 else:
535 url = solution_deps[d]
536 # if we have a From reference dependent on another solution, then
537 # just skip the From reference. When we pull deps for the solution,
538 # we will take care of this dependency.
539 #
540 # If multiple solutions all have the same From reference, then we
541 # should only add one to our list of dependencies.
542 if type(url) != str:
543 if url.module_name in solution_urls:
544 # Already parsed.
545 continue
546 if d in deps and type(deps[d]) != str:
547 if url.module_name == deps[d].module_name:
548 continue
549 else:
550 parsed_url = urlparse.urlparse(url)
551 scheme = parsed_url[0]
552 if not scheme:
553 # A relative url. Fetch the real base.
554 path = parsed_url[2]
555 if path[0] != "/":
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000556 raise gclient_utils.Error(
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000557 "relative DEPS entry \"%s\" must begin with a slash" % d)
558 # Create a scm just to query the full url.
msb@chromium.orgcb5442b2009-09-22 16:51:24 +0000559 scm = gclient_scm.CreateSCM(solution["url"], self._root_dir,
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000560 None)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000561 url = scm.FullUrlForRelativeUrl(url)
562 if d in deps and deps[d] != url:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000563 raise gclient_utils.Error(
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000564 "Solutions have conflicting versions of dependency \"%s\"" % d)
565 if d in solution_urls and solution_urls[d] != url:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000566 raise gclient_utils.Error(
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000567 "Dependency \"%s\" conflicts with specified solution" % d)
568 # Grab the dependency.
569 deps[d] = url
570 return deps
571
phajdan.jr@chromium.org71b40682009-07-31 23:40:09 +0000572 def _RunHookAction(self, hook_dict, matching_file_list):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000573 """Runs the action from a single hook.
574 """
575 command = hook_dict['action'][:]
576 if command[0] == 'python':
577 # If the hook specified "python" as the first item, the action is a
578 # Python script. Run it by starting a new copy of the same
579 # interpreter.
580 command[0] = sys.executable
581
phajdan.jr@chromium.org71b40682009-07-31 23:40:09 +0000582 if '$matching_files' in command:
phajdan.jr@chromium.org68f2e092009-08-06 17:05:35 +0000583 splice_index = command.index('$matching_files')
584 command[splice_index:splice_index + 1] = matching_file_list
phajdan.jr@chromium.org71b40682009-07-31 23:40:09 +0000585
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000586 # Use a discrete exit status code of 2 to indicate that a hook action
587 # failed. Users of this script may wish to treat hook action failures
588 # differently from VC failures.
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000589 gclient_utils.SubprocessCall(command, self._root_dir, fail_status=2)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000590
591 def _RunHooks(self, command, file_list, is_using_git):
592 """Evaluates all hooks, running actions as needed.
593 """
594 # Hooks only run for these command types.
595 if not command in ('update', 'revert', 'runhooks'):
596 return
597
evan@chromium.org67820ef2009-07-27 17:23:00 +0000598 # Hooks only run when --nohooks is not specified
599 if self._options.nohooks:
600 return
601
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000602 # Get any hooks from the .gclient file.
603 hooks = self.GetVar("hooks", [])
604 # Add any hooks found in DEPS files.
605 hooks.extend(self._deps_hooks)
606
607 # If "--force" was specified, run all hooks regardless of what files have
608 # changed. If the user is using git, then we don't know what files have
609 # changed so we always run all hooks.
610 if self._options.force or is_using_git:
611 for hook_dict in hooks:
phajdan.jr@chromium.org71b40682009-07-31 23:40:09 +0000612 self._RunHookAction(hook_dict, [])
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000613 return
614
615 # Run hooks on the basis of whether the files from the gclient operation
616 # match each hook's pattern.
617 for hook_dict in hooks:
618 pattern = re.compile(hook_dict['pattern'])
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000619 matching_file_list = [f for f in file_list if pattern.search(f)]
phajdan.jr@chromium.org71b40682009-07-31 23:40:09 +0000620 if matching_file_list:
621 self._RunHookAction(hook_dict, matching_file_list)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000622
623 def RunOnDeps(self, command, args):
624 """Runs a command on each dependency in a client and its dependencies.
625
626 The module's dependencies are specified in its top-level DEPS files.
627
628 Args:
629 command: The command to use (e.g., 'status' or 'diff')
630 args: list of str - extra arguments to add to the command line.
631
632 Raises:
633 Error: If the client has conflicting entries.
634 """
635 if not command in self.supported_commands:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000636 raise gclient_utils.Error("'%s' is an unsupported command" % command)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000637
638 # Check for revision overrides.
639 revision_overrides = {}
640 for revision in self._options.revisions:
641 if revision.find("@") == -1:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000642 raise gclient_utils.Error(
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000643 "Specify the full dependency when specifying a revision number.")
644 revision_elem = revision.split("@")
645 # Disallow conflicting revs
646 if revision_overrides.has_key(revision_elem[0]) and \
647 revision_overrides[revision_elem[0]] != revision_elem[1]:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000648 raise gclient_utils.Error(
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000649 "Conflicting revision numbers specified.")
650 revision_overrides[revision_elem[0]] = revision_elem[1]
651
652 solutions = self.GetVar("solutions")
653 if not solutions:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000654 raise gclient_utils.Error("No solution specified")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000655
656 # When running runhooks --force, there's no need to consult the SCM.
657 # All known hooks are expected to run unconditionally regardless of working
658 # copy state, so skip the SCM status check.
659 run_scm = not (command == 'runhooks' and self._options.force)
660
661 entries = {}
662 entries_deps_content = {}
663 file_list = []
664 # Run on the base solutions first.
665 for solution in solutions:
666 name = solution["name"]
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000667 deps_file = solution.get("deps_file", self._options.deps_file)
668 if '/' in deps_file or '\\' in deps_file:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000669 raise gclient_utils.Error('deps_file name must not be a path, just a '
670 'filename.')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000671 if name in entries:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000672 raise gclient_utils.Error("solution %s specified more than once" % name)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000673 url = solution["url"]
674 entries[name] = url
yaar@chromium.orgf1328042009-09-22 23:14:23 +0000675 if run_scm and url:
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000676 self._options.revision = revision_overrides.get(name)
msb@chromium.orgcb5442b2009-09-22 16:51:24 +0000677 scm = gclient_scm.CreateSCM(url, self._root_dir, name)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000678 scm.RunCommand(command, self._options, args, file_list)
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000679 file_list = [os.path.join(name, f.strip()) for f in file_list]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000680 self._options.revision = None
681 try:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000682 deps_content = gclient_utils.FileRead(
683 os.path.join(self._root_dir, name, deps_file))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000684 except IOError, e:
685 if e.errno != errno.ENOENT:
686 raise
687 deps_content = ""
688 entries_deps_content[name] = deps_content
689
690 # Process the dependencies next (sort alphanumerically to ensure that
691 # containing directories get populated first and for readability)
692 deps = self._ParseAllDeps(entries, entries_deps_content)
693 deps_to_process = deps.keys()
694 deps_to_process.sort()
695
696 # First pass for direct dependencies.
697 for d in deps_to_process:
698 if type(deps[d]) == str:
699 url = deps[d]
700 entries[d] = url
701 if run_scm:
702 self._options.revision = revision_overrides.get(d)
msb@chromium.orgcb5442b2009-09-22 16:51:24 +0000703 scm = gclient_scm.CreateSCM(url, self._root_dir, d)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000704 scm.RunCommand(command, self._options, args, file_list)
705 self._options.revision = None
706
707 # Second pass for inherited deps (via the From keyword)
708 for d in deps_to_process:
709 if type(deps[d]) != str:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000710 filename = os.path.join(self._root_dir,
711 deps[d].module_name,
712 self._options.deps_file)
713 content = gclient_utils.FileRead(filename)
714 sub_deps = self._ParseSolutionDeps(deps[d].module_name, content, {})
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000715 url = sub_deps[d]
716 entries[d] = url
717 if run_scm:
718 self._options.revision = revision_overrides.get(d)
msb@chromium.orgcb5442b2009-09-22 16:51:24 +0000719 scm = gclient_scm.CreateSCM(url, self._root_dir, d)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000720 scm.RunCommand(command, self._options, args, file_list)
721 self._options.revision = None
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000722
phajdan.jr@chromium.orgd83b2b22009-08-11 15:30:55 +0000723 # Convert all absolute paths to relative.
724 for i in range(len(file_list)):
725 # TODO(phajdan.jr): We should know exactly when the paths are absolute.
726 # It depends on the command being executed (like runhooks vs sync).
727 if not os.path.isabs(file_list[i]):
728 continue
729
730 prefix = os.path.commonprefix([self._root_dir.lower(),
731 file_list[i].lower()])
732 file_list[i] = file_list[i][len(prefix):]
733
734 # Strip any leading path separators.
735 while file_list[i].startswith('\\') or file_list[i].startswith('/'):
736 file_list[i] = file_list[i][1:]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000737
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000738 is_using_git = gclient_utils.IsUsingGit(self._root_dir, entries.keys())
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000739 self._RunHooks(command, file_list, is_using_git)
740
741 if command == 'update':
ajwong@chromium.orgcdcee802009-06-23 15:30:42 +0000742 # Notify the user if there is an orphaned entry in their working copy.
743 # Only delete the directory if there are no changes in it, and
744 # delete_unversioned_trees is set to true.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000745 prev_entries = self._ReadEntries()
746 for entry in prev_entries:
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000747 # Fix path separator on Windows.
748 entry_fixed = entry.replace('/', os.path.sep)
749 e_dir = os.path.join(self._root_dir, entry_fixed)
750 # Use entry and not entry_fixed there.
maruel@chromium.org0329e672009-05-13 18:41:04 +0000751 if entry not in entries and os.path.exists(e_dir):
msb@chromium.org83017012009-09-28 18:52:12 +0000752 modified_files = False
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +0000753 if isinstance(prev_entries, list):
msb@chromium.org83017012009-09-28 18:52:12 +0000754 # old .gclient_entries format was list, now dict
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +0000755 modified_files = gclient_scm.scm.SVN.CaptureStatus(e_dir)
msb@chromium.org83017012009-09-28 18:52:12 +0000756 else:
757 file_list = []
758 scm = gclient_scm.CreateSCM(prev_entries[entry], self._root_dir,
759 entry_fixed)
760 scm.status(self._options, [], file_list)
761 modified_files = file_list != []
762 if not self._options.delete_unversioned_trees or modified_files:
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000763 # There are modified files in this entry. Keep warning until
764 # removed.
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000765 print(("\nWARNING: \"%s\" is no longer part of this client. "
766 "It is recommended that you manually remove it.\n") %
767 entry_fixed)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000768 else:
769 # Delete the entry
maruel@chromium.orgdf7a3132009-05-12 17:49:49 +0000770 print("\n________ deleting \'%s\' " +
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000771 "in \'%s\'") % (entry_fixed, self._root_dir)
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000772 gclient_utils.RemoveDirectory(e_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000773 # record the current list of entries for next time
774 self._SaveEntries(entries)
775
776 def PrintRevInfo(self):
777 """Output revision info mapping for the client and its dependencies. This
778 allows the capture of a overall "revision" for the source tree that can
779 be used to reproduce the same tree in the future. The actual output
780 contains enough information (source paths, svn server urls and revisions)
781 that it can be used either to generate external svn commands (without
782 gclient) or as input to gclient's --rev option (with some massaging of
783 the data).
784
785 NOTE: Unlike RunOnDeps this does not require a local checkout and is run
786 on the Pulse master. It MUST NOT execute hooks.
787
788 Raises:
789 Error: If the client has conflicting entries.
790 """
791 # Check for revision overrides.
792 revision_overrides = {}
793 for revision in self._options.revisions:
794 if revision.find("@") < 0:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000795 raise gclient_utils.Error(
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000796 "Specify the full dependency when specifying a revision number.")
797 revision_elem = revision.split("@")
798 # Disallow conflicting revs
799 if revision_overrides.has_key(revision_elem[0]) and \
800 revision_overrides[revision_elem[0]] != revision_elem[1]:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000801 raise gclient_utils.Error(
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000802 "Conflicting revision numbers specified.")
803 revision_overrides[revision_elem[0]] = revision_elem[1]
804
805 solutions = self.GetVar("solutions")
806 if not solutions:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000807 raise gclient_utils.Error("No solution specified")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000808
809 entries = {}
810 entries_deps_content = {}
811
812 # Inner helper to generate base url and rev tuple (including honoring
813 # |revision_overrides|)
814 def GetURLAndRev(name, original_url):
msb@chromium.orgd5a035e2009-11-13 17:58:26 +0000815 url, revision = gclient_utils.SplitUrlRevision(original_url)
msb@chromium.orgac915bb2009-11-13 17:03:01 +0000816 if not revision:
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000817 if revision_overrides.has_key(name):
msb@chromium.orgac915bb2009-11-13 17:03:01 +0000818 return (url, revision_overrides[name])
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000819 else:
msb@chromium.org0f282062009-11-06 20:14:02 +0000820 scm = gclient_scm.CreateSCM(solution["url"], self._root_dir, name)
msb@chromium.orgac915bb2009-11-13 17:03:01 +0000821 return (url, scm.revinfo(self._options, [], None))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000822 else:
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000823 if revision_overrides.has_key(name):
msb@chromium.orgac915bb2009-11-13 17:03:01 +0000824 return (url, revision_overrides[name])
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000825 else:
msb@chromium.orgac915bb2009-11-13 17:03:01 +0000826 return (url, revision)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000827
828 # Run on the base solutions first.
829 for solution in solutions:
830 name = solution["name"]
831 if name in entries:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000832 raise gclient_utils.Error("solution %s specified more than once" % name)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000833 (url, rev) = GetURLAndRev(name, solution["url"])
msb@chromium.org770ff9e2009-09-23 17:18:18 +0000834 entries[name] = "%s@%s" % (url, rev)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000835 # TODO(aharper): SVN/SCMWrapper cleanup (non-local commandset)
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +0000836 entries_deps_content[name] = gclient_scm.scm.SVN.Capture(
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000837 ["cat",
msb@chromium.org770ff9e2009-09-23 17:18:18 +0000838 "%s/%s@%s" % (url,
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000839 self._options.deps_file,
840 rev)],
841 os.getcwd())
842
843 # Process the dependencies next (sort alphanumerically to ensure that
844 # containing directories get populated first and for readability)
845 deps = self._ParseAllDeps(entries, entries_deps_content)
846 deps_to_process = deps.keys()
847 deps_to_process.sort()
848
849 # First pass for direct dependencies.
850 for d in deps_to_process:
851 if type(deps[d]) == str:
852 (url, rev) = GetURLAndRev(d, deps[d])
msb@chromium.org770ff9e2009-09-23 17:18:18 +0000853 entries[d] = "%s@%s" % (url, rev)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000854
855 # Second pass for inherited deps (via the From keyword)
856 for d in deps_to_process:
857 if type(deps[d]) != str:
858 deps_parent_url = entries[deps[d].module_name]
859 if deps_parent_url.find("@") < 0:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000860 raise gclient_utils.Error("From %s missing revisioned url" %
861 deps[d].module_name)
862 content = gclient_utils.FileRead(os.path.join(self._root_dir,
863 deps[d].module_name,
864 self._options.deps_file))
865 sub_deps = self._ParseSolutionDeps(deps[d].module_name, content, {})
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000866 (url, rev) = GetURLAndRev(d, sub_deps[d])
msb@chromium.org770ff9e2009-09-23 17:18:18 +0000867 entries[d] = "%s@%s" % (url, rev)
maruel@chromium.org57e893e2009-08-19 18:12:09 +0000868 print(";\n\n".join(["%s: %s" % (x, entries[x])
869 for x in sorted(entries.keys())]))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000870
871
872## gclient commands.
873
874
875def DoCleanup(options, args):
876 """Handle the cleanup subcommand.
877
878 Raises:
879 Error: if client isn't configured properly.
880 """
maruel@chromium.org2806acc2009-05-15 12:33:34 +0000881 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000882 if not client:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000883 raise gclient_utils.Error("client not configured; see 'gclient config'")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000884 if options.verbose:
885 # Print out the .gclient file. This is longer than if we just printed the
886 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.orgdf7a3132009-05-12 17:49:49 +0000887 print(client.ConfigContent())
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000888 return client.RunOnDeps('cleanup', args)
889
890
891def DoConfig(options, args):
892 """Handle the config subcommand.
893
894 Args:
895 options: If options.spec set, a string providing contents of config file.
896 args: The command line args. If spec is not set,
897 then args[0] is a string URL to get for config file.
898
899 Raises:
900 Error: on usage error
901 """
902 if len(args) < 1 and not options.spec:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000903 raise gclient_utils.Error("required argument missing; see 'gclient help "
904 "config'")
maruel@chromium.org0329e672009-05-13 18:41:04 +0000905 if os.path.exists(options.config_filename):
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000906 raise gclient_utils.Error("%s file already exists in the current directory"
907 % options.config_filename)
maruel@chromium.org2806acc2009-05-15 12:33:34 +0000908 client = GClient('.', options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000909 if options.spec:
910 client.SetConfig(options.spec)
911 else:
912 # TODO(darin): it would be nice to be able to specify an alternate relpath
913 # for the given URL.
maruel@chromium.org1ab7ffc2009-06-03 17:21:37 +0000914 base_url = args[0].rstrip('/')
915 name = base_url.split("/")[-1]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000916 safesync_url = ""
917 if len(args) > 1:
918 safesync_url = args[1]
919 client.SetDefaultConfig(name, base_url, safesync_url)
920 client.SaveConfig()
921
922
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +0000923def DoExport(options, args):
924 """Handle the export subcommand.
phajdan.jr@chromium.org71b40682009-07-31 23:40:09 +0000925
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +0000926 Raises:
927 Error: on usage error
928 """
929 if len(args) != 1:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000930 raise gclient_utils.Error("Need directory name")
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +0000931 client = GClient.LoadCurrentConfig(options)
932
933 if not client:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000934 raise gclient_utils.Error("client not configured; see 'gclient config'")
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +0000935
936 if options.verbose:
937 # Print out the .gclient file. This is longer than if we just printed the
938 # client dict, but more legible, and it might contain helpful comments.
939 print(client.ConfigContent())
940 return client.RunOnDeps('export', args)
941
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000942def DoHelp(options, args):
943 """Handle the help subcommand giving help for another subcommand.
944
945 Raises:
946 Error: if the command is unknown.
947 """
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000948 __pychecker__ = 'unusednames=options'
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000949 if len(args) == 1 and args[0] in COMMAND_USAGE_TEXT:
maruel@chromium.orgdf7a3132009-05-12 17:49:49 +0000950 print(COMMAND_USAGE_TEXT[args[0]])
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000951 else:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000952 raise gclient_utils.Error("unknown subcommand '%s'; see 'gclient help'" %
953 args[0])
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000954
955
kbr@google.comab318592009-09-04 00:54:55 +0000956def DoPack(options, args):
957 """Handle the pack subcommand.
958
959 Raises:
960 Error: if client isn't configured properly.
961 """
962 client = GClient.LoadCurrentConfig(options)
963 if not client:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000964 raise gclient_utils.Error("client not configured; see 'gclient config'")
kbr@google.comab318592009-09-04 00:54:55 +0000965 if options.verbose:
966 # Print out the .gclient file. This is longer than if we just printed the
967 # client dict, but more legible, and it might contain helpful comments.
968 print(client.ConfigContent())
kbr@google.comab318592009-09-04 00:54:55 +0000969 return client.RunOnDeps('pack', args)
970
971
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000972def DoStatus(options, args):
973 """Handle the status subcommand.
974
975 Raises:
976 Error: if client isn't configured properly.
977 """
maruel@chromium.org2806acc2009-05-15 12:33:34 +0000978 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000979 if not client:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000980 raise gclient_utils.Error("client not configured; see 'gclient config'")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000981 if options.verbose:
982 # Print out the .gclient file. This is longer than if we just printed the
983 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.orgdf7a3132009-05-12 17:49:49 +0000984 print(client.ConfigContent())
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000985 return client.RunOnDeps('status', args)
986
987
988def DoUpdate(options, args):
989 """Handle the update and sync subcommands.
990
991 Raises:
992 Error: if client isn't configured properly.
993 """
maruel@chromium.org2806acc2009-05-15 12:33:34 +0000994 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000995
996 if not client:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000997 raise gclient_utils.Error("client not configured; see 'gclient config'")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000998
999 if not options.head:
1000 solutions = client.GetVar('solutions')
1001 if solutions:
1002 for s in solutions:
1003 if s.get('safesync_url', ''):
1004 # rip through revisions and make sure we're not over-riding
1005 # something that was explicitly passed
1006 has_key = False
1007 for r in options.revisions:
1008 if r.split('@')[0] == s['name']:
1009 has_key = True
1010 break
1011
1012 if not has_key:
1013 handle = urllib.urlopen(s['safesync_url'])
1014 rev = handle.read().strip()
1015 handle.close()
1016 if len(rev):
1017 options.revisions.append(s['name']+'@'+rev)
1018
1019 if options.verbose:
1020 # Print out the .gclient file. This is longer than if we just printed the
1021 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.orgdf7a3132009-05-12 17:49:49 +00001022 print(client.ConfigContent())
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001023 return client.RunOnDeps('update', args)
1024
1025
1026def DoDiff(options, args):
1027 """Handle the diff subcommand.
1028
1029 Raises:
1030 Error: if client isn't configured properly.
1031 """
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001032 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001033 if not client:
maruel@chromium.orge3608df2009-11-10 20:22:57 +00001034 raise gclient_utils.Error("client not configured; see 'gclient config'")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001035 if options.verbose:
1036 # Print out the .gclient file. This is longer than if we just printed the
1037 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.orgdf7a3132009-05-12 17:49:49 +00001038 print(client.ConfigContent())
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001039 return client.RunOnDeps('diff', args)
1040
1041
1042def DoRevert(options, args):
1043 """Handle the revert subcommand.
1044
1045 Raises:
1046 Error: if client isn't configured properly.
1047 """
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001048 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001049 if not client:
maruel@chromium.orge3608df2009-11-10 20:22:57 +00001050 raise gclient_utils.Error("client not configured; see 'gclient config'")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001051 return client.RunOnDeps('revert', args)
1052
1053
1054def DoRunHooks(options, args):
1055 """Handle the runhooks subcommand.
1056
1057 Raises:
1058 Error: if client isn't configured properly.
1059 """
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001060 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001061 if not client:
maruel@chromium.orge3608df2009-11-10 20:22:57 +00001062 raise gclient_utils.Error("client not configured; see 'gclient config'")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001063 if options.verbose:
1064 # Print out the .gclient file. This is longer than if we just printed the
1065 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.orgdf7a3132009-05-12 17:49:49 +00001066 print(client.ConfigContent())
maruel@chromium.org5df6a462009-08-28 18:52:26 +00001067 options.force = True
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001068 return client.RunOnDeps('runhooks', args)
1069
1070
1071def DoRevInfo(options, args):
1072 """Handle the revinfo subcommand.
1073
1074 Raises:
1075 Error: if client isn't configured properly.
1076 """
maruel@chromium.orge3608df2009-11-10 20:22:57 +00001077 __pychecker__ = 'unusednames=args'
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001078 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001079 if not client:
maruel@chromium.orge3608df2009-11-10 20:22:57 +00001080 raise gclient_utils.Error("client not configured; see 'gclient config'")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001081 client.PrintRevInfo()
1082
1083
1084gclient_command_map = {
1085 "cleanup": DoCleanup,
1086 "config": DoConfig,
1087 "diff": DoDiff,
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +00001088 "export": DoExport,
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001089 "help": DoHelp,
kbr@google.comab318592009-09-04 00:54:55 +00001090 "pack": DoPack,
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001091 "status": DoStatus,
1092 "sync": DoUpdate,
1093 "update": DoUpdate,
1094 "revert": DoRevert,
1095 "runhooks": DoRunHooks,
1096 "revinfo" : DoRevInfo,
1097}
1098
1099
1100def DispatchCommand(command, options, args, command_map=None):
1101 """Dispatches the appropriate subcommand based on command line arguments."""
1102 if command_map is None:
1103 command_map = gclient_command_map
1104
1105 if command in command_map:
1106 return command_map[command](options, args)
1107 else:
maruel@chromium.orge3608df2009-11-10 20:22:57 +00001108 raise gclient_utils.Error("unknown subcommand '%s'; see 'gclient help'" %
1109 command)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001110
1111
1112def Main(argv):
1113 """Parse command line arguments and dispatch command."""
1114
1115 option_parser = optparse.OptionParser(usage=DEFAULT_USAGE_TEXT,
1116 version=__version__)
1117 option_parser.disable_interspersed_args()
1118 option_parser.add_option("", "--force", action="store_true", default=False,
1119 help=("(update/sync only) force update even "
1120 "for modules which haven't changed"))
evan@chromium.org67820ef2009-07-27 17:23:00 +00001121 option_parser.add_option("", "--nohooks", action="store_true", default=False,
1122 help=("(update/sync/revert only) prevent the hooks from "
1123 "running"))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001124 option_parser.add_option("", "--revision", action="append", dest="revisions",
1125 metavar="REV", default=[],
1126 help=("(update/sync only) sync to a specific "
1127 "revision, can be used multiple times for "
1128 "each solution, e.g. --revision=src@123, "
1129 "--revision=internal@32"))
1130 option_parser.add_option("", "--deps", default=None, dest="deps_os",
1131 metavar="OS_LIST",
1132 help=("(update/sync only) sync deps for the "
1133 "specified (comma-separated) platform(s); "
1134 "'all' will sync all platforms"))
1135 option_parser.add_option("", "--spec", default=None,
1136 help=("(config only) create a gclient file "
1137 "containing the provided string"))
1138 option_parser.add_option("", "--verbose", action="store_true", default=False,
1139 help="produce additional output for diagnostics")
maruel@chromium.org7753d242009-10-07 17:40:24 +00001140 option_parser.add_option("", "--manually_grab_svn_rev", action="store_true",
1141 default=False,
1142 help="Skip svn up whenever possible by requesting "
1143 "actual HEAD revision from the repository")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001144 option_parser.add_option("", "--head", action="store_true", default=False,
1145 help=("skips any safesync_urls specified in "
1146 "configured solutions"))
ajwong@chromium.orgcdcee802009-06-23 15:30:42 +00001147 option_parser.add_option("", "--delete_unversioned_trees",
1148 action="store_true", default=False,
1149 help=("on update, delete any unexpected "
1150 "unversioned trees that are in the checkout"))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001151
1152 if len(argv) < 2:
1153 # Users don't need to be told to use the 'help' command.
1154 option_parser.print_help()
1155 return 1
1156 # Add manual support for --version as first argument.
1157 if argv[1] == '--version':
1158 option_parser.print_version()
1159 return 0
1160
1161 # Add manual support for --help as first argument.
1162 if argv[1] == '--help':
1163 argv[1] = 'help'
1164
1165 command = argv[1]
1166 options, args = option_parser.parse_args(argv[2:])
1167
1168 if len(argv) < 3 and command == "help":
1169 option_parser.print_help()
1170 return 0
1171
maruel@chromium.org754960e2009-09-21 12:31:05 +00001172 if options.verbose:
1173 logging.basicConfig(level=logging.DEBUG)
1174
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001175 # Files used for configuration and state saving.
1176 options.config_filename = os.environ.get("GCLIENT_FILE", ".gclient")
1177 options.entries_filename = ".gclient_entries"
1178 options.deps_file = "DEPS"
1179
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001180 options.platform = sys.platform
1181 return DispatchCommand(command, options, args)
1182
1183
1184if "__main__" == __name__:
1185 try:
1186 result = Main(sys.argv)
maruel@chromium.orge3608df2009-11-10 20:22:57 +00001187 except gclient_utils.Error, e:
maruel@chromium.orgdf7a3132009-05-12 17:49:49 +00001188 print >> sys.stderr, "Error: %s" % str(e)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001189 result = 1
1190 sys.exit(result)
1191
1192# vim: ts=2:sw=2:tw=80:et: