blob: 41dc58b9c69c3937249203ea39d53d311b4e9f3e [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
maruel@chromium.org621939b2010-08-10 20:12:00 +000057import posixpath
msb@chromium.org2e38de72009-09-28 17:04:47 +000058import pprint
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000059import re
piman@chromium.org4b90e3a2010-07-01 20:28:26 +000060import subprocess
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000061import sys
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000062import urlparse
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000063import urllib
64
maruel@chromium.orgada4c652009-12-03 15:32:01 +000065import breakpad
66
maruel@chromium.org5f3eee32009-09-17 00:34:30 +000067import gclient_scm
68import gclient_utils
nasser@codeaurora.org1f7a3d12010-02-04 15:11:50 +000069from third_party.repo.progress import Progress
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000070
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000071
maruel@chromium.org1f7d1182010-05-17 18:17:38 +000072def attr(attr, data):
73 """Sets an attribute on a function."""
74 def hook(fn):
75 setattr(fn, attr, data)
76 return fn
77 return hook
maruel@chromium.orge3da35f2010-03-09 21:40:45 +000078
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000079
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000080## GClient implementation.
81
82
maruel@chromium.org116704f2010-06-11 17:34:38 +000083class GClientKeywords(object):
84 class FromImpl(object):
85 """Used to implement the From() syntax."""
86
87 def __init__(self, module_name, sub_target_name=None):
88 """module_name is the dep module we want to include from. It can also be
89 the name of a subdirectory to include from.
90
91 sub_target_name is an optional parameter if the module name in the other
92 DEPS file is different. E.g., you might want to map src/net to net."""
93 self.module_name = module_name
94 self.sub_target_name = sub_target_name
95
96 def __str__(self):
97 return 'From(%s, %s)' % (repr(self.module_name),
98 repr(self.sub_target_name))
99
maruel@chromium.org116704f2010-06-11 17:34:38 +0000100 class FileImpl(object):
101 """Used to implement the File('') syntax which lets you sync a single file
maruel@chromium.orge3216c62010-07-08 03:31:43 +0000102 from a SVN repo."""
maruel@chromium.org116704f2010-06-11 17:34:38 +0000103
104 def __init__(self, file_location):
105 self.file_location = file_location
106
107 def __str__(self):
108 return 'File("%s")' % self.file_location
109
110 def GetPath(self):
111 return os.path.split(self.file_location)[0]
112
113 def GetFilename(self):
114 rev_tokens = self.file_location.split('@')
115 return os.path.split(rev_tokens[0])[1]
116
117 def GetRevision(self):
118 rev_tokens = self.file_location.split('@')
119 if len(rev_tokens) > 1:
120 return rev_tokens[1]
121 return None
122
123 class VarImpl(object):
124 def __init__(self, custom_vars, local_scope):
125 self._custom_vars = custom_vars
126 self._local_scope = local_scope
127
128 def Lookup(self, var_name):
129 """Implements the Var syntax."""
130 if var_name in self._custom_vars:
131 return self._custom_vars[var_name]
132 elif var_name in self._local_scope.get("vars", {}):
133 return self._local_scope["vars"][var_name]
134 raise gclient_utils.Error("Var is not defined: %s" % var_name)
135
136
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000137class Dependency(GClientKeywords):
138 """Object that represents a dependency checkout."""
maruel@chromium.org9eda4112010-06-11 18:56:10 +0000139 DEPS_FILE = 'DEPS'
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000140
maruel@chromium.org0d812442010-08-10 12:41:08 +0000141 def __init__(self, parent, name, url, safesync_url, custom_deps,
142 custom_vars, deps_file):
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000143 GClientKeywords.__init__(self)
144 self.parent = parent
145 self.name = name
146 self.url = url
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000147 self.parsed_url = None
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000148 # These 2 are only set in .gclient and not in DEPS files.
149 self.safesync_url = safesync_url
150 self.custom_vars = custom_vars or {}
151 self.custom_deps = custom_deps or {}
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000152 self.deps_hooks = []
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000153 self.dependencies = []
154 self.deps_file = deps_file or self.DEPS_FILE
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000155 # A cache of the files affected by the current operation, necessary for
156 # hooks.
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000157 self._file_list = []
maruel@chromium.org85c2a192010-07-22 21:14:43 +0000158 # If it is not set to True, the dependency wasn't processed for its child
159 # dependency, i.e. its DEPS wasn't read.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000160 self.deps_parsed = False
maruel@chromium.org85c2a192010-07-22 21:14:43 +0000161 # A direct reference is dependency that is referenced by a deps, deps_os or
162 # solution. A indirect one is one that was loaded with From() or that
163 # exceeded recursion limit.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000164 self.direct_reference = False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000165 # This dependency has been processed, i.e. checked out
166 self.processed = False
167 # This dependency had its hook run
168 self.hooks_ran = False
maruel@chromium.org621939b2010-08-10 20:12:00 +0000169 # Required dependencies to run before running this one:
170 self.requirements = []
171 if self.parent and self.parent.name:
172 self.requirements.append(self.parent.name)
173 if isinstance(self.url, self.FromImpl):
174 self.requirements.append(self.url.module_name)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000175
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000176 # Sanity checks
177 if not self.name and self.parent:
178 raise gclient_utils.Error('Dependency without name')
179 if not isinstance(self.url,
180 (basestring, self.FromImpl, self.FileImpl, None.__class__)):
181 raise gclient_utils.Error('dependency url must be either a string, None, '
182 'File() or From() instead of %s' %
183 self.url.__class__.__name__)
184 if '/' in self.deps_file or '\\' in self.deps_file:
185 raise gclient_utils.Error('deps_file name must not be a path, just a '
186 'filename. %s' % self.deps_file)
187
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000188 def LateOverride(self, url):
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000189 """Resolves the parsed url from url.
190
191 Manages From() keyword accordingly. Do not touch self.parsed_url nor
192 self.url because it may called with other urls due to From()."""
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.
208 ref.ParseDepsFile(False)
209 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.org271375b2010-06-23 19:17:38 +0000249 def ParseDepsFile(self, direct_reference):
250 """Parses the DEPS file for this dependency."""
251 if direct_reference:
252 # Maybe it was referenced earlier by a From() keyword but it's now
253 # directly referenced.
254 self.direct_reference = direct_reference
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000255 if self.deps_parsed:
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000256 logging.debug('%s was already parsed' % self.name)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000257 return
maruel@chromium.org271375b2010-06-23 19:17:38 +0000258 self.deps_parsed = True
259 filepath = os.path.join(self.root_dir(), self.name, self.deps_file)
260 if not os.path.isfile(filepath):
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000261 logging.info('%s: No DEPS file found at %s' % (self.name, filepath))
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000262 return
maruel@chromium.org271375b2010-06-23 19:17:38 +0000263 deps_content = gclient_utils.FileRead(filepath)
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000264 logging.debug(deps_content)
maruel@chromium.org0d425922010-06-21 19:22:24 +0000265
maruel@chromium.org271375b2010-06-23 19:17:38 +0000266 # Eval the content.
267 # One thing is unintuitive, vars= {} must happen before Var() use.
268 local_scope = {}
269 var = self.VarImpl(self.custom_vars, local_scope)
270 global_scope = {
271 'File': self.FileImpl,
272 'From': self.FromImpl,
273 'Var': var.Lookup,
274 'deps_os': {},
275 }
maruel@chromium.org5990f9d2010-07-07 18:02:58 +0000276 try:
277 exec(deps_content, global_scope, local_scope)
278 except SyntaxError, e:
279 gclient_utils.SyntaxErrorToError(filepath, e)
maruel@chromium.org271375b2010-06-23 19:17:38 +0000280 deps = local_scope.get('deps', {})
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000281 # load os specific dependencies if defined. these dependencies may
282 # override or extend the values defined by the 'deps' member.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000283 if 'deps_os' in local_scope:
284 for deps_os_key in self.enforced_os():
285 os_deps = local_scope['deps_os'].get(deps_os_key, {})
286 if len(self.enforced_os()) > 1:
287 # Ignore any conflict when including deps for more than one
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000288 # platform, so we collect the broadest set of dependencies available.
289 # We may end up with the wrong revision of something for our
290 # platform, but this is the best we can do.
291 deps.update([x for x in os_deps.items() if not x[0] in deps])
292 else:
293 deps.update(os_deps)
294
maruel@chromium.org271375b2010-06-23 19:17:38 +0000295 self.deps_hooks.extend(local_scope.get('hooks', []))
296
297 # If a line is in custom_deps, but not in the solution, we want to append
298 # this line to the solution.
299 for d in self.custom_deps:
300 if d not in deps:
301 deps[d] = self.custom_deps[d]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000302
303 # If use_relative_paths is set in the DEPS file, regenerate
304 # the dictionary using paths relative to the directory containing
305 # the DEPS file.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000306 use_relative_paths = local_scope.get('use_relative_paths', False)
307 if use_relative_paths:
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000308 rel_deps = {}
309 for d, url in deps.items():
310 # normpath is required to allow DEPS to use .. in their
311 # dependency local path.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000312 rel_deps[os.path.normpath(os.path.join(self.name, d))] = url
313 deps = rel_deps
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000314
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000315 # Convert the deps into real Dependency.
316 for name, url in deps.iteritems():
317 if name in [s.name for s in self.dependencies]:
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000318 raise gclient_utils.Error(
319 'The same name "%s" appears multiple times in the deps section' %
320 name)
maruel@chromium.org0d812442010-08-10 12:41:08 +0000321 self.dependencies.append(Dependency(self, name, url, None, None, None,
322 None))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000323 # Sorting by name would in theory make the whole thing coherent, since
324 # subdirectories will be sorted after the parent directory, but that doens't
325 # work with From() that fetch from a dependency with a name being sorted
326 # later. But if this would be removed right now, many projects wouldn't be
327 # able to sync anymore.
bradnelson@google.com5f8f2a82010-07-23 00:05:46 +0000328 self.dependencies.sort(key=lambda x: x.name)
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000329 logging.debug('Loaded: %s' % str(self))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000330
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000331 def RunCommandRecursively(self, options, revision_overrides,
332 command, args, pm):
333 """Runs 'command' before parsing the DEPS in case it's a initial checkout
334 or a revert."""
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000335 assert self._file_list == []
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000336 # When running runhooks, there's no need to consult the SCM.
337 # All known hooks are expected to run unconditionally regardless of working
338 # copy state, so skip the SCM status check.
339 run_scm = command not in ('runhooks', None)
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000340 self.parsed_url = self.LateOverride(self.url)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000341 if run_scm and self.parsed_url:
342 if isinstance(self.parsed_url, self.FileImpl):
343 # Special support for single-file checkout.
344 if not command in (None, 'cleanup', 'diff', 'pack', 'status'):
345 options.revision = self.parsed_url.GetRevision()
346 scm = gclient_scm.SVNWrapper(self.parsed_url.GetPath(),
347 self.root_dir(),
348 self.name)
349 scm.RunCommand('updatesingle', options,
350 args + [self.parsed_url.GetFilename()],
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000351 self._file_list)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000352 else:
353 options.revision = revision_overrides.get(self.name)
354 scm = gclient_scm.CreateSCM(self.parsed_url, self.root_dir(), self.name)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000355 scm.RunCommand(command, options, args, self._file_list)
356 self._file_list = [os.path.join(self.name, f.strip())
357 for f in self._file_list]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000358 options.revision = None
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000359 self.processed = True
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000360 if pm:
361 # The + 1 comes from the fact that .gclient is considered a step in
362 # itself, .i.e. this code is called one time for the .gclient. This is not
363 # conceptually correct but it simplifies code.
364 pm._total = len(self.tree(False)) + 1
365 pm.update()
366 if self.recursion_limit():
367 # Then we can parse the DEPS file.
368 self.ParseDepsFile(True)
369 if pm:
370 pm._total = len(self.tree(False)) + 1
371 pm.update(0)
maruel@chromium.org621939b2010-08-10 20:12:00 +0000372 # Adjust the implicit dependency requirement; e.g. if a DEPS file contains
373 # both src/foo and src/foo/bar, src/foo/bar is implicitly dependent of
374 # src/foo. Yes, it's O(n^2)...
375 for s in self.dependencies:
376 for s2 in self.dependencies:
377 if s is s2:
378 continue
379 if s.name.startswith(posixpath.join(s2.name, '')):
380 s.requirements.append(s2.name)
381
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000382 # Parse the dependencies of this dependency.
383 for s in self.dependencies:
384 # TODO(maruel): All these can run concurrently! No need for threads,
385 # just buffer stdout&stderr on pipes and flush as they complete.
386 # Watch out for stdin.
387 s.RunCommandRecursively(options, revision_overrides, command, args, pm)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000388
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000389 def RunHooksRecursively(self, options):
390 """Evaluates all hooks, running actions as needed. RunCommandRecursively()
391 must have been called before to load the DEPS."""
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.
394 if self.deps_hooks and self.direct_reference:
395 # 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)
429 if self.recursion_limit():
430 for s in self.dependencies:
431 s.RunHooksRecursively(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000432
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000433 def _RunHookAction(self, hook_dict, matching_file_list):
434 """Runs the action from a single hook."""
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000435 # A single DEPS file can specify multiple hooks so this function can be
436 # called multiple times on a single Dependency.
437 #assert self.hooks_ran == False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000438 self.hooks_ran = True
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000439 logging.debug(hook_dict)
440 logging.debug(matching_file_list)
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000441 command = hook_dict['action'][:]
442 if command[0] == 'python':
443 # If the hook specified "python" as the first item, the action is a
444 # Python script. Run it by starting a new copy of the same
445 # interpreter.
446 command[0] = sys.executable
447
448 if '$matching_files' in command:
449 splice_index = command.index('$matching_files')
450 command[splice_index:splice_index + 1] = matching_file_list
451
452 # Use a discrete exit status code of 2 to indicate that a hook action
453 # failed. Users of this script may wish to treat hook action failures
454 # differently from VC failures.
455 return gclient_utils.SubprocessCall(command, self.root_dir(), fail_status=2)
456
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.orgc57e4f22010-07-22 21:37:46 +0000470 result = []
471 # Add breadth-first.
maruel@chromium.org0d812442010-08-10 12:41:08 +0000472 if self.direct_reference or include_all:
maruel@chromium.orgc57e4f22010-07-22 21:37:46 +0000473 for d in self.dependencies:
maruel@chromium.org044f4e32010-07-22 21:59:57 +0000474 result.append(d)
maruel@chromium.orgc57e4f22010-07-22 21:37:46 +0000475 for d in self.dependencies:
maruel@chromium.org0d812442010-08-10 12:41:08 +0000476 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',
495 'custom_vars', 'deps_hooks', '_file_list', 'processed',
maruel@chromium.org621939b2010-08-10 20:12:00 +0000496 '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
571 Dependency.__init__(self, None, None, None, None, None, None, None)
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:
593 self.dependencies.append(Dependency(
594 self, s['name'], s['url'],
595 s.get('safesync_url', None),
596 s.get('custom_deps', {}),
maruel@chromium.org0d812442010-08-10 12:41:08 +0000597 s.get('custom_vars', {}),
598 None))
maruel@chromium.org81843b82010-06-28 16:49:26 +0000599 except KeyError:
600 raise gclient_utils.Error('Invalid .gclient file. Solution is '
601 'incomplete: %s' % s)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000602 # .gclient can have hooks.
603 self.deps_hooks = config_dict.get('hooks', [])
604
605 def SaveConfig(self):
606 gclient_utils.FileWrite(os.path.join(self.root_dir(),
607 self._options.config_filename),
608 self.config_content)
609
610 @staticmethod
611 def LoadCurrentConfig(options):
612 """Searches for and loads a .gclient file relative to the current working
613 dir. Returns a GClient object."""
614 path = gclient_utils.FindGclientRoot(os.getcwd(), options.config_filename)
615 if not path:
616 return None
617 client = GClient(path, options)
618 client.SetConfig(gclient_utils.FileRead(
619 os.path.join(path, options.config_filename)))
620 return client
621
622 def SetDefaultConfig(self, solution_name, solution_url, safesync_url):
623 self.SetConfig(self.DEFAULT_CLIENT_FILE_TEXT % {
624 'solution_name': solution_name,
625 'solution_url': solution_url,
626 'safesync_url' : safesync_url,
627 })
628
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000629 def _SaveEntries(self):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000630 """Creates a .gclient_entries file to record the list of unique checkouts.
631
632 The .gclient_entries file lives in the same directory as .gclient.
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000633 """
634 # Sometimes pprint.pformat will use {', sometimes it'll use { ' ... It
635 # makes testing a bit too fun.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000636 result = 'entries = {\n'
637 for entry in self.tree(False):
638 # Skip over File() dependencies as we can't version them.
639 if not isinstance(entry.parsed_url, self.FileImpl):
640 result += ' %s: %s,\n' % (pprint.pformat(entry.name),
641 pprint.pformat(entry.parsed_url))
642 result += '}\n'
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000643 file_path = os.path.join(self.root_dir(), self._options.entries_filename)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000644 logging.info(result)
645 gclient_utils.FileWrite(file_path, result)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000646
647 def _ReadEntries(self):
648 """Read the .gclient_entries file for the given client.
649
650 Returns:
651 A sequence of solution names, which will be empty if there is the
652 entries file hasn't been created yet.
653 """
654 scope = {}
655 filename = os.path.join(self.root_dir(), self._options.entries_filename)
656 if not os.path.exists(filename):
maruel@chromium.org73e21142010-07-05 13:32:01 +0000657 return {}
maruel@chromium.org5990f9d2010-07-07 18:02:58 +0000658 try:
659 exec(gclient_utils.FileRead(filename), scope)
660 except SyntaxError, e:
661 gclient_utils.SyntaxErrorToError(filename, e)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000662 return scope['entries']
663
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000664 def _EnforceRevisions(self):
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000665 """Checks for revision overrides."""
666 revision_overrides = {}
maruel@chromium.org307d1792010-05-31 20:03:13 +0000667 if self._options.head:
668 return revision_overrides
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000669 for s in self.dependencies:
670 if not s.safesync_url:
maruel@chromium.org307d1792010-05-31 20:03:13 +0000671 continue
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000672 handle = urllib.urlopen(s.safesync_url)
maruel@chromium.org307d1792010-05-31 20:03:13 +0000673 rev = handle.read().strip()
674 handle.close()
675 if len(rev):
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000676 self._options.revisions.append('%s@%s' % (s.name, rev))
maruel@chromium.org307d1792010-05-31 20:03:13 +0000677 if not self._options.revisions:
678 return revision_overrides
679 # --revision will take over safesync_url.
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000680 solutions_names = [s.name for s in self.dependencies]
maruel@chromium.org307d1792010-05-31 20:03:13 +0000681 index = 0
682 for revision in self._options.revisions:
683 if not '@' in revision:
684 # Support for --revision 123
685 revision = '%s@%s' % (solutions_names[index], revision)
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000686 sol, rev = revision.split('@', 1)
maruel@chromium.org307d1792010-05-31 20:03:13 +0000687 if not sol in solutions_names:
688 #raise gclient_utils.Error('%s is not a valid solution.' % sol)
689 print >> sys.stderr, ('Please fix your script, having invalid '
690 '--revision flags will soon considered an error.')
691 else:
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000692 revision_overrides[sol] = rev
maruel@chromium.org307d1792010-05-31 20:03:13 +0000693 index += 1
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000694 return revision_overrides
695
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000696 def RunOnDeps(self, command, args):
697 """Runs a command on each dependency in a client and its dependencies.
698
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000699 Args:
700 command: The command to use (e.g., 'status' or 'diff')
701 args: list of str - extra arguments to add to the command line.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000702 """
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000703 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +0000704 raise gclient_utils.Error('No solution specified')
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000705 revision_overrides = self._EnforceRevisions()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000706 pm = None
tony@chromium.orgd2e92562010-04-27 01:55:18 +0000707 if command == 'update' and not self._options.verbose:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000708 pm = Progress('Syncing projects', len(self.tree(False)) + 1)
709 self.RunCommandRecursively(self._options, revision_overrides,
710 command, args, pm)
711 if pm:
tony@chromium.orgd2e92562010-04-27 01:55:18 +0000712 pm.end()
piman@chromium.org6f363722010-04-27 00:41:09 +0000713
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000714 # Once all the dependencies have been processed, it's now safe to run the
715 # hooks.
716 if not self._options.nohooks:
717 self.RunHooksRecursively(self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000718
719 if command == 'update':
ajwong@chromium.orgcdcee802009-06-23 15:30:42 +0000720 # Notify the user if there is an orphaned entry in their working copy.
721 # Only delete the directory if there are no changes in it, and
722 # delete_unversioned_trees is set to true.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000723 entries = [i.name for i in self.tree(False)]
724 for entry, prev_url in self._ReadEntries().iteritems():
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000725 # Fix path separator on Windows.
726 entry_fixed = entry.replace('/', os.path.sep)
maruel@chromium.org75a59272010-06-11 22:34:03 +0000727 e_dir = os.path.join(self.root_dir(), entry_fixed)
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000728 # Use entry and not entry_fixed there.
maruel@chromium.org0329e672009-05-13 18:41:04 +0000729 if entry not in entries and os.path.exists(e_dir):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000730 file_list = []
731 scm = gclient_scm.CreateSCM(prev_url, self.root_dir(), entry_fixed)
732 scm.status(self._options, [], file_list)
733 modified_files = file_list != []
msb@chromium.org83017012009-09-28 18:52:12 +0000734 if not self._options.delete_unversioned_trees or modified_files:
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000735 # There are modified files in this entry. Keep warning until
736 # removed.
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000737 print(('\nWARNING: \'%s\' is no longer part of this client. '
738 'It is recommended that you manually remove it.\n') %
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000739 entry_fixed)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000740 else:
741 # Delete the entry
maruel@chromium.org73e21142010-07-05 13:32:01 +0000742 print('\n________ deleting \'%s\' in \'%s\'' % (
743 entry_fixed, self.root_dir()))
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000744 gclient_utils.RemoveDirectory(e_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000745 # record the current list of entries for next time
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000746 self._SaveEntries()
maruel@chromium.org17cdf762010-05-28 17:30:52 +0000747 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000748
749 def PrintRevInfo(self):
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000750 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +0000751 raise gclient_utils.Error('No solution specified')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000752 # Load all the settings.
753 self.RunCommandRecursively(self._options, {}, None, [], None)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000754
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000755 def GetURLAndRev(dep):
756 """Returns the revision-qualified SCM url for a Dependency."""
757 if dep.parsed_url is None:
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +0000758 return None
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000759 if isinstance(dep.parsed_url, self.FileImpl):
760 original_url = dep.parsed_url.file_location
761 else:
762 original_url = dep.parsed_url
nasser@codeaurora.org5d63eb82010-03-24 23:22:09 +0000763 url, _ = gclient_utils.SplitUrlRevision(original_url)
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000764 scm = gclient_scm.CreateSCM(original_url, self.root_dir(), dep.name)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000765 if not os.path.isdir(scm.checkout_path):
766 return None
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +0000767 return '%s@%s' % (url, scm.revinfo(self._options, [], None))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000768
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +0000769 if self._options.snapshot:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000770 new_gclient = ''
771 # First level at .gclient
772 for d in self.dependencies:
773 entries = {}
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000774 def GrabDeps(dep):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000775 """Recursively grab dependencies."""
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000776 for d in dep.dependencies:
777 entries[d.name] = GetURLAndRev(d)
778 GrabDeps(d)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000779 GrabDeps(d)
780 custom_deps = []
781 for k in sorted(entries.keys()):
782 if entries[k]:
783 # Quotes aren't escaped...
784 custom_deps.append(' \"%s\": \'%s\',\n' % (k, entries[k]))
785 else:
786 custom_deps.append(' \"%s\": None,\n' % k)
787 new_gclient += self.DEFAULT_SNAPSHOT_SOLUTION_TEXT % {
788 'solution_name': d.name,
789 'solution_url': d.url,
790 'safesync_url' : d.safesync_url or '',
791 'solution_deps': ''.join(custom_deps),
792 }
793 # Print the snapshot configuration file
794 print(self.DEFAULT_SNAPSHOT_FILE_TEXT % {'solution_list': new_gclient})
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +0000795 else:
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +0000796 entries = {}
797 for d in self.tree(False):
798 if self._options.actual:
799 entries[d.name] = GetURLAndRev(d)
800 else:
801 entries[d.name] = d.parsed_url
802 keys = sorted(entries.keys())
803 for x in keys:
804 line = '%s: %s' % (x, entries[x])
805 if x is not keys[-1]:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000806 line += ';'
807 print line
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000808 logging.info(str(self))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000809
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000810 def ParseDepsFile(self, direct_reference):
811 """No DEPS to parse for a .gclient file."""
maruel@chromium.org98d05fa2010-07-22 21:58:01 +0000812 self.direct_reference = True
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000813 self.deps_parsed = True
814
maruel@chromium.org75a59272010-06-11 22:34:03 +0000815 def root_dir(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000816 """Root directory of gclient checkout."""
maruel@chromium.org75a59272010-06-11 22:34:03 +0000817 return self._root_dir
818
maruel@chromium.org271375b2010-06-23 19:17:38 +0000819 def enforced_os(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000820 """What deps_os entries that are to be parsed."""
maruel@chromium.org271375b2010-06-23 19:17:38 +0000821 return self._enforced_os
822
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000823 def recursion_limit(self):
824 """How recursive can each dependencies in DEPS file can load DEPS file."""
825 return self._recursion_limit
826
maruel@chromium.org0d812442010-08-10 12:41:08 +0000827 def tree(self, include_all):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000828 """Returns a flat list of all the dependencies."""
maruel@chromium.org0d812442010-08-10 12:41:08 +0000829 return self.subtree(include_all)
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000830
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000831
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000832#### gclient commands.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000833
834
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000835def CMDcleanup(parser, args):
836 """Cleans up all working copies.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +0000837
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000838Mostly svn-specific. Simply runs 'svn cleanup' for each module.
maruel@chromium.org79692d62010-05-14 18:57:13 +0000839"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000840 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
841 help='override deps for the specified (comma-separated) '
842 'platform(s); \'all\' will process all deps_os '
843 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000844 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +0000845 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000846 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000847 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000848 if options.verbose:
849 # Print out the .gclient file. This is longer than if we just printed the
850 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +0000851 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000852 return client.RunOnDeps('cleanup', args)
853
854
piman@chromium.org4b90e3a2010-07-01 20:28:26 +0000855@attr('usage', '[command] [args ...]')
856def CMDrecurse(parser, args):
857 """Operates on all the entries.
858
859 Runs a shell command on all entries.
860 """
861 # Stop parsing at the first non-arg so that these go through to the command
862 parser.disable_interspersed_args()
863 parser.add_option('-s', '--scm', action='append', default=[],
864 help='choose scm types to operate upon')
865 options, args = parser.parse_args(args)
866 root, entries = gclient_utils.GetGClientRootAndEntries()
867 scm_set = set()
868 for scm in options.scm:
869 scm_set.update(scm.split(','))
870
871 # Pass in the SCM type as an env variable
872 env = os.environ.copy()
873
874 for path, url in entries.iteritems():
875 scm = gclient_scm.GetScmName(url)
876 if scm_set and scm not in scm_set:
877 continue
878 dir = os.path.normpath(os.path.join(root, path))
879 env['GCLIENT_SCM'] = scm
880 env['GCLIENT_URL'] = url
881 subprocess.Popen(args, cwd=dir, env=env).communicate()
882
883
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000884@attr('usage', '[url] [safesync url]')
885def CMDconfig(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +0000886 """Create a .gclient file in the current directory.
887
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000888This specifies the configuration for further commands. After update/sync,
maruel@chromium.org79692d62010-05-14 18:57:13 +0000889top-level DEPS files in each module are read to determine dependent
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000890modules to operate on as well. If optional [url] parameter is
maruel@chromium.org79692d62010-05-14 18:57:13 +0000891provided, then configuration is read from a specified Subversion server
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000892URL.
maruel@chromium.org79692d62010-05-14 18:57:13 +0000893"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000894 parser.add_option('--spec',
895 help='create a gclient file containing the provided '
896 'string. Due to Cygwin/Python brokenness, it '
897 'probably can\'t contain any newlines.')
898 parser.add_option('--name',
899 help='overrides the default name for the solution')
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000900 (options, args) = parser.parse_args(args)
maruel@chromium.org5fc2a332010-05-26 19:37:15 +0000901 if ((options.spec and args) or len(args) > 2 or
902 (not options.spec and not args)):
903 parser.error('Inconsistent arguments. Use either --spec or one or 2 args')
904
maruel@chromium.org0329e672009-05-13 18:41:04 +0000905 if os.path.exists(options.config_filename):
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000906 raise gclient_utils.Error('%s file already exists in the current directory'
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000907 % options.config_filename)
maruel@chromium.org2806acc2009-05-15 12:33:34 +0000908 client = GClient('.', options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000909 if options.spec:
910 client.SetConfig(options.spec)
911 else:
maruel@chromium.org1ab7ffc2009-06-03 17:21:37 +0000912 base_url = args[0].rstrip('/')
iposva@chromium.org8cf7a392010-04-07 17:20:26 +0000913 if not options.name:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000914 name = base_url.split('/')[-1]
iposva@chromium.org8cf7a392010-04-07 17:20:26 +0000915 else:
916 # specify an alternate relpath for the given URL.
917 name = options.name
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000918 safesync_url = ''
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000919 if len(args) > 1:
920 safesync_url = args[1]
921 client.SetDefaultConfig(name, base_url, safesync_url)
922 client.SaveConfig()
maruel@chromium.org79692d62010-05-14 18:57:13 +0000923 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000924
925
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000926def CMDexport(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +0000927 """Wrapper for svn export for all managed directories."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000928 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
929 help='override deps for the specified (comma-separated) '
930 'platform(s); \'all\' will process all deps_os '
931 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000932 (options, args) = parser.parse_args(args)
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +0000933 if len(args) != 1:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000934 raise gclient_utils.Error('Need directory name')
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +0000935 client = GClient.LoadCurrentConfig(options)
936
937 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000938 raise gclient_utils.Error('client not configured; see \'gclient config\'')
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +0000939
940 if options.verbose:
941 # Print out the .gclient file. This is longer than if we just printed the
942 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +0000943 print(client.config_content)
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +0000944 return client.RunOnDeps('export', args)
945
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000946
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000947@attr('epilog', """Example:
948 gclient pack > patch.txt
949 generate simple patch for configured client and dependences
950""")
951def CMDpack(parser, args):
maruel@chromium.org79692d62010-05-14 18:57:13 +0000952 """Generate a patch which can be applied at the root of the tree.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +0000953
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000954Internally, runs 'svn diff'/'git diff' on each checked out module and
maruel@chromium.org79692d62010-05-14 18:57:13 +0000955dependencies, and performs minimal postprocessing of the output. The
956resulting patch is printed to stdout and can be applied to a freshly
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000957checked out tree via 'patch -p0 < patchfile'.
maruel@chromium.org79692d62010-05-14 18:57:13 +0000958"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000959 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
960 help='override deps for the specified (comma-separated) '
961 'platform(s); \'all\' will process all deps_os '
962 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000963 (options, args) = parser.parse_args(args)
kbr@google.comab318592009-09-04 00:54:55 +0000964 client = GClient.LoadCurrentConfig(options)
965 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000966 raise gclient_utils.Error('client not configured; see \'gclient config\'')
kbr@google.comab318592009-09-04 00:54:55 +0000967 if options.verbose:
968 # Print out the .gclient file. This is longer than if we just printed the
969 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +0000970 print(client.config_content)
kbr@google.comab318592009-09-04 00:54:55 +0000971 return client.RunOnDeps('pack', args)
972
973
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000974def CMDstatus(parser, args):
975 """Show modification status for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000976 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
977 help='override deps for the specified (comma-separated) '
978 'platform(s); \'all\' will process all deps_os '
979 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000980 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +0000981 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000982 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000983 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000984 if options.verbose:
985 # Print out the .gclient file. This is longer than if we just printed the
986 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +0000987 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000988 return client.RunOnDeps('status', args)
989
990
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000991@attr('epilog', """Examples:
maruel@chromium.org79692d62010-05-14 18:57:13 +0000992 gclient sync
993 update files from SCM according to current configuration,
994 *for modules which have changed since last update or sync*
995 gclient sync --force
996 update files from SCM according to current configuration, for
997 all modules (useful for recovering files deleted from local copy)
998 gclient sync --revision src@31000
999 update src directory to r31000
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001000""")
1001def CMDsync(parser, args):
1002 """Checkout/update all modules."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001003 parser.add_option('-f', '--force', action='store_true',
1004 help='force update even for unchanged modules')
1005 parser.add_option('-n', '--nohooks', action='store_true',
1006 help='don\'t run hooks after the update is complete')
1007 parser.add_option('-r', '--revision', action='append',
1008 dest='revisions', metavar='REV', default=[],
1009 help='Enforces revision/hash for the solutions with the '
1010 'format src@rev. The src@ part is optional and can be '
1011 'skipped. -r can be used multiple times when .gclient '
1012 'has multiple solutions configured and will work even '
1013 'if the src@ part is skipped.')
1014 parser.add_option('-H', '--head', action='store_true',
1015 help='skips any safesync_urls specified in '
1016 'configured solutions and sync to head instead')
1017 parser.add_option('-D', '--delete_unversioned_trees', action='store_true',
1018 help='delete any unexpected unversioned trees '
1019 'that are in the checkout')
1020 parser.add_option('-R', '--reset', action='store_true',
1021 help='resets any local changes before updating (git only)')
1022 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1023 help='override deps for the specified (comma-separated) '
1024 'platform(s); \'all\' will process all deps_os '
1025 'references')
1026 parser.add_option('-m', '--manually_grab_svn_rev', action='store_true',
1027 help='Skip svn up whenever possible by requesting '
1028 'actual HEAD revision from the repository')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001029 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001030 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001031
1032 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001033 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001034
maruel@chromium.org307d1792010-05-31 20:03:13 +00001035 if options.revisions and options.head:
1036 # TODO(maruel): Make it a parser.error if it doesn't break any builder.
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001037 print('Warning: you cannot use both --head and --revision')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001038
1039 if options.verbose:
1040 # Print out the .gclient file. This is longer than if we just printed the
1041 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001042 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001043 return client.RunOnDeps('update', args)
1044
1045
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001046def CMDupdate(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001047 """Alias for the sync command. Deprecated."""
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001048 return CMDsync(parser, args)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001049
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001050def CMDdiff(parser, args):
1051 """Displays local diff for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001052 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1053 help='override deps for the specified (comma-separated) '
1054 'platform(s); \'all\' will process all deps_os '
1055 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001056 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001057 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001058 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001059 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001060 if options.verbose:
1061 # Print out the .gclient file. This is longer than if we just printed the
1062 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001063 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001064 return client.RunOnDeps('diff', args)
1065
1066
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001067def CMDrevert(parser, args):
1068 """Revert all modifications in every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001069 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1070 help='override deps for the specified (comma-separated) '
1071 'platform(s); \'all\' will process all deps_os '
1072 'references')
1073 parser.add_option('-n', '--nohooks', action='store_true',
1074 help='don\'t run hooks after the revert is complete')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001075 (options, args) = parser.parse_args(args)
1076 # --force is implied.
1077 options.force = True
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001078 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001079 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001080 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001081 return client.RunOnDeps('revert', args)
1082
1083
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001084def CMDrunhooks(parser, args):
1085 """Runs hooks for files that have been modified in the local working copy."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001086 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1087 help='override deps for the specified (comma-separated) '
1088 'platform(s); \'all\' will process all deps_os '
1089 'references')
1090 parser.add_option('-f', '--force', action='store_true', default=True,
1091 help='Deprecated. No effect.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001092 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001093 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001094 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001095 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001096 if options.verbose:
1097 # Print out the .gclient file. This is longer than if we just printed the
1098 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001099 print(client.config_content)
maruel@chromium.org5df6a462009-08-28 18:52:26 +00001100 options.force = True
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001101 options.nohooks = False
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001102 return client.RunOnDeps('runhooks', args)
1103
1104
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001105def CMDrevinfo(parser, args):
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001106 """Output revision info mapping for the client and its dependencies.
1107
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001108 This allows the capture of an overall 'revision' for the source tree that
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001109 can be used to reproduce the same tree in the future. It is only useful for
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001110 'unpinned dependencies', i.e. DEPS/deps references without a svn revision
1111 number or a git hash. A git branch name isn't 'pinned' since the actual
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001112 commit can change.
1113 """
1114 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1115 help='override deps for the specified (comma-separated) '
1116 'platform(s); \'all\' will process all deps_os '
1117 'references')
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001118 parser.add_option('-a', '--actual', action='store_true',
1119 help='gets the actual checked out revisions instead of the '
1120 'ones specified in the DEPS and .gclient files')
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001121 parser.add_option('-s', '--snapshot', action='store_true',
1122 help='creates a snapshot .gclient file of the current '
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001123 'version of all repositories to reproduce the tree, '
1124 'implies -a')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001125 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001126 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001127 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001128 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001129 client.PrintRevInfo()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001130 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001131
1132
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001133def Command(name):
1134 return getattr(sys.modules[__name__], 'CMD' + name, None)
1135
1136
1137def CMDhelp(parser, args):
1138 """Prints list of commands or help for a specific command."""
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001139 (_, args) = parser.parse_args(args)
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001140 if len(args) == 1:
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001141 return Main(args + ['--help'])
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001142 parser.print_help()
1143 return 0
1144
1145
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001146def GenUsage(parser, command):
1147 """Modify an OptParse object with the function's documentation."""
1148 obj = Command(command)
1149 if command == 'help':
1150 command = '<command>'
1151 # OptParser.description prefer nicely non-formatted strings.
1152 parser.description = re.sub('[\r\n ]{2,}', ' ', obj.__doc__)
1153 usage = getattr(obj, 'usage', '')
1154 parser.set_usage('%%prog %s [options] %s' % (command, usage))
1155 parser.epilog = getattr(obj, 'epilog', None)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001156
1157
1158def Main(argv):
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001159 """Doesn't parse the arguments here, just find the right subcommand to
1160 execute."""
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001161 try:
1162 # Do it late so all commands are listed.
1163 CMDhelp.usage = ('\n\nCommands are:\n' + '\n'.join([
1164 ' %-10s %s' % (fn[3:], Command(fn[3:]).__doc__.split('\n')[0].strip())
1165 for fn in dir(sys.modules[__name__]) if fn.startswith('CMD')]))
1166 parser = optparse.OptionParser(version='%prog ' + __version__)
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001167 parser.add_option('-v', '--verbose', action='count', default=0,
1168 help='Produces additional output for diagnostics. Can be '
1169 'used up to three times for more logging info.')
1170 parser.add_option('--gclientfile', dest='config_filename',
1171 default=os.environ.get('GCLIENT_FILE', '.gclient'),
1172 help='Specify an alternate %default file')
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001173 # Integrate standard options processing.
1174 old_parser = parser.parse_args
1175 def Parse(args):
1176 (options, args) = old_parser(args)
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001177 level = None
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001178 if options.verbose == 2:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001179 level = logging.INFO
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001180 elif options.verbose > 2:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001181 level = logging.DEBUG
1182 logging.basicConfig(level=level,
1183 format='%(module)s(%(lineno)d) %(funcName)s:%(message)s')
1184 options.entries_filename = options.config_filename + '_entries'
maruel@chromium.orge3216c62010-07-08 03:31:43 +00001185
1186 # These hacks need to die.
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001187 if not hasattr(options, 'revisions'):
1188 # GClient.RunOnDeps expects it even if not applicable.
1189 options.revisions = []
1190 if not hasattr(options, 'head'):
1191 options.head = None
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001192 if not hasattr(options, 'nohooks'):
1193 options.nohooks = True
1194 if not hasattr(options, 'deps_os'):
1195 options.deps_os = None
maruel@chromium.orge3216c62010-07-08 03:31:43 +00001196 if not hasattr(options, 'manually_grab_svn_rev'):
1197 options.manually_grab_svn_rev = None
1198 if not hasattr(options, 'force'):
1199 options.force = None
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001200 return (options, args)
1201 parser.parse_args = Parse
1202 # We don't want wordwrapping in epilog (usually examples)
1203 parser.format_epilog = lambda _: parser.epilog or ''
1204 if argv:
1205 command = Command(argv[0])
1206 if command:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001207 # 'fix' the usage and the description now that we know the subcommand.
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001208 GenUsage(parser, argv[0])
1209 return command(parser, argv[1:])
1210 # Not a known command. Default to help.
1211 GenUsage(parser, 'help')
1212 return CMDhelp(parser, argv)
1213 except gclient_utils.Error, e:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001214 print >> sys.stderr, 'Error: %s' % str(e)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001215 return 1
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001216
1217
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001218if '__main__' == __name__:
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001219 sys.exit(Main(sys.argv[1:]))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001220
1221# vim: ts=2:sw=2:tw=80:et: