blob: b1dc49a82d5a0ef3a054d65c67d843528178933c [file] [log] [blame]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001#!/usr/bin/python
maruel@chromium.orgba551772010-02-03 18:21:42 +00002# Copyright (c) 2010 The Chromium Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00005
6"""A wrapper script to manage a set of client modules in different SCM.
7
8This script is intended to be used to help basic management of client
msb@chromium.orgd6504212010-01-13 17:34:31 +00009program sources residing in one or more Subversion modules and Git
10repositories, along with other modules it depends on, also in Subversion or Git,
11but possibly on multiple respositories, making a wrapper system apparently
12necessary.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000013
14Files
15 .gclient : Current client configuration, written by 'config' command.
16 Format is a Python script defining 'solutions', a list whose
17 entries each are maps binding the strings "name" and "url"
18 to strings specifying the name and location of the client
19 module, as well as "custom_deps" to a map similar to the DEPS
20 file below.
21 .gclient_entries : A cache constructed by 'update' command. Format is a
22 Python script defining 'entries', a list of the names
23 of all modules in the client
24 <module>/DEPS : Python script defining var 'deps' as a map from each requisite
25 submodule name to a URL where it can be found (via one SCM)
26
27Hooks
28 .gclient and DEPS files may optionally contain a list named "hooks" to
29 allow custom actions to be performed based on files that have changed in the
evan@chromium.org67820ef2009-07-27 17:23:00 +000030 working copy as a result of a "sync"/"update" or "revert" operation. This
31 could be prevented by using --nohooks (hooks run by default). Hooks can also
maruel@chromium.org5df6a462009-08-28 18:52:26 +000032 be forced to run with the "runhooks" operation. If "sync" is run with
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000033 --force, all known hooks will run regardless of the state of the working
34 copy.
35
36 Each item in a "hooks" list is a dict, containing these two keys:
37 "pattern" The associated value is a string containing a regular
38 expression. When a file whose pathname matches the expression
39 is checked out, updated, or reverted, the hook's "action" will
40 run.
41 "action" A list describing a command to run along with its arguments, if
42 any. An action command will run at most one time per gclient
43 invocation, regardless of how many files matched the pattern.
44 The action is executed in the same directory as the .gclient
45 file. If the first item in the list is the string "python",
46 the current Python interpreter (sys.executable) will be used
phajdan.jr@chromium.org71b40682009-07-31 23:40:09 +000047 to run the command. If the list contains string "$matching_files"
48 it will be removed from the list and the list will be extended
49 by the list of matching files.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000050
51 Example:
52 hooks = [
53 { "pattern": "\\.(gif|jpe?g|pr0n|png)$",
54 "action": ["python", "image_indexer.py", "--all"]},
55 ]
56"""
57
58__author__ = "darinf@gmail.com (Darin Fisher)"
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +000059__version__ = "0.3.4"
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000060
61import errno
maruel@chromium.org754960e2009-09-21 12:31:05 +000062import logging
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000063import optparse
64import os
msb@chromium.org2e38de72009-09-28 17:04:47 +000065import pprint
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000066import re
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000067import sys
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000068import urlparse
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000069import urllib
70
maruel@chromium.orgada4c652009-12-03 15:32:01 +000071import breakpad
72
maruel@chromium.org5f3eee32009-09-17 00:34:30 +000073import gclient_scm
74import gclient_utils
nasser@codeaurora.org1f7a3d12010-02-04 15:11:50 +000075from third_party.repo.progress import Progress
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000076
77# default help text
78DEFAULT_USAGE_TEXT = (
msb@chromium.orgd6504212010-01-13 17:34:31 +000079"""usage: %prog <subcommand> [options] [--] [SCM options/args...]
80a wrapper for managing a set of svn client modules and/or git repositories.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000081Version """ + __version__ + """
82
83subcommands:
84 cleanup
85 config
86 diff
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +000087 export
kbr@google.comab318592009-09-04 00:54:55 +000088 pack
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000089 revert
90 status
91 sync
92 update
93 runhooks
94 revinfo
95
msb@chromium.orgd6504212010-01-13 17:34:31 +000096Options and extra arguments can be passed to invoked SCM commands by
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000097appending them to the command line. Note that if the first such
98appended option starts with a dash (-) then the options must be
99preceded by -- to distinguish them from gclient options.
100
101For additional help on a subcommand or examples of usage, try
102 %prog help <subcommand>
103 %prog help files
104""")
105
106GENERIC_UPDATE_USAGE_TEXT = (
107 """Perform a checkout/update of the modules specified by the gclient
108configuration; see 'help config'. Unless --revision is specified,
109then the latest revision of the root solutions is checked out, with
110dependent submodule versions updated according to DEPS files.
111If --revision is specified, then the given revision is used in place
112of the latest, either for a single solution or for all solutions.
113Unless the --force option is provided, solutions and modules whose
114local revision matches the one to update (i.e., they have not changed
evan@chromium.org67820ef2009-07-27 17:23:00 +0000115in the repository) are *not* modified. Unless --nohooks is provided,
116the hooks are run.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000117This a synonym for 'gclient %(alias)s'
118
msb@chromium.orgd6504212010-01-13 17:34:31 +0000119usage: gclient %(cmd)s [options] [--] [SCM update options/args]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000120
121Valid options:
estade@chromium.org3b5cba42009-12-01 00:37:08 +0000122 --force : force update even for unchanged modules
123 --nohooks : don't run the hooks after the update is complete
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000124 --revision SOLUTION@REV : update given solution to specified revision
estade@chromium.org3b5cba42009-12-01 00:37:08 +0000125 --deps PLATFORM(S) : sync deps for the given platform(s), or 'all'
126 --verbose : output additional diagnostics
127 --head : update to latest revision, instead of last good revision
davemoore@chromium.org793796d2010-02-19 17:27:41 +0000128 --reset : resets any local changes before updating (git only)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000129
130Examples:
131 gclient %(cmd)s
msb@chromium.orgd6504212010-01-13 17:34:31 +0000132 update files from SCM according to current configuration,
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000133 *for modules which have changed since last update or sync*
134 gclient %(cmd)s --force
msb@chromium.orgd6504212010-01-13 17:34:31 +0000135 update files from SCM according to current configuration, for
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000136 all modules (useful for recovering files deleted from local copy)
estade@chromium.org3b5cba42009-12-01 00:37:08 +0000137 gclient %(cmd)s --revision src@31000
138 update src directory to r31000
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000139""")
140
141COMMAND_USAGE_TEXT = {
142 "cleanup":
143 """Clean up all working copies, using 'svn cleanup' for each module.
144Additional options and args may be passed to 'svn cleanup'.
145
146usage: cleanup [options] [--] [svn cleanup args/options]
147
148Valid options:
149 --verbose : output additional diagnostics
150""",
151 "config": """Create a .gclient file in the current directory; this
152specifies the configuration for further commands. After update/sync,
153top-level DEPS files in each module are read to determine dependent
154modules to operate on as well. If optional [url] parameter is
155provided, then configuration is read from a specified Subversion server
iposva@chromium.org8cf7a392010-04-07 17:20:26 +0000156URL. Otherwise, a --spec option must be provided. A --name option overrides
157the default name for the solutions.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000158
159usage: config [option | url] [safesync url]
160
161Valid options:
iposva@chromium.org8cf7a392010-04-07 17:20:26 +0000162 --name path : alternate relative path for the solution
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000163 --spec=GCLIENT_SPEC : contents of .gclient are read from string parameter.
164 *Note that due to Cygwin/Python brokenness, it
165 probably can't contain any newlines.*
166
167Examples:
168 gclient config https://gclient.googlecode.com/svn/trunk/gclient
169 configure a new client to check out gclient.py tool sources
iposva@chromium.org8cf7a392010-04-07 17:20:26 +0000170 gclient config --name tools https://gclient.googlecode.com/svn/trunk/gclient
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000171 gclient config --spec='solutions=[{"name":"gclient","""
172 '"url":"https://gclient.googlecode.com/svn/trunk/gclient",'
173 '"custom_deps":{}}]',
174 "diff": """Display the differences between two revisions of modules.
175(Does 'svn diff' for each checked out module and dependences.)
176Additional args and options to 'svn diff' can be passed after
177gclient options.
178
179usage: diff [options] [--] [svn args/options]
180
181Valid options:
182 --verbose : output additional diagnostics
183
184Examples:
185 gclient diff
186 simple 'svn diff' for configured client and dependences
187 gclient diff -- -x -b
188 use 'svn diff -x -b' to suppress whitespace-only differences
189 gclient diff -- -r HEAD -x -b
190 diff versus the latest version of each module
191""",
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +0000192 "export":
193 """Wrapper for svn export for all managed directories
194""",
kbr@google.comab318592009-09-04 00:54:55 +0000195 "pack":
196
197 """Generate a patch which can be applied at the root of the tree.
198Internally, runs 'svn diff' on each checked out module and
199dependencies, and performs minimal postprocessing of the output. The
200resulting patch is printed to stdout and can be applied to a freshly
201checked out tree via 'patch -p0 < patchfile'. Additional args and
202options to 'svn diff' can be passed after gclient options.
203
204usage: pack [options] [--] [svn args/options]
205
206Valid options:
207 --verbose : output additional diagnostics
208
209Examples:
210 gclient pack > patch.txt
211 generate simple patch for configured client and dependences
212 gclient pack -- -x -b > patch.txt
213 generate patch using 'svn diff -x -b' to suppress
214 whitespace-only differences
215 gclient pack -- -r HEAD -x -b > patch.txt
216 generate patch, diffing each file versus the latest version of
217 each module
218""",
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000219 "revert":
220 """Revert every file in every managed directory in the client view.
221
222usage: revert
223""",
224 "status":
225 """Show the status of client and dependent modules, using 'svn diff'
226for each module. Additional options and args may be passed to 'svn diff'.
227
228usage: status [options] [--] [svn diff args/options]
229
230Valid options:
231 --verbose : output additional diagnostics
evan@chromium.org67820ef2009-07-27 17:23:00 +0000232 --nohooks : don't run the hooks after the update is complete
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000233""",
234 "sync": GENERIC_UPDATE_USAGE_TEXT % {"cmd": "sync", "alias": "update"},
235 "update": GENERIC_UPDATE_USAGE_TEXT % {"cmd": "update", "alias": "sync"},
236 "help": """Describe the usage of this program or its subcommands.
237
238usage: help [options] [subcommand]
239
240Valid options:
241 --verbose : output additional diagnostics
242""",
243 "runhooks":
244 """Runs hooks for files that have been modified in the local working copy,
maruel@chromium.org5df6a462009-08-28 18:52:26 +0000245according to 'svn status'. Implies --force.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000246
247usage: runhooks [options]
248
249Valid options:
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000250 --verbose : output additional diagnostics
251""",
252 "revinfo":
253 """Outputs source path, server URL and revision information for every
nasser@codeaurora.org5d63eb82010-03-24 23:22:09 +0000254dependency in all solutions.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000255
256usage: revinfo [options]
257""",
258}
259
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000260DEFAULT_CLIENT_FILE_TEXT = ("""\
261# An element of this array (a "solution") describes a repository directory
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000262# that will be checked out into your working copy. Each solution may
263# optionally define additional dependencies (via its DEPS file) to be
264# checked out alongside the solution's directory. A solution may also
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000265# specify custom dependencies (via the "custom_deps" property) that
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000266# override or augment the dependencies specified by the DEPS file.
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000267# If a "safesync_url" is specified, it is assumed to reference the location of
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000268# a text file which contains nothing but the last known good SCM revision to
269# sync against. It is fetched if specified and used unless --head is passed
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000270
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000271solutions = [
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000272 { "name" : "%(solution_name)s",
273 "url" : "%(solution_url)s",
274 "custom_deps" : {
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000275 # To use the trunk of a component instead of what's in DEPS:
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000276 #"component": "https://svnserver/component/trunk/",
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000277 # To exclude a component from your working copy:
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000278 #"data/really_large_component": None,
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000279 },
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000280 "safesync_url": "%(safesync_url)s"
281 },
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000282]
283""")
284
maruel@chromium.orge3da35f2010-03-09 21:40:45 +0000285DEFAULT_SNAPSHOT_SOLUTION_TEXT = ("""\
286 { "name" : "%(solution_name)s",
287 "url" : "%(solution_url)s",
288 "custom_deps" : {
289 %(solution_deps)s,
290 },
291 "safesync_url": "%(safesync_url)s"
292 },
293""")
294
295DEFAULT_SNAPSHOT_FILE_TEXT = ("""\
296# An element of this array (a "solution") describes a repository directory
297# that will be checked out into your working copy. Each solution may
298# optionally define additional dependencies (via its DEPS file) to be
299# checked out alongside the solution's directory. A solution may also
300# specify custom dependencies (via the "custom_deps" property) that
301# override or augment the dependencies specified by the DEPS file.
302# If a "safesync_url" is specified, it is assumed to reference the location of
303# a text file which contains nothing but the last known good SCM revision to
304# sync against. It is fetched if specified and used unless --head is passed
305
306solutions = [
307%(solution_list)s
308]
309""")
310
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000311
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000312## GClient implementation.
313
314
315class GClient(object):
316 """Object that represent a gclient checkout."""
317
318 supported_commands = [
kbr@google.comab318592009-09-04 00:54:55 +0000319 'cleanup', 'diff', 'export', 'pack', 'revert', 'status', 'update',
320 'runhooks'
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000321 ]
322
323 def __init__(self, root_dir, options):
324 self._root_dir = root_dir
325 self._options = options
326 self._config_content = None
327 self._config_dict = {}
328 self._deps_hooks = []
329
330 def SetConfig(self, content):
331 self._config_dict = {}
332 self._config_content = content
skylined@chromium.orgdf0032c2009-05-29 10:43:56 +0000333 try:
334 exec(content, self._config_dict)
335 except SyntaxError, e:
336 try:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000337 __pychecker__ = 'no-objattrs'
skylined@chromium.orgdf0032c2009-05-29 10:43:56 +0000338 # Try to construct a human readable error message
339 error_message = [
340 'There is a syntax error in your configuration file.',
341 'Line #%s, character %s:' % (e.lineno, e.offset),
342 '"%s"' % re.sub(r'[\r\n]*$', '', e.text) ]
343 except:
344 # Something went wrong, re-raise the original exception
345 raise e
346 else:
347 # Raise a new exception with the human readable message:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000348 raise gclient_utils.Error('\n'.join(error_message))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000349
350 def SaveConfig(self):
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000351 gclient_utils.FileWrite(os.path.join(self._root_dir,
352 self._options.config_filename),
353 self._config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000354
355 def _LoadConfig(self):
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000356 client_source = gclient_utils.FileRead(
357 os.path.join(self._root_dir, self._options.config_filename))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000358 self.SetConfig(client_source)
359
360 def ConfigContent(self):
361 return self._config_content
362
363 def GetVar(self, key, default=None):
364 return self._config_dict.get(key, default)
365
366 @staticmethod
367 def LoadCurrentConfig(options, from_dir=None):
368 """Searches for and loads a .gclient file relative to the current working
369 dir.
370
371 Returns:
372 A dict representing the contents of the .gclient file or an empty dict if
373 the .gclient file doesn't exist.
374 """
375 if not from_dir:
376 from_dir = os.curdir
377 path = os.path.realpath(from_dir)
maruel@chromium.org0329e672009-05-13 18:41:04 +0000378 while not os.path.exists(os.path.join(path, options.config_filename)):
maruel@chromium.org55e724e2010-03-11 19:36:49 +0000379 split_path = os.path.split(path)
380 if not split_path[1]:
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000381 return None
maruel@chromium.org55e724e2010-03-11 19:36:49 +0000382 path = split_path[0]
maruel@chromium.org2806acc2009-05-15 12:33:34 +0000383 client = GClient(path, options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000384 client._LoadConfig()
385 return client
386
387 def SetDefaultConfig(self, solution_name, solution_url, safesync_url):
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000388 self.SetConfig(DEFAULT_CLIENT_FILE_TEXT % {
389 'solution_name': solution_name,
390 'solution_url': solution_url,
391 'safesync_url' : safesync_url,
392 })
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000393
394 def _SaveEntries(self, entries):
395 """Creates a .gclient_entries file to record the list of unique checkouts.
396
397 The .gclient_entries file lives in the same directory as .gclient.
398
399 Args:
400 entries: A sequence of solution names.
401 """
msb@chromium.org2e38de72009-09-28 17:04:47 +0000402 text = "entries = \\\n" + pprint.pformat(entries, 2) + '\n'
403 file_path = os.path.join(self._root_dir, self._options.entries_filename)
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000404 gclient_utils.FileWrite(file_path, text)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000405
406 def _ReadEntries(self):
407 """Read the .gclient_entries file for the given client.
408
409 Args:
410 client: The client for which the entries file should be read.
411
412 Returns:
413 A sequence of solution names, which will be empty if there is the
414 entries file hasn't been created yet.
415 """
416 scope = {}
417 filename = os.path.join(self._root_dir, self._options.entries_filename)
maruel@chromium.org0329e672009-05-13 18:41:04 +0000418 if not os.path.exists(filename):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000419 return []
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000420 exec(gclient_utils.FileRead(filename), scope)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000421 return scope["entries"]
422
423 class FromImpl:
424 """Used to implement the From syntax."""
425
426 def __init__(self, module_name):
427 self.module_name = module_name
428
429 def __str__(self):
430 return 'From("%s")' % self.module_name
431
tony@chromium.org4b5b1772010-04-08 01:52:56 +0000432 class FileImpl:
433 """Used to implement the File('') syntax which lets you sync a single file
434 from an SVN repo."""
435
436 def __init__(self, file_location):
437 self.file_location = file_location
438
439 def __str__(self):
440 return 'File("%s")' % self.file_location
441
442 def GetPath(self):
443 return os.path.split(self.file_location)[0]
444
445 def GetFilename(self):
446 rev_tokens = self.file_location.split('@')
447 return os.path.split(rev_tokens[0])[1]
448
449 def GetRevision(self):
450 rev_tokens = self.file_location.split('@')
451 if len(rev_tokens) > 1:
452 return rev_tokens[1]
453 return None
454
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000455 class _VarImpl:
456 def __init__(self, custom_vars, local_scope):
457 self._custom_vars = custom_vars
458 self._local_scope = local_scope
459
460 def Lookup(self, var_name):
461 """Implements the Var syntax."""
462 if var_name in self._custom_vars:
463 return self._custom_vars[var_name]
464 elif var_name in self._local_scope.get("vars", {}):
465 return self._local_scope["vars"][var_name]
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000466 raise gclient_utils.Error("Var is not defined: %s" % var_name)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000467
468 def _ParseSolutionDeps(self, solution_name, solution_deps_content,
469 custom_vars):
470 """Parses the DEPS file for the specified solution.
471
472 Args:
473 solution_name: The name of the solution to query.
474 solution_deps_content: Content of the DEPS file for the solution
475 custom_vars: A dict of vars to override any vars defined in the DEPS file.
476
477 Returns:
478 A dict mapping module names (as relative paths) to URLs or an empty
479 dict if the solution does not have a DEPS file.
480 """
481 # Skip empty
482 if not solution_deps_content:
483 return {}
484 # Eval the content
485 local_scope = {}
486 var = self._VarImpl(custom_vars, local_scope)
tony@chromium.org4b5b1772010-04-08 01:52:56 +0000487 global_scope = {
488 "File": self.FileImpl,
489 "From": self.FromImpl,
490 "Var": var.Lookup,
491 "deps_os": {},
492 }
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000493 exec(solution_deps_content, global_scope, local_scope)
494 deps = local_scope.get("deps", {})
495
496 # load os specific dependencies if defined. these dependencies may
497 # override or extend the values defined by the 'deps' member.
498 if "deps_os" in local_scope:
499 deps_os_choices = {
500 "win32": "win",
501 "win": "win",
502 "cygwin": "win",
503 "darwin": "mac",
504 "mac": "mac",
505 "unix": "unix",
506 "linux": "unix",
507 "linux2": "unix",
508 }
509
510 if self._options.deps_os is not None:
511 deps_to_include = self._options.deps_os.split(",")
512 if "all" in deps_to_include:
maruel@chromium.org55e724e2010-03-11 19:36:49 +0000513 deps_to_include = list(set(deps_os_choices.itervalues()))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000514 else:
515 deps_to_include = [deps_os_choices.get(self._options.platform, "unix")]
516
517 deps_to_include = set(deps_to_include)
518 for deps_os_key in deps_to_include:
519 os_deps = local_scope["deps_os"].get(deps_os_key, {})
520 if len(deps_to_include) > 1:
521 # Ignore any overrides when including deps for more than one
522 # platform, so we collect the broadest set of dependencies available.
523 # We may end up with the wrong revision of something for our
524 # platform, but this is the best we can do.
525 deps.update([x for x in os_deps.items() if not x[0] in deps])
526 else:
527 deps.update(os_deps)
528
529 if 'hooks' in local_scope:
530 self._deps_hooks.extend(local_scope['hooks'])
531
532 # If use_relative_paths is set in the DEPS file, regenerate
533 # the dictionary using paths relative to the directory containing
534 # the DEPS file.
535 if local_scope.get('use_relative_paths'):
536 rel_deps = {}
537 for d, url in deps.items():
538 # normpath is required to allow DEPS to use .. in their
539 # dependency local path.
540 rel_deps[os.path.normpath(os.path.join(solution_name, d))] = url
541 return rel_deps
542 else:
543 return deps
544
545 def _ParseAllDeps(self, solution_urls, solution_deps_content):
546 """Parse the complete list of dependencies for the client.
547
548 Args:
549 solution_urls: A dict mapping module names (as relative paths) to URLs
550 corresponding to the solutions specified by the client. This parameter
551 is passed as an optimization.
552 solution_deps_content: A dict mapping module names to the content
553 of their DEPS files
554
555 Returns:
556 A dict mapping module names (as relative paths) to URLs corresponding
557 to the entire set of dependencies to checkout for the given client.
558
559 Raises:
560 Error: If a dependency conflicts with another dependency or of a solution.
561 """
562 deps = {}
563 for solution in self.GetVar("solutions"):
564 custom_vars = solution.get("custom_vars", {})
565 solution_deps = self._ParseSolutionDeps(
566 solution["name"],
567 solution_deps_content[solution["name"]],
568 custom_vars)
569
570 # If a line is in custom_deps, but not in the solution, we want to append
571 # this line to the solution.
572 if "custom_deps" in solution:
573 for d in solution["custom_deps"]:
574 if d not in solution_deps:
575 solution_deps[d] = solution["custom_deps"][d]
576
577 for d in solution_deps:
578 if "custom_deps" in solution and d in solution["custom_deps"]:
579 # Dependency is overriden.
580 url = solution["custom_deps"][d]
581 if url is None:
582 continue
583 else:
584 url = solution_deps[d]
585 # if we have a From reference dependent on another solution, then
586 # just skip the From reference. When we pull deps for the solution,
587 # we will take care of this dependency.
588 #
589 # If multiple solutions all have the same From reference, then we
590 # should only add one to our list of dependencies.
tony@chromium.org4b5b1772010-04-08 01:52:56 +0000591 if isinstance(url, self.FromImpl):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000592 if url.module_name in solution_urls:
593 # Already parsed.
594 continue
595 if d in deps and type(deps[d]) != str:
596 if url.module_name == deps[d].module_name:
597 continue
tony@chromium.org4b5b1772010-04-08 01:52:56 +0000598 elif isinstance(url, str):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000599 parsed_url = urlparse.urlparse(url)
600 scheme = parsed_url[0]
601 if not scheme:
602 # A relative url. Fetch the real base.
603 path = parsed_url[2]
604 if path[0] != "/":
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000605 raise gclient_utils.Error(
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000606 "relative DEPS entry \"%s\" must begin with a slash" % d)
msb@chromium.orge6f78352010-01-13 17:05:33 +0000607 # Create a scm just to query the full url.
608 scm = gclient_scm.CreateSCM(solution["url"], self._root_dir,
609 None)
610 url = scm.FullUrlForRelativeUrl(url)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000611 if d in deps and deps[d] != url:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000612 raise gclient_utils.Error(
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000613 "Solutions have conflicting versions of dependency \"%s\"" % d)
614 if d in solution_urls and solution_urls[d] != url:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000615 raise gclient_utils.Error(
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000616 "Dependency \"%s\" conflicts with specified solution" % d)
617 # Grab the dependency.
618 deps[d] = url
619 return deps
620
phajdan.jr@chromium.org71b40682009-07-31 23:40:09 +0000621 def _RunHookAction(self, hook_dict, matching_file_list):
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000622 """Runs the action from a single hook.
623 """
624 command = hook_dict['action'][:]
625 if command[0] == 'python':
626 # If the hook specified "python" as the first item, the action is a
627 # Python script. Run it by starting a new copy of the same
628 # interpreter.
629 command[0] = sys.executable
630
phajdan.jr@chromium.org71b40682009-07-31 23:40:09 +0000631 if '$matching_files' in command:
phajdan.jr@chromium.org68f2e092009-08-06 17:05:35 +0000632 splice_index = command.index('$matching_files')
633 command[splice_index:splice_index + 1] = matching_file_list
phajdan.jr@chromium.org71b40682009-07-31 23:40:09 +0000634
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000635 # Use a discrete exit status code of 2 to indicate that a hook action
636 # failed. Users of this script may wish to treat hook action failures
637 # differently from VC failures.
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000638 gclient_utils.SubprocessCall(command, self._root_dir, fail_status=2)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000639
640 def _RunHooks(self, command, file_list, is_using_git):
641 """Evaluates all hooks, running actions as needed.
642 """
643 # Hooks only run for these command types.
644 if not command in ('update', 'revert', 'runhooks'):
645 return
646
evan@chromium.org67820ef2009-07-27 17:23:00 +0000647 # Hooks only run when --nohooks is not specified
648 if self._options.nohooks:
649 return
650
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000651 # Get any hooks from the .gclient file.
652 hooks = self.GetVar("hooks", [])
653 # Add any hooks found in DEPS files.
654 hooks.extend(self._deps_hooks)
655
656 # If "--force" was specified, run all hooks regardless of what files have
657 # changed. If the user is using git, then we don't know what files have
658 # changed so we always run all hooks.
659 if self._options.force or is_using_git:
660 for hook_dict in hooks:
phajdan.jr@chromium.org71b40682009-07-31 23:40:09 +0000661 self._RunHookAction(hook_dict, [])
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000662 return
663
664 # Run hooks on the basis of whether the files from the gclient operation
665 # match each hook's pattern.
666 for hook_dict in hooks:
667 pattern = re.compile(hook_dict['pattern'])
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000668 matching_file_list = [f for f in file_list if pattern.search(f)]
phajdan.jr@chromium.org71b40682009-07-31 23:40:09 +0000669 if matching_file_list:
670 self._RunHookAction(hook_dict, matching_file_list)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000671
672 def RunOnDeps(self, command, args):
673 """Runs a command on each dependency in a client and its dependencies.
674
675 The module's dependencies are specified in its top-level DEPS files.
676
677 Args:
678 command: The command to use (e.g., 'status' or 'diff')
679 args: list of str - extra arguments to add to the command line.
680
681 Raises:
682 Error: If the client has conflicting entries.
683 """
684 if not command in self.supported_commands:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000685 raise gclient_utils.Error("'%s' is an unsupported command" % command)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000686
687 # Check for revision overrides.
688 revision_overrides = {}
689 for revision in self._options.revisions:
690 if revision.find("@") == -1:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000691 raise gclient_utils.Error(
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000692 "Specify the full dependency when specifying a revision number.")
693 revision_elem = revision.split("@")
694 # Disallow conflicting revs
695 if revision_overrides.has_key(revision_elem[0]) and \
696 revision_overrides[revision_elem[0]] != revision_elem[1]:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000697 raise gclient_utils.Error(
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000698 "Conflicting revision numbers specified.")
699 revision_overrides[revision_elem[0]] = revision_elem[1]
700
701 solutions = self.GetVar("solutions")
702 if not solutions:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000703 raise gclient_utils.Error("No solution specified")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000704
705 # When running runhooks --force, there's no need to consult the SCM.
706 # All known hooks are expected to run unconditionally regardless of working
707 # copy state, so skip the SCM status check.
708 run_scm = not (command == 'runhooks' and self._options.force)
709
710 entries = {}
711 entries_deps_content = {}
712 file_list = []
713 # Run on the base solutions first.
714 for solution in solutions:
715 name = solution["name"]
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000716 deps_file = solution.get("deps_file", self._options.deps_file)
717 if '/' in deps_file or '\\' in deps_file:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000718 raise gclient_utils.Error('deps_file name must not be a path, just a '
719 'filename.')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000720 if name in entries:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000721 raise gclient_utils.Error("solution %s specified more than once" % name)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000722 url = solution["url"]
723 entries[name] = url
yaar@chromium.orgf1328042009-09-22 23:14:23 +0000724 if run_scm and url:
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000725 self._options.revision = revision_overrides.get(name)
msb@chromium.orgcb5442b2009-09-22 16:51:24 +0000726 scm = gclient_scm.CreateSCM(url, self._root_dir, name)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000727 scm.RunCommand(command, self._options, args, file_list)
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000728 file_list = [os.path.join(name, f.strip()) for f in file_list]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000729 self._options.revision = None
730 try:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000731 deps_content = gclient_utils.FileRead(
732 os.path.join(self._root_dir, name, deps_file))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000733 except IOError, e:
734 if e.errno != errno.ENOENT:
735 raise
736 deps_content = ""
737 entries_deps_content[name] = deps_content
738
739 # Process the dependencies next (sort alphanumerically to ensure that
740 # containing directories get populated first and for readability)
741 deps = self._ParseAllDeps(entries, entries_deps_content)
742 deps_to_process = deps.keys()
743 deps_to_process.sort()
744
745 # First pass for direct dependencies.
nasser@codeaurora.org1f7a3d12010-02-04 15:11:50 +0000746 if command == 'update' and not self._options.verbose:
747 pm = Progress('Syncing projects', len(deps_to_process))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000748 for d in deps_to_process:
nasser@codeaurora.org1f7a3d12010-02-04 15:11:50 +0000749 if command == 'update' and not self._options.verbose:
750 pm.update()
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000751 if type(deps[d]) == str:
752 url = deps[d]
753 entries[d] = url
754 if run_scm:
755 self._options.revision = revision_overrides.get(d)
msb@chromium.orgcb5442b2009-09-22 16:51:24 +0000756 scm = gclient_scm.CreateSCM(url, self._root_dir, d)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000757 scm.RunCommand(command, self._options, args, file_list)
758 self._options.revision = None
tony@chromium.org4b5b1772010-04-08 01:52:56 +0000759 elif isinstance(deps[d], self.FileImpl):
760 file = deps[d]
761 self._options.revision = file.GetRevision()
762 if run_scm:
763 scm = gclient_scm.CreateSCM(file.GetPath(), self._root_dir, d)
764 scm.RunCommand("updatesingle", self._options,
765 args + [file.GetFilename()], file_list)
766
nasser@codeaurora.org1f7a3d12010-02-04 15:11:50 +0000767 if command == 'update' and not self._options.verbose:
768 pm.end()
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000769
770 # Second pass for inherited deps (via the From keyword)
771 for d in deps_to_process:
tony@chromium.org4b5b1772010-04-08 01:52:56 +0000772 if isinstance(deps[d], self.FromImpl):
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000773 filename = os.path.join(self._root_dir,
774 deps[d].module_name,
775 self._options.deps_file)
776 content = gclient_utils.FileRead(filename)
777 sub_deps = self._ParseSolutionDeps(deps[d].module_name, content, {})
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000778 url = sub_deps[d]
779 entries[d] = url
780 if run_scm:
781 self._options.revision = revision_overrides.get(d)
msb@chromium.orgcb5442b2009-09-22 16:51:24 +0000782 scm = gclient_scm.CreateSCM(url, self._root_dir, d)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000783 scm.RunCommand(command, self._options, args, file_list)
784 self._options.revision = None
gspencer@google.comdf2d5902009-09-11 22:16:21 +0000785
phajdan.jr@chromium.orgd83b2b22009-08-11 15:30:55 +0000786 # Convert all absolute paths to relative.
787 for i in range(len(file_list)):
788 # TODO(phajdan.jr): We should know exactly when the paths are absolute.
789 # It depends on the command being executed (like runhooks vs sync).
790 if not os.path.isabs(file_list[i]):
791 continue
792
793 prefix = os.path.commonprefix([self._root_dir.lower(),
794 file_list[i].lower()])
795 file_list[i] = file_list[i][len(prefix):]
796
797 # Strip any leading path separators.
798 while file_list[i].startswith('\\') or file_list[i].startswith('/'):
799 file_list[i] = file_list[i][1:]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000800
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000801 is_using_git = gclient_utils.IsUsingGit(self._root_dir, entries.keys())
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000802 self._RunHooks(command, file_list, is_using_git)
803
804 if command == 'update':
ajwong@chromium.orgcdcee802009-06-23 15:30:42 +0000805 # Notify the user if there is an orphaned entry in their working copy.
806 # Only delete the directory if there are no changes in it, and
807 # delete_unversioned_trees is set to true.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000808 prev_entries = self._ReadEntries()
809 for entry in prev_entries:
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000810 # Fix path separator on Windows.
811 entry_fixed = entry.replace('/', os.path.sep)
812 e_dir = os.path.join(self._root_dir, entry_fixed)
813 # Use entry and not entry_fixed there.
maruel@chromium.org0329e672009-05-13 18:41:04 +0000814 if entry not in entries and os.path.exists(e_dir):
msb@chromium.org83017012009-09-28 18:52:12 +0000815 modified_files = False
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +0000816 if isinstance(prev_entries, list):
msb@chromium.org83017012009-09-28 18:52:12 +0000817 # old .gclient_entries format was list, now dict
maruel@chromium.org5aeb7dd2009-11-17 18:09:01 +0000818 modified_files = gclient_scm.scm.SVN.CaptureStatus(e_dir)
msb@chromium.org83017012009-09-28 18:52:12 +0000819 else:
820 file_list = []
821 scm = gclient_scm.CreateSCM(prev_entries[entry], self._root_dir,
822 entry_fixed)
823 scm.status(self._options, [], file_list)
824 modified_files = file_list != []
825 if not self._options.delete_unversioned_trees or modified_files:
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000826 # There are modified files in this entry. Keep warning until
827 # removed.
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000828 print(("\nWARNING: \"%s\" is no longer part of this client. "
829 "It is recommended that you manually remove it.\n") %
830 entry_fixed)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000831 else:
832 # Delete the entry
maruel@chromium.orgdf7a3132009-05-12 17:49:49 +0000833 print("\n________ deleting \'%s\' " +
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000834 "in \'%s\'") % (entry_fixed, self._root_dir)
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000835 gclient_utils.RemoveDirectory(e_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000836 # record the current list of entries for next time
837 self._SaveEntries(entries)
838
839 def PrintRevInfo(self):
nasser@codeaurora.org5d63eb82010-03-24 23:22:09 +0000840 """Output revision info mapping for the client and its dependencies.
841
842 This allows the capture of an overall "revision" for the source tree that
843 can be used to reproduce the same tree in the future. The actual output
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000844 contains enough information (source paths, svn server urls and revisions)
nasser@codeaurora.org5d63eb82010-03-24 23:22:09 +0000845 that it can be used either to generate external svn/git commands (without
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000846 gclient) or as input to gclient's --rev option (with some massaging of
847 the data).
848
nasser@codeaurora.org5d63eb82010-03-24 23:22:09 +0000849 Since we care about the revision of the current source tree, for git
850 repositories this command uses the revision of the HEAD. For subversion we
851 use BASE.
852
853 The --snapshot option allows creating a .gclient file to reproduce the tree.
854
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000855 Raises:
856 Error: If the client has conflicting entries.
857 """
858 # Check for revision overrides.
859 revision_overrides = {}
860 for revision in self._options.revisions:
861 if revision.find("@") < 0:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000862 raise gclient_utils.Error(
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000863 "Specify the full dependency when specifying a revision number.")
864 revision_elem = revision.split("@")
865 # Disallow conflicting revs
866 if revision_overrides.has_key(revision_elem[0]) and \
867 revision_overrides[revision_elem[0]] != revision_elem[1]:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000868 raise gclient_utils.Error(
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000869 "Conflicting revision numbers specified.")
870 revision_overrides[revision_elem[0]] = revision_elem[1]
871
872 solutions = self.GetVar("solutions")
873 if not solutions:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000874 raise gclient_utils.Error("No solution specified")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000875
nasser@codeaurora.org5d63eb82010-03-24 23:22:09 +0000876 # Inner helper to generate base url and rev tuple
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000877 def GetURLAndRev(name, original_url):
nasser@codeaurora.org5d63eb82010-03-24 23:22:09 +0000878 url, _ = gclient_utils.SplitUrlRevision(original_url)
879 scm = gclient_scm.CreateSCM(original_url, self._root_dir, name)
880 return (url, scm.revinfo(self._options, [], None))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000881
maruel@chromium.orge3da35f2010-03-09 21:40:45 +0000882 # text of the snapshot gclient file
883 new_gclient = ""
884 # Dictionary of { path : SCM url } to ensure no duplicate solutions
885 solution_names = {}
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +0000886 entries = {}
887 entries_deps_content = {}
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000888 # Run on the base solutions first.
889 for solution in solutions:
maruel@chromium.orge3da35f2010-03-09 21:40:45 +0000890 # Dictionary of { path : SCM url } to describe the gclient checkout
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000891 name = solution["name"]
maruel@chromium.orge3da35f2010-03-09 21:40:45 +0000892 if name in solution_names:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000893 raise gclient_utils.Error("solution %s specified more than once" % name)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000894 (url, rev) = GetURLAndRev(name, solution["url"])
msb@chromium.org770ff9e2009-09-23 17:18:18 +0000895 entries[name] = "%s@%s" % (url, rev)
maruel@chromium.orge3da35f2010-03-09 21:40:45 +0000896 solution_names[name] = "%s@%s" % (url, rev)
nasser@codeaurora.org952d7c72010-03-01 20:41:01 +0000897 deps_file = solution.get("deps_file", self._options.deps_file)
898 if '/' in deps_file or '\\' in deps_file:
899 raise gclient_utils.Error('deps_file name must not be a path, just a '
900 'filename.')
901 try:
902 deps_content = gclient_utils.FileRead(
903 os.path.join(self._root_dir, name, deps_file))
904 except IOError, e:
905 if e.errno != errno.ENOENT:
906 raise
907 deps_content = ""
908 entries_deps_content[name] = deps_content
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000909
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +0000910 # Process the dependencies next (sort alphanumerically to ensure that
911 # containing directories get populated first and for readability)
912 deps = self._ParseAllDeps(entries, entries_deps_content)
913 deps_to_process = deps.keys()
914 deps_to_process.sort()
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000915
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +0000916 # First pass for direct dependencies.
917 for d in deps_to_process:
918 if type(deps[d]) == str:
919 (url, rev) = GetURLAndRev(d, deps[d])
920 entries[d] = "%s@%s" % (url, rev)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000921
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +0000922 # Second pass for inherited deps (via the From keyword)
923 for d in deps_to_process:
tony@chromium.org4b5b1772010-04-08 01:52:56 +0000924 if isinstance(deps[d], self.FromImpl):
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +0000925 deps_parent_url = entries[deps[d].module_name]
926 if deps_parent_url.find("@") < 0:
927 raise gclient_utils.Error("From %s missing revisioned url" %
928 deps[d].module_name)
929 content = gclient_utils.FileRead(os.path.join(
930 self._root_dir,
931 deps[d].module_name,
932 self._options.deps_file))
933 sub_deps = self._ParseSolutionDeps(deps[d].module_name, content, {})
934 (url, rev) = GetURLAndRev(d, sub_deps[d])
935 entries[d] = "%s@%s" % (url, rev)
maruel@chromium.orge3da35f2010-03-09 21:40:45 +0000936
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +0000937 # Build the snapshot configuration string
938 if self._options.snapshot:
939 url = entries.pop(name)
940 custom_deps = ",\n ".join(["\"%s\": \"%s\"" % (x, entries[x])
941 for x in sorted(entries.keys())])
maruel@chromium.orge3da35f2010-03-09 21:40:45 +0000942
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +0000943 new_gclient += DEFAULT_SNAPSHOT_SOLUTION_TEXT % {
944 'solution_name': name,
945 'solution_url': url,
946 'safesync_url' : "",
947 'solution_deps': custom_deps,
948 }
949 else:
950 print(";\n".join(["%s: %s" % (x, entries[x])
951 for x in sorted(entries.keys())]))
maruel@chromium.orge3da35f2010-03-09 21:40:45 +0000952
953 # Print the snapshot configuration file
954 if self._options.snapshot:
955 config = DEFAULT_SNAPSHOT_FILE_TEXT % {'solution_list': new_gclient}
956 snapclient = GClient(self._root_dir, self._options)
957 snapclient.SetConfig(config)
958 print(snapclient._config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000959
960
961## gclient commands.
962
963
964def DoCleanup(options, args):
965 """Handle the cleanup subcommand.
966
967 Raises:
968 Error: if client isn't configured properly.
969 """
maruel@chromium.org2806acc2009-05-15 12:33:34 +0000970 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000971 if not client:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000972 raise gclient_utils.Error("client not configured; see 'gclient config'")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000973 if options.verbose:
974 # Print out the .gclient file. This is longer than if we just printed the
975 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.orgdf7a3132009-05-12 17:49:49 +0000976 print(client.ConfigContent())
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000977 return client.RunOnDeps('cleanup', args)
978
979
980def DoConfig(options, args):
981 """Handle the config subcommand.
982
983 Args:
984 options: If options.spec set, a string providing contents of config file.
985 args: The command line args. If spec is not set,
986 then args[0] is a string URL to get for config file.
987
988 Raises:
989 Error: on usage error
990 """
991 if len(args) < 1 and not options.spec:
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000992 raise gclient_utils.Error("required argument missing; see 'gclient help "
993 "config'")
maruel@chromium.org0329e672009-05-13 18:41:04 +0000994 if os.path.exists(options.config_filename):
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000995 raise gclient_utils.Error("%s file already exists in the current directory"
996 % options.config_filename)
maruel@chromium.org2806acc2009-05-15 12:33:34 +0000997 client = GClient('.', options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000998 if options.spec:
999 client.SetConfig(options.spec)
1000 else:
maruel@chromium.org1ab7ffc2009-06-03 17:21:37 +00001001 base_url = args[0].rstrip('/')
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001002 if not options.name:
1003 name = base_url.split("/")[-1]
1004 else:
1005 # specify an alternate relpath for the given URL.
1006 name = options.name
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001007 safesync_url = ""
1008 if len(args) > 1:
1009 safesync_url = args[1]
1010 client.SetDefaultConfig(name, base_url, safesync_url)
1011 client.SaveConfig()
1012
1013
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +00001014def DoExport(options, args):
1015 """Handle the export subcommand.
phajdan.jr@chromium.org71b40682009-07-31 23:40:09 +00001016
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +00001017 Raises:
1018 Error: on usage error
1019 """
1020 if len(args) != 1:
maruel@chromium.orge3608df2009-11-10 20:22:57 +00001021 raise gclient_utils.Error("Need directory name")
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +00001022 client = GClient.LoadCurrentConfig(options)
1023
1024 if not client:
maruel@chromium.orge3608df2009-11-10 20:22:57 +00001025 raise gclient_utils.Error("client not configured; see 'gclient config'")
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +00001026
1027 if options.verbose:
1028 # Print out the .gclient file. This is longer than if we just printed the
1029 # client dict, but more legible, and it might contain helpful comments.
1030 print(client.ConfigContent())
1031 return client.RunOnDeps('export', args)
1032
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001033def DoHelp(options, args):
1034 """Handle the help subcommand giving help for another subcommand.
1035
1036 Raises:
1037 Error: if the command is unknown.
1038 """
maruel@chromium.orge3608df2009-11-10 20:22:57 +00001039 __pychecker__ = 'unusednames=options'
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001040 if len(args) == 1 and args[0] in COMMAND_USAGE_TEXT:
maruel@chromium.orgdf7a3132009-05-12 17:49:49 +00001041 print(COMMAND_USAGE_TEXT[args[0]])
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001042 else:
maruel@chromium.orge3608df2009-11-10 20:22:57 +00001043 raise gclient_utils.Error("unknown subcommand '%s'; see 'gclient help'" %
1044 args[0])
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001045
1046
kbr@google.comab318592009-09-04 00:54:55 +00001047def DoPack(options, args):
1048 """Handle the pack subcommand.
1049
1050 Raises:
1051 Error: if client isn't configured properly.
1052 """
1053 client = GClient.LoadCurrentConfig(options)
1054 if not client:
maruel@chromium.orge3608df2009-11-10 20:22:57 +00001055 raise gclient_utils.Error("client not configured; see 'gclient config'")
kbr@google.comab318592009-09-04 00:54:55 +00001056 if options.verbose:
1057 # Print out the .gclient file. This is longer than if we just printed the
1058 # client dict, but more legible, and it might contain helpful comments.
1059 print(client.ConfigContent())
kbr@google.comab318592009-09-04 00:54:55 +00001060 return client.RunOnDeps('pack', args)
1061
1062
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001063def DoStatus(options, args):
1064 """Handle the status subcommand.
1065
1066 Raises:
1067 Error: if client isn't configured properly.
1068 """
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001069 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001070 if not client:
maruel@chromium.orge3608df2009-11-10 20:22:57 +00001071 raise gclient_utils.Error("client not configured; see 'gclient config'")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001072 if options.verbose:
1073 # Print out the .gclient file. This is longer than if we just printed the
1074 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.orgdf7a3132009-05-12 17:49:49 +00001075 print(client.ConfigContent())
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001076 return client.RunOnDeps('status', args)
1077
1078
1079def DoUpdate(options, args):
1080 """Handle the update and sync subcommands.
1081
1082 Raises:
1083 Error: if client isn't configured properly.
1084 """
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001085 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001086
1087 if not client:
maruel@chromium.orge3608df2009-11-10 20:22:57 +00001088 raise gclient_utils.Error("client not configured; see 'gclient config'")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001089
1090 if not options.head:
1091 solutions = client.GetVar('solutions')
1092 if solutions:
1093 for s in solutions:
1094 if s.get('safesync_url', ''):
1095 # rip through revisions and make sure we're not over-riding
1096 # something that was explicitly passed
1097 has_key = False
1098 for r in options.revisions:
1099 if r.split('@')[0] == s['name']:
1100 has_key = True
1101 break
1102
1103 if not has_key:
1104 handle = urllib.urlopen(s['safesync_url'])
1105 rev = handle.read().strip()
1106 handle.close()
1107 if len(rev):
1108 options.revisions.append(s['name']+'@'+rev)
1109
1110 if options.verbose:
1111 # Print out the .gclient file. This is longer than if we just printed the
1112 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.orgdf7a3132009-05-12 17:49:49 +00001113 print(client.ConfigContent())
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001114 return client.RunOnDeps('update', args)
1115
1116
1117def DoDiff(options, args):
1118 """Handle the diff subcommand.
1119
1120 Raises:
1121 Error: if client isn't configured properly.
1122 """
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001123 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001124 if not client:
maruel@chromium.orge3608df2009-11-10 20:22:57 +00001125 raise gclient_utils.Error("client not configured; see 'gclient config'")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001126 if options.verbose:
1127 # Print out the .gclient file. This is longer than if we just printed the
1128 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.orgdf7a3132009-05-12 17:49:49 +00001129 print(client.ConfigContent())
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001130 return client.RunOnDeps('diff', args)
1131
1132
1133def DoRevert(options, args):
1134 """Handle the revert subcommand.
1135
1136 Raises:
1137 Error: if client isn't configured properly.
1138 """
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001139 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001140 if not client:
maruel@chromium.orge3608df2009-11-10 20:22:57 +00001141 raise gclient_utils.Error("client not configured; see 'gclient config'")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001142 return client.RunOnDeps('revert', args)
1143
1144
1145def DoRunHooks(options, args):
1146 """Handle the runhooks subcommand.
1147
1148 Raises:
1149 Error: if client isn't configured properly.
1150 """
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001151 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001152 if not client:
maruel@chromium.orge3608df2009-11-10 20:22:57 +00001153 raise gclient_utils.Error("client not configured; see 'gclient config'")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001154 if options.verbose:
1155 # Print out the .gclient file. This is longer than if we just printed the
1156 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.orgdf7a3132009-05-12 17:49:49 +00001157 print(client.ConfigContent())
maruel@chromium.org5df6a462009-08-28 18:52:26 +00001158 options.force = True
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001159 return client.RunOnDeps('runhooks', args)
1160
1161
1162def DoRevInfo(options, args):
1163 """Handle the revinfo subcommand.
1164
1165 Raises:
1166 Error: if client isn't configured properly.
1167 """
maruel@chromium.orge3608df2009-11-10 20:22:57 +00001168 __pychecker__ = 'unusednames=args'
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001169 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001170 if not client:
maruel@chromium.orge3608df2009-11-10 20:22:57 +00001171 raise gclient_utils.Error("client not configured; see 'gclient config'")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001172 client.PrintRevInfo()
1173
1174
1175gclient_command_map = {
1176 "cleanup": DoCleanup,
1177 "config": DoConfig,
1178 "diff": DoDiff,
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +00001179 "export": DoExport,
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001180 "help": DoHelp,
kbr@google.comab318592009-09-04 00:54:55 +00001181 "pack": DoPack,
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001182 "status": DoStatus,
1183 "sync": DoUpdate,
1184 "update": DoUpdate,
1185 "revert": DoRevert,
1186 "runhooks": DoRunHooks,
1187 "revinfo" : DoRevInfo,
1188}
1189
1190
1191def DispatchCommand(command, options, args, command_map=None):
1192 """Dispatches the appropriate subcommand based on command line arguments."""
1193 if command_map is None:
1194 command_map = gclient_command_map
1195
1196 if command in command_map:
1197 return command_map[command](options, args)
1198 else:
maruel@chromium.orge3608df2009-11-10 20:22:57 +00001199 raise gclient_utils.Error("unknown subcommand '%s'; see 'gclient help'" %
1200 command)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001201
1202
1203def Main(argv):
1204 """Parse command line arguments and dispatch command."""
1205
1206 option_parser = optparse.OptionParser(usage=DEFAULT_USAGE_TEXT,
1207 version=__version__)
1208 option_parser.disable_interspersed_args()
1209 option_parser.add_option("", "--force", action="store_true", default=False,
1210 help=("(update/sync only) force update even "
1211 "for modules which haven't changed"))
evan@chromium.org67820ef2009-07-27 17:23:00 +00001212 option_parser.add_option("", "--nohooks", action="store_true", default=False,
1213 help=("(update/sync/revert only) prevent the hooks from "
1214 "running"))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001215 option_parser.add_option("", "--revision", action="append", dest="revisions",
1216 metavar="REV", default=[],
1217 help=("(update/sync only) sync to a specific "
1218 "revision, can be used multiple times for "
1219 "each solution, e.g. --revision=src@123, "
1220 "--revision=internal@32"))
1221 option_parser.add_option("", "--deps", default=None, dest="deps_os",
1222 metavar="OS_LIST",
1223 help=("(update/sync only) sync deps for the "
1224 "specified (comma-separated) platform(s); "
1225 "'all' will sync all platforms"))
davemoore@chromium.org793796d2010-02-19 17:27:41 +00001226 option_parser.add_option("", "--reset", action="store_true", default=False,
1227 help=("(update/sync only) resets any local changes "
1228 "before updating (git only)"))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001229 option_parser.add_option("", "--spec", default=None,
1230 help=("(config only) create a gclient file "
1231 "containing the provided string"))
maruel@chromium.orga6220d12010-01-06 21:04:17 +00001232 option_parser.add_option("-v", "--verbose", action="count", default=0,
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001233 help="produce additional output for diagnostics")
maruel@chromium.org7753d242009-10-07 17:40:24 +00001234 option_parser.add_option("", "--manually_grab_svn_rev", action="store_true",
1235 default=False,
1236 help="Skip svn up whenever possible by requesting "
1237 "actual HEAD revision from the repository")
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001238 option_parser.add_option("", "--head", action="store_true", default=False,
1239 help=("skips any safesync_urls specified in "
1240 "configured solutions"))
ajwong@chromium.orgcdcee802009-06-23 15:30:42 +00001241 option_parser.add_option("", "--delete_unversioned_trees",
1242 action="store_true", default=False,
1243 help=("on update, delete any unexpected "
1244 "unversioned trees that are in the checkout"))
maruel@chromium.orge3da35f2010-03-09 21:40:45 +00001245 option_parser.add_option("", "--snapshot", action="store_true", default=False,
1246 help=("(revinfo only), create a snapshot file "
1247 "of the current version of all repositories"))
iposva@chromium.org8cf7a392010-04-07 17:20:26 +00001248 option_parser.add_option("", "--name",
1249 help="specify alternate relative solution path")
maruel@chromium.orge3da35f2010-03-09 21:40:45 +00001250 option_parser.add_option("", "--gclientfile", default=None,
1251 metavar="FILENAME",
1252 help=("specify an alternate .gclient file"))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001253
1254 if len(argv) < 2:
1255 # Users don't need to be told to use the 'help' command.
1256 option_parser.print_help()
1257 return 1
1258 # Add manual support for --version as first argument.
1259 if argv[1] == '--version':
1260 option_parser.print_version()
1261 return 0
1262
1263 # Add manual support for --help as first argument.
1264 if argv[1] == '--help':
1265 argv[1] = 'help'
1266
1267 command = argv[1]
1268 options, args = option_parser.parse_args(argv[2:])
1269
1270 if len(argv) < 3 and command == "help":
1271 option_parser.print_help()
1272 return 0
1273
maruel@chromium.orga6220d12010-01-06 21:04:17 +00001274 if options.verbose > 1:
maruel@chromium.org754960e2009-09-21 12:31:05 +00001275 logging.basicConfig(level=logging.DEBUG)
1276
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001277 # Files used for configuration and state saving.
1278 options.config_filename = os.environ.get("GCLIENT_FILE", ".gclient")
maruel@chromium.orge3da35f2010-03-09 21:40:45 +00001279 if options.gclientfile:
1280 options.config_filename = options.gclientfile
1281 options.entries_filename = options.config_filename + "_entries"
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001282 options.deps_file = "DEPS"
1283
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001284 options.platform = sys.platform
1285 return DispatchCommand(command, options, args)
1286
1287
1288if "__main__" == __name__:
1289 try:
1290 result = Main(sys.argv)
maruel@chromium.orge3608df2009-11-10 20:22:57 +00001291 except gclient_utils.Error, e:
maruel@chromium.orgdf7a3132009-05-12 17:49:49 +00001292 print >> sys.stderr, "Error: %s" % str(e)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001293 result = 1
1294 sys.exit(result)
1295
1296# vim: ts=2:sw=2:tw=80:et: