blob: e6a6432f9a31baa4f05ecd82ba9094dc302e0fb0 [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
msb@chromium.orgc532e172009-12-15 17:18:32 +0000521 local_scope = {}
522 var = self._VarImpl(custom_vars, local_scope)
523 global_scope = {"From": self.FromImpl, "Var": var.Lookup, "deps_os": {}}
524 exec(solution_deps_content[solution["name"]], global_scope, local_scope)
525
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000526 # If a line is in custom_deps, but not in the solution, we want to append
527 # this line to the solution.
528 if "custom_deps" in solution:
529 for d in solution["custom_deps"]:
530 if d not in solution_deps:
531 solution_deps[d] = solution["custom_deps"][d]
532
533 for d in solution_deps:
534 if "custom_deps" in solution and d in solution["custom_deps"]:
535 # Dependency is overriden.
536 url = solution["custom_deps"][d]
537 if url is None:
538 continue
539 else:
540 url = solution_deps[d]
541 # if we have a From reference dependent on another solution, then
542 # just skip the From reference. When we pull deps for the solution,
543 # we will take care of this dependency.
544 #
545 # If multiple solutions all have the same From reference, then we
546 # should only add one to our list of dependencies.
547 if type(url) != str:
548 if url.module_name in solution_urls:
549 # Already parsed.
550 continue
551 if d in deps and type(deps[d]) != str:
552 if url.module_name == deps[d].module_name:
553 continue
554 else:
555 parsed_url = urlparse.urlparse(url)
556 scheme = parsed_url[0]
557 if not scheme:
558 # A relative url. Fetch the real base.
559 path = parsed_url[2]
560 if path[0] != "/":
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000561 raise gclient_utils.Error(
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000562 "relative DEPS entry \"%s\" must begin with a slash" % d)
msb@chromium.orgc532e172009-12-15 17:18:32 +0000563 if local_scope.get('use_relative_urls2'):
564 url = gclient_utils.FullUrlFromRelative2(solution["url"], url)
565 else:
566 url = gclient_utils.FullUrlFromRelative(solution["url"], url)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000567 if d in deps and deps[d] != url:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000568 raise gclient_utils.Error(
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000569 "Solutions have conflicting versions of dependency \"%s\"" % d)
570 if d in solution_urls and solution_urls[d] != url:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000571 raise gclient_utils.Error(
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000572 "Dependency \"%s\" conflicts with specified solution" % d)
573 # Grab the dependency.
574 deps[d] = url
575 return deps
576
phajdan.jr@chromium.org71b40682009-07-31 23:40:09 +0000577 def _RunHookAction(self, hook_dict, matching_file_list):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000578 """Runs the action from a single hook.
579 """
580 command = hook_dict['action'][:]
581 if command[0] == 'python':
582 # If the hook specified "python" as the first item, the action is a
583 # Python script. Run it by starting a new copy of the same
584 # interpreter.
585 command[0] = sys.executable
586
phajdan.jr@chromium.org71b40682009-07-31 23:40:09 +0000587 if '$matching_files' in command:
phajdan.jr@chromium.org68f2e092009-08-06 17:05:35 +0000588 splice_index = command.index('$matching_files')
589 command[splice_index:splice_index + 1] = matching_file_list
phajdan.jr@chromium.org71b40682009-07-31 23:40:09 +0000590
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000591 # Use a discrete exit status code of 2 to indicate that a hook action
592 # failed. Users of this script may wish to treat hook action failures
593 # differently from VC failures.
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000594 gclient_utils.SubprocessCall(command, self._root_dir, fail_status=2)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000595
596 def _RunHooks(self, command, file_list, is_using_git):
597 """Evaluates all hooks, running actions as needed.
598 """
599 # Hooks only run for these command types.
600 if not command in ('update', 'revert', 'runhooks'):
601 return
602
evan@chromium.org67820ef2009-07-27 17:23:00 +0000603 # Hooks only run when --nohooks is not specified
604 if self._options.nohooks:
605 return
606
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000607 # Get any hooks from the .gclient file.
608 hooks = self.GetVar("hooks", [])
609 # Add any hooks found in DEPS files.
610 hooks.extend(self._deps_hooks)
611
612 # If "--force" was specified, run all hooks regardless of what files have
613 # changed. If the user is using git, then we don't know what files have
614 # changed so we always run all hooks.
615 if self._options.force or is_using_git:
616 for hook_dict in hooks:
phajdan.jr@chromium.org71b40682009-07-31 23:40:09 +0000617 self._RunHookAction(hook_dict, [])
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000618 return
619
620 # Run hooks on the basis of whether the files from the gclient operation
621 # match each hook's pattern.
622 for hook_dict in hooks:
623 pattern = re.compile(hook_dict['pattern'])
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000624 matching_file_list = [f for f in file_list if pattern.search(f)]
phajdan.jr@chromium.org71b40682009-07-31 23:40:09 +0000625 if matching_file_list:
626 self._RunHookAction(hook_dict, matching_file_list)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000627
628 def RunOnDeps(self, command, args):
629 """Runs a command on each dependency in a client and its dependencies.
630
631 The module's dependencies are specified in its top-level DEPS files.
632
633 Args:
634 command: The command to use (e.g., 'status' or 'diff')
635 args: list of str - extra arguments to add to the command line.
636
637 Raises:
638 Error: If the client has conflicting entries.
639 """
640 if not command in self.supported_commands:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000641 raise gclient_utils.Error("'%s' is an unsupported command" % command)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000642
643 # Check for revision overrides.
644 revision_overrides = {}
645 for revision in self._options.revisions:
646 if revision.find("@") == -1:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000647 raise gclient_utils.Error(
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000648 "Specify the full dependency when specifying a revision number.")
649 revision_elem = revision.split("@")
650 # Disallow conflicting revs
651 if revision_overrides.has_key(revision_elem[0]) and \
652 revision_overrides[revision_elem[0]] != revision_elem[1]:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000653 raise gclient_utils.Error(
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000654 "Conflicting revision numbers specified.")
655 revision_overrides[revision_elem[0]] = revision_elem[1]
656
657 solutions = self.GetVar("solutions")
658 if not solutions:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000659 raise gclient_utils.Error("No solution specified")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000660
661 # When running runhooks --force, there's no need to consult the SCM.
662 # All known hooks are expected to run unconditionally regardless of working
663 # copy state, so skip the SCM status check.
664 run_scm = not (command == 'runhooks' and self._options.force)
665
666 entries = {}
667 entries_deps_content = {}
668 file_list = []
669 # Run on the base solutions first.
670 for solution in solutions:
671 name = solution["name"]
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000672 deps_file = solution.get("deps_file", self._options.deps_file)
673 if '/' in deps_file or '\\' in deps_file:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000674 raise gclient_utils.Error('deps_file name must not be a path, just a '
675 'filename.')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000676 if name in entries:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000677 raise gclient_utils.Error("solution %s specified more than once" % name)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000678 url = solution["url"]
679 entries[name] = url
yaar@chromium.orgf1328042009-09-22 23:14:23 +0000680 if run_scm and url:
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000681 self._options.revision = revision_overrides.get(name)
msb@chromium.orgcb5442b2009-09-22 16:51:24 +0000682 scm = gclient_scm.CreateSCM(url, self._root_dir, name)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000683 scm.RunCommand(command, self._options, args, file_list)
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000684 file_list = [os.path.join(name, f.strip()) for f in file_list]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000685 self._options.revision = None
686 try:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000687 deps_content = gclient_utils.FileRead(
688 os.path.join(self._root_dir, name, deps_file))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000689 except IOError, e:
690 if e.errno != errno.ENOENT:
691 raise
692 deps_content = ""
693 entries_deps_content[name] = deps_content
694
695 # Process the dependencies next (sort alphanumerically to ensure that
696 # containing directories get populated first and for readability)
697 deps = self._ParseAllDeps(entries, entries_deps_content)
698 deps_to_process = deps.keys()
699 deps_to_process.sort()
700
701 # First pass for direct dependencies.
702 for d in deps_to_process:
703 if type(deps[d]) == str:
704 url = deps[d]
705 entries[d] = url
706 if run_scm:
707 self._options.revision = revision_overrides.get(d)
msb@chromium.orgcb5442b2009-09-22 16:51:24 +0000708 scm = gclient_scm.CreateSCM(url, self._root_dir, d)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000709 scm.RunCommand(command, self._options, args, file_list)
710 self._options.revision = None
711
712 # Second pass for inherited deps (via the From keyword)
713 for d in deps_to_process:
714 if type(deps[d]) != str:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000715 filename = os.path.join(self._root_dir,
716 deps[d].module_name,
717 self._options.deps_file)
718 content = gclient_utils.FileRead(filename)
719 sub_deps = self._ParseSolutionDeps(deps[d].module_name, content, {})
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000720 url = sub_deps[d]
721 entries[d] = url
722 if run_scm:
723 self._options.revision = revision_overrides.get(d)
msb@chromium.orgcb5442b2009-09-22 16:51:24 +0000724 scm = gclient_scm.CreateSCM(url, self._root_dir, d)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000725 scm.RunCommand(command, self._options, args, file_list)
726 self._options.revision = None
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000727
phajdan.jr@chromium.orgd83b2b22009-08-11 15:30:55 +0000728 # Convert all absolute paths to relative.
729 for i in range(len(file_list)):
730 # TODO(phajdan.jr): We should know exactly when the paths are absolute.
731 # It depends on the command being executed (like runhooks vs sync).
732 if not os.path.isabs(file_list[i]):
733 continue
734
735 prefix = os.path.commonprefix([self._root_dir.lower(),
736 file_list[i].lower()])
737 file_list[i] = file_list[i][len(prefix):]
738
739 # Strip any leading path separators.
740 while file_list[i].startswith('\\') or file_list[i].startswith('/'):
741 file_list[i] = file_list[i][1:]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000742
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000743 is_using_git = gclient_utils.IsUsingGit(self._root_dir, entries.keys())
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000744 self._RunHooks(command, file_list, is_using_git)
745
746 if command == 'update':
ajwong@chromium.orgcdcee802009-06-23 15:30:42 +0000747 # Notify the user if there is an orphaned entry in their working copy.
748 # Only delete the directory if there are no changes in it, and
749 # delete_unversioned_trees is set to true.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000750 prev_entries = self._ReadEntries()
751 for entry in prev_entries:
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000752 # Fix path separator on Windows.
753 entry_fixed = entry.replace('/', os.path.sep)
754 e_dir = os.path.join(self._root_dir, entry_fixed)
755 # Use entry and not entry_fixed there.
maruel@chromium.org0329e672009-05-13 18:41:04 +0000756 if entry not in entries and os.path.exists(e_dir):
msb@chromium.org83017012009-09-28 18:52:12 +0000757 modified_files = False
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +0000758 if isinstance(prev_entries, list):
msb@chromium.org83017012009-09-28 18:52:12 +0000759 # old .gclient_entries format was list, now dict
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +0000760 modified_files = gclient_scm.scm.SVN.CaptureStatus(e_dir)
msb@chromium.org83017012009-09-28 18:52:12 +0000761 else:
762 file_list = []
763 scm = gclient_scm.CreateSCM(prev_entries[entry], self._root_dir,
764 entry_fixed)
765 scm.status(self._options, [], file_list)
766 modified_files = file_list != []
767 if not self._options.delete_unversioned_trees or modified_files:
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000768 # There are modified files in this entry. Keep warning until
769 # removed.
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000770 print(("\nWARNING: \"%s\" is no longer part of this client. "
771 "It is recommended that you manually remove it.\n") %
772 entry_fixed)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000773 else:
774 # Delete the entry
maruel@chromium.orgdf7a3132009-05-12 17:49:49 +0000775 print("\n________ deleting \'%s\' " +
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000776 "in \'%s\'") % (entry_fixed, self._root_dir)
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000777 gclient_utils.RemoveDirectory(e_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000778 # record the current list of entries for next time
779 self._SaveEntries(entries)
780
781 def PrintRevInfo(self):
782 """Output revision info mapping for the client and its dependencies. This
783 allows the capture of a overall "revision" for the source tree that can
784 be used to reproduce the same tree in the future. The actual output
785 contains enough information (source paths, svn server urls and revisions)
786 that it can be used either to generate external svn commands (without
787 gclient) or as input to gclient's --rev option (with some massaging of
788 the data).
789
790 NOTE: Unlike RunOnDeps this does not require a local checkout and is run
791 on the Pulse master. It MUST NOT execute hooks.
792
793 Raises:
794 Error: If the client has conflicting entries.
795 """
796 # Check for revision overrides.
797 revision_overrides = {}
798 for revision in self._options.revisions:
799 if revision.find("@") < 0:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000800 raise gclient_utils.Error(
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000801 "Specify the full dependency when specifying a revision number.")
802 revision_elem = revision.split("@")
803 # Disallow conflicting revs
804 if revision_overrides.has_key(revision_elem[0]) and \
805 revision_overrides[revision_elem[0]] != revision_elem[1]:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000806 raise gclient_utils.Error(
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000807 "Conflicting revision numbers specified.")
808 revision_overrides[revision_elem[0]] = revision_elem[1]
809
810 solutions = self.GetVar("solutions")
811 if not solutions:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000812 raise gclient_utils.Error("No solution specified")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000813
814 entries = {}
815 entries_deps_content = {}
816
817 # Inner helper to generate base url and rev tuple (including honoring
818 # |revision_overrides|)
819 def GetURLAndRev(name, original_url):
msb@chromium.orgd5a035e2009-11-13 17:58:26 +0000820 url, revision = gclient_utils.SplitUrlRevision(original_url)
msb@chromium.orgac915bb2009-11-13 17:03:01 +0000821 if not revision:
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000822 if revision_overrides.has_key(name):
msb@chromium.orgac915bb2009-11-13 17:03:01 +0000823 return (url, revision_overrides[name])
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000824 else:
msb@chromium.org0f282062009-11-06 20:14:02 +0000825 scm = gclient_scm.CreateSCM(solution["url"], self._root_dir, name)
msb@chromium.orgac915bb2009-11-13 17:03:01 +0000826 return (url, scm.revinfo(self._options, [], None))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000827 else:
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000828 if revision_overrides.has_key(name):
msb@chromium.orgac915bb2009-11-13 17:03:01 +0000829 return (url, revision_overrides[name])
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000830 else:
msb@chromium.orgac915bb2009-11-13 17:03:01 +0000831 return (url, revision)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000832
833 # Run on the base solutions first.
834 for solution in solutions:
835 name = solution["name"]
836 if name in entries:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000837 raise gclient_utils.Error("solution %s specified more than once" % name)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000838 (url, rev) = GetURLAndRev(name, solution["url"])
msb@chromium.org770ff9e2009-09-23 17:18:18 +0000839 entries[name] = "%s@%s" % (url, rev)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000840 # TODO(aharper): SVN/SCMWrapper cleanup (non-local commandset)
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +0000841 entries_deps_content[name] = gclient_scm.scm.SVN.Capture(
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000842 ["cat",
msb@chromium.org770ff9e2009-09-23 17:18:18 +0000843 "%s/%s@%s" % (url,
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000844 self._options.deps_file,
845 rev)],
846 os.getcwd())
847
848 # Process the dependencies next (sort alphanumerically to ensure that
849 # containing directories get populated first and for readability)
850 deps = self._ParseAllDeps(entries, entries_deps_content)
851 deps_to_process = deps.keys()
852 deps_to_process.sort()
853
854 # First pass for direct dependencies.
855 for d in deps_to_process:
856 if type(deps[d]) == str:
857 (url, rev) = GetURLAndRev(d, deps[d])
msb@chromium.org770ff9e2009-09-23 17:18:18 +0000858 entries[d] = "%s@%s" % (url, rev)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000859
860 # Second pass for inherited deps (via the From keyword)
861 for d in deps_to_process:
862 if type(deps[d]) != str:
863 deps_parent_url = entries[deps[d].module_name]
864 if deps_parent_url.find("@") < 0:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000865 raise gclient_utils.Error("From %s missing revisioned url" %
866 deps[d].module_name)
867 content = gclient_utils.FileRead(os.path.join(self._root_dir,
868 deps[d].module_name,
869 self._options.deps_file))
870 sub_deps = self._ParseSolutionDeps(deps[d].module_name, content, {})
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000871 (url, rev) = GetURLAndRev(d, sub_deps[d])
msb@chromium.org770ff9e2009-09-23 17:18:18 +0000872 entries[d] = "%s@%s" % (url, rev)
maruel@chromium.org57e893e2009-08-19 18:12:09 +0000873 print(";\n\n".join(["%s: %s" % (x, entries[x])
874 for x in sorted(entries.keys())]))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000875
876
877## gclient commands.
878
879
880def DoCleanup(options, args):
881 """Handle the cleanup subcommand.
882
883 Raises:
884 Error: if client isn't configured properly.
885 """
maruel@chromium.org2806acc2009-05-15 12:33:34 +0000886 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000887 if not client:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000888 raise gclient_utils.Error("client not configured; see 'gclient config'")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000889 if options.verbose:
890 # Print out the .gclient file. This is longer than if we just printed the
891 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.orgdf7a3132009-05-12 17:49:49 +0000892 print(client.ConfigContent())
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000893 return client.RunOnDeps('cleanup', args)
894
895
896def DoConfig(options, args):
897 """Handle the config subcommand.
898
899 Args:
900 options: If options.spec set, a string providing contents of config file.
901 args: The command line args. If spec is not set,
902 then args[0] is a string URL to get for config file.
903
904 Raises:
905 Error: on usage error
906 """
907 if len(args) < 1 and not options.spec:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000908 raise gclient_utils.Error("required argument missing; see 'gclient help "
909 "config'")
maruel@chromium.org0329e672009-05-13 18:41:04 +0000910 if os.path.exists(options.config_filename):
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000911 raise gclient_utils.Error("%s file already exists in the current directory"
912 % options.config_filename)
maruel@chromium.org2806acc2009-05-15 12:33:34 +0000913 client = GClient('.', options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000914 if options.spec:
915 client.SetConfig(options.spec)
916 else:
917 # TODO(darin): it would be nice to be able to specify an alternate relpath
918 # for the given URL.
maruel@chromium.org1ab7ffc2009-06-03 17:21:37 +0000919 base_url = args[0].rstrip('/')
920 name = base_url.split("/")[-1]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000921 safesync_url = ""
922 if len(args) > 1:
923 safesync_url = args[1]
924 client.SetDefaultConfig(name, base_url, safesync_url)
925 client.SaveConfig()
926
927
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +0000928def DoExport(options, args):
929 """Handle the export subcommand.
phajdan.jr@chromium.org71b40682009-07-31 23:40:09 +0000930
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +0000931 Raises:
932 Error: on usage error
933 """
934 if len(args) != 1:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000935 raise gclient_utils.Error("Need directory name")
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +0000936 client = GClient.LoadCurrentConfig(options)
937
938 if not client:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000939 raise gclient_utils.Error("client not configured; see 'gclient config'")
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +0000940
941 if options.verbose:
942 # Print out the .gclient file. This is longer than if we just printed the
943 # client dict, but more legible, and it might contain helpful comments.
944 print(client.ConfigContent())
945 return client.RunOnDeps('export', args)
946
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000947def DoHelp(options, args):
948 """Handle the help subcommand giving help for another subcommand.
949
950 Raises:
951 Error: if the command is unknown.
952 """
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000953 __pychecker__ = 'unusednames=options'
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000954 if len(args) == 1 and args[0] in COMMAND_USAGE_TEXT:
maruel@chromium.orgdf7a3132009-05-12 17:49:49 +0000955 print(COMMAND_USAGE_TEXT[args[0]])
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000956 else:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000957 raise gclient_utils.Error("unknown subcommand '%s'; see 'gclient help'" %
958 args[0])
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000959
960
kbr@google.comab318592009-09-04 00:54:55 +0000961def DoPack(options, args):
962 """Handle the pack subcommand.
963
964 Raises:
965 Error: if client isn't configured properly.
966 """
967 client = GClient.LoadCurrentConfig(options)
968 if not client:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000969 raise gclient_utils.Error("client not configured; see 'gclient config'")
kbr@google.comab318592009-09-04 00:54:55 +0000970 if options.verbose:
971 # Print out the .gclient file. This is longer than if we just printed the
972 # client dict, but more legible, and it might contain helpful comments.
973 print(client.ConfigContent())
kbr@google.comab318592009-09-04 00:54:55 +0000974 return client.RunOnDeps('pack', args)
975
976
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000977def DoStatus(options, args):
978 """Handle the status subcommand.
979
980 Raises:
981 Error: if client isn't configured properly.
982 """
maruel@chromium.org2806acc2009-05-15 12:33:34 +0000983 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000984 if not client:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000985 raise gclient_utils.Error("client not configured; see 'gclient config'")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000986 if options.verbose:
987 # Print out the .gclient file. This is longer than if we just printed the
988 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.orgdf7a3132009-05-12 17:49:49 +0000989 print(client.ConfigContent())
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000990 return client.RunOnDeps('status', args)
991
992
993def DoUpdate(options, args):
994 """Handle the update and sync subcommands.
995
996 Raises:
997 Error: if client isn't configured properly.
998 """
maruel@chromium.org2806acc2009-05-15 12:33:34 +0000999 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001000
1001 if not client:
maruel@chromium.orge3608df2009-11-10 20:22:57 +00001002 raise gclient_utils.Error("client not configured; see 'gclient config'")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001003
1004 if not options.head:
1005 solutions = client.GetVar('solutions')
1006 if solutions:
1007 for s in solutions:
1008 if s.get('safesync_url', ''):
1009 # rip through revisions and make sure we're not over-riding
1010 # something that was explicitly passed
1011 has_key = False
1012 for r in options.revisions:
1013 if r.split('@')[0] == s['name']:
1014 has_key = True
1015 break
1016
1017 if not has_key:
1018 handle = urllib.urlopen(s['safesync_url'])
1019 rev = handle.read().strip()
1020 handle.close()
1021 if len(rev):
1022 options.revisions.append(s['name']+'@'+rev)
1023
1024 if options.verbose:
1025 # Print out the .gclient file. This is longer than if we just printed the
1026 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.orgdf7a3132009-05-12 17:49:49 +00001027 print(client.ConfigContent())
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001028 return client.RunOnDeps('update', args)
1029
1030
1031def DoDiff(options, args):
1032 """Handle the diff subcommand.
1033
1034 Raises:
1035 Error: if client isn't configured properly.
1036 """
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001037 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001038 if not client:
maruel@chromium.orge3608df2009-11-10 20:22:57 +00001039 raise gclient_utils.Error("client not configured; see 'gclient config'")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001040 if options.verbose:
1041 # Print out the .gclient file. This is longer than if we just printed the
1042 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.orgdf7a3132009-05-12 17:49:49 +00001043 print(client.ConfigContent())
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001044 return client.RunOnDeps('diff', args)
1045
1046
1047def DoRevert(options, args):
1048 """Handle the revert subcommand.
1049
1050 Raises:
1051 Error: if client isn't configured properly.
1052 """
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001053 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001054 if not client:
maruel@chromium.orge3608df2009-11-10 20:22:57 +00001055 raise gclient_utils.Error("client not configured; see 'gclient config'")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001056 return client.RunOnDeps('revert', args)
1057
1058
1059def DoRunHooks(options, args):
1060 """Handle the runhooks subcommand.
1061
1062 Raises:
1063 Error: if client isn't configured properly.
1064 """
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001065 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001066 if not client:
maruel@chromium.orge3608df2009-11-10 20:22:57 +00001067 raise gclient_utils.Error("client not configured; see 'gclient config'")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001068 if options.verbose:
1069 # Print out the .gclient file. This is longer than if we just printed the
1070 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.orgdf7a3132009-05-12 17:49:49 +00001071 print(client.ConfigContent())
maruel@chromium.org5df6a462009-08-28 18:52:26 +00001072 options.force = True
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001073 return client.RunOnDeps('runhooks', args)
1074
1075
1076def DoRevInfo(options, args):
1077 """Handle the revinfo subcommand.
1078
1079 Raises:
1080 Error: if client isn't configured properly.
1081 """
maruel@chromium.orge3608df2009-11-10 20:22:57 +00001082 __pychecker__ = 'unusednames=args'
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001083 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001084 if not client:
maruel@chromium.orge3608df2009-11-10 20:22:57 +00001085 raise gclient_utils.Error("client not configured; see 'gclient config'")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001086 client.PrintRevInfo()
1087
1088
1089gclient_command_map = {
1090 "cleanup": DoCleanup,
1091 "config": DoConfig,
1092 "diff": DoDiff,
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +00001093 "export": DoExport,
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001094 "help": DoHelp,
kbr@google.comab318592009-09-04 00:54:55 +00001095 "pack": DoPack,
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001096 "status": DoStatus,
1097 "sync": DoUpdate,
1098 "update": DoUpdate,
1099 "revert": DoRevert,
1100 "runhooks": DoRunHooks,
1101 "revinfo" : DoRevInfo,
1102}
1103
1104
1105def DispatchCommand(command, options, args, command_map=None):
1106 """Dispatches the appropriate subcommand based on command line arguments."""
1107 if command_map is None:
1108 command_map = gclient_command_map
1109
1110 if command in command_map:
1111 return command_map[command](options, args)
1112 else:
maruel@chromium.orge3608df2009-11-10 20:22:57 +00001113 raise gclient_utils.Error("unknown subcommand '%s'; see 'gclient help'" %
1114 command)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001115
1116
1117def Main(argv):
1118 """Parse command line arguments and dispatch command."""
1119
1120 option_parser = optparse.OptionParser(usage=DEFAULT_USAGE_TEXT,
1121 version=__version__)
1122 option_parser.disable_interspersed_args()
1123 option_parser.add_option("", "--force", action="store_true", default=False,
1124 help=("(update/sync only) force update even "
1125 "for modules which haven't changed"))
evan@chromium.org67820ef2009-07-27 17:23:00 +00001126 option_parser.add_option("", "--nohooks", action="store_true", default=False,
1127 help=("(update/sync/revert only) prevent the hooks from "
1128 "running"))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001129 option_parser.add_option("", "--revision", action="append", dest="revisions",
1130 metavar="REV", default=[],
1131 help=("(update/sync only) sync to a specific "
1132 "revision, can be used multiple times for "
1133 "each solution, e.g. --revision=src@123, "
1134 "--revision=internal@32"))
1135 option_parser.add_option("", "--deps", default=None, dest="deps_os",
1136 metavar="OS_LIST",
1137 help=("(update/sync only) sync deps for the "
1138 "specified (comma-separated) platform(s); "
1139 "'all' will sync all platforms"))
1140 option_parser.add_option("", "--spec", default=None,
1141 help=("(config only) create a gclient file "
1142 "containing the provided string"))
maruel@chromium.orga6220d12010-01-06 21:04:17 +00001143 option_parser.add_option("-v", "--verbose", action="count", default=0,
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001144 help="produce additional output for diagnostics")
maruel@chromium.org7753d242009-10-07 17:40:24 +00001145 option_parser.add_option("", "--manually_grab_svn_rev", action="store_true",
1146 default=False,
1147 help="Skip svn up whenever possible by requesting "
1148 "actual HEAD revision from the repository")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001149 option_parser.add_option("", "--head", action="store_true", default=False,
1150 help=("skips any safesync_urls specified in "
1151 "configured solutions"))
ajwong@chromium.orgcdcee802009-06-23 15:30:42 +00001152 option_parser.add_option("", "--delete_unversioned_trees",
1153 action="store_true", default=False,
1154 help=("on update, delete any unexpected "
1155 "unversioned trees that are in the checkout"))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001156
1157 if len(argv) < 2:
1158 # Users don't need to be told to use the 'help' command.
1159 option_parser.print_help()
1160 return 1
1161 # Add manual support for --version as first argument.
1162 if argv[1] == '--version':
1163 option_parser.print_version()
1164 return 0
1165
1166 # Add manual support for --help as first argument.
1167 if argv[1] == '--help':
1168 argv[1] = 'help'
1169
1170 command = argv[1]
1171 options, args = option_parser.parse_args(argv[2:])
1172
1173 if len(argv) < 3 and command == "help":
1174 option_parser.print_help()
1175 return 0
1176
maruel@chromium.orga6220d12010-01-06 21:04:17 +00001177 if options.verbose > 1:
maruel@chromium.org754960e2009-09-21 12:31:05 +00001178 logging.basicConfig(level=logging.DEBUG)
1179
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001180 # Files used for configuration and state saving.
1181 options.config_filename = os.environ.get("GCLIENT_FILE", ".gclient")
1182 options.entries_filename = ".gclient_entries"
1183 options.deps_file = "DEPS"
1184
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001185 options.platform = sys.platform
1186 return DispatchCommand(command, options, args)
1187
1188
1189if "__main__" == __name__:
1190 try:
1191 result = Main(sys.argv)
maruel@chromium.orge3608df2009-11-10 20:22:57 +00001192 except gclient_utils.Error, e:
maruel@chromium.orgdf7a3132009-05-12 17:49:49 +00001193 print >> sys.stderr, "Error: %s" % str(e)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001194 result = 1
1195 sys.exit(result)
1196
1197# vim: ts=2:sw=2:tw=80:et: