blob: 9a678220f117760ad2c9c5c4cdefa21705a35839 [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@chromium.org31cb48a2011-04-04 18:01:36 +000071import subprocess2
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000072
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000073
maruel@chromium.orgcb2985f2010-11-03 14:08:31 +000074def attr(attribute, data):
maruel@chromium.org1f7d1182010-05-17 18:17:38 +000075 """Sets an attribute on a function."""
76 def hook(fn):
maruel@chromium.orgcb2985f2010-11-03 14:08:31 +000077 setattr(fn, attribute, data)
maruel@chromium.org1f7d1182010-05-17 18:17:38 +000078 return fn
79 return hook
maruel@chromium.orge3da35f2010-03-09 21:40:45 +000080
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000081
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000082## GClient implementation.
83
84
maruel@chromium.org116704f2010-06-11 17:34:38 +000085class GClientKeywords(object):
86 class FromImpl(object):
87 """Used to implement the From() syntax."""
88
89 def __init__(self, module_name, sub_target_name=None):
90 """module_name is the dep module we want to include from. It can also be
91 the name of a subdirectory to include from.
92
93 sub_target_name is an optional parameter if the module name in the other
94 DEPS file is different. E.g., you might want to map src/net to net."""
95 self.module_name = module_name
96 self.sub_target_name = sub_target_name
97
98 def __str__(self):
99 return 'From(%s, %s)' % (repr(self.module_name),
100 repr(self.sub_target_name))
101
maruel@chromium.org116704f2010-06-11 17:34:38 +0000102 class FileImpl(object):
103 """Used to implement the File('') syntax which lets you sync a single file
maruel@chromium.orge3216c62010-07-08 03:31:43 +0000104 from a SVN repo."""
maruel@chromium.org116704f2010-06-11 17:34:38 +0000105
106 def __init__(self, file_location):
107 self.file_location = file_location
108
109 def __str__(self):
110 return 'File("%s")' % self.file_location
111
112 def GetPath(self):
113 return os.path.split(self.file_location)[0]
114
115 def GetFilename(self):
116 rev_tokens = self.file_location.split('@')
117 return os.path.split(rev_tokens[0])[1]
118
119 def GetRevision(self):
120 rev_tokens = self.file_location.split('@')
121 if len(rev_tokens) > 1:
122 return rev_tokens[1]
123 return None
124
125 class VarImpl(object):
126 def __init__(self, custom_vars, local_scope):
127 self._custom_vars = custom_vars
128 self._local_scope = local_scope
129
130 def Lookup(self, var_name):
131 """Implements the Var syntax."""
132 if var_name in self._custom_vars:
133 return self._custom_vars[var_name]
134 elif var_name in self._local_scope.get("vars", {}):
135 return self._local_scope["vars"][var_name]
136 raise gclient_utils.Error("Var is not defined: %s" % var_name)
137
138
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000139class Dependency(GClientKeywords, gclient_utils.WorkItem):
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000140 """Object that represents a dependency checkout."""
maruel@chromium.org9eda4112010-06-11 18:56:10 +0000141 DEPS_FILE = 'DEPS'
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000142
maruel@chromium.org0d812442010-08-10 12:41:08 +0000143 def __init__(self, parent, name, url, safesync_url, custom_deps,
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000144 custom_vars, deps_file, should_process):
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000145 GClientKeywords.__init__(self)
maruel@chromium.org6985efc2010-09-08 13:26:12 +0000146 gclient_utils.WorkItem.__init__(self)
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000147 self.parent = parent
148 self.name = name
149 self.url = url
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000150 self.parsed_url = None
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000151 # These 2 are only set in .gclient and not in DEPS files.
152 self.safesync_url = safesync_url
153 self.custom_vars = custom_vars or {}
154 self.custom_deps = custom_deps or {}
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000155 self.deps_hooks = []
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000156 self.dependencies = []
157 self.deps_file = deps_file or self.DEPS_FILE
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000158 # A cache of the files affected by the current operation, necessary for
159 # hooks.
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000160 self._file_list = []
maruel@chromium.org85c2a192010-07-22 21:14:43 +0000161 # If it is not set to True, the dependency wasn't processed for its child
162 # dependency, i.e. its DEPS wasn't read.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000163 self.deps_parsed = False
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000164 # This dependency should be processed, i.e. checked out
165 self.should_process = should_process
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000166 # This dependency has been processed, i.e. checked out
167 self.processed = False
168 # This dependency had its hook run
169 self.hooks_ran = False
maruel@chromium.org621939b2010-08-10 20:12:00 +0000170 # Required dependencies to run before running this one:
171 self.requirements = []
172 if self.parent and self.parent.name:
173 self.requirements.append(self.parent.name)
174 if isinstance(self.url, self.FromImpl):
175 self.requirements.append(self.url.module_name)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000176
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000177 # Sanity checks
178 if not self.name and self.parent:
179 raise gclient_utils.Error('Dependency without name')
180 if not isinstance(self.url,
181 (basestring, self.FromImpl, self.FileImpl, None.__class__)):
182 raise gclient_utils.Error('dependency url must be either a string, None, '
183 'File() or From() instead of %s' %
184 self.url.__class__.__name__)
185 if '/' in self.deps_file or '\\' in self.deps_file:
186 raise gclient_utils.Error('deps_file name must not be a path, just a '
187 'filename. %s' % self.deps_file)
188
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000189 def LateOverride(self, url):
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000190 """Resolves the parsed url from url.
191
192 Manages From() keyword accordingly. Do not touch self.parsed_url nor
193 self.url because it may called with other urls due to From()."""
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000194 assert self.parsed_url == None or not self.should_process, self.parsed_url
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000195 overriden_url = self.get_custom_deps(self.name, url)
196 if overriden_url != url:
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000197 logging.info('%s, %s was overriden to %s' % (self.name, url,
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000198 overriden_url))
199 return overriden_url
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000200 elif isinstance(url, self.FromImpl):
201 ref = [dep for dep in self.tree(True) if url.module_name == dep.name]
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000202 if not ref:
203 raise gclient_utils.Error('Failed to find one reference to %s. %s' % (
204 url.module_name, ref))
205 # It may happen that len(ref) > 1 but it's no big deal.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000206 ref = ref[0]
maruel@chromium.org98d05fa2010-07-22 21:58:01 +0000207 sub_target = url.sub_target_name or self.name
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000208 # Make sure the referenced dependency DEPS file is loaded and file the
209 # inner referenced dependency.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000210 ref.ParseDepsFile()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000211 found_dep = None
212 for d in ref.dependencies:
213 if d.name == sub_target:
214 found_dep = d
215 break
216 if not found_dep:
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000217 raise gclient_utils.Error(
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000218 'Couldn\'t find %s in %s, referenced by %s\n%s' % (
219 sub_target, ref.name, self.name, str(self.root_parent())))
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000220 # Call LateOverride() again.
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000221 parsed_url = found_dep.LateOverride(found_dep.url)
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000222 logging.info('%s, %s to %s' % (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000223 return parsed_url
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000224 elif isinstance(url, basestring):
225 parsed_url = urlparse.urlparse(url)
226 if not parsed_url[0]:
227 # A relative url. Fetch the real base.
228 path = parsed_url[2]
229 if not path.startswith('/'):
230 raise gclient_utils.Error(
231 'relative DEPS entry \'%s\' must begin with a slash' % url)
232 # Create a scm just to query the full url.
233 parent_url = self.parent.parsed_url
234 if isinstance(parent_url, self.FileImpl):
235 parent_url = parent_url.file_location
236 scm = gclient_scm.CreateSCM(parent_url, self.root_dir(), None)
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000237 parsed_url = scm.FullUrlForRelativeUrl(url)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000238 else:
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000239 parsed_url = url
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000240 logging.info('%s, %s -> %s' % (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000241 return parsed_url
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000242 elif isinstance(url, self.FileImpl):
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000243 parsed_url = url
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000244 logging.info('%s, %s -> %s (File)' % (self.name, url, parsed_url))
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000245 return parsed_url
246 elif url is None:
247 return None
248 else:
249 raise gclient_utils.Error('Unkown url type')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000250
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000251 def ParseDepsFile(self):
maruel@chromium.org271375b2010-06-23 19:17:38 +0000252 """Parses the DEPS file for this dependency."""
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000253 assert self.processed == True
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000254 if self.deps_parsed:
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000255 logging.debug('%s was already parsed' % self.name)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000256 return
maruel@chromium.org271375b2010-06-23 19:17:38 +0000257 self.deps_parsed = True
maruel@chromium.org271375b2010-06-23 19:17:38 +0000258 # One thing is unintuitive, vars= {} must happen before Var() use.
259 local_scope = {}
260 var = self.VarImpl(self.custom_vars, local_scope)
261 global_scope = {
262 'File': self.FileImpl,
263 'From': self.FromImpl,
264 'Var': var.Lookup,
265 'deps_os': {},
266 }
maruel@chromium.org46304292010-10-28 11:42:00 +0000267 filepath = os.path.join(self.root_dir(), self.name, self.deps_file)
268 if not os.path.isfile(filepath):
269 logging.info('%s: No DEPS file found at %s' % (self.name, filepath))
270 else:
271 deps_content = gclient_utils.FileRead(filepath)
272 logging.debug(deps_content)
273 # Eval the content.
274 try:
275 exec(deps_content, global_scope, local_scope)
276 except SyntaxError, e:
277 gclient_utils.SyntaxErrorToError(filepath, e)
maruel@chromium.org271375b2010-06-23 19:17:38 +0000278 deps = local_scope.get('deps', {})
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000279 # load os specific dependencies if defined. these dependencies may
280 # override or extend the values defined by the 'deps' member.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000281 if 'deps_os' in local_scope:
282 for deps_os_key in self.enforced_os():
283 os_deps = local_scope['deps_os'].get(deps_os_key, {})
284 if len(self.enforced_os()) > 1:
285 # Ignore any conflict when including deps for more than one
maruel@chromium.org46304292010-10-28 11:42:00 +0000286 # platform, so we collect the broadest set of dependencies
287 # available. We may end up with the wrong revision of something for
288 # our platform, but this is the best we can do.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000289 deps.update([x for x in os_deps.items() if not x[0] in deps])
290 else:
291 deps.update(os_deps)
292
maruel@chromium.org271375b2010-06-23 19:17:38 +0000293 self.deps_hooks.extend(local_scope.get('hooks', []))
294
295 # If a line is in custom_deps, but not in the solution, we want to append
296 # this line to the solution.
297 for d in self.custom_deps:
298 if d not in deps:
299 deps[d] = self.custom_deps[d]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000300
301 # If use_relative_paths is set in the DEPS file, regenerate
302 # the dictionary using paths relative to the directory containing
303 # the DEPS file.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000304 use_relative_paths = local_scope.get('use_relative_paths', False)
305 if use_relative_paths:
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000306 rel_deps = {}
307 for d, url in deps.items():
308 # normpath is required to allow DEPS to use .. in their
309 # dependency local path.
maruel@chromium.org271375b2010-06-23 19:17:38 +0000310 rel_deps[os.path.normpath(os.path.join(self.name, d))] = url
311 deps = rel_deps
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000312
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000313 # Convert the deps into real Dependency.
314 for name, url in deps.iteritems():
315 if name in [s.name for s in self.dependencies]:
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000316 raise gclient_utils.Error(
317 'The same name "%s" appears multiple times in the deps section' %
318 name)
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000319 should_process = self.recursion_limit() > 0 and self.should_process
320 if should_process:
321 tree = dict((d.name, d) for d in self.tree(False))
322 if name in tree:
323 if url == tree[name].url:
324 logging.info('Won\'t process duplicate dependency %s' % tree[name])
325 # In theory we could keep it as a shadow of the other one. In
326 # practice, simply ignore it.
327 #should_process = False
328 continue
329 else:
330 raise gclient_utils.Error(
331 'Dependency %s specified more than once:\n %s\nvs\n %s' %
332 (name, tree[name].hierarchy(), self.hierarchy()))
maruel@chromium.org0d812442010-08-10 12:41:08 +0000333 self.dependencies.append(Dependency(self, name, url, None, None, None,
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000334 None, should_process))
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000335 logging.debug('Loaded: %s' % str(self))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000336
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +0000337 # Arguments number differs from overridden method
338 # pylint: disable=W0221
maruel@chromium.org3742c842010-09-09 19:27:14 +0000339 def run(self, revision_overrides, command, args, work_queue, options):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000340 """Runs 'command' before parsing the DEPS in case it's a initial checkout
341 or a revert."""
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000342 assert self._file_list == []
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000343 if not self.should_process:
344 return
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000345 # When running runhooks, there's no need to consult the SCM.
346 # All known hooks are expected to run unconditionally regardless of working
347 # copy state, so skip the SCM status check.
348 run_scm = command not in ('runhooks', None)
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000349 self.parsed_url = self.LateOverride(self.url)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000350 if run_scm and self.parsed_url:
351 if isinstance(self.parsed_url, self.FileImpl):
352 # Special support for single-file checkout.
353 if not command in (None, 'cleanup', 'diff', 'pack', 'status'):
354 options.revision = self.parsed_url.GetRevision()
355 scm = gclient_scm.SVNWrapper(self.parsed_url.GetPath(),
356 self.root_dir(),
357 self.name)
358 scm.RunCommand('updatesingle', options,
359 args + [self.parsed_url.GetFilename()],
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000360 self._file_list)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000361 else:
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000362 # Create a shallow copy to mutate revision.
363 options = copy.copy(options)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000364 options.revision = revision_overrides.get(self.name)
365 scm = gclient_scm.CreateSCM(self.parsed_url, self.root_dir(), self.name)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000366 scm.RunCommand(command, options, args, self._file_list)
367 self._file_list = [os.path.join(self.name, f.strip())
368 for f in self._file_list]
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000369 self.processed = True
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000370 if self.recursion_limit() > 0:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000371 # Then we can parse the DEPS file.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000372 self.ParseDepsFile()
maruel@chromium.org621939b2010-08-10 20:12:00 +0000373 # Adjust the implicit dependency requirement; e.g. if a DEPS file contains
374 # both src/foo and src/foo/bar, src/foo/bar is implicitly dependent of
maruel@chromium.org049bced2010-08-12 13:37:20 +0000375 # src/foo. Yes, it's O(n^2)... It's important to do that before
376 # enqueueing them.
maruel@chromium.org621939b2010-08-10 20:12:00 +0000377 for s in self.dependencies:
378 for s2 in self.dependencies:
379 if s is s2:
380 continue
381 if s.name.startswith(posixpath.join(s2.name, '')):
382 s.requirements.append(s2.name)
383
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000384 # Parse the dependencies of this dependency.
385 for s in self.dependencies:
maruel@chromium.org049bced2010-08-12 13:37:20 +0000386 work_queue.enqueue(s)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000387
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000388 def RunHooksRecursively(self, options):
maruel@chromium.org049bced2010-08-12 13:37:20 +0000389 """Evaluates all hooks, running actions as needed. run()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000390 must have been called before to load the DEPS."""
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000391 assert self.hooks_ran == False
392 if not self.should_process or self.recursion_limit() <= 0:
393 # Don't run the hook when it is above recursion_limit.
394 return
maruel@chromium.orgdc7445d2010-07-09 21:05:29 +0000395 # If "--force" was specified, run all hooks regardless of what files have
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000396 # changed.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000397 if self.deps_hooks:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000398 # TODO(maruel): If the user is using git or git-svn, then we don't know
399 # what files have changed so we always run all hooks. It'd be nice to fix
400 # that.
401 if (options.force or
402 isinstance(self.parsed_url, self.FileImpl) or
403 gclient_scm.GetScmName(self.parsed_url) in ('git', None) or
404 os.path.isdir(os.path.join(self.root_dir(), self.name, '.git'))):
405 for hook_dict in self.deps_hooks:
406 self._RunHookAction(hook_dict, [])
407 else:
408 # TODO(phajdan.jr): We should know exactly when the paths are absolute.
409 # Convert all absolute paths to relative.
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000410 file_list = self.file_list()
411 for i in range(len(file_list)):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000412 # It depends on the command being executed (like runhooks vs sync).
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000413 if not os.path.isabs(file_list[i]):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000414 continue
maruel@chromium.orgdc7445d2010-07-09 21:05:29 +0000415
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000416 prefix = os.path.commonprefix([self.root_dir().lower(),
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000417 file_list[i].lower()])
418 file_list[i] = file_list[i][len(prefix):]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000419
420 # Strip any leading path separators.
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000421 while (file_list[i].startswith('\\') or
422 file_list[i].startswith('/')):
423 file_list[i] = file_list[i][1:]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000424
425 # Run hooks on the basis of whether the files from the gclient operation
426 # match each hook's pattern.
427 for hook_dict in self.deps_hooks:
428 pattern = re.compile(hook_dict['pattern'])
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000429 matching_file_list = [f for f in file_list if pattern.search(f)]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000430 if matching_file_list:
431 self._RunHookAction(hook_dict, matching_file_list)
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000432 for s in self.dependencies:
433 s.RunHooksRecursively(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000434
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000435 def _RunHookAction(self, hook_dict, matching_file_list):
436 """Runs the action from a single hook."""
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000437 # A single DEPS file can specify multiple hooks so this function can be
438 # called multiple times on a single Dependency.
439 #assert self.hooks_ran == False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000440 self.hooks_ran = True
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000441 logging.debug(hook_dict)
442 logging.debug(matching_file_list)
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000443 command = hook_dict['action'][:]
444 if command[0] == 'python':
445 # If the hook specified "python" as the first item, the action is a
446 # Python script. Run it by starting a new copy of the same
447 # interpreter.
448 command[0] = sys.executable
449
450 if '$matching_files' in command:
451 splice_index = command.index('$matching_files')
452 command[splice_index:splice_index + 1] = matching_file_list
453
maruel@chromium.org17d01792010-09-01 18:07:10 +0000454 try:
455 gclient_utils.CheckCallAndFilterAndHeader(
456 command, cwd=self.root_dir(), always=True)
maruel@chromium.org31cb48a2011-04-04 18:01:36 +0000457 except (gclient_utils.Error, subprocess2.CalledProcessError), e:
maruel@chromium.org17d01792010-09-01 18:07:10 +0000458 # Use a discrete exit status code of 2 to indicate that a hook action
459 # failed. Users of this script may wish to treat hook action failures
460 # differently from VC failures.
461 print >> sys.stderr, 'Error: %s' % str(e)
462 sys.exit(2)
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000463
maruel@chromium.org271375b2010-06-23 19:17:38 +0000464 def root_dir(self):
465 return self.parent.root_dir()
466
467 def enforced_os(self):
468 return self.parent.enforced_os()
469
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000470 def recursion_limit(self):
471 return self.parent.recursion_limit() - 1
472
maruel@chromium.org0d812442010-08-10 12:41:08 +0000473 def tree(self, include_all):
474 return self.parent.tree(include_all)
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000475
maruel@chromium.org0d812442010-08-10 12:41:08 +0000476 def subtree(self, include_all):
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000477 """Breadth first"""
maruel@chromium.orgc57e4f22010-07-22 21:37:46 +0000478 result = []
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000479 for d in self.dependencies:
480 if d.should_process or include_all:
maruel@chromium.org044f4e32010-07-22 21:59:57 +0000481 result.append(d)
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000482 for d in self.dependencies:
483 result.extend(d.subtree(include_all))
maruel@chromium.orgc57e4f22010-07-22 21:37:46 +0000484 return result
485
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000486 def get_custom_deps(self, name, url):
487 """Returns a custom deps if applicable."""
488 if self.parent:
489 url = self.parent.get_custom_deps(name, url)
490 # None is a valid return value to disable a dependency.
491 return self.custom_deps.get(name, url)
492
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000493 def file_list(self):
494 result = self._file_list[:]
495 for d in self.dependencies:
496 result.extend(d.file_list())
497 return result
498
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000499 def __str__(self):
500 out = []
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000501 for i in ('name', 'url', 'parsed_url', 'safesync_url', 'custom_deps',
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000502 'custom_vars', 'deps_hooks', '_file_list', 'should_process',
503 'processed', 'hooks_ran', 'deps_parsed', 'requirements'):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000504 # 'deps_file'
505 if self.__dict__[i]:
506 out.append('%s: %s' % (i, self.__dict__[i]))
507
508 for d in self.dependencies:
509 out.extend([' ' + x for x in str(d).splitlines()])
510 out.append('')
511 return '\n'.join(out)
512
513 def __repr__(self):
514 return '%s: %s' % (self.name, self.url)
515
maruel@chromium.orgbffb9042010-07-22 20:59:36 +0000516 def hierarchy(self):
maruel@chromium.orgbc2d2f92010-07-22 21:26:48 +0000517 """Returns a human-readable hierarchical reference to a Dependency."""
maruel@chromium.orgbffb9042010-07-22 20:59:36 +0000518 out = '%s(%s)' % (self.name, self.url)
519 i = self.parent
520 while i and i.name:
521 out = '%s(%s) -> %s' % (i.name, i.url, out)
522 i = i.parent
523 return out
524
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000525 def root_parent(self):
526 """Returns the root object, normally a GClient object."""
527 d = self
528 while d.parent:
529 d = d.parent
530 return d
531
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000532
533class GClient(Dependency):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000534 """Object that represent a gclient checkout. A tree of Dependency(), one per
535 solution or DEPS entry."""
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000536
537 DEPS_OS_CHOICES = {
538 "win32": "win",
539 "win": "win",
540 "cygwin": "win",
541 "darwin": "mac",
542 "mac": "mac",
543 "unix": "unix",
544 "linux": "unix",
545 "linux2": "unix",
546 }
547
548 DEFAULT_CLIENT_FILE_TEXT = ("""\
549solutions = [
550 { "name" : "%(solution_name)s",
551 "url" : "%(solution_url)s",
552 "custom_deps" : {
553 },
maruel@chromium.org73e21142010-07-05 13:32:01 +0000554 "safesync_url": "%(safesync_url)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000555 },
556]
557""")
558
559 DEFAULT_SNAPSHOT_SOLUTION_TEXT = ("""\
560 { "name" : "%(solution_name)s",
561 "url" : "%(solution_url)s",
562 "custom_deps" : {
maruel@chromium.org73e21142010-07-05 13:32:01 +0000563%(solution_deps)s },
564 "safesync_url": "%(safesync_url)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000565 },
566""")
567
568 DEFAULT_SNAPSHOT_FILE_TEXT = ("""\
569# Snapshot generated with gclient revinfo --snapshot
570solutions = [
maruel@chromium.org73e21142010-07-05 13:32:01 +0000571%(solution_list)s]
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000572""")
573
574 def __init__(self, root_dir, options):
maruel@chromium.org0d812442010-08-10 12:41:08 +0000575 # Do not change previous behavior. Only solution level and immediate DEPS
576 # are processed.
577 self._recursion_limit = 2
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000578 Dependency.__init__(self, None, None, None, None, None, None, None, True)
maruel@chromium.org0d425922010-06-21 19:22:24 +0000579 self._options = options
maruel@chromium.org271375b2010-06-23 19:17:38 +0000580 if options.deps_os:
581 enforced_os = options.deps_os.split(',')
582 else:
583 enforced_os = [self.DEPS_OS_CHOICES.get(sys.platform, 'unix')]
584 if 'all' in enforced_os:
585 enforced_os = self.DEPS_OS_CHOICES.itervalues()
586 self._enforced_os = list(set(enforced_os))
587 self._root_dir = root_dir
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000588 self.config_content = None
589
590 def SetConfig(self, content):
591 assert self.dependencies == []
592 config_dict = {}
593 self.config_content = content
594 try:
595 exec(content, config_dict)
596 except SyntaxError, e:
maruel@chromium.org5990f9d2010-07-07 18:02:58 +0000597 gclient_utils.SyntaxErrorToError('.gclient', e)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000598 for s in config_dict.get('solutions', []):
maruel@chromium.org81843b82010-06-28 16:49:26 +0000599 try:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000600 tree = dict((d.name, d) for d in self.tree(False))
601 if s['name'] in tree:
602 raise gclient_utils.Error(
603 'Dependency %s specified more than once in .gclient' % s['name'])
maruel@chromium.org81843b82010-06-28 16:49:26 +0000604 self.dependencies.append(Dependency(
605 self, s['name'], s['url'],
606 s.get('safesync_url', None),
607 s.get('custom_deps', {}),
maruel@chromium.org0d812442010-08-10 12:41:08 +0000608 s.get('custom_vars', {}),
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000609 None,
610 True))
maruel@chromium.org81843b82010-06-28 16:49:26 +0000611 except KeyError:
612 raise gclient_utils.Error('Invalid .gclient file. Solution is '
613 'incomplete: %s' % s)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000614 # .gclient can have hooks.
615 self.deps_hooks = config_dict.get('hooks', [])
maruel@chromium.org049bced2010-08-12 13:37:20 +0000616 self.deps_parsed = True
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000617
618 def SaveConfig(self):
619 gclient_utils.FileWrite(os.path.join(self.root_dir(),
620 self._options.config_filename),
621 self.config_content)
622
623 @staticmethod
624 def LoadCurrentConfig(options):
625 """Searches for and loads a .gclient file relative to the current working
626 dir. Returns a GClient object."""
maruel@chromium.org15804092010-09-02 17:07:37 +0000627 path = gclient_utils.FindGclientRoot(os.getcwd(), options.config_filename)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000628 if not path:
629 return None
630 client = GClient(path, options)
631 client.SetConfig(gclient_utils.FileRead(
632 os.path.join(path, options.config_filename)))
maruel@chromium.org15804092010-09-02 17:07:37 +0000633 return client
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000634
635 def SetDefaultConfig(self, solution_name, solution_url, safesync_url):
636 self.SetConfig(self.DEFAULT_CLIENT_FILE_TEXT % {
637 'solution_name': solution_name,
638 'solution_url': solution_url,
639 'safesync_url' : safesync_url,
640 })
641
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000642 def _SaveEntries(self):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000643 """Creates a .gclient_entries file to record the list of unique checkouts.
644
645 The .gclient_entries file lives in the same directory as .gclient.
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000646 """
647 # Sometimes pprint.pformat will use {', sometimes it'll use { ' ... It
648 # makes testing a bit too fun.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000649 result = 'entries = {\n'
650 for entry in self.tree(False):
651 # Skip over File() dependencies as we can't version them.
652 if not isinstance(entry.parsed_url, self.FileImpl):
653 result += ' %s: %s,\n' % (pprint.pformat(entry.name),
654 pprint.pformat(entry.parsed_url))
655 result += '}\n'
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000656 file_path = os.path.join(self.root_dir(), self._options.entries_filename)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000657 logging.info(result)
658 gclient_utils.FileWrite(file_path, result)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000659
660 def _ReadEntries(self):
661 """Read the .gclient_entries file for the given client.
662
663 Returns:
664 A sequence of solution names, which will be empty if there is the
665 entries file hasn't been created yet.
666 """
667 scope = {}
668 filename = os.path.join(self.root_dir(), self._options.entries_filename)
669 if not os.path.exists(filename):
maruel@chromium.org73e21142010-07-05 13:32:01 +0000670 return {}
maruel@chromium.org5990f9d2010-07-07 18:02:58 +0000671 try:
672 exec(gclient_utils.FileRead(filename), scope)
673 except SyntaxError, e:
674 gclient_utils.SyntaxErrorToError(filename, e)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000675 return scope['entries']
676
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000677 def _EnforceRevisions(self):
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000678 """Checks for revision overrides."""
679 revision_overrides = {}
maruel@chromium.org307d1792010-05-31 20:03:13 +0000680 if self._options.head:
681 return revision_overrides
joi@chromium.org792ea882010-11-10 02:37:27 +0000682 # Do not check safesync_url if one or more --revision flag is specified.
683 if not self._options.revisions:
684 for s in self.dependencies:
685 if not s.safesync_url:
686 continue
687 handle = urllib.urlopen(s.safesync_url)
688 rev = handle.read().strip()
689 handle.close()
690 if len(rev):
691 self._options.revisions.append('%s@%s' % (s.name, rev))
maruel@chromium.org307d1792010-05-31 20:03:13 +0000692 if not self._options.revisions:
693 return revision_overrides
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000694 solutions_names = [s.name for s in self.dependencies]
maruel@chromium.org307d1792010-05-31 20:03:13 +0000695 index = 0
696 for revision in self._options.revisions:
697 if not '@' in revision:
698 # Support for --revision 123
699 revision = '%s@%s' % (solutions_names[index], revision)
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000700 sol, rev = revision.split('@', 1)
maruel@chromium.org307d1792010-05-31 20:03:13 +0000701 if not sol in solutions_names:
702 #raise gclient_utils.Error('%s is not a valid solution.' % sol)
703 print >> sys.stderr, ('Please fix your script, having invalid '
704 '--revision flags will soon considered an error.')
705 else:
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000706 revision_overrides[sol] = rev
maruel@chromium.org307d1792010-05-31 20:03:13 +0000707 index += 1
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000708 return revision_overrides
709
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000710 def RunOnDeps(self, command, args):
711 """Runs a command on each dependency in a client and its dependencies.
712
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000713 Args:
714 command: The command to use (e.g., 'status' or 'diff')
715 args: list of str - extra arguments to add to the command line.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000716 """
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000717 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +0000718 raise gclient_utils.Error('No solution specified')
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000719 revision_overrides = self._EnforceRevisions()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000720 pm = None
maruel@chromium.org5b3f8852010-09-10 16:49:54 +0000721 # Disable progress for non-tty stdout.
maruel@chromium.orga116e7d2010-10-05 19:58:02 +0000722 if (command in ('update', 'revert') and sys.stdout.isatty() and not
723 self._options.verbose):
maruel@chromium.org049bced2010-08-12 13:37:20 +0000724 pm = Progress('Syncing projects', 1)
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000725 work_queue = gclient_utils.ExecutionQueue(self._options.jobs, pm)
maruel@chromium.org049bced2010-08-12 13:37:20 +0000726 for s in self.dependencies:
727 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +0000728 work_queue.flush(revision_overrides, command, args, options=self._options)
piman@chromium.org6f363722010-04-27 00:41:09 +0000729
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000730 # Once all the dependencies have been processed, it's now safe to run the
731 # hooks.
732 if not self._options.nohooks:
733 self.RunHooksRecursively(self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000734
735 if command == 'update':
ajwong@chromium.orgcdcee802009-06-23 15:30:42 +0000736 # Notify the user if there is an orphaned entry in their working copy.
737 # Only delete the directory if there are no changes in it, and
738 # delete_unversioned_trees is set to true.
thomasvl@chromium.org9ea49d22011-03-08 15:30:47 +0000739 entries = [i.name for i in self.tree(False) if i.url]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000740 for entry, prev_url in self._ReadEntries().iteritems():
maruel@chromium.org04dd7de2010-10-14 13:25:49 +0000741 if not prev_url:
742 # entry must have been overridden via .gclient custom_deps
743 continue
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000744 # Fix path separator on Windows.
745 entry_fixed = entry.replace('/', os.path.sep)
maruel@chromium.org75a59272010-06-11 22:34:03 +0000746 e_dir = os.path.join(self.root_dir(), entry_fixed)
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000747 # Use entry and not entry_fixed there.
maruel@chromium.org0329e672009-05-13 18:41:04 +0000748 if entry not in entries and os.path.exists(e_dir):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000749 file_list = []
750 scm = gclient_scm.CreateSCM(prev_url, self.root_dir(), entry_fixed)
751 scm.status(self._options, [], file_list)
752 modified_files = file_list != []
maruel@chromium.org28d14bd2010-11-11 20:37:09 +0000753 if (not self._options.delete_unversioned_trees or
754 (modified_files and not self._options.force)):
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000755 # There are modified files in this entry. Keep warning until
756 # removed.
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000757 print(('\nWARNING: \'%s\' is no longer part of this client. '
758 'It is recommended that you manually remove it.\n') %
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000759 entry_fixed)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000760 else:
761 # Delete the entry
maruel@chromium.org73e21142010-07-05 13:32:01 +0000762 print('\n________ deleting \'%s\' in \'%s\'' % (
763 entry_fixed, self.root_dir()))
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000764 gclient_utils.RemoveDirectory(e_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000765 # record the current list of entries for next time
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000766 self._SaveEntries()
maruel@chromium.org17cdf762010-05-28 17:30:52 +0000767 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000768
769 def PrintRevInfo(self):
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000770 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +0000771 raise gclient_utils.Error('No solution specified')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000772 # Load all the settings.
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000773 work_queue = gclient_utils.ExecutionQueue(self._options.jobs, None)
maruel@chromium.org049bced2010-08-12 13:37:20 +0000774 for s in self.dependencies:
775 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +0000776 work_queue.flush({}, None, [], options=self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000777
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000778 def GetURLAndRev(dep):
779 """Returns the revision-qualified SCM url for a Dependency."""
780 if dep.parsed_url is None:
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +0000781 return None
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000782 if isinstance(dep.parsed_url, self.FileImpl):
783 original_url = dep.parsed_url.file_location
784 else:
785 original_url = dep.parsed_url
nasser@codeaurora.org5d63eb82010-03-24 23:22:09 +0000786 url, _ = gclient_utils.SplitUrlRevision(original_url)
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000787 scm = gclient_scm.CreateSCM(original_url, self.root_dir(), dep.name)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000788 if not os.path.isdir(scm.checkout_path):
789 return None
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +0000790 return '%s@%s' % (url, scm.revinfo(self._options, [], None))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000791
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +0000792 if self._options.snapshot:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000793 new_gclient = ''
794 # First level at .gclient
795 for d in self.dependencies:
796 entries = {}
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000797 def GrabDeps(dep):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000798 """Recursively grab dependencies."""
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000799 for d in dep.dependencies:
800 entries[d.name] = GetURLAndRev(d)
801 GrabDeps(d)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000802 GrabDeps(d)
803 custom_deps = []
804 for k in sorted(entries.keys()):
805 if entries[k]:
806 # Quotes aren't escaped...
807 custom_deps.append(' \"%s\": \'%s\',\n' % (k, entries[k]))
808 else:
809 custom_deps.append(' \"%s\": None,\n' % k)
810 new_gclient += self.DEFAULT_SNAPSHOT_SOLUTION_TEXT % {
811 'solution_name': d.name,
812 'solution_url': d.url,
813 'safesync_url' : d.safesync_url or '',
814 'solution_deps': ''.join(custom_deps),
815 }
816 # Print the snapshot configuration file
817 print(self.DEFAULT_SNAPSHOT_FILE_TEXT % {'solution_list': new_gclient})
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +0000818 else:
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +0000819 entries = {}
820 for d in self.tree(False):
821 if self._options.actual:
822 entries[d.name] = GetURLAndRev(d)
823 else:
824 entries[d.name] = d.parsed_url
825 keys = sorted(entries.keys())
826 for x in keys:
maruel@chromium.orgce464892010-08-12 17:12:18 +0000827 print('%s: %s' % (x, entries[x]))
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000828 logging.info(str(self))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000829
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000830 def ParseDepsFile(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000831 """No DEPS to parse for a .gclient file."""
maruel@chromium.org049bced2010-08-12 13:37:20 +0000832 raise gclient_utils.Error('Internal error')
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000833
maruel@chromium.org75a59272010-06-11 22:34:03 +0000834 def root_dir(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000835 """Root directory of gclient checkout."""
maruel@chromium.org75a59272010-06-11 22:34:03 +0000836 return self._root_dir
837
maruel@chromium.org271375b2010-06-23 19:17:38 +0000838 def enforced_os(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000839 """What deps_os entries that are to be parsed."""
maruel@chromium.org271375b2010-06-23 19:17:38 +0000840 return self._enforced_os
841
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000842 def recursion_limit(self):
843 """How recursive can each dependencies in DEPS file can load DEPS file."""
844 return self._recursion_limit
845
maruel@chromium.org0d812442010-08-10 12:41:08 +0000846 def tree(self, include_all):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000847 """Returns a flat list of all the dependencies."""
maruel@chromium.org0d812442010-08-10 12:41:08 +0000848 return self.subtree(include_all)
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000849
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000850
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000851#### gclient commands.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000852
853
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000854def CMDcleanup(parser, args):
855 """Cleans up all working copies.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +0000856
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000857Mostly svn-specific. Simply runs 'svn cleanup' for each module.
maruel@chromium.org79692d62010-05-14 18:57:13 +0000858"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000859 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
860 help='override deps for the specified (comma-separated) '
861 'platform(s); \'all\' will process all deps_os '
862 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000863 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +0000864 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000865 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000866 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000867 if options.verbose:
868 # Print out the .gclient file. This is longer than if we just printed the
869 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +0000870 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000871 return client.RunOnDeps('cleanup', args)
872
873
piman@chromium.org4b90e3a2010-07-01 20:28:26 +0000874@attr('usage', '[command] [args ...]')
875def CMDrecurse(parser, args):
876 """Operates on all the entries.
877
878 Runs a shell command on all entries.
879 """
880 # Stop parsing at the first non-arg so that these go through to the command
881 parser.disable_interspersed_args()
882 parser.add_option('-s', '--scm', action='append', default=[],
883 help='choose scm types to operate upon')
884 options, args = parser.parse_args(args)
maruel@chromium.org45e9f2d2010-10-18 13:33:46 +0000885 if not args:
886 print >> sys.stderr, 'Need to supply a command!'
887 return 1
maruel@chromium.org78cba522010-10-18 13:32:05 +0000888 root_and_entries = gclient_utils.GetGClientRootAndEntries()
889 if not root_and_entries:
890 print >> sys.stderr, (
891 'You need to run gclient sync at least once to use \'recurse\'.\n'
892 'This is because .gclient_entries needs to exist and be up to date.')
893 return 1
894 root, entries = root_and_entries
piman@chromium.org4b90e3a2010-07-01 20:28:26 +0000895 scm_set = set()
896 for scm in options.scm:
897 scm_set.update(scm.split(','))
898
899 # Pass in the SCM type as an env variable
900 env = os.environ.copy()
901
902 for path, url in entries.iteritems():
903 scm = gclient_scm.GetScmName(url)
904 if scm_set and scm not in scm_set:
905 continue
maruel@chromium.org2b9aa8e2010-08-25 20:01:42 +0000906 cwd = os.path.normpath(os.path.join(root, path))
maruel@chromium.orgac610232010-10-13 14:01:31 +0000907 if scm:
908 env['GCLIENT_SCM'] = scm
909 if url:
910 env['GCLIENT_URL'] = url
911 gclient_utils.Popen(args, cwd=cwd, env=env).communicate()
912 return 0
piman@chromium.org4b90e3a2010-07-01 20:28:26 +0000913
914
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000915@attr('usage', '[url] [safesync url]')
916def CMDconfig(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +0000917 """Create a .gclient file in the current directory.
918
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000919This specifies the configuration for further commands. After update/sync,
maruel@chromium.org79692d62010-05-14 18:57:13 +0000920top-level DEPS files in each module are read to determine dependent
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000921modules to operate on as well. If optional [url] parameter is
maruel@chromium.org79692d62010-05-14 18:57:13 +0000922provided, then configuration is read from a specified Subversion server
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000923URL.
maruel@chromium.org79692d62010-05-14 18:57:13 +0000924"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000925 parser.add_option('--spec',
926 help='create a gclient file containing the provided '
927 'string. Due to Cygwin/Python brokenness, it '
928 'probably can\'t contain any newlines.')
929 parser.add_option('--name',
930 help='overrides the default name for the solution')
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000931 (options, args) = parser.parse_args(args)
maruel@chromium.org5fc2a332010-05-26 19:37:15 +0000932 if ((options.spec and args) or len(args) > 2 or
933 (not options.spec and not args)):
934 parser.error('Inconsistent arguments. Use either --spec or one or 2 args')
935
maruel@chromium.org0329e672009-05-13 18:41:04 +0000936 if os.path.exists(options.config_filename):
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000937 raise gclient_utils.Error('%s file already exists in the current directory'
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000938 % options.config_filename)
maruel@chromium.org2806acc2009-05-15 12:33:34 +0000939 client = GClient('.', options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000940 if options.spec:
941 client.SetConfig(options.spec)
942 else:
maruel@chromium.org1ab7ffc2009-06-03 17:21:37 +0000943 base_url = args[0].rstrip('/')
iposva@chromium.org8cf7a392010-04-07 17:20:26 +0000944 if not options.name:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000945 name = base_url.split('/')[-1]
iposva@chromium.org8cf7a392010-04-07 17:20:26 +0000946 else:
947 # specify an alternate relpath for the given URL.
948 name = options.name
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000949 safesync_url = ''
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000950 if len(args) > 1:
951 safesync_url = args[1]
952 client.SetDefaultConfig(name, base_url, safesync_url)
953 client.SaveConfig()
maruel@chromium.org79692d62010-05-14 18:57:13 +0000954 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000955
956
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000957def CMDexport(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +0000958 """Wrapper for svn export for all managed directories."""
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)
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +0000964 if len(args) != 1:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000965 raise gclient_utils.Error('Need directory name')
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +0000966 client = GClient.LoadCurrentConfig(options)
967
968 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000969 raise gclient_utils.Error('client not configured; see \'gclient config\'')
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +0000970
971 if options.verbose:
972 # Print out the .gclient file. This is longer than if we just printed the
973 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +0000974 print(client.config_content)
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +0000975 return client.RunOnDeps('export', args)
976
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000977
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000978@attr('epilog', """Example:
979 gclient pack > patch.txt
980 generate simple patch for configured client and dependences
981""")
982def CMDpack(parser, args):
maruel@chromium.org79692d62010-05-14 18:57:13 +0000983 """Generate a patch which can be applied at the root of the tree.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +0000984
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000985Internally, runs 'svn diff'/'git diff' on each checked out module and
maruel@chromium.org79692d62010-05-14 18:57:13 +0000986dependencies, and performs minimal postprocessing of the output. The
987resulting patch is printed to stdout and can be applied to a freshly
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000988checked out tree via 'patch -p0 < patchfile'.
maruel@chromium.org79692d62010-05-14 18:57:13 +0000989"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000990 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
991 help='override deps for the specified (comma-separated) '
992 'platform(s); \'all\' will process all deps_os '
993 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000994 (options, args) = parser.parse_args(args)
kbr@google.comab318592009-09-04 00:54:55 +0000995 client = GClient.LoadCurrentConfig(options)
996 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000997 raise gclient_utils.Error('client not configured; see \'gclient config\'')
kbr@google.comab318592009-09-04 00:54:55 +0000998 if options.verbose:
999 # Print out the .gclient file. This is longer than if we just printed the
1000 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001001 print(client.config_content)
kbr@google.comab318592009-09-04 00:54:55 +00001002 return client.RunOnDeps('pack', args)
1003
1004
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001005def CMDstatus(parser, args):
1006 """Show modification status for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001007 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1008 help='override deps for the specified (comma-separated) '
1009 'platform(s); \'all\' will process all deps_os '
1010 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001011 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001012 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001013 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001014 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001015 if options.verbose:
1016 # Print out the .gclient file. This is longer than if we just printed the
1017 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001018 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001019 return client.RunOnDeps('status', args)
1020
1021
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001022@attr('epilog', """Examples:
maruel@chromium.org79692d62010-05-14 18:57:13 +00001023 gclient sync
1024 update files from SCM according to current configuration,
1025 *for modules which have changed since last update or sync*
1026 gclient sync --force
1027 update files from SCM according to current configuration, for
1028 all modules (useful for recovering files deleted from local copy)
1029 gclient sync --revision src@31000
1030 update src directory to r31000
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001031""")
1032def CMDsync(parser, args):
1033 """Checkout/update all modules."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001034 parser.add_option('-f', '--force', action='store_true',
1035 help='force update even for unchanged modules')
1036 parser.add_option('-n', '--nohooks', action='store_true',
1037 help='don\'t run hooks after the update is complete')
1038 parser.add_option('-r', '--revision', action='append',
1039 dest='revisions', metavar='REV', default=[],
1040 help='Enforces revision/hash for the solutions with the '
1041 'format src@rev. The src@ part is optional and can be '
1042 'skipped. -r can be used multiple times when .gclient '
1043 'has multiple solutions configured and will work even '
joi@chromium.org792ea882010-11-10 02:37:27 +00001044 'if the src@ part is skipped. Note that specifying '
1045 '--revision means your safesync_url gets ignored.')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001046 parser.add_option('-H', '--head', action='store_true',
1047 help='skips any safesync_urls specified in '
1048 'configured solutions and sync to head instead')
1049 parser.add_option('-D', '--delete_unversioned_trees', action='store_true',
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001050 help='delete any dependency that have been removed from '
1051 'last sync as long as there is no local modification. '
1052 'Coupled with --force, it will remove them even with '
1053 'local modifications')
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001054 parser.add_option('-R', '--reset', action='store_true',
1055 help='resets any local changes before updating (git only)')
1056 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1057 help='override deps for the specified (comma-separated) '
1058 'platform(s); \'all\' will process all deps_os '
1059 'references')
1060 parser.add_option('-m', '--manually_grab_svn_rev', action='store_true',
1061 help='Skip svn up whenever possible by requesting '
1062 'actual HEAD revision from the repository')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001063 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001064 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001065
1066 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001067 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001068
maruel@chromium.org307d1792010-05-31 20:03:13 +00001069 if options.revisions and options.head:
1070 # TODO(maruel): Make it a parser.error if it doesn't break any builder.
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001071 print('Warning: you cannot use both --head and --revision')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001072
1073 if options.verbose:
1074 # Print out the .gclient file. This is longer than if we just printed the
1075 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001076 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001077 return client.RunOnDeps('update', args)
1078
1079
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001080def CMDupdate(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001081 """Alias for the sync command. Deprecated."""
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001082 return CMDsync(parser, args)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001083
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001084def CMDdiff(parser, args):
1085 """Displays local diff for every dependencies."""
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')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001090 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001091 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001092 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001093 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001094 if options.verbose:
1095 # Print out the .gclient file. This is longer than if we just printed the
1096 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001097 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001098 return client.RunOnDeps('diff', args)
1099
1100
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001101def CMDrevert(parser, args):
maruel@chromium.org28d14bd2010-11-11 20:37:09 +00001102 """Revert all modifications in every dependencies.
1103
1104 That's the nuclear option to get back to a 'clean' state. It removes anything
1105 that shows up in svn status."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001106 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1107 help='override deps for the specified (comma-separated) '
1108 'platform(s); \'all\' will process all deps_os '
1109 'references')
1110 parser.add_option('-n', '--nohooks', action='store_true',
1111 help='don\'t run hooks after the revert is complete')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001112 (options, args) = parser.parse_args(args)
1113 # --force is implied.
1114 options.force = True
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001115 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001116 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001117 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001118 return client.RunOnDeps('revert', args)
1119
1120
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001121def CMDrunhooks(parser, args):
1122 """Runs hooks for files that have been modified in the local working copy."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001123 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1124 help='override deps for the specified (comma-separated) '
1125 'platform(s); \'all\' will process all deps_os '
1126 'references')
1127 parser.add_option('-f', '--force', action='store_true', default=True,
1128 help='Deprecated. No effect.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001129 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001130 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001131 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001132 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001133 if options.verbose:
1134 # Print out the .gclient file. This is longer than if we just printed the
1135 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001136 print(client.config_content)
maruel@chromium.org5df6a462009-08-28 18:52:26 +00001137 options.force = True
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001138 options.nohooks = False
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001139 return client.RunOnDeps('runhooks', args)
1140
1141
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001142def CMDrevinfo(parser, args):
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001143 """Output revision info mapping for the client and its dependencies.
1144
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001145 This allows the capture of an overall 'revision' for the source tree that
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001146 can be used to reproduce the same tree in the future. It is only useful for
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001147 'unpinned dependencies', i.e. DEPS/deps references without a svn revision
1148 number or a git hash. A git branch name isn't 'pinned' since the actual
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001149 commit can change.
1150 """
1151 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1152 help='override deps for the specified (comma-separated) '
1153 'platform(s); \'all\' will process all deps_os '
1154 'references')
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001155 parser.add_option('-a', '--actual', action='store_true',
1156 help='gets the actual checked out revisions instead of the '
1157 'ones specified in the DEPS and .gclient files')
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001158 parser.add_option('-s', '--snapshot', action='store_true',
1159 help='creates a snapshot .gclient file of the current '
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001160 'version of all repositories to reproduce the tree, '
1161 'implies -a')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001162 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001163 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001164 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001165 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001166 client.PrintRevInfo()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001167 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001168
1169
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001170def Command(name):
1171 return getattr(sys.modules[__name__], 'CMD' + name, None)
1172
1173
1174def CMDhelp(parser, args):
1175 """Prints list of commands or help for a specific command."""
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001176 (_, args) = parser.parse_args(args)
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001177 if len(args) == 1:
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001178 return Main(args + ['--help'])
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001179 parser.print_help()
1180 return 0
1181
1182
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001183def GenUsage(parser, command):
1184 """Modify an OptParse object with the function's documentation."""
1185 obj = Command(command)
1186 if command == 'help':
1187 command = '<command>'
1188 # OptParser.description prefer nicely non-formatted strings.
1189 parser.description = re.sub('[\r\n ]{2,}', ' ', obj.__doc__)
1190 usage = getattr(obj, 'usage', '')
1191 parser.set_usage('%%prog %s [options] %s' % (command, usage))
1192 parser.epilog = getattr(obj, 'epilog', None)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001193
1194
1195def Main(argv):
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001196 """Doesn't parse the arguments here, just find the right subcommand to
1197 execute."""
maruel@chromium.orgc3a15a22010-11-20 03:12:27 +00001198 if sys.hexversion < 0x02050000:
1199 print >> sys.stderr, (
1200 '\nYour python version is unsupported, please upgrade.\n')
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001201 try:
maruel@chromium.orge0de9cb2010-09-17 15:07:14 +00001202 # Make stdout auto-flush so buildbot doesn't kill us during lengthy
1203 # operations. Python as a strong tendency to buffer sys.stdout.
1204 sys.stdout = gclient_utils.MakeFileAutoFlush(sys.stdout)
maruel@chromium.org4ed34182010-09-17 15:57:47 +00001205 # Make stdout annotated with the thread ids.
1206 sys.stdout = gclient_utils.MakeFileAnnotated(sys.stdout)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001207 # Do it late so all commands are listed.
maruel@chromium.orgb17b55b2010-11-03 14:42:37 +00001208 # Unused variable 'usage'
1209 # pylint: disable=W0612
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001210 CMDhelp.usage = ('\n\nCommands are:\n' + '\n'.join([
1211 ' %-10s %s' % (fn[3:], Command(fn[3:]).__doc__.split('\n')[0].strip())
1212 for fn in dir(sys.modules[__name__]) if fn.startswith('CMD')]))
1213 parser = optparse.OptionParser(version='%prog ' + __version__)
maruel@chromium.orgfac9d3d2010-09-10 20:38:49 +00001214 parser.add_option('-j', '--jobs', default=1, type='int',
maruel@chromium.org3b84d4c2010-09-10 18:02:43 +00001215 help='Specify how many SCM commands can run in parallel; '
1216 'default=%default')
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001217 parser.add_option('-v', '--verbose', action='count', default=0,
1218 help='Produces additional output for diagnostics. Can be '
1219 'used up to three times for more logging info.')
1220 parser.add_option('--gclientfile', dest='config_filename',
1221 default=os.environ.get('GCLIENT_FILE', '.gclient'),
1222 help='Specify an alternate %default file')
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001223 # Integrate standard options processing.
1224 old_parser = parser.parse_args
1225 def Parse(args):
1226 (options, args) = old_parser(args)
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001227 level = None
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001228 if options.verbose == 2:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001229 level = logging.INFO
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001230 elif options.verbose > 2:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001231 level = logging.DEBUG
1232 logging.basicConfig(level=level,
1233 format='%(module)s(%(lineno)d) %(funcName)s:%(message)s')
1234 options.entries_filename = options.config_filename + '_entries'
maruel@chromium.org9e5317a2010-08-13 20:35:11 +00001235 if options.jobs < 1:
1236 parser.error('--jobs must be 1 or higher')
maruel@chromium.orge3216c62010-07-08 03:31:43 +00001237
1238 # These hacks need to die.
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001239 if not hasattr(options, 'revisions'):
1240 # GClient.RunOnDeps expects it even if not applicable.
1241 options.revisions = []
1242 if not hasattr(options, 'head'):
1243 options.head = None
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001244 if not hasattr(options, 'nohooks'):
1245 options.nohooks = True
1246 if not hasattr(options, 'deps_os'):
1247 options.deps_os = None
maruel@chromium.orge3216c62010-07-08 03:31:43 +00001248 if not hasattr(options, 'manually_grab_svn_rev'):
1249 options.manually_grab_svn_rev = None
1250 if not hasattr(options, 'force'):
1251 options.force = None
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001252 return (options, args)
1253 parser.parse_args = Parse
1254 # We don't want wordwrapping in epilog (usually examples)
1255 parser.format_epilog = lambda _: parser.epilog or ''
1256 if argv:
1257 command = Command(argv[0])
1258 if command:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001259 # 'fix' the usage and the description now that we know the subcommand.
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001260 GenUsage(parser, argv[0])
1261 return command(parser, argv[1:])
1262 # Not a known command. Default to help.
1263 GenUsage(parser, 'help')
1264 return CMDhelp(parser, argv)
maruel@chromium.org31cb48a2011-04-04 18:01:36 +00001265 except (gclient_utils.Error, subprocess2.CalledProcessError), e:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001266 print >> sys.stderr, 'Error: %s' % str(e)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001267 return 1
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001268
1269
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001270if '__main__' == __name__:
maruel@chromium.org35625c72011-03-23 17:34:02 +00001271 fix_encoding.fix_encoding()
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001272 sys.exit(Main(sys.argv[1:]))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001273
1274# vim: ts=2:sw=2:tw=80:et: