blob: 70fad2d423b6fbddcafbd49a3d0ff5eb225dcfc9 [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.orgdf2b3152010-07-21 17:35:24 +000052__version__ = "0.5"
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000053
maruel@chromium.org754960e2009-09-21 12:31:05 +000054import logging
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000055import optparse
56import os
msb@chromium.org2e38de72009-09-28 17:04:47 +000057import pprint
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000058import re
piman@chromium.org4b90e3a2010-07-01 20:28:26 +000059import subprocess
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000060import sys
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000061import urlparse
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000062import urllib
63
maruel@chromium.orgada4c652009-12-03 15:32:01 +000064import breakpad
65
maruel@chromium.org5f3eee32009-09-17 00:34:30 +000066import gclient_scm
67import gclient_utils
nasser@codeaurora.org1f7a3d12010-02-04 15:11:50 +000068from third_party.repo.progress import Progress
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000069
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000070
maruel@chromium.org1f7d1182010-05-17 18:17:38 +000071def attr(attr, data):
72 """Sets an attribute on a function."""
73 def hook(fn):
74 setattr(fn, attr, data)
75 return fn
76 return hook
maruel@chromium.orge3da35f2010-03-09 21:40:45 +000077
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000078
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000079## GClient implementation.
80
81
maruel@chromium.org116704f2010-06-11 17:34:38 +000082class GClientKeywords(object):
83 class FromImpl(object):
84 """Used to implement the From() syntax."""
85
86 def __init__(self, module_name, sub_target_name=None):
87 """module_name is the dep module we want to include from. It can also be
88 the name of a subdirectory to include from.
89
90 sub_target_name is an optional parameter if the module name in the other
91 DEPS file is different. E.g., you might want to map src/net to net."""
92 self.module_name = module_name
93 self.sub_target_name = sub_target_name
94
95 def __str__(self):
96 return 'From(%s, %s)' % (repr(self.module_name),
97 repr(self.sub_target_name))
98
maruel@chromium.org116704f2010-06-11 17:34:38 +000099 class FileImpl(object):
100 """Used to implement the File('') syntax which lets you sync a single file
maruel@chromium.orge3216c62010-07-08 03:31:43 +0000101 from a SVN repo."""
maruel@chromium.org116704f2010-06-11 17:34:38 +0000102
103 def __init__(self, file_location):
104 self.file_location = file_location
105
106 def __str__(self):
107 return 'File("%s")' % self.file_location
108
109 def GetPath(self):
110 return os.path.split(self.file_location)[0]
111
112 def GetFilename(self):
113 rev_tokens = self.file_location.split('@')
114 return os.path.split(rev_tokens[0])[1]
115
116 def GetRevision(self):
117 rev_tokens = self.file_location.split('@')
118 if len(rev_tokens) > 1:
119 return rev_tokens[1]
120 return None
121
122 class VarImpl(object):
123 def __init__(self, custom_vars, local_scope):
124 self._custom_vars = custom_vars
125 self._local_scope = local_scope
126
127 def Lookup(self, var_name):
128 """Implements the Var syntax."""
129 if var_name in self._custom_vars:
130 return self._custom_vars[var_name]
131 elif var_name in self._local_scope.get("vars", {}):
132 return self._local_scope["vars"][var_name]
133 raise gclient_utils.Error("Var is not defined: %s" % var_name)
134
135
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000136class Dependency(GClientKeywords):
137 """Object that represents a dependency checkout."""
maruel@chromium.org9eda4112010-06-11 18:56:10 +0000138 DEPS_FILE = 'DEPS'
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000139
maruel@chromium.org0d812442010-08-10 12:41:08 +0000140 def __init__(self, parent, name, url, safesync_url, custom_deps,
141 custom_vars, deps_file):
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000142 GClientKeywords.__init__(self)
143 self.parent = parent
144 self.name = name
145 self.url = url
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000146 self.parsed_url = None
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000147 # These 2 are only set in .gclient and not in DEPS files.
148 self.safesync_url = safesync_url
149 self.custom_vars = custom_vars or {}
150 self.custom_deps = custom_deps or {}
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000151 self.deps_hooks = []
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000152 self.dependencies = []
153 self.deps_file = deps_file or self.DEPS_FILE
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000154 # A cache of the files affected by the current operation, necessary for
155 # hooks.
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000156 self._file_list = []
maruel@chromium.org85c2a192010-07-22 21:14:43 +0000157 # If it is not set to True, the dependency wasn't processed for its child
158 # dependency, i.e. its DEPS wasn't read.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000159 self.deps_parsed = False
maruel@chromium.org85c2a192010-07-22 21:14:43 +0000160 # A direct reference is dependency that is referenced by a deps, deps_os or
161 # solution. A indirect one is one that was loaded with From() or that
162 # exceeded recursion limit.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000163 self.direct_reference = False
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@google.comfb2b8eb2009-04-23 21:03:42 +0000168
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000169 # Sanity checks
170 if not self.name and self.parent:
171 raise gclient_utils.Error('Dependency without name')
172 if not isinstance(self.url,
173 (basestring, self.FromImpl, self.FileImpl, None.__class__)):
174 raise gclient_utils.Error('dependency url must be either a string, None, '
175 'File() or From() instead of %s' %
176 self.url.__class__.__name__)
177 if '/' in self.deps_file or '\\' in self.deps_file:
178 raise gclient_utils.Error('deps_file name must not be a path, just a '
179 'filename. %s' % self.deps_file)
180
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000181 def LateOverride(self, url):
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000182 """Resolves the parsed url from url.
183
184 Manages From() keyword accordingly. Do not touch self.parsed_url nor
185 self.url because it may called with other urls due to From()."""
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000186 overriden_url = self.get_custom_deps(self.name, url)
187 if overriden_url != url:
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000188 logging.info('%s, %s was overriden to %s' % (self.name, url,
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000189 overriden_url))
190 return overriden_url
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000191 elif isinstance(url, self.FromImpl):
192 ref = [dep for dep in self.tree(True) if url.module_name == dep.name]
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000193 if not ref:
194 raise gclient_utils.Error('Failed to find one reference to %s. %s' % (
195 url.module_name, ref))
196 # It may happen that len(ref) > 1 but it's no big deal.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000197 ref = ref[0]
maruel@chromium.org98d05fa2010-07-22 21:58:01 +0000198 sub_target = url.sub_target_name or self.name
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000199 # Make sure the referenced dependency DEPS file is loaded and file the
200 # inner referenced dependency.
201 ref.ParseDepsFile(False)
202 found_dep = None
203 for d in ref.dependencies:
204 if d.name == sub_target:
205 found_dep = d
206 break
207 if not found_dep:
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000208 raise gclient_utils.Error(
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000209 'Couldn\'t find %s in %s, referenced by %s\n%s' % (
210 sub_target, ref.name, self.name, str(self.root_parent())))
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000211 # Call LateOverride() again.
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000212 parsed_url = found_dep.LateOverride(found_dep.url)
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000213 logging.info('%s, %s to %s' % (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000214 return parsed_url
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000215 elif isinstance(url, basestring):
216 parsed_url = urlparse.urlparse(url)
217 if not parsed_url[0]:
218 # A relative url. Fetch the real base.
219 path = parsed_url[2]
220 if not path.startswith('/'):
221 raise gclient_utils.Error(
222 'relative DEPS entry \'%s\' must begin with a slash' % url)
223 # Create a scm just to query the full url.
224 parent_url = self.parent.parsed_url
225 if isinstance(parent_url, self.FileImpl):
226 parent_url = parent_url.file_location
227 scm = gclient_scm.CreateSCM(parent_url, self.root_dir(), None)
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000228 parsed_url = scm.FullUrlForRelativeUrl(url)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000229 else:
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000230 parsed_url = url
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000231 logging.info('%s, %s -> %s' % (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000232 return parsed_url
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000233 elif isinstance(url, self.FileImpl):
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000234 parsed_url = url
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000235 logging.info('%s, %s -> %s (File)' % (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000236 return parsed_url
237 elif url is None:
238 return None
239 else:
240 raise gclient_utils.Error('Unkown url type')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000241
maruel@chromium.org271375b2010-06-23 19:17:38 +0000242 def ParseDepsFile(self, direct_reference):
243 """Parses the DEPS file for this dependency."""
244 if direct_reference:
245 # Maybe it was referenced earlier by a From() keyword but it's now
246 # directly referenced.
247 self.direct_reference = direct_reference
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000248 if self.deps_parsed:
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000249 logging.debug('%s was already parsed' % self.name)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000250 return
maruel@chromium.org271375b2010-06-23 19:17:38 +0000251 self.deps_parsed = True
252 filepath = os.path.join(self.root_dir(), self.name, self.deps_file)
253 if not os.path.isfile(filepath):
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000254 logging.info('%s: No DEPS file found at %s' % (self.name, filepath))
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000255 return
maruel@chromium.org271375b2010-06-23 19:17:38 +0000256 deps_content = gclient_utils.FileRead(filepath)
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000257 logging.debug(deps_content)
maruel@chromium.org0d425922010-06-21 19:22:24 +0000258
maruel@chromium.org271375b2010-06-23 19:17:38 +0000259 # Eval the content.
260 # One thing is unintuitive, vars= {} must happen before Var() use.
261 local_scope = {}
262 var = self.VarImpl(self.custom_vars, local_scope)
263 global_scope = {
264 'File': self.FileImpl,
265 'From': self.FromImpl,
266 'Var': var.Lookup,
267 'deps_os': {},
268 }
maruel@chromium.org5990f9d2010-07-07 18:02:58 +0000269 try:
270 exec(deps_content, global_scope, local_scope)
271 except SyntaxError, e:
272 gclient_utils.SyntaxErrorToError(filepath, e)
maruel@chromium.org271375b2010-06-23 19:17:38 +0000273 deps = local_scope.get('deps', {})
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000274 # load os specific dependencies if defined. these dependencies may
275 # override or extend the values defined by the 'deps' member.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000276 if 'deps_os' in local_scope:
277 for deps_os_key in self.enforced_os():
278 os_deps = local_scope['deps_os'].get(deps_os_key, {})
279 if len(self.enforced_os()) > 1:
280 # Ignore any conflict when including deps for more than one
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000281 # platform, so we collect the broadest set of dependencies available.
282 # We may end up with the wrong revision of something for our
283 # platform, but this is the best we can do.
284 deps.update([x for x in os_deps.items() if not x[0] in deps])
285 else:
286 deps.update(os_deps)
287
maruel@chromium.org271375b2010-06-23 19:17:38 +0000288 self.deps_hooks.extend(local_scope.get('hooks', []))
289
290 # If a line is in custom_deps, but not in the solution, we want to append
291 # this line to the solution.
292 for d in self.custom_deps:
293 if d not in deps:
294 deps[d] = self.custom_deps[d]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000295
296 # If use_relative_paths is set in the DEPS file, regenerate
297 # the dictionary using paths relative to the directory containing
298 # the DEPS file.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000299 use_relative_paths = local_scope.get('use_relative_paths', False)
300 if use_relative_paths:
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000301 rel_deps = {}
302 for d, url in deps.items():
303 # normpath is required to allow DEPS to use .. in their
304 # dependency local path.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000305 rel_deps[os.path.normpath(os.path.join(self.name, d))] = url
306 deps = rel_deps
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000307
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000308 # Convert the deps into real Dependency.
309 for name, url in deps.iteritems():
310 if name in [s.name for s in self.dependencies]:
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000311 raise gclient_utils.Error(
312 'The same name "%s" appears multiple times in the deps section' %
313 name)
maruel@chromium.org0d812442010-08-10 12:41:08 +0000314 self.dependencies.append(Dependency(self, name, url, None, None, None,
315 None))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000316 # Sorting by name would in theory make the whole thing coherent, since
317 # subdirectories will be sorted after the parent directory, but that doens't
318 # work with From() that fetch from a dependency with a name being sorted
319 # later. But if this would be removed right now, many projects wouldn't be
320 # able to sync anymore.
bradnelson@google.com5f8f2a82010-07-23 00:05:46 +0000321 self.dependencies.sort(key=lambda x: x.name)
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000322 logging.debug('Loaded: %s' % str(self))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000323
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000324 def RunCommandRecursively(self, options, revision_overrides,
325 command, args, pm):
326 """Runs 'command' before parsing the DEPS in case it's a initial checkout
327 or a revert."""
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000328 assert self._file_list == []
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000329 # When running runhooks, there's no need to consult the SCM.
330 # All known hooks are expected to run unconditionally regardless of working
331 # copy state, so skip the SCM status check.
332 run_scm = command not in ('runhooks', None)
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000333 self.parsed_url = self.LateOverride(self.url)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000334 if run_scm and self.parsed_url:
335 if isinstance(self.parsed_url, self.FileImpl):
336 # Special support for single-file checkout.
337 if not command in (None, 'cleanup', 'diff', 'pack', 'status'):
338 options.revision = self.parsed_url.GetRevision()
339 scm = gclient_scm.SVNWrapper(self.parsed_url.GetPath(),
340 self.root_dir(),
341 self.name)
342 scm.RunCommand('updatesingle', options,
343 args + [self.parsed_url.GetFilename()],
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000344 self._file_list)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000345 else:
346 options.revision = revision_overrides.get(self.name)
347 scm = gclient_scm.CreateSCM(self.parsed_url, self.root_dir(), self.name)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000348 scm.RunCommand(command, options, args, self._file_list)
349 self._file_list = [os.path.join(self.name, f.strip())
350 for f in self._file_list]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000351 options.revision = None
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000352 self.processed = True
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000353 if pm:
354 # The + 1 comes from the fact that .gclient is considered a step in
355 # itself, .i.e. this code is called one time for the .gclient. This is not
356 # conceptually correct but it simplifies code.
357 pm._total = len(self.tree(False)) + 1
358 pm.update()
359 if self.recursion_limit():
360 # Then we can parse the DEPS file.
361 self.ParseDepsFile(True)
362 if pm:
363 pm._total = len(self.tree(False)) + 1
364 pm.update(0)
365 # Parse the dependencies of this dependency.
366 for s in self.dependencies:
367 # TODO(maruel): All these can run concurrently! No need for threads,
368 # just buffer stdout&stderr on pipes and flush as they complete.
369 # Watch out for stdin.
370 s.RunCommandRecursively(options, revision_overrides, command, args, pm)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000371
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000372 def RunHooksRecursively(self, options):
373 """Evaluates all hooks, running actions as needed. RunCommandRecursively()
374 must have been called before to load the DEPS."""
maruel@chromium.orgdc7445d2010-07-09 21:05:29 +0000375 # If "--force" was specified, run all hooks regardless of what files have
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000376 # changed.
377 if self.deps_hooks and self.direct_reference:
378 # TODO(maruel): If the user is using git or git-svn, then we don't know
379 # what files have changed so we always run all hooks. It'd be nice to fix
380 # that.
381 if (options.force or
382 isinstance(self.parsed_url, self.FileImpl) or
383 gclient_scm.GetScmName(self.parsed_url) in ('git', None) or
384 os.path.isdir(os.path.join(self.root_dir(), self.name, '.git'))):
385 for hook_dict in self.deps_hooks:
386 self._RunHookAction(hook_dict, [])
387 else:
388 # TODO(phajdan.jr): We should know exactly when the paths are absolute.
389 # Convert all absolute paths to relative.
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000390 file_list = self.file_list()
391 for i in range(len(file_list)):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000392 # It depends on the command being executed (like runhooks vs sync).
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000393 if not os.path.isabs(file_list[i]):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000394 continue
maruel@chromium.orgdc7445d2010-07-09 21:05:29 +0000395
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000396 prefix = os.path.commonprefix([self.root_dir().lower(),
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000397 file_list[i].lower()])
398 file_list[i] = file_list[i][len(prefix):]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000399
400 # Strip any leading path separators.
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000401 while (file_list[i].startswith('\\') or
402 file_list[i].startswith('/')):
403 file_list[i] = file_list[i][1:]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000404
405 # Run hooks on the basis of whether the files from the gclient operation
406 # match each hook's pattern.
407 for hook_dict in self.deps_hooks:
408 pattern = re.compile(hook_dict['pattern'])
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000409 matching_file_list = [f for f in file_list if pattern.search(f)]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000410 if matching_file_list:
411 self._RunHookAction(hook_dict, matching_file_list)
412 if self.recursion_limit():
413 for s in self.dependencies:
414 s.RunHooksRecursively(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000415
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000416 def _RunHookAction(self, hook_dict, matching_file_list):
417 """Runs the action from a single hook."""
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000418 # A single DEPS file can specify multiple hooks so this function can be
419 # called multiple times on a single Dependency.
420 #assert self.hooks_ran == False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000421 self.hooks_ran = True
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000422 logging.debug(hook_dict)
423 logging.debug(matching_file_list)
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000424 command = hook_dict['action'][:]
425 if command[0] == 'python':
426 # If the hook specified "python" as the first item, the action is a
427 # Python script. Run it by starting a new copy of the same
428 # interpreter.
429 command[0] = sys.executable
430
431 if '$matching_files' in command:
432 splice_index = command.index('$matching_files')
433 command[splice_index:splice_index + 1] = matching_file_list
434
435 # Use a discrete exit status code of 2 to indicate that a hook action
436 # failed. Users of this script may wish to treat hook action failures
437 # differently from VC failures.
438 return gclient_utils.SubprocessCall(command, self.root_dir(), fail_status=2)
439
maruel@chromium.org271375b2010-06-23 19:17:38 +0000440 def root_dir(self):
441 return self.parent.root_dir()
442
443 def enforced_os(self):
444 return self.parent.enforced_os()
445
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000446 def recursion_limit(self):
447 return self.parent.recursion_limit() - 1
448
maruel@chromium.org0d812442010-08-10 12:41:08 +0000449 def tree(self, include_all):
450 return self.parent.tree(include_all)
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000451
maruel@chromium.org0d812442010-08-10 12:41:08 +0000452 def subtree(self, include_all):
maruel@chromium.orgc57e4f22010-07-22 21:37:46 +0000453 result = []
454 # Add breadth-first.
maruel@chromium.org0d812442010-08-10 12:41:08 +0000455 if self.direct_reference or include_all:
maruel@chromium.orgc57e4f22010-07-22 21:37:46 +0000456 for d in self.dependencies:
maruel@chromium.org044f4e32010-07-22 21:59:57 +0000457 result.append(d)
maruel@chromium.orgc57e4f22010-07-22 21:37:46 +0000458 for d in self.dependencies:
maruel@chromium.org0d812442010-08-10 12:41:08 +0000459 result.extend(d.subtree(include_all))
maruel@chromium.orgc57e4f22010-07-22 21:37:46 +0000460 return result
461
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000462 def get_custom_deps(self, name, url):
463 """Returns a custom deps if applicable."""
464 if self.parent:
465 url = self.parent.get_custom_deps(name, url)
466 # None is a valid return value to disable a dependency.
467 return self.custom_deps.get(name, url)
468
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000469 def file_list(self):
470 result = self._file_list[:]
471 for d in self.dependencies:
472 result.extend(d.file_list())
473 return result
474
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000475 def __str__(self):
476 out = []
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000477 for i in ('name', 'url', 'parsed_url', 'safesync_url', 'custom_deps',
478 'custom_vars', 'deps_hooks', '_file_list', 'processed',
479 'hooks_ran', 'deps_parsed'):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000480 # 'deps_file'
481 if self.__dict__[i]:
482 out.append('%s: %s' % (i, self.__dict__[i]))
483
484 for d in self.dependencies:
485 out.extend([' ' + x for x in str(d).splitlines()])
486 out.append('')
487 return '\n'.join(out)
488
489 def __repr__(self):
490 return '%s: %s' % (self.name, self.url)
491
maruel@chromium.orgbffb9042010-07-22 20:59:36 +0000492 def hierarchy(self):
maruel@chromium.orgbc2d2f92010-07-22 21:26:48 +0000493 """Returns a human-readable hierarchical reference to a Dependency."""
maruel@chromium.orgbffb9042010-07-22 20:59:36 +0000494 out = '%s(%s)' % (self.name, self.url)
495 i = self.parent
496 while i and i.name:
497 out = '%s(%s) -> %s' % (i.name, i.url, out)
498 i = i.parent
499 return out
500
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000501 def root_parent(self):
502 """Returns the root object, normally a GClient object."""
503 d = self
504 while d.parent:
505 d = d.parent
506 return d
507
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000508
509class GClient(Dependency):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000510 """Object that represent a gclient checkout. A tree of Dependency(), one per
511 solution or DEPS entry."""
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000512
513 DEPS_OS_CHOICES = {
514 "win32": "win",
515 "win": "win",
516 "cygwin": "win",
517 "darwin": "mac",
518 "mac": "mac",
519 "unix": "unix",
520 "linux": "unix",
521 "linux2": "unix",
522 }
523
524 DEFAULT_CLIENT_FILE_TEXT = ("""\
525solutions = [
526 { "name" : "%(solution_name)s",
527 "url" : "%(solution_url)s",
528 "custom_deps" : {
529 },
maruel@chromium.org73e21142010-07-05 13:32:01 +0000530 "safesync_url": "%(safesync_url)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000531 },
532]
533""")
534
535 DEFAULT_SNAPSHOT_SOLUTION_TEXT = ("""\
536 { "name" : "%(solution_name)s",
537 "url" : "%(solution_url)s",
538 "custom_deps" : {
maruel@chromium.org73e21142010-07-05 13:32:01 +0000539%(solution_deps)s },
540 "safesync_url": "%(safesync_url)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000541 },
542""")
543
544 DEFAULT_SNAPSHOT_FILE_TEXT = ("""\
545# Snapshot generated with gclient revinfo --snapshot
546solutions = [
maruel@chromium.org73e21142010-07-05 13:32:01 +0000547%(solution_list)s]
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000548""")
549
550 def __init__(self, root_dir, options):
maruel@chromium.org0d812442010-08-10 12:41:08 +0000551 # Do not change previous behavior. Only solution level and immediate DEPS
552 # are processed.
553 self._recursion_limit = 2
554 Dependency.__init__(self, None, None, None, None, None, None, None)
maruel@chromium.org0d425922010-06-21 19:22:24 +0000555 self._options = options
maruel@chromium.org271375b2010-06-23 19:17:38 +0000556 if options.deps_os:
557 enforced_os = options.deps_os.split(',')
558 else:
559 enforced_os = [self.DEPS_OS_CHOICES.get(sys.platform, 'unix')]
560 if 'all' in enforced_os:
561 enforced_os = self.DEPS_OS_CHOICES.itervalues()
562 self._enforced_os = list(set(enforced_os))
563 self._root_dir = root_dir
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000564 self.config_content = None
565
566 def SetConfig(self, content):
567 assert self.dependencies == []
568 config_dict = {}
569 self.config_content = content
570 try:
571 exec(content, config_dict)
572 except SyntaxError, e:
maruel@chromium.org5990f9d2010-07-07 18:02:58 +0000573 gclient_utils.SyntaxErrorToError('.gclient', e)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000574 for s in config_dict.get('solutions', []):
maruel@chromium.org81843b82010-06-28 16:49:26 +0000575 try:
576 self.dependencies.append(Dependency(
577 self, s['name'], s['url'],
578 s.get('safesync_url', None),
579 s.get('custom_deps', {}),
maruel@chromium.org0d812442010-08-10 12:41:08 +0000580 s.get('custom_vars', {}),
581 None))
maruel@chromium.org81843b82010-06-28 16:49:26 +0000582 except KeyError:
583 raise gclient_utils.Error('Invalid .gclient file. Solution is '
584 'incomplete: %s' % s)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000585 # .gclient can have hooks.
586 self.deps_hooks = config_dict.get('hooks', [])
587
588 def SaveConfig(self):
589 gclient_utils.FileWrite(os.path.join(self.root_dir(),
590 self._options.config_filename),
591 self.config_content)
592
593 @staticmethod
594 def LoadCurrentConfig(options):
595 """Searches for and loads a .gclient file relative to the current working
596 dir. Returns a GClient object."""
597 path = gclient_utils.FindGclientRoot(os.getcwd(), options.config_filename)
598 if not path:
599 return None
600 client = GClient(path, options)
601 client.SetConfig(gclient_utils.FileRead(
602 os.path.join(path, options.config_filename)))
603 return client
604
605 def SetDefaultConfig(self, solution_name, solution_url, safesync_url):
606 self.SetConfig(self.DEFAULT_CLIENT_FILE_TEXT % {
607 'solution_name': solution_name,
608 'solution_url': solution_url,
609 'safesync_url' : safesync_url,
610 })
611
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000612 def _SaveEntries(self):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000613 """Creates a .gclient_entries file to record the list of unique checkouts.
614
615 The .gclient_entries file lives in the same directory as .gclient.
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000616 """
617 # Sometimes pprint.pformat will use {', sometimes it'll use { ' ... It
618 # makes testing a bit too fun.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000619 result = 'entries = {\n'
620 for entry in self.tree(False):
621 # Skip over File() dependencies as we can't version them.
622 if not isinstance(entry.parsed_url, self.FileImpl):
623 result += ' %s: %s,\n' % (pprint.pformat(entry.name),
624 pprint.pformat(entry.parsed_url))
625 result += '}\n'
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000626 file_path = os.path.join(self.root_dir(), self._options.entries_filename)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000627 logging.info(result)
628 gclient_utils.FileWrite(file_path, result)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000629
630 def _ReadEntries(self):
631 """Read the .gclient_entries file for the given client.
632
633 Returns:
634 A sequence of solution names, which will be empty if there is the
635 entries file hasn't been created yet.
636 """
637 scope = {}
638 filename = os.path.join(self.root_dir(), self._options.entries_filename)
639 if not os.path.exists(filename):
maruel@chromium.org73e21142010-07-05 13:32:01 +0000640 return {}
maruel@chromium.org5990f9d2010-07-07 18:02:58 +0000641 try:
642 exec(gclient_utils.FileRead(filename), scope)
643 except SyntaxError, e:
644 gclient_utils.SyntaxErrorToError(filename, e)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000645 return scope['entries']
646
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000647 def _EnforceRevisions(self):
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000648 """Checks for revision overrides."""
649 revision_overrides = {}
maruel@chromium.org307d1792010-05-31 20:03:13 +0000650 if self._options.head:
651 return revision_overrides
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000652 for s in self.dependencies:
653 if not s.safesync_url:
maruel@chromium.org307d1792010-05-31 20:03:13 +0000654 continue
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000655 handle = urllib.urlopen(s.safesync_url)
maruel@chromium.org307d1792010-05-31 20:03:13 +0000656 rev = handle.read().strip()
657 handle.close()
658 if len(rev):
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000659 self._options.revisions.append('%s@%s' % (s.name, rev))
maruel@chromium.org307d1792010-05-31 20:03:13 +0000660 if not self._options.revisions:
661 return revision_overrides
662 # --revision will take over safesync_url.
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000663 solutions_names = [s.name for s in self.dependencies]
maruel@chromium.org307d1792010-05-31 20:03:13 +0000664 index = 0
665 for revision in self._options.revisions:
666 if not '@' in revision:
667 # Support for --revision 123
668 revision = '%s@%s' % (solutions_names[index], revision)
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000669 sol, rev = revision.split('@', 1)
maruel@chromium.org307d1792010-05-31 20:03:13 +0000670 if not sol in solutions_names:
671 #raise gclient_utils.Error('%s is not a valid solution.' % sol)
672 print >> sys.stderr, ('Please fix your script, having invalid '
673 '--revision flags will soon considered an error.')
674 else:
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000675 revision_overrides[sol] = rev
maruel@chromium.org307d1792010-05-31 20:03:13 +0000676 index += 1
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000677 return revision_overrides
678
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000679 def RunOnDeps(self, command, args):
680 """Runs a command on each dependency in a client and its dependencies.
681
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000682 Args:
683 command: The command to use (e.g., 'status' or 'diff')
684 args: list of str - extra arguments to add to the command line.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000685 """
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000686 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +0000687 raise gclient_utils.Error('No solution specified')
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000688 revision_overrides = self._EnforceRevisions()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000689 pm = None
tony@chromium.orgd2e92562010-04-27 01:55:18 +0000690 if command == 'update' and not self._options.verbose:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000691 pm = Progress('Syncing projects', len(self.tree(False)) + 1)
692 self.RunCommandRecursively(self._options, revision_overrides,
693 command, args, pm)
694 if pm:
tony@chromium.orgd2e92562010-04-27 01:55:18 +0000695 pm.end()
piman@chromium.org6f363722010-04-27 00:41:09 +0000696
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000697 # Once all the dependencies have been processed, it's now safe to run the
698 # hooks.
699 if not self._options.nohooks:
700 self.RunHooksRecursively(self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000701
702 if command == 'update':
ajwong@chromium.orgcdcee802009-06-23 15:30:42 +0000703 # Notify the user if there is an orphaned entry in their working copy.
704 # Only delete the directory if there are no changes in it, and
705 # delete_unversioned_trees is set to true.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000706 entries = [i.name for i in self.tree(False)]
707 for entry, prev_url in self._ReadEntries().iteritems():
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000708 # Fix path separator on Windows.
709 entry_fixed = entry.replace('/', os.path.sep)
maruel@chromium.org75a59272010-06-11 22:34:03 +0000710 e_dir = os.path.join(self.root_dir(), entry_fixed)
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000711 # Use entry and not entry_fixed there.
maruel@chromium.org0329e672009-05-13 18:41:04 +0000712 if entry not in entries and os.path.exists(e_dir):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000713 file_list = []
714 scm = gclient_scm.CreateSCM(prev_url, self.root_dir(), entry_fixed)
715 scm.status(self._options, [], file_list)
716 modified_files = file_list != []
msb@chromium.org83017012009-09-28 18:52:12 +0000717 if not self._options.delete_unversioned_trees or modified_files:
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000718 # There are modified files in this entry. Keep warning until
719 # removed.
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000720 print(('\nWARNING: \'%s\' is no longer part of this client. '
721 'It is recommended that you manually remove it.\n') %
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000722 entry_fixed)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000723 else:
724 # Delete the entry
maruel@chromium.org73e21142010-07-05 13:32:01 +0000725 print('\n________ deleting \'%s\' in \'%s\'' % (
726 entry_fixed, self.root_dir()))
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000727 gclient_utils.RemoveDirectory(e_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000728 # record the current list of entries for next time
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000729 self._SaveEntries()
maruel@chromium.org17cdf762010-05-28 17:30:52 +0000730 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000731
732 def PrintRevInfo(self):
nasser@codeaurora.org5d63eb82010-03-24 23:22:09 +0000733 """Output revision info mapping for the client and its dependencies.
734
735 This allows the capture of an overall "revision" for the source tree that
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000736 can be used to reproduce the same tree in the future. It is only useful for
737 "unpinned dependencies", i.e. DEPS/deps references without a svn revision
738 number or a git hash. A git branch name isn't "pinned" since the actual
739 commit can change.
nasser@codeaurora.org5d63eb82010-03-24 23:22:09 +0000740
741 The --snapshot option allows creating a .gclient file to reproduce the tree.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000742 """
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000743 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +0000744 raise gclient_utils.Error('No solution specified')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000745 # Load all the settings.
746 self.RunCommandRecursively(self._options, {}, None, [], None)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000747
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000748 def GetURLAndRev(name, original_url):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000749 """Returns the revision-qualified SCM url."""
750 if original_url is None:
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +0000751 return None
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000752 if isinstance(original_url, self.FileImpl):
753 original_url = original_url.file_location
nasser@codeaurora.org5d63eb82010-03-24 23:22:09 +0000754 url, _ = gclient_utils.SplitUrlRevision(original_url)
maruel@chromium.org75a59272010-06-11 22:34:03 +0000755 scm = gclient_scm.CreateSCM(original_url, self.root_dir(), name)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000756 if not os.path.isdir(scm.checkout_path):
757 return None
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +0000758 return '%s@%s' % (url, scm.revinfo(self._options, [], None))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000759
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +0000760 if self._options.snapshot:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000761 new_gclient = ''
762 # First level at .gclient
763 for d in self.dependencies:
764 entries = {}
765 def GrabDeps(sol):
766 """Recursively grab dependencies."""
767 for i in sol.dependencies:
768 entries[i.name] = GetURLAndRev(i.name, i.parsed_url)
769 GrabDeps(i)
770 GrabDeps(d)
771 custom_deps = []
772 for k in sorted(entries.keys()):
773 if entries[k]:
774 # Quotes aren't escaped...
775 custom_deps.append(' \"%s\": \'%s\',\n' % (k, entries[k]))
776 else:
777 custom_deps.append(' \"%s\": None,\n' % k)
778 new_gclient += self.DEFAULT_SNAPSHOT_SOLUTION_TEXT % {
779 'solution_name': d.name,
780 'solution_url': d.url,
781 'safesync_url' : d.safesync_url or '',
782 'solution_deps': ''.join(custom_deps),
783 }
784 # Print the snapshot configuration file
785 print(self.DEFAULT_SNAPSHOT_FILE_TEXT % {'solution_list': new_gclient})
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +0000786 else:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000787 entries = sorted(self.tree(False), key=lambda i: i.name)
788 for entry in entries:
789 entry_url = GetURLAndRev(entry.name, entry.parsed_url)
790 line = '%s: %s' % (entry.name, entry_url)
791 if not entry is entries[-1]:
792 line += ';'
793 print line
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000794 logging.info(str(self))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000795
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000796 def ParseDepsFile(self, direct_reference):
797 """No DEPS to parse for a .gclient file."""
maruel@chromium.org98d05fa2010-07-22 21:58:01 +0000798 self.direct_reference = True
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000799 self.deps_parsed = True
800
maruel@chromium.org75a59272010-06-11 22:34:03 +0000801 def root_dir(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000802 """Root directory of gclient checkout."""
maruel@chromium.org75a59272010-06-11 22:34:03 +0000803 return self._root_dir
804
maruel@chromium.org271375b2010-06-23 19:17:38 +0000805 def enforced_os(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000806 """What deps_os entries that are to be parsed."""
maruel@chromium.org271375b2010-06-23 19:17:38 +0000807 return self._enforced_os
808
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000809 def recursion_limit(self):
810 """How recursive can each dependencies in DEPS file can load DEPS file."""
811 return self._recursion_limit
812
maruel@chromium.org0d812442010-08-10 12:41:08 +0000813 def tree(self, include_all):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000814 """Returns a flat list of all the dependencies."""
maruel@chromium.org0d812442010-08-10 12:41:08 +0000815 return self.subtree(include_all)
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000816
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000817
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000818#### gclient commands.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000819
820
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000821def CMDcleanup(parser, args):
822 """Cleans up all working copies.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +0000823
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000824Mostly svn-specific. Simply runs 'svn cleanup' for each module.
maruel@chromium.org79692d62010-05-14 18:57:13 +0000825"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000826 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
827 help='override deps for the specified (comma-separated) '
828 'platform(s); \'all\' will process all deps_os '
829 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000830 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +0000831 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000832 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000833 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000834 if options.verbose:
835 # Print out the .gclient file. This is longer than if we just printed the
836 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +0000837 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000838 return client.RunOnDeps('cleanup', args)
839
840
piman@chromium.org4b90e3a2010-07-01 20:28:26 +0000841@attr('usage', '[command] [args ...]')
842def CMDrecurse(parser, args):
843 """Operates on all the entries.
844
845 Runs a shell command on all entries.
846 """
847 # Stop parsing at the first non-arg so that these go through to the command
848 parser.disable_interspersed_args()
849 parser.add_option('-s', '--scm', action='append', default=[],
850 help='choose scm types to operate upon')
851 options, args = parser.parse_args(args)
852 root, entries = gclient_utils.GetGClientRootAndEntries()
853 scm_set = set()
854 for scm in options.scm:
855 scm_set.update(scm.split(','))
856
857 # Pass in the SCM type as an env variable
858 env = os.environ.copy()
859
860 for path, url in entries.iteritems():
861 scm = gclient_scm.GetScmName(url)
862 if scm_set and scm not in scm_set:
863 continue
864 dir = os.path.normpath(os.path.join(root, path))
865 env['GCLIENT_SCM'] = scm
866 env['GCLIENT_URL'] = url
867 subprocess.Popen(args, cwd=dir, env=env).communicate()
868
869
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000870@attr('usage', '[url] [safesync url]')
871def CMDconfig(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +0000872 """Create a .gclient file in the current directory.
873
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000874This specifies the configuration for further commands. After update/sync,
maruel@chromium.org79692d62010-05-14 18:57:13 +0000875top-level DEPS files in each module are read to determine dependent
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000876modules to operate on as well. If optional [url] parameter is
maruel@chromium.org79692d62010-05-14 18:57:13 +0000877provided, then configuration is read from a specified Subversion server
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000878URL.
maruel@chromium.org79692d62010-05-14 18:57:13 +0000879"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000880 parser.add_option('--spec',
881 help='create a gclient file containing the provided '
882 'string. Due to Cygwin/Python brokenness, it '
883 'probably can\'t contain any newlines.')
884 parser.add_option('--name',
885 help='overrides the default name for the solution')
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000886 (options, args) = parser.parse_args(args)
maruel@chromium.org5fc2a332010-05-26 19:37:15 +0000887 if ((options.spec and args) or len(args) > 2 or
888 (not options.spec and not args)):
889 parser.error('Inconsistent arguments. Use either --spec or one or 2 args')
890
maruel@chromium.org0329e672009-05-13 18:41:04 +0000891 if os.path.exists(options.config_filename):
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000892 raise gclient_utils.Error('%s file already exists in the current directory'
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000893 % options.config_filename)
maruel@chromium.org2806acc2009-05-15 12:33:34 +0000894 client = GClient('.', options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000895 if options.spec:
896 client.SetConfig(options.spec)
897 else:
maruel@chromium.org1ab7ffc2009-06-03 17:21:37 +0000898 base_url = args[0].rstrip('/')
iposva@chromium.org8cf7a392010-04-07 17:20:26 +0000899 if not options.name:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000900 name = base_url.split('/')[-1]
iposva@chromium.org8cf7a392010-04-07 17:20:26 +0000901 else:
902 # specify an alternate relpath for the given URL.
903 name = options.name
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000904 safesync_url = ''
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000905 if len(args) > 1:
906 safesync_url = args[1]
907 client.SetDefaultConfig(name, base_url, safesync_url)
908 client.SaveConfig()
maruel@chromium.org79692d62010-05-14 18:57:13 +0000909 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000910
911
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000912def CMDexport(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +0000913 """Wrapper for svn export for all managed directories."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000914 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
915 help='override deps for the specified (comma-separated) '
916 'platform(s); \'all\' will process all deps_os '
917 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000918 (options, args) = parser.parse_args(args)
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +0000919 if len(args) != 1:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000920 raise gclient_utils.Error('Need directory name')
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +0000921 client = GClient.LoadCurrentConfig(options)
922
923 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000924 raise gclient_utils.Error('client not configured; see \'gclient config\'')
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +0000925
926 if options.verbose:
927 # Print out the .gclient file. This is longer than if we just printed the
928 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +0000929 print(client.config_content)
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +0000930 return client.RunOnDeps('export', args)
931
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000932
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000933@attr('epilog', """Example:
934 gclient pack > patch.txt
935 generate simple patch for configured client and dependences
936""")
937def CMDpack(parser, args):
maruel@chromium.org79692d62010-05-14 18:57:13 +0000938 """Generate a patch which can be applied at the root of the tree.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +0000939
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000940Internally, runs 'svn diff'/'git diff' on each checked out module and
maruel@chromium.org79692d62010-05-14 18:57:13 +0000941dependencies, and performs minimal postprocessing of the output. The
942resulting patch is printed to stdout and can be applied to a freshly
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000943checked out tree via 'patch -p0 < patchfile'.
maruel@chromium.org79692d62010-05-14 18:57:13 +0000944"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000945 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
946 help='override deps for the specified (comma-separated) '
947 'platform(s); \'all\' will process all deps_os '
948 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000949 (options, args) = parser.parse_args(args)
kbr@google.comab318592009-09-04 00:54:55 +0000950 client = GClient.LoadCurrentConfig(options)
951 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000952 raise gclient_utils.Error('client not configured; see \'gclient config\'')
kbr@google.comab318592009-09-04 00:54:55 +0000953 if options.verbose:
954 # Print out the .gclient file. This is longer than if we just printed the
955 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +0000956 print(client.config_content)
kbr@google.comab318592009-09-04 00:54:55 +0000957 return client.RunOnDeps('pack', args)
958
959
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000960def CMDstatus(parser, args):
961 """Show modification status for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000962 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
963 help='override deps for the specified (comma-separated) '
964 'platform(s); \'all\' will process all deps_os '
965 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000966 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +0000967 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000968 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000969 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000970 if options.verbose:
971 # Print out the .gclient file. This is longer than if we just printed the
972 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +0000973 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000974 return client.RunOnDeps('status', args)
975
976
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000977@attr('epilog', """Examples:
maruel@chromium.org79692d62010-05-14 18:57:13 +0000978 gclient sync
979 update files from SCM according to current configuration,
980 *for modules which have changed since last update or sync*
981 gclient sync --force
982 update files from SCM according to current configuration, for
983 all modules (useful for recovering files deleted from local copy)
984 gclient sync --revision src@31000
985 update src directory to r31000
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000986""")
987def CMDsync(parser, args):
988 """Checkout/update all modules."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000989 parser.add_option('-f', '--force', action='store_true',
990 help='force update even for unchanged modules')
991 parser.add_option('-n', '--nohooks', action='store_true',
992 help='don\'t run hooks after the update is complete')
993 parser.add_option('-r', '--revision', action='append',
994 dest='revisions', metavar='REV', default=[],
995 help='Enforces revision/hash for the solutions with the '
996 'format src@rev. The src@ part is optional and can be '
997 'skipped. -r can be used multiple times when .gclient '
998 'has multiple solutions configured and will work even '
999 'if the src@ part is skipped.')
1000 parser.add_option('-H', '--head', action='store_true',
1001 help='skips any safesync_urls specified in '
1002 'configured solutions and sync to head instead')
1003 parser.add_option('-D', '--delete_unversioned_trees', action='store_true',
1004 help='delete any unexpected unversioned trees '
1005 'that are in the checkout')
1006 parser.add_option('-R', '--reset', action='store_true',
1007 help='resets any local changes before updating (git only)')
1008 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1009 help='override deps for the specified (comma-separated) '
1010 'platform(s); \'all\' will process all deps_os '
1011 'references')
1012 parser.add_option('-m', '--manually_grab_svn_rev', action='store_true',
1013 help='Skip svn up whenever possible by requesting '
1014 'actual HEAD revision from the repository')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001015 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001016 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001017
1018 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001019 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001020
maruel@chromium.org307d1792010-05-31 20:03:13 +00001021 if options.revisions and options.head:
1022 # TODO(maruel): Make it a parser.error if it doesn't break any builder.
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001023 print('Warning: you cannot use both --head and --revision')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001024
1025 if options.verbose:
1026 # Print out the .gclient file. This is longer than if we just printed the
1027 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001028 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001029 return client.RunOnDeps('update', args)
1030
1031
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001032def CMDupdate(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001033 """Alias for the sync command. Deprecated."""
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001034 return CMDsync(parser, args)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001035
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001036def CMDdiff(parser, args):
1037 """Displays local diff for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001038 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1039 help='override deps for the specified (comma-separated) '
1040 'platform(s); \'all\' will process all deps_os '
1041 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001042 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001043 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001044 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001045 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001046 if options.verbose:
1047 # Print out the .gclient file. This is longer than if we just printed the
1048 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001049 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001050 return client.RunOnDeps('diff', args)
1051
1052
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001053def CMDrevert(parser, args):
1054 """Revert all modifications in every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001055 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1056 help='override deps for the specified (comma-separated) '
1057 'platform(s); \'all\' will process all deps_os '
1058 'references')
1059 parser.add_option('-n', '--nohooks', action='store_true',
1060 help='don\'t run hooks after the revert is complete')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001061 (options, args) = parser.parse_args(args)
1062 # --force is implied.
1063 options.force = True
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001064 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001065 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001066 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001067 return client.RunOnDeps('revert', args)
1068
1069
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001070def CMDrunhooks(parser, args):
1071 """Runs hooks for files that have been modified in the local working copy."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001072 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1073 help='override deps for the specified (comma-separated) '
1074 'platform(s); \'all\' will process all deps_os '
1075 'references')
1076 parser.add_option('-f', '--force', action='store_true', default=True,
1077 help='Deprecated. No effect.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001078 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001079 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001080 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001081 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001082 if options.verbose:
1083 # Print out the .gclient file. This is longer than if we just printed the
1084 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001085 print(client.config_content)
maruel@chromium.org5df6a462009-08-28 18:52:26 +00001086 options.force = True
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001087 options.nohooks = False
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001088 return client.RunOnDeps('runhooks', args)
1089
1090
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001091def CMDrevinfo(parser, args):
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001092 """Output revision info mapping for the client and its dependencies.
1093
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001094 This allows the capture of an overall 'revision' for the source tree that
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001095 can be used to reproduce the same tree in the future. It is only useful for
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001096 'unpinned dependencies', i.e. DEPS/deps references without a svn revision
1097 number or a git hash. A git branch name isn't 'pinned' since the actual
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001098 commit can change.
1099 """
1100 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1101 help='override deps for the specified (comma-separated) '
1102 'platform(s); \'all\' will process all deps_os '
1103 'references')
1104 parser.add_option('-s', '--snapshot', action='store_true',
1105 help='creates a snapshot .gclient file of the current '
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +00001106 'version of all repositories to reproduce the tree')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001107 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001108 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001109 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001110 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001111 client.PrintRevInfo()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001112 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001113
1114
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001115def Command(name):
1116 return getattr(sys.modules[__name__], 'CMD' + name, None)
1117
1118
1119def CMDhelp(parser, args):
1120 """Prints list of commands or help for a specific command."""
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001121 (_, args) = parser.parse_args(args)
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001122 if len(args) == 1:
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001123 return Main(args + ['--help'])
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001124 parser.print_help()
1125 return 0
1126
1127
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001128def GenUsage(parser, command):
1129 """Modify an OptParse object with the function's documentation."""
1130 obj = Command(command)
1131 if command == 'help':
1132 command = '<command>'
1133 # OptParser.description prefer nicely non-formatted strings.
1134 parser.description = re.sub('[\r\n ]{2,}', ' ', obj.__doc__)
1135 usage = getattr(obj, 'usage', '')
1136 parser.set_usage('%%prog %s [options] %s' % (command, usage))
1137 parser.epilog = getattr(obj, 'epilog', None)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001138
1139
1140def Main(argv):
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001141 """Doesn't parse the arguments here, just find the right subcommand to
1142 execute."""
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001143 try:
1144 # Do it late so all commands are listed.
1145 CMDhelp.usage = ('\n\nCommands are:\n' + '\n'.join([
1146 ' %-10s %s' % (fn[3:], Command(fn[3:]).__doc__.split('\n')[0].strip())
1147 for fn in dir(sys.modules[__name__]) if fn.startswith('CMD')]))
1148 parser = optparse.OptionParser(version='%prog ' + __version__)
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001149 parser.add_option('-v', '--verbose', action='count', default=0,
1150 help='Produces additional output for diagnostics. Can be '
1151 'used up to three times for more logging info.')
1152 parser.add_option('--gclientfile', dest='config_filename',
1153 default=os.environ.get('GCLIENT_FILE', '.gclient'),
1154 help='Specify an alternate %default file')
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001155 # Integrate standard options processing.
1156 old_parser = parser.parse_args
1157 def Parse(args):
1158 (options, args) = old_parser(args)
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001159 level = None
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001160 if options.verbose == 2:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001161 level = logging.INFO
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001162 elif options.verbose > 2:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001163 level = logging.DEBUG
1164 logging.basicConfig(level=level,
1165 format='%(module)s(%(lineno)d) %(funcName)s:%(message)s')
1166 options.entries_filename = options.config_filename + '_entries'
maruel@chromium.orge3216c62010-07-08 03:31:43 +00001167
1168 # These hacks need to die.
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001169 if not hasattr(options, 'revisions'):
1170 # GClient.RunOnDeps expects it even if not applicable.
1171 options.revisions = []
1172 if not hasattr(options, 'head'):
1173 options.head = None
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001174 if not hasattr(options, 'nohooks'):
1175 options.nohooks = True
1176 if not hasattr(options, 'deps_os'):
1177 options.deps_os = None
maruel@chromium.orge3216c62010-07-08 03:31:43 +00001178 if not hasattr(options, 'manually_grab_svn_rev'):
1179 options.manually_grab_svn_rev = None
1180 if not hasattr(options, 'force'):
1181 options.force = None
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001182 return (options, args)
1183 parser.parse_args = Parse
1184 # We don't want wordwrapping in epilog (usually examples)
1185 parser.format_epilog = lambda _: parser.epilog or ''
1186 if argv:
1187 command = Command(argv[0])
1188 if command:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001189 # 'fix' the usage and the description now that we know the subcommand.
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001190 GenUsage(parser, argv[0])
1191 return command(parser, argv[1:])
1192 # Not a known command. Default to help.
1193 GenUsage(parser, 'help')
1194 return CMDhelp(parser, argv)
1195 except gclient_utils.Error, e:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001196 print >> sys.stderr, 'Error: %s' % str(e)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001197 return 1
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001198
1199
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001200if '__main__' == __name__:
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001201 sys.exit(Main(sys.argv[1:]))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001202
1203# vim: ts=2:sw=2:tw=80:et: