blob: 1e58e031d80ceb97642e0dcc98ee96754b6cdb24 [file] [log] [blame]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001#!/usr/bin/python
maruel@chromium.orgba551772010-02-03 18:21:42 +00002# Copyright (c) 2010 The Chromium Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00005
6"""A wrapper script to manage a set of client modules in different SCM.
7
8This script is intended to be used to help basic management of client
msb@chromium.orgd6504212010-01-13 17:34:31 +00009program sources residing in one or more Subversion modules and Git
10repositories, along with other modules it depends on, also in Subversion or Git,
11but possibly on multiple respositories, making a wrapper system apparently
12necessary.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000013
14Files
15 .gclient : Current client configuration, written by 'config' command.
16 Format is a Python script defining 'solutions', a list whose
17 entries each are maps binding the strings "name" and "url"
18 to strings specifying the name and location of the client
19 module, as well as "custom_deps" to a map similar to the DEPS
20 file below.
21 .gclient_entries : A cache constructed by 'update' command. Format is a
22 Python script defining 'entries', a list of the names
23 of all modules in the client
24 <module>/DEPS : Python script defining var 'deps' as a map from each requisite
25 submodule name to a URL where it can be found (via one SCM)
26
27Hooks
28 .gclient and DEPS files may optionally contain a list named "hooks" to
29 allow custom actions to be performed based on files that have changed in the
evan@chromium.org67820ef2009-07-27 17:23:00 +000030 working copy as a result of a "sync"/"update" or "revert" operation. This
31 could be prevented by using --nohooks (hooks run by default). Hooks can also
maruel@chromium.org5df6a462009-08-28 18:52:26 +000032 be forced to run with the "runhooks" operation. If "sync" is run with
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000033 --force, all known hooks will run regardless of the state of the working
34 copy.
35
36 Each item in a "hooks" list is a dict, containing these two keys:
37 "pattern" The associated value is a string containing a regular
38 expression. When a file whose pathname matches the expression
39 is checked out, updated, or reverted, the hook's "action" will
40 run.
41 "action" A list describing a command to run along with its arguments, if
42 any. An action command will run at most one time per gclient
43 invocation, regardless of how many files matched the pattern.
44 The action is executed in the same directory as the .gclient
45 file. If the first item in the list is the string "python",
46 the current Python interpreter (sys.executable) will be used
phajdan.jr@chromium.org71b40682009-07-31 23:40:09 +000047 to run the command. If the list contains string "$matching_files"
48 it will be removed from the list and the list will be extended
49 by the list of matching files.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000050
51 Example:
52 hooks = [
53 { "pattern": "\\.(gif|jpe?g|pr0n|png)$",
54 "action": ["python", "image_indexer.py", "--all"]},
55 ]
56"""
57
58__author__ = "darinf@gmail.com (Darin Fisher)"
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +000059__version__ = "0.3.4"
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000060
61import errno
maruel@chromium.org754960e2009-09-21 12:31:05 +000062import logging
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000063import optparse
64import os
msb@chromium.org2e38de72009-09-28 17:04:47 +000065import pprint
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000066import re
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000067import sys
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000068import urlparse
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000069import urllib
70
maruel@chromium.orgada4c652009-12-03 15:32:01 +000071import breakpad
72
maruel@chromium.org5f3eee32009-09-17 00:34:30 +000073import gclient_scm
74import gclient_utils
nasser@codeaurora.org1f7a3d12010-02-04 15:11:50 +000075from third_party.repo.progress import Progress
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000076
77# default help text
78DEFAULT_USAGE_TEXT = (
msb@chromium.orgd6504212010-01-13 17:34:31 +000079"""usage: %prog <subcommand> [options] [--] [SCM options/args...]
80a wrapper for managing a set of svn client modules and/or git repositories.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000081Version """ + __version__ + """
82
83subcommands:
84 cleanup
85 config
86 diff
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +000087 export
kbr@google.comab318592009-09-04 00:54:55 +000088 pack
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000089 revert
90 status
91 sync
92 update
93 runhooks
94 revinfo
95
msb@chromium.orgd6504212010-01-13 17:34:31 +000096Options and extra arguments can be passed to invoked SCM commands by
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000097appending them to the command line. Note that if the first such
98appended option starts with a dash (-) then the options must be
99preceded by -- to distinguish them from gclient options.
100
101For additional help on a subcommand or examples of usage, try
102 %prog help <subcommand>
103 %prog help files
104""")
105
106GENERIC_UPDATE_USAGE_TEXT = (
107 """Perform a checkout/update of the modules specified by the gclient
108configuration; see 'help config'. Unless --revision is specified,
109then the latest revision of the root solutions is checked out, with
110dependent submodule versions updated according to DEPS files.
111If --revision is specified, then the given revision is used in place
112of the latest, either for a single solution or for all solutions.
113Unless the --force option is provided, solutions and modules whose
114local revision matches the one to update (i.e., they have not changed
evan@chromium.org67820ef2009-07-27 17:23:00 +0000115in the repository) are *not* modified. Unless --nohooks is provided,
116the hooks are run.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000117This a synonym for 'gclient %(alias)s'
118
msb@chromium.orgd6504212010-01-13 17:34:31 +0000119usage: gclient %(cmd)s [options] [--] [SCM update options/args]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000120
121Valid options:
estade@chromium.org3b5cba42009-12-01 00:37:08 +0000122 --force : force update even for unchanged modules
123 --nohooks : don't run the hooks after the update is complete
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000124 --revision SOLUTION@REV : update given solution to specified revision
estade@chromium.org3b5cba42009-12-01 00:37:08 +0000125 --deps PLATFORM(S) : sync deps for the given platform(s), or 'all'
126 --verbose : output additional diagnostics
127 --head : update to latest revision, instead of last good revision
davemoore@chromium.org793796d2010-02-19 17:27:41 +0000128 --reset : resets any local changes before updating (git only)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000129
130Examples:
131 gclient %(cmd)s
msb@chromium.orgd6504212010-01-13 17:34:31 +0000132 update files from SCM according to current configuration,
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000133 *for modules which have changed since last update or sync*
134 gclient %(cmd)s --force
msb@chromium.orgd6504212010-01-13 17:34:31 +0000135 update files from SCM according to current configuration, for
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000136 all modules (useful for recovering files deleted from local copy)
estade@chromium.org3b5cba42009-12-01 00:37:08 +0000137 gclient %(cmd)s --revision src@31000
138 update src directory to r31000
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000139""")
140
141COMMAND_USAGE_TEXT = {
142 "cleanup":
143 """Clean up all working copies, using 'svn cleanup' for each module.
144Additional options and args may be passed to 'svn cleanup'.
145
146usage: cleanup [options] [--] [svn cleanup args/options]
147
148Valid options:
149 --verbose : output additional diagnostics
150""",
151 "config": """Create a .gclient file in the current directory; this
152specifies the configuration for further commands. After update/sync,
153top-level DEPS files in each module are read to determine dependent
154modules to operate on as well. If optional [url] parameter is
155provided, then configuration is read from a specified Subversion server
156URL. Otherwise, a --spec option must be provided.
157
158usage: config [option | url] [safesync url]
159
160Valid options:
161 --spec=GCLIENT_SPEC : contents of .gclient are read from string parameter.
162 *Note that due to Cygwin/Python brokenness, it
163 probably can't contain any newlines.*
164
165Examples:
166 gclient config https://gclient.googlecode.com/svn/trunk/gclient
167 configure a new client to check out gclient.py tool sources
168 gclient config --spec='solutions=[{"name":"gclient","""
169 '"url":"https://gclient.googlecode.com/svn/trunk/gclient",'
170 '"custom_deps":{}}]',
171 "diff": """Display the differences between two revisions of modules.
172(Does 'svn diff' for each checked out module and dependences.)
173Additional args and options to 'svn diff' can be passed after
174gclient options.
175
176usage: diff [options] [--] [svn args/options]
177
178Valid options:
179 --verbose : output additional diagnostics
180
181Examples:
182 gclient diff
183 simple 'svn diff' for configured client and dependences
184 gclient diff -- -x -b
185 use 'svn diff -x -b' to suppress whitespace-only differences
186 gclient diff -- -r HEAD -x -b
187 diff versus the latest version of each module
188""",
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +0000189 "export":
190 """Wrapper for svn export for all managed directories
191""",
kbr@google.comab318592009-09-04 00:54:55 +0000192 "pack":
193
194 """Generate a patch which can be applied at the root of the tree.
195Internally, runs 'svn diff' on each checked out module and
196dependencies, and performs minimal postprocessing of the output. The
197resulting patch is printed to stdout and can be applied to a freshly
198checked out tree via 'patch -p0 < patchfile'. Additional args and
199options to 'svn diff' can be passed after gclient options.
200
201usage: pack [options] [--] [svn args/options]
202
203Valid options:
204 --verbose : output additional diagnostics
205
206Examples:
207 gclient pack > patch.txt
208 generate simple patch for configured client and dependences
209 gclient pack -- -x -b > patch.txt
210 generate patch using 'svn diff -x -b' to suppress
211 whitespace-only differences
212 gclient pack -- -r HEAD -x -b > patch.txt
213 generate patch, diffing each file versus the latest version of
214 each module
215""",
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000216 "revert":
217 """Revert every file in every managed directory in the client view.
218
219usage: revert
220""",
221 "status":
222 """Show the status of client and dependent modules, using 'svn diff'
223for each module. Additional options and args may be passed to 'svn diff'.
224
225usage: status [options] [--] [svn diff args/options]
226
227Valid options:
228 --verbose : output additional diagnostics
evan@chromium.org67820ef2009-07-27 17:23:00 +0000229 --nohooks : don't run the hooks after the update is complete
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000230""",
231 "sync": GENERIC_UPDATE_USAGE_TEXT % {"cmd": "sync", "alias": "update"},
232 "update": GENERIC_UPDATE_USAGE_TEXT % {"cmd": "update", "alias": "sync"},
233 "help": """Describe the usage of this program or its subcommands.
234
235usage: help [options] [subcommand]
236
237Valid options:
238 --verbose : output additional diagnostics
239""",
240 "runhooks":
241 """Runs hooks for files that have been modified in the local working copy,
maruel@chromium.org5df6a462009-08-28 18:52:26 +0000242according to 'svn status'. Implies --force.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000243
244usage: runhooks [options]
245
246Valid options:
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000247 --verbose : output additional diagnostics
248""",
249 "revinfo":
250 """Outputs source path, server URL and revision information for every
251dependency in all solutions (no local checkout required).
252
253usage: revinfo [options]
254""",
255}
256
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000257DEFAULT_CLIENT_FILE_TEXT = ("""\
258# An element of this array (a "solution") describes a repository directory
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000259# that will be checked out into your working copy. Each solution may
260# optionally define additional dependencies (via its DEPS file) to be
261# checked out alongside the solution's directory. A solution may also
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000262# specify custom dependencies (via the "custom_deps" property) that
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000263# override or augment the dependencies specified by the DEPS file.
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000264# If a "safesync_url" is specified, it is assumed to reference the location of
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000265# a text file which contains nothing but the last known good SCM revision to
266# sync against. It is fetched if specified and used unless --head is passed
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000267
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000268solutions = [
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000269 { "name" : "%(solution_name)s",
270 "url" : "%(solution_url)s",
271 "custom_deps" : {
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000272 # To use the trunk of a component instead of what's in DEPS:
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000273 #"component": "https://svnserver/component/trunk/",
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000274 # To exclude a component from your working copy:
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000275 #"data/really_large_component": None,
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000276 },
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000277 "safesync_url": "%(safesync_url)s"
278 },
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000279]
280""")
281
maruel@chromium.orge3da35f2010-03-09 21:40:45 +0000282DEFAULT_SNAPSHOT_SOLUTION_TEXT = ("""\
283 { "name" : "%(solution_name)s",
284 "url" : "%(solution_url)s",
285 "custom_deps" : {
286 %(solution_deps)s,
287 },
288 "safesync_url": "%(safesync_url)s"
289 },
290""")
291
292DEFAULT_SNAPSHOT_FILE_TEXT = ("""\
293# An element of this array (a "solution") describes a repository directory
294# that will be checked out into your working copy. Each solution may
295# optionally define additional dependencies (via its DEPS file) to be
296# checked out alongside the solution's directory. A solution may also
297# specify custom dependencies (via the "custom_deps" property) that
298# override or augment the dependencies specified by the DEPS file.
299# If a "safesync_url" is specified, it is assumed to reference the location of
300# a text file which contains nothing but the last known good SCM revision to
301# sync against. It is fetched if specified and used unless --head is passed
302
303solutions = [
304%(solution_list)s
305]
306""")
307
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000308
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000309## GClient implementation.
310
311
312class GClient(object):
313 """Object that represent a gclient checkout."""
314
315 supported_commands = [
kbr@google.comab318592009-09-04 00:54:55 +0000316 'cleanup', 'diff', 'export', 'pack', 'revert', 'status', 'update',
317 'runhooks'
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000318 ]
319
320 def __init__(self, root_dir, options):
321 self._root_dir = root_dir
322 self._options = options
323 self._config_content = None
324 self._config_dict = {}
325 self._deps_hooks = []
326
327 def SetConfig(self, content):
328 self._config_dict = {}
329 self._config_content = content
skylined@chromium.orgdf0032c2009-05-29 10:43:56 +0000330 try:
331 exec(content, self._config_dict)
332 except SyntaxError, e:
333 try:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000334 __pychecker__ = 'no-objattrs'
skylined@chromium.orgdf0032c2009-05-29 10:43:56 +0000335 # Try to construct a human readable error message
336 error_message = [
337 'There is a syntax error in your configuration file.',
338 'Line #%s, character %s:' % (e.lineno, e.offset),
339 '"%s"' % re.sub(r'[\r\n]*$', '', e.text) ]
340 except:
341 # Something went wrong, re-raise the original exception
342 raise e
343 else:
344 # Raise a new exception with the human readable message:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000345 raise gclient_utils.Error('\n'.join(error_message))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000346
347 def SaveConfig(self):
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000348 gclient_utils.FileWrite(os.path.join(self._root_dir,
349 self._options.config_filename),
350 self._config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000351
352 def _LoadConfig(self):
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000353 client_source = gclient_utils.FileRead(
354 os.path.join(self._root_dir, self._options.config_filename))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000355 self.SetConfig(client_source)
356
357 def ConfigContent(self):
358 return self._config_content
359
360 def GetVar(self, key, default=None):
361 return self._config_dict.get(key, default)
362
363 @staticmethod
364 def LoadCurrentConfig(options, from_dir=None):
365 """Searches for and loads a .gclient file relative to the current working
366 dir.
367
368 Returns:
369 A dict representing the contents of the .gclient file or an empty dict if
370 the .gclient file doesn't exist.
371 """
372 if not from_dir:
373 from_dir = os.curdir
374 path = os.path.realpath(from_dir)
maruel@chromium.org0329e672009-05-13 18:41:04 +0000375 while not os.path.exists(os.path.join(path, options.config_filename)):
maruel@chromium.org55e724e2010-03-11 19:36:49 +0000376 split_path = os.path.split(path)
377 if not split_path[1]:
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000378 return None
maruel@chromium.org55e724e2010-03-11 19:36:49 +0000379 path = split_path[0]
maruel@chromium.org2806acc2009-05-15 12:33:34 +0000380 client = GClient(path, options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000381 client._LoadConfig()
382 return client
383
384 def SetDefaultConfig(self, solution_name, solution_url, safesync_url):
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000385 self.SetConfig(DEFAULT_CLIENT_FILE_TEXT % {
386 'solution_name': solution_name,
387 'solution_url': solution_url,
388 'safesync_url' : safesync_url,
389 })
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000390
391 def _SaveEntries(self, entries):
392 """Creates a .gclient_entries file to record the list of unique checkouts.
393
394 The .gclient_entries file lives in the same directory as .gclient.
395
396 Args:
397 entries: A sequence of solution names.
398 """
msb@chromium.org2e38de72009-09-28 17:04:47 +0000399 text = "entries = \\\n" + pprint.pformat(entries, 2) + '\n'
400 file_path = os.path.join(self._root_dir, self._options.entries_filename)
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000401 gclient_utils.FileWrite(file_path, text)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000402
403 def _ReadEntries(self):
404 """Read the .gclient_entries file for the given client.
405
406 Args:
407 client: The client for which the entries file should be read.
408
409 Returns:
410 A sequence of solution names, which will be empty if there is the
411 entries file hasn't been created yet.
412 """
413 scope = {}
414 filename = os.path.join(self._root_dir, self._options.entries_filename)
maruel@chromium.org0329e672009-05-13 18:41:04 +0000415 if not os.path.exists(filename):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000416 return []
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000417 exec(gclient_utils.FileRead(filename), scope)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000418 return scope["entries"]
419
420 class FromImpl:
421 """Used to implement the From syntax."""
422
423 def __init__(self, module_name):
424 self.module_name = module_name
425
426 def __str__(self):
427 return 'From("%s")' % self.module_name
428
429 class _VarImpl:
430 def __init__(self, custom_vars, local_scope):
431 self._custom_vars = custom_vars
432 self._local_scope = local_scope
433
434 def Lookup(self, var_name):
435 """Implements the Var syntax."""
436 if var_name in self._custom_vars:
437 return self._custom_vars[var_name]
438 elif var_name in self._local_scope.get("vars", {}):
439 return self._local_scope["vars"][var_name]
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000440 raise gclient_utils.Error("Var is not defined: %s" % var_name)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000441
442 def _ParseSolutionDeps(self, solution_name, solution_deps_content,
443 custom_vars):
444 """Parses the DEPS file for the specified solution.
445
446 Args:
447 solution_name: The name of the solution to query.
448 solution_deps_content: Content of the DEPS file for the solution
449 custom_vars: A dict of vars to override any vars defined in the DEPS file.
450
451 Returns:
452 A dict mapping module names (as relative paths) to URLs or an empty
453 dict if the solution does not have a DEPS file.
454 """
455 # Skip empty
456 if not solution_deps_content:
457 return {}
458 # Eval the content
459 local_scope = {}
460 var = self._VarImpl(custom_vars, local_scope)
461 global_scope = {"From": self.FromImpl, "Var": var.Lookup, "deps_os": {}}
462 exec(solution_deps_content, global_scope, local_scope)
463 deps = local_scope.get("deps", {})
464
465 # load os specific dependencies if defined. these dependencies may
466 # override or extend the values defined by the 'deps' member.
467 if "deps_os" in local_scope:
468 deps_os_choices = {
469 "win32": "win",
470 "win": "win",
471 "cygwin": "win",
472 "darwin": "mac",
473 "mac": "mac",
474 "unix": "unix",
475 "linux": "unix",
476 "linux2": "unix",
477 }
478
479 if self._options.deps_os is not None:
480 deps_to_include = self._options.deps_os.split(",")
481 if "all" in deps_to_include:
maruel@chromium.org55e724e2010-03-11 19:36:49 +0000482 deps_to_include = list(set(deps_os_choices.itervalues()))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000483 else:
484 deps_to_include = [deps_os_choices.get(self._options.platform, "unix")]
485
486 deps_to_include = set(deps_to_include)
487 for deps_os_key in deps_to_include:
488 os_deps = local_scope["deps_os"].get(deps_os_key, {})
489 if len(deps_to_include) > 1:
490 # Ignore any overrides when including deps for more than one
491 # platform, so we collect the broadest set of dependencies available.
492 # We may end up with the wrong revision of something for our
493 # platform, but this is the best we can do.
494 deps.update([x for x in os_deps.items() if not x[0] in deps])
495 else:
496 deps.update(os_deps)
497
498 if 'hooks' in local_scope:
499 self._deps_hooks.extend(local_scope['hooks'])
500
501 # If use_relative_paths is set in the DEPS file, regenerate
502 # the dictionary using paths relative to the directory containing
503 # the DEPS file.
504 if local_scope.get('use_relative_paths'):
505 rel_deps = {}
506 for d, url in deps.items():
507 # normpath is required to allow DEPS to use .. in their
508 # dependency local path.
509 rel_deps[os.path.normpath(os.path.join(solution_name, d))] = url
510 return rel_deps
511 else:
512 return deps
513
514 def _ParseAllDeps(self, solution_urls, solution_deps_content):
515 """Parse the complete list of dependencies for the client.
516
517 Args:
518 solution_urls: A dict mapping module names (as relative paths) to URLs
519 corresponding to the solutions specified by the client. This parameter
520 is passed as an optimization.
521 solution_deps_content: A dict mapping module names to the content
522 of their DEPS files
523
524 Returns:
525 A dict mapping module names (as relative paths) to URLs corresponding
526 to the entire set of dependencies to checkout for the given client.
527
528 Raises:
529 Error: If a dependency conflicts with another dependency or of a solution.
530 """
531 deps = {}
532 for solution in self.GetVar("solutions"):
533 custom_vars = solution.get("custom_vars", {})
534 solution_deps = self._ParseSolutionDeps(
535 solution["name"],
536 solution_deps_content[solution["name"]],
537 custom_vars)
538
539 # If a line is in custom_deps, but not in the solution, we want to append
540 # this line to the solution.
541 if "custom_deps" in solution:
542 for d in solution["custom_deps"]:
543 if d not in solution_deps:
544 solution_deps[d] = solution["custom_deps"][d]
545
546 for d in solution_deps:
547 if "custom_deps" in solution and d in solution["custom_deps"]:
548 # Dependency is overriden.
549 url = solution["custom_deps"][d]
550 if url is None:
551 continue
552 else:
553 url = solution_deps[d]
554 # if we have a From reference dependent on another solution, then
555 # just skip the From reference. When we pull deps for the solution,
556 # we will take care of this dependency.
557 #
558 # If multiple solutions all have the same From reference, then we
559 # should only add one to our list of dependencies.
560 if type(url) != str:
561 if url.module_name in solution_urls:
562 # Already parsed.
563 continue
564 if d in deps and type(deps[d]) != str:
565 if url.module_name == deps[d].module_name:
566 continue
567 else:
568 parsed_url = urlparse.urlparse(url)
569 scheme = parsed_url[0]
570 if not scheme:
571 # A relative url. Fetch the real base.
572 path = parsed_url[2]
573 if path[0] != "/":
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000574 raise gclient_utils.Error(
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000575 "relative DEPS entry \"%s\" must begin with a slash" % d)
msb@chromium.orge6f78352010-01-13 17:05:33 +0000576 # Create a scm just to query the full url.
577 scm = gclient_scm.CreateSCM(solution["url"], self._root_dir,
578 None)
579 url = scm.FullUrlForRelativeUrl(url)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000580 if d in deps and deps[d] != url:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000581 raise gclient_utils.Error(
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000582 "Solutions have conflicting versions of dependency \"%s\"" % d)
583 if d in solution_urls and solution_urls[d] != url:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000584 raise gclient_utils.Error(
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000585 "Dependency \"%s\" conflicts with specified solution" % d)
586 # Grab the dependency.
587 deps[d] = url
588 return deps
589
phajdan.jr@chromium.org71b40682009-07-31 23:40:09 +0000590 def _RunHookAction(self, hook_dict, matching_file_list):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000591 """Runs the action from a single hook.
592 """
593 command = hook_dict['action'][:]
594 if command[0] == 'python':
595 # If the hook specified "python" as the first item, the action is a
596 # Python script. Run it by starting a new copy of the same
597 # interpreter.
598 command[0] = sys.executable
599
phajdan.jr@chromium.org71b40682009-07-31 23:40:09 +0000600 if '$matching_files' in command:
phajdan.jr@chromium.org68f2e092009-08-06 17:05:35 +0000601 splice_index = command.index('$matching_files')
602 command[splice_index:splice_index + 1] = matching_file_list
phajdan.jr@chromium.org71b40682009-07-31 23:40:09 +0000603
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000604 # Use a discrete exit status code of 2 to indicate that a hook action
605 # failed. Users of this script may wish to treat hook action failures
606 # differently from VC failures.
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000607 gclient_utils.SubprocessCall(command, self._root_dir, fail_status=2)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000608
609 def _RunHooks(self, command, file_list, is_using_git):
610 """Evaluates all hooks, running actions as needed.
611 """
612 # Hooks only run for these command types.
613 if not command in ('update', 'revert', 'runhooks'):
614 return
615
evan@chromium.org67820ef2009-07-27 17:23:00 +0000616 # Hooks only run when --nohooks is not specified
617 if self._options.nohooks:
618 return
619
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000620 # Get any hooks from the .gclient file.
621 hooks = self.GetVar("hooks", [])
622 # Add any hooks found in DEPS files.
623 hooks.extend(self._deps_hooks)
624
625 # If "--force" was specified, run all hooks regardless of what files have
626 # changed. If the user is using git, then we don't know what files have
627 # changed so we always run all hooks.
628 if self._options.force or is_using_git:
629 for hook_dict in hooks:
phajdan.jr@chromium.org71b40682009-07-31 23:40:09 +0000630 self._RunHookAction(hook_dict, [])
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000631 return
632
633 # Run hooks on the basis of whether the files from the gclient operation
634 # match each hook's pattern.
635 for hook_dict in hooks:
636 pattern = re.compile(hook_dict['pattern'])
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000637 matching_file_list = [f for f in file_list if pattern.search(f)]
phajdan.jr@chromium.org71b40682009-07-31 23:40:09 +0000638 if matching_file_list:
639 self._RunHookAction(hook_dict, matching_file_list)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000640
641 def RunOnDeps(self, command, args):
642 """Runs a command on each dependency in a client and its dependencies.
643
644 The module's dependencies are specified in its top-level DEPS files.
645
646 Args:
647 command: The command to use (e.g., 'status' or 'diff')
648 args: list of str - extra arguments to add to the command line.
649
650 Raises:
651 Error: If the client has conflicting entries.
652 """
653 if not command in self.supported_commands:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000654 raise gclient_utils.Error("'%s' is an unsupported command" % command)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000655
656 # Check for revision overrides.
657 revision_overrides = {}
658 for revision in self._options.revisions:
659 if revision.find("@") == -1:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000660 raise gclient_utils.Error(
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000661 "Specify the full dependency when specifying a revision number.")
662 revision_elem = revision.split("@")
663 # Disallow conflicting revs
664 if revision_overrides.has_key(revision_elem[0]) and \
665 revision_overrides[revision_elem[0]] != revision_elem[1]:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000666 raise gclient_utils.Error(
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000667 "Conflicting revision numbers specified.")
668 revision_overrides[revision_elem[0]] = revision_elem[1]
669
670 solutions = self.GetVar("solutions")
671 if not solutions:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000672 raise gclient_utils.Error("No solution specified")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000673
674 # When running runhooks --force, there's no need to consult the SCM.
675 # All known hooks are expected to run unconditionally regardless of working
676 # copy state, so skip the SCM status check.
677 run_scm = not (command == 'runhooks' and self._options.force)
678
679 entries = {}
680 entries_deps_content = {}
681 file_list = []
682 # Run on the base solutions first.
683 for solution in solutions:
684 name = solution["name"]
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000685 deps_file = solution.get("deps_file", self._options.deps_file)
686 if '/' in deps_file or '\\' in deps_file:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000687 raise gclient_utils.Error('deps_file name must not be a path, just a '
688 'filename.')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000689 if name in entries:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000690 raise gclient_utils.Error("solution %s specified more than once" % name)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000691 url = solution["url"]
692 entries[name] = url
yaar@chromium.orgf1328042009-09-22 23:14:23 +0000693 if run_scm and url:
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000694 self._options.revision = revision_overrides.get(name)
msb@chromium.orgcb5442b2009-09-22 16:51:24 +0000695 scm = gclient_scm.CreateSCM(url, self._root_dir, name)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000696 scm.RunCommand(command, self._options, args, file_list)
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000697 file_list = [os.path.join(name, f.strip()) for f in file_list]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000698 self._options.revision = None
699 try:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000700 deps_content = gclient_utils.FileRead(
701 os.path.join(self._root_dir, name, deps_file))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000702 except IOError, e:
703 if e.errno != errno.ENOENT:
704 raise
705 deps_content = ""
706 entries_deps_content[name] = deps_content
707
708 # Process the dependencies next (sort alphanumerically to ensure that
709 # containing directories get populated first and for readability)
710 deps = self._ParseAllDeps(entries, entries_deps_content)
711 deps_to_process = deps.keys()
712 deps_to_process.sort()
713
714 # First pass for direct dependencies.
nasser@codeaurora.org1f7a3d12010-02-04 15:11:50 +0000715 if command == 'update' and not self._options.verbose:
716 pm = Progress('Syncing projects', len(deps_to_process))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000717 for d in deps_to_process:
nasser@codeaurora.org1f7a3d12010-02-04 15:11:50 +0000718 if command == 'update' and not self._options.verbose:
719 pm.update()
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000720 if type(deps[d]) == str:
721 url = deps[d]
722 entries[d] = url
723 if run_scm:
724 self._options.revision = revision_overrides.get(d)
msb@chromium.orgcb5442b2009-09-22 16:51:24 +0000725 scm = gclient_scm.CreateSCM(url, self._root_dir, d)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000726 scm.RunCommand(command, self._options, args, file_list)
727 self._options.revision = None
nasser@codeaurora.org1f7a3d12010-02-04 15:11:50 +0000728 if command == 'update' and not self._options.verbose:
729 pm.end()
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000730
731 # Second pass for inherited deps (via the From keyword)
732 for d in deps_to_process:
733 if type(deps[d]) != str:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000734 filename = os.path.join(self._root_dir,
735 deps[d].module_name,
736 self._options.deps_file)
737 content = gclient_utils.FileRead(filename)
738 sub_deps = self._ParseSolutionDeps(deps[d].module_name, content, {})
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000739 url = sub_deps[d]
740 entries[d] = url
741 if run_scm:
742 self._options.revision = revision_overrides.get(d)
msb@chromium.orgcb5442b2009-09-22 16:51:24 +0000743 scm = gclient_scm.CreateSCM(url, self._root_dir, d)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000744 scm.RunCommand(command, self._options, args, file_list)
745 self._options.revision = None
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000746
phajdan.jr@chromium.orgd83b2b22009-08-11 15:30:55 +0000747 # Convert all absolute paths to relative.
748 for i in range(len(file_list)):
749 # TODO(phajdan.jr): We should know exactly when the paths are absolute.
750 # It depends on the command being executed (like runhooks vs sync).
751 if not os.path.isabs(file_list[i]):
752 continue
753
754 prefix = os.path.commonprefix([self._root_dir.lower(),
755 file_list[i].lower()])
756 file_list[i] = file_list[i][len(prefix):]
757
758 # Strip any leading path separators.
759 while file_list[i].startswith('\\') or file_list[i].startswith('/'):
760 file_list[i] = file_list[i][1:]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000761
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000762 is_using_git = gclient_utils.IsUsingGit(self._root_dir, entries.keys())
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000763 self._RunHooks(command, file_list, is_using_git)
764
765 if command == 'update':
ajwong@chromium.orgcdcee802009-06-23 15:30:42 +0000766 # Notify the user if there is an orphaned entry in their working copy.
767 # Only delete the directory if there are no changes in it, and
768 # delete_unversioned_trees is set to true.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000769 prev_entries = self._ReadEntries()
770 for entry in prev_entries:
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000771 # Fix path separator on Windows.
772 entry_fixed = entry.replace('/', os.path.sep)
773 e_dir = os.path.join(self._root_dir, entry_fixed)
774 # Use entry and not entry_fixed there.
maruel@chromium.org0329e672009-05-13 18:41:04 +0000775 if entry not in entries and os.path.exists(e_dir):
msb@chromium.org83017012009-09-28 18:52:12 +0000776 modified_files = False
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +0000777 if isinstance(prev_entries, list):
msb@chromium.org83017012009-09-28 18:52:12 +0000778 # old .gclient_entries format was list, now dict
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +0000779 modified_files = gclient_scm.scm.SVN.CaptureStatus(e_dir)
msb@chromium.org83017012009-09-28 18:52:12 +0000780 else:
781 file_list = []
782 scm = gclient_scm.CreateSCM(prev_entries[entry], self._root_dir,
783 entry_fixed)
784 scm.status(self._options, [], file_list)
785 modified_files = file_list != []
786 if not self._options.delete_unversioned_trees or modified_files:
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000787 # There are modified files in this entry. Keep warning until
788 # removed.
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000789 print(("\nWARNING: \"%s\" is no longer part of this client. "
790 "It is recommended that you manually remove it.\n") %
791 entry_fixed)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000792 else:
793 # Delete the entry
maruel@chromium.orgdf7a3132009-05-12 17:49:49 +0000794 print("\n________ deleting \'%s\' " +
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000795 "in \'%s\'") % (entry_fixed, self._root_dir)
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000796 gclient_utils.RemoveDirectory(e_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000797 # record the current list of entries for next time
798 self._SaveEntries(entries)
799
800 def PrintRevInfo(self):
801 """Output revision info mapping for the client and its dependencies. This
nasser@codeaurora.org952d7c72010-03-01 20:41:01 +0000802 allows the capture of an overall "revision" for the source tree that can
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000803 be used to reproduce the same tree in the future. The actual output
804 contains enough information (source paths, svn server urls and revisions)
805 that it can be used either to generate external svn commands (without
806 gclient) or as input to gclient's --rev option (with some massaging of
807 the data).
808
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000809 Raises:
810 Error: If the client has conflicting entries.
811 """
812 # Check for revision overrides.
813 revision_overrides = {}
814 for revision in self._options.revisions:
815 if revision.find("@") < 0:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000816 raise gclient_utils.Error(
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000817 "Specify the full dependency when specifying a revision number.")
818 revision_elem = revision.split("@")
819 # Disallow conflicting revs
820 if revision_overrides.has_key(revision_elem[0]) and \
821 revision_overrides[revision_elem[0]] != revision_elem[1]:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000822 raise gclient_utils.Error(
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000823 "Conflicting revision numbers specified.")
824 revision_overrides[revision_elem[0]] = revision_elem[1]
825
826 solutions = self.GetVar("solutions")
827 if not solutions:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000828 raise gclient_utils.Error("No solution specified")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000829
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000830 # Inner helper to generate base url and rev tuple (including honoring
831 # |revision_overrides|)
832 def GetURLAndRev(name, original_url):
msb@chromium.orgd5a035e2009-11-13 17:58:26 +0000833 url, revision = gclient_utils.SplitUrlRevision(original_url)
msb@chromium.orgac915bb2009-11-13 17:03:01 +0000834 if not revision:
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000835 if revision_overrides.has_key(name):
msb@chromium.orgac915bb2009-11-13 17:03:01 +0000836 return (url, revision_overrides[name])
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000837 else:
msb@chromium.org0f282062009-11-06 20:14:02 +0000838 scm = gclient_scm.CreateSCM(solution["url"], self._root_dir, name)
msb@chromium.orgac915bb2009-11-13 17:03:01 +0000839 return (url, scm.revinfo(self._options, [], None))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000840 else:
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000841 if revision_overrides.has_key(name):
msb@chromium.orgac915bb2009-11-13 17:03:01 +0000842 return (url, revision_overrides[name])
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000843 else:
msb@chromium.orgac915bb2009-11-13 17:03:01 +0000844 return (url, revision)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000845
maruel@chromium.orge3da35f2010-03-09 21:40:45 +0000846 # text of the snapshot gclient file
847 new_gclient = ""
848 # Dictionary of { path : SCM url } to ensure no duplicate solutions
849 solution_names = {}
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +0000850 entries = {}
851 entries_deps_content = {}
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000852 # Run on the base solutions first.
853 for solution in solutions:
maruel@chromium.orge3da35f2010-03-09 21:40:45 +0000854 # Dictionary of { path : SCM url } to describe the gclient checkout
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000855 name = solution["name"]
maruel@chromium.orge3da35f2010-03-09 21:40:45 +0000856 if name in solution_names:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000857 raise gclient_utils.Error("solution %s specified more than once" % name)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000858 (url, rev) = GetURLAndRev(name, solution["url"])
msb@chromium.org770ff9e2009-09-23 17:18:18 +0000859 entries[name] = "%s@%s" % (url, rev)
maruel@chromium.orge3da35f2010-03-09 21:40:45 +0000860 solution_names[name] = "%s@%s" % (url, rev)
nasser@codeaurora.org952d7c72010-03-01 20:41:01 +0000861 deps_file = solution.get("deps_file", self._options.deps_file)
862 if '/' in deps_file or '\\' in deps_file:
863 raise gclient_utils.Error('deps_file name must not be a path, just a '
864 'filename.')
865 try:
866 deps_content = gclient_utils.FileRead(
867 os.path.join(self._root_dir, name, deps_file))
868 except IOError, e:
869 if e.errno != errno.ENOENT:
870 raise
871 deps_content = ""
872 entries_deps_content[name] = deps_content
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000873
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +0000874 # Process the dependencies next (sort alphanumerically to ensure that
875 # containing directories get populated first and for readability)
876 deps = self._ParseAllDeps(entries, entries_deps_content)
877 deps_to_process = deps.keys()
878 deps_to_process.sort()
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000879
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +0000880 # First pass for direct dependencies.
881 for d in deps_to_process:
882 if type(deps[d]) == str:
883 (url, rev) = GetURLAndRev(d, deps[d])
884 entries[d] = "%s@%s" % (url, rev)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000885
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +0000886 # Second pass for inherited deps (via the From keyword)
887 for d in deps_to_process:
888 if type(deps[d]) != str:
889 deps_parent_url = entries[deps[d].module_name]
890 if deps_parent_url.find("@") < 0:
891 raise gclient_utils.Error("From %s missing revisioned url" %
892 deps[d].module_name)
893 content = gclient_utils.FileRead(os.path.join(
894 self._root_dir,
895 deps[d].module_name,
896 self._options.deps_file))
897 sub_deps = self._ParseSolutionDeps(deps[d].module_name, content, {})
898 (url, rev) = GetURLAndRev(d, sub_deps[d])
899 entries[d] = "%s@%s" % (url, rev)
maruel@chromium.orge3da35f2010-03-09 21:40:45 +0000900
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +0000901 # Build the snapshot configuration string
902 if self._options.snapshot:
903 url = entries.pop(name)
904 custom_deps = ",\n ".join(["\"%s\": \"%s\"" % (x, entries[x])
905 for x in sorted(entries.keys())])
maruel@chromium.orge3da35f2010-03-09 21:40:45 +0000906
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +0000907 new_gclient += DEFAULT_SNAPSHOT_SOLUTION_TEXT % {
908 'solution_name': name,
909 'solution_url': url,
910 'safesync_url' : "",
911 'solution_deps': custom_deps,
912 }
913 else:
914 print(";\n".join(["%s: %s" % (x, entries[x])
915 for x in sorted(entries.keys())]))
maruel@chromium.orge3da35f2010-03-09 21:40:45 +0000916
917 # Print the snapshot configuration file
918 if self._options.snapshot:
919 config = DEFAULT_SNAPSHOT_FILE_TEXT % {'solution_list': new_gclient}
920 snapclient = GClient(self._root_dir, self._options)
921 snapclient.SetConfig(config)
922 print(snapclient._config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000923
924
925## gclient commands.
926
927
928def DoCleanup(options, args):
929 """Handle the cleanup subcommand.
930
931 Raises:
932 Error: if client isn't configured properly.
933 """
maruel@chromium.org2806acc2009-05-15 12:33:34 +0000934 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000935 if not client:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000936 raise gclient_utils.Error("client not configured; see 'gclient config'")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000937 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.
maruel@chromium.orgdf7a3132009-05-12 17:49:49 +0000940 print(client.ConfigContent())
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000941 return client.RunOnDeps('cleanup', args)
942
943
944def DoConfig(options, args):
945 """Handle the config subcommand.
946
947 Args:
948 options: If options.spec set, a string providing contents of config file.
949 args: The command line args. If spec is not set,
950 then args[0] is a string URL to get for config file.
951
952 Raises:
953 Error: on usage error
954 """
955 if len(args) < 1 and not options.spec:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000956 raise gclient_utils.Error("required argument missing; see 'gclient help "
957 "config'")
maruel@chromium.org0329e672009-05-13 18:41:04 +0000958 if os.path.exists(options.config_filename):
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000959 raise gclient_utils.Error("%s file already exists in the current directory"
960 % options.config_filename)
maruel@chromium.org2806acc2009-05-15 12:33:34 +0000961 client = GClient('.', options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000962 if options.spec:
963 client.SetConfig(options.spec)
964 else:
965 # TODO(darin): it would be nice to be able to specify an alternate relpath
966 # for the given URL.
maruel@chromium.org1ab7ffc2009-06-03 17:21:37 +0000967 base_url = args[0].rstrip('/')
968 name = base_url.split("/")[-1]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000969 safesync_url = ""
970 if len(args) > 1:
971 safesync_url = args[1]
972 client.SetDefaultConfig(name, base_url, safesync_url)
973 client.SaveConfig()
974
975
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +0000976def DoExport(options, args):
977 """Handle the export subcommand.
phajdan.jr@chromium.org71b40682009-07-31 23:40:09 +0000978
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +0000979 Raises:
980 Error: on usage error
981 """
982 if len(args) != 1:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000983 raise gclient_utils.Error("Need directory name")
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +0000984 client = GClient.LoadCurrentConfig(options)
985
986 if not client:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000987 raise gclient_utils.Error("client not configured; see 'gclient config'")
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +0000988
989 if options.verbose:
990 # Print out the .gclient file. This is longer than if we just printed the
991 # client dict, but more legible, and it might contain helpful comments.
992 print(client.ConfigContent())
993 return client.RunOnDeps('export', args)
994
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000995def DoHelp(options, args):
996 """Handle the help subcommand giving help for another subcommand.
997
998 Raises:
999 Error: if the command is unknown.
1000 """
maruel@chromium.orge3608df2009-11-10 20:22:57 +00001001 __pychecker__ = 'unusednames=options'
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001002 if len(args) == 1 and args[0] in COMMAND_USAGE_TEXT:
maruel@chromium.orgdf7a3132009-05-12 17:49:49 +00001003 print(COMMAND_USAGE_TEXT[args[0]])
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001004 else:
maruel@chromium.orge3608df2009-11-10 20:22:57 +00001005 raise gclient_utils.Error("unknown subcommand '%s'; see 'gclient help'" %
1006 args[0])
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001007
1008
kbr@google.comab318592009-09-04 00:54:55 +00001009def DoPack(options, args):
1010 """Handle the pack subcommand.
1011
1012 Raises:
1013 Error: if client isn't configured properly.
1014 """
1015 client = GClient.LoadCurrentConfig(options)
1016 if not client:
maruel@chromium.orge3608df2009-11-10 20:22:57 +00001017 raise gclient_utils.Error("client not configured; see 'gclient config'")
kbr@google.comab318592009-09-04 00:54:55 +00001018 if options.verbose:
1019 # Print out the .gclient file. This is longer than if we just printed the
1020 # client dict, but more legible, and it might contain helpful comments.
1021 print(client.ConfigContent())
kbr@google.comab318592009-09-04 00:54:55 +00001022 return client.RunOnDeps('pack', args)
1023
1024
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001025def DoStatus(options, args):
1026 """Handle the status subcommand.
1027
1028 Raises:
1029 Error: if client isn't configured properly.
1030 """
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001031 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001032 if not client:
maruel@chromium.orge3608df2009-11-10 20:22:57 +00001033 raise gclient_utils.Error("client not configured; see 'gclient config'")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001034 if options.verbose:
1035 # Print out the .gclient file. This is longer than if we just printed the
1036 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.orgdf7a3132009-05-12 17:49:49 +00001037 print(client.ConfigContent())
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001038 return client.RunOnDeps('status', args)
1039
1040
1041def DoUpdate(options, args):
1042 """Handle the update and sync subcommands.
1043
1044 Raises:
1045 Error: if client isn't configured properly.
1046 """
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001047 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001048
1049 if not client:
maruel@chromium.orge3608df2009-11-10 20:22:57 +00001050 raise gclient_utils.Error("client not configured; see 'gclient config'")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001051
1052 if not options.head:
1053 solutions = client.GetVar('solutions')
1054 if solutions:
1055 for s in solutions:
1056 if s.get('safesync_url', ''):
1057 # rip through revisions and make sure we're not over-riding
1058 # something that was explicitly passed
1059 has_key = False
1060 for r in options.revisions:
1061 if r.split('@')[0] == s['name']:
1062 has_key = True
1063 break
1064
1065 if not has_key:
1066 handle = urllib.urlopen(s['safesync_url'])
1067 rev = handle.read().strip()
1068 handle.close()
1069 if len(rev):
1070 options.revisions.append(s['name']+'@'+rev)
1071
1072 if options.verbose:
1073 # Print out the .gclient file. This is longer than if we just printed the
1074 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.orgdf7a3132009-05-12 17:49:49 +00001075 print(client.ConfigContent())
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001076 return client.RunOnDeps('update', args)
1077
1078
1079def DoDiff(options, args):
1080 """Handle the diff subcommand.
1081
1082 Raises:
1083 Error: if client isn't configured properly.
1084 """
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001085 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001086 if not client:
maruel@chromium.orge3608df2009-11-10 20:22:57 +00001087 raise gclient_utils.Error("client not configured; see 'gclient config'")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001088 if options.verbose:
1089 # Print out the .gclient file. This is longer than if we just printed the
1090 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.orgdf7a3132009-05-12 17:49:49 +00001091 print(client.ConfigContent())
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001092 return client.RunOnDeps('diff', args)
1093
1094
1095def DoRevert(options, args):
1096 """Handle the revert subcommand.
1097
1098 Raises:
1099 Error: if client isn't configured properly.
1100 """
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001101 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001102 if not client:
maruel@chromium.orge3608df2009-11-10 20:22:57 +00001103 raise gclient_utils.Error("client not configured; see 'gclient config'")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001104 return client.RunOnDeps('revert', args)
1105
1106
1107def DoRunHooks(options, args):
1108 """Handle the runhooks subcommand.
1109
1110 Raises:
1111 Error: if client isn't configured properly.
1112 """
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001113 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001114 if not client:
maruel@chromium.orge3608df2009-11-10 20:22:57 +00001115 raise gclient_utils.Error("client not configured; see 'gclient config'")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001116 if options.verbose:
1117 # Print out the .gclient file. This is longer than if we just printed the
1118 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.orgdf7a3132009-05-12 17:49:49 +00001119 print(client.ConfigContent())
maruel@chromium.org5df6a462009-08-28 18:52:26 +00001120 options.force = True
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001121 return client.RunOnDeps('runhooks', args)
1122
1123
1124def DoRevInfo(options, args):
1125 """Handle the revinfo subcommand.
1126
1127 Raises:
1128 Error: if client isn't configured properly.
1129 """
maruel@chromium.orge3608df2009-11-10 20:22:57 +00001130 __pychecker__ = 'unusednames=args'
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001131 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001132 if not client:
maruel@chromium.orge3608df2009-11-10 20:22:57 +00001133 raise gclient_utils.Error("client not configured; see 'gclient config'")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001134 client.PrintRevInfo()
1135
1136
1137gclient_command_map = {
1138 "cleanup": DoCleanup,
1139 "config": DoConfig,
1140 "diff": DoDiff,
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +00001141 "export": DoExport,
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001142 "help": DoHelp,
kbr@google.comab318592009-09-04 00:54:55 +00001143 "pack": DoPack,
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001144 "status": DoStatus,
1145 "sync": DoUpdate,
1146 "update": DoUpdate,
1147 "revert": DoRevert,
1148 "runhooks": DoRunHooks,
1149 "revinfo" : DoRevInfo,
1150}
1151
1152
1153def DispatchCommand(command, options, args, command_map=None):
1154 """Dispatches the appropriate subcommand based on command line arguments."""
1155 if command_map is None:
1156 command_map = gclient_command_map
1157
1158 if command in command_map:
1159 return command_map[command](options, args)
1160 else:
maruel@chromium.orge3608df2009-11-10 20:22:57 +00001161 raise gclient_utils.Error("unknown subcommand '%s'; see 'gclient help'" %
1162 command)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001163
1164
1165def Main(argv):
1166 """Parse command line arguments and dispatch command."""
1167
1168 option_parser = optparse.OptionParser(usage=DEFAULT_USAGE_TEXT,
1169 version=__version__)
1170 option_parser.disable_interspersed_args()
1171 option_parser.add_option("", "--force", action="store_true", default=False,
1172 help=("(update/sync only) force update even "
1173 "for modules which haven't changed"))
evan@chromium.org67820ef2009-07-27 17:23:00 +00001174 option_parser.add_option("", "--nohooks", action="store_true", default=False,
1175 help=("(update/sync/revert only) prevent the hooks from "
1176 "running"))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001177 option_parser.add_option("", "--revision", action="append", dest="revisions",
1178 metavar="REV", default=[],
1179 help=("(update/sync only) sync to a specific "
1180 "revision, can be used multiple times for "
1181 "each solution, e.g. --revision=src@123, "
1182 "--revision=internal@32"))
1183 option_parser.add_option("", "--deps", default=None, dest="deps_os",
1184 metavar="OS_LIST",
1185 help=("(update/sync only) sync deps for the "
1186 "specified (comma-separated) platform(s); "
1187 "'all' will sync all platforms"))
davemoore@chromium.org793796d2010-02-19 17:27:41 +00001188 option_parser.add_option("", "--reset", action="store_true", default=False,
1189 help=("(update/sync only) resets any local changes "
1190 "before updating (git only)"))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001191 option_parser.add_option("", "--spec", default=None,
1192 help=("(config only) create a gclient file "
1193 "containing the provided string"))
maruel@chromium.orga6220d12010-01-06 21:04:17 +00001194 option_parser.add_option("-v", "--verbose", action="count", default=0,
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001195 help="produce additional output for diagnostics")
maruel@chromium.org7753d242009-10-07 17:40:24 +00001196 option_parser.add_option("", "--manually_grab_svn_rev", action="store_true",
1197 default=False,
1198 help="Skip svn up whenever possible by requesting "
1199 "actual HEAD revision from the repository")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001200 option_parser.add_option("", "--head", action="store_true", default=False,
1201 help=("skips any safesync_urls specified in "
1202 "configured solutions"))
ajwong@chromium.orgcdcee802009-06-23 15:30:42 +00001203 option_parser.add_option("", "--delete_unversioned_trees",
1204 action="store_true", default=False,
1205 help=("on update, delete any unexpected "
1206 "unversioned trees that are in the checkout"))
maruel@chromium.orge3da35f2010-03-09 21:40:45 +00001207 option_parser.add_option("", "--snapshot", action="store_true", default=False,
1208 help=("(revinfo only), create a snapshot file "
1209 "of the current version of all repositories"))
1210 option_parser.add_option("", "--gclientfile", default=None,
1211 metavar="FILENAME",
1212 help=("specify an alternate .gclient file"))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001213
1214 if len(argv) < 2:
1215 # Users don't need to be told to use the 'help' command.
1216 option_parser.print_help()
1217 return 1
1218 # Add manual support for --version as first argument.
1219 if argv[1] == '--version':
1220 option_parser.print_version()
1221 return 0
1222
1223 # Add manual support for --help as first argument.
1224 if argv[1] == '--help':
1225 argv[1] = 'help'
1226
1227 command = argv[1]
1228 options, args = option_parser.parse_args(argv[2:])
1229
1230 if len(argv) < 3 and command == "help":
1231 option_parser.print_help()
1232 return 0
1233
maruel@chromium.orga6220d12010-01-06 21:04:17 +00001234 if options.verbose > 1:
maruel@chromium.org754960e2009-09-21 12:31:05 +00001235 logging.basicConfig(level=logging.DEBUG)
1236
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001237 # Files used for configuration and state saving.
1238 options.config_filename = os.environ.get("GCLIENT_FILE", ".gclient")
maruel@chromium.orge3da35f2010-03-09 21:40:45 +00001239 if options.gclientfile:
1240 options.config_filename = options.gclientfile
1241 options.entries_filename = options.config_filename + "_entries"
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001242 options.deps_file = "DEPS"
1243
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001244 options.platform = sys.platform
1245 return DispatchCommand(command, options, args)
1246
1247
1248if "__main__" == __name__:
1249 try:
1250 result = Main(sys.argv)
maruel@chromium.orge3608df2009-11-10 20:22:57 +00001251 except gclient_utils.Error, e:
maruel@chromium.orgdf7a3132009-05-12 17:49:49 +00001252 print >> sys.stderr, "Error: %s" % str(e)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001253 result = 1
1254 sys.exit(result)
1255
1256# vim: ts=2:sw=2:tw=80:et: