blob: 48a940fa31ac020c3102517398c8a3aee49a8912 [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
tony@chromium.org30ef9ae2010-04-09 02:18:05 +0000431 def __init__(self, module_name, sub_target_name=None):
432 """module_name is the dep module we want to include from. It can also be
433 the name of a subdirectory to include from.
434
435 sub_target_name is an optional parameter if the module name in the other
436 DEPS file is different. E.g., you might want to map src/net to net."""
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000437 self.module_name = module_name
tony@chromium.org30ef9ae2010-04-09 02:18:05 +0000438 self.sub_target_name = sub_target_name
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000439
440 def __str__(self):
tony@chromium.org30ef9ae2010-04-09 02:18:05 +0000441 return 'From(%s, %s)' % (repr(self.module_name),
442 repr(self.sub_target_name))
443
444 def GetUrl(self, target_name, sub_deps_base_url, root_dir, sub_deps):
445 """Resolve the URL for this From entry."""
446 sub_deps_target_name = target_name
447 if self.sub_target_name:
448 sub_deps_target_name = self.sub_target_name
449 url = sub_deps[sub_deps_target_name]
450 if url.startswith('/'):
451 # If it's a relative URL, we need to resolve the URL relative to the
452 # sub deps base URL.
453 if not isinstance(sub_deps_base_url, basestring):
454 sub_deps_base_url = sub_deps_base_url.GetPath()
455 scm = gclient_scm.CreateSCM(sub_deps_base_url, root_dir,
456 None)
457 url = scm.FullUrlForRelativeUrl(url)
458 return url
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000459
tony@chromium.org4b5b1772010-04-08 01:52:56 +0000460 class FileImpl:
461 """Used to implement the File('') syntax which lets you sync a single file
462 from an SVN repo."""
463
464 def __init__(self, file_location):
465 self.file_location = file_location
466
467 def __str__(self):
468 return 'File("%s")' % self.file_location
469
470 def GetPath(self):
471 return os.path.split(self.file_location)[0]
472
473 def GetFilename(self):
474 rev_tokens = self.file_location.split('@')
475 return os.path.split(rev_tokens[0])[1]
476
477 def GetRevision(self):
478 rev_tokens = self.file_location.split('@')
479 if len(rev_tokens) > 1:
480 return rev_tokens[1]
481 return None
482
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000483 class _VarImpl:
484 def __init__(self, custom_vars, local_scope):
485 self._custom_vars = custom_vars
486 self._local_scope = local_scope
487
488 def Lookup(self, var_name):
489 """Implements the Var syntax."""
490 if var_name in self._custom_vars:
491 return self._custom_vars[var_name]
492 elif var_name in self._local_scope.get("vars", {}):
493 return self._local_scope["vars"][var_name]
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000494 raise gclient_utils.Error("Var is not defined: %s" % var_name)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000495
496 def _ParseSolutionDeps(self, solution_name, solution_deps_content,
tony@chromium.org30ef9ae2010-04-09 02:18:05 +0000497 custom_vars, parse_hooks):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000498 """Parses the DEPS file for the specified solution.
499
500 Args:
501 solution_name: The name of the solution to query.
502 solution_deps_content: Content of the DEPS file for the solution
503 custom_vars: A dict of vars to override any vars defined in the DEPS file.
504
505 Returns:
506 A dict mapping module names (as relative paths) to URLs or an empty
507 dict if the solution does not have a DEPS file.
508 """
509 # Skip empty
510 if not solution_deps_content:
511 return {}
512 # Eval the content
513 local_scope = {}
514 var = self._VarImpl(custom_vars, local_scope)
tony@chromium.org4b5b1772010-04-08 01:52:56 +0000515 global_scope = {
516 "File": self.FileImpl,
517 "From": self.FromImpl,
518 "Var": var.Lookup,
519 "deps_os": {},
520 }
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000521 exec(solution_deps_content, global_scope, local_scope)
522 deps = local_scope.get("deps", {})
523
524 # load os specific dependencies if defined. these dependencies may
525 # override or extend the values defined by the 'deps' member.
526 if "deps_os" in local_scope:
527 deps_os_choices = {
528 "win32": "win",
529 "win": "win",
530 "cygwin": "win",
531 "darwin": "mac",
532 "mac": "mac",
533 "unix": "unix",
534 "linux": "unix",
535 "linux2": "unix",
536 }
537
538 if self._options.deps_os is not None:
539 deps_to_include = self._options.deps_os.split(",")
540 if "all" in deps_to_include:
maruel@chromium.org55e724e2010-03-11 19:36:49 +0000541 deps_to_include = list(set(deps_os_choices.itervalues()))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000542 else:
543 deps_to_include = [deps_os_choices.get(self._options.platform, "unix")]
544
545 deps_to_include = set(deps_to_include)
546 for deps_os_key in deps_to_include:
547 os_deps = local_scope["deps_os"].get(deps_os_key, {})
548 if len(deps_to_include) > 1:
549 # Ignore any overrides when including deps for more than one
550 # platform, so we collect the broadest set of dependencies available.
551 # We may end up with the wrong revision of something for our
552 # platform, but this is the best we can do.
553 deps.update([x for x in os_deps.items() if not x[0] in deps])
554 else:
555 deps.update(os_deps)
556
tony@chromium.org30ef9ae2010-04-09 02:18:05 +0000557 if 'hooks' in local_scope and parse_hooks:
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000558 self._deps_hooks.extend(local_scope['hooks'])
559
560 # If use_relative_paths is set in the DEPS file, regenerate
561 # the dictionary using paths relative to the directory containing
562 # the DEPS file.
563 if local_scope.get('use_relative_paths'):
564 rel_deps = {}
565 for d, url in deps.items():
566 # normpath is required to allow DEPS to use .. in their
567 # dependency local path.
568 rel_deps[os.path.normpath(os.path.join(solution_name, d))] = url
569 return rel_deps
570 else:
571 return deps
572
573 def _ParseAllDeps(self, solution_urls, solution_deps_content):
574 """Parse the complete list of dependencies for the client.
575
576 Args:
577 solution_urls: A dict mapping module names (as relative paths) to URLs
578 corresponding to the solutions specified by the client. This parameter
579 is passed as an optimization.
580 solution_deps_content: A dict mapping module names to the content
581 of their DEPS files
582
583 Returns:
584 A dict mapping module names (as relative paths) to URLs corresponding
585 to the entire set of dependencies to checkout for the given client.
586
587 Raises:
588 Error: If a dependency conflicts with another dependency or of a solution.
589 """
590 deps = {}
591 for solution in self.GetVar("solutions"):
592 custom_vars = solution.get("custom_vars", {})
593 solution_deps = self._ParseSolutionDeps(
594 solution["name"],
595 solution_deps_content[solution["name"]],
tony@chromium.org30ef9ae2010-04-09 02:18:05 +0000596 custom_vars,
597 True)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000598
599 # If a line is in custom_deps, but not in the solution, we want to append
600 # this line to the solution.
601 if "custom_deps" in solution:
602 for d in solution["custom_deps"]:
603 if d not in solution_deps:
604 solution_deps[d] = solution["custom_deps"][d]
605
606 for d in solution_deps:
607 if "custom_deps" in solution and d in solution["custom_deps"]:
608 # Dependency is overriden.
609 url = solution["custom_deps"][d]
610 if url is None:
611 continue
612 else:
613 url = solution_deps[d]
614 # if we have a From reference dependent on another solution, then
615 # just skip the From reference. When we pull deps for the solution,
616 # we will take care of this dependency.
617 #
618 # If multiple solutions all have the same From reference, then we
619 # should only add one to our list of dependencies.
tony@chromium.org4b5b1772010-04-08 01:52:56 +0000620 if isinstance(url, self.FromImpl):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000621 if url.module_name in solution_urls:
622 # Already parsed.
623 continue
624 if d in deps and type(deps[d]) != str:
625 if url.module_name == deps[d].module_name:
626 continue
tony@chromium.org4b5b1772010-04-08 01:52:56 +0000627 elif isinstance(url, str):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000628 parsed_url = urlparse.urlparse(url)
629 scheme = parsed_url[0]
630 if not scheme:
631 # A relative url. Fetch the real base.
632 path = parsed_url[2]
633 if path[0] != "/":
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000634 raise gclient_utils.Error(
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000635 "relative DEPS entry \"%s\" must begin with a slash" % d)
msb@chromium.orge6f78352010-01-13 17:05:33 +0000636 # Create a scm just to query the full url.
637 scm = gclient_scm.CreateSCM(solution["url"], self._root_dir,
638 None)
639 url = scm.FullUrlForRelativeUrl(url)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000640 if d in deps and deps[d] != url:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000641 raise gclient_utils.Error(
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000642 "Solutions have conflicting versions of dependency \"%s\"" % d)
643 if d in solution_urls and solution_urls[d] != url:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000644 raise gclient_utils.Error(
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000645 "Dependency \"%s\" conflicts with specified solution" % d)
646 # Grab the dependency.
647 deps[d] = url
648 return deps
649
phajdan.jr@chromium.org71b40682009-07-31 23:40:09 +0000650 def _RunHookAction(self, hook_dict, matching_file_list):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000651 """Runs the action from a single hook.
652 """
653 command = hook_dict['action'][:]
654 if command[0] == 'python':
655 # If the hook specified "python" as the first item, the action is a
656 # Python script. Run it by starting a new copy of the same
657 # interpreter.
658 command[0] = sys.executable
659
phajdan.jr@chromium.org71b40682009-07-31 23:40:09 +0000660 if '$matching_files' in command:
phajdan.jr@chromium.org68f2e092009-08-06 17:05:35 +0000661 splice_index = command.index('$matching_files')
662 command[splice_index:splice_index + 1] = matching_file_list
phajdan.jr@chromium.org71b40682009-07-31 23:40:09 +0000663
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000664 # Use a discrete exit status code of 2 to indicate that a hook action
665 # failed. Users of this script may wish to treat hook action failures
666 # differently from VC failures.
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000667 gclient_utils.SubprocessCall(command, self._root_dir, fail_status=2)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000668
669 def _RunHooks(self, command, file_list, is_using_git):
670 """Evaluates all hooks, running actions as needed.
671 """
672 # Hooks only run for these command types.
673 if not command in ('update', 'revert', 'runhooks'):
674 return
675
evan@chromium.org67820ef2009-07-27 17:23:00 +0000676 # Hooks only run when --nohooks is not specified
677 if self._options.nohooks:
678 return
679
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000680 # Get any hooks from the .gclient file.
681 hooks = self.GetVar("hooks", [])
682 # Add any hooks found in DEPS files.
683 hooks.extend(self._deps_hooks)
684
685 # If "--force" was specified, run all hooks regardless of what files have
686 # changed. If the user is using git, then we don't know what files have
687 # changed so we always run all hooks.
688 if self._options.force or is_using_git:
689 for hook_dict in hooks:
phajdan.jr@chromium.org71b40682009-07-31 23:40:09 +0000690 self._RunHookAction(hook_dict, [])
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000691 return
692
693 # Run hooks on the basis of whether the files from the gclient operation
694 # match each hook's pattern.
695 for hook_dict in hooks:
696 pattern = re.compile(hook_dict['pattern'])
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000697 matching_file_list = [f for f in file_list if pattern.search(f)]
phajdan.jr@chromium.org71b40682009-07-31 23:40:09 +0000698 if matching_file_list:
699 self._RunHookAction(hook_dict, matching_file_list)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000700
701 def RunOnDeps(self, command, args):
702 """Runs a command on each dependency in a client and its dependencies.
703
704 The module's dependencies are specified in its top-level DEPS files.
705
706 Args:
707 command: The command to use (e.g., 'status' or 'diff')
708 args: list of str - extra arguments to add to the command line.
709
710 Raises:
711 Error: If the client has conflicting entries.
712 """
713 if not command in self.supported_commands:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000714 raise gclient_utils.Error("'%s' is an unsupported command" % command)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000715
716 # Check for revision overrides.
717 revision_overrides = {}
718 for revision in self._options.revisions:
719 if revision.find("@") == -1:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000720 raise gclient_utils.Error(
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000721 "Specify the full dependency when specifying a revision number.")
722 revision_elem = revision.split("@")
723 # Disallow conflicting revs
724 if revision_overrides.has_key(revision_elem[0]) and \
725 revision_overrides[revision_elem[0]] != revision_elem[1]:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000726 raise gclient_utils.Error(
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000727 "Conflicting revision numbers specified.")
728 revision_overrides[revision_elem[0]] = revision_elem[1]
729
730 solutions = self.GetVar("solutions")
731 if not solutions:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000732 raise gclient_utils.Error("No solution specified")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000733
734 # When running runhooks --force, there's no need to consult the SCM.
735 # All known hooks are expected to run unconditionally regardless of working
736 # copy state, so skip the SCM status check.
737 run_scm = not (command == 'runhooks' and self._options.force)
738
739 entries = {}
740 entries_deps_content = {}
piman@chromium.org54c3a7e2010-04-17 02:38:58 +0000741 file_list = []
742 # Run on the base solutions first.
743 for solution in solutions:
744 name = solution["name"]
745 deps_file = solution.get("deps_file", self._options.deps_file)
746 if '/' in deps_file or '\\' in deps_file:
747 raise gclient_utils.Error('deps_file name must not be a path, just a '
748 'filename.')
749 if name in entries:
750 raise gclient_utils.Error("solution %s specified more than once" % name)
751 url = solution["url"]
752 entries[name] = url
753 if run_scm and url:
754 self._options.revision = revision_overrides.get(name)
755 scm = gclient_scm.CreateSCM(url, self._root_dir, name)
756 scm.RunCommand(command, self._options, args, file_list)
757 file_list = [os.path.join(name, f.strip()) for f in file_list]
758 self._options.revision = None
759 try:
760 deps_content = gclient_utils.FileRead(
761 os.path.join(self._root_dir, name, deps_file))
762 except IOError, e:
763 if e.errno != errno.ENOENT:
764 raise
765 deps_content = ""
766 entries_deps_content[name] = deps_content
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000767
piman@chromium.org54c3a7e2010-04-17 02:38:58 +0000768 # Process the dependencies next (sort alphanumerically to ensure that
769 # containing directories get populated first and for readability)
770 deps = self._ParseAllDeps(entries, entries_deps_content)
771 deps_to_process = deps.keys()
772 deps_to_process.sort()
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000773
piman@chromium.org54c3a7e2010-04-17 02:38:58 +0000774 # First pass for direct dependencies.
775 if command == 'update' and not self._options.verbose:
776 pm = Progress('Syncing projects', len(deps_to_process))
777 for d in deps_to_process:
nasser@codeaurora.org1f7a3d12010-02-04 15:11:50 +0000778 if command == 'update' and not self._options.verbose:
piman@chromium.org54c3a7e2010-04-17 02:38:58 +0000779 pm.update()
780 if type(deps[d]) == str:
781 url = deps[d]
782 entries[d] = url
783 if run_scm:
784 self._options.revision = revision_overrides.get(d)
785 scm = gclient_scm.CreateSCM(url, self._root_dir, d)
786 scm.RunCommand(command, self._options, args, file_list)
787 self._options.revision = None
788 elif isinstance(deps[d], self.FileImpl):
789 file = deps[d]
790 self._options.revision = file.GetRevision()
791 if run_scm:
792 scm = gclient_scm.CreateSCM(file.GetPath(), self._root_dir, d)
793 scm.RunCommand("updatesingle", self._options,
794 args + [file.GetFilename()], file_list)
piman@chromium.org8b5086d2010-04-17 02:05:32 +0000795
piman@chromium.org54c3a7e2010-04-17 02:38:58 +0000796 if command == 'update' and not self._options.verbose:
797 pm.end()
piman@chromium.org8b5086d2010-04-17 02:05:32 +0000798
piman@chromium.org54c3a7e2010-04-17 02:38:58 +0000799 # Second pass for inherited deps (via the From keyword)
800 for d in deps_to_process:
801 if isinstance(deps[d], self.FromImpl):
802 filename = os.path.join(self._root_dir,
803 deps[d].module_name,
804 self._options.deps_file)
805 content = gclient_utils.FileRead(filename)
806 sub_deps = self._ParseSolutionDeps(deps[d].module_name, content, {},
807 False)
808 # Getting the URL from the sub_deps file can involve having to resolve
809 # a File() or having to resolve a relative URL. To resolve relative
810 # URLs, we need to pass in the orignal sub deps URL.
811 sub_deps_base_url = deps[deps[d].module_name]
812 url = deps[d].GetUrl(d, sub_deps_base_url, self._root_dir, sub_deps)
813 entries[d] = url
814 if run_scm:
815 self._options.revision = revision_overrides.get(d)
816 scm = gclient_scm.CreateSCM(url, self._root_dir, d)
817 scm.RunCommand(command, self._options, args, file_list)
818 self._options.revision = None
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000819
phajdan.jr@chromium.orgd83b2b22009-08-11 15:30:55 +0000820 # Convert all absolute paths to relative.
821 for i in range(len(file_list)):
822 # TODO(phajdan.jr): We should know exactly when the paths are absolute.
823 # It depends on the command being executed (like runhooks vs sync).
824 if not os.path.isabs(file_list[i]):
825 continue
826
827 prefix = os.path.commonprefix([self._root_dir.lower(),
828 file_list[i].lower()])
829 file_list[i] = file_list[i][len(prefix):]
830
831 # Strip any leading path separators.
832 while file_list[i].startswith('\\') or file_list[i].startswith('/'):
833 file_list[i] = file_list[i][1:]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000834
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000835 is_using_git = gclient_utils.IsUsingGit(self._root_dir, entries.keys())
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000836 self._RunHooks(command, file_list, is_using_git)
837
838 if command == 'update':
ajwong@chromium.orgcdcee802009-06-23 15:30:42 +0000839 # Notify the user if there is an orphaned entry in their working copy.
840 # Only delete the directory if there are no changes in it, and
841 # delete_unversioned_trees is set to true.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000842 prev_entries = self._ReadEntries()
843 for entry in prev_entries:
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000844 # Fix path separator on Windows.
845 entry_fixed = entry.replace('/', os.path.sep)
846 e_dir = os.path.join(self._root_dir, entry_fixed)
847 # Use entry and not entry_fixed there.
maruel@chromium.org0329e672009-05-13 18:41:04 +0000848 if entry not in entries and os.path.exists(e_dir):
msb@chromium.org83017012009-09-28 18:52:12 +0000849 modified_files = False
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +0000850 if isinstance(prev_entries, list):
msb@chromium.org83017012009-09-28 18:52:12 +0000851 # old .gclient_entries format was list, now dict
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +0000852 modified_files = gclient_scm.scm.SVN.CaptureStatus(e_dir)
msb@chromium.org83017012009-09-28 18:52:12 +0000853 else:
854 file_list = []
855 scm = gclient_scm.CreateSCM(prev_entries[entry], self._root_dir,
856 entry_fixed)
857 scm.status(self._options, [], file_list)
858 modified_files = file_list != []
859 if not self._options.delete_unversioned_trees or modified_files:
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000860 # There are modified files in this entry. Keep warning until
861 # removed.
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000862 print(("\nWARNING: \"%s\" is no longer part of this client. "
863 "It is recommended that you manually remove it.\n") %
864 entry_fixed)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000865 else:
866 # Delete the entry
maruel@chromium.orgdf7a3132009-05-12 17:49:49 +0000867 print("\n________ deleting \'%s\' " +
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000868 "in \'%s\'") % (entry_fixed, self._root_dir)
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000869 gclient_utils.RemoveDirectory(e_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000870 # record the current list of entries for next time
871 self._SaveEntries(entries)
872
873 def PrintRevInfo(self):
nasser@codeaurora.org5d63eb82010-03-24 23:22:09 +0000874 """Output revision info mapping for the client and its dependencies.
875
876 This allows the capture of an overall "revision" for the source tree that
877 can be used to reproduce the same tree in the future. The actual output
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000878 contains enough information (source paths, svn server urls and revisions)
nasser@codeaurora.org5d63eb82010-03-24 23:22:09 +0000879 that it can be used either to generate external svn/git commands (without
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000880 gclient) or as input to gclient's --rev option (with some massaging of
881 the data).
882
nasser@codeaurora.org5d63eb82010-03-24 23:22:09 +0000883 Since we care about the revision of the current source tree, for git
884 repositories this command uses the revision of the HEAD. For subversion we
885 use BASE.
886
887 The --snapshot option allows creating a .gclient file to reproduce the tree.
888
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000889 Raises:
890 Error: If the client has conflicting entries.
891 """
892 # Check for revision overrides.
893 revision_overrides = {}
894 for revision in self._options.revisions:
895 if revision.find("@") < 0:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000896 raise gclient_utils.Error(
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000897 "Specify the full dependency when specifying a revision number.")
898 revision_elem = revision.split("@")
899 # Disallow conflicting revs
900 if revision_overrides.has_key(revision_elem[0]) and \
901 revision_overrides[revision_elem[0]] != revision_elem[1]:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000902 raise gclient_utils.Error(
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000903 "Conflicting revision numbers specified.")
904 revision_overrides[revision_elem[0]] = revision_elem[1]
905
906 solutions = self.GetVar("solutions")
907 if not solutions:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000908 raise gclient_utils.Error("No solution specified")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000909
nasser@codeaurora.org5d63eb82010-03-24 23:22:09 +0000910 # Inner helper to generate base url and rev tuple
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000911 def GetURLAndRev(name, original_url):
nasser@codeaurora.org5d63eb82010-03-24 23:22:09 +0000912 url, _ = gclient_utils.SplitUrlRevision(original_url)
913 scm = gclient_scm.CreateSCM(original_url, self._root_dir, name)
914 return (url, scm.revinfo(self._options, [], None))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000915
maruel@chromium.orge3da35f2010-03-09 21:40:45 +0000916 # text of the snapshot gclient file
917 new_gclient = ""
918 # Dictionary of { path : SCM url } to ensure no duplicate solutions
919 solution_names = {}
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +0000920 entries = {}
921 entries_deps_content = {}
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000922 # Run on the base solutions first.
923 for solution in solutions:
maruel@chromium.orge3da35f2010-03-09 21:40:45 +0000924 # Dictionary of { path : SCM url } to describe the gclient checkout
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000925 name = solution["name"]
maruel@chromium.orge3da35f2010-03-09 21:40:45 +0000926 if name in solution_names:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000927 raise gclient_utils.Error("solution %s specified more than once" % name)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000928 (url, rev) = GetURLAndRev(name, solution["url"])
msb@chromium.org770ff9e2009-09-23 17:18:18 +0000929 entries[name] = "%s@%s" % (url, rev)
maruel@chromium.orge3da35f2010-03-09 21:40:45 +0000930 solution_names[name] = "%s@%s" % (url, rev)
nasser@codeaurora.org952d7c72010-03-01 20:41:01 +0000931 deps_file = solution.get("deps_file", self._options.deps_file)
932 if '/' in deps_file or '\\' in deps_file:
933 raise gclient_utils.Error('deps_file name must not be a path, just a '
934 'filename.')
935 try:
936 deps_content = gclient_utils.FileRead(
937 os.path.join(self._root_dir, name, deps_file))
938 except IOError, e:
939 if e.errno != errno.ENOENT:
940 raise
941 deps_content = ""
942 entries_deps_content[name] = deps_content
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000943
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +0000944 # Process the dependencies next (sort alphanumerically to ensure that
945 # containing directories get populated first and for readability)
946 deps = self._ParseAllDeps(entries, entries_deps_content)
947 deps_to_process = deps.keys()
948 deps_to_process.sort()
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000949
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +0000950 # First pass for direct dependencies.
951 for d in deps_to_process:
952 if type(deps[d]) == str:
953 (url, rev) = GetURLAndRev(d, deps[d])
954 entries[d] = "%s@%s" % (url, rev)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000955
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +0000956 # Second pass for inherited deps (via the From keyword)
957 for d in deps_to_process:
tony@chromium.org4b5b1772010-04-08 01:52:56 +0000958 if isinstance(deps[d], self.FromImpl):
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +0000959 deps_parent_url = entries[deps[d].module_name]
960 if deps_parent_url.find("@") < 0:
961 raise gclient_utils.Error("From %s missing revisioned url" %
962 deps[d].module_name)
963 content = gclient_utils.FileRead(os.path.join(
964 self._root_dir,
965 deps[d].module_name,
966 self._options.deps_file))
967 sub_deps = self._ParseSolutionDeps(deps[d].module_name, content, {})
968 (url, rev) = GetURLAndRev(d, sub_deps[d])
969 entries[d] = "%s@%s" % (url, rev)
maruel@chromium.orge3da35f2010-03-09 21:40:45 +0000970
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +0000971 # Build the snapshot configuration string
972 if self._options.snapshot:
973 url = entries.pop(name)
974 custom_deps = ",\n ".join(["\"%s\": \"%s\"" % (x, entries[x])
975 for x in sorted(entries.keys())])
maruel@chromium.orge3da35f2010-03-09 21:40:45 +0000976
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +0000977 new_gclient += DEFAULT_SNAPSHOT_SOLUTION_TEXT % {
978 'solution_name': name,
979 'solution_url': url,
980 'safesync_url' : "",
981 'solution_deps': custom_deps,
982 }
983 else:
984 print(";\n".join(["%s: %s" % (x, entries[x])
985 for x in sorted(entries.keys())]))
maruel@chromium.orge3da35f2010-03-09 21:40:45 +0000986
987 # Print the snapshot configuration file
988 if self._options.snapshot:
989 config = DEFAULT_SNAPSHOT_FILE_TEXT % {'solution_list': new_gclient}
990 snapclient = GClient(self._root_dir, self._options)
991 snapclient.SetConfig(config)
992 print(snapclient._config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000993
994
995## gclient commands.
996
997
998def DoCleanup(options, args):
999 """Handle the cleanup subcommand.
1000
1001 Raises:
1002 Error: if client isn't configured properly.
1003 """
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001004 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001005 if not client:
maruel@chromium.orge3608df2009-11-10 20:22:57 +00001006 raise gclient_utils.Error("client not configured; see 'gclient config'")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001007 if options.verbose:
1008 # Print out the .gclient file. This is longer than if we just printed the
1009 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.orgdf7a3132009-05-12 17:49:49 +00001010 print(client.ConfigContent())
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001011 return client.RunOnDeps('cleanup', args)
1012
1013
1014def DoConfig(options, args):
1015 """Handle the config subcommand.
1016
1017 Args:
1018 options: If options.spec set, a string providing contents of config file.
1019 args: The command line args. If spec is not set,
1020 then args[0] is a string URL to get for config file.
1021
1022 Raises:
1023 Error: on usage error
1024 """
1025 if len(args) < 1 and not options.spec:
maruel@chromium.orge3608df2009-11-10 20:22:57 +00001026 raise gclient_utils.Error("required argument missing; see 'gclient help "
1027 "config'")
maruel@chromium.org0329e672009-05-13 18:41:04 +00001028 if os.path.exists(options.config_filename):
maruel@chromium.orge3608df2009-11-10 20:22:57 +00001029 raise gclient_utils.Error("%s file already exists in the current directory"
1030 % options.config_filename)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001031 client = GClient('.', options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001032 if options.spec:
1033 client.SetConfig(options.spec)
1034 else:
maruel@chromium.org1ab7ffc2009-06-03 17:21:37 +00001035 base_url = args[0].rstrip('/')
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001036 if not options.name:
1037 name = base_url.split("/")[-1]
1038 else:
1039 # specify an alternate relpath for the given URL.
1040 name = options.name
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001041 safesync_url = ""
1042 if len(args) > 1:
1043 safesync_url = args[1]
1044 client.SetDefaultConfig(name, base_url, safesync_url)
1045 client.SaveConfig()
1046
1047
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +00001048def DoExport(options, args):
1049 """Handle the export subcommand.
phajdan.jr@chromium.org71b40682009-07-31 23:40:09 +00001050
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +00001051 Raises:
1052 Error: on usage error
1053 """
1054 if len(args) != 1:
maruel@chromium.orge3608df2009-11-10 20:22:57 +00001055 raise gclient_utils.Error("Need directory name")
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +00001056 client = GClient.LoadCurrentConfig(options)
1057
1058 if not client:
maruel@chromium.orge3608df2009-11-10 20:22:57 +00001059 raise gclient_utils.Error("client not configured; see 'gclient config'")
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +00001060
1061 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())
1065 return client.RunOnDeps('export', args)
1066
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001067def DoHelp(options, args):
1068 """Handle the help subcommand giving help for another subcommand.
1069
1070 Raises:
1071 Error: if the command is unknown.
1072 """
maruel@chromium.orge3608df2009-11-10 20:22:57 +00001073 __pychecker__ = 'unusednames=options'
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001074 if len(args) == 1 and args[0] in COMMAND_USAGE_TEXT:
maruel@chromium.orgdf7a3132009-05-12 17:49:49 +00001075 print(COMMAND_USAGE_TEXT[args[0]])
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001076 else:
maruel@chromium.orge3608df2009-11-10 20:22:57 +00001077 raise gclient_utils.Error("unknown subcommand '%s'; see 'gclient help'" %
1078 args[0])
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001079
1080
kbr@google.comab318592009-09-04 00:54:55 +00001081def DoPack(options, args):
1082 """Handle the pack subcommand.
1083
1084 Raises:
1085 Error: if client isn't configured properly.
1086 """
1087 client = GClient.LoadCurrentConfig(options)
1088 if not client:
maruel@chromium.orge3608df2009-11-10 20:22:57 +00001089 raise gclient_utils.Error("client not configured; see 'gclient config'")
kbr@google.comab318592009-09-04 00:54:55 +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.
1093 print(client.ConfigContent())
kbr@google.comab318592009-09-04 00:54:55 +00001094 return client.RunOnDeps('pack', args)
1095
1096
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001097def DoStatus(options, args):
1098 """Handle the status 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 if options.verbose:
1107 # Print out the .gclient file. This is longer than if we just printed the
1108 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.orgdf7a3132009-05-12 17:49:49 +00001109 print(client.ConfigContent())
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001110 return client.RunOnDeps('status', args)
1111
1112
1113def DoUpdate(options, args):
1114 """Handle the update and sync subcommands.
1115
1116 Raises:
1117 Error: if client isn't configured properly.
1118 """
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001119 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001120
1121 if not client:
maruel@chromium.orge3608df2009-11-10 20:22:57 +00001122 raise gclient_utils.Error("client not configured; see 'gclient config'")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001123
1124 if not options.head:
1125 solutions = client.GetVar('solutions')
1126 if solutions:
1127 for s in solutions:
1128 if s.get('safesync_url', ''):
1129 # rip through revisions and make sure we're not over-riding
1130 # something that was explicitly passed
1131 has_key = False
1132 for r in options.revisions:
1133 if r.split('@')[0] == s['name']:
1134 has_key = True
1135 break
1136
1137 if not has_key:
1138 handle = urllib.urlopen(s['safesync_url'])
1139 rev = handle.read().strip()
1140 handle.close()
1141 if len(rev):
1142 options.revisions.append(s['name']+'@'+rev)
1143
1144 if options.verbose:
1145 # Print out the .gclient file. This is longer than if we just printed the
1146 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.orgdf7a3132009-05-12 17:49:49 +00001147 print(client.ConfigContent())
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001148 return client.RunOnDeps('update', args)
1149
1150
1151def DoDiff(options, args):
1152 """Handle the diff subcommand.
1153
1154 Raises:
1155 Error: if client isn't configured properly.
1156 """
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001157 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001158 if not client:
maruel@chromium.orge3608df2009-11-10 20:22:57 +00001159 raise gclient_utils.Error("client not configured; see 'gclient config'")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001160 if options.verbose:
1161 # Print out the .gclient file. This is longer than if we just printed the
1162 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.orgdf7a3132009-05-12 17:49:49 +00001163 print(client.ConfigContent())
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001164 return client.RunOnDeps('diff', args)
1165
1166
1167def DoRevert(options, args):
1168 """Handle the revert subcommand.
1169
1170 Raises:
1171 Error: if client isn't configured properly.
1172 """
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001173 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001174 if not client:
maruel@chromium.orge3608df2009-11-10 20:22:57 +00001175 raise gclient_utils.Error("client not configured; see 'gclient config'")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001176 return client.RunOnDeps('revert', args)
1177
1178
1179def DoRunHooks(options, args):
1180 """Handle the runhooks subcommand.
1181
1182 Raises:
1183 Error: if client isn't configured properly.
1184 """
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001185 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001186 if not client:
maruel@chromium.orge3608df2009-11-10 20:22:57 +00001187 raise gclient_utils.Error("client not configured; see 'gclient config'")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001188 if options.verbose:
1189 # Print out the .gclient file. This is longer than if we just printed the
1190 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.orgdf7a3132009-05-12 17:49:49 +00001191 print(client.ConfigContent())
maruel@chromium.org5df6a462009-08-28 18:52:26 +00001192 options.force = True
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001193 return client.RunOnDeps('runhooks', args)
1194
1195
1196def DoRevInfo(options, args):
1197 """Handle the revinfo subcommand.
1198
1199 Raises:
1200 Error: if client isn't configured properly.
1201 """
maruel@chromium.orge3608df2009-11-10 20:22:57 +00001202 __pychecker__ = 'unusednames=args'
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001203 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001204 if not client:
maruel@chromium.orge3608df2009-11-10 20:22:57 +00001205 raise gclient_utils.Error("client not configured; see 'gclient config'")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001206 client.PrintRevInfo()
1207
1208
1209gclient_command_map = {
1210 "cleanup": DoCleanup,
1211 "config": DoConfig,
1212 "diff": DoDiff,
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +00001213 "export": DoExport,
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001214 "help": DoHelp,
kbr@google.comab318592009-09-04 00:54:55 +00001215 "pack": DoPack,
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001216 "status": DoStatus,
1217 "sync": DoUpdate,
1218 "update": DoUpdate,
1219 "revert": DoRevert,
1220 "runhooks": DoRunHooks,
1221 "revinfo" : DoRevInfo,
1222}
1223
1224
1225def DispatchCommand(command, options, args, command_map=None):
1226 """Dispatches the appropriate subcommand based on command line arguments."""
1227 if command_map is None:
1228 command_map = gclient_command_map
1229
1230 if command in command_map:
1231 return command_map[command](options, args)
1232 else:
maruel@chromium.orge3608df2009-11-10 20:22:57 +00001233 raise gclient_utils.Error("unknown subcommand '%s'; see 'gclient help'" %
1234 command)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001235
1236
1237def Main(argv):
1238 """Parse command line arguments and dispatch command."""
1239
1240 option_parser = optparse.OptionParser(usage=DEFAULT_USAGE_TEXT,
1241 version=__version__)
1242 option_parser.disable_interspersed_args()
1243 option_parser.add_option("", "--force", action="store_true", default=False,
1244 help=("(update/sync only) force update even "
1245 "for modules which haven't changed"))
evan@chromium.org67820ef2009-07-27 17:23:00 +00001246 option_parser.add_option("", "--nohooks", action="store_true", default=False,
1247 help=("(update/sync/revert only) prevent the hooks from "
1248 "running"))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001249 option_parser.add_option("", "--revision", action="append", dest="revisions",
1250 metavar="REV", default=[],
1251 help=("(update/sync only) sync to a specific "
1252 "revision, can be used multiple times for "
1253 "each solution, e.g. --revision=src@123, "
1254 "--revision=internal@32"))
1255 option_parser.add_option("", "--deps", default=None, dest="deps_os",
1256 metavar="OS_LIST",
1257 help=("(update/sync only) sync deps for the "
1258 "specified (comma-separated) platform(s); "
1259 "'all' will sync all platforms"))
davemoore@chromium.org793796d2010-02-19 17:27:41 +00001260 option_parser.add_option("", "--reset", action="store_true", default=False,
1261 help=("(update/sync only) resets any local changes "
1262 "before updating (git only)"))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001263 option_parser.add_option("", "--spec", default=None,
1264 help=("(config only) create a gclient file "
1265 "containing the provided string"))
maruel@chromium.orga6220d12010-01-06 21:04:17 +00001266 option_parser.add_option("-v", "--verbose", action="count", default=0,
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001267 help="produce additional output for diagnostics")
maruel@chromium.org7753d242009-10-07 17:40:24 +00001268 option_parser.add_option("", "--manually_grab_svn_rev", action="store_true",
1269 default=False,
1270 help="Skip svn up whenever possible by requesting "
1271 "actual HEAD revision from the repository")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001272 option_parser.add_option("", "--head", action="store_true", default=False,
1273 help=("skips any safesync_urls specified in "
1274 "configured solutions"))
ajwong@chromium.orgcdcee802009-06-23 15:30:42 +00001275 option_parser.add_option("", "--delete_unversioned_trees",
1276 action="store_true", default=False,
1277 help=("on update, delete any unexpected "
1278 "unversioned trees that are in the checkout"))
maruel@chromium.orge3da35f2010-03-09 21:40:45 +00001279 option_parser.add_option("", "--snapshot", action="store_true", default=False,
1280 help=("(revinfo only), create a snapshot file "
1281 "of the current version of all repositories"))
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001282 option_parser.add_option("", "--name",
1283 help="specify alternate relative solution path")
maruel@chromium.orge3da35f2010-03-09 21:40:45 +00001284 option_parser.add_option("", "--gclientfile", default=None,
1285 metavar="FILENAME",
1286 help=("specify an alternate .gclient file"))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001287
1288 if len(argv) < 2:
1289 # Users don't need to be told to use the 'help' command.
1290 option_parser.print_help()
1291 return 1
1292 # Add manual support for --version as first argument.
1293 if argv[1] == '--version':
1294 option_parser.print_version()
1295 return 0
1296
1297 # Add manual support for --help as first argument.
1298 if argv[1] == '--help':
1299 argv[1] = 'help'
1300
1301 command = argv[1]
1302 options, args = option_parser.parse_args(argv[2:])
1303
1304 if len(argv) < 3 and command == "help":
1305 option_parser.print_help()
1306 return 0
1307
maruel@chromium.orga6220d12010-01-06 21:04:17 +00001308 if options.verbose > 1:
maruel@chromium.org754960e2009-09-21 12:31:05 +00001309 logging.basicConfig(level=logging.DEBUG)
1310
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001311 # Files used for configuration and state saving.
1312 options.config_filename = os.environ.get("GCLIENT_FILE", ".gclient")
maruel@chromium.orge3da35f2010-03-09 21:40:45 +00001313 if options.gclientfile:
1314 options.config_filename = options.gclientfile
1315 options.entries_filename = options.config_filename + "_entries"
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001316 options.deps_file = "DEPS"
1317
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001318 options.platform = sys.platform
1319 return DispatchCommand(command, options, args)
1320
1321
1322if "__main__" == __name__:
1323 try:
1324 result = Main(sys.argv)
maruel@chromium.orge3608df2009-11-10 20:22:57 +00001325 except gclient_utils.Error, e:
maruel@chromium.orgdf7a3132009-05-12 17:49:49 +00001326 print >> sys.stderr, "Error: %s" % str(e)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001327 result = 1
1328 sys.exit(result)
1329
1330# vim: ts=2:sw=2:tw=80:et: