blob: 0d1e2f93f2f950066e8969fc208cba195a067b1a [file] [log] [blame]
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001#!/usr/bin/python
maruel@chromium.orgba551772010-02-03 18:21:42 +00002# Copyright (c) 2010 The Chromium Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00005
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00006"""Meta checkout manager supporting both Subversion and GIT.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00007
8Files
9 .gclient : Current client configuration, written by 'config' command.
10 Format is a Python script defining 'solutions', a list whose
11 entries each are maps binding the strings "name" and "url"
12 to strings specifying the name and location of the client
13 module, as well as "custom_deps" to a map similar to the DEPS
14 file below.
15 .gclient_entries : A cache constructed by 'update' command. Format is a
16 Python script defining 'entries', a list of the names
17 of all modules in the client
18 <module>/DEPS : Python script defining var 'deps' as a map from each requisite
19 submodule name to a URL where it can be found (via one SCM)
20
21Hooks
22 .gclient and DEPS files may optionally contain a list named "hooks" to
23 allow custom actions to be performed based on files that have changed in the
evan@chromium.org67820ef2009-07-27 17:23:00 +000024 working copy as a result of a "sync"/"update" or "revert" operation. This
maruel@chromium.org0b6a0842010-06-15 14:34:19 +000025 can be prevented by using --nohooks (hooks run by default). Hooks can also
maruel@chromium.org5df6a462009-08-28 18:52:26 +000026 be forced to run with the "runhooks" operation. If "sync" is run with
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000027 --force, all known hooks will run regardless of the state of the working
28 copy.
29
30 Each item in a "hooks" list is a dict, containing these two keys:
31 "pattern" The associated value is a string containing a regular
32 expression. When a file whose pathname matches the expression
33 is checked out, updated, or reverted, the hook's "action" will
34 run.
35 "action" A list describing a command to run along with its arguments, if
36 any. An action command will run at most one time per gclient
37 invocation, regardless of how many files matched the pattern.
38 The action is executed in the same directory as the .gclient
39 file. If the first item in the list is the string "python",
40 the current Python interpreter (sys.executable) will be used
phajdan.jr@chromium.org71b40682009-07-31 23:40:09 +000041 to run the command. If the list contains string "$matching_files"
42 it will be removed from the list and the list will be extended
43 by the list of matching files.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000044
45 Example:
46 hooks = [
47 { "pattern": "\\.(gif|jpe?g|pr0n|png)$",
48 "action": ["python", "image_indexer.py", "--all"]},
49 ]
50"""
51
maruel@chromium.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
piman@chromium.org4b90e3a2010-07-01 20:28:26 +000061import subprocess
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000062import sys
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000063import urlparse
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000064import urllib
65
maruel@chromium.orgada4c652009-12-03 15:32:01 +000066import breakpad
67
maruel@chromium.org5f3eee32009-09-17 00:34:30 +000068import gclient_scm
69import gclient_utils
nasser@codeaurora.org1f7a3d12010-02-04 15:11:50 +000070from third_party.repo.progress import Progress
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000071
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000072
maruel@chromium.org1f7d1182010-05-17 18:17:38 +000073def attr(attr, data):
74 """Sets an attribute on a function."""
75 def hook(fn):
76 setattr(fn, attr, data)
77 return fn
78 return hook
maruel@chromium.orge3da35f2010-03-09 21:40:45 +000079
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000080
maruel@google.comfb2b8eb2009-04-23 21:03:42 +000081## GClient implementation.
82
83
maruel@chromium.org116704f2010-06-11 17:34:38 +000084class GClientKeywords(object):
85 class FromImpl(object):
86 """Used to implement the From() syntax."""
87
88 def __init__(self, module_name, sub_target_name=None):
89 """module_name is the dep module we want to include from. It can also be
90 the name of a subdirectory to include from.
91
92 sub_target_name is an optional parameter if the module name in the other
93 DEPS file is different. E.g., you might want to map src/net to net."""
94 self.module_name = module_name
95 self.sub_target_name = sub_target_name
96
97 def __str__(self):
98 return 'From(%s, %s)' % (repr(self.module_name),
99 repr(self.sub_target_name))
100
maruel@chromium.org116704f2010-06-11 17:34:38 +0000101 class FileImpl(object):
102 """Used to implement the File('') syntax which lets you sync a single file
maruel@chromium.orge3216c62010-07-08 03:31:43 +0000103 from a SVN repo."""
maruel@chromium.org116704f2010-06-11 17:34:38 +0000104
105 def __init__(self, file_location):
106 self.file_location = file_location
107
108 def __str__(self):
109 return 'File("%s")' % self.file_location
110
111 def GetPath(self):
112 return os.path.split(self.file_location)[0]
113
114 def GetFilename(self):
115 rev_tokens = self.file_location.split('@')
116 return os.path.split(rev_tokens[0])[1]
117
118 def GetRevision(self):
119 rev_tokens = self.file_location.split('@')
120 if len(rev_tokens) > 1:
121 return rev_tokens[1]
122 return None
123
124 class VarImpl(object):
125 def __init__(self, custom_vars, local_scope):
126 self._custom_vars = custom_vars
127 self._local_scope = local_scope
128
129 def Lookup(self, var_name):
130 """Implements the Var syntax."""
131 if var_name in self._custom_vars:
132 return self._custom_vars[var_name]
133 elif var_name in self._local_scope.get("vars", {}):
134 return self._local_scope["vars"][var_name]
135 raise gclient_utils.Error("Var is not defined: %s" % var_name)
136
137
maruel@chromium.org80cbe8b2010-08-13 13:53:07 +0000138class Dependency(GClientKeywords, gclient_utils.WorkItem):
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000139 """Object that represents a dependency checkout."""
maruel@chromium.org9eda4112010-06-11 18:56:10 +0000140 DEPS_FILE = 'DEPS'
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000141
maruel@chromium.org0d812442010-08-10 12:41:08 +0000142 def __init__(self, parent, name, url, safesync_url, custom_deps,
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000143 custom_vars, deps_file, should_process):
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000144 GClientKeywords.__init__(self)
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.org3742c842010-09-09 19:27:14 +0000336 def run(self, revision_overrides, command, args, work_queue, options):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000337 """Runs 'command' before parsing the DEPS in case it's a initial checkout
338 or a revert."""
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000339 assert self._file_list == []
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000340 if not self.should_process:
341 return
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000342 # When running runhooks, there's no need to consult the SCM.
343 # All known hooks are expected to run unconditionally regardless of working
344 # copy state, so skip the SCM status check.
345 run_scm = command not in ('runhooks', None)
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000346 self.parsed_url = self.LateOverride(self.url)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000347 if run_scm and self.parsed_url:
348 if isinstance(self.parsed_url, self.FileImpl):
349 # Special support for single-file checkout.
350 if not command in (None, 'cleanup', 'diff', 'pack', 'status'):
351 options.revision = self.parsed_url.GetRevision()
352 scm = gclient_scm.SVNWrapper(self.parsed_url.GetPath(),
353 self.root_dir(),
354 self.name)
355 scm.RunCommand('updatesingle', options,
356 args + [self.parsed_url.GetFilename()],
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000357 self._file_list)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000358 else:
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000359 # Create a shallow copy to mutate revision.
360 options = copy.copy(options)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000361 options.revision = revision_overrides.get(self.name)
362 scm = gclient_scm.CreateSCM(self.parsed_url, self.root_dir(), self.name)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000363 scm.RunCommand(command, options, args, self._file_list)
364 self._file_list = [os.path.join(self.name, f.strip())
365 for f in self._file_list]
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000366 self.processed = True
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000367 if self.recursion_limit() > 0:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000368 # Then we can parse the DEPS file.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000369 self.ParseDepsFile()
maruel@chromium.org621939b2010-08-10 20:12:00 +0000370 # Adjust the implicit dependency requirement; e.g. if a DEPS file contains
371 # both src/foo and src/foo/bar, src/foo/bar is implicitly dependent of
maruel@chromium.org049bced2010-08-12 13:37:20 +0000372 # src/foo. Yes, it's O(n^2)... It's important to do that before
373 # enqueueing them.
maruel@chromium.org621939b2010-08-10 20:12:00 +0000374 for s in self.dependencies:
375 for s2 in self.dependencies:
376 if s is s2:
377 continue
378 if s.name.startswith(posixpath.join(s2.name, '')):
379 s.requirements.append(s2.name)
380
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000381 # Parse the dependencies of this dependency.
382 for s in self.dependencies:
maruel@chromium.org049bced2010-08-12 13:37:20 +0000383 work_queue.enqueue(s)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000384
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000385 def RunHooksRecursively(self, options):
maruel@chromium.org049bced2010-08-12 13:37:20 +0000386 """Evaluates all hooks, running actions as needed. run()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000387 must have been called before to load the DEPS."""
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000388 assert self.hooks_ran == False
389 if not self.should_process or self.recursion_limit() <= 0:
390 # Don't run the hook when it is above recursion_limit.
391 return
maruel@chromium.orgdc7445d2010-07-09 21:05:29 +0000392 # If "--force" was specified, run all hooks regardless of what files have
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000393 # changed.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000394 if self.deps_hooks:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000395 # TODO(maruel): If the user is using git or git-svn, then we don't know
396 # what files have changed so we always run all hooks. It'd be nice to fix
397 # that.
398 if (options.force or
399 isinstance(self.parsed_url, self.FileImpl) or
400 gclient_scm.GetScmName(self.parsed_url) in ('git', None) or
401 os.path.isdir(os.path.join(self.root_dir(), self.name, '.git'))):
402 for hook_dict in self.deps_hooks:
403 self._RunHookAction(hook_dict, [])
404 else:
405 # TODO(phajdan.jr): We should know exactly when the paths are absolute.
406 # Convert all absolute paths to relative.
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000407 file_list = self.file_list()
408 for i in range(len(file_list)):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000409 # It depends on the command being executed (like runhooks vs sync).
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000410 if not os.path.isabs(file_list[i]):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000411 continue
maruel@chromium.orgdc7445d2010-07-09 21:05:29 +0000412
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000413 prefix = os.path.commonprefix([self.root_dir().lower(),
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000414 file_list[i].lower()])
415 file_list[i] = file_list[i][len(prefix):]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000416
417 # Strip any leading path separators.
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000418 while (file_list[i].startswith('\\') or
419 file_list[i].startswith('/')):
420 file_list[i] = file_list[i][1:]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000421
422 # Run hooks on the basis of whether the files from the gclient operation
423 # match each hook's pattern.
424 for hook_dict in self.deps_hooks:
425 pattern = re.compile(hook_dict['pattern'])
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000426 matching_file_list = [f for f in file_list if pattern.search(f)]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000427 if matching_file_list:
428 self._RunHookAction(hook_dict, matching_file_list)
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000429 for s in self.dependencies:
430 s.RunHooksRecursively(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000431
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000432 def _RunHookAction(self, hook_dict, matching_file_list):
433 """Runs the action from a single hook."""
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000434 # A single DEPS file can specify multiple hooks so this function can be
435 # called multiple times on a single Dependency.
436 #assert self.hooks_ran == False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000437 self.hooks_ran = True
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000438 logging.debug(hook_dict)
439 logging.debug(matching_file_list)
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000440 command = hook_dict['action'][:]
441 if command[0] == 'python':
442 # If the hook specified "python" as the first item, the action is a
443 # Python script. Run it by starting a new copy of the same
444 # interpreter.
445 command[0] = sys.executable
446
447 if '$matching_files' in command:
448 splice_index = command.index('$matching_files')
449 command[splice_index:splice_index + 1] = matching_file_list
450
maruel@chromium.org17d01792010-09-01 18:07:10 +0000451 try:
452 gclient_utils.CheckCallAndFilterAndHeader(
453 command, cwd=self.root_dir(), always=True)
454 except gclient_utils.Error, e:
455 # Use a discrete exit status code of 2 to indicate that a hook action
456 # failed. Users of this script may wish to treat hook action failures
457 # differently from VC failures.
458 print >> sys.stderr, 'Error: %s' % str(e)
459 sys.exit(2)
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000460
maruel@chromium.org271375b2010-06-23 19:17:38 +0000461 def root_dir(self):
462 return self.parent.root_dir()
463
464 def enforced_os(self):
465 return self.parent.enforced_os()
466
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000467 def recursion_limit(self):
468 return self.parent.recursion_limit() - 1
469
maruel@chromium.org0d812442010-08-10 12:41:08 +0000470 def tree(self, include_all):
471 return self.parent.tree(include_all)
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000472
maruel@chromium.org0d812442010-08-10 12:41:08 +0000473 def subtree(self, include_all):
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000474 """Breadth first"""
maruel@chromium.orgc57e4f22010-07-22 21:37:46 +0000475 result = []
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000476 for d in self.dependencies:
477 if d.should_process or include_all:
maruel@chromium.org044f4e32010-07-22 21:59:57 +0000478 result.append(d)
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000479 for d in self.dependencies:
480 result.extend(d.subtree(include_all))
maruel@chromium.orgc57e4f22010-07-22 21:37:46 +0000481 return result
482
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000483 def get_custom_deps(self, name, url):
484 """Returns a custom deps if applicable."""
485 if self.parent:
486 url = self.parent.get_custom_deps(name, url)
487 # None is a valid return value to disable a dependency.
488 return self.custom_deps.get(name, url)
489
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000490 def file_list(self):
491 result = self._file_list[:]
492 for d in self.dependencies:
493 result.extend(d.file_list())
494 return result
495
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000496 def __str__(self):
497 out = []
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000498 for i in ('name', 'url', 'parsed_url', 'safesync_url', 'custom_deps',
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000499 'custom_vars', 'deps_hooks', '_file_list', 'should_process',
500 'processed', 'hooks_ran', 'deps_parsed', 'requirements'):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000501 # 'deps_file'
502 if self.__dict__[i]:
503 out.append('%s: %s' % (i, self.__dict__[i]))
504
505 for d in self.dependencies:
506 out.extend([' ' + x for x in str(d).splitlines()])
507 out.append('')
508 return '\n'.join(out)
509
510 def __repr__(self):
511 return '%s: %s' % (self.name, self.url)
512
maruel@chromium.orgbffb9042010-07-22 20:59:36 +0000513 def hierarchy(self):
maruel@chromium.orgbc2d2f92010-07-22 21:26:48 +0000514 """Returns a human-readable hierarchical reference to a Dependency."""
maruel@chromium.orgbffb9042010-07-22 20:59:36 +0000515 out = '%s(%s)' % (self.name, self.url)
516 i = self.parent
517 while i and i.name:
518 out = '%s(%s) -> %s' % (i.name, i.url, out)
519 i = i.parent
520 return out
521
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000522 def root_parent(self):
523 """Returns the root object, normally a GClient object."""
524 d = self
525 while d.parent:
526 d = d.parent
527 return d
528
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000529
530class GClient(Dependency):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000531 """Object that represent a gclient checkout. A tree of Dependency(), one per
532 solution or DEPS entry."""
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000533
534 DEPS_OS_CHOICES = {
535 "win32": "win",
536 "win": "win",
537 "cygwin": "win",
538 "darwin": "mac",
539 "mac": "mac",
540 "unix": "unix",
541 "linux": "unix",
542 "linux2": "unix",
543 }
544
545 DEFAULT_CLIENT_FILE_TEXT = ("""\
546solutions = [
547 { "name" : "%(solution_name)s",
548 "url" : "%(solution_url)s",
549 "custom_deps" : {
550 },
maruel@chromium.org73e21142010-07-05 13:32:01 +0000551 "safesync_url": "%(safesync_url)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000552 },
553]
554""")
555
556 DEFAULT_SNAPSHOT_SOLUTION_TEXT = ("""\
557 { "name" : "%(solution_name)s",
558 "url" : "%(solution_url)s",
559 "custom_deps" : {
maruel@chromium.org73e21142010-07-05 13:32:01 +0000560%(solution_deps)s },
561 "safesync_url": "%(safesync_url)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000562 },
563""")
564
565 DEFAULT_SNAPSHOT_FILE_TEXT = ("""\
566# Snapshot generated with gclient revinfo --snapshot
567solutions = [
maruel@chromium.org73e21142010-07-05 13:32:01 +0000568%(solution_list)s]
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000569""")
570
571 def __init__(self, root_dir, options):
maruel@chromium.org0d812442010-08-10 12:41:08 +0000572 # Do not change previous behavior. Only solution level and immediate DEPS
573 # are processed.
574 self._recursion_limit = 2
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000575 Dependency.__init__(self, None, None, None, None, None, None, None, True)
maruel@chromium.org0d425922010-06-21 19:22:24 +0000576 self._options = options
maruel@chromium.org271375b2010-06-23 19:17:38 +0000577 if options.deps_os:
578 enforced_os = options.deps_os.split(',')
579 else:
580 enforced_os = [self.DEPS_OS_CHOICES.get(sys.platform, 'unix')]
581 if 'all' in enforced_os:
582 enforced_os = self.DEPS_OS_CHOICES.itervalues()
583 self._enforced_os = list(set(enforced_os))
584 self._root_dir = root_dir
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000585 self.config_content = None
586
587 def SetConfig(self, content):
588 assert self.dependencies == []
589 config_dict = {}
590 self.config_content = content
591 try:
592 exec(content, config_dict)
593 except SyntaxError, e:
maruel@chromium.org5990f9d2010-07-07 18:02:58 +0000594 gclient_utils.SyntaxErrorToError('.gclient', e)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000595 for s in config_dict.get('solutions', []):
maruel@chromium.org81843b82010-06-28 16:49:26 +0000596 try:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000597 tree = dict((d.name, d) for d in self.tree(False))
598 if s['name'] in tree:
599 raise gclient_utils.Error(
600 'Dependency %s specified more than once in .gclient' % s['name'])
maruel@chromium.org81843b82010-06-28 16:49:26 +0000601 self.dependencies.append(Dependency(
602 self, s['name'], s['url'],
603 s.get('safesync_url', None),
604 s.get('custom_deps', {}),
maruel@chromium.org0d812442010-08-10 12:41:08 +0000605 s.get('custom_vars', {}),
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000606 None,
607 True))
maruel@chromium.org81843b82010-06-28 16:49:26 +0000608 except KeyError:
609 raise gclient_utils.Error('Invalid .gclient file. Solution is '
610 'incomplete: %s' % s)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000611 # .gclient can have hooks.
612 self.deps_hooks = config_dict.get('hooks', [])
maruel@chromium.org049bced2010-08-12 13:37:20 +0000613 self.deps_parsed = True
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000614
615 def SaveConfig(self):
616 gclient_utils.FileWrite(os.path.join(self.root_dir(),
617 self._options.config_filename),
618 self.config_content)
619
620 @staticmethod
621 def LoadCurrentConfig(options):
622 """Searches for and loads a .gclient file relative to the current working
623 dir. Returns a GClient object."""
maruel@chromium.org15804092010-09-02 17:07:37 +0000624 path = gclient_utils.FindGclientRoot(os.getcwd(), options.config_filename)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000625 if not path:
626 return None
627 client = GClient(path, options)
628 client.SetConfig(gclient_utils.FileRead(
629 os.path.join(path, options.config_filename)))
maruel@chromium.org15804092010-09-02 17:07:37 +0000630 return client
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000631
632 def SetDefaultConfig(self, solution_name, solution_url, safesync_url):
633 self.SetConfig(self.DEFAULT_CLIENT_FILE_TEXT % {
634 'solution_name': solution_name,
635 'solution_url': solution_url,
636 'safesync_url' : safesync_url,
637 })
638
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000639 def _SaveEntries(self):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000640 """Creates a .gclient_entries file to record the list of unique checkouts.
641
642 The .gclient_entries file lives in the same directory as .gclient.
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000643 """
644 # Sometimes pprint.pformat will use {', sometimes it'll use { ' ... It
645 # makes testing a bit too fun.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000646 result = 'entries = {\n'
647 for entry in self.tree(False):
648 # Skip over File() dependencies as we can't version them.
649 if not isinstance(entry.parsed_url, self.FileImpl):
650 result += ' %s: %s,\n' % (pprint.pformat(entry.name),
651 pprint.pformat(entry.parsed_url))
652 result += '}\n'
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000653 file_path = os.path.join(self.root_dir(), self._options.entries_filename)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000654 logging.info(result)
655 gclient_utils.FileWrite(file_path, result)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000656
657 def _ReadEntries(self):
658 """Read the .gclient_entries file for the given client.
659
660 Returns:
661 A sequence of solution names, which will be empty if there is the
662 entries file hasn't been created yet.
663 """
664 scope = {}
665 filename = os.path.join(self.root_dir(), self._options.entries_filename)
666 if not os.path.exists(filename):
maruel@chromium.org73e21142010-07-05 13:32:01 +0000667 return {}
maruel@chromium.org5990f9d2010-07-07 18:02:58 +0000668 try:
669 exec(gclient_utils.FileRead(filename), scope)
670 except SyntaxError, e:
671 gclient_utils.SyntaxErrorToError(filename, e)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000672 return scope['entries']
673
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000674 def _EnforceRevisions(self):
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000675 """Checks for revision overrides."""
676 revision_overrides = {}
maruel@chromium.org307d1792010-05-31 20:03:13 +0000677 if self._options.head:
678 return revision_overrides
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000679 for s in self.dependencies:
680 if not s.safesync_url:
maruel@chromium.org307d1792010-05-31 20:03:13 +0000681 continue
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000682 handle = urllib.urlopen(s.safesync_url)
maruel@chromium.org307d1792010-05-31 20:03:13 +0000683 rev = handle.read().strip()
684 handle.close()
685 if len(rev):
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000686 self._options.revisions.append('%s@%s' % (s.name, rev))
maruel@chromium.org307d1792010-05-31 20:03:13 +0000687 if not self._options.revisions:
688 return revision_overrides
689 # --revision will take over safesync_url.
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000690 solutions_names = [s.name for s in self.dependencies]
maruel@chromium.org307d1792010-05-31 20:03:13 +0000691 index = 0
692 for revision in self._options.revisions:
693 if not '@' in revision:
694 # Support for --revision 123
695 revision = '%s@%s' % (solutions_names[index], revision)
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000696 sol, rev = revision.split('@', 1)
maruel@chromium.org307d1792010-05-31 20:03:13 +0000697 if not sol in solutions_names:
698 #raise gclient_utils.Error('%s is not a valid solution.' % sol)
699 print >> sys.stderr, ('Please fix your script, having invalid '
700 '--revision flags will soon considered an error.')
701 else:
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000702 revision_overrides[sol] = rev
maruel@chromium.org307d1792010-05-31 20:03:13 +0000703 index += 1
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000704 return revision_overrides
705
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000706 def RunOnDeps(self, command, args):
707 """Runs a command on each dependency in a client and its dependencies.
708
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000709 Args:
710 command: The command to use (e.g., 'status' or 'diff')
711 args: list of str - extra arguments to add to the command line.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000712 """
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000713 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +0000714 raise gclient_utils.Error('No solution specified')
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000715 revision_overrides = self._EnforceRevisions()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000716 pm = None
maruel@chromium.org5b3f8852010-09-10 16:49:54 +0000717 # Disable progress for non-tty stdout.
maruel@chromium.orga116e7d2010-10-05 19:58:02 +0000718 if (command in ('update', 'revert') and sys.stdout.isatty() and not
719 self._options.verbose):
maruel@chromium.org049bced2010-08-12 13:37:20 +0000720 pm = Progress('Syncing projects', 1)
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000721 work_queue = gclient_utils.ExecutionQueue(self._options.jobs, pm)
maruel@chromium.org049bced2010-08-12 13:37:20 +0000722 for s in self.dependencies:
723 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +0000724 work_queue.flush(revision_overrides, command, args, options=self._options)
piman@chromium.org6f363722010-04-27 00:41:09 +0000725
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000726 # Once all the dependencies have been processed, it's now safe to run the
727 # hooks.
728 if not self._options.nohooks:
729 self.RunHooksRecursively(self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000730
731 if command == 'update':
ajwong@chromium.orgcdcee802009-06-23 15:30:42 +0000732 # Notify the user if there is an orphaned entry in their working copy.
733 # Only delete the directory if there are no changes in it, and
734 # delete_unversioned_trees is set to true.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000735 entries = [i.name for i in self.tree(False)]
736 for entry, prev_url in self._ReadEntries().iteritems():
maruel@chromium.org04dd7de2010-10-14 13:25:49 +0000737 if not prev_url:
738 # entry must have been overridden via .gclient custom_deps
739 continue
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000740 # Fix path separator on Windows.
741 entry_fixed = entry.replace('/', os.path.sep)
maruel@chromium.org75a59272010-06-11 22:34:03 +0000742 e_dir = os.path.join(self.root_dir(), entry_fixed)
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000743 # Use entry and not entry_fixed there.
maruel@chromium.org0329e672009-05-13 18:41:04 +0000744 if entry not in entries and os.path.exists(e_dir):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000745 file_list = []
746 scm = gclient_scm.CreateSCM(prev_url, self.root_dir(), entry_fixed)
747 scm.status(self._options, [], file_list)
748 modified_files = file_list != []
msb@chromium.org83017012009-09-28 18:52:12 +0000749 if not self._options.delete_unversioned_trees or modified_files:
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000750 # There are modified files in this entry. Keep warning until
751 # removed.
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000752 print(('\nWARNING: \'%s\' is no longer part of this client. '
753 'It is recommended that you manually remove it.\n') %
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000754 entry_fixed)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000755 else:
756 # Delete the entry
maruel@chromium.org73e21142010-07-05 13:32:01 +0000757 print('\n________ deleting \'%s\' in \'%s\'' % (
758 entry_fixed, self.root_dir()))
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000759 gclient_utils.RemoveDirectory(e_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000760 # record the current list of entries for next time
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000761 self._SaveEntries()
maruel@chromium.org17cdf762010-05-28 17:30:52 +0000762 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000763
764 def PrintRevInfo(self):
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000765 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +0000766 raise gclient_utils.Error('No solution specified')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000767 # Load all the settings.
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000768 work_queue = gclient_utils.ExecutionQueue(self._options.jobs, None)
maruel@chromium.org049bced2010-08-12 13:37:20 +0000769 for s in self.dependencies:
770 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +0000771 work_queue.flush({}, None, [], options=self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000772
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000773 def GetURLAndRev(dep):
774 """Returns the revision-qualified SCM url for a Dependency."""
775 if dep.parsed_url is None:
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +0000776 return None
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000777 if isinstance(dep.parsed_url, self.FileImpl):
778 original_url = dep.parsed_url.file_location
779 else:
780 original_url = dep.parsed_url
nasser@codeaurora.org5d63eb82010-03-24 23:22:09 +0000781 url, _ = gclient_utils.SplitUrlRevision(original_url)
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000782 scm = gclient_scm.CreateSCM(original_url, self.root_dir(), dep.name)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000783 if not os.path.isdir(scm.checkout_path):
784 return None
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +0000785 return '%s@%s' % (url, scm.revinfo(self._options, [], None))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000786
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +0000787 if self._options.snapshot:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000788 new_gclient = ''
789 # First level at .gclient
790 for d in self.dependencies:
791 entries = {}
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000792 def GrabDeps(dep):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000793 """Recursively grab dependencies."""
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000794 for d in dep.dependencies:
795 entries[d.name] = GetURLAndRev(d)
796 GrabDeps(d)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000797 GrabDeps(d)
798 custom_deps = []
799 for k in sorted(entries.keys()):
800 if entries[k]:
801 # Quotes aren't escaped...
802 custom_deps.append(' \"%s\": \'%s\',\n' % (k, entries[k]))
803 else:
804 custom_deps.append(' \"%s\": None,\n' % k)
805 new_gclient += self.DEFAULT_SNAPSHOT_SOLUTION_TEXT % {
806 'solution_name': d.name,
807 'solution_url': d.url,
808 'safesync_url' : d.safesync_url or '',
809 'solution_deps': ''.join(custom_deps),
810 }
811 # Print the snapshot configuration file
812 print(self.DEFAULT_SNAPSHOT_FILE_TEXT % {'solution_list': new_gclient})
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +0000813 else:
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +0000814 entries = {}
815 for d in self.tree(False):
816 if self._options.actual:
817 entries[d.name] = GetURLAndRev(d)
818 else:
819 entries[d.name] = d.parsed_url
820 keys = sorted(entries.keys())
821 for x in keys:
maruel@chromium.orgce464892010-08-12 17:12:18 +0000822 print('%s: %s' % (x, entries[x]))
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000823 logging.info(str(self))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000824
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000825 def ParseDepsFile(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000826 """No DEPS to parse for a .gclient file."""
maruel@chromium.org049bced2010-08-12 13:37:20 +0000827 raise gclient_utils.Error('Internal error')
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000828
maruel@chromium.org75a59272010-06-11 22:34:03 +0000829 def root_dir(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000830 """Root directory of gclient checkout."""
maruel@chromium.org75a59272010-06-11 22:34:03 +0000831 return self._root_dir
832
maruel@chromium.org271375b2010-06-23 19:17:38 +0000833 def enforced_os(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000834 """What deps_os entries that are to be parsed."""
maruel@chromium.org271375b2010-06-23 19:17:38 +0000835 return self._enforced_os
836
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000837 def recursion_limit(self):
838 """How recursive can each dependencies in DEPS file can load DEPS file."""
839 return self._recursion_limit
840
maruel@chromium.org0d812442010-08-10 12:41:08 +0000841 def tree(self, include_all):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000842 """Returns a flat list of all the dependencies."""
maruel@chromium.org0d812442010-08-10 12:41:08 +0000843 return self.subtree(include_all)
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000844
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000845
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000846#### gclient commands.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000847
848
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000849def CMDcleanup(parser, args):
850 """Cleans up all working copies.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +0000851
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000852Mostly svn-specific. Simply runs 'svn cleanup' for each module.
maruel@chromium.org79692d62010-05-14 18:57:13 +0000853"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000854 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
855 help='override deps for the specified (comma-separated) '
856 'platform(s); \'all\' will process all deps_os '
857 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000858 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +0000859 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000860 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000861 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000862 if options.verbose:
863 # Print out the .gclient file. This is longer than if we just printed the
864 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +0000865 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000866 return client.RunOnDeps('cleanup', args)
867
868
piman@chromium.org4b90e3a2010-07-01 20:28:26 +0000869@attr('usage', '[command] [args ...]')
870def CMDrecurse(parser, args):
871 """Operates on all the entries.
872
873 Runs a shell command on all entries.
874 """
875 # Stop parsing at the first non-arg so that these go through to the command
876 parser.disable_interspersed_args()
877 parser.add_option('-s', '--scm', action='append', default=[],
878 help='choose scm types to operate upon')
879 options, args = parser.parse_args(args)
maruel@chromium.org45e9f2d2010-10-18 13:33:46 +0000880 if not args:
881 print >> sys.stderr, 'Need to supply a command!'
882 return 1
maruel@chromium.org78cba522010-10-18 13:32:05 +0000883 root_and_entries = gclient_utils.GetGClientRootAndEntries()
884 if not root_and_entries:
885 print >> sys.stderr, (
886 'You need to run gclient sync at least once to use \'recurse\'.\n'
887 'This is because .gclient_entries needs to exist and be up to date.')
888 return 1
889 root, entries = root_and_entries
piman@chromium.org4b90e3a2010-07-01 20:28:26 +0000890 scm_set = set()
891 for scm in options.scm:
892 scm_set.update(scm.split(','))
893
894 # Pass in the SCM type as an env variable
895 env = os.environ.copy()
896
897 for path, url in entries.iteritems():
898 scm = gclient_scm.GetScmName(url)
899 if scm_set and scm not in scm_set:
900 continue
maruel@chromium.org2b9aa8e2010-08-25 20:01:42 +0000901 cwd = os.path.normpath(os.path.join(root, path))
maruel@chromium.orgac610232010-10-13 14:01:31 +0000902 if scm:
903 env['GCLIENT_SCM'] = scm
904 if url:
905 env['GCLIENT_URL'] = url
906 gclient_utils.Popen(args, cwd=cwd, env=env).communicate()
907 return 0
piman@chromium.org4b90e3a2010-07-01 20:28:26 +0000908
909
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000910@attr('usage', '[url] [safesync url]')
911def CMDconfig(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +0000912 """Create a .gclient file in the current directory.
913
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000914This specifies the configuration for further commands. After update/sync,
maruel@chromium.org79692d62010-05-14 18:57:13 +0000915top-level DEPS files in each module are read to determine dependent
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000916modules to operate on as well. If optional [url] parameter is
maruel@chromium.org79692d62010-05-14 18:57:13 +0000917provided, then configuration is read from a specified Subversion server
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000918URL.
maruel@chromium.org79692d62010-05-14 18:57:13 +0000919"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000920 parser.add_option('--spec',
921 help='create a gclient file containing the provided '
922 'string. Due to Cygwin/Python brokenness, it '
923 'probably can\'t contain any newlines.')
924 parser.add_option('--name',
925 help='overrides the default name for the solution')
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000926 (options, args) = parser.parse_args(args)
maruel@chromium.org5fc2a332010-05-26 19:37:15 +0000927 if ((options.spec and args) or len(args) > 2 or
928 (not options.spec and not args)):
929 parser.error('Inconsistent arguments. Use either --spec or one or 2 args')
930
maruel@chromium.org0329e672009-05-13 18:41:04 +0000931 if os.path.exists(options.config_filename):
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000932 raise gclient_utils.Error('%s file already exists in the current directory'
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000933 % options.config_filename)
maruel@chromium.org2806acc2009-05-15 12:33:34 +0000934 client = GClient('.', options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000935 if options.spec:
936 client.SetConfig(options.spec)
937 else:
maruel@chromium.org1ab7ffc2009-06-03 17:21:37 +0000938 base_url = args[0].rstrip('/')
iposva@chromium.org8cf7a392010-04-07 17:20:26 +0000939 if not options.name:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000940 name = base_url.split('/')[-1]
iposva@chromium.org8cf7a392010-04-07 17:20:26 +0000941 else:
942 # specify an alternate relpath for the given URL.
943 name = options.name
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000944 safesync_url = ''
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000945 if len(args) > 1:
946 safesync_url = args[1]
947 client.SetDefaultConfig(name, base_url, safesync_url)
948 client.SaveConfig()
maruel@chromium.org79692d62010-05-14 18:57:13 +0000949 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000950
951
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000952def CMDexport(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +0000953 """Wrapper for svn export for all managed directories."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000954 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
955 help='override deps for the specified (comma-separated) '
956 'platform(s); \'all\' will process all deps_os '
957 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000958 (options, args) = parser.parse_args(args)
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +0000959 if len(args) != 1:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000960 raise gclient_utils.Error('Need directory name')
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +0000961 client = GClient.LoadCurrentConfig(options)
962
963 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000964 raise gclient_utils.Error('client not configured; see \'gclient config\'')
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +0000965
966 if options.verbose:
967 # Print out the .gclient file. This is longer than if we just printed the
968 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +0000969 print(client.config_content)
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +0000970 return client.RunOnDeps('export', args)
971
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000972
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000973@attr('epilog', """Example:
974 gclient pack > patch.txt
975 generate simple patch for configured client and dependences
976""")
977def CMDpack(parser, args):
maruel@chromium.org79692d62010-05-14 18:57:13 +0000978 """Generate a patch which can be applied at the root of the tree.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +0000979
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000980Internally, runs 'svn diff'/'git diff' on each checked out module and
maruel@chromium.org79692d62010-05-14 18:57:13 +0000981dependencies, and performs minimal postprocessing of the output. The
982resulting patch is printed to stdout and can be applied to a freshly
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000983checked out tree via 'patch -p0 < patchfile'.
maruel@chromium.org79692d62010-05-14 18:57:13 +0000984"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000985 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
986 help='override deps for the specified (comma-separated) '
987 'platform(s); \'all\' will process all deps_os '
988 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000989 (options, args) = parser.parse_args(args)
kbr@google.comab318592009-09-04 00:54:55 +0000990 client = GClient.LoadCurrentConfig(options)
991 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000992 raise gclient_utils.Error('client not configured; see \'gclient config\'')
kbr@google.comab318592009-09-04 00:54:55 +0000993 if options.verbose:
994 # Print out the .gclient file. This is longer than if we just printed the
995 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +0000996 print(client.config_content)
kbr@google.comab318592009-09-04 00:54:55 +0000997 return client.RunOnDeps('pack', args)
998
999
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001000def CMDstatus(parser, args):
1001 """Show modification status for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001002 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1003 help='override deps for the specified (comma-separated) '
1004 'platform(s); \'all\' will process all deps_os '
1005 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001006 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001007 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001008 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001009 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001010 if options.verbose:
1011 # Print out the .gclient file. This is longer than if we just printed the
1012 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001013 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001014 return client.RunOnDeps('status', args)
1015
1016
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001017@attr('epilog', """Examples:
maruel@chromium.org79692d62010-05-14 18:57:13 +00001018 gclient sync
1019 update files from SCM according to current configuration,
1020 *for modules which have changed since last update or sync*
1021 gclient sync --force
1022 update files from SCM according to current configuration, for
1023 all modules (useful for recovering files deleted from local copy)
1024 gclient sync --revision src@31000
1025 update src directory to r31000
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001026""")
1027def CMDsync(parser, args):
1028 """Checkout/update all modules."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001029 parser.add_option('-f', '--force', action='store_true',
1030 help='force update even for unchanged modules')
1031 parser.add_option('-n', '--nohooks', action='store_true',
1032 help='don\'t run hooks after the update is complete')
1033 parser.add_option('-r', '--revision', action='append',
1034 dest='revisions', metavar='REV', default=[],
1035 help='Enforces revision/hash for the solutions with the '
1036 'format src@rev. The src@ part is optional and can be '
1037 'skipped. -r can be used multiple times when .gclient '
1038 'has multiple solutions configured and will work even '
1039 'if the src@ part is skipped.')
1040 parser.add_option('-H', '--head', action='store_true',
1041 help='skips any safesync_urls specified in '
1042 'configured solutions and sync to head instead')
1043 parser.add_option('-D', '--delete_unversioned_trees', action='store_true',
1044 help='delete any unexpected unversioned trees '
1045 'that are in the checkout')
1046 parser.add_option('-R', '--reset', action='store_true',
1047 help='resets any local changes before updating (git only)')
1048 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1049 help='override deps for the specified (comma-separated) '
1050 'platform(s); \'all\' will process all deps_os '
1051 'references')
1052 parser.add_option('-m', '--manually_grab_svn_rev', action='store_true',
1053 help='Skip svn up whenever possible by requesting '
1054 'actual HEAD revision from the repository')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001055 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001056 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001057
1058 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001059 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001060
maruel@chromium.org307d1792010-05-31 20:03:13 +00001061 if options.revisions and options.head:
1062 # TODO(maruel): Make it a parser.error if it doesn't break any builder.
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001063 print('Warning: you cannot use both --head and --revision')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001064
1065 if options.verbose:
1066 # Print out the .gclient file. This is longer than if we just printed the
1067 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001068 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001069 return client.RunOnDeps('update', args)
1070
1071
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001072def CMDupdate(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001073 """Alias for the sync command. Deprecated."""
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001074 return CMDsync(parser, args)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001075
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001076def CMDdiff(parser, args):
1077 """Displays local diff for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001078 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1079 help='override deps for the specified (comma-separated) '
1080 'platform(s); \'all\' will process all deps_os '
1081 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001082 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001083 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001084 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001085 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001086 if options.verbose:
1087 # Print out the .gclient file. This is longer than if we just printed the
1088 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001089 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001090 return client.RunOnDeps('diff', args)
1091
1092
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001093def CMDrevert(parser, args):
1094 """Revert all modifications in every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001095 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1096 help='override deps for the specified (comma-separated) '
1097 'platform(s); \'all\' will process all deps_os '
1098 'references')
1099 parser.add_option('-n', '--nohooks', action='store_true',
1100 help='don\'t run hooks after the revert is complete')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001101 (options, args) = parser.parse_args(args)
1102 # --force is implied.
1103 options.force = True
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001104 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001105 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001106 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001107 return client.RunOnDeps('revert', args)
1108
1109
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001110def CMDrunhooks(parser, args):
1111 """Runs hooks for files that have been modified in the local working copy."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001112 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1113 help='override deps for the specified (comma-separated) '
1114 'platform(s); \'all\' will process all deps_os '
1115 'references')
1116 parser.add_option('-f', '--force', action='store_true', default=True,
1117 help='Deprecated. No effect.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001118 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001119 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001120 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001121 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001122 if options.verbose:
1123 # Print out the .gclient file. This is longer than if we just printed the
1124 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001125 print(client.config_content)
maruel@chromium.org5df6a462009-08-28 18:52:26 +00001126 options.force = True
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001127 options.nohooks = False
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001128 return client.RunOnDeps('runhooks', args)
1129
1130
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001131def CMDrevinfo(parser, args):
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001132 """Output revision info mapping for the client and its dependencies.
1133
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001134 This allows the capture of an overall 'revision' for the source tree that
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001135 can be used to reproduce the same tree in the future. It is only useful for
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001136 'unpinned dependencies', i.e. DEPS/deps references without a svn revision
1137 number or a git hash. A git branch name isn't 'pinned' since the actual
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001138 commit can change.
1139 """
1140 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1141 help='override deps for the specified (comma-separated) '
1142 'platform(s); \'all\' will process all deps_os '
1143 'references')
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001144 parser.add_option('-a', '--actual', action='store_true',
1145 help='gets the actual checked out revisions instead of the '
1146 'ones specified in the DEPS and .gclient files')
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001147 parser.add_option('-s', '--snapshot', action='store_true',
1148 help='creates a snapshot .gclient file of the current '
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001149 'version of all repositories to reproduce the tree, '
1150 'implies -a')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001151 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001152 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001153 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001154 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001155 client.PrintRevInfo()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001156 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001157
1158
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001159def Command(name):
1160 return getattr(sys.modules[__name__], 'CMD' + name, None)
1161
1162
1163def CMDhelp(parser, args):
1164 """Prints list of commands or help for a specific command."""
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001165 (_, args) = parser.parse_args(args)
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001166 if len(args) == 1:
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001167 return Main(args + ['--help'])
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001168 parser.print_help()
1169 return 0
1170
1171
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001172def GenUsage(parser, command):
1173 """Modify an OptParse object with the function's documentation."""
1174 obj = Command(command)
1175 if command == 'help':
1176 command = '<command>'
1177 # OptParser.description prefer nicely non-formatted strings.
1178 parser.description = re.sub('[\r\n ]{2,}', ' ', obj.__doc__)
1179 usage = getattr(obj, 'usage', '')
1180 parser.set_usage('%%prog %s [options] %s' % (command, usage))
1181 parser.epilog = getattr(obj, 'epilog', None)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001182
1183
1184def Main(argv):
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001185 """Doesn't parse the arguments here, just find the right subcommand to
1186 execute."""
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001187 try:
maruel@chromium.orge0de9cb2010-09-17 15:07:14 +00001188 # Make stdout auto-flush so buildbot doesn't kill us during lengthy
1189 # operations. Python as a strong tendency to buffer sys.stdout.
1190 sys.stdout = gclient_utils.MakeFileAutoFlush(sys.stdout)
maruel@chromium.org4ed34182010-09-17 15:57:47 +00001191 # Make stdout annotated with the thread ids.
1192 sys.stdout = gclient_utils.MakeFileAnnotated(sys.stdout)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001193 # Do it late so all commands are listed.
1194 CMDhelp.usage = ('\n\nCommands are:\n' + '\n'.join([
1195 ' %-10s %s' % (fn[3:], Command(fn[3:]).__doc__.split('\n')[0].strip())
1196 for fn in dir(sys.modules[__name__]) if fn.startswith('CMD')]))
1197 parser = optparse.OptionParser(version='%prog ' + __version__)
maruel@chromium.orgfac9d3d2010-09-10 20:38:49 +00001198 parser.add_option('-j', '--jobs', default=1, type='int',
maruel@chromium.org3b84d4c2010-09-10 18:02:43 +00001199 help='Specify how many SCM commands can run in parallel; '
1200 'default=%default')
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001201 parser.add_option('-v', '--verbose', action='count', default=0,
1202 help='Produces additional output for diagnostics. Can be '
1203 'used up to three times for more logging info.')
1204 parser.add_option('--gclientfile', dest='config_filename',
1205 default=os.environ.get('GCLIENT_FILE', '.gclient'),
1206 help='Specify an alternate %default file')
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001207 # Integrate standard options processing.
1208 old_parser = parser.parse_args
1209 def Parse(args):
1210 (options, args) = old_parser(args)
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001211 level = None
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001212 if options.verbose == 2:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001213 level = logging.INFO
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001214 elif options.verbose > 2:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001215 level = logging.DEBUG
1216 logging.basicConfig(level=level,
1217 format='%(module)s(%(lineno)d) %(funcName)s:%(message)s')
1218 options.entries_filename = options.config_filename + '_entries'
maruel@chromium.org9e5317a2010-08-13 20:35:11 +00001219 if options.jobs < 1:
1220 parser.error('--jobs must be 1 or higher')
maruel@chromium.orge3216c62010-07-08 03:31:43 +00001221
1222 # These hacks need to die.
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001223 if not hasattr(options, 'revisions'):
1224 # GClient.RunOnDeps expects it even if not applicable.
1225 options.revisions = []
1226 if not hasattr(options, 'head'):
1227 options.head = None
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001228 if not hasattr(options, 'nohooks'):
1229 options.nohooks = True
1230 if not hasattr(options, 'deps_os'):
1231 options.deps_os = None
maruel@chromium.orge3216c62010-07-08 03:31:43 +00001232 if not hasattr(options, 'manually_grab_svn_rev'):
1233 options.manually_grab_svn_rev = None
1234 if not hasattr(options, 'force'):
1235 options.force = None
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001236 return (options, args)
1237 parser.parse_args = Parse
1238 # We don't want wordwrapping in epilog (usually examples)
1239 parser.format_epilog = lambda _: parser.epilog or ''
1240 if argv:
1241 command = Command(argv[0])
1242 if command:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001243 # 'fix' the usage and the description now that we know the subcommand.
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001244 GenUsage(parser, argv[0])
1245 return command(parser, argv[1:])
1246 # Not a known command. Default to help.
1247 GenUsage(parser, 'help')
1248 return CMDhelp(parser, argv)
1249 except gclient_utils.Error, e:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001250 print >> sys.stderr, 'Error: %s' % str(e)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001251 return 1
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001252
1253
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001254if '__main__' == __name__:
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001255 sys.exit(Main(sys.argv[1:]))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001256
1257# vim: ts=2:sw=2:tw=80:et: