blob: deb6d8d291ccb8c8a6a0c0aee7ff79c9415dad99 [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
iposva@chromium.org8cf7a392010-04-07 17:20:26 +0000156URL. Otherwise, a --spec option must be provided. A --name option overrides
157the default name for the solutions.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000158
159usage: config [option | url] [safesync url]
160
161Valid options:
iposva@chromium.org8cf7a392010-04-07 17:20:26 +0000162 --name path : alternate relative path for the solution
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000163 --spec=GCLIENT_SPEC : contents of .gclient are read from string parameter.
164 *Note that due to Cygwin/Python brokenness, it
165 probably can't contain any newlines.*
166
167Examples:
168 gclient config https://gclient.googlecode.com/svn/trunk/gclient
169 configure a new client to check out gclient.py tool sources
iposva@chromium.org8cf7a392010-04-07 17:20:26 +0000170 gclient config --name tools https://gclient.googlecode.com/svn/trunk/gclient
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000171 gclient config --spec='solutions=[{"name":"gclient","""
172 '"url":"https://gclient.googlecode.com/svn/trunk/gclient",'
173 '"custom_deps":{}}]',
174 "diff": """Display the differences between two revisions of modules.
175(Does 'svn diff' for each checked out module and dependences.)
176Additional args and options to 'svn diff' can be passed after
177gclient options.
178
179usage: diff [options] [--] [svn args/options]
180
181Valid options:
182 --verbose : output additional diagnostics
183
184Examples:
185 gclient diff
186 simple 'svn diff' for configured client and dependences
187 gclient diff -- -x -b
188 use 'svn diff -x -b' to suppress whitespace-only differences
189 gclient diff -- -r HEAD -x -b
190 diff versus the latest version of each module
191""",
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +0000192 "export":
193 """Wrapper for svn export for all managed directories
194""",
kbr@google.comab318592009-09-04 00:54:55 +0000195 "pack":
196
197 """Generate a patch which can be applied at the root of the tree.
198Internally, runs 'svn diff' on each checked out module and
199dependencies, and performs minimal postprocessing of the output. The
200resulting patch is printed to stdout and can be applied to a freshly
201checked out tree via 'patch -p0 < patchfile'. Additional args and
202options to 'svn diff' can be passed after gclient options.
203
204usage: pack [options] [--] [svn args/options]
205
206Valid options:
207 --verbose : output additional diagnostics
208
209Examples:
210 gclient pack > patch.txt
211 generate simple patch for configured client and dependences
212 gclient pack -- -x -b > patch.txt
213 generate patch using 'svn diff -x -b' to suppress
214 whitespace-only differences
215 gclient pack -- -r HEAD -x -b > patch.txt
216 generate patch, diffing each file versus the latest version of
217 each module
218""",
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000219 "revert":
220 """Revert every file in every managed directory in the client view.
221
222usage: revert
223""",
224 "status":
225 """Show the status of client and dependent modules, using 'svn diff'
226for each module. Additional options and args may be passed to 'svn diff'.
227
228usage: status [options] [--] [svn diff args/options]
229
230Valid options:
231 --verbose : output additional diagnostics
evan@chromium.org67820ef2009-07-27 17:23:00 +0000232 --nohooks : don't run the hooks after the update is complete
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000233""",
234 "sync": GENERIC_UPDATE_USAGE_TEXT % {"cmd": "sync", "alias": "update"},
235 "update": GENERIC_UPDATE_USAGE_TEXT % {"cmd": "update", "alias": "sync"},
236 "help": """Describe the usage of this program or its subcommands.
237
238usage: help [options] [subcommand]
239
240Valid options:
241 --verbose : output additional diagnostics
242""",
243 "runhooks":
244 """Runs hooks for files that have been modified in the local working copy,
maruel@chromium.org5df6a462009-08-28 18:52:26 +0000245according to 'svn status'. Implies --force.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000246
247usage: runhooks [options]
248
249Valid options:
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000250 --verbose : output additional diagnostics
251""",
252 "revinfo":
253 """Outputs source path, server URL and revision information for every
nasser@codeaurora.org5d63eb82010-03-24 23:22:09 +0000254dependency in all solutions.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000255
256usage: revinfo [options]
257""",
258}
259
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000260DEFAULT_CLIENT_FILE_TEXT = ("""\
261# An element of this array (a "solution") describes a repository directory
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000262# that will be checked out into your working copy. Each solution may
263# optionally define additional dependencies (via its DEPS file) to be
264# checked out alongside the solution's directory. A solution may also
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000265# specify custom dependencies (via the "custom_deps" property) that
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000266# override or augment the dependencies specified by the DEPS file.
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000267# If a "safesync_url" is specified, it is assumed to reference the location of
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000268# a text file which contains nothing but the last known good SCM revision to
269# sync against. It is fetched if specified and used unless --head is passed
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000270
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000271solutions = [
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000272 { "name" : "%(solution_name)s",
273 "url" : "%(solution_url)s",
274 "custom_deps" : {
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000275 # To use the trunk of a component instead of what's in DEPS:
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000276 #"component": "https://svnserver/component/trunk/",
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000277 # To exclude a component from your working copy:
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000278 #"data/really_large_component": None,
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000279 },
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000280 "safesync_url": "%(safesync_url)s"
281 },
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000282]
283""")
284
maruel@chromium.orge3da35f2010-03-09 21:40:45 +0000285DEFAULT_SNAPSHOT_SOLUTION_TEXT = ("""\
286 { "name" : "%(solution_name)s",
287 "url" : "%(solution_url)s",
288 "custom_deps" : {
289 %(solution_deps)s,
290 },
291 "safesync_url": "%(safesync_url)s"
292 },
293""")
294
295DEFAULT_SNAPSHOT_FILE_TEXT = ("""\
296# An element of this array (a "solution") describes a repository directory
297# that will be checked out into your working copy. Each solution may
298# optionally define additional dependencies (via its DEPS file) to be
299# checked out alongside the solution's directory. A solution may also
300# specify custom dependencies (via the "custom_deps" property) that
301# override or augment the dependencies specified by the DEPS file.
302# If a "safesync_url" is specified, it is assumed to reference the location of
303# a text file which contains nothing but the last known good SCM revision to
304# sync against. It is fetched if specified and used unless --head is passed
305
306solutions = [
307%(solution_list)s
308]
309""")
310
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000311
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000312## GClient implementation.
313
314
315class GClient(object):
316 """Object that represent a gclient checkout."""
317
318 supported_commands = [
kbr@google.comab318592009-09-04 00:54:55 +0000319 'cleanup', 'diff', 'export', 'pack', 'revert', 'status', 'update',
320 'runhooks'
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000321 ]
322
323 def __init__(self, root_dir, options):
324 self._root_dir = root_dir
325 self._options = options
326 self._config_content = None
327 self._config_dict = {}
328 self._deps_hooks = []
329
330 def SetConfig(self, content):
331 self._config_dict = {}
332 self._config_content = content
skylined@chromium.orgdf0032c2009-05-29 10:43:56 +0000333 try:
334 exec(content, self._config_dict)
335 except SyntaxError, e:
336 try:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000337 __pychecker__ = 'no-objattrs'
skylined@chromium.orgdf0032c2009-05-29 10:43:56 +0000338 # Try to construct a human readable error message
339 error_message = [
340 'There is a syntax error in your configuration file.',
341 'Line #%s, character %s:' % (e.lineno, e.offset),
342 '"%s"' % re.sub(r'[\r\n]*$', '', e.text) ]
343 except:
344 # Something went wrong, re-raise the original exception
345 raise e
346 else:
347 # Raise a new exception with the human readable message:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000348 raise gclient_utils.Error('\n'.join(error_message))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000349
350 def SaveConfig(self):
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000351 gclient_utils.FileWrite(os.path.join(self._root_dir,
352 self._options.config_filename),
353 self._config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000354
355 def _LoadConfig(self):
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000356 client_source = gclient_utils.FileRead(
357 os.path.join(self._root_dir, self._options.config_filename))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000358 self.SetConfig(client_source)
359
360 def ConfigContent(self):
361 return self._config_content
362
363 def GetVar(self, key, default=None):
364 return self._config_dict.get(key, default)
365
366 @staticmethod
367 def LoadCurrentConfig(options, from_dir=None):
368 """Searches for and loads a .gclient file relative to the current working
369 dir.
370
371 Returns:
372 A dict representing the contents of the .gclient file or an empty dict if
373 the .gclient file doesn't exist.
374 """
375 if not from_dir:
376 from_dir = os.curdir
377 path = os.path.realpath(from_dir)
maruel@chromium.org0329e672009-05-13 18:41:04 +0000378 while not os.path.exists(os.path.join(path, options.config_filename)):
maruel@chromium.org55e724e2010-03-11 19:36:49 +0000379 split_path = os.path.split(path)
380 if not split_path[1]:
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000381 return None
maruel@chromium.org55e724e2010-03-11 19:36:49 +0000382 path = split_path[0]
maruel@chromium.org2806acc2009-05-15 12:33:34 +0000383 client = GClient(path, options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000384 client._LoadConfig()
385 return client
386
387 def SetDefaultConfig(self, solution_name, solution_url, safesync_url):
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000388 self.SetConfig(DEFAULT_CLIENT_FILE_TEXT % {
389 'solution_name': solution_name,
390 'solution_url': solution_url,
391 'safesync_url' : safesync_url,
392 })
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000393
394 def _SaveEntries(self, entries):
395 """Creates a .gclient_entries file to record the list of unique checkouts.
396
397 The .gclient_entries file lives in the same directory as .gclient.
398
399 Args:
400 entries: A sequence of solution names.
401 """
msb@chromium.org2e38de72009-09-28 17:04:47 +0000402 text = "entries = \\\n" + pprint.pformat(entries, 2) + '\n'
403 file_path = os.path.join(self._root_dir, self._options.entries_filename)
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000404 gclient_utils.FileWrite(file_path, text)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000405
406 def _ReadEntries(self):
407 """Read the .gclient_entries file for the given client.
408
409 Args:
410 client: The client for which the entries file should be read.
411
412 Returns:
413 A sequence of solution names, which will be empty if there is the
414 entries file hasn't been created yet.
415 """
416 scope = {}
417 filename = os.path.join(self._root_dir, self._options.entries_filename)
maruel@chromium.org0329e672009-05-13 18:41:04 +0000418 if not os.path.exists(filename):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000419 return []
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000420 exec(gclient_utils.FileRead(filename), scope)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000421 return scope["entries"]
422
423 class FromImpl:
424 """Used to implement the From syntax."""
425
426 def __init__(self, module_name):
427 self.module_name = module_name
428
429 def __str__(self):
430 return 'From("%s")' % self.module_name
431
432 class _VarImpl:
433 def __init__(self, custom_vars, local_scope):
434 self._custom_vars = custom_vars
435 self._local_scope = local_scope
436
437 def Lookup(self, var_name):
438 """Implements the Var syntax."""
439 if var_name in self._custom_vars:
440 return self._custom_vars[var_name]
441 elif var_name in self._local_scope.get("vars", {}):
442 return self._local_scope["vars"][var_name]
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000443 raise gclient_utils.Error("Var is not defined: %s" % var_name)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000444
445 def _ParseSolutionDeps(self, solution_name, solution_deps_content,
446 custom_vars):
447 """Parses the DEPS file for the specified solution.
448
449 Args:
450 solution_name: The name of the solution to query.
451 solution_deps_content: Content of the DEPS file for the solution
452 custom_vars: A dict of vars to override any vars defined in the DEPS file.
453
454 Returns:
455 A dict mapping module names (as relative paths) to URLs or an empty
456 dict if the solution does not have a DEPS file.
457 """
458 # Skip empty
459 if not solution_deps_content:
460 return {}
461 # Eval the content
462 local_scope = {}
463 var = self._VarImpl(custom_vars, local_scope)
464 global_scope = {"From": self.FromImpl, "Var": var.Lookup, "deps_os": {}}
465 exec(solution_deps_content, global_scope, local_scope)
466 deps = local_scope.get("deps", {})
467
468 # load os specific dependencies if defined. these dependencies may
469 # override or extend the values defined by the 'deps' member.
470 if "deps_os" in local_scope:
471 deps_os_choices = {
472 "win32": "win",
473 "win": "win",
474 "cygwin": "win",
475 "darwin": "mac",
476 "mac": "mac",
477 "unix": "unix",
478 "linux": "unix",
479 "linux2": "unix",
480 }
481
482 if self._options.deps_os is not None:
483 deps_to_include = self._options.deps_os.split(",")
484 if "all" in deps_to_include:
maruel@chromium.org55e724e2010-03-11 19:36:49 +0000485 deps_to_include = list(set(deps_os_choices.itervalues()))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000486 else:
487 deps_to_include = [deps_os_choices.get(self._options.platform, "unix")]
488
489 deps_to_include = set(deps_to_include)
490 for deps_os_key in deps_to_include:
491 os_deps = local_scope["deps_os"].get(deps_os_key, {})
492 if len(deps_to_include) > 1:
493 # Ignore any overrides when including deps for more than one
494 # platform, so we collect the broadest set of dependencies available.
495 # We may end up with the wrong revision of something for our
496 # platform, but this is the best we can do.
497 deps.update([x for x in os_deps.items() if not x[0] in deps])
498 else:
499 deps.update(os_deps)
500
501 if 'hooks' in local_scope:
502 self._deps_hooks.extend(local_scope['hooks'])
503
504 # If use_relative_paths is set in the DEPS file, regenerate
505 # the dictionary using paths relative to the directory containing
506 # the DEPS file.
507 if local_scope.get('use_relative_paths'):
508 rel_deps = {}
509 for d, url in deps.items():
510 # normpath is required to allow DEPS to use .. in their
511 # dependency local path.
512 rel_deps[os.path.normpath(os.path.join(solution_name, d))] = url
513 return rel_deps
514 else:
515 return deps
516
517 def _ParseAllDeps(self, solution_urls, solution_deps_content):
518 """Parse the complete list of dependencies for the client.
519
520 Args:
521 solution_urls: A dict mapping module names (as relative paths) to URLs
522 corresponding to the solutions specified by the client. This parameter
523 is passed as an optimization.
524 solution_deps_content: A dict mapping module names to the content
525 of their DEPS files
526
527 Returns:
528 A dict mapping module names (as relative paths) to URLs corresponding
529 to the entire set of dependencies to checkout for the given client.
530
531 Raises:
532 Error: If a dependency conflicts with another dependency or of a solution.
533 """
534 deps = {}
535 for solution in self.GetVar("solutions"):
536 custom_vars = solution.get("custom_vars", {})
537 solution_deps = self._ParseSolutionDeps(
538 solution["name"],
539 solution_deps_content[solution["name"]],
540 custom_vars)
541
542 # If a line is in custom_deps, but not in the solution, we want to append
543 # this line to the solution.
544 if "custom_deps" in solution:
545 for d in solution["custom_deps"]:
546 if d not in solution_deps:
547 solution_deps[d] = solution["custom_deps"][d]
548
549 for d in solution_deps:
550 if "custom_deps" in solution and d in solution["custom_deps"]:
551 # Dependency is overriden.
552 url = solution["custom_deps"][d]
553 if url is None:
554 continue
555 else:
556 url = solution_deps[d]
557 # if we have a From reference dependent on another solution, then
558 # just skip the From reference. When we pull deps for the solution,
559 # we will take care of this dependency.
560 #
561 # If multiple solutions all have the same From reference, then we
562 # should only add one to our list of dependencies.
563 if type(url) != str:
564 if url.module_name in solution_urls:
565 # Already parsed.
566 continue
567 if d in deps and type(deps[d]) != str:
568 if url.module_name == deps[d].module_name:
569 continue
570 else:
571 parsed_url = urlparse.urlparse(url)
572 scheme = parsed_url[0]
573 if not scheme:
574 # A relative url. Fetch the real base.
575 path = parsed_url[2]
576 if path[0] != "/":
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000577 raise gclient_utils.Error(
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000578 "relative DEPS entry \"%s\" must begin with a slash" % d)
msb@chromium.orge6f78352010-01-13 17:05:33 +0000579 # Create a scm just to query the full url.
580 scm = gclient_scm.CreateSCM(solution["url"], self._root_dir,
581 None)
582 url = scm.FullUrlForRelativeUrl(url)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000583 if d in deps and deps[d] != url:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000584 raise gclient_utils.Error(
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000585 "Solutions have conflicting versions of dependency \"%s\"" % d)
586 if d in solution_urls and solution_urls[d] != url:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000587 raise gclient_utils.Error(
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000588 "Dependency \"%s\" conflicts with specified solution" % d)
589 # Grab the dependency.
590 deps[d] = url
591 return deps
592
phajdan.jr@chromium.org71b40682009-07-31 23:40:09 +0000593 def _RunHookAction(self, hook_dict, matching_file_list):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000594 """Runs the action from a single hook.
595 """
596 command = hook_dict['action'][:]
597 if command[0] == 'python':
598 # If the hook specified "python" as the first item, the action is a
599 # Python script. Run it by starting a new copy of the same
600 # interpreter.
601 command[0] = sys.executable
602
phajdan.jr@chromium.org71b40682009-07-31 23:40:09 +0000603 if '$matching_files' in command:
phajdan.jr@chromium.org68f2e092009-08-06 17:05:35 +0000604 splice_index = command.index('$matching_files')
605 command[splice_index:splice_index + 1] = matching_file_list
phajdan.jr@chromium.org71b40682009-07-31 23:40:09 +0000606
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000607 # Use a discrete exit status code of 2 to indicate that a hook action
608 # failed. Users of this script may wish to treat hook action failures
609 # differently from VC failures.
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000610 gclient_utils.SubprocessCall(command, self._root_dir, fail_status=2)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000611
612 def _RunHooks(self, command, file_list, is_using_git):
613 """Evaluates all hooks, running actions as needed.
614 """
615 # Hooks only run for these command types.
616 if not command in ('update', 'revert', 'runhooks'):
617 return
618
evan@chromium.org67820ef2009-07-27 17:23:00 +0000619 # Hooks only run when --nohooks is not specified
620 if self._options.nohooks:
621 return
622
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000623 # Get any hooks from the .gclient file.
624 hooks = self.GetVar("hooks", [])
625 # Add any hooks found in DEPS files.
626 hooks.extend(self._deps_hooks)
627
628 # If "--force" was specified, run all hooks regardless of what files have
629 # changed. If the user is using git, then we don't know what files have
630 # changed so we always run all hooks.
631 if self._options.force or is_using_git:
632 for hook_dict in hooks:
phajdan.jr@chromium.org71b40682009-07-31 23:40:09 +0000633 self._RunHookAction(hook_dict, [])
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000634 return
635
636 # Run hooks on the basis of whether the files from the gclient operation
637 # match each hook's pattern.
638 for hook_dict in hooks:
639 pattern = re.compile(hook_dict['pattern'])
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000640 matching_file_list = [f for f in file_list if pattern.search(f)]
phajdan.jr@chromium.org71b40682009-07-31 23:40:09 +0000641 if matching_file_list:
642 self._RunHookAction(hook_dict, matching_file_list)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000643
644 def RunOnDeps(self, command, args):
645 """Runs a command on each dependency in a client and its dependencies.
646
647 The module's dependencies are specified in its top-level DEPS files.
648
649 Args:
650 command: The command to use (e.g., 'status' or 'diff')
651 args: list of str - extra arguments to add to the command line.
652
653 Raises:
654 Error: If the client has conflicting entries.
655 """
656 if not command in self.supported_commands:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000657 raise gclient_utils.Error("'%s' is an unsupported command" % command)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000658
659 # Check for revision overrides.
660 revision_overrides = {}
661 for revision in self._options.revisions:
662 if revision.find("@") == -1:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000663 raise gclient_utils.Error(
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000664 "Specify the full dependency when specifying a revision number.")
665 revision_elem = revision.split("@")
666 # Disallow conflicting revs
667 if revision_overrides.has_key(revision_elem[0]) and \
668 revision_overrides[revision_elem[0]] != revision_elem[1]:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000669 raise gclient_utils.Error(
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000670 "Conflicting revision numbers specified.")
671 revision_overrides[revision_elem[0]] = revision_elem[1]
672
673 solutions = self.GetVar("solutions")
674 if not solutions:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000675 raise gclient_utils.Error("No solution specified")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000676
677 # When running runhooks --force, there's no need to consult the SCM.
678 # All known hooks are expected to run unconditionally regardless of working
679 # copy state, so skip the SCM status check.
680 run_scm = not (command == 'runhooks' and self._options.force)
681
682 entries = {}
683 entries_deps_content = {}
684 file_list = []
685 # Run on the base solutions first.
686 for solution in solutions:
687 name = solution["name"]
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000688 deps_file = solution.get("deps_file", self._options.deps_file)
689 if '/' in deps_file or '\\' in deps_file:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000690 raise gclient_utils.Error('deps_file name must not be a path, just a '
691 'filename.')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000692 if name in entries:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000693 raise gclient_utils.Error("solution %s specified more than once" % name)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000694 url = solution["url"]
695 entries[name] = url
yaar@chromium.orgf1328042009-09-22 23:14:23 +0000696 if run_scm and url:
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000697 self._options.revision = revision_overrides.get(name)
msb@chromium.orgcb5442b2009-09-22 16:51:24 +0000698 scm = gclient_scm.CreateSCM(url, self._root_dir, name)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000699 scm.RunCommand(command, self._options, args, file_list)
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000700 file_list = [os.path.join(name, f.strip()) for f in file_list]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000701 self._options.revision = None
702 try:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000703 deps_content = gclient_utils.FileRead(
704 os.path.join(self._root_dir, name, deps_file))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000705 except IOError, e:
706 if e.errno != errno.ENOENT:
707 raise
708 deps_content = ""
709 entries_deps_content[name] = deps_content
710
711 # Process the dependencies next (sort alphanumerically to ensure that
712 # containing directories get populated first and for readability)
713 deps = self._ParseAllDeps(entries, entries_deps_content)
714 deps_to_process = deps.keys()
715 deps_to_process.sort()
716
717 # First pass for direct dependencies.
nasser@codeaurora.org1f7a3d12010-02-04 15:11:50 +0000718 if command == 'update' and not self._options.verbose:
719 pm = Progress('Syncing projects', len(deps_to_process))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000720 for d in deps_to_process:
nasser@codeaurora.org1f7a3d12010-02-04 15:11:50 +0000721 if command == 'update' and not self._options.verbose:
722 pm.update()
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000723 if type(deps[d]) == str:
724 url = deps[d]
725 entries[d] = url
726 if run_scm:
727 self._options.revision = revision_overrides.get(d)
msb@chromium.orgcb5442b2009-09-22 16:51:24 +0000728 scm = gclient_scm.CreateSCM(url, self._root_dir, d)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000729 scm.RunCommand(command, self._options, args, file_list)
730 self._options.revision = None
nasser@codeaurora.org1f7a3d12010-02-04 15:11:50 +0000731 if command == 'update' and not self._options.verbose:
732 pm.end()
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000733
734 # Second pass for inherited deps (via the From keyword)
735 for d in deps_to_process:
736 if type(deps[d]) != str:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000737 filename = os.path.join(self._root_dir,
738 deps[d].module_name,
739 self._options.deps_file)
740 content = gclient_utils.FileRead(filename)
741 sub_deps = self._ParseSolutionDeps(deps[d].module_name, content, {})
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000742 url = sub_deps[d]
743 entries[d] = url
744 if run_scm:
745 self._options.revision = revision_overrides.get(d)
msb@chromium.orgcb5442b2009-09-22 16:51:24 +0000746 scm = gclient_scm.CreateSCM(url, self._root_dir, d)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000747 scm.RunCommand(command, self._options, args, file_list)
748 self._options.revision = None
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000749
phajdan.jr@chromium.orgd83b2b22009-08-11 15:30:55 +0000750 # Convert all absolute paths to relative.
751 for i in range(len(file_list)):
752 # TODO(phajdan.jr): We should know exactly when the paths are absolute.
753 # It depends on the command being executed (like runhooks vs sync).
754 if not os.path.isabs(file_list[i]):
755 continue
756
757 prefix = os.path.commonprefix([self._root_dir.lower(),
758 file_list[i].lower()])
759 file_list[i] = file_list[i][len(prefix):]
760
761 # Strip any leading path separators.
762 while file_list[i].startswith('\\') or file_list[i].startswith('/'):
763 file_list[i] = file_list[i][1:]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000764
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000765 is_using_git = gclient_utils.IsUsingGit(self._root_dir, entries.keys())
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000766 self._RunHooks(command, file_list, is_using_git)
767
768 if command == 'update':
ajwong@chromium.orgcdcee802009-06-23 15:30:42 +0000769 # Notify the user if there is an orphaned entry in their working copy.
770 # Only delete the directory if there are no changes in it, and
771 # delete_unversioned_trees is set to true.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000772 prev_entries = self._ReadEntries()
773 for entry in prev_entries:
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000774 # Fix path separator on Windows.
775 entry_fixed = entry.replace('/', os.path.sep)
776 e_dir = os.path.join(self._root_dir, entry_fixed)
777 # Use entry and not entry_fixed there.
maruel@chromium.org0329e672009-05-13 18:41:04 +0000778 if entry not in entries and os.path.exists(e_dir):
msb@chromium.org83017012009-09-28 18:52:12 +0000779 modified_files = False
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +0000780 if isinstance(prev_entries, list):
msb@chromium.org83017012009-09-28 18:52:12 +0000781 # old .gclient_entries format was list, now dict
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +0000782 modified_files = gclient_scm.scm.SVN.CaptureStatus(e_dir)
msb@chromium.org83017012009-09-28 18:52:12 +0000783 else:
784 file_list = []
785 scm = gclient_scm.CreateSCM(prev_entries[entry], self._root_dir,
786 entry_fixed)
787 scm.status(self._options, [], file_list)
788 modified_files = file_list != []
789 if not self._options.delete_unversioned_trees or modified_files:
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000790 # There are modified files in this entry. Keep warning until
791 # removed.
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000792 print(("\nWARNING: \"%s\" is no longer part of this client. "
793 "It is recommended that you manually remove it.\n") %
794 entry_fixed)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000795 else:
796 # Delete the entry
maruel@chromium.orgdf7a3132009-05-12 17:49:49 +0000797 print("\n________ deleting \'%s\' " +
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000798 "in \'%s\'") % (entry_fixed, self._root_dir)
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000799 gclient_utils.RemoveDirectory(e_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000800 # record the current list of entries for next time
801 self._SaveEntries(entries)
802
803 def PrintRevInfo(self):
nasser@codeaurora.org5d63eb82010-03-24 23:22:09 +0000804 """Output revision info mapping for the client and its dependencies.
805
806 This allows the capture of an overall "revision" for the source tree that
807 can be used to reproduce the same tree in the future. The actual output
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000808 contains enough information (source paths, svn server urls and revisions)
nasser@codeaurora.org5d63eb82010-03-24 23:22:09 +0000809 that it can be used either to generate external svn/git commands (without
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000810 gclient) or as input to gclient's --rev option (with some massaging of
811 the data).
812
nasser@codeaurora.org5d63eb82010-03-24 23:22:09 +0000813 Since we care about the revision of the current source tree, for git
814 repositories this command uses the revision of the HEAD. For subversion we
815 use BASE.
816
817 The --snapshot option allows creating a .gclient file to reproduce the tree.
818
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000819 Raises:
820 Error: If the client has conflicting entries.
821 """
822 # Check for revision overrides.
823 revision_overrides = {}
824 for revision in self._options.revisions:
825 if revision.find("@") < 0:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000826 raise gclient_utils.Error(
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000827 "Specify the full dependency when specifying a revision number.")
828 revision_elem = revision.split("@")
829 # Disallow conflicting revs
830 if revision_overrides.has_key(revision_elem[0]) and \
831 revision_overrides[revision_elem[0]] != revision_elem[1]:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000832 raise gclient_utils.Error(
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000833 "Conflicting revision numbers specified.")
834 revision_overrides[revision_elem[0]] = revision_elem[1]
835
836 solutions = self.GetVar("solutions")
837 if not solutions:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000838 raise gclient_utils.Error("No solution specified")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000839
nasser@codeaurora.org5d63eb82010-03-24 23:22:09 +0000840 # Inner helper to generate base url and rev tuple
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000841 def GetURLAndRev(name, original_url):
nasser@codeaurora.org5d63eb82010-03-24 23:22:09 +0000842 url, _ = gclient_utils.SplitUrlRevision(original_url)
843 scm = gclient_scm.CreateSCM(original_url, self._root_dir, name)
844 return (url, scm.revinfo(self._options, [], None))
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:
maruel@chromium.org1ab7ffc2009-06-03 17:21:37 +0000965 base_url = args[0].rstrip('/')
iposva@chromium.org8cf7a392010-04-07 17:20:26 +0000966 if not options.name:
967 name = base_url.split("/")[-1]
968 else:
969 # specify an alternate relpath for the given URL.
970 name = options.name
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000971 safesync_url = ""
972 if len(args) > 1:
973 safesync_url = args[1]
974 client.SetDefaultConfig(name, base_url, safesync_url)
975 client.SaveConfig()
976
977
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +0000978def DoExport(options, args):
979 """Handle the export subcommand.
phajdan.jr@chromium.org71b40682009-07-31 23:40:09 +0000980
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +0000981 Raises:
982 Error: on usage error
983 """
984 if len(args) != 1:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000985 raise gclient_utils.Error("Need directory name")
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +0000986 client = GClient.LoadCurrentConfig(options)
987
988 if not client:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000989 raise gclient_utils.Error("client not configured; see 'gclient config'")
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +0000990
991 if options.verbose:
992 # Print out the .gclient file. This is longer than if we just printed the
993 # client dict, but more legible, and it might contain helpful comments.
994 print(client.ConfigContent())
995 return client.RunOnDeps('export', args)
996
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000997def DoHelp(options, args):
998 """Handle the help subcommand giving help for another subcommand.
999
1000 Raises:
1001 Error: if the command is unknown.
1002 """
maruel@chromium.orge3608df2009-11-10 20:22:57 +00001003 __pychecker__ = 'unusednames=options'
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001004 if len(args) == 1 and args[0] in COMMAND_USAGE_TEXT:
maruel@chromium.orgdf7a3132009-05-12 17:49:49 +00001005 print(COMMAND_USAGE_TEXT[args[0]])
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001006 else:
maruel@chromium.orge3608df2009-11-10 20:22:57 +00001007 raise gclient_utils.Error("unknown subcommand '%s'; see 'gclient help'" %
1008 args[0])
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001009
1010
kbr@google.comab318592009-09-04 00:54:55 +00001011def DoPack(options, args):
1012 """Handle the pack subcommand.
1013
1014 Raises:
1015 Error: if client isn't configured properly.
1016 """
1017 client = GClient.LoadCurrentConfig(options)
1018 if not client:
maruel@chromium.orge3608df2009-11-10 20:22:57 +00001019 raise gclient_utils.Error("client not configured; see 'gclient config'")
kbr@google.comab318592009-09-04 00:54:55 +00001020 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.
1023 print(client.ConfigContent())
kbr@google.comab318592009-09-04 00:54:55 +00001024 return client.RunOnDeps('pack', args)
1025
1026
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001027def DoStatus(options, args):
1028 """Handle the status 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('status', args)
1041
1042
1043def DoUpdate(options, args):
1044 """Handle the update and sync subcommands.
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
1051 if not client:
maruel@chromium.orge3608df2009-11-10 20:22:57 +00001052 raise gclient_utils.Error("client not configured; see 'gclient config'")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001053
1054 if not options.head:
1055 solutions = client.GetVar('solutions')
1056 if solutions:
1057 for s in solutions:
1058 if s.get('safesync_url', ''):
1059 # rip through revisions and make sure we're not over-riding
1060 # something that was explicitly passed
1061 has_key = False
1062 for r in options.revisions:
1063 if r.split('@')[0] == s['name']:
1064 has_key = True
1065 break
1066
1067 if not has_key:
1068 handle = urllib.urlopen(s['safesync_url'])
1069 rev = handle.read().strip()
1070 handle.close()
1071 if len(rev):
1072 options.revisions.append(s['name']+'@'+rev)
1073
1074 if options.verbose:
1075 # Print out the .gclient file. This is longer than if we just printed the
1076 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.orgdf7a3132009-05-12 17:49:49 +00001077 print(client.ConfigContent())
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001078 return client.RunOnDeps('update', args)
1079
1080
1081def DoDiff(options, args):
1082 """Handle the diff subcommand.
1083
1084 Raises:
1085 Error: if client isn't configured properly.
1086 """
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001087 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001088 if not client:
maruel@chromium.orge3608df2009-11-10 20:22:57 +00001089 raise gclient_utils.Error("client not configured; see 'gclient config'")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001090 if options.verbose:
1091 # Print out the .gclient file. This is longer than if we just printed the
1092 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.orgdf7a3132009-05-12 17:49:49 +00001093 print(client.ConfigContent())
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001094 return client.RunOnDeps('diff', args)
1095
1096
1097def DoRevert(options, args):
1098 """Handle the revert subcommand.
1099
1100 Raises:
1101 Error: if client isn't configured properly.
1102 """
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001103 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001104 if not client:
maruel@chromium.orge3608df2009-11-10 20:22:57 +00001105 raise gclient_utils.Error("client not configured; see 'gclient config'")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001106 return client.RunOnDeps('revert', args)
1107
1108
1109def DoRunHooks(options, args):
1110 """Handle the runhooks subcommand.
1111
1112 Raises:
1113 Error: if client isn't configured properly.
1114 """
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001115 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001116 if not client:
maruel@chromium.orge3608df2009-11-10 20:22:57 +00001117 raise gclient_utils.Error("client not configured; see 'gclient config'")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001118 if options.verbose:
1119 # Print out the .gclient file. This is longer than if we just printed the
1120 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.orgdf7a3132009-05-12 17:49:49 +00001121 print(client.ConfigContent())
maruel@chromium.org5df6a462009-08-28 18:52:26 +00001122 options.force = True
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001123 return client.RunOnDeps('runhooks', args)
1124
1125
1126def DoRevInfo(options, args):
1127 """Handle the revinfo subcommand.
1128
1129 Raises:
1130 Error: if client isn't configured properly.
1131 """
maruel@chromium.orge3608df2009-11-10 20:22:57 +00001132 __pychecker__ = 'unusednames=args'
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001133 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001134 if not client:
maruel@chromium.orge3608df2009-11-10 20:22:57 +00001135 raise gclient_utils.Error("client not configured; see 'gclient config'")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001136 client.PrintRevInfo()
1137
1138
1139gclient_command_map = {
1140 "cleanup": DoCleanup,
1141 "config": DoConfig,
1142 "diff": DoDiff,
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +00001143 "export": DoExport,
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001144 "help": DoHelp,
kbr@google.comab318592009-09-04 00:54:55 +00001145 "pack": DoPack,
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001146 "status": DoStatus,
1147 "sync": DoUpdate,
1148 "update": DoUpdate,
1149 "revert": DoRevert,
1150 "runhooks": DoRunHooks,
1151 "revinfo" : DoRevInfo,
1152}
1153
1154
1155def DispatchCommand(command, options, args, command_map=None):
1156 """Dispatches the appropriate subcommand based on command line arguments."""
1157 if command_map is None:
1158 command_map = gclient_command_map
1159
1160 if command in command_map:
1161 return command_map[command](options, args)
1162 else:
maruel@chromium.orge3608df2009-11-10 20:22:57 +00001163 raise gclient_utils.Error("unknown subcommand '%s'; see 'gclient help'" %
1164 command)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001165
1166
1167def Main(argv):
1168 """Parse command line arguments and dispatch command."""
1169
1170 option_parser = optparse.OptionParser(usage=DEFAULT_USAGE_TEXT,
1171 version=__version__)
1172 option_parser.disable_interspersed_args()
1173 option_parser.add_option("", "--force", action="store_true", default=False,
1174 help=("(update/sync only) force update even "
1175 "for modules which haven't changed"))
evan@chromium.org67820ef2009-07-27 17:23:00 +00001176 option_parser.add_option("", "--nohooks", action="store_true", default=False,
1177 help=("(update/sync/revert only) prevent the hooks from "
1178 "running"))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001179 option_parser.add_option("", "--revision", action="append", dest="revisions",
1180 metavar="REV", default=[],
1181 help=("(update/sync only) sync to a specific "
1182 "revision, can be used multiple times for "
1183 "each solution, e.g. --revision=src@123, "
1184 "--revision=internal@32"))
1185 option_parser.add_option("", "--deps", default=None, dest="deps_os",
1186 metavar="OS_LIST",
1187 help=("(update/sync only) sync deps for the "
1188 "specified (comma-separated) platform(s); "
1189 "'all' will sync all platforms"))
davemoore@chromium.org793796d2010-02-19 17:27:41 +00001190 option_parser.add_option("", "--reset", action="store_true", default=False,
1191 help=("(update/sync only) resets any local changes "
1192 "before updating (git only)"))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001193 option_parser.add_option("", "--spec", default=None,
1194 help=("(config only) create a gclient file "
1195 "containing the provided string"))
maruel@chromium.orga6220d12010-01-06 21:04:17 +00001196 option_parser.add_option("-v", "--verbose", action="count", default=0,
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001197 help="produce additional output for diagnostics")
maruel@chromium.org7753d242009-10-07 17:40:24 +00001198 option_parser.add_option("", "--manually_grab_svn_rev", action="store_true",
1199 default=False,
1200 help="Skip svn up whenever possible by requesting "
1201 "actual HEAD revision from the repository")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001202 option_parser.add_option("", "--head", action="store_true", default=False,
1203 help=("skips any safesync_urls specified in "
1204 "configured solutions"))
ajwong@chromium.orgcdcee802009-06-23 15:30:42 +00001205 option_parser.add_option("", "--delete_unversioned_trees",
1206 action="store_true", default=False,
1207 help=("on update, delete any unexpected "
1208 "unversioned trees that are in the checkout"))
maruel@chromium.orge3da35f2010-03-09 21:40:45 +00001209 option_parser.add_option("", "--snapshot", action="store_true", default=False,
1210 help=("(revinfo only), create a snapshot file "
1211 "of the current version of all repositories"))
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001212 option_parser.add_option("", "--name",
1213 help="specify alternate relative solution path")
maruel@chromium.orge3da35f2010-03-09 21:40:45 +00001214 option_parser.add_option("", "--gclientfile", default=None,
1215 metavar="FILENAME",
1216 help=("specify an alternate .gclient file"))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001217
1218 if len(argv) < 2:
1219 # Users don't need to be told to use the 'help' command.
1220 option_parser.print_help()
1221 return 1
1222 # Add manual support for --version as first argument.
1223 if argv[1] == '--version':
1224 option_parser.print_version()
1225 return 0
1226
1227 # Add manual support for --help as first argument.
1228 if argv[1] == '--help':
1229 argv[1] = 'help'
1230
1231 command = argv[1]
1232 options, args = option_parser.parse_args(argv[2:])
1233
1234 if len(argv) < 3 and command == "help":
1235 option_parser.print_help()
1236 return 0
1237
maruel@chromium.orga6220d12010-01-06 21:04:17 +00001238 if options.verbose > 1:
maruel@chromium.org754960e2009-09-21 12:31:05 +00001239 logging.basicConfig(level=logging.DEBUG)
1240
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001241 # Files used for configuration and state saving.
1242 options.config_filename = os.environ.get("GCLIENT_FILE", ".gclient")
maruel@chromium.orge3da35f2010-03-09 21:40:45 +00001243 if options.gclientfile:
1244 options.config_filename = options.gclientfile
1245 options.entries_filename = options.config_filename + "_entries"
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001246 options.deps_file = "DEPS"
1247
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001248 options.platform = sys.platform
1249 return DispatchCommand(command, options, args)
1250
1251
1252if "__main__" == __name__:
1253 try:
1254 result = Main(sys.argv)
maruel@chromium.orge3608df2009-11-10 20:22:57 +00001255 except gclient_utils.Error, e:
maruel@chromium.orgdf7a3132009-05-12 17:49:49 +00001256 print >> sys.stderr, "Error: %s" % str(e)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001257 result = 1
1258 sys.exit(result)
1259
1260# vim: ts=2:sw=2:tw=80:et: