blob: 778e52f373a0655c6c35699fe9e56a31b773184e [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
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00006"""Meta checkout manager supporting both Subversion and GIT.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00007
8Files
9 .gclient : Current client configuration, written by 'config' command.
10 Format is a Python script defining 'solutions', a list whose
11 entries each are maps binding the strings "name" and "url"
12 to strings specifying the name and location of the client
13 module, as well as "custom_deps" to a map similar to the DEPS
14 file below.
15 .gclient_entries : A cache constructed by 'update' command. Format is a
16 Python script defining 'entries', a list of the names
17 of all modules in the client
18 <module>/DEPS : Python script defining var 'deps' as a map from each requisite
19 submodule name to a URL where it can be found (via one SCM)
20
21Hooks
22 .gclient and DEPS files may optionally contain a list named "hooks" to
23 allow custom actions to be performed based on files that have changed in the
evan@chromium.org67820ef2009-07-27 17:23:00 +000024 working copy as a result of a "sync"/"update" or "revert" operation. This
maruel@chromium.org0b6a0842010-06-15 14:34:19 +000025 can be prevented by using --nohooks (hooks run by default). Hooks can also
maruel@chromium.org5df6a462009-08-28 18:52:26 +000026 be forced to run with the "runhooks" operation. If "sync" is run with
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000027 --force, all known hooks will run regardless of the state of the working
28 copy.
29
30 Each item in a "hooks" list is a dict, containing these two keys:
31 "pattern" The associated value is a string containing a regular
32 expression. When a file whose pathname matches the expression
33 is checked out, updated, or reverted, the hook's "action" will
34 run.
35 "action" A list describing a command to run along with its arguments, if
36 any. An action command will run at most one time per gclient
37 invocation, regardless of how many files matched the pattern.
38 The action is executed in the same directory as the .gclient
39 file. If the first item in the list is the string "python",
40 the current Python interpreter (sys.executable) will be used
phajdan.jr@chromium.org71b40682009-07-31 23:40:09 +000041 to run the command. If the list contains string "$matching_files"
42 it will be removed from the list and the list will be extended
43 by the list of matching files.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000044
45 Example:
46 hooks = [
47 { "pattern": "\\.(gif|jpe?g|pr0n|png)$",
48 "action": ["python", "image_indexer.py", "--all"]},
49 ]
50"""
51
maruel@chromium.org9e5317a2010-08-13 20:35:11 +000052__version__ = "0.6"
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000053
maruel@chromium.org9e5317a2010-08-13 20:35:11 +000054import copy
maruel@chromium.org754960e2009-09-21 12:31:05 +000055import logging
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000056import optparse
57import os
maruel@chromium.org621939b2010-08-10 20:12:00 +000058import posixpath
msb@chromium.org2e38de72009-09-28 17:04:47 +000059import pprint
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000060import re
piman@chromium.org4b90e3a2010-07-01 20:28:26 +000061import subprocess
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000062import sys
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000063import urlparse
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000064import urllib
65
maruel@chromium.orgada4c652009-12-03 15:32:01 +000066import breakpad
67
maruel@chromium.org5f3eee32009-09-17 00:34:30 +000068import gclient_scm
69import gclient_utils
nasser@codeaurora.org1f7a3d12010-02-04 15:11:50 +000070from third_party.repo.progress import Progress
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000071
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000072
maruel@chromium.org1f7d1182010-05-17 18:17:38 +000073def attr(attr, data):
74 """Sets an attribute on a function."""
75 def hook(fn):
76 setattr(fn, attr, data)
77 return fn
78 return hook
maruel@chromium.orge3da35f2010-03-09 21:40:45 +000079
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000080
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000081## GClient implementation.
82
83
maruel@chromium.org116704f2010-06-11 17:34:38 +000084class GClientKeywords(object):
85 class FromImpl(object):
86 """Used to implement the From() syntax."""
87
88 def __init__(self, module_name, sub_target_name=None):
89 """module_name is the dep module we want to include from. It can also be
90 the name of a subdirectory to include from.
91
92 sub_target_name is an optional parameter if the module name in the other
93 DEPS file is different. E.g., you might want to map src/net to net."""
94 self.module_name = module_name
95 self.sub_target_name = sub_target_name
96
97 def __str__(self):
98 return 'From(%s, %s)' % (repr(self.module_name),
99 repr(self.sub_target_name))
100
maruel@chromium.org116704f2010-06-11 17:34:38 +0000101 class FileImpl(object):
102 """Used to implement the File('') syntax which lets you sync a single file
maruel@chromium.orge3216c62010-07-08 03:31:43 +0000103 from a SVN repo."""
maruel@chromium.org116704f2010-06-11 17:34:38 +0000104
105 def __init__(self, file_location):
106 self.file_location = file_location
107
108 def __str__(self):
109 return 'File("%s")' % self.file_location
110
111 def GetPath(self):
112 return os.path.split(self.file_location)[0]
113
114 def GetFilename(self):
115 rev_tokens = self.file_location.split('@')
116 return os.path.split(rev_tokens[0])[1]
117
118 def GetRevision(self):
119 rev_tokens = self.file_location.split('@')
120 if len(rev_tokens) > 1:
121 return rev_tokens[1]
122 return None
123
124 class VarImpl(object):
125 def __init__(self, custom_vars, local_scope):
126 self._custom_vars = custom_vars
127 self._local_scope = local_scope
128
129 def Lookup(self, var_name):
130 """Implements the Var syntax."""
131 if var_name in self._custom_vars:
132 return self._custom_vars[var_name]
133 elif var_name in self._local_scope.get("vars", {}):
134 return self._local_scope["vars"][var_name]
135 raise gclient_utils.Error("Var is not defined: %s" % var_name)
136
137
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000138class Dependency(GClientKeywords, gclient_utils.WorkItem):
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000139 """Object that represents a dependency checkout."""
maruel@chromium.org9eda4112010-06-11 18:56:10 +0000140 DEPS_FILE = 'DEPS'
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000141
maruel@chromium.org0d812442010-08-10 12:41:08 +0000142 def __init__(self, parent, name, url, safesync_url, custom_deps,
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000143 custom_vars, deps_file, should_process):
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000144 GClientKeywords.__init__(self)
145 self.parent = parent
146 self.name = name
147 self.url = url
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000148 self.parsed_url = None
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000149 # These 2 are only set in .gclient and not in DEPS files.
150 self.safesync_url = safesync_url
151 self.custom_vars = custom_vars or {}
152 self.custom_deps = custom_deps or {}
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000153 self.deps_hooks = []
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000154 self.dependencies = []
155 self.deps_file = deps_file or self.DEPS_FILE
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000156 # A cache of the files affected by the current operation, necessary for
157 # hooks.
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000158 self._file_list = []
maruel@chromium.org85c2a192010-07-22 21:14:43 +0000159 # If it is not set to True, the dependency wasn't processed for its child
160 # dependency, i.e. its DEPS wasn't read.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000161 self.deps_parsed = False
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000162 # This dependency should be processed, i.e. checked out
163 self.should_process = should_process
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000164 # This dependency has been processed, i.e. checked out
165 self.processed = False
166 # This dependency had its hook run
167 self.hooks_ran = False
maruel@chromium.org621939b2010-08-10 20:12:00 +0000168 # Required dependencies to run before running this one:
169 self.requirements = []
170 if self.parent and self.parent.name:
171 self.requirements.append(self.parent.name)
172 if isinstance(self.url, self.FromImpl):
173 self.requirements.append(self.url.module_name)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000174
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000175 # Sanity checks
176 if not self.name and self.parent:
177 raise gclient_utils.Error('Dependency without name')
178 if not isinstance(self.url,
179 (basestring, self.FromImpl, self.FileImpl, None.__class__)):
180 raise gclient_utils.Error('dependency url must be either a string, None, '
181 'File() or From() instead of %s' %
182 self.url.__class__.__name__)
183 if '/' in self.deps_file or '\\' in self.deps_file:
184 raise gclient_utils.Error('deps_file name must not be a path, just a '
185 'filename. %s' % self.deps_file)
186
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000187 def LateOverride(self, url):
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000188 """Resolves the parsed url from url.
189
190 Manages From() keyword accordingly. Do not touch self.parsed_url nor
191 self.url because it may called with other urls due to From()."""
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000192 assert self.parsed_url == None or not self.should_process, self.parsed_url
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000193 overriden_url = self.get_custom_deps(self.name, url)
194 if overriden_url != url:
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000195 logging.info('%s, %s was overriden to %s' % (self.name, url,
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000196 overriden_url))
197 return overriden_url
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000198 elif isinstance(url, self.FromImpl):
199 ref = [dep for dep in self.tree(True) if url.module_name == dep.name]
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000200 if not ref:
201 raise gclient_utils.Error('Failed to find one reference to %s. %s' % (
202 url.module_name, ref))
203 # It may happen that len(ref) > 1 but it's no big deal.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000204 ref = ref[0]
maruel@chromium.org98d05fa2010-07-22 21:58:01 +0000205 sub_target = url.sub_target_name or self.name
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000206 # Make sure the referenced dependency DEPS file is loaded and file the
207 # inner referenced dependency.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000208 ref.ParseDepsFile()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000209 found_dep = None
210 for d in ref.dependencies:
211 if d.name == sub_target:
212 found_dep = d
213 break
214 if not found_dep:
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000215 raise gclient_utils.Error(
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000216 'Couldn\'t find %s in %s, referenced by %s\n%s' % (
217 sub_target, ref.name, self.name, str(self.root_parent())))
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000218 # Call LateOverride() again.
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000219 parsed_url = found_dep.LateOverride(found_dep.url)
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000220 logging.info('%s, %s to %s' % (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000221 return parsed_url
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000222 elif isinstance(url, basestring):
223 parsed_url = urlparse.urlparse(url)
224 if not parsed_url[0]:
225 # A relative url. Fetch the real base.
226 path = parsed_url[2]
227 if not path.startswith('/'):
228 raise gclient_utils.Error(
229 'relative DEPS entry \'%s\' must begin with a slash' % url)
230 # Create a scm just to query the full url.
231 parent_url = self.parent.parsed_url
232 if isinstance(parent_url, self.FileImpl):
233 parent_url = parent_url.file_location
234 scm = gclient_scm.CreateSCM(parent_url, self.root_dir(), None)
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000235 parsed_url = scm.FullUrlForRelativeUrl(url)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000236 else:
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000237 parsed_url = url
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000238 logging.info('%s, %s -> %s' % (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000239 return parsed_url
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000240 elif isinstance(url, self.FileImpl):
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000241 parsed_url = url
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000242 logging.info('%s, %s -> %s (File)' % (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000243 return parsed_url
244 elif url is None:
245 return None
246 else:
247 raise gclient_utils.Error('Unkown url type')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000248
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000249 def ParseDepsFile(self):
maruel@chromium.org271375b2010-06-23 19:17:38 +0000250 """Parses the DEPS file for this dependency."""
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000251 assert self.processed == True
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000252 if self.deps_parsed:
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000253 logging.debug('%s was already parsed' % self.name)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000254 return
maruel@chromium.org271375b2010-06-23 19:17:38 +0000255 self.deps_parsed = True
256 filepath = os.path.join(self.root_dir(), self.name, self.deps_file)
257 if not os.path.isfile(filepath):
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000258 logging.info('%s: No DEPS file found at %s' % (self.name, filepath))
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000259 return
maruel@chromium.org271375b2010-06-23 19:17:38 +0000260 deps_content = gclient_utils.FileRead(filepath)
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000261 logging.debug(deps_content)
maruel@chromium.org0d425922010-06-21 19:22:24 +0000262
maruel@chromium.org271375b2010-06-23 19:17:38 +0000263 # Eval the content.
264 # One thing is unintuitive, vars= {} must happen before Var() use.
265 local_scope = {}
266 var = self.VarImpl(self.custom_vars, local_scope)
267 global_scope = {
268 'File': self.FileImpl,
269 'From': self.FromImpl,
270 'Var': var.Lookup,
271 'deps_os': {},
272 }
maruel@chromium.org5990f9d2010-07-07 18:02:58 +0000273 try:
274 exec(deps_content, global_scope, local_scope)
275 except SyntaxError, e:
276 gclient_utils.SyntaxErrorToError(filepath, e)
maruel@chromium.org271375b2010-06-23 19:17:38 +0000277 deps = local_scope.get('deps', {})
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000278 # load os specific dependencies if defined. these dependencies may
279 # override or extend the values defined by the 'deps' member.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000280 if 'deps_os' in local_scope:
281 for deps_os_key in self.enforced_os():
282 os_deps = local_scope['deps_os'].get(deps_os_key, {})
283 if len(self.enforced_os()) > 1:
284 # Ignore any conflict when including deps for more than one
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000285 # platform, so we collect the broadest set of dependencies available.
286 # We may end up with the wrong revision of something for our
287 # platform, but this is the best we can do.
288 deps.update([x for x in os_deps.items() if not x[0] in deps])
289 else:
290 deps.update(os_deps)
291
maruel@chromium.org271375b2010-06-23 19:17:38 +0000292 self.deps_hooks.extend(local_scope.get('hooks', []))
293
294 # If a line is in custom_deps, but not in the solution, we want to append
295 # this line to the solution.
296 for d in self.custom_deps:
297 if d not in deps:
298 deps[d] = self.custom_deps[d]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000299
300 # If use_relative_paths is set in the DEPS file, regenerate
301 # the dictionary using paths relative to the directory containing
302 # the DEPS file.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000303 use_relative_paths = local_scope.get('use_relative_paths', False)
304 if use_relative_paths:
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000305 rel_deps = {}
306 for d, url in deps.items():
307 # normpath is required to allow DEPS to use .. in their
308 # dependency local path.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000309 rel_deps[os.path.normpath(os.path.join(self.name, d))] = url
310 deps = rel_deps
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000311
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000312 # Convert the deps into real Dependency.
313 for name, url in deps.iteritems():
314 if name in [s.name for s in self.dependencies]:
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000315 raise gclient_utils.Error(
316 'The same name "%s" appears multiple times in the deps section' %
317 name)
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000318 should_process = self.recursion_limit() > 0 and self.should_process
319 if should_process:
320 tree = dict((d.name, d) for d in self.tree(False))
321 if name in tree:
322 if url == tree[name].url:
323 logging.info('Won\'t process duplicate dependency %s' % tree[name])
324 # In theory we could keep it as a shadow of the other one. In
325 # practice, simply ignore it.
326 #should_process = False
327 continue
328 else:
329 raise gclient_utils.Error(
330 'Dependency %s specified more than once:\n %s\nvs\n %s' %
331 (name, tree[name].hierarchy(), self.hierarchy()))
maruel@chromium.org0d812442010-08-10 12:41:08 +0000332 self.dependencies.append(Dependency(self, name, url, None, None, None,
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000333 None, should_process))
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000334 logging.debug('Loaded: %s' % str(self))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000335
maruel@chromium.org049bced2010-08-12 13:37:20 +0000336 def run(self, options, revision_overrides, command, args, work_queue):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000337 """Runs 'command' before parsing the DEPS in case it's a initial checkout
338 or a revert."""
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000339 assert self._file_list == []
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000340 if not self.should_process:
341 return
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000342 # When running runhooks, there's no need to consult the SCM.
343 # All known hooks are expected to run unconditionally regardless of working
344 # copy state, so skip the SCM status check.
345 run_scm = command not in ('runhooks', None)
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000346 self.parsed_url = self.LateOverride(self.url)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000347 if run_scm and self.parsed_url:
348 if isinstance(self.parsed_url, self.FileImpl):
349 # Special support for single-file checkout.
350 if not command in (None, 'cleanup', 'diff', 'pack', 'status'):
351 options.revision = self.parsed_url.GetRevision()
352 scm = gclient_scm.SVNWrapper(self.parsed_url.GetPath(),
353 self.root_dir(),
354 self.name)
355 scm.RunCommand('updatesingle', options,
356 args + [self.parsed_url.GetFilename()],
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000357 self._file_list)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000358 else:
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000359 # Create a shallow copy to mutate revision.
360 options = copy.copy(options)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000361 options.revision = revision_overrides.get(self.name)
362 scm = gclient_scm.CreateSCM(self.parsed_url, self.root_dir(), self.name)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000363 scm.RunCommand(command, options, args, self._file_list)
364 self._file_list = [os.path.join(self.name, f.strip())
365 for f in self._file_list]
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000366 self.processed = True
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000367 if self.recursion_limit() > 0:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000368 # Then we can parse the DEPS file.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000369 self.ParseDepsFile()
maruel@chromium.org621939b2010-08-10 20:12:00 +0000370 # Adjust the implicit dependency requirement; e.g. if a DEPS file contains
371 # both src/foo and src/foo/bar, src/foo/bar is implicitly dependent of
maruel@chromium.org049bced2010-08-12 13:37:20 +0000372 # src/foo. Yes, it's O(n^2)... It's important to do that before
373 # enqueueing them.
maruel@chromium.org621939b2010-08-10 20:12:00 +0000374 for s in self.dependencies:
375 for s2 in self.dependencies:
376 if s is s2:
377 continue
378 if s.name.startswith(posixpath.join(s2.name, '')):
379 s.requirements.append(s2.name)
380
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000381 # Parse the dependencies of this dependency.
382 for s in self.dependencies:
maruel@chromium.org049bced2010-08-12 13:37:20 +0000383 work_queue.enqueue(s)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000384
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000385 def RunHooksRecursively(self, options):
maruel@chromium.org049bced2010-08-12 13:37:20 +0000386 """Evaluates all hooks, running actions as needed. run()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000387 must have been called before to load the DEPS."""
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000388 assert self.hooks_ran == False
389 if not self.should_process or self.recursion_limit() <= 0:
390 # Don't run the hook when it is above recursion_limit.
391 return
maruel@chromium.orgdc7445d2010-07-09 21:05:29 +0000392 # If "--force" was specified, run all hooks regardless of what files have
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000393 # changed.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000394 if self.deps_hooks:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000395 # TODO(maruel): If the user is using git or git-svn, then we don't know
396 # what files have changed so we always run all hooks. It'd be nice to fix
397 # that.
398 if (options.force or
399 isinstance(self.parsed_url, self.FileImpl) or
400 gclient_scm.GetScmName(self.parsed_url) in ('git', None) or
401 os.path.isdir(os.path.join(self.root_dir(), self.name, '.git'))):
402 for hook_dict in self.deps_hooks:
403 self._RunHookAction(hook_dict, [])
404 else:
405 # TODO(phajdan.jr): We should know exactly when the paths are absolute.
406 # Convert all absolute paths to relative.
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000407 file_list = self.file_list()
408 for i in range(len(file_list)):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000409 # It depends on the command being executed (like runhooks vs sync).
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000410 if not os.path.isabs(file_list[i]):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000411 continue
maruel@chromium.orgdc7445d2010-07-09 21:05:29 +0000412
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000413 prefix = os.path.commonprefix([self.root_dir().lower(),
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000414 file_list[i].lower()])
415 file_list[i] = file_list[i][len(prefix):]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000416
417 # Strip any leading path separators.
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000418 while (file_list[i].startswith('\\') or
419 file_list[i].startswith('/')):
420 file_list[i] = file_list[i][1:]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000421
422 # Run hooks on the basis of whether the files from the gclient operation
423 # match each hook's pattern.
424 for hook_dict in self.deps_hooks:
425 pattern = re.compile(hook_dict['pattern'])
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000426 matching_file_list = [f for f in file_list if pattern.search(f)]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000427 if matching_file_list:
428 self._RunHookAction(hook_dict, matching_file_list)
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000429 for s in self.dependencies:
430 s.RunHooksRecursively(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000431
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000432 def _RunHookAction(self, hook_dict, matching_file_list):
433 """Runs the action from a single hook."""
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000434 # A single DEPS file can specify multiple hooks so this function can be
435 # called multiple times on a single Dependency.
436 #assert self.hooks_ran == False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000437 self.hooks_ran = True
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000438 logging.debug(hook_dict)
439 logging.debug(matching_file_list)
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000440 command = hook_dict['action'][:]
441 if command[0] == 'python':
442 # If the hook specified "python" as the first item, the action is a
443 # Python script. Run it by starting a new copy of the same
444 # interpreter.
445 command[0] = sys.executable
446
447 if '$matching_files' in command:
448 splice_index = command.index('$matching_files')
449 command[splice_index:splice_index + 1] = matching_file_list
450
451 # Use a discrete exit status code of 2 to indicate that a hook action
452 # failed. Users of this script may wish to treat hook action failures
453 # differently from VC failures.
maruel@chromium.org2b9aa8e2010-08-25 20:01:42 +0000454 return gclient_utils.SubprocessCall(command, cwd=self.root_dir(),
455 fail_status=2)
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000456
maruel@chromium.org271375b2010-06-23 19:17:38 +0000457 def root_dir(self):
458 return self.parent.root_dir()
459
460 def enforced_os(self):
461 return self.parent.enforced_os()
462
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000463 def recursion_limit(self):
464 return self.parent.recursion_limit() - 1
465
maruel@chromium.org0d812442010-08-10 12:41:08 +0000466 def tree(self, include_all):
467 return self.parent.tree(include_all)
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000468
maruel@chromium.org0d812442010-08-10 12:41:08 +0000469 def subtree(self, include_all):
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000470 """Breadth first"""
maruel@chromium.orgc57e4f22010-07-22 21:37:46 +0000471 result = []
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000472 for d in self.dependencies:
473 if d.should_process or include_all:
maruel@chromium.org044f4e32010-07-22 21:59:57 +0000474 result.append(d)
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000475 for d in self.dependencies:
476 result.extend(d.subtree(include_all))
maruel@chromium.orgc57e4f22010-07-22 21:37:46 +0000477 return result
478
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000479 def get_custom_deps(self, name, url):
480 """Returns a custom deps if applicable."""
481 if self.parent:
482 url = self.parent.get_custom_deps(name, url)
483 # None is a valid return value to disable a dependency.
484 return self.custom_deps.get(name, url)
485
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000486 def file_list(self):
487 result = self._file_list[:]
488 for d in self.dependencies:
489 result.extend(d.file_list())
490 return result
491
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000492 def __str__(self):
493 out = []
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000494 for i in ('name', 'url', 'parsed_url', 'safesync_url', 'custom_deps',
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000495 'custom_vars', 'deps_hooks', '_file_list', 'should_process',
496 'processed', 'hooks_ran', 'deps_parsed', 'requirements'):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000497 # 'deps_file'
498 if self.__dict__[i]:
499 out.append('%s: %s' % (i, self.__dict__[i]))
500
501 for d in self.dependencies:
502 out.extend([' ' + x for x in str(d).splitlines()])
503 out.append('')
504 return '\n'.join(out)
505
506 def __repr__(self):
507 return '%s: %s' % (self.name, self.url)
508
maruel@chromium.orgbffb9042010-07-22 20:59:36 +0000509 def hierarchy(self):
maruel@chromium.orgbc2d2f92010-07-22 21:26:48 +0000510 """Returns a human-readable hierarchical reference to a Dependency."""
maruel@chromium.orgbffb9042010-07-22 20:59:36 +0000511 out = '%s(%s)' % (self.name, self.url)
512 i = self.parent
513 while i and i.name:
514 out = '%s(%s) -> %s' % (i.name, i.url, out)
515 i = i.parent
516 return out
517
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000518 def root_parent(self):
519 """Returns the root object, normally a GClient object."""
520 d = self
521 while d.parent:
522 d = d.parent
523 return d
524
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000525
526class GClient(Dependency):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000527 """Object that represent a gclient checkout. A tree of Dependency(), one per
528 solution or DEPS entry."""
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000529
530 DEPS_OS_CHOICES = {
531 "win32": "win",
532 "win": "win",
533 "cygwin": "win",
534 "darwin": "mac",
535 "mac": "mac",
536 "unix": "unix",
537 "linux": "unix",
538 "linux2": "unix",
539 }
540
541 DEFAULT_CLIENT_FILE_TEXT = ("""\
542solutions = [
543 { "name" : "%(solution_name)s",
544 "url" : "%(solution_url)s",
545 "custom_deps" : {
546 },
maruel@chromium.org73e21142010-07-05 13:32:01 +0000547 "safesync_url": "%(safesync_url)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000548 },
549]
550""")
551
552 DEFAULT_SNAPSHOT_SOLUTION_TEXT = ("""\
553 { "name" : "%(solution_name)s",
554 "url" : "%(solution_url)s",
555 "custom_deps" : {
maruel@chromium.org73e21142010-07-05 13:32:01 +0000556%(solution_deps)s },
557 "safesync_url": "%(safesync_url)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000558 },
559""")
560
561 DEFAULT_SNAPSHOT_FILE_TEXT = ("""\
562# Snapshot generated with gclient revinfo --snapshot
563solutions = [
maruel@chromium.org73e21142010-07-05 13:32:01 +0000564%(solution_list)s]
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000565""")
566
567 def __init__(self, root_dir, options):
maruel@chromium.org0d812442010-08-10 12:41:08 +0000568 # Do not change previous behavior. Only solution level and immediate DEPS
569 # are processed.
570 self._recursion_limit = 2
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000571 Dependency.__init__(self, None, None, None, None, None, None, None, True)
maruel@chromium.org0d425922010-06-21 19:22:24 +0000572 self._options = options
maruel@chromium.org271375b2010-06-23 19:17:38 +0000573 if options.deps_os:
574 enforced_os = options.deps_os.split(',')
575 else:
576 enforced_os = [self.DEPS_OS_CHOICES.get(sys.platform, 'unix')]
577 if 'all' in enforced_os:
578 enforced_os = self.DEPS_OS_CHOICES.itervalues()
579 self._enforced_os = list(set(enforced_os))
580 self._root_dir = root_dir
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000581 self.config_content = None
582
583 def SetConfig(self, content):
584 assert self.dependencies == []
585 config_dict = {}
586 self.config_content = content
587 try:
588 exec(content, config_dict)
589 except SyntaxError, e:
maruel@chromium.org5990f9d2010-07-07 18:02:58 +0000590 gclient_utils.SyntaxErrorToError('.gclient', e)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000591 for s in config_dict.get('solutions', []):
maruel@chromium.org81843b82010-06-28 16:49:26 +0000592 try:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000593 tree = dict((d.name, d) for d in self.tree(False))
594 if s['name'] in tree:
595 raise gclient_utils.Error(
596 'Dependency %s specified more than once in .gclient' % s['name'])
maruel@chromium.org81843b82010-06-28 16:49:26 +0000597 self.dependencies.append(Dependency(
598 self, s['name'], s['url'],
599 s.get('safesync_url', None),
600 s.get('custom_deps', {}),
maruel@chromium.org0d812442010-08-10 12:41:08 +0000601 s.get('custom_vars', {}),
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000602 None,
603 True))
maruel@chromium.org81843b82010-06-28 16:49:26 +0000604 except KeyError:
605 raise gclient_utils.Error('Invalid .gclient file. Solution is '
606 'incomplete: %s' % s)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000607 # .gclient can have hooks.
608 self.deps_hooks = config_dict.get('hooks', [])
maruel@chromium.org049bced2010-08-12 13:37:20 +0000609 self.deps_parsed = True
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000610
611 def SaveConfig(self):
612 gclient_utils.FileWrite(os.path.join(self.root_dir(),
613 self._options.config_filename),
614 self.config_content)
615
616 @staticmethod
617 def LoadCurrentConfig(options):
618 """Searches for and loads a .gclient file relative to the current working
619 dir. Returns a GClient object."""
620 path = gclient_utils.FindGclientRoot(os.getcwd(), options.config_filename)
621 if not path:
622 return None
623 client = GClient(path, options)
624 client.SetConfig(gclient_utils.FileRead(
625 os.path.join(path, options.config_filename)))
626 return client
627
628 def SetDefaultConfig(self, solution_name, solution_url, safesync_url):
629 self.SetConfig(self.DEFAULT_CLIENT_FILE_TEXT % {
630 'solution_name': solution_name,
631 'solution_url': solution_url,
632 'safesync_url' : safesync_url,
633 })
634
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000635 def _SaveEntries(self):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000636 """Creates a .gclient_entries file to record the list of unique checkouts.
637
638 The .gclient_entries file lives in the same directory as .gclient.
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000639 """
640 # Sometimes pprint.pformat will use {', sometimes it'll use { ' ... It
641 # makes testing a bit too fun.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000642 result = 'entries = {\n'
643 for entry in self.tree(False):
644 # Skip over File() dependencies as we can't version them.
645 if not isinstance(entry.parsed_url, self.FileImpl):
646 result += ' %s: %s,\n' % (pprint.pformat(entry.name),
647 pprint.pformat(entry.parsed_url))
648 result += '}\n'
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000649 file_path = os.path.join(self.root_dir(), self._options.entries_filename)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000650 logging.info(result)
651 gclient_utils.FileWrite(file_path, result)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000652
653 def _ReadEntries(self):
654 """Read the .gclient_entries file for the given client.
655
656 Returns:
657 A sequence of solution names, which will be empty if there is the
658 entries file hasn't been created yet.
659 """
660 scope = {}
661 filename = os.path.join(self.root_dir(), self._options.entries_filename)
662 if not os.path.exists(filename):
maruel@chromium.org73e21142010-07-05 13:32:01 +0000663 return {}
maruel@chromium.org5990f9d2010-07-07 18:02:58 +0000664 try:
665 exec(gclient_utils.FileRead(filename), scope)
666 except SyntaxError, e:
667 gclient_utils.SyntaxErrorToError(filename, e)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000668 return scope['entries']
669
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000670 def _EnforceRevisions(self):
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000671 """Checks for revision overrides."""
672 revision_overrides = {}
maruel@chromium.org307d1792010-05-31 20:03:13 +0000673 if self._options.head:
674 return revision_overrides
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000675 for s in self.dependencies:
676 if not s.safesync_url:
maruel@chromium.org307d1792010-05-31 20:03:13 +0000677 continue
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000678 handle = urllib.urlopen(s.safesync_url)
maruel@chromium.org307d1792010-05-31 20:03:13 +0000679 rev = handle.read().strip()
680 handle.close()
681 if len(rev):
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000682 self._options.revisions.append('%s@%s' % (s.name, rev))
maruel@chromium.org307d1792010-05-31 20:03:13 +0000683 if not self._options.revisions:
684 return revision_overrides
685 # --revision will take over safesync_url.
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000686 solutions_names = [s.name for s in self.dependencies]
maruel@chromium.org307d1792010-05-31 20:03:13 +0000687 index = 0
688 for revision in self._options.revisions:
689 if not '@' in revision:
690 # Support for --revision 123
691 revision = '%s@%s' % (solutions_names[index], revision)
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000692 sol, rev = revision.split('@', 1)
maruel@chromium.org307d1792010-05-31 20:03:13 +0000693 if not sol in solutions_names:
694 #raise gclient_utils.Error('%s is not a valid solution.' % sol)
695 print >> sys.stderr, ('Please fix your script, having invalid '
696 '--revision flags will soon considered an error.')
697 else:
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000698 revision_overrides[sol] = rev
maruel@chromium.org307d1792010-05-31 20:03:13 +0000699 index += 1
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000700 return revision_overrides
701
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000702 def RunOnDeps(self, command, args):
703 """Runs a command on each dependency in a client and its dependencies.
704
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000705 Args:
706 command: The command to use (e.g., 'status' or 'diff')
707 args: list of str - extra arguments to add to the command line.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000708 """
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000709 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +0000710 raise gclient_utils.Error('No solution specified')
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000711 revision_overrides = self._EnforceRevisions()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000712 pm = None
tony@chromium.orgd2e92562010-04-27 01:55:18 +0000713 if command == 'update' and not self._options.verbose:
maruel@chromium.org049bced2010-08-12 13:37:20 +0000714 pm = Progress('Syncing projects', 1)
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000715 work_queue = gclient_utils.ExecutionQueue(self._options.jobs, pm)
maruel@chromium.org049bced2010-08-12 13:37:20 +0000716 for s in self.dependencies:
717 work_queue.enqueue(s)
718 work_queue.flush(self._options, revision_overrides, command, args,
719 work_queue)
piman@chromium.org6f363722010-04-27 00:41:09 +0000720
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000721 # Once all the dependencies have been processed, it's now safe to run the
722 # hooks.
723 if not self._options.nohooks:
724 self.RunHooksRecursively(self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000725
726 if command == 'update':
ajwong@chromium.orgcdcee802009-06-23 15:30:42 +0000727 # Notify the user if there is an orphaned entry in their working copy.
728 # Only delete the directory if there are no changes in it, and
729 # delete_unversioned_trees is set to true.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000730 entries = [i.name for i in self.tree(False)]
731 for entry, prev_url in self._ReadEntries().iteritems():
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000732 # Fix path separator on Windows.
733 entry_fixed = entry.replace('/', os.path.sep)
maruel@chromium.org75a59272010-06-11 22:34:03 +0000734 e_dir = os.path.join(self.root_dir(), entry_fixed)
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000735 # Use entry and not entry_fixed there.
maruel@chromium.org0329e672009-05-13 18:41:04 +0000736 if entry not in entries and os.path.exists(e_dir):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000737 file_list = []
738 scm = gclient_scm.CreateSCM(prev_url, self.root_dir(), entry_fixed)
739 scm.status(self._options, [], file_list)
740 modified_files = file_list != []
msb@chromium.org83017012009-09-28 18:52:12 +0000741 if not self._options.delete_unversioned_trees or modified_files:
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000742 # There are modified files in this entry. Keep warning until
743 # removed.
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000744 print(('\nWARNING: \'%s\' is no longer part of this client. '
745 'It is recommended that you manually remove it.\n') %
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000746 entry_fixed)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000747 else:
748 # Delete the entry
maruel@chromium.org73e21142010-07-05 13:32:01 +0000749 print('\n________ deleting \'%s\' in \'%s\'' % (
750 entry_fixed, self.root_dir()))
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000751 gclient_utils.RemoveDirectory(e_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000752 # record the current list of entries for next time
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000753 self._SaveEntries()
maruel@chromium.org17cdf762010-05-28 17:30:52 +0000754 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000755
756 def PrintRevInfo(self):
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000757 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +0000758 raise gclient_utils.Error('No solution specified')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000759 # Load all the settings.
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000760 work_queue = gclient_utils.ExecutionQueue(self._options.jobs, None)
maruel@chromium.org049bced2010-08-12 13:37:20 +0000761 for s in self.dependencies:
762 work_queue.enqueue(s)
763 work_queue.flush(self._options, {}, None, [], work_queue)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000764
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000765 def GetURLAndRev(dep):
766 """Returns the revision-qualified SCM url for a Dependency."""
767 if dep.parsed_url is None:
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +0000768 return None
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000769 if isinstance(dep.parsed_url, self.FileImpl):
770 original_url = dep.parsed_url.file_location
771 else:
772 original_url = dep.parsed_url
nasser@codeaurora.org5d63eb82010-03-24 23:22:09 +0000773 url, _ = gclient_utils.SplitUrlRevision(original_url)
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000774 scm = gclient_scm.CreateSCM(original_url, self.root_dir(), dep.name)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000775 if not os.path.isdir(scm.checkout_path):
776 return None
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +0000777 return '%s@%s' % (url, scm.revinfo(self._options, [], None))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000778
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +0000779 if self._options.snapshot:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000780 new_gclient = ''
781 # First level at .gclient
782 for d in self.dependencies:
783 entries = {}
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000784 def GrabDeps(dep):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000785 """Recursively grab dependencies."""
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000786 for d in dep.dependencies:
787 entries[d.name] = GetURLAndRev(d)
788 GrabDeps(d)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000789 GrabDeps(d)
790 custom_deps = []
791 for k in sorted(entries.keys()):
792 if entries[k]:
793 # Quotes aren't escaped...
794 custom_deps.append(' \"%s\": \'%s\',\n' % (k, entries[k]))
795 else:
796 custom_deps.append(' \"%s\": None,\n' % k)
797 new_gclient += self.DEFAULT_SNAPSHOT_SOLUTION_TEXT % {
798 'solution_name': d.name,
799 'solution_url': d.url,
800 'safesync_url' : d.safesync_url or '',
801 'solution_deps': ''.join(custom_deps),
802 }
803 # Print the snapshot configuration file
804 print(self.DEFAULT_SNAPSHOT_FILE_TEXT % {'solution_list': new_gclient})
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +0000805 else:
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +0000806 entries = {}
807 for d in self.tree(False):
808 if self._options.actual:
809 entries[d.name] = GetURLAndRev(d)
810 else:
811 entries[d.name] = d.parsed_url
812 keys = sorted(entries.keys())
813 for x in keys:
maruel@chromium.orgce464892010-08-12 17:12:18 +0000814 print('%s: %s' % (x, entries[x]))
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000815 logging.info(str(self))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000816
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000817 def ParseDepsFile(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000818 """No DEPS to parse for a .gclient file."""
maruel@chromium.org049bced2010-08-12 13:37:20 +0000819 raise gclient_utils.Error('Internal error')
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000820
maruel@chromium.org75a59272010-06-11 22:34:03 +0000821 def root_dir(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000822 """Root directory of gclient checkout."""
maruel@chromium.org75a59272010-06-11 22:34:03 +0000823 return self._root_dir
824
maruel@chromium.org271375b2010-06-23 19:17:38 +0000825 def enforced_os(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000826 """What deps_os entries that are to be parsed."""
maruel@chromium.org271375b2010-06-23 19:17:38 +0000827 return self._enforced_os
828
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000829 def recursion_limit(self):
830 """How recursive can each dependencies in DEPS file can load DEPS file."""
831 return self._recursion_limit
832
maruel@chromium.org0d812442010-08-10 12:41:08 +0000833 def tree(self, include_all):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000834 """Returns a flat list of all the dependencies."""
maruel@chromium.org0d812442010-08-10 12:41:08 +0000835 return self.subtree(include_all)
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000836
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000837
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000838#### gclient commands.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000839
840
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000841def CMDcleanup(parser, args):
842 """Cleans up all working copies.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +0000843
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000844Mostly svn-specific. Simply runs 'svn cleanup' for each module.
maruel@chromium.org79692d62010-05-14 18:57:13 +0000845"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000846 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
847 help='override deps for the specified (comma-separated) '
848 'platform(s); \'all\' will process all deps_os '
849 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000850 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +0000851 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000852 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000853 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000854 if options.verbose:
855 # Print out the .gclient file. This is longer than if we just printed the
856 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +0000857 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000858 return client.RunOnDeps('cleanup', args)
859
860
piman@chromium.org4b90e3a2010-07-01 20:28:26 +0000861@attr('usage', '[command] [args ...]')
862def CMDrecurse(parser, args):
863 """Operates on all the entries.
864
865 Runs a shell command on all entries.
866 """
867 # Stop parsing at the first non-arg so that these go through to the command
868 parser.disable_interspersed_args()
869 parser.add_option('-s', '--scm', action='append', default=[],
870 help='choose scm types to operate upon')
871 options, args = parser.parse_args(args)
872 root, entries = gclient_utils.GetGClientRootAndEntries()
873 scm_set = set()
874 for scm in options.scm:
875 scm_set.update(scm.split(','))
876
877 # Pass in the SCM type as an env variable
878 env = os.environ.copy()
879
880 for path, url in entries.iteritems():
881 scm = gclient_scm.GetScmName(url)
882 if scm_set and scm not in scm_set:
883 continue
maruel@chromium.org2b9aa8e2010-08-25 20:01:42 +0000884 cwd = os.path.normpath(os.path.join(root, path))
piman@chromium.org4b90e3a2010-07-01 20:28:26 +0000885 env['GCLIENT_SCM'] = scm
886 env['GCLIENT_URL'] = url
maruel@chromium.org2b9aa8e2010-08-25 20:01:42 +0000887 subprocess.Popen(args, cwd=cwd, env=env).communicate()
piman@chromium.org4b90e3a2010-07-01 20:28:26 +0000888
889
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000890@attr('usage', '[url] [safesync url]')
891def CMDconfig(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +0000892 """Create a .gclient file in the current directory.
893
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000894This specifies the configuration for further commands. After update/sync,
maruel@chromium.org79692d62010-05-14 18:57:13 +0000895top-level DEPS files in each module are read to determine dependent
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000896modules to operate on as well. If optional [url] parameter is
maruel@chromium.org79692d62010-05-14 18:57:13 +0000897provided, then configuration is read from a specified Subversion server
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000898URL.
maruel@chromium.org79692d62010-05-14 18:57:13 +0000899"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000900 parser.add_option('--spec',
901 help='create a gclient file containing the provided '
902 'string. Due to Cygwin/Python brokenness, it '
903 'probably can\'t contain any newlines.')
904 parser.add_option('--name',
905 help='overrides the default name for the solution')
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000906 (options, args) = parser.parse_args(args)
maruel@chromium.org5fc2a332010-05-26 19:37:15 +0000907 if ((options.spec and args) or len(args) > 2 or
908 (not options.spec and not args)):
909 parser.error('Inconsistent arguments. Use either --spec or one or 2 args')
910
maruel@chromium.org0329e672009-05-13 18:41:04 +0000911 if os.path.exists(options.config_filename):
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000912 raise gclient_utils.Error('%s file already exists in the current directory'
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000913 % options.config_filename)
maruel@chromium.org2806acc2009-05-15 12:33:34 +0000914 client = GClient('.', options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000915 if options.spec:
916 client.SetConfig(options.spec)
917 else:
maruel@chromium.org1ab7ffc2009-06-03 17:21:37 +0000918 base_url = args[0].rstrip('/')
iposva@chromium.org8cf7a392010-04-07 17:20:26 +0000919 if not options.name:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000920 name = base_url.split('/')[-1]
iposva@chromium.org8cf7a392010-04-07 17:20:26 +0000921 else:
922 # specify an alternate relpath for the given URL.
923 name = options.name
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000924 safesync_url = ''
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000925 if len(args) > 1:
926 safesync_url = args[1]
927 client.SetDefaultConfig(name, base_url, safesync_url)
928 client.SaveConfig()
maruel@chromium.org79692d62010-05-14 18:57:13 +0000929 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000930
931
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000932def CMDexport(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +0000933 """Wrapper for svn export for all managed directories."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000934 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
935 help='override deps for the specified (comma-separated) '
936 'platform(s); \'all\' will process all deps_os '
937 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000938 (options, args) = parser.parse_args(args)
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +0000939 if len(args) != 1:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000940 raise gclient_utils.Error('Need directory name')
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +0000941 client = GClient.LoadCurrentConfig(options)
942
943 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000944 raise gclient_utils.Error('client not configured; see \'gclient config\'')
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +0000945
946 if options.verbose:
947 # Print out the .gclient file. This is longer than if we just printed the
948 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +0000949 print(client.config_content)
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +0000950 return client.RunOnDeps('export', args)
951
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000952
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000953@attr('epilog', """Example:
954 gclient pack > patch.txt
955 generate simple patch for configured client and dependences
956""")
957def CMDpack(parser, args):
maruel@chromium.org79692d62010-05-14 18:57:13 +0000958 """Generate a patch which can be applied at the root of the tree.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +0000959
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000960Internally, runs 'svn diff'/'git diff' on each checked out module and
maruel@chromium.org79692d62010-05-14 18:57:13 +0000961dependencies, and performs minimal postprocessing of the output. The
962resulting patch is printed to stdout and can be applied to a freshly
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000963checked out tree via 'patch -p0 < patchfile'.
maruel@chromium.org79692d62010-05-14 18:57:13 +0000964"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000965 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
966 help='override deps for the specified (comma-separated) '
967 'platform(s); \'all\' will process all deps_os '
968 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000969 (options, args) = parser.parse_args(args)
kbr@google.comab318592009-09-04 00:54:55 +0000970 client = GClient.LoadCurrentConfig(options)
971 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000972 raise gclient_utils.Error('client not configured; see \'gclient config\'')
kbr@google.comab318592009-09-04 00:54:55 +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.org116704f2010-06-11 17:34:38 +0000976 print(client.config_content)
kbr@google.comab318592009-09-04 00:54:55 +0000977 return client.RunOnDeps('pack', args)
978
979
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000980def CMDstatus(parser, args):
981 """Show modification status for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000982 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
983 help='override deps for the specified (comma-separated) '
984 'platform(s); \'all\' will process all deps_os '
985 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000986 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +0000987 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000988 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000989 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000990 if options.verbose:
991 # Print out the .gclient file. This is longer than if we just printed the
992 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +0000993 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000994 return client.RunOnDeps('status', args)
995
996
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000997@attr('epilog', """Examples:
maruel@chromium.org79692d62010-05-14 18:57:13 +0000998 gclient sync
999 update files from SCM according to current configuration,
1000 *for modules which have changed since last update or sync*
1001 gclient sync --force
1002 update files from SCM according to current configuration, for
1003 all modules (useful for recovering files deleted from local copy)
1004 gclient sync --revision src@31000
1005 update src directory to r31000
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001006""")
1007def CMDsync(parser, args):
1008 """Checkout/update all modules."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001009 parser.add_option('-f', '--force', action='store_true',
1010 help='force update even for unchanged modules')
1011 parser.add_option('-n', '--nohooks', action='store_true',
1012 help='don\'t run hooks after the update is complete')
1013 parser.add_option('-r', '--revision', action='append',
1014 dest='revisions', metavar='REV', default=[],
1015 help='Enforces revision/hash for the solutions with the '
1016 'format src@rev. The src@ part is optional and can be '
1017 'skipped. -r can be used multiple times when .gclient '
1018 'has multiple solutions configured and will work even '
1019 'if the src@ part is skipped.')
1020 parser.add_option('-H', '--head', action='store_true',
1021 help='skips any safesync_urls specified in '
1022 'configured solutions and sync to head instead')
1023 parser.add_option('-D', '--delete_unversioned_trees', action='store_true',
1024 help='delete any unexpected unversioned trees '
1025 'that are in the checkout')
1026 parser.add_option('-R', '--reset', action='store_true',
1027 help='resets any local changes before updating (git only)')
1028 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1029 help='override deps for the specified (comma-separated) '
1030 'platform(s); \'all\' will process all deps_os '
1031 'references')
1032 parser.add_option('-m', '--manually_grab_svn_rev', action='store_true',
1033 help='Skip svn up whenever possible by requesting '
1034 'actual HEAD revision from the repository')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001035 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001036 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001037
1038 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001039 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001040
maruel@chromium.org307d1792010-05-31 20:03:13 +00001041 if options.revisions and options.head:
1042 # TODO(maruel): Make it a parser.error if it doesn't break any builder.
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001043 print('Warning: you cannot use both --head and --revision')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001044
1045 if options.verbose:
1046 # Print out the .gclient file. This is longer than if we just printed the
1047 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001048 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001049 return client.RunOnDeps('update', args)
1050
1051
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001052def CMDupdate(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001053 """Alias for the sync command. Deprecated."""
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001054 return CMDsync(parser, args)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001055
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001056def CMDdiff(parser, args):
1057 """Displays local diff for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001058 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1059 help='override deps for the specified (comma-separated) '
1060 'platform(s); \'all\' will process all deps_os '
1061 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001062 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001063 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001064 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001065 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001066 if options.verbose:
1067 # Print out the .gclient file. This is longer than if we just printed the
1068 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001069 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001070 return client.RunOnDeps('diff', args)
1071
1072
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001073def CMDrevert(parser, args):
1074 """Revert all modifications in every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001075 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1076 help='override deps for the specified (comma-separated) '
1077 'platform(s); \'all\' will process all deps_os '
1078 'references')
1079 parser.add_option('-n', '--nohooks', action='store_true',
1080 help='don\'t run hooks after the revert is complete')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001081 (options, args) = parser.parse_args(args)
1082 # --force is implied.
1083 options.force = True
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001084 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001085 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001086 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001087 return client.RunOnDeps('revert', args)
1088
1089
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001090def CMDrunhooks(parser, args):
1091 """Runs hooks for files that have been modified in the local working copy."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001092 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1093 help='override deps for the specified (comma-separated) '
1094 'platform(s); \'all\' will process all deps_os '
1095 'references')
1096 parser.add_option('-f', '--force', action='store_true', default=True,
1097 help='Deprecated. No effect.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001098 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001099 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001100 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001101 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001102 if options.verbose:
1103 # Print out the .gclient file. This is longer than if we just printed the
1104 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001105 print(client.config_content)
maruel@chromium.org5df6a462009-08-28 18:52:26 +00001106 options.force = True
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001107 options.nohooks = False
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001108 return client.RunOnDeps('runhooks', args)
1109
1110
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001111def CMDrevinfo(parser, args):
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001112 """Output revision info mapping for the client and its dependencies.
1113
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001114 This allows the capture of an overall 'revision' for the source tree that
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001115 can be used to reproduce the same tree in the future. It is only useful for
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001116 'unpinned dependencies', i.e. DEPS/deps references without a svn revision
1117 number or a git hash. A git branch name isn't 'pinned' since the actual
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001118 commit can change.
1119 """
1120 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1121 help='override deps for the specified (comma-separated) '
1122 'platform(s); \'all\' will process all deps_os '
1123 'references')
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001124 parser.add_option('-a', '--actual', action='store_true',
1125 help='gets the actual checked out revisions instead of the '
1126 'ones specified in the DEPS and .gclient files')
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001127 parser.add_option('-s', '--snapshot', action='store_true',
1128 help='creates a snapshot .gclient file of the current '
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001129 'version of all repositories to reproduce the tree, '
1130 'implies -a')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001131 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001132 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001133 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001134 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001135 client.PrintRevInfo()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001136 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001137
1138
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001139def Command(name):
1140 return getattr(sys.modules[__name__], 'CMD' + name, None)
1141
1142
1143def CMDhelp(parser, args):
1144 """Prints list of commands or help for a specific command."""
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001145 (_, args) = parser.parse_args(args)
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001146 if len(args) == 1:
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001147 return Main(args + ['--help'])
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001148 parser.print_help()
1149 return 0
1150
1151
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001152def GenUsage(parser, command):
1153 """Modify an OptParse object with the function's documentation."""
1154 obj = Command(command)
1155 if command == 'help':
1156 command = '<command>'
1157 # OptParser.description prefer nicely non-formatted strings.
1158 parser.description = re.sub('[\r\n ]{2,}', ' ', obj.__doc__)
1159 usage = getattr(obj, 'usage', '')
1160 parser.set_usage('%%prog %s [options] %s' % (command, usage))
1161 parser.epilog = getattr(obj, 'epilog', None)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001162
1163
1164def Main(argv):
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001165 """Doesn't parse the arguments here, just find the right subcommand to
1166 execute."""
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001167 try:
1168 # Do it late so all commands are listed.
1169 CMDhelp.usage = ('\n\nCommands are:\n' + '\n'.join([
1170 ' %-10s %s' % (fn[3:], Command(fn[3:]).__doc__.split('\n')[0].strip())
1171 for fn in dir(sys.modules[__name__]) if fn.startswith('CMD')]))
1172 parser = optparse.OptionParser(version='%prog ' + __version__)
maruel@chromium.org9e5317a2010-08-13 20:35:11 +00001173 parser.add_option('-j', '--jobs', default=1, type='int',
1174 help='Specify how many SCM commands can run in parallel')
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001175 parser.add_option('-v', '--verbose', action='count', default=0,
1176 help='Produces additional output for diagnostics. Can be '
1177 'used up to three times for more logging info.')
1178 parser.add_option('--gclientfile', dest='config_filename',
1179 default=os.environ.get('GCLIENT_FILE', '.gclient'),
1180 help='Specify an alternate %default file')
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001181 # Integrate standard options processing.
1182 old_parser = parser.parse_args
1183 def Parse(args):
1184 (options, args) = old_parser(args)
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001185 level = None
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001186 if options.verbose == 2:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001187 level = logging.INFO
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001188 elif options.verbose > 2:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001189 level = logging.DEBUG
1190 logging.basicConfig(level=level,
1191 format='%(module)s(%(lineno)d) %(funcName)s:%(message)s')
1192 options.entries_filename = options.config_filename + '_entries'
maruel@chromium.org9e5317a2010-08-13 20:35:11 +00001193 if options.jobs < 1:
1194 parser.error('--jobs must be 1 or higher')
maruel@chromium.org559c3f82010-08-23 19:26:08 +00001195 # Useful for --jobs.
1196 options.stdout = sys.stdout
maruel@chromium.orge3216c62010-07-08 03:31:43 +00001197
1198 # These hacks need to die.
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001199 if not hasattr(options, 'revisions'):
1200 # GClient.RunOnDeps expects it even if not applicable.
1201 options.revisions = []
1202 if not hasattr(options, 'head'):
1203 options.head = None
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001204 if not hasattr(options, 'nohooks'):
1205 options.nohooks = True
1206 if not hasattr(options, 'deps_os'):
1207 options.deps_os = None
maruel@chromium.orge3216c62010-07-08 03:31:43 +00001208 if not hasattr(options, 'manually_grab_svn_rev'):
1209 options.manually_grab_svn_rev = None
1210 if not hasattr(options, 'force'):
1211 options.force = None
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001212 return (options, args)
1213 parser.parse_args = Parse
1214 # We don't want wordwrapping in epilog (usually examples)
1215 parser.format_epilog = lambda _: parser.epilog or ''
1216 if argv:
1217 command = Command(argv[0])
1218 if command:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001219 # 'fix' the usage and the description now that we know the subcommand.
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001220 GenUsage(parser, argv[0])
1221 return command(parser, argv[1:])
1222 # Not a known command. Default to help.
1223 GenUsage(parser, 'help')
1224 return CMDhelp(parser, argv)
1225 except gclient_utils.Error, e:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001226 print >> sys.stderr, 'Error: %s' % str(e)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001227 return 1
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001228
1229
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001230if '__main__' == __name__:
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001231 sys.exit(Main(sys.argv[1:]))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001232
1233# vim: ts=2:sw=2:tw=80:et: