blob: 560badae13e11a7c540b99278b3b42557477f457 [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
msb@chromium.orgd6504212010-01-13 17:34:31 +000020program sources residing in one or more Subversion modules and Git
21repositories, along with other modules it depends on, also in Subversion or Git,
22but possibly on multiple respositories, making a wrapper system apparently
23necessary.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000024
25Files
26 .gclient : Current client configuration, written by 'config' command.
27 Format is a Python script defining 'solutions', a list whose
28 entries each are maps binding the strings "name" and "url"
29 to strings specifying the name and location of the client
30 module, as well as "custom_deps" to a map similar to the DEPS
31 file below.
32 .gclient_entries : A cache constructed by 'update' command. Format is a
33 Python script defining 'entries', a list of the names
34 of all modules in the client
35 <module>/DEPS : Python script defining var 'deps' as a map from each requisite
36 submodule name to a URL where it can be found (via one SCM)
37
38Hooks
39 .gclient and DEPS files may optionally contain a list named "hooks" to
40 allow custom actions to be performed based on files that have changed in the
evan@chromium.org67820ef2009-07-27 17:23:00 +000041 working copy as a result of a "sync"/"update" or "revert" operation. This
42 could be prevented by using --nohooks (hooks run by default). Hooks can also
maruel@chromium.org5df6a462009-08-28 18:52:26 +000043 be forced to run with the "runhooks" operation. If "sync" is run with
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000044 --force, all known hooks will run regardless of the state of the working
45 copy.
46
47 Each item in a "hooks" list is a dict, containing these two keys:
48 "pattern" The associated value is a string containing a regular
49 expression. When a file whose pathname matches the expression
50 is checked out, updated, or reverted, the hook's "action" will
51 run.
52 "action" A list describing a command to run along with its arguments, if
53 any. An action command will run at most one time per gclient
54 invocation, regardless of how many files matched the pattern.
55 The action is executed in the same directory as the .gclient
56 file. If the first item in the list is the string "python",
57 the current Python interpreter (sys.executable) will be used
phajdan.jr@chromium.org71b40682009-07-31 23:40:09 +000058 to run the command. If the list contains string "$matching_files"
59 it will be removed from the list and the list will be extended
60 by the list of matching files.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000061
62 Example:
63 hooks = [
64 { "pattern": "\\.(gif|jpe?g|pr0n|png)$",
65 "action": ["python", "image_indexer.py", "--all"]},
66 ]
67"""
68
69__author__ = "darinf@gmail.com (Darin Fisher)"
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +000070__version__ = "0.3.4"
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000071
72import errno
maruel@chromium.org754960e2009-09-21 12:31:05 +000073import logging
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000074import optparse
75import os
msb@chromium.org2e38de72009-09-28 17:04:47 +000076import pprint
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000077import re
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000078import sys
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000079import urlparse
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000080import urllib
81
maruel@chromium.orgada4c652009-12-03 15:32:01 +000082import breakpad
83
maruel@chromium.org5f3eee32009-09-17 00:34:30 +000084import gclient_scm
85import gclient_utils
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000086
87# default help text
88DEFAULT_USAGE_TEXT = (
msb@chromium.orgd6504212010-01-13 17:34:31 +000089"""usage: %prog <subcommand> [options] [--] [SCM options/args...]
90a wrapper for managing a set of svn client modules and/or git repositories.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000091Version """ + __version__ + """
92
93subcommands:
94 cleanup
95 config
96 diff
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +000097 export
kbr@google.comab318592009-09-04 00:54:55 +000098 pack
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000099 revert
100 status
101 sync
102 update
103 runhooks
104 revinfo
105
msb@chromium.orgd6504212010-01-13 17:34:31 +0000106Options and extra arguments can be passed to invoked SCM commands by
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000107appending them to the command line. Note that if the first such
108appended option starts with a dash (-) then the options must be
109preceded by -- to distinguish them from gclient options.
110
111For additional help on a subcommand or examples of usage, try
112 %prog help <subcommand>
113 %prog help files
114""")
115
116GENERIC_UPDATE_USAGE_TEXT = (
117 """Perform a checkout/update of the modules specified by the gclient
118configuration; see 'help config'. Unless --revision is specified,
119then the latest revision of the root solutions is checked out, with
120dependent submodule versions updated according to DEPS files.
121If --revision is specified, then the given revision is used in place
122of the latest, either for a single solution or for all solutions.
123Unless the --force option is provided, solutions and modules whose
124local revision matches the one to update (i.e., they have not changed
evan@chromium.org67820ef2009-07-27 17:23:00 +0000125in the repository) are *not* modified. Unless --nohooks is provided,
126the hooks are run.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000127This a synonym for 'gclient %(alias)s'
128
msb@chromium.orgd6504212010-01-13 17:34:31 +0000129usage: gclient %(cmd)s [options] [--] [SCM update options/args]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000130
131Valid options:
estade@chromium.org3b5cba42009-12-01 00:37:08 +0000132 --force : force update even for unchanged modules
133 --nohooks : don't run the hooks after the update is complete
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000134 --revision SOLUTION@REV : update given solution to specified revision
estade@chromium.org3b5cba42009-12-01 00:37:08 +0000135 --deps PLATFORM(S) : sync deps for the given platform(s), or 'all'
136 --verbose : output additional diagnostics
137 --head : update to latest revision, instead of last good revision
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000138
139Examples:
140 gclient %(cmd)s
msb@chromium.orgd6504212010-01-13 17:34:31 +0000141 update files from SCM according to current configuration,
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000142 *for modules which have changed since last update or sync*
143 gclient %(cmd)s --force
msb@chromium.orgd6504212010-01-13 17:34:31 +0000144 update files from SCM according to current configuration, for
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000145 all modules (useful for recovering files deleted from local copy)
estade@chromium.org3b5cba42009-12-01 00:37:08 +0000146 gclient %(cmd)s --revision src@31000
147 update src directory to r31000
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000148""")
149
150COMMAND_USAGE_TEXT = {
151 "cleanup":
152 """Clean up all working copies, using 'svn cleanup' for each module.
153Additional options and args may be passed to 'svn cleanup'.
154
155usage: cleanup [options] [--] [svn cleanup args/options]
156
157Valid options:
158 --verbose : output additional diagnostics
159""",
160 "config": """Create a .gclient file in the current directory; this
161specifies the configuration for further commands. After update/sync,
162top-level DEPS files in each module are read to determine dependent
163modules to operate on as well. If optional [url] parameter is
164provided, then configuration is read from a specified Subversion server
165URL. Otherwise, a --spec option must be provided.
166
167usage: config [option | url] [safesync url]
168
169Valid options:
170 --spec=GCLIENT_SPEC : contents of .gclient are read from string parameter.
171 *Note that due to Cygwin/Python brokenness, it
172 probably can't contain any newlines.*
173
174Examples:
175 gclient config https://gclient.googlecode.com/svn/trunk/gclient
176 configure a new client to check out gclient.py tool sources
177 gclient config --spec='solutions=[{"name":"gclient","""
178 '"url":"https://gclient.googlecode.com/svn/trunk/gclient",'
179 '"custom_deps":{}}]',
180 "diff": """Display the differences between two revisions of modules.
181(Does 'svn diff' for each checked out module and dependences.)
182Additional args and options to 'svn diff' can be passed after
183gclient options.
184
185usage: diff [options] [--] [svn args/options]
186
187Valid options:
188 --verbose : output additional diagnostics
189
190Examples:
191 gclient diff
192 simple 'svn diff' for configured client and dependences
193 gclient diff -- -x -b
194 use 'svn diff -x -b' to suppress whitespace-only differences
195 gclient diff -- -r HEAD -x -b
196 diff versus the latest version of each module
197""",
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +0000198 "export":
199 """Wrapper for svn export for all managed directories
200""",
kbr@google.comab318592009-09-04 00:54:55 +0000201 "pack":
202
203 """Generate a patch which can be applied at the root of the tree.
204Internally, runs 'svn diff' on each checked out module and
205dependencies, and performs minimal postprocessing of the output. The
206resulting patch is printed to stdout and can be applied to a freshly
207checked out tree via 'patch -p0 < patchfile'. Additional args and
208options to 'svn diff' can be passed after gclient options.
209
210usage: pack [options] [--] [svn args/options]
211
212Valid options:
213 --verbose : output additional diagnostics
214
215Examples:
216 gclient pack > patch.txt
217 generate simple patch for configured client and dependences
218 gclient pack -- -x -b > patch.txt
219 generate patch using 'svn diff -x -b' to suppress
220 whitespace-only differences
221 gclient pack -- -r HEAD -x -b > patch.txt
222 generate patch, diffing each file versus the latest version of
223 each module
224""",
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000225 "revert":
226 """Revert every file in every managed directory in the client view.
227
228usage: revert
229""",
230 "status":
231 """Show the status of client and dependent modules, using 'svn diff'
232for each module. Additional options and args may be passed to 'svn diff'.
233
234usage: status [options] [--] [svn diff args/options]
235
236Valid options:
237 --verbose : output additional diagnostics
evan@chromium.org67820ef2009-07-27 17:23:00 +0000238 --nohooks : don't run the hooks after the update is complete
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000239""",
240 "sync": GENERIC_UPDATE_USAGE_TEXT % {"cmd": "sync", "alias": "update"},
241 "update": GENERIC_UPDATE_USAGE_TEXT % {"cmd": "update", "alias": "sync"},
242 "help": """Describe the usage of this program or its subcommands.
243
244usage: help [options] [subcommand]
245
246Valid options:
247 --verbose : output additional diagnostics
248""",
249 "runhooks":
250 """Runs hooks for files that have been modified in the local working copy,
maruel@chromium.org5df6a462009-08-28 18:52:26 +0000251according to 'svn status'. Implies --force.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000252
253usage: runhooks [options]
254
255Valid options:
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000256 --verbose : output additional diagnostics
257""",
258 "revinfo":
259 """Outputs source path, server URL and revision information for every
260dependency in all solutions (no local checkout required).
261
262usage: revinfo [options]
263""",
264}
265
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000266DEFAULT_CLIENT_FILE_TEXT = ("""\
267# An element of this array (a "solution") describes a repository directory
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000268# that will be checked out into your working copy. Each solution may
269# optionally define additional dependencies (via its DEPS file) to be
270# checked out alongside the solution's directory. A solution may also
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000271# specify custom dependencies (via the "custom_deps" property) that
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000272# override or augment the dependencies specified by the DEPS file.
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000273# If a "safesync_url" is specified, it is assumed to reference the location of
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000274# a text file which contains nothing but the last known good SCM revision to
275# sync against. It is fetched if specified and used unless --head is passed
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000276
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000277solutions = [
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000278 { "name" : "%(solution_name)s",
279 "url" : "%(solution_url)s",
280 "custom_deps" : {
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000281 # To use the trunk of a component instead of what's in DEPS:
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000282 #"component": "https://svnserver/component/trunk/",
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000283 # To exclude a component from your working copy:
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000284 #"data/really_large_component": None,
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000285 },
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000286 "safesync_url": "%(safesync_url)s"
287 },
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000288]
289""")
290
291
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000292## GClient implementation.
293
294
295class GClient(object):
296 """Object that represent a gclient checkout."""
297
298 supported_commands = [
kbr@google.comab318592009-09-04 00:54:55 +0000299 'cleanup', 'diff', 'export', 'pack', 'revert', 'status', 'update',
300 'runhooks'
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000301 ]
302
303 def __init__(self, root_dir, options):
304 self._root_dir = root_dir
305 self._options = options
306 self._config_content = None
307 self._config_dict = {}
308 self._deps_hooks = []
309
310 def SetConfig(self, content):
311 self._config_dict = {}
312 self._config_content = content
skylined@chromium.orgdf0032c2009-05-29 10:43:56 +0000313 try:
314 exec(content, self._config_dict)
315 except SyntaxError, e:
316 try:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000317 __pychecker__ = 'no-objattrs'
skylined@chromium.orgdf0032c2009-05-29 10:43:56 +0000318 # Try to construct a human readable error message
319 error_message = [
320 'There is a syntax error in your configuration file.',
321 'Line #%s, character %s:' % (e.lineno, e.offset),
322 '"%s"' % re.sub(r'[\r\n]*$', '', e.text) ]
323 except:
324 # Something went wrong, re-raise the original exception
325 raise e
326 else:
327 # Raise a new exception with the human readable message:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000328 raise gclient_utils.Error('\n'.join(error_message))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000329
330 def SaveConfig(self):
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000331 gclient_utils.FileWrite(os.path.join(self._root_dir,
332 self._options.config_filename),
333 self._config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000334
335 def _LoadConfig(self):
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000336 client_source = gclient_utils.FileRead(
337 os.path.join(self._root_dir, self._options.config_filename))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000338 self.SetConfig(client_source)
339
340 def ConfigContent(self):
341 return self._config_content
342
343 def GetVar(self, key, default=None):
344 return self._config_dict.get(key, default)
345
346 @staticmethod
347 def LoadCurrentConfig(options, from_dir=None):
348 """Searches for and loads a .gclient file relative to the current working
349 dir.
350
351 Returns:
352 A dict representing the contents of the .gclient file or an empty dict if
353 the .gclient file doesn't exist.
354 """
355 if not from_dir:
356 from_dir = os.curdir
357 path = os.path.realpath(from_dir)
maruel@chromium.org0329e672009-05-13 18:41:04 +0000358 while not os.path.exists(os.path.join(path, options.config_filename)):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000359 next = os.path.split(path)
360 if not next[1]:
361 return None
362 path = next[0]
maruel@chromium.org2806acc2009-05-15 12:33:34 +0000363 client = GClient(path, options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000364 client._LoadConfig()
365 return client
366
367 def SetDefaultConfig(self, solution_name, solution_url, safesync_url):
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000368 self.SetConfig(DEFAULT_CLIENT_FILE_TEXT % {
369 'solution_name': solution_name,
370 'solution_url': solution_url,
371 'safesync_url' : safesync_url,
372 })
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000373
374 def _SaveEntries(self, entries):
375 """Creates a .gclient_entries file to record the list of unique checkouts.
376
377 The .gclient_entries file lives in the same directory as .gclient.
378
379 Args:
380 entries: A sequence of solution names.
381 """
msb@chromium.org2e38de72009-09-28 17:04:47 +0000382 text = "entries = \\\n" + pprint.pformat(entries, 2) + '\n'
383 file_path = os.path.join(self._root_dir, self._options.entries_filename)
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000384 gclient_utils.FileWrite(file_path, text)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000385
386 def _ReadEntries(self):
387 """Read the .gclient_entries file for the given client.
388
389 Args:
390 client: The client for which the entries file should be read.
391
392 Returns:
393 A sequence of solution names, which will be empty if there is the
394 entries file hasn't been created yet.
395 """
396 scope = {}
397 filename = os.path.join(self._root_dir, self._options.entries_filename)
maruel@chromium.org0329e672009-05-13 18:41:04 +0000398 if not os.path.exists(filename):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000399 return []
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000400 exec(gclient_utils.FileRead(filename), scope)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000401 return scope["entries"]
402
403 class FromImpl:
404 """Used to implement the From syntax."""
405
406 def __init__(self, module_name):
407 self.module_name = module_name
408
409 def __str__(self):
410 return 'From("%s")' % self.module_name
411
412 class _VarImpl:
413 def __init__(self, custom_vars, local_scope):
414 self._custom_vars = custom_vars
415 self._local_scope = local_scope
416
417 def Lookup(self, var_name):
418 """Implements the Var syntax."""
419 if var_name in self._custom_vars:
420 return self._custom_vars[var_name]
421 elif var_name in self._local_scope.get("vars", {}):
422 return self._local_scope["vars"][var_name]
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000423 raise gclient_utils.Error("Var is not defined: %s" % var_name)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000424
425 def _ParseSolutionDeps(self, solution_name, solution_deps_content,
426 custom_vars):
427 """Parses the DEPS file for the specified solution.
428
429 Args:
430 solution_name: The name of the solution to query.
431 solution_deps_content: Content of the DEPS file for the solution
432 custom_vars: A dict of vars to override any vars defined in the DEPS file.
433
434 Returns:
435 A dict mapping module names (as relative paths) to URLs or an empty
436 dict if the solution does not have a DEPS file.
437 """
438 # Skip empty
439 if not solution_deps_content:
440 return {}
441 # Eval the content
442 local_scope = {}
443 var = self._VarImpl(custom_vars, local_scope)
444 global_scope = {"From": self.FromImpl, "Var": var.Lookup, "deps_os": {}}
445 exec(solution_deps_content, global_scope, local_scope)
446 deps = local_scope.get("deps", {})
447
448 # load os specific dependencies if defined. these dependencies may
449 # override or extend the values defined by the 'deps' member.
450 if "deps_os" in local_scope:
451 deps_os_choices = {
452 "win32": "win",
453 "win": "win",
454 "cygwin": "win",
455 "darwin": "mac",
456 "mac": "mac",
457 "unix": "unix",
458 "linux": "unix",
459 "linux2": "unix",
460 }
461
462 if self._options.deps_os is not None:
463 deps_to_include = self._options.deps_os.split(",")
464 if "all" in deps_to_include:
465 deps_to_include = deps_os_choices.values()
466 else:
467 deps_to_include = [deps_os_choices.get(self._options.platform, "unix")]
468
469 deps_to_include = set(deps_to_include)
470 for deps_os_key in deps_to_include:
471 os_deps = local_scope["deps_os"].get(deps_os_key, {})
472 if len(deps_to_include) > 1:
473 # Ignore any overrides when including deps for more than one
474 # platform, so we collect the broadest set of dependencies available.
475 # We may end up with the wrong revision of something for our
476 # platform, but this is the best we can do.
477 deps.update([x for x in os_deps.items() if not x[0] in deps])
478 else:
479 deps.update(os_deps)
480
481 if 'hooks' in local_scope:
482 self._deps_hooks.extend(local_scope['hooks'])
483
484 # If use_relative_paths is set in the DEPS file, regenerate
485 # the dictionary using paths relative to the directory containing
486 # the DEPS file.
487 if local_scope.get('use_relative_paths'):
488 rel_deps = {}
489 for d, url in deps.items():
490 # normpath is required to allow DEPS to use .. in their
491 # dependency local path.
492 rel_deps[os.path.normpath(os.path.join(solution_name, d))] = url
493 return rel_deps
494 else:
495 return deps
496
497 def _ParseAllDeps(self, solution_urls, solution_deps_content):
498 """Parse the complete list of dependencies for the client.
499
500 Args:
501 solution_urls: A dict mapping module names (as relative paths) to URLs
502 corresponding to the solutions specified by the client. This parameter
503 is passed as an optimization.
504 solution_deps_content: A dict mapping module names to the content
505 of their DEPS files
506
507 Returns:
508 A dict mapping module names (as relative paths) to URLs corresponding
509 to the entire set of dependencies to checkout for the given client.
510
511 Raises:
512 Error: If a dependency conflicts with another dependency or of a solution.
513 """
514 deps = {}
515 for solution in self.GetVar("solutions"):
516 custom_vars = solution.get("custom_vars", {})
517 solution_deps = self._ParseSolutionDeps(
518 solution["name"],
519 solution_deps_content[solution["name"]],
520 custom_vars)
521
522 # If a line is in custom_deps, but not in the solution, we want to append
523 # this line to the solution.
524 if "custom_deps" in solution:
525 for d in solution["custom_deps"]:
526 if d not in solution_deps:
527 solution_deps[d] = solution["custom_deps"][d]
528
529 for d in solution_deps:
530 if "custom_deps" in solution and d in solution["custom_deps"]:
531 # Dependency is overriden.
532 url = solution["custom_deps"][d]
533 if url is None:
534 continue
535 else:
536 url = solution_deps[d]
537 # if we have a From reference dependent on another solution, then
538 # just skip the From reference. When we pull deps for the solution,
539 # we will take care of this dependency.
540 #
541 # If multiple solutions all have the same From reference, then we
542 # should only add one to our list of dependencies.
543 if type(url) != str:
544 if url.module_name in solution_urls:
545 # Already parsed.
546 continue
547 if d in deps and type(deps[d]) != str:
548 if url.module_name == deps[d].module_name:
549 continue
550 else:
551 parsed_url = urlparse.urlparse(url)
552 scheme = parsed_url[0]
553 if not scheme:
554 # A relative url. Fetch the real base.
555 path = parsed_url[2]
556 if path[0] != "/":
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000557 raise gclient_utils.Error(
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000558 "relative DEPS entry \"%s\" must begin with a slash" % d)
msb@chromium.orge6f78352010-01-13 17:05:33 +0000559 # Create a scm just to query the full url.
560 scm = gclient_scm.CreateSCM(solution["url"], self._root_dir,
561 None)
562 url = scm.FullUrlForRelativeUrl(url)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000563 if d in deps and deps[d] != url:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000564 raise gclient_utils.Error(
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000565 "Solutions have conflicting versions of dependency \"%s\"" % d)
566 if d in solution_urls and solution_urls[d] != url:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000567 raise gclient_utils.Error(
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000568 "Dependency \"%s\" conflicts with specified solution" % d)
569 # Grab the dependency.
570 deps[d] = url
571 return deps
572
phajdan.jr@chromium.org71b40682009-07-31 23:40:09 +0000573 def _RunHookAction(self, hook_dict, matching_file_list):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000574 """Runs the action from a single hook.
575 """
576 command = hook_dict['action'][:]
577 if command[0] == 'python':
578 # If the hook specified "python" as the first item, the action is a
579 # Python script. Run it by starting a new copy of the same
580 # interpreter.
581 command[0] = sys.executable
582
phajdan.jr@chromium.org71b40682009-07-31 23:40:09 +0000583 if '$matching_files' in command:
phajdan.jr@chromium.org68f2e092009-08-06 17:05:35 +0000584 splice_index = command.index('$matching_files')
585 command[splice_index:splice_index + 1] = matching_file_list
phajdan.jr@chromium.org71b40682009-07-31 23:40:09 +0000586
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000587 # Use a discrete exit status code of 2 to indicate that a hook action
588 # failed. Users of this script may wish to treat hook action failures
589 # differently from VC failures.
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000590 gclient_utils.SubprocessCall(command, self._root_dir, fail_status=2)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000591
592 def _RunHooks(self, command, file_list, is_using_git):
593 """Evaluates all hooks, running actions as needed.
594 """
595 # Hooks only run for these command types.
596 if not command in ('update', 'revert', 'runhooks'):
597 return
598
evan@chromium.org67820ef2009-07-27 17:23:00 +0000599 # Hooks only run when --nohooks is not specified
600 if self._options.nohooks:
601 return
602
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000603 # Get any hooks from the .gclient file.
604 hooks = self.GetVar("hooks", [])
605 # Add any hooks found in DEPS files.
606 hooks.extend(self._deps_hooks)
607
608 # If "--force" was specified, run all hooks regardless of what files have
609 # changed. If the user is using git, then we don't know what files have
610 # changed so we always run all hooks.
611 if self._options.force or is_using_git:
612 for hook_dict in hooks:
phajdan.jr@chromium.org71b40682009-07-31 23:40:09 +0000613 self._RunHookAction(hook_dict, [])
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000614 return
615
616 # Run hooks on the basis of whether the files from the gclient operation
617 # match each hook's pattern.
618 for hook_dict in hooks:
619 pattern = re.compile(hook_dict['pattern'])
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000620 matching_file_list = [f for f in file_list if pattern.search(f)]
phajdan.jr@chromium.org71b40682009-07-31 23:40:09 +0000621 if matching_file_list:
622 self._RunHookAction(hook_dict, matching_file_list)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000623
624 def RunOnDeps(self, command, args):
625 """Runs a command on each dependency in a client and its dependencies.
626
627 The module's dependencies are specified in its top-level DEPS files.
628
629 Args:
630 command: The command to use (e.g., 'status' or 'diff')
631 args: list of str - extra arguments to add to the command line.
632
633 Raises:
634 Error: If the client has conflicting entries.
635 """
636 if not command in self.supported_commands:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000637 raise gclient_utils.Error("'%s' is an unsupported command" % command)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000638
639 # Check for revision overrides.
640 revision_overrides = {}
641 for revision in self._options.revisions:
642 if revision.find("@") == -1:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000643 raise gclient_utils.Error(
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000644 "Specify the full dependency when specifying a revision number.")
645 revision_elem = revision.split("@")
646 # Disallow conflicting revs
647 if revision_overrides.has_key(revision_elem[0]) and \
648 revision_overrides[revision_elem[0]] != revision_elem[1]:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000649 raise gclient_utils.Error(
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000650 "Conflicting revision numbers specified.")
651 revision_overrides[revision_elem[0]] = revision_elem[1]
652
653 solutions = self.GetVar("solutions")
654 if not solutions:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000655 raise gclient_utils.Error("No solution specified")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000656
657 # When running runhooks --force, there's no need to consult the SCM.
658 # All known hooks are expected to run unconditionally regardless of working
659 # copy state, so skip the SCM status check.
660 run_scm = not (command == 'runhooks' and self._options.force)
661
662 entries = {}
663 entries_deps_content = {}
664 file_list = []
665 # Run on the base solutions first.
666 for solution in solutions:
667 name = solution["name"]
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000668 deps_file = solution.get("deps_file", self._options.deps_file)
669 if '/' in deps_file or '\\' in deps_file:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000670 raise gclient_utils.Error('deps_file name must not be a path, just a '
671 'filename.')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000672 if name in entries:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000673 raise gclient_utils.Error("solution %s specified more than once" % name)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000674 url = solution["url"]
675 entries[name] = url
yaar@chromium.orgf1328042009-09-22 23:14:23 +0000676 if run_scm and url:
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000677 self._options.revision = revision_overrides.get(name)
msb@chromium.orgcb5442b2009-09-22 16:51:24 +0000678 scm = gclient_scm.CreateSCM(url, self._root_dir, name)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000679 scm.RunCommand(command, self._options, args, file_list)
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000680 file_list = [os.path.join(name, f.strip()) for f in file_list]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000681 self._options.revision = None
682 try:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000683 deps_content = gclient_utils.FileRead(
684 os.path.join(self._root_dir, name, deps_file))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000685 except IOError, e:
686 if e.errno != errno.ENOENT:
687 raise
688 deps_content = ""
689 entries_deps_content[name] = deps_content
690
691 # Process the dependencies next (sort alphanumerically to ensure that
692 # containing directories get populated first and for readability)
693 deps = self._ParseAllDeps(entries, entries_deps_content)
694 deps_to_process = deps.keys()
695 deps_to_process.sort()
696
697 # First pass for direct dependencies.
698 for d in deps_to_process:
699 if type(deps[d]) == str:
700 url = deps[d]
701 entries[d] = url
702 if run_scm:
703 self._options.revision = revision_overrides.get(d)
msb@chromium.orgcb5442b2009-09-22 16:51:24 +0000704 scm = gclient_scm.CreateSCM(url, self._root_dir, d)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000705 scm.RunCommand(command, self._options, args, file_list)
706 self._options.revision = None
707
708 # Second pass for inherited deps (via the From keyword)
709 for d in deps_to_process:
710 if type(deps[d]) != str:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000711 filename = os.path.join(self._root_dir,
712 deps[d].module_name,
713 self._options.deps_file)
714 content = gclient_utils.FileRead(filename)
715 sub_deps = self._ParseSolutionDeps(deps[d].module_name, content, {})
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000716 url = sub_deps[d]
717 entries[d] = url
718 if run_scm:
719 self._options.revision = revision_overrides.get(d)
msb@chromium.orgcb5442b2009-09-22 16:51:24 +0000720 scm = gclient_scm.CreateSCM(url, self._root_dir, d)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000721 scm.RunCommand(command, self._options, args, file_list)
722 self._options.revision = None
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000723
phajdan.jr@chromium.orgd83b2b22009-08-11 15:30:55 +0000724 # Convert all absolute paths to relative.
725 for i in range(len(file_list)):
726 # TODO(phajdan.jr): We should know exactly when the paths are absolute.
727 # It depends on the command being executed (like runhooks vs sync).
728 if not os.path.isabs(file_list[i]):
729 continue
730
731 prefix = os.path.commonprefix([self._root_dir.lower(),
732 file_list[i].lower()])
733 file_list[i] = file_list[i][len(prefix):]
734
735 # Strip any leading path separators.
736 while file_list[i].startswith('\\') or file_list[i].startswith('/'):
737 file_list[i] = file_list[i][1:]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000738
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000739 is_using_git = gclient_utils.IsUsingGit(self._root_dir, entries.keys())
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000740 self._RunHooks(command, file_list, is_using_git)
741
742 if command == 'update':
ajwong@chromium.orgcdcee802009-06-23 15:30:42 +0000743 # Notify the user if there is an orphaned entry in their working copy.
744 # Only delete the directory if there are no changes in it, and
745 # delete_unversioned_trees is set to true.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000746 prev_entries = self._ReadEntries()
747 for entry in prev_entries:
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000748 # Fix path separator on Windows.
749 entry_fixed = entry.replace('/', os.path.sep)
750 e_dir = os.path.join(self._root_dir, entry_fixed)
751 # Use entry and not entry_fixed there.
maruel@chromium.org0329e672009-05-13 18:41:04 +0000752 if entry not in entries and os.path.exists(e_dir):
msb@chromium.org83017012009-09-28 18:52:12 +0000753 modified_files = False
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +0000754 if isinstance(prev_entries, list):
msb@chromium.org83017012009-09-28 18:52:12 +0000755 # old .gclient_entries format was list, now dict
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +0000756 modified_files = gclient_scm.scm.SVN.CaptureStatus(e_dir)
msb@chromium.org83017012009-09-28 18:52:12 +0000757 else:
758 file_list = []
759 scm = gclient_scm.CreateSCM(prev_entries[entry], self._root_dir,
760 entry_fixed)
761 scm.status(self._options, [], file_list)
762 modified_files = file_list != []
763 if not self._options.delete_unversioned_trees or modified_files:
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000764 # There are modified files in this entry. Keep warning until
765 # removed.
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000766 print(("\nWARNING: \"%s\" is no longer part of this client. "
767 "It is recommended that you manually remove it.\n") %
768 entry_fixed)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000769 else:
770 # Delete the entry
maruel@chromium.orgdf7a3132009-05-12 17:49:49 +0000771 print("\n________ deleting \'%s\' " +
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000772 "in \'%s\'") % (entry_fixed, self._root_dir)
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000773 gclient_utils.RemoveDirectory(e_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000774 # record the current list of entries for next time
775 self._SaveEntries(entries)
776
777 def PrintRevInfo(self):
778 """Output revision info mapping for the client and its dependencies. This
779 allows the capture of a overall "revision" for the source tree that can
780 be used to reproduce the same tree in the future. The actual output
781 contains enough information (source paths, svn server urls and revisions)
782 that it can be used either to generate external svn commands (without
783 gclient) or as input to gclient's --rev option (with some massaging of
784 the data).
785
786 NOTE: Unlike RunOnDeps this does not require a local checkout and is run
787 on the Pulse master. It MUST NOT execute hooks.
788
789 Raises:
790 Error: If the client has conflicting entries.
791 """
792 # Check for revision overrides.
793 revision_overrides = {}
794 for revision in self._options.revisions:
795 if revision.find("@") < 0:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000796 raise gclient_utils.Error(
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000797 "Specify the full dependency when specifying a revision number.")
798 revision_elem = revision.split("@")
799 # Disallow conflicting revs
800 if revision_overrides.has_key(revision_elem[0]) and \
801 revision_overrides[revision_elem[0]] != revision_elem[1]:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000802 raise gclient_utils.Error(
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000803 "Conflicting revision numbers specified.")
804 revision_overrides[revision_elem[0]] = revision_elem[1]
805
806 solutions = self.GetVar("solutions")
807 if not solutions:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000808 raise gclient_utils.Error("No solution specified")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000809
810 entries = {}
811 entries_deps_content = {}
812
813 # Inner helper to generate base url and rev tuple (including honoring
814 # |revision_overrides|)
815 def GetURLAndRev(name, original_url):
msb@chromium.orgd5a035e2009-11-13 17:58:26 +0000816 url, revision = gclient_utils.SplitUrlRevision(original_url)
msb@chromium.orgac915bb2009-11-13 17:03:01 +0000817 if not revision:
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000818 if revision_overrides.has_key(name):
msb@chromium.orgac915bb2009-11-13 17:03:01 +0000819 return (url, revision_overrides[name])
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000820 else:
msb@chromium.org0f282062009-11-06 20:14:02 +0000821 scm = gclient_scm.CreateSCM(solution["url"], self._root_dir, name)
msb@chromium.orgac915bb2009-11-13 17:03:01 +0000822 return (url, scm.revinfo(self._options, [], None))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000823 else:
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000824 if revision_overrides.has_key(name):
msb@chromium.orgac915bb2009-11-13 17:03:01 +0000825 return (url, revision_overrides[name])
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000826 else:
msb@chromium.orgac915bb2009-11-13 17:03:01 +0000827 return (url, revision)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000828
829 # Run on the base solutions first.
830 for solution in solutions:
831 name = solution["name"]
832 if name in entries:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000833 raise gclient_utils.Error("solution %s specified more than once" % name)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000834 (url, rev) = GetURLAndRev(name, solution["url"])
msb@chromium.org770ff9e2009-09-23 17:18:18 +0000835 entries[name] = "%s@%s" % (url, rev)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000836 # TODO(aharper): SVN/SCMWrapper cleanup (non-local commandset)
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +0000837 entries_deps_content[name] = gclient_scm.scm.SVN.Capture(
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000838 ["cat",
msb@chromium.org770ff9e2009-09-23 17:18:18 +0000839 "%s/%s@%s" % (url,
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000840 self._options.deps_file,
841 rev)],
842 os.getcwd())
843
844 # Process the dependencies next (sort alphanumerically to ensure that
845 # containing directories get populated first and for readability)
846 deps = self._ParseAllDeps(entries, entries_deps_content)
847 deps_to_process = deps.keys()
848 deps_to_process.sort()
849
850 # First pass for direct dependencies.
851 for d in deps_to_process:
852 if type(deps[d]) == str:
853 (url, rev) = GetURLAndRev(d, deps[d])
msb@chromium.org770ff9e2009-09-23 17:18:18 +0000854 entries[d] = "%s@%s" % (url, rev)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000855
856 # Second pass for inherited deps (via the From keyword)
857 for d in deps_to_process:
858 if type(deps[d]) != str:
859 deps_parent_url = entries[deps[d].module_name]
860 if deps_parent_url.find("@") < 0:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000861 raise gclient_utils.Error("From %s missing revisioned url" %
862 deps[d].module_name)
863 content = gclient_utils.FileRead(os.path.join(self._root_dir,
864 deps[d].module_name,
865 self._options.deps_file))
866 sub_deps = self._ParseSolutionDeps(deps[d].module_name, content, {})
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000867 (url, rev) = GetURLAndRev(d, sub_deps[d])
msb@chromium.org770ff9e2009-09-23 17:18:18 +0000868 entries[d] = "%s@%s" % (url, rev)
maruel@chromium.org57e893e2009-08-19 18:12:09 +0000869 print(";\n\n".join(["%s: %s" % (x, entries[x])
870 for x in sorted(entries.keys())]))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000871
872
873## gclient commands.
874
875
876def DoCleanup(options, args):
877 """Handle the cleanup subcommand.
878
879 Raises:
880 Error: if client isn't configured properly.
881 """
maruel@chromium.org2806acc2009-05-15 12:33:34 +0000882 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000883 if not client:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000884 raise gclient_utils.Error("client not configured; see 'gclient config'")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000885 if options.verbose:
886 # Print out the .gclient file. This is longer than if we just printed the
887 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.orgdf7a3132009-05-12 17:49:49 +0000888 print(client.ConfigContent())
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000889 return client.RunOnDeps('cleanup', args)
890
891
892def DoConfig(options, args):
893 """Handle the config subcommand.
894
895 Args:
896 options: If options.spec set, a string providing contents of config file.
897 args: The command line args. If spec is not set,
898 then args[0] is a string URL to get for config file.
899
900 Raises:
901 Error: on usage error
902 """
903 if len(args) < 1 and not options.spec:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000904 raise gclient_utils.Error("required argument missing; see 'gclient help "
905 "config'")
maruel@chromium.org0329e672009-05-13 18:41:04 +0000906 if os.path.exists(options.config_filename):
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000907 raise gclient_utils.Error("%s file already exists in the current directory"
908 % options.config_filename)
maruel@chromium.org2806acc2009-05-15 12:33:34 +0000909 client = GClient('.', options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000910 if options.spec:
911 client.SetConfig(options.spec)
912 else:
913 # TODO(darin): it would be nice to be able to specify an alternate relpath
914 # for the given URL.
maruel@chromium.org1ab7ffc2009-06-03 17:21:37 +0000915 base_url = args[0].rstrip('/')
916 name = base_url.split("/")[-1]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000917 safesync_url = ""
918 if len(args) > 1:
919 safesync_url = args[1]
920 client.SetDefaultConfig(name, base_url, safesync_url)
921 client.SaveConfig()
922
923
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +0000924def DoExport(options, args):
925 """Handle the export subcommand.
phajdan.jr@chromium.org71b40682009-07-31 23:40:09 +0000926
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +0000927 Raises:
928 Error: on usage error
929 """
930 if len(args) != 1:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000931 raise gclient_utils.Error("Need directory name")
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +0000932 client = GClient.LoadCurrentConfig(options)
933
934 if not client:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000935 raise gclient_utils.Error("client not configured; see 'gclient config'")
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +0000936
937 if options.verbose:
938 # Print out the .gclient file. This is longer than if we just printed the
939 # client dict, but more legible, and it might contain helpful comments.
940 print(client.ConfigContent())
941 return client.RunOnDeps('export', args)
942
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000943def DoHelp(options, args):
944 """Handle the help subcommand giving help for another subcommand.
945
946 Raises:
947 Error: if the command is unknown.
948 """
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000949 __pychecker__ = 'unusednames=options'
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000950 if len(args) == 1 and args[0] in COMMAND_USAGE_TEXT:
maruel@chromium.orgdf7a3132009-05-12 17:49:49 +0000951 print(COMMAND_USAGE_TEXT[args[0]])
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000952 else:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000953 raise gclient_utils.Error("unknown subcommand '%s'; see 'gclient help'" %
954 args[0])
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000955
956
kbr@google.comab318592009-09-04 00:54:55 +0000957def DoPack(options, args):
958 """Handle the pack subcommand.
959
960 Raises:
961 Error: if client isn't configured properly.
962 """
963 client = GClient.LoadCurrentConfig(options)
964 if not client:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000965 raise gclient_utils.Error("client not configured; see 'gclient config'")
kbr@google.comab318592009-09-04 00:54:55 +0000966 if options.verbose:
967 # Print out the .gclient file. This is longer than if we just printed the
968 # client dict, but more legible, and it might contain helpful comments.
969 print(client.ConfigContent())
kbr@google.comab318592009-09-04 00:54:55 +0000970 return client.RunOnDeps('pack', args)
971
972
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000973def DoStatus(options, args):
974 """Handle the status subcommand.
975
976 Raises:
977 Error: if client isn't configured properly.
978 """
maruel@chromium.org2806acc2009-05-15 12:33:34 +0000979 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000980 if not client:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000981 raise gclient_utils.Error("client not configured; see 'gclient config'")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000982 if options.verbose:
983 # Print out the .gclient file. This is longer than if we just printed the
984 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.orgdf7a3132009-05-12 17:49:49 +0000985 print(client.ConfigContent())
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000986 return client.RunOnDeps('status', args)
987
988
989def DoUpdate(options, args):
990 """Handle the update and sync subcommands.
991
992 Raises:
993 Error: if client isn't configured properly.
994 """
maruel@chromium.org2806acc2009-05-15 12:33:34 +0000995 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000996
997 if not client:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000998 raise gclient_utils.Error("client not configured; see 'gclient config'")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000999
1000 if not options.head:
1001 solutions = client.GetVar('solutions')
1002 if solutions:
1003 for s in solutions:
1004 if s.get('safesync_url', ''):
1005 # rip through revisions and make sure we're not over-riding
1006 # something that was explicitly passed
1007 has_key = False
1008 for r in options.revisions:
1009 if r.split('@')[0] == s['name']:
1010 has_key = True
1011 break
1012
1013 if not has_key:
1014 handle = urllib.urlopen(s['safesync_url'])
1015 rev = handle.read().strip()
1016 handle.close()
1017 if len(rev):
1018 options.revisions.append(s['name']+'@'+rev)
1019
1020 if options.verbose:
1021 # Print out the .gclient file. This is longer than if we just printed the
1022 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.orgdf7a3132009-05-12 17:49:49 +00001023 print(client.ConfigContent())
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001024 return client.RunOnDeps('update', args)
1025
1026
1027def DoDiff(options, args):
1028 """Handle the diff subcommand.
1029
1030 Raises:
1031 Error: if client isn't configured properly.
1032 """
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001033 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001034 if not client:
maruel@chromium.orge3608df2009-11-10 20:22:57 +00001035 raise gclient_utils.Error("client not configured; see 'gclient config'")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001036 if options.verbose:
1037 # Print out the .gclient file. This is longer than if we just printed the
1038 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.orgdf7a3132009-05-12 17:49:49 +00001039 print(client.ConfigContent())
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001040 return client.RunOnDeps('diff', args)
1041
1042
1043def DoRevert(options, args):
1044 """Handle the revert subcommand.
1045
1046 Raises:
1047 Error: if client isn't configured properly.
1048 """
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001049 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001050 if not client:
maruel@chromium.orge3608df2009-11-10 20:22:57 +00001051 raise gclient_utils.Error("client not configured; see 'gclient config'")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001052 return client.RunOnDeps('revert', args)
1053
1054
1055def DoRunHooks(options, args):
1056 """Handle the runhooks subcommand.
1057
1058 Raises:
1059 Error: if client isn't configured properly.
1060 """
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001061 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001062 if not client:
maruel@chromium.orge3608df2009-11-10 20:22:57 +00001063 raise gclient_utils.Error("client not configured; see 'gclient config'")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001064 if options.verbose:
1065 # Print out the .gclient file. This is longer than if we just printed the
1066 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.orgdf7a3132009-05-12 17:49:49 +00001067 print(client.ConfigContent())
maruel@chromium.org5df6a462009-08-28 18:52:26 +00001068 options.force = True
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001069 return client.RunOnDeps('runhooks', args)
1070
1071
1072def DoRevInfo(options, args):
1073 """Handle the revinfo subcommand.
1074
1075 Raises:
1076 Error: if client isn't configured properly.
1077 """
maruel@chromium.orge3608df2009-11-10 20:22:57 +00001078 __pychecker__ = 'unusednames=args'
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001079 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001080 if not client:
maruel@chromium.orge3608df2009-11-10 20:22:57 +00001081 raise gclient_utils.Error("client not configured; see 'gclient config'")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001082 client.PrintRevInfo()
1083
1084
1085gclient_command_map = {
1086 "cleanup": DoCleanup,
1087 "config": DoConfig,
1088 "diff": DoDiff,
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +00001089 "export": DoExport,
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001090 "help": DoHelp,
kbr@google.comab318592009-09-04 00:54:55 +00001091 "pack": DoPack,
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001092 "status": DoStatus,
1093 "sync": DoUpdate,
1094 "update": DoUpdate,
1095 "revert": DoRevert,
1096 "runhooks": DoRunHooks,
1097 "revinfo" : DoRevInfo,
1098}
1099
1100
1101def DispatchCommand(command, options, args, command_map=None):
1102 """Dispatches the appropriate subcommand based on command line arguments."""
1103 if command_map is None:
1104 command_map = gclient_command_map
1105
1106 if command in command_map:
1107 return command_map[command](options, args)
1108 else:
maruel@chromium.orge3608df2009-11-10 20:22:57 +00001109 raise gclient_utils.Error("unknown subcommand '%s'; see 'gclient help'" %
1110 command)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001111
1112
1113def Main(argv):
1114 """Parse command line arguments and dispatch command."""
1115
1116 option_parser = optparse.OptionParser(usage=DEFAULT_USAGE_TEXT,
1117 version=__version__)
1118 option_parser.disable_interspersed_args()
1119 option_parser.add_option("", "--force", action="store_true", default=False,
1120 help=("(update/sync only) force update even "
1121 "for modules which haven't changed"))
evan@chromium.org67820ef2009-07-27 17:23:00 +00001122 option_parser.add_option("", "--nohooks", action="store_true", default=False,
1123 help=("(update/sync/revert only) prevent the hooks from "
1124 "running"))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001125 option_parser.add_option("", "--revision", action="append", dest="revisions",
1126 metavar="REV", default=[],
1127 help=("(update/sync only) sync to a specific "
1128 "revision, can be used multiple times for "
1129 "each solution, e.g. --revision=src@123, "
1130 "--revision=internal@32"))
1131 option_parser.add_option("", "--deps", default=None, dest="deps_os",
1132 metavar="OS_LIST",
1133 help=("(update/sync only) sync deps for the "
1134 "specified (comma-separated) platform(s); "
1135 "'all' will sync all platforms"))
1136 option_parser.add_option("", "--spec", default=None,
1137 help=("(config only) create a gclient file "
1138 "containing the provided string"))
maruel@chromium.orga6220d12010-01-06 21:04:17 +00001139 option_parser.add_option("-v", "--verbose", action="count", default=0,
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001140 help="produce additional output for diagnostics")
maruel@chromium.org7753d242009-10-07 17:40:24 +00001141 option_parser.add_option("", "--manually_grab_svn_rev", action="store_true",
1142 default=False,
1143 help="Skip svn up whenever possible by requesting "
1144 "actual HEAD revision from the repository")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001145 option_parser.add_option("", "--head", action="store_true", default=False,
1146 help=("skips any safesync_urls specified in "
1147 "configured solutions"))
ajwong@chromium.orgcdcee802009-06-23 15:30:42 +00001148 option_parser.add_option("", "--delete_unversioned_trees",
1149 action="store_true", default=False,
1150 help=("on update, delete any unexpected "
1151 "unversioned trees that are in the checkout"))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001152
1153 if len(argv) < 2:
1154 # Users don't need to be told to use the 'help' command.
1155 option_parser.print_help()
1156 return 1
1157 # Add manual support for --version as first argument.
1158 if argv[1] == '--version':
1159 option_parser.print_version()
1160 return 0
1161
1162 # Add manual support for --help as first argument.
1163 if argv[1] == '--help':
1164 argv[1] = 'help'
1165
1166 command = argv[1]
1167 options, args = option_parser.parse_args(argv[2:])
1168
1169 if len(argv) < 3 and command == "help":
1170 option_parser.print_help()
1171 return 0
1172
maruel@chromium.orga6220d12010-01-06 21:04:17 +00001173 if options.verbose > 1:
maruel@chromium.org754960e2009-09-21 12:31:05 +00001174 logging.basicConfig(level=logging.DEBUG)
1175
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001176 # Files used for configuration and state saving.
1177 options.config_filename = os.environ.get("GCLIENT_FILE", ".gclient")
1178 options.entries_filename = ".gclient_entries"
1179 options.deps_file = "DEPS"
1180
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001181 options.platform = sys.platform
1182 return DispatchCommand(command, options, args)
1183
1184
1185if "__main__" == __name__:
1186 try:
1187 result = Main(sys.argv)
maruel@chromium.orge3608df2009-11-10 20:22:57 +00001188 except gclient_utils.Error, e:
maruel@chromium.orgdf7a3132009-05-12 17:49:49 +00001189 print >> sys.stderr, "Error: %s" % str(e)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001190 result = 1
1191 sys.exit(result)
1192
1193# vim: ts=2:sw=2:tw=80:et: