blob: 61d36d59d149eeac17b50bcc45bcd3371bdbec8d [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 """
maruel@chromium.orge41f6822010-04-08 16:37:06 +0000402 # Sometimes pprint.pformat will use {', sometimes it'll use { ' ... It
403 # makes testing a bit too fun.
404 result = pprint.pformat(entries, 2)
405 if result.startswith('{\''):
maruel@chromium.org1edec4d2010-04-08 17:17:06 +0000406 result = '{ \'' + result[2:]
maruel@chromium.orge41f6822010-04-08 16:37:06 +0000407 text = "entries = \\\n" + result + '\n'
msb@chromium.org2e38de72009-09-28 17:04:47 +0000408 file_path = os.path.join(self._root_dir, self._options.entries_filename)
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000409 gclient_utils.FileWrite(file_path, text)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000410
411 def _ReadEntries(self):
412 """Read the .gclient_entries file for the given client.
413
414 Args:
415 client: The client for which the entries file should be read.
416
417 Returns:
418 A sequence of solution names, which will be empty if there is the
419 entries file hasn't been created yet.
420 """
421 scope = {}
422 filename = os.path.join(self._root_dir, self._options.entries_filename)
maruel@chromium.org0329e672009-05-13 18:41:04 +0000423 if not os.path.exists(filename):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000424 return []
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000425 exec(gclient_utils.FileRead(filename), scope)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000426 return scope["entries"]
427
428 class FromImpl:
429 """Used to implement the From syntax."""
430
431 def __init__(self, module_name):
432 self.module_name = module_name
433
434 def __str__(self):
435 return 'From("%s")' % self.module_name
436
tony@chromium.org4b5b1772010-04-08 01:52:56 +0000437 class FileImpl:
438 """Used to implement the File('') syntax which lets you sync a single file
439 from an SVN repo."""
440
441 def __init__(self, file_location):
442 self.file_location = file_location
443
444 def __str__(self):
445 return 'File("%s")' % self.file_location
446
447 def GetPath(self):
448 return os.path.split(self.file_location)[0]
449
450 def GetFilename(self):
451 rev_tokens = self.file_location.split('@')
452 return os.path.split(rev_tokens[0])[1]
453
454 def GetRevision(self):
455 rev_tokens = self.file_location.split('@')
456 if len(rev_tokens) > 1:
457 return rev_tokens[1]
458 return None
459
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000460 class _VarImpl:
461 def __init__(self, custom_vars, local_scope):
462 self._custom_vars = custom_vars
463 self._local_scope = local_scope
464
465 def Lookup(self, var_name):
466 """Implements the Var syntax."""
467 if var_name in self._custom_vars:
468 return self._custom_vars[var_name]
469 elif var_name in self._local_scope.get("vars", {}):
470 return self._local_scope["vars"][var_name]
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000471 raise gclient_utils.Error("Var is not defined: %s" % var_name)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000472
473 def _ParseSolutionDeps(self, solution_name, solution_deps_content,
474 custom_vars):
475 """Parses the DEPS file for the specified solution.
476
477 Args:
478 solution_name: The name of the solution to query.
479 solution_deps_content: Content of the DEPS file for the solution
480 custom_vars: A dict of vars to override any vars defined in the DEPS file.
481
482 Returns:
483 A dict mapping module names (as relative paths) to URLs or an empty
484 dict if the solution does not have a DEPS file.
485 """
486 # Skip empty
487 if not solution_deps_content:
488 return {}
489 # Eval the content
490 local_scope = {}
491 var = self._VarImpl(custom_vars, local_scope)
tony@chromium.org4b5b1772010-04-08 01:52:56 +0000492 global_scope = {
493 "File": self.FileImpl,
494 "From": self.FromImpl,
495 "Var": var.Lookup,
496 "deps_os": {},
497 }
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000498 exec(solution_deps_content, global_scope, local_scope)
499 deps = local_scope.get("deps", {})
500
501 # load os specific dependencies if defined. these dependencies may
502 # override or extend the values defined by the 'deps' member.
503 if "deps_os" in local_scope:
504 deps_os_choices = {
505 "win32": "win",
506 "win": "win",
507 "cygwin": "win",
508 "darwin": "mac",
509 "mac": "mac",
510 "unix": "unix",
511 "linux": "unix",
512 "linux2": "unix",
513 }
514
515 if self._options.deps_os is not None:
516 deps_to_include = self._options.deps_os.split(",")
517 if "all" in deps_to_include:
maruel@chromium.org55e724e2010-03-11 19:36:49 +0000518 deps_to_include = list(set(deps_os_choices.itervalues()))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000519 else:
520 deps_to_include = [deps_os_choices.get(self._options.platform, "unix")]
521
522 deps_to_include = set(deps_to_include)
523 for deps_os_key in deps_to_include:
524 os_deps = local_scope["deps_os"].get(deps_os_key, {})
525 if len(deps_to_include) > 1:
526 # Ignore any overrides when including deps for more than one
527 # platform, so we collect the broadest set of dependencies available.
528 # We may end up with the wrong revision of something for our
529 # platform, but this is the best we can do.
530 deps.update([x for x in os_deps.items() if not x[0] in deps])
531 else:
532 deps.update(os_deps)
533
534 if 'hooks' in local_scope:
535 self._deps_hooks.extend(local_scope['hooks'])
536
537 # If use_relative_paths is set in the DEPS file, regenerate
538 # the dictionary using paths relative to the directory containing
539 # the DEPS file.
540 if local_scope.get('use_relative_paths'):
541 rel_deps = {}
542 for d, url in deps.items():
543 # normpath is required to allow DEPS to use .. in their
544 # dependency local path.
545 rel_deps[os.path.normpath(os.path.join(solution_name, d))] = url
546 return rel_deps
547 else:
548 return deps
549
550 def _ParseAllDeps(self, solution_urls, solution_deps_content):
551 """Parse the complete list of dependencies for the client.
552
553 Args:
554 solution_urls: A dict mapping module names (as relative paths) to URLs
555 corresponding to the solutions specified by the client. This parameter
556 is passed as an optimization.
557 solution_deps_content: A dict mapping module names to the content
558 of their DEPS files
559
560 Returns:
561 A dict mapping module names (as relative paths) to URLs corresponding
562 to the entire set of dependencies to checkout for the given client.
563
564 Raises:
565 Error: If a dependency conflicts with another dependency or of a solution.
566 """
567 deps = {}
568 for solution in self.GetVar("solutions"):
569 custom_vars = solution.get("custom_vars", {})
570 solution_deps = self._ParseSolutionDeps(
571 solution["name"],
572 solution_deps_content[solution["name"]],
573 custom_vars)
574
575 # If a line is in custom_deps, but not in the solution, we want to append
576 # this line to the solution.
577 if "custom_deps" in solution:
578 for d in solution["custom_deps"]:
579 if d not in solution_deps:
580 solution_deps[d] = solution["custom_deps"][d]
581
582 for d in solution_deps:
583 if "custom_deps" in solution and d in solution["custom_deps"]:
584 # Dependency is overriden.
585 url = solution["custom_deps"][d]
586 if url is None:
587 continue
588 else:
589 url = solution_deps[d]
590 # if we have a From reference dependent on another solution, then
591 # just skip the From reference. When we pull deps for the solution,
592 # we will take care of this dependency.
593 #
594 # If multiple solutions all have the same From reference, then we
595 # should only add one to our list of dependencies.
tony@chromium.org4b5b1772010-04-08 01:52:56 +0000596 if isinstance(url, self.FromImpl):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000597 if url.module_name in solution_urls:
598 # Already parsed.
599 continue
600 if d in deps and type(deps[d]) != str:
601 if url.module_name == deps[d].module_name:
602 continue
tony@chromium.org4b5b1772010-04-08 01:52:56 +0000603 elif isinstance(url, str):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000604 parsed_url = urlparse.urlparse(url)
605 scheme = parsed_url[0]
606 if not scheme:
607 # A relative url. Fetch the real base.
608 path = parsed_url[2]
609 if path[0] != "/":
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000610 raise gclient_utils.Error(
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000611 "relative DEPS entry \"%s\" must begin with a slash" % d)
msb@chromium.orge6f78352010-01-13 17:05:33 +0000612 # Create a scm just to query the full url.
613 scm = gclient_scm.CreateSCM(solution["url"], self._root_dir,
614 None)
615 url = scm.FullUrlForRelativeUrl(url)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000616 if d in deps and deps[d] != url:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000617 raise gclient_utils.Error(
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000618 "Solutions have conflicting versions of dependency \"%s\"" % d)
619 if d in solution_urls and solution_urls[d] != url:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000620 raise gclient_utils.Error(
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000621 "Dependency \"%s\" conflicts with specified solution" % d)
622 # Grab the dependency.
623 deps[d] = url
624 return deps
625
phajdan.jr@chromium.org71b40682009-07-31 23:40:09 +0000626 def _RunHookAction(self, hook_dict, matching_file_list):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000627 """Runs the action from a single hook.
628 """
629 command = hook_dict['action'][:]
630 if command[0] == 'python':
631 # If the hook specified "python" as the first item, the action is a
632 # Python script. Run it by starting a new copy of the same
633 # interpreter.
634 command[0] = sys.executable
635
phajdan.jr@chromium.org71b40682009-07-31 23:40:09 +0000636 if '$matching_files' in command:
phajdan.jr@chromium.org68f2e092009-08-06 17:05:35 +0000637 splice_index = command.index('$matching_files')
638 command[splice_index:splice_index + 1] = matching_file_list
phajdan.jr@chromium.org71b40682009-07-31 23:40:09 +0000639
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000640 # Use a discrete exit status code of 2 to indicate that a hook action
641 # failed. Users of this script may wish to treat hook action failures
642 # differently from VC failures.
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000643 gclient_utils.SubprocessCall(command, self._root_dir, fail_status=2)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000644
645 def _RunHooks(self, command, file_list, is_using_git):
646 """Evaluates all hooks, running actions as needed.
647 """
648 # Hooks only run for these command types.
649 if not command in ('update', 'revert', 'runhooks'):
650 return
651
evan@chromium.org67820ef2009-07-27 17:23:00 +0000652 # Hooks only run when --nohooks is not specified
653 if self._options.nohooks:
654 return
655
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000656 # Get any hooks from the .gclient file.
657 hooks = self.GetVar("hooks", [])
658 # Add any hooks found in DEPS files.
659 hooks.extend(self._deps_hooks)
660
661 # If "--force" was specified, run all hooks regardless of what files have
662 # changed. If the user is using git, then we don't know what files have
663 # changed so we always run all hooks.
664 if self._options.force or is_using_git:
665 for hook_dict in hooks:
phajdan.jr@chromium.org71b40682009-07-31 23:40:09 +0000666 self._RunHookAction(hook_dict, [])
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000667 return
668
669 # Run hooks on the basis of whether the files from the gclient operation
670 # match each hook's pattern.
671 for hook_dict in hooks:
672 pattern = re.compile(hook_dict['pattern'])
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000673 matching_file_list = [f for f in file_list if pattern.search(f)]
phajdan.jr@chromium.org71b40682009-07-31 23:40:09 +0000674 if matching_file_list:
675 self._RunHookAction(hook_dict, matching_file_list)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000676
677 def RunOnDeps(self, command, args):
678 """Runs a command on each dependency in a client and its dependencies.
679
680 The module's dependencies are specified in its top-level DEPS files.
681
682 Args:
683 command: The command to use (e.g., 'status' or 'diff')
684 args: list of str - extra arguments to add to the command line.
685
686 Raises:
687 Error: If the client has conflicting entries.
688 """
689 if not command in self.supported_commands:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000690 raise gclient_utils.Error("'%s' is an unsupported command" % command)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000691
692 # Check for revision overrides.
693 revision_overrides = {}
694 for revision in self._options.revisions:
695 if revision.find("@") == -1:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000696 raise gclient_utils.Error(
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000697 "Specify the full dependency when specifying a revision number.")
698 revision_elem = revision.split("@")
699 # Disallow conflicting revs
700 if revision_overrides.has_key(revision_elem[0]) and \
701 revision_overrides[revision_elem[0]] != revision_elem[1]:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000702 raise gclient_utils.Error(
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000703 "Conflicting revision numbers specified.")
704 revision_overrides[revision_elem[0]] = revision_elem[1]
705
706 solutions = self.GetVar("solutions")
707 if not solutions:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000708 raise gclient_utils.Error("No solution specified")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000709
710 # When running runhooks --force, there's no need to consult the SCM.
711 # All known hooks are expected to run unconditionally regardless of working
712 # copy state, so skip the SCM status check.
713 run_scm = not (command == 'runhooks' and self._options.force)
714
715 entries = {}
716 entries_deps_content = {}
717 file_list = []
718 # Run on the base solutions first.
719 for solution in solutions:
720 name = solution["name"]
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000721 deps_file = solution.get("deps_file", self._options.deps_file)
722 if '/' in deps_file or '\\' in deps_file:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000723 raise gclient_utils.Error('deps_file name must not be a path, just a '
724 'filename.')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000725 if name in entries:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000726 raise gclient_utils.Error("solution %s specified more than once" % name)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000727 url = solution["url"]
728 entries[name] = url
yaar@chromium.orgf1328042009-09-22 23:14:23 +0000729 if run_scm and url:
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000730 self._options.revision = revision_overrides.get(name)
msb@chromium.orgcb5442b2009-09-22 16:51:24 +0000731 scm = gclient_scm.CreateSCM(url, self._root_dir, name)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000732 scm.RunCommand(command, self._options, args, file_list)
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000733 file_list = [os.path.join(name, f.strip()) for f in file_list]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000734 self._options.revision = None
735 try:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000736 deps_content = gclient_utils.FileRead(
737 os.path.join(self._root_dir, name, deps_file))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000738 except IOError, e:
739 if e.errno != errno.ENOENT:
740 raise
741 deps_content = ""
742 entries_deps_content[name] = deps_content
743
744 # Process the dependencies next (sort alphanumerically to ensure that
745 # containing directories get populated first and for readability)
746 deps = self._ParseAllDeps(entries, entries_deps_content)
747 deps_to_process = deps.keys()
748 deps_to_process.sort()
749
750 # First pass for direct dependencies.
nasser@codeaurora.org1f7a3d12010-02-04 15:11:50 +0000751 if command == 'update' and not self._options.verbose:
752 pm = Progress('Syncing projects', len(deps_to_process))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000753 for d in deps_to_process:
nasser@codeaurora.org1f7a3d12010-02-04 15:11:50 +0000754 if command == 'update' and not self._options.verbose:
755 pm.update()
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000756 if type(deps[d]) == str:
757 url = deps[d]
758 entries[d] = url
759 if run_scm:
760 self._options.revision = revision_overrides.get(d)
msb@chromium.orgcb5442b2009-09-22 16:51:24 +0000761 scm = gclient_scm.CreateSCM(url, self._root_dir, d)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000762 scm.RunCommand(command, self._options, args, file_list)
763 self._options.revision = None
tony@chromium.org4b5b1772010-04-08 01:52:56 +0000764 elif isinstance(deps[d], self.FileImpl):
765 file = deps[d]
766 self._options.revision = file.GetRevision()
767 if run_scm:
768 scm = gclient_scm.CreateSCM(file.GetPath(), self._root_dir, d)
769 scm.RunCommand("updatesingle", self._options,
770 args + [file.GetFilename()], file_list)
771
nasser@codeaurora.org1f7a3d12010-02-04 15:11:50 +0000772 if command == 'update' and not self._options.verbose:
773 pm.end()
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000774
775 # Second pass for inherited deps (via the From keyword)
776 for d in deps_to_process:
tony@chromium.org4b5b1772010-04-08 01:52:56 +0000777 if isinstance(deps[d], self.FromImpl):
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000778 filename = os.path.join(self._root_dir,
779 deps[d].module_name,
780 self._options.deps_file)
781 content = gclient_utils.FileRead(filename)
782 sub_deps = self._ParseSolutionDeps(deps[d].module_name, content, {})
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000783 url = sub_deps[d]
784 entries[d] = url
785 if run_scm:
786 self._options.revision = revision_overrides.get(d)
msb@chromium.orgcb5442b2009-09-22 16:51:24 +0000787 scm = gclient_scm.CreateSCM(url, self._root_dir, d)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000788 scm.RunCommand(command, self._options, args, file_list)
789 self._options.revision = None
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000790
phajdan.jr@chromium.orgd83b2b22009-08-11 15:30:55 +0000791 # Convert all absolute paths to relative.
792 for i in range(len(file_list)):
793 # TODO(phajdan.jr): We should know exactly when the paths are absolute.
794 # It depends on the command being executed (like runhooks vs sync).
795 if not os.path.isabs(file_list[i]):
796 continue
797
798 prefix = os.path.commonprefix([self._root_dir.lower(),
799 file_list[i].lower()])
800 file_list[i] = file_list[i][len(prefix):]
801
802 # Strip any leading path separators.
803 while file_list[i].startswith('\\') or file_list[i].startswith('/'):
804 file_list[i] = file_list[i][1:]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000805
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000806 is_using_git = gclient_utils.IsUsingGit(self._root_dir, entries.keys())
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000807 self._RunHooks(command, file_list, is_using_git)
808
809 if command == 'update':
ajwong@chromium.orgcdcee802009-06-23 15:30:42 +0000810 # Notify the user if there is an orphaned entry in their working copy.
811 # Only delete the directory if there are no changes in it, and
812 # delete_unversioned_trees is set to true.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000813 prev_entries = self._ReadEntries()
814 for entry in prev_entries:
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000815 # Fix path separator on Windows.
816 entry_fixed = entry.replace('/', os.path.sep)
817 e_dir = os.path.join(self._root_dir, entry_fixed)
818 # Use entry and not entry_fixed there.
maruel@chromium.org0329e672009-05-13 18:41:04 +0000819 if entry not in entries and os.path.exists(e_dir):
msb@chromium.org83017012009-09-28 18:52:12 +0000820 modified_files = False
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +0000821 if isinstance(prev_entries, list):
msb@chromium.org83017012009-09-28 18:52:12 +0000822 # old .gclient_entries format was list, now dict
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +0000823 modified_files = gclient_scm.scm.SVN.CaptureStatus(e_dir)
msb@chromium.org83017012009-09-28 18:52:12 +0000824 else:
825 file_list = []
826 scm = gclient_scm.CreateSCM(prev_entries[entry], self._root_dir,
827 entry_fixed)
828 scm.status(self._options, [], file_list)
829 modified_files = file_list != []
830 if not self._options.delete_unversioned_trees or modified_files:
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000831 # There are modified files in this entry. Keep warning until
832 # removed.
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000833 print(("\nWARNING: \"%s\" is no longer part of this client. "
834 "It is recommended that you manually remove it.\n") %
835 entry_fixed)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000836 else:
837 # Delete the entry
maruel@chromium.orgdf7a3132009-05-12 17:49:49 +0000838 print("\n________ deleting \'%s\' " +
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000839 "in \'%s\'") % (entry_fixed, self._root_dir)
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000840 gclient_utils.RemoveDirectory(e_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000841 # record the current list of entries for next time
842 self._SaveEntries(entries)
843
844 def PrintRevInfo(self):
nasser@codeaurora.org5d63eb82010-03-24 23:22:09 +0000845 """Output revision info mapping for the client and its dependencies.
846
847 This allows the capture of an overall "revision" for the source tree that
848 can be used to reproduce the same tree in the future. The actual output
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000849 contains enough information (source paths, svn server urls and revisions)
nasser@codeaurora.org5d63eb82010-03-24 23:22:09 +0000850 that it can be used either to generate external svn/git commands (without
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000851 gclient) or as input to gclient's --rev option (with some massaging of
852 the data).
853
nasser@codeaurora.org5d63eb82010-03-24 23:22:09 +0000854 Since we care about the revision of the current source tree, for git
855 repositories this command uses the revision of the HEAD. For subversion we
856 use BASE.
857
858 The --snapshot option allows creating a .gclient file to reproduce the tree.
859
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000860 Raises:
861 Error: If the client has conflicting entries.
862 """
863 # Check for revision overrides.
864 revision_overrides = {}
865 for revision in self._options.revisions:
866 if revision.find("@") < 0:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000867 raise gclient_utils.Error(
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000868 "Specify the full dependency when specifying a revision number.")
869 revision_elem = revision.split("@")
870 # Disallow conflicting revs
871 if revision_overrides.has_key(revision_elem[0]) and \
872 revision_overrides[revision_elem[0]] != revision_elem[1]:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000873 raise gclient_utils.Error(
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000874 "Conflicting revision numbers specified.")
875 revision_overrides[revision_elem[0]] = revision_elem[1]
876
877 solutions = self.GetVar("solutions")
878 if not solutions:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000879 raise gclient_utils.Error("No solution specified")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000880
nasser@codeaurora.org5d63eb82010-03-24 23:22:09 +0000881 # Inner helper to generate base url and rev tuple
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000882 def GetURLAndRev(name, original_url):
nasser@codeaurora.org5d63eb82010-03-24 23:22:09 +0000883 url, _ = gclient_utils.SplitUrlRevision(original_url)
884 scm = gclient_scm.CreateSCM(original_url, self._root_dir, name)
885 return (url, scm.revinfo(self._options, [], None))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000886
maruel@chromium.orge3da35f2010-03-09 21:40:45 +0000887 # text of the snapshot gclient file
888 new_gclient = ""
889 # Dictionary of { path : SCM url } to ensure no duplicate solutions
890 solution_names = {}
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +0000891 entries = {}
892 entries_deps_content = {}
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000893 # Run on the base solutions first.
894 for solution in solutions:
maruel@chromium.orge3da35f2010-03-09 21:40:45 +0000895 # Dictionary of { path : SCM url } to describe the gclient checkout
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000896 name = solution["name"]
maruel@chromium.orge3da35f2010-03-09 21:40:45 +0000897 if name in solution_names:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000898 raise gclient_utils.Error("solution %s specified more than once" % name)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000899 (url, rev) = GetURLAndRev(name, solution["url"])
msb@chromium.org770ff9e2009-09-23 17:18:18 +0000900 entries[name] = "%s@%s" % (url, rev)
maruel@chromium.orge3da35f2010-03-09 21:40:45 +0000901 solution_names[name] = "%s@%s" % (url, rev)
nasser@codeaurora.org952d7c72010-03-01 20:41:01 +0000902 deps_file = solution.get("deps_file", self._options.deps_file)
903 if '/' in deps_file or '\\' in deps_file:
904 raise gclient_utils.Error('deps_file name must not be a path, just a '
905 'filename.')
906 try:
907 deps_content = gclient_utils.FileRead(
908 os.path.join(self._root_dir, name, deps_file))
909 except IOError, e:
910 if e.errno != errno.ENOENT:
911 raise
912 deps_content = ""
913 entries_deps_content[name] = deps_content
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000914
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +0000915 # Process the dependencies next (sort alphanumerically to ensure that
916 # containing directories get populated first and for readability)
917 deps = self._ParseAllDeps(entries, entries_deps_content)
918 deps_to_process = deps.keys()
919 deps_to_process.sort()
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000920
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +0000921 # First pass for direct dependencies.
922 for d in deps_to_process:
923 if type(deps[d]) == str:
924 (url, rev) = GetURLAndRev(d, deps[d])
925 entries[d] = "%s@%s" % (url, rev)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000926
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +0000927 # Second pass for inherited deps (via the From keyword)
928 for d in deps_to_process:
tony@chromium.org4b5b1772010-04-08 01:52:56 +0000929 if isinstance(deps[d], self.FromImpl):
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +0000930 deps_parent_url = entries[deps[d].module_name]
931 if deps_parent_url.find("@") < 0:
932 raise gclient_utils.Error("From %s missing revisioned url" %
933 deps[d].module_name)
934 content = gclient_utils.FileRead(os.path.join(
935 self._root_dir,
936 deps[d].module_name,
937 self._options.deps_file))
938 sub_deps = self._ParseSolutionDeps(deps[d].module_name, content, {})
939 (url, rev) = GetURLAndRev(d, sub_deps[d])
940 entries[d] = "%s@%s" % (url, rev)
maruel@chromium.orge3da35f2010-03-09 21:40:45 +0000941
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +0000942 # Build the snapshot configuration string
943 if self._options.snapshot:
944 url = entries.pop(name)
945 custom_deps = ",\n ".join(["\"%s\": \"%s\"" % (x, entries[x])
946 for x in sorted(entries.keys())])
maruel@chromium.orge3da35f2010-03-09 21:40:45 +0000947
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +0000948 new_gclient += DEFAULT_SNAPSHOT_SOLUTION_TEXT % {
949 'solution_name': name,
950 'solution_url': url,
951 'safesync_url' : "",
952 'solution_deps': custom_deps,
953 }
954 else:
955 print(";\n".join(["%s: %s" % (x, entries[x])
956 for x in sorted(entries.keys())]))
maruel@chromium.orge3da35f2010-03-09 21:40:45 +0000957
958 # Print the snapshot configuration file
959 if self._options.snapshot:
960 config = DEFAULT_SNAPSHOT_FILE_TEXT % {'solution_list': new_gclient}
961 snapclient = GClient(self._root_dir, self._options)
962 snapclient.SetConfig(config)
963 print(snapclient._config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000964
965
966## gclient commands.
967
968
969def DoCleanup(options, args):
970 """Handle the cleanup subcommand.
971
972 Raises:
973 Error: if client isn't configured properly.
974 """
maruel@chromium.org2806acc2009-05-15 12:33:34 +0000975 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000976 if not client:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000977 raise gclient_utils.Error("client not configured; see 'gclient config'")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000978 if options.verbose:
979 # Print out the .gclient file. This is longer than if we just printed the
980 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.orgdf7a3132009-05-12 17:49:49 +0000981 print(client.ConfigContent())
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000982 return client.RunOnDeps('cleanup', args)
983
984
985def DoConfig(options, args):
986 """Handle the config subcommand.
987
988 Args:
989 options: If options.spec set, a string providing contents of config file.
990 args: The command line args. If spec is not set,
991 then args[0] is a string URL to get for config file.
992
993 Raises:
994 Error: on usage error
995 """
996 if len(args) < 1 and not options.spec:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000997 raise gclient_utils.Error("required argument missing; see 'gclient help "
998 "config'")
maruel@chromium.org0329e672009-05-13 18:41:04 +0000999 if os.path.exists(options.config_filename):
maruel@chromium.orge3608df2009-11-10 20:22:57 +00001000 raise gclient_utils.Error("%s file already exists in the current directory"
1001 % options.config_filename)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001002 client = GClient('.', options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001003 if options.spec:
1004 client.SetConfig(options.spec)
1005 else:
maruel@chromium.org1ab7ffc2009-06-03 17:21:37 +00001006 base_url = args[0].rstrip('/')
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001007 if not options.name:
1008 name = base_url.split("/")[-1]
1009 else:
1010 # specify an alternate relpath for the given URL.
1011 name = options.name
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001012 safesync_url = ""
1013 if len(args) > 1:
1014 safesync_url = args[1]
1015 client.SetDefaultConfig(name, base_url, safesync_url)
1016 client.SaveConfig()
1017
1018
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +00001019def DoExport(options, args):
1020 """Handle the export subcommand.
phajdan.jr@chromium.org71b40682009-07-31 23:40:09 +00001021
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +00001022 Raises:
1023 Error: on usage error
1024 """
1025 if len(args) != 1:
maruel@chromium.orge3608df2009-11-10 20:22:57 +00001026 raise gclient_utils.Error("Need directory name")
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +00001027 client = GClient.LoadCurrentConfig(options)
1028
1029 if not client:
maruel@chromium.orge3608df2009-11-10 20:22:57 +00001030 raise gclient_utils.Error("client not configured; see 'gclient config'")
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +00001031
1032 if options.verbose:
1033 # Print out the .gclient file. This is longer than if we just printed the
1034 # client dict, but more legible, and it might contain helpful comments.
1035 print(client.ConfigContent())
1036 return client.RunOnDeps('export', args)
1037
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001038def DoHelp(options, args):
1039 """Handle the help subcommand giving help for another subcommand.
1040
1041 Raises:
1042 Error: if the command is unknown.
1043 """
maruel@chromium.orge3608df2009-11-10 20:22:57 +00001044 __pychecker__ = 'unusednames=options'
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001045 if len(args) == 1 and args[0] in COMMAND_USAGE_TEXT:
maruel@chromium.orgdf7a3132009-05-12 17:49:49 +00001046 print(COMMAND_USAGE_TEXT[args[0]])
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001047 else:
maruel@chromium.orge3608df2009-11-10 20:22:57 +00001048 raise gclient_utils.Error("unknown subcommand '%s'; see 'gclient help'" %
1049 args[0])
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001050
1051
kbr@google.comab318592009-09-04 00:54:55 +00001052def DoPack(options, args):
1053 """Handle the pack subcommand.
1054
1055 Raises:
1056 Error: if client isn't configured properly.
1057 """
1058 client = GClient.LoadCurrentConfig(options)
1059 if not client:
maruel@chromium.orge3608df2009-11-10 20:22:57 +00001060 raise gclient_utils.Error("client not configured; see 'gclient config'")
kbr@google.comab318592009-09-04 00:54:55 +00001061 if options.verbose:
1062 # Print out the .gclient file. This is longer than if we just printed the
1063 # client dict, but more legible, and it might contain helpful comments.
1064 print(client.ConfigContent())
kbr@google.comab318592009-09-04 00:54:55 +00001065 return client.RunOnDeps('pack', args)
1066
1067
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001068def DoStatus(options, args):
1069 """Handle the status subcommand.
1070
1071 Raises:
1072 Error: if client isn't configured properly.
1073 """
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001074 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001075 if not client:
maruel@chromium.orge3608df2009-11-10 20:22:57 +00001076 raise gclient_utils.Error("client not configured; see 'gclient config'")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001077 if options.verbose:
1078 # Print out the .gclient file. This is longer than if we just printed the
1079 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.orgdf7a3132009-05-12 17:49:49 +00001080 print(client.ConfigContent())
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001081 return client.RunOnDeps('status', args)
1082
1083
1084def DoUpdate(options, args):
1085 """Handle the update and sync subcommands.
1086
1087 Raises:
1088 Error: if client isn't configured properly.
1089 """
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001090 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001091
1092 if not client:
maruel@chromium.orge3608df2009-11-10 20:22:57 +00001093 raise gclient_utils.Error("client not configured; see 'gclient config'")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001094
1095 if not options.head:
1096 solutions = client.GetVar('solutions')
1097 if solutions:
1098 for s in solutions:
1099 if s.get('safesync_url', ''):
1100 # rip through revisions and make sure we're not over-riding
1101 # something that was explicitly passed
1102 has_key = False
1103 for r in options.revisions:
1104 if r.split('@')[0] == s['name']:
1105 has_key = True
1106 break
1107
1108 if not has_key:
1109 handle = urllib.urlopen(s['safesync_url'])
1110 rev = handle.read().strip()
1111 handle.close()
1112 if len(rev):
1113 options.revisions.append(s['name']+'@'+rev)
1114
1115 if options.verbose:
1116 # Print out the .gclient file. This is longer than if we just printed the
1117 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.orgdf7a3132009-05-12 17:49:49 +00001118 print(client.ConfigContent())
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001119 return client.RunOnDeps('update', args)
1120
1121
1122def DoDiff(options, args):
1123 """Handle the diff subcommand.
1124
1125 Raises:
1126 Error: if client isn't configured properly.
1127 """
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001128 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001129 if not client:
maruel@chromium.orge3608df2009-11-10 20:22:57 +00001130 raise gclient_utils.Error("client not configured; see 'gclient config'")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001131 if options.verbose:
1132 # Print out the .gclient file. This is longer than if we just printed the
1133 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.orgdf7a3132009-05-12 17:49:49 +00001134 print(client.ConfigContent())
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001135 return client.RunOnDeps('diff', args)
1136
1137
1138def DoRevert(options, args):
1139 """Handle the revert subcommand.
1140
1141 Raises:
1142 Error: if client isn't configured properly.
1143 """
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001144 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001145 if not client:
maruel@chromium.orge3608df2009-11-10 20:22:57 +00001146 raise gclient_utils.Error("client not configured; see 'gclient config'")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001147 return client.RunOnDeps('revert', args)
1148
1149
1150def DoRunHooks(options, args):
1151 """Handle the runhooks subcommand.
1152
1153 Raises:
1154 Error: if client isn't configured properly.
1155 """
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001156 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001157 if not client:
maruel@chromium.orge3608df2009-11-10 20:22:57 +00001158 raise gclient_utils.Error("client not configured; see 'gclient config'")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001159 if options.verbose:
1160 # Print out the .gclient file. This is longer than if we just printed the
1161 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.orgdf7a3132009-05-12 17:49:49 +00001162 print(client.ConfigContent())
maruel@chromium.org5df6a462009-08-28 18:52:26 +00001163 options.force = True
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001164 return client.RunOnDeps('runhooks', args)
1165
1166
1167def DoRevInfo(options, args):
1168 """Handle the revinfo subcommand.
1169
1170 Raises:
1171 Error: if client isn't configured properly.
1172 """
maruel@chromium.orge3608df2009-11-10 20:22:57 +00001173 __pychecker__ = 'unusednames=args'
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001174 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001175 if not client:
maruel@chromium.orge3608df2009-11-10 20:22:57 +00001176 raise gclient_utils.Error("client not configured; see 'gclient config'")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001177 client.PrintRevInfo()
1178
1179
1180gclient_command_map = {
1181 "cleanup": DoCleanup,
1182 "config": DoConfig,
1183 "diff": DoDiff,
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +00001184 "export": DoExport,
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001185 "help": DoHelp,
kbr@google.comab318592009-09-04 00:54:55 +00001186 "pack": DoPack,
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001187 "status": DoStatus,
1188 "sync": DoUpdate,
1189 "update": DoUpdate,
1190 "revert": DoRevert,
1191 "runhooks": DoRunHooks,
1192 "revinfo" : DoRevInfo,
1193}
1194
1195
1196def DispatchCommand(command, options, args, command_map=None):
1197 """Dispatches the appropriate subcommand based on command line arguments."""
1198 if command_map is None:
1199 command_map = gclient_command_map
1200
1201 if command in command_map:
1202 return command_map[command](options, args)
1203 else:
maruel@chromium.orge3608df2009-11-10 20:22:57 +00001204 raise gclient_utils.Error("unknown subcommand '%s'; see 'gclient help'" %
1205 command)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001206
1207
1208def Main(argv):
1209 """Parse command line arguments and dispatch command."""
1210
1211 option_parser = optparse.OptionParser(usage=DEFAULT_USAGE_TEXT,
1212 version=__version__)
1213 option_parser.disable_interspersed_args()
1214 option_parser.add_option("", "--force", action="store_true", default=False,
1215 help=("(update/sync only) force update even "
1216 "for modules which haven't changed"))
evan@chromium.org67820ef2009-07-27 17:23:00 +00001217 option_parser.add_option("", "--nohooks", action="store_true", default=False,
1218 help=("(update/sync/revert only) prevent the hooks from "
1219 "running"))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001220 option_parser.add_option("", "--revision", action="append", dest="revisions",
1221 metavar="REV", default=[],
1222 help=("(update/sync only) sync to a specific "
1223 "revision, can be used multiple times for "
1224 "each solution, e.g. --revision=src@123, "
1225 "--revision=internal@32"))
1226 option_parser.add_option("", "--deps", default=None, dest="deps_os",
1227 metavar="OS_LIST",
1228 help=("(update/sync only) sync deps for the "
1229 "specified (comma-separated) platform(s); "
1230 "'all' will sync all platforms"))
davemoore@chromium.org793796d2010-02-19 17:27:41 +00001231 option_parser.add_option("", "--reset", action="store_true", default=False,
1232 help=("(update/sync only) resets any local changes "
1233 "before updating (git only)"))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001234 option_parser.add_option("", "--spec", default=None,
1235 help=("(config only) create a gclient file "
1236 "containing the provided string"))
maruel@chromium.orga6220d12010-01-06 21:04:17 +00001237 option_parser.add_option("-v", "--verbose", action="count", default=0,
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001238 help="produce additional output for diagnostics")
maruel@chromium.org7753d242009-10-07 17:40:24 +00001239 option_parser.add_option("", "--manually_grab_svn_rev", action="store_true",
1240 default=False,
1241 help="Skip svn up whenever possible by requesting "
1242 "actual HEAD revision from the repository")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001243 option_parser.add_option("", "--head", action="store_true", default=False,
1244 help=("skips any safesync_urls specified in "
1245 "configured solutions"))
ajwong@chromium.orgcdcee802009-06-23 15:30:42 +00001246 option_parser.add_option("", "--delete_unversioned_trees",
1247 action="store_true", default=False,
1248 help=("on update, delete any unexpected "
1249 "unversioned trees that are in the checkout"))
maruel@chromium.orge3da35f2010-03-09 21:40:45 +00001250 option_parser.add_option("", "--snapshot", action="store_true", default=False,
1251 help=("(revinfo only), create a snapshot file "
1252 "of the current version of all repositories"))
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001253 option_parser.add_option("", "--name",
1254 help="specify alternate relative solution path")
maruel@chromium.orge3da35f2010-03-09 21:40:45 +00001255 option_parser.add_option("", "--gclientfile", default=None,
1256 metavar="FILENAME",
1257 help=("specify an alternate .gclient file"))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001258
1259 if len(argv) < 2:
1260 # Users don't need to be told to use the 'help' command.
1261 option_parser.print_help()
1262 return 1
1263 # Add manual support for --version as first argument.
1264 if argv[1] == '--version':
1265 option_parser.print_version()
1266 return 0
1267
1268 # Add manual support for --help as first argument.
1269 if argv[1] == '--help':
1270 argv[1] = 'help'
1271
1272 command = argv[1]
1273 options, args = option_parser.parse_args(argv[2:])
1274
1275 if len(argv) < 3 and command == "help":
1276 option_parser.print_help()
1277 return 0
1278
maruel@chromium.orga6220d12010-01-06 21:04:17 +00001279 if options.verbose > 1:
maruel@chromium.org754960e2009-09-21 12:31:05 +00001280 logging.basicConfig(level=logging.DEBUG)
1281
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001282 # Files used for configuration and state saving.
1283 options.config_filename = os.environ.get("GCLIENT_FILE", ".gclient")
maruel@chromium.orge3da35f2010-03-09 21:40:45 +00001284 if options.gclientfile:
1285 options.config_filename = options.gclientfile
1286 options.entries_filename = options.config_filename + "_entries"
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001287 options.deps_file = "DEPS"
1288
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001289 options.platform = sys.platform
1290 return DispatchCommand(command, options, args)
1291
1292
1293if "__main__" == __name__:
1294 try:
1295 result = Main(sys.argv)
maruel@chromium.orge3608df2009-11-10 20:22:57 +00001296 except gclient_utils.Error, e:
maruel@chromium.orgdf7a3132009-05-12 17:49:49 +00001297 print >> sys.stderr, "Error: %s" % str(e)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001298 result = 1
1299 sys.exit(result)
1300
1301# vim: ts=2:sw=2:tw=80:et: