blob: e63f85abd0d25feb905ead132990e08143c4f963 [file] [log] [blame]
maruel@chromium.org725f1c32011-04-01 20:24:54 +00001#!/usr/bin/env python
2# Copyright (c) 2011 The Chromium Authors. All rights reserved.
maruel@chromium.orgba551772010-02-03 18:21:42 +00003# 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.org46304292010-10-28 11:42:00 +000052__version__ = "0.6.1"
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000053
maruel@chromium.org9e5317a2010-08-13 20:35:11 +000054import copy
maruel@chromium.org754960e2009-09-21 12:31:05 +000055import logging
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000056import optparse
57import os
maruel@chromium.org621939b2010-08-10 20:12:00 +000058import posixpath
msb@chromium.org2e38de72009-09-28 17:04:47 +000059import pprint
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000060import re
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.orgcb2985f2010-11-03 14:08:31 +000065import breakpad # pylint: disable=W0611
maruel@chromium.orgada4c652009-12-03 15:32:01 +000066
maruel@chromium.org35625c72011-03-23 17:34:02 +000067import fix_encoding
maruel@chromium.org5f3eee32009-09-17 00:34:30 +000068import gclient_scm
69import gclient_utils
nasser@codeaurora.org1f7a3d12010-02-04 15:11:50 +000070from third_party.repo.progress import Progress
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000071
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000072
maruel@chromium.orgcb2985f2010-11-03 14:08:31 +000073def attr(attribute, data):
maruel@chromium.org1f7d1182010-05-17 18:17:38 +000074 """Sets an attribute on a function."""
75 def hook(fn):
maruel@chromium.orgcb2985f2010-11-03 14:08:31 +000076 setattr(fn, attribute, data)
maruel@chromium.org1f7d1182010-05-17 18:17:38 +000077 return fn
78 return hook
maruel@chromium.orge3da35f2010-03-09 21:40:45 +000079
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000080
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000081## GClient implementation.
82
83
maruel@chromium.org116704f2010-06-11 17:34:38 +000084class GClientKeywords(object):
85 class FromImpl(object):
86 """Used to implement the From() syntax."""
87
88 def __init__(self, module_name, sub_target_name=None):
89 """module_name is the dep module we want to include from. It can also be
90 the name of a subdirectory to include from.
91
92 sub_target_name is an optional parameter if the module name in the other
93 DEPS file is different. E.g., you might want to map src/net to net."""
94 self.module_name = module_name
95 self.sub_target_name = sub_target_name
96
97 def __str__(self):
98 return 'From(%s, %s)' % (repr(self.module_name),
99 repr(self.sub_target_name))
100
maruel@chromium.org116704f2010-06-11 17:34:38 +0000101 class FileImpl(object):
102 """Used to implement the File('') syntax which lets you sync a single file
maruel@chromium.orge3216c62010-07-08 03:31:43 +0000103 from a SVN repo."""
maruel@chromium.org116704f2010-06-11 17:34:38 +0000104
105 def __init__(self, file_location):
106 self.file_location = file_location
107
108 def __str__(self):
109 return 'File("%s")' % self.file_location
110
111 def GetPath(self):
112 return os.path.split(self.file_location)[0]
113
114 def GetFilename(self):
115 rev_tokens = self.file_location.split('@')
116 return os.path.split(rev_tokens[0])[1]
117
118 def GetRevision(self):
119 rev_tokens = self.file_location.split('@')
120 if len(rev_tokens) > 1:
121 return rev_tokens[1]
122 return None
123
124 class VarImpl(object):
125 def __init__(self, custom_vars, local_scope):
126 self._custom_vars = custom_vars
127 self._local_scope = local_scope
128
129 def Lookup(self, var_name):
130 """Implements the Var syntax."""
131 if var_name in self._custom_vars:
132 return self._custom_vars[var_name]
133 elif var_name in self._local_scope.get("vars", {}):
134 return self._local_scope["vars"][var_name]
135 raise gclient_utils.Error("Var is not defined: %s" % var_name)
136
137
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000138class Dependency(GClientKeywords, gclient_utils.WorkItem):
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000139 """Object that represents a dependency checkout."""
maruel@chromium.org9eda4112010-06-11 18:56:10 +0000140 DEPS_FILE = 'DEPS'
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000141
maruel@chromium.org0d812442010-08-10 12:41:08 +0000142 def __init__(self, parent, name, url, safesync_url, custom_deps,
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000143 custom_vars, deps_file, should_process):
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000144 GClientKeywords.__init__(self)
maruel@chromium.org6985efc2010-09-08 13:26:12 +0000145 gclient_utils.WorkItem.__init__(self)
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000146 self.parent = parent
147 self.name = name
148 self.url = url
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000149 self.parsed_url = None
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000150 # These 2 are only set in .gclient and not in DEPS files.
151 self.safesync_url = safesync_url
152 self.custom_vars = custom_vars or {}
153 self.custom_deps = custom_deps or {}
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000154 self.deps_hooks = []
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000155 self.dependencies = []
156 self.deps_file = deps_file or self.DEPS_FILE
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000157 # A cache of the files affected by the current operation, necessary for
158 # hooks.
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000159 self._file_list = []
maruel@chromium.org85c2a192010-07-22 21:14:43 +0000160 # If it is not set to True, the dependency wasn't processed for its child
161 # dependency, i.e. its DEPS wasn't read.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000162 self.deps_parsed = False
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000163 # This dependency should be processed, i.e. checked out
164 self.should_process = should_process
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.orgf50907b2010-08-12 17:05:48 +0000193 assert self.parsed_url == None or not self.should_process, self.parsed_url
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000194 overriden_url = self.get_custom_deps(self.name, url)
195 if overriden_url != url:
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000196 logging.info('%s, %s was overriden to %s' % (self.name, url,
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000197 overriden_url))
198 return overriden_url
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000199 elif isinstance(url, self.FromImpl):
200 ref = [dep for dep in self.tree(True) if url.module_name == dep.name]
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000201 if not ref:
202 raise gclient_utils.Error('Failed to find one reference to %s. %s' % (
203 url.module_name, ref))
204 # It may happen that len(ref) > 1 but it's no big deal.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000205 ref = ref[0]
maruel@chromium.org98d05fa2010-07-22 21:58:01 +0000206 sub_target = url.sub_target_name or self.name
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000207 # Make sure the referenced dependency DEPS file is loaded and file the
208 # inner referenced dependency.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000209 ref.ParseDepsFile()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000210 found_dep = None
211 for d in ref.dependencies:
212 if d.name == sub_target:
213 found_dep = d
214 break
215 if not found_dep:
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000216 raise gclient_utils.Error(
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000217 'Couldn\'t find %s in %s, referenced by %s\n%s' % (
218 sub_target, ref.name, self.name, str(self.root_parent())))
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000219 # Call LateOverride() again.
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000220 parsed_url = found_dep.LateOverride(found_dep.url)
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000221 logging.info('%s, %s to %s' % (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000222 return parsed_url
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000223 elif isinstance(url, basestring):
224 parsed_url = urlparse.urlparse(url)
225 if not parsed_url[0]:
226 # A relative url. Fetch the real base.
227 path = parsed_url[2]
228 if not path.startswith('/'):
229 raise gclient_utils.Error(
230 'relative DEPS entry \'%s\' must begin with a slash' % url)
231 # Create a scm just to query the full url.
232 parent_url = self.parent.parsed_url
233 if isinstance(parent_url, self.FileImpl):
234 parent_url = parent_url.file_location
235 scm = gclient_scm.CreateSCM(parent_url, self.root_dir(), None)
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000236 parsed_url = scm.FullUrlForRelativeUrl(url)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000237 else:
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000238 parsed_url = url
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000239 logging.info('%s, %s -> %s' % (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000240 return parsed_url
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000241 elif isinstance(url, self.FileImpl):
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000242 parsed_url = url
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000243 logging.info('%s, %s -> %s (File)' % (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000244 return parsed_url
245 elif url is None:
246 return None
247 else:
248 raise gclient_utils.Error('Unkown url type')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000249
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000250 def ParseDepsFile(self):
maruel@chromium.org271375b2010-06-23 19:17:38 +0000251 """Parses the DEPS file for this dependency."""
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000252 assert self.processed == True
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000253 if self.deps_parsed:
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000254 logging.debug('%s was already parsed' % self.name)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000255 return
maruel@chromium.org271375b2010-06-23 19:17:38 +0000256 self.deps_parsed = True
maruel@chromium.org271375b2010-06-23 19:17:38 +0000257 # One thing is unintuitive, vars= {} must happen before Var() use.
258 local_scope = {}
259 var = self.VarImpl(self.custom_vars, local_scope)
260 global_scope = {
261 'File': self.FileImpl,
262 'From': self.FromImpl,
263 'Var': var.Lookup,
264 'deps_os': {},
265 }
maruel@chromium.org46304292010-10-28 11:42:00 +0000266 filepath = os.path.join(self.root_dir(), self.name, self.deps_file)
267 if not os.path.isfile(filepath):
268 logging.info('%s: No DEPS file found at %s' % (self.name, filepath))
269 else:
270 deps_content = gclient_utils.FileRead(filepath)
271 logging.debug(deps_content)
272 # Eval the content.
273 try:
274 exec(deps_content, global_scope, local_scope)
275 except SyntaxError, e:
276 gclient_utils.SyntaxErrorToError(filepath, e)
maruel@chromium.org271375b2010-06-23 19:17:38 +0000277 deps = local_scope.get('deps', {})
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000278 # load os specific dependencies if defined. these dependencies may
279 # override or extend the values defined by the 'deps' member.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000280 if 'deps_os' in local_scope:
281 for deps_os_key in self.enforced_os():
282 os_deps = local_scope['deps_os'].get(deps_os_key, {})
283 if len(self.enforced_os()) > 1:
284 # Ignore any conflict when including deps for more than one
maruel@chromium.org46304292010-10-28 11:42:00 +0000285 # platform, so we collect the broadest set of dependencies
286 # available. We may end up with the wrong revision of something for
287 # our platform, but this is the best we can do.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000288 deps.update([x for x in os_deps.items() if not x[0] in deps])
289 else:
290 deps.update(os_deps)
291
maruel@chromium.org271375b2010-06-23 19:17:38 +0000292 self.deps_hooks.extend(local_scope.get('hooks', []))
293
294 # If a line is in custom_deps, but not in the solution, we want to append
295 # this line to the solution.
296 for d in self.custom_deps:
297 if d not in deps:
298 deps[d] = self.custom_deps[d]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000299
300 # If use_relative_paths is set in the DEPS file, regenerate
301 # the dictionary using paths relative to the directory containing
302 # the DEPS file.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000303 use_relative_paths = local_scope.get('use_relative_paths', False)
304 if use_relative_paths:
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000305 rel_deps = {}
306 for d, url in deps.items():
307 # normpath is required to allow DEPS to use .. in their
308 # dependency local path.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000309 rel_deps[os.path.normpath(os.path.join(self.name, d))] = url
310 deps = rel_deps
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000311
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000312 # Convert the deps into real Dependency.
313 for name, url in deps.iteritems():
314 if name in [s.name for s in self.dependencies]:
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000315 raise gclient_utils.Error(
316 'The same name "%s" appears multiple times in the deps section' %
317 name)
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000318 should_process = self.recursion_limit() > 0 and self.should_process
319 if should_process:
320 tree = dict((d.name, d) for d in self.tree(False))
321 if name in tree:
322 if url == tree[name].url:
323 logging.info('Won\'t process duplicate dependency %s' % tree[name])
324 # In theory we could keep it as a shadow of the other one. In
325 # practice, simply ignore it.
326 #should_process = False
327 continue
328 else:
329 raise gclient_utils.Error(
330 'Dependency %s specified more than once:\n %s\nvs\n %s' %
331 (name, tree[name].hierarchy(), self.hierarchy()))
maruel@chromium.org0d812442010-08-10 12:41:08 +0000332 self.dependencies.append(Dependency(self, name, url, None, None, None,
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000333 None, should_process))
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000334 logging.debug('Loaded: %s' % str(self))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000335
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +0000336 # Arguments number differs from overridden method
337 # pylint: disable=W0221
maruel@chromium.org3742c842010-09-09 19:27:14 +0000338 def run(self, revision_overrides, command, args, work_queue, options):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000339 """Runs 'command' before parsing the DEPS in case it's a initial checkout
340 or a revert."""
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000341 assert self._file_list == []
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000342 if not self.should_process:
343 return
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000344 # When running runhooks, there's no need to consult the SCM.
345 # All known hooks are expected to run unconditionally regardless of working
346 # copy state, so skip the SCM status check.
347 run_scm = command not in ('runhooks', None)
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000348 self.parsed_url = self.LateOverride(self.url)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000349 if run_scm and self.parsed_url:
350 if isinstance(self.parsed_url, self.FileImpl):
351 # Special support for single-file checkout.
352 if not command in (None, 'cleanup', 'diff', 'pack', 'status'):
353 options.revision = self.parsed_url.GetRevision()
354 scm = gclient_scm.SVNWrapper(self.parsed_url.GetPath(),
355 self.root_dir(),
356 self.name)
357 scm.RunCommand('updatesingle', options,
358 args + [self.parsed_url.GetFilename()],
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000359 self._file_list)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000360 else:
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000361 # Create a shallow copy to mutate revision.
362 options = copy.copy(options)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000363 options.revision = revision_overrides.get(self.name)
364 scm = gclient_scm.CreateSCM(self.parsed_url, self.root_dir(), self.name)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000365 scm.RunCommand(command, options, args, self._file_list)
366 self._file_list = [os.path.join(self.name, f.strip())
367 for f in self._file_list]
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000368 self.processed = True
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000369 if self.recursion_limit() > 0:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000370 # Then we can parse the DEPS file.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000371 self.ParseDepsFile()
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
maruel@chromium.org049bced2010-08-12 13:37:20 +0000374 # src/foo. Yes, it's O(n^2)... It's important to do that before
375 # enqueueing them.
maruel@chromium.org621939b2010-08-10 20:12:00 +0000376 for s in self.dependencies:
377 for s2 in self.dependencies:
378 if s is s2:
379 continue
380 if s.name.startswith(posixpath.join(s2.name, '')):
381 s.requirements.append(s2.name)
382
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000383 # Parse the dependencies of this dependency.
384 for s in self.dependencies:
maruel@chromium.org049bced2010-08-12 13:37:20 +0000385 work_queue.enqueue(s)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000386
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000387 def RunHooksRecursively(self, options):
maruel@chromium.org049bced2010-08-12 13:37:20 +0000388 """Evaluates all hooks, running actions as needed. run()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000389 must have been called before to load the DEPS."""
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000390 assert self.hooks_ran == False
391 if not self.should_process or self.recursion_limit() <= 0:
392 # Don't run the hook when it is above recursion_limit.
393 return
maruel@chromium.orgdc7445d2010-07-09 21:05:29 +0000394 # If "--force" was specified, run all hooks regardless of what files have
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000395 # changed.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000396 if self.deps_hooks:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000397 # TODO(maruel): If the user is using git or git-svn, then we don't know
398 # what files have changed so we always run all hooks. It'd be nice to fix
399 # that.
400 if (options.force or
401 isinstance(self.parsed_url, self.FileImpl) or
402 gclient_scm.GetScmName(self.parsed_url) in ('git', None) or
403 os.path.isdir(os.path.join(self.root_dir(), self.name, '.git'))):
404 for hook_dict in self.deps_hooks:
405 self._RunHookAction(hook_dict, [])
406 else:
407 # TODO(phajdan.jr): We should know exactly when the paths are absolute.
408 # Convert all absolute paths to relative.
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000409 file_list = self.file_list()
410 for i in range(len(file_list)):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000411 # It depends on the command being executed (like runhooks vs sync).
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000412 if not os.path.isabs(file_list[i]):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000413 continue
maruel@chromium.orgdc7445d2010-07-09 21:05:29 +0000414
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000415 prefix = os.path.commonprefix([self.root_dir().lower(),
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000416 file_list[i].lower()])
417 file_list[i] = file_list[i][len(prefix):]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000418
419 # Strip any leading path separators.
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000420 while (file_list[i].startswith('\\') or
421 file_list[i].startswith('/')):
422 file_list[i] = file_list[i][1:]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000423
424 # Run hooks on the basis of whether the files from the gclient operation
425 # match each hook's pattern.
426 for hook_dict in self.deps_hooks:
427 pattern = re.compile(hook_dict['pattern'])
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000428 matching_file_list = [f for f in file_list if pattern.search(f)]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000429 if matching_file_list:
430 self._RunHookAction(hook_dict, matching_file_list)
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000431 for s in self.dependencies:
432 s.RunHooksRecursively(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000433
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000434 def _RunHookAction(self, hook_dict, matching_file_list):
435 """Runs the action from a single hook."""
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000436 # A single DEPS file can specify multiple hooks so this function can be
437 # called multiple times on a single Dependency.
438 #assert self.hooks_ran == False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000439 self.hooks_ran = True
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000440 logging.debug(hook_dict)
441 logging.debug(matching_file_list)
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000442 command = hook_dict['action'][:]
443 if command[0] == 'python':
444 # If the hook specified "python" as the first item, the action is a
445 # Python script. Run it by starting a new copy of the same
446 # interpreter.
447 command[0] = sys.executable
448
449 if '$matching_files' in command:
450 splice_index = command.index('$matching_files')
451 command[splice_index:splice_index + 1] = matching_file_list
452
maruel@chromium.org17d01792010-09-01 18:07:10 +0000453 try:
454 gclient_utils.CheckCallAndFilterAndHeader(
455 command, cwd=self.root_dir(), always=True)
456 except gclient_utils.Error, e:
457 # Use a discrete exit status code of 2 to indicate that a hook action
458 # failed. Users of this script may wish to treat hook action failures
459 # differently from VC failures.
460 print >> sys.stderr, 'Error: %s' % str(e)
461 sys.exit(2)
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000462
maruel@chromium.org271375b2010-06-23 19:17:38 +0000463 def root_dir(self):
464 return self.parent.root_dir()
465
466 def enforced_os(self):
467 return self.parent.enforced_os()
468
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000469 def recursion_limit(self):
470 return self.parent.recursion_limit() - 1
471
maruel@chromium.org0d812442010-08-10 12:41:08 +0000472 def tree(self, include_all):
473 return self.parent.tree(include_all)
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000474
maruel@chromium.org0d812442010-08-10 12:41:08 +0000475 def subtree(self, include_all):
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000476 """Breadth first"""
maruel@chromium.orgc57e4f22010-07-22 21:37:46 +0000477 result = []
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000478 for d in self.dependencies:
479 if d.should_process or include_all:
maruel@chromium.org044f4e32010-07-22 21:59:57 +0000480 result.append(d)
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000481 for d in self.dependencies:
482 result.extend(d.subtree(include_all))
maruel@chromium.orgc57e4f22010-07-22 21:37:46 +0000483 return result
484
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000485 def get_custom_deps(self, name, url):
486 """Returns a custom deps if applicable."""
487 if self.parent:
488 url = self.parent.get_custom_deps(name, url)
489 # None is a valid return value to disable a dependency.
490 return self.custom_deps.get(name, url)
491
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000492 def file_list(self):
493 result = self._file_list[:]
494 for d in self.dependencies:
495 result.extend(d.file_list())
496 return result
497
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000498 def __str__(self):
499 out = []
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000500 for i in ('name', 'url', 'parsed_url', 'safesync_url', 'custom_deps',
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000501 'custom_vars', 'deps_hooks', '_file_list', 'should_process',
502 'processed', 'hooks_ran', 'deps_parsed', 'requirements'):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000503 # 'deps_file'
504 if self.__dict__[i]:
505 out.append('%s: %s' % (i, self.__dict__[i]))
506
507 for d in self.dependencies:
508 out.extend([' ' + x for x in str(d).splitlines()])
509 out.append('')
510 return '\n'.join(out)
511
512 def __repr__(self):
513 return '%s: %s' % (self.name, self.url)
514
maruel@chromium.orgbffb9042010-07-22 20:59:36 +0000515 def hierarchy(self):
maruel@chromium.orgbc2d2f92010-07-22 21:26:48 +0000516 """Returns a human-readable hierarchical reference to a Dependency."""
maruel@chromium.orgbffb9042010-07-22 20:59:36 +0000517 out = '%s(%s)' % (self.name, self.url)
518 i = self.parent
519 while i and i.name:
520 out = '%s(%s) -> %s' % (i.name, i.url, out)
521 i = i.parent
522 return out
523
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000524 def root_parent(self):
525 """Returns the root object, normally a GClient object."""
526 d = self
527 while d.parent:
528 d = d.parent
529 return d
530
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000531
532class GClient(Dependency):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000533 """Object that represent a gclient checkout. A tree of Dependency(), one per
534 solution or DEPS entry."""
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000535
536 DEPS_OS_CHOICES = {
537 "win32": "win",
538 "win": "win",
539 "cygwin": "win",
540 "darwin": "mac",
541 "mac": "mac",
542 "unix": "unix",
543 "linux": "unix",
544 "linux2": "unix",
545 }
546
547 DEFAULT_CLIENT_FILE_TEXT = ("""\
548solutions = [
549 { "name" : "%(solution_name)s",
550 "url" : "%(solution_url)s",
551 "custom_deps" : {
552 },
maruel@chromium.org73e21142010-07-05 13:32:01 +0000553 "safesync_url": "%(safesync_url)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000554 },
555]
556""")
557
558 DEFAULT_SNAPSHOT_SOLUTION_TEXT = ("""\
559 { "name" : "%(solution_name)s",
560 "url" : "%(solution_url)s",
561 "custom_deps" : {
maruel@chromium.org73e21142010-07-05 13:32:01 +0000562%(solution_deps)s },
563 "safesync_url": "%(safesync_url)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000564 },
565""")
566
567 DEFAULT_SNAPSHOT_FILE_TEXT = ("""\
568# Snapshot generated with gclient revinfo --snapshot
569solutions = [
maruel@chromium.org73e21142010-07-05 13:32:01 +0000570%(solution_list)s]
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000571""")
572
573 def __init__(self, root_dir, options):
maruel@chromium.org0d812442010-08-10 12:41:08 +0000574 # Do not change previous behavior. Only solution level and immediate DEPS
575 # are processed.
576 self._recursion_limit = 2
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000577 Dependency.__init__(self, None, None, None, None, None, None, None, True)
maruel@chromium.org0d425922010-06-21 19:22:24 +0000578 self._options = options
maruel@chromium.org271375b2010-06-23 19:17:38 +0000579 if options.deps_os:
580 enforced_os = options.deps_os.split(',')
581 else:
582 enforced_os = [self.DEPS_OS_CHOICES.get(sys.platform, 'unix')]
583 if 'all' in enforced_os:
584 enforced_os = self.DEPS_OS_CHOICES.itervalues()
585 self._enforced_os = list(set(enforced_os))
586 self._root_dir = root_dir
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000587 self.config_content = None
588
589 def SetConfig(self, content):
590 assert self.dependencies == []
591 config_dict = {}
592 self.config_content = content
593 try:
594 exec(content, config_dict)
595 except SyntaxError, e:
maruel@chromium.org5990f9d2010-07-07 18:02:58 +0000596 gclient_utils.SyntaxErrorToError('.gclient', e)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000597 for s in config_dict.get('solutions', []):
maruel@chromium.org81843b82010-06-28 16:49:26 +0000598 try:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000599 tree = dict((d.name, d) for d in self.tree(False))
600 if s['name'] in tree:
601 raise gclient_utils.Error(
602 'Dependency %s specified more than once in .gclient' % s['name'])
maruel@chromium.org81843b82010-06-28 16:49:26 +0000603 self.dependencies.append(Dependency(
604 self, s['name'], s['url'],
605 s.get('safesync_url', None),
606 s.get('custom_deps', {}),
maruel@chromium.org0d812442010-08-10 12:41:08 +0000607 s.get('custom_vars', {}),
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000608 None,
609 True))
maruel@chromium.org81843b82010-06-28 16:49:26 +0000610 except KeyError:
611 raise gclient_utils.Error('Invalid .gclient file. Solution is '
612 'incomplete: %s' % s)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000613 # .gclient can have hooks.
614 self.deps_hooks = config_dict.get('hooks', [])
maruel@chromium.org049bced2010-08-12 13:37:20 +0000615 self.deps_parsed = True
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000616
617 def SaveConfig(self):
618 gclient_utils.FileWrite(os.path.join(self.root_dir(),
619 self._options.config_filename),
620 self.config_content)
621
622 @staticmethod
623 def LoadCurrentConfig(options):
624 """Searches for and loads a .gclient file relative to the current working
625 dir. Returns a GClient object."""
maruel@chromium.org15804092010-09-02 17:07:37 +0000626 path = gclient_utils.FindGclientRoot(os.getcwd(), options.config_filename)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000627 if not path:
628 return None
629 client = GClient(path, options)
630 client.SetConfig(gclient_utils.FileRead(
631 os.path.join(path, options.config_filename)))
maruel@chromium.org15804092010-09-02 17:07:37 +0000632 return client
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000633
634 def SetDefaultConfig(self, solution_name, solution_url, safesync_url):
635 self.SetConfig(self.DEFAULT_CLIENT_FILE_TEXT % {
636 'solution_name': solution_name,
637 'solution_url': solution_url,
638 'safesync_url' : safesync_url,
639 })
640
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000641 def _SaveEntries(self):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000642 """Creates a .gclient_entries file to record the list of unique checkouts.
643
644 The .gclient_entries file lives in the same directory as .gclient.
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000645 """
646 # Sometimes pprint.pformat will use {', sometimes it'll use { ' ... It
647 # makes testing a bit too fun.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000648 result = 'entries = {\n'
649 for entry in self.tree(False):
650 # Skip over File() dependencies as we can't version them.
651 if not isinstance(entry.parsed_url, self.FileImpl):
652 result += ' %s: %s,\n' % (pprint.pformat(entry.name),
653 pprint.pformat(entry.parsed_url))
654 result += '}\n'
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000655 file_path = os.path.join(self.root_dir(), self._options.entries_filename)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000656 logging.info(result)
657 gclient_utils.FileWrite(file_path, result)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000658
659 def _ReadEntries(self):
660 """Read the .gclient_entries file for the given client.
661
662 Returns:
663 A sequence of solution names, which will be empty if there is the
664 entries file hasn't been created yet.
665 """
666 scope = {}
667 filename = os.path.join(self.root_dir(), self._options.entries_filename)
668 if not os.path.exists(filename):
maruel@chromium.org73e21142010-07-05 13:32:01 +0000669 return {}
maruel@chromium.org5990f9d2010-07-07 18:02:58 +0000670 try:
671 exec(gclient_utils.FileRead(filename), scope)
672 except SyntaxError, e:
673 gclient_utils.SyntaxErrorToError(filename, e)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000674 return scope['entries']
675
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000676 def _EnforceRevisions(self):
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000677 """Checks for revision overrides."""
678 revision_overrides = {}
maruel@chromium.org307d1792010-05-31 20:03:13 +0000679 if self._options.head:
680 return revision_overrides
joi@chromium.org792ea882010-11-10 02:37:27 +0000681 # Do not check safesync_url if one or more --revision flag is specified.
682 if not self._options.revisions:
683 for s in self.dependencies:
684 if not s.safesync_url:
685 continue
686 handle = urllib.urlopen(s.safesync_url)
687 rev = handle.read().strip()
688 handle.close()
689 if len(rev):
690 self._options.revisions.append('%s@%s' % (s.name, rev))
maruel@chromium.org307d1792010-05-31 20:03:13 +0000691 if not self._options.revisions:
692 return revision_overrides
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000693 solutions_names = [s.name for s in self.dependencies]
maruel@chromium.org307d1792010-05-31 20:03:13 +0000694 index = 0
695 for revision in self._options.revisions:
696 if not '@' in revision:
697 # Support for --revision 123
698 revision = '%s@%s' % (solutions_names[index], revision)
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000699 sol, rev = revision.split('@', 1)
maruel@chromium.org307d1792010-05-31 20:03:13 +0000700 if not sol in solutions_names:
701 #raise gclient_utils.Error('%s is not a valid solution.' % sol)
702 print >> sys.stderr, ('Please fix your script, having invalid '
703 '--revision flags will soon considered an error.')
704 else:
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000705 revision_overrides[sol] = rev
maruel@chromium.org307d1792010-05-31 20:03:13 +0000706 index += 1
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000707 return revision_overrides
708
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000709 def RunOnDeps(self, command, args):
710 """Runs a command on each dependency in a client and its dependencies.
711
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000712 Args:
713 command: The command to use (e.g., 'status' or 'diff')
714 args: list of str - extra arguments to add to the command line.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000715 """
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000716 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +0000717 raise gclient_utils.Error('No solution specified')
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000718 revision_overrides = self._EnforceRevisions()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000719 pm = None
maruel@chromium.org5b3f8852010-09-10 16:49:54 +0000720 # Disable progress for non-tty stdout.
maruel@chromium.orga116e7d2010-10-05 19:58:02 +0000721 if (command in ('update', 'revert') and sys.stdout.isatty() and not
722 self._options.verbose):
maruel@chromium.org049bced2010-08-12 13:37:20 +0000723 pm = Progress('Syncing projects', 1)
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000724 work_queue = gclient_utils.ExecutionQueue(self._options.jobs, pm)
maruel@chromium.org049bced2010-08-12 13:37:20 +0000725 for s in self.dependencies:
726 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +0000727 work_queue.flush(revision_overrides, command, args, options=self._options)
piman@chromium.org6f363722010-04-27 00:41:09 +0000728
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000729 # Once all the dependencies have been processed, it's now safe to run the
730 # hooks.
731 if not self._options.nohooks:
732 self.RunHooksRecursively(self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000733
734 if command == 'update':
ajwong@chromium.orgcdcee802009-06-23 15:30:42 +0000735 # Notify the user if there is an orphaned entry in their working copy.
736 # Only delete the directory if there are no changes in it, and
737 # delete_unversioned_trees is set to true.
thomasvl@chromium.org9ea49d22011-03-08 15:30:47 +0000738 entries = [i.name for i in self.tree(False) if i.url]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000739 for entry, prev_url in self._ReadEntries().iteritems():
maruel@chromium.org04dd7de2010-10-14 13:25:49 +0000740 if not prev_url:
741 # entry must have been overridden via .gclient custom_deps
742 continue
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000743 # Fix path separator on Windows.
744 entry_fixed = entry.replace('/', os.path.sep)
maruel@chromium.org75a59272010-06-11 22:34:03 +0000745 e_dir = os.path.join(self.root_dir(), entry_fixed)
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000746 # Use entry and not entry_fixed there.
maruel@chromium.org0329e672009-05-13 18:41:04 +0000747 if entry not in entries and os.path.exists(e_dir):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000748 file_list = []
749 scm = gclient_scm.CreateSCM(prev_url, self.root_dir(), entry_fixed)
750 scm.status(self._options, [], file_list)
751 modified_files = file_list != []
maruel@chromium.org28d14bd2010-11-11 20:37:09 +0000752 if (not self._options.delete_unversioned_trees or
753 (modified_files and not self._options.force)):
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000754 # There are modified files in this entry. Keep warning until
755 # removed.
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000756 print(('\nWARNING: \'%s\' is no longer part of this client. '
757 'It is recommended that you manually remove it.\n') %
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000758 entry_fixed)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000759 else:
760 # Delete the entry
maruel@chromium.org73e21142010-07-05 13:32:01 +0000761 print('\n________ deleting \'%s\' in \'%s\'' % (
762 entry_fixed, self.root_dir()))
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000763 gclient_utils.RemoveDirectory(e_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000764 # record the current list of entries for next time
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000765 self._SaveEntries()
maruel@chromium.org17cdf762010-05-28 17:30:52 +0000766 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000767
768 def PrintRevInfo(self):
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000769 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +0000770 raise gclient_utils.Error('No solution specified')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000771 # Load all the settings.
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000772 work_queue = gclient_utils.ExecutionQueue(self._options.jobs, None)
maruel@chromium.org049bced2010-08-12 13:37:20 +0000773 for s in self.dependencies:
774 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +0000775 work_queue.flush({}, None, [], options=self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000776
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000777 def GetURLAndRev(dep):
778 """Returns the revision-qualified SCM url for a Dependency."""
779 if dep.parsed_url is None:
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +0000780 return None
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000781 if isinstance(dep.parsed_url, self.FileImpl):
782 original_url = dep.parsed_url.file_location
783 else:
784 original_url = dep.parsed_url
nasser@codeaurora.org5d63eb82010-03-24 23:22:09 +0000785 url, _ = gclient_utils.SplitUrlRevision(original_url)
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000786 scm = gclient_scm.CreateSCM(original_url, self.root_dir(), dep.name)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000787 if not os.path.isdir(scm.checkout_path):
788 return None
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +0000789 return '%s@%s' % (url, scm.revinfo(self._options, [], None))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000790
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +0000791 if self._options.snapshot:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000792 new_gclient = ''
793 # First level at .gclient
794 for d in self.dependencies:
795 entries = {}
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000796 def GrabDeps(dep):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000797 """Recursively grab dependencies."""
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000798 for d in dep.dependencies:
799 entries[d.name] = GetURLAndRev(d)
800 GrabDeps(d)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000801 GrabDeps(d)
802 custom_deps = []
803 for k in sorted(entries.keys()):
804 if entries[k]:
805 # Quotes aren't escaped...
806 custom_deps.append(' \"%s\": \'%s\',\n' % (k, entries[k]))
807 else:
808 custom_deps.append(' \"%s\": None,\n' % k)
809 new_gclient += self.DEFAULT_SNAPSHOT_SOLUTION_TEXT % {
810 'solution_name': d.name,
811 'solution_url': d.url,
812 'safesync_url' : d.safesync_url or '',
813 'solution_deps': ''.join(custom_deps),
814 }
815 # Print the snapshot configuration file
816 print(self.DEFAULT_SNAPSHOT_FILE_TEXT % {'solution_list': new_gclient})
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +0000817 else:
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +0000818 entries = {}
819 for d in self.tree(False):
820 if self._options.actual:
821 entries[d.name] = GetURLAndRev(d)
822 else:
823 entries[d.name] = d.parsed_url
824 keys = sorted(entries.keys())
825 for x in keys:
maruel@chromium.orgce464892010-08-12 17:12:18 +0000826 print('%s: %s' % (x, entries[x]))
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000827 logging.info(str(self))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000828
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000829 def ParseDepsFile(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000830 """No DEPS to parse for a .gclient file."""
maruel@chromium.org049bced2010-08-12 13:37:20 +0000831 raise gclient_utils.Error('Internal error')
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000832
maruel@chromium.org75a59272010-06-11 22:34:03 +0000833 def root_dir(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000834 """Root directory of gclient checkout."""
maruel@chromium.org75a59272010-06-11 22:34:03 +0000835 return self._root_dir
836
maruel@chromium.org271375b2010-06-23 19:17:38 +0000837 def enforced_os(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000838 """What deps_os entries that are to be parsed."""
maruel@chromium.org271375b2010-06-23 19:17:38 +0000839 return self._enforced_os
840
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000841 def recursion_limit(self):
842 """How recursive can each dependencies in DEPS file can load DEPS file."""
843 return self._recursion_limit
844
maruel@chromium.org0d812442010-08-10 12:41:08 +0000845 def tree(self, include_all):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000846 """Returns a flat list of all the dependencies."""
maruel@chromium.org0d812442010-08-10 12:41:08 +0000847 return self.subtree(include_all)
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000848
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000849
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000850#### gclient commands.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000851
852
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000853def CMDcleanup(parser, args):
854 """Cleans up all working copies.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +0000855
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000856Mostly svn-specific. Simply runs 'svn cleanup' for each module.
maruel@chromium.org79692d62010-05-14 18:57:13 +0000857"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000858 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
859 help='override deps for the specified (comma-separated) '
860 'platform(s); \'all\' will process all deps_os '
861 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000862 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +0000863 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000864 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000865 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000866 if options.verbose:
867 # Print out the .gclient file. This is longer than if we just printed the
868 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +0000869 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000870 return client.RunOnDeps('cleanup', args)
871
872
piman@chromium.org4b90e3a2010-07-01 20:28:26 +0000873@attr('usage', '[command] [args ...]')
874def CMDrecurse(parser, args):
875 """Operates on all the entries.
876
877 Runs a shell command on all entries.
878 """
879 # Stop parsing at the first non-arg so that these go through to the command
880 parser.disable_interspersed_args()
881 parser.add_option('-s', '--scm', action='append', default=[],
882 help='choose scm types to operate upon')
883 options, args = parser.parse_args(args)
maruel@chromium.org45e9f2d2010-10-18 13:33:46 +0000884 if not args:
885 print >> sys.stderr, 'Need to supply a command!'
886 return 1
maruel@chromium.org78cba522010-10-18 13:32:05 +0000887 root_and_entries = gclient_utils.GetGClientRootAndEntries()
888 if not root_and_entries:
889 print >> sys.stderr, (
890 'You need to run gclient sync at least once to use \'recurse\'.\n'
891 'This is because .gclient_entries needs to exist and be up to date.')
892 return 1
893 root, entries = root_and_entries
piman@chromium.org4b90e3a2010-07-01 20:28:26 +0000894 scm_set = set()
895 for scm in options.scm:
896 scm_set.update(scm.split(','))
897
898 # Pass in the SCM type as an env variable
899 env = os.environ.copy()
900
901 for path, url in entries.iteritems():
902 scm = gclient_scm.GetScmName(url)
903 if scm_set and scm not in scm_set:
904 continue
maruel@chromium.org2b9aa8e2010-08-25 20:01:42 +0000905 cwd = os.path.normpath(os.path.join(root, path))
maruel@chromium.orgac610232010-10-13 14:01:31 +0000906 if scm:
907 env['GCLIENT_SCM'] = scm
908 if url:
909 env['GCLIENT_URL'] = url
910 gclient_utils.Popen(args, cwd=cwd, env=env).communicate()
911 return 0
piman@chromium.org4b90e3a2010-07-01 20:28:26 +0000912
913
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000914@attr('usage', '[url] [safesync url]')
915def CMDconfig(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +0000916 """Create a .gclient file in the current directory.
917
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000918This specifies the configuration for further commands. After update/sync,
maruel@chromium.org79692d62010-05-14 18:57:13 +0000919top-level DEPS files in each module are read to determine dependent
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000920modules to operate on as well. If optional [url] parameter is
maruel@chromium.org79692d62010-05-14 18:57:13 +0000921provided, then configuration is read from a specified Subversion server
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000922URL.
maruel@chromium.org79692d62010-05-14 18:57:13 +0000923"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000924 parser.add_option('--spec',
925 help='create a gclient file containing the provided '
926 'string. Due to Cygwin/Python brokenness, it '
927 'probably can\'t contain any newlines.')
928 parser.add_option('--name',
929 help='overrides the default name for the solution')
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000930 (options, args) = parser.parse_args(args)
maruel@chromium.org5fc2a332010-05-26 19:37:15 +0000931 if ((options.spec and args) or len(args) > 2 or
932 (not options.spec and not args)):
933 parser.error('Inconsistent arguments. Use either --spec or one or 2 args')
934
maruel@chromium.org0329e672009-05-13 18:41:04 +0000935 if os.path.exists(options.config_filename):
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000936 raise gclient_utils.Error('%s file already exists in the current directory'
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000937 % options.config_filename)
maruel@chromium.org2806acc2009-05-15 12:33:34 +0000938 client = GClient('.', options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000939 if options.spec:
940 client.SetConfig(options.spec)
941 else:
maruel@chromium.org1ab7ffc2009-06-03 17:21:37 +0000942 base_url = args[0].rstrip('/')
iposva@chromium.org8cf7a392010-04-07 17:20:26 +0000943 if not options.name:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000944 name = base_url.split('/')[-1]
iposva@chromium.org8cf7a392010-04-07 17:20:26 +0000945 else:
946 # specify an alternate relpath for the given URL.
947 name = options.name
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000948 safesync_url = ''
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000949 if len(args) > 1:
950 safesync_url = args[1]
951 client.SetDefaultConfig(name, base_url, safesync_url)
952 client.SaveConfig()
maruel@chromium.org79692d62010-05-14 18:57:13 +0000953 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000954
955
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000956def CMDexport(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +0000957 """Wrapper for svn export for all managed directories."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000958 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
959 help='override deps for the specified (comma-separated) '
960 'platform(s); \'all\' will process all deps_os '
961 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000962 (options, args) = parser.parse_args(args)
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +0000963 if len(args) != 1:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000964 raise gclient_utils.Error('Need directory name')
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +0000965 client = GClient.LoadCurrentConfig(options)
966
967 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000968 raise gclient_utils.Error('client not configured; see \'gclient config\'')
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +0000969
970 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)
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +0000974 return client.RunOnDeps('export', args)
975
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000976
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000977@attr('epilog', """Example:
978 gclient pack > patch.txt
979 generate simple patch for configured client and dependences
980""")
981def CMDpack(parser, args):
maruel@chromium.org79692d62010-05-14 18:57:13 +0000982 """Generate a patch which can be applied at the root of the tree.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +0000983
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000984Internally, runs 'svn diff'/'git diff' on each checked out module and
maruel@chromium.org79692d62010-05-14 18:57:13 +0000985dependencies, and performs minimal postprocessing of the output. The
986resulting patch is printed to stdout and can be applied to a freshly
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000987checked out tree via 'patch -p0 < patchfile'.
maruel@chromium.org79692d62010-05-14 18:57:13 +0000988"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000989 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
990 help='override deps for the specified (comma-separated) '
991 'platform(s); \'all\' will process all deps_os '
992 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000993 (options, args) = parser.parse_args(args)
kbr@google.comab318592009-09-04 00:54:55 +0000994 client = GClient.LoadCurrentConfig(options)
995 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000996 raise gclient_utils.Error('client not configured; see \'gclient config\'')
kbr@google.comab318592009-09-04 00:54:55 +0000997 if options.verbose:
998 # Print out the .gclient file. This is longer than if we just printed the
999 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001000 print(client.config_content)
kbr@google.comab318592009-09-04 00:54:55 +00001001 return client.RunOnDeps('pack', args)
1002
1003
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001004def CMDstatus(parser, args):
1005 """Show modification status for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001006 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1007 help='override deps for the specified (comma-separated) '
1008 'platform(s); \'all\' will process all deps_os '
1009 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001010 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001011 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001012 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001013 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001014 if options.verbose:
1015 # Print out the .gclient file. This is longer than if we just printed the
1016 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001017 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001018 return client.RunOnDeps('status', args)
1019
1020
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001021@attr('epilog', """Examples:
maruel@chromium.org79692d62010-05-14 18:57:13 +00001022 gclient sync
1023 update files from SCM according to current configuration,
1024 *for modules which have changed since last update or sync*
1025 gclient sync --force
1026 update files from SCM according to current configuration, for
1027 all modules (useful for recovering files deleted from local copy)
1028 gclient sync --revision src@31000
1029 update src directory to r31000
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001030""")
1031def CMDsync(parser, args):
1032 """Checkout/update all modules."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001033 parser.add_option('-f', '--force', action='store_true',
1034 help='force update even for unchanged modules')
1035 parser.add_option('-n', '--nohooks', action='store_true',
1036 help='don\'t run hooks after the update is complete')
1037 parser.add_option('-r', '--revision', action='append',
1038 dest='revisions', metavar='REV', default=[],
1039 help='Enforces revision/hash for the solutions with the '
1040 'format src@rev. The src@ part is optional and can be '
1041 'skipped. -r can be used multiple times when .gclient '
1042 'has multiple solutions configured and will work even '
joi@chromium.org792ea882010-11-10 02:37:27 +00001043 'if the src@ part is skipped. Note that specifying '
1044 '--revision means your safesync_url gets ignored.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001045 parser.add_option('-H', '--head', action='store_true',
1046 help='skips any safesync_urls specified in '
1047 'configured solutions and sync to head instead')
1048 parser.add_option('-D', '--delete_unversioned_trees', action='store_true',
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001049 help='delete any dependency that have been removed from '
1050 'last sync as long as there is no local modification. '
1051 'Coupled with --force, it will remove them even with '
1052 'local modifications')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001053 parser.add_option('-R', '--reset', action='store_true',
1054 help='resets any local changes before updating (git only)')
1055 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('-m', '--manually_grab_svn_rev', action='store_true',
1060 help='Skip svn up whenever possible by requesting '
1061 'actual HEAD revision from the repository')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001062 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001063 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001064
1065 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
maruel@chromium.org307d1792010-05-31 20:03:13 +00001068 if options.revisions and options.head:
1069 # TODO(maruel): Make it a parser.error if it doesn't break any builder.
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001070 print('Warning: you cannot use both --head and --revision')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001071
1072 if options.verbose:
1073 # Print out the .gclient file. This is longer than if we just printed the
1074 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001075 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001076 return client.RunOnDeps('update', args)
1077
1078
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001079def CMDupdate(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001080 """Alias for the sync command. Deprecated."""
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001081 return CMDsync(parser, args)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001082
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001083def CMDdiff(parser, args):
1084 """Displays local diff for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001085 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1086 help='override deps for the specified (comma-separated) '
1087 'platform(s); \'all\' will process all deps_os '
1088 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001089 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001090 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001091 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001092 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001093 if options.verbose:
1094 # Print out the .gclient file. This is longer than if we just printed the
1095 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001096 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001097 return client.RunOnDeps('diff', args)
1098
1099
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001100def CMDrevert(parser, args):
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001101 """Revert all modifications in every dependencies.
1102
1103 That's the nuclear option to get back to a 'clean' state. It removes anything
1104 that shows up in svn status."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001105 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1106 help='override deps for the specified (comma-separated) '
1107 'platform(s); \'all\' will process all deps_os '
1108 'references')
1109 parser.add_option('-n', '--nohooks', action='store_true',
1110 help='don\'t run hooks after the revert is complete')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001111 (options, args) = parser.parse_args(args)
1112 # --force is implied.
1113 options.force = True
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001114 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001115 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001116 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001117 return client.RunOnDeps('revert', args)
1118
1119
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001120def CMDrunhooks(parser, args):
1121 """Runs hooks for files that have been modified in the local working copy."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001122 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1123 help='override deps for the specified (comma-separated) '
1124 'platform(s); \'all\' will process all deps_os '
1125 'references')
1126 parser.add_option('-f', '--force', action='store_true', default=True,
1127 help='Deprecated. No effect.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001128 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001129 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001130 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001131 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001132 if options.verbose:
1133 # Print out the .gclient file. This is longer than if we just printed the
1134 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001135 print(client.config_content)
maruel@chromium.org5df6a462009-08-28 18:52:26 +00001136 options.force = True
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001137 options.nohooks = False
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001138 return client.RunOnDeps('runhooks', args)
1139
1140
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001141def CMDrevinfo(parser, args):
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001142 """Output revision info mapping for the client and its dependencies.
1143
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001144 This allows the capture of an overall 'revision' for the source tree that
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001145 can be used to reproduce the same tree in the future. It is only useful for
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001146 'unpinned dependencies', i.e. DEPS/deps references without a svn revision
1147 number or a git hash. A git branch name isn't 'pinned' since the actual
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001148 commit can change.
1149 """
1150 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1151 help='override deps for the specified (comma-separated) '
1152 'platform(s); \'all\' will process all deps_os '
1153 'references')
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001154 parser.add_option('-a', '--actual', action='store_true',
1155 help='gets the actual checked out revisions instead of the '
1156 'ones specified in the DEPS and .gclient files')
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001157 parser.add_option('-s', '--snapshot', action='store_true',
1158 help='creates a snapshot .gclient file of the current '
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001159 'version of all repositories to reproduce the tree, '
1160 'implies -a')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001161 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001162 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001163 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001164 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001165 client.PrintRevInfo()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001166 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001167
1168
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001169def Command(name):
1170 return getattr(sys.modules[__name__], 'CMD' + name, None)
1171
1172
1173def CMDhelp(parser, args):
1174 """Prints list of commands or help for a specific command."""
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001175 (_, args) = parser.parse_args(args)
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001176 if len(args) == 1:
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001177 return Main(args + ['--help'])
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001178 parser.print_help()
1179 return 0
1180
1181
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001182def GenUsage(parser, command):
1183 """Modify an OptParse object with the function's documentation."""
1184 obj = Command(command)
1185 if command == 'help':
1186 command = '<command>'
1187 # OptParser.description prefer nicely non-formatted strings.
1188 parser.description = re.sub('[\r\n ]{2,}', ' ', obj.__doc__)
1189 usage = getattr(obj, 'usage', '')
1190 parser.set_usage('%%prog %s [options] %s' % (command, usage))
1191 parser.epilog = getattr(obj, 'epilog', None)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001192
1193
1194def Main(argv):
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001195 """Doesn't parse the arguments here, just find the right subcommand to
1196 execute."""
maruel@chromium.orgc3a15a22010-11-20 03:12:27 +00001197 if sys.hexversion < 0x02050000:
1198 print >> sys.stderr, (
1199 '\nYour python version is unsupported, please upgrade.\n')
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001200 try:
maruel@chromium.orge0de9cb2010-09-17 15:07:14 +00001201 # Make stdout auto-flush so buildbot doesn't kill us during lengthy
1202 # operations. Python as a strong tendency to buffer sys.stdout.
1203 sys.stdout = gclient_utils.MakeFileAutoFlush(sys.stdout)
maruel@chromium.org4ed34182010-09-17 15:57:47 +00001204 # Make stdout annotated with the thread ids.
1205 sys.stdout = gclient_utils.MakeFileAnnotated(sys.stdout)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001206 # Do it late so all commands are listed.
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +00001207 # Unused variable 'usage'
1208 # pylint: disable=W0612
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001209 CMDhelp.usage = ('\n\nCommands are:\n' + '\n'.join([
1210 ' %-10s %s' % (fn[3:], Command(fn[3:]).__doc__.split('\n')[0].strip())
1211 for fn in dir(sys.modules[__name__]) if fn.startswith('CMD')]))
1212 parser = optparse.OptionParser(version='%prog ' + __version__)
maruel@chromium.orgfac9d3d2010-09-10 20:38:49 +00001213 parser.add_option('-j', '--jobs', default=1, type='int',
maruel@chromium.org3b84d4c2010-09-10 18:02:43 +00001214 help='Specify how many SCM commands can run in parallel; '
1215 'default=%default')
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001216 parser.add_option('-v', '--verbose', action='count', default=0,
1217 help='Produces additional output for diagnostics. Can be '
1218 'used up to three times for more logging info.')
1219 parser.add_option('--gclientfile', dest='config_filename',
1220 default=os.environ.get('GCLIENT_FILE', '.gclient'),
1221 help='Specify an alternate %default file')
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001222 # Integrate standard options processing.
1223 old_parser = parser.parse_args
1224 def Parse(args):
1225 (options, args) = old_parser(args)
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001226 level = None
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001227 if options.verbose == 2:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001228 level = logging.INFO
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001229 elif options.verbose > 2:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001230 level = logging.DEBUG
1231 logging.basicConfig(level=level,
1232 format='%(module)s(%(lineno)d) %(funcName)s:%(message)s')
1233 options.entries_filename = options.config_filename + '_entries'
maruel@chromium.org9e5317a2010-08-13 20:35:11 +00001234 if options.jobs < 1:
1235 parser.error('--jobs must be 1 or higher')
maruel@chromium.orge3216c62010-07-08 03:31:43 +00001236
1237 # These hacks need to die.
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001238 if not hasattr(options, 'revisions'):
1239 # GClient.RunOnDeps expects it even if not applicable.
1240 options.revisions = []
1241 if not hasattr(options, 'head'):
1242 options.head = None
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001243 if not hasattr(options, 'nohooks'):
1244 options.nohooks = True
1245 if not hasattr(options, 'deps_os'):
1246 options.deps_os = None
maruel@chromium.orge3216c62010-07-08 03:31:43 +00001247 if not hasattr(options, 'manually_grab_svn_rev'):
1248 options.manually_grab_svn_rev = None
1249 if not hasattr(options, 'force'):
1250 options.force = None
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001251 return (options, args)
1252 parser.parse_args = Parse
1253 # We don't want wordwrapping in epilog (usually examples)
1254 parser.format_epilog = lambda _: parser.epilog or ''
1255 if argv:
1256 command = Command(argv[0])
1257 if command:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001258 # 'fix' the usage and the description now that we know the subcommand.
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001259 GenUsage(parser, argv[0])
1260 return command(parser, argv[1:])
1261 # Not a known command. Default to help.
1262 GenUsage(parser, 'help')
1263 return CMDhelp(parser, argv)
1264 except gclient_utils.Error, e:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001265 print >> sys.stderr, 'Error: %s' % str(e)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001266 return 1
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001267
1268
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001269if '__main__' == __name__:
maruel@chromium.org35625c72011-03-23 17:34:02 +00001270 fix_encoding.fix_encoding()
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001271 sys.exit(Main(sys.argv[1:]))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001272
1273# vim: ts=2:sw=2:tw=80:et: