blob: 6482efaa95e99c57539caaacc94d66859c0928b2 [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.org9e5317a2010-08-13 20:35:11 +000052__version__ = "0.6"
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
257 filepath = os.path.join(self.root_dir(), self.name, self.deps_file)
258 if not os.path.isfile(filepath):
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000259 logging.info('%s: No DEPS file found at %s' % (self.name, filepath))
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000260 return
maruel@chromium.org271375b2010-06-23 19:17:38 +0000261 deps_content = gclient_utils.FileRead(filepath)
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000262 logging.debug(deps_content)
maruel@chromium.org0d425922010-06-21 19:22:24 +0000263
maruel@chromium.org271375b2010-06-23 19:17:38 +0000264 # Eval the content.
265 # One thing is unintuitive, vars= {} must happen before Var() use.
266 local_scope = {}
267 var = self.VarImpl(self.custom_vars, local_scope)
268 global_scope = {
269 'File': self.FileImpl,
270 'From': self.FromImpl,
271 'Var': var.Lookup,
272 'deps_os': {},
273 }
maruel@chromium.org5990f9d2010-07-07 18:02:58 +0000274 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@google.comfb2b8eb2009-04-23 21:03:42 +0000286 # platform, so we collect the broadest set of dependencies available.
287 # We may end up with the wrong revision of something for our
288 # platform, but this is the best we can do.
289 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.org3742c842010-09-09 19:27:14 +0000337 def run(self, revision_overrides, command, args, work_queue, options):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000338 """Runs 'command' before parsing the DEPS in case it's a initial checkout
339 or a revert."""
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000340 assert self._file_list == []
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000341 if not self.should_process:
342 return
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000343 # When running runhooks, there's no need to consult the SCM.
344 # All known hooks are expected to run unconditionally regardless of working
345 # copy state, so skip the SCM status check.
346 run_scm = command not in ('runhooks', None)
maruel@chromium.orgda7a1f92010-08-10 17:19:02 +0000347 self.parsed_url = self.LateOverride(self.url)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000348 if run_scm and self.parsed_url:
349 if isinstance(self.parsed_url, self.FileImpl):
350 # Special support for single-file checkout.
351 if not command in (None, 'cleanup', 'diff', 'pack', 'status'):
352 options.revision = self.parsed_url.GetRevision()
353 scm = gclient_scm.SVNWrapper(self.parsed_url.GetPath(),
354 self.root_dir(),
355 self.name)
356 scm.RunCommand('updatesingle', options,
357 args + [self.parsed_url.GetFilename()],
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000358 self._file_list)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000359 else:
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000360 # Create a shallow copy to mutate revision.
361 options = copy.copy(options)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000362 options.revision = revision_overrides.get(self.name)
363 scm = gclient_scm.CreateSCM(self.parsed_url, self.root_dir(), self.name)
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000364 scm.RunCommand(command, options, args, self._file_list)
365 self._file_list = [os.path.join(self.name, f.strip())
366 for f in self._file_list]
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000367 self.processed = True
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000368 if self.recursion_limit() > 0:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000369 # Then we can parse the DEPS file.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000370 self.ParseDepsFile()
maruel@chromium.org621939b2010-08-10 20:12:00 +0000371 # Adjust the implicit dependency requirement; e.g. if a DEPS file contains
372 # both src/foo and src/foo/bar, src/foo/bar is implicitly dependent of
maruel@chromium.org049bced2010-08-12 13:37:20 +0000373 # src/foo. Yes, it's O(n^2)... It's important to do that before
374 # enqueueing them.
maruel@chromium.org621939b2010-08-10 20:12:00 +0000375 for s in self.dependencies:
376 for s2 in self.dependencies:
377 if s is s2:
378 continue
379 if s.name.startswith(posixpath.join(s2.name, '')):
380 s.requirements.append(s2.name)
381
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000382 # Parse the dependencies of this dependency.
383 for s in self.dependencies:
maruel@chromium.org049bced2010-08-12 13:37:20 +0000384 work_queue.enqueue(s)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000385
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000386 def RunHooksRecursively(self, options):
maruel@chromium.org049bced2010-08-12 13:37:20 +0000387 """Evaluates all hooks, running actions as needed. run()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000388 must have been called before to load the DEPS."""
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000389 assert self.hooks_ran == False
390 if not self.should_process or self.recursion_limit() <= 0:
391 # Don't run the hook when it is above recursion_limit.
392 return
maruel@chromium.orgdc7445d2010-07-09 21:05:29 +0000393 # If "--force" was specified, run all hooks regardless of what files have
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000394 # changed.
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000395 if self.deps_hooks:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000396 # TODO(maruel): If the user is using git or git-svn, then we don't know
397 # what files have changed so we always run all hooks. It'd be nice to fix
398 # that.
399 if (options.force or
400 isinstance(self.parsed_url, self.FileImpl) or
401 gclient_scm.GetScmName(self.parsed_url) in ('git', None) or
402 os.path.isdir(os.path.join(self.root_dir(), self.name, '.git'))):
403 for hook_dict in self.deps_hooks:
404 self._RunHookAction(hook_dict, [])
405 else:
406 # TODO(phajdan.jr): We should know exactly when the paths are absolute.
407 # Convert all absolute paths to relative.
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000408 file_list = self.file_list()
409 for i in range(len(file_list)):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000410 # It depends on the command being executed (like runhooks vs sync).
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000411 if not os.path.isabs(file_list[i]):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000412 continue
maruel@chromium.orgdc7445d2010-07-09 21:05:29 +0000413
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000414 prefix = os.path.commonprefix([self.root_dir().lower(),
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000415 file_list[i].lower()])
416 file_list[i] = file_list[i][len(prefix):]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000417
418 # Strip any leading path separators.
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000419 while (file_list[i].startswith('\\') or
420 file_list[i].startswith('/')):
421 file_list[i] = file_list[i][1:]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000422
423 # Run hooks on the basis of whether the files from the gclient operation
424 # match each hook's pattern.
425 for hook_dict in self.deps_hooks:
426 pattern = re.compile(hook_dict['pattern'])
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000427 matching_file_list = [f for f in file_list if pattern.search(f)]
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000428 if matching_file_list:
429 self._RunHookAction(hook_dict, matching_file_list)
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000430 for s in self.dependencies:
431 s.RunHooksRecursively(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000432
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000433 def _RunHookAction(self, hook_dict, matching_file_list):
434 """Runs the action from a single hook."""
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000435 # A single DEPS file can specify multiple hooks so this function can be
436 # called multiple times on a single Dependency.
437 #assert self.hooks_ran == False
maruel@chromium.orgf3abb802010-08-10 17:19:56 +0000438 self.hooks_ran = True
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000439 logging.debug(hook_dict)
440 logging.debug(matching_file_list)
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000441 command = hook_dict['action'][:]
442 if command[0] == 'python':
443 # If the hook specified "python" as the first item, the action is a
444 # Python script. Run it by starting a new copy of the same
445 # interpreter.
446 command[0] = sys.executable
447
448 if '$matching_files' in command:
449 splice_index = command.index('$matching_files')
450 command[splice_index:splice_index + 1] = matching_file_list
451
maruel@chromium.org17d01792010-09-01 18:07:10 +0000452 try:
453 gclient_utils.CheckCallAndFilterAndHeader(
454 command, cwd=self.root_dir(), always=True)
455 except gclient_utils.Error, e:
456 # Use a discrete exit status code of 2 to indicate that a hook action
457 # failed. Users of this script may wish to treat hook action failures
458 # differently from VC failures.
459 print >> sys.stderr, 'Error: %s' % str(e)
460 sys.exit(2)
maruel@chromium.orgeaf61062010-07-07 18:42:39 +0000461
maruel@chromium.org271375b2010-06-23 19:17:38 +0000462 def root_dir(self):
463 return self.parent.root_dir()
464
465 def enforced_os(self):
466 return self.parent.enforced_os()
467
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000468 def recursion_limit(self):
469 return self.parent.recursion_limit() - 1
470
maruel@chromium.org0d812442010-08-10 12:41:08 +0000471 def tree(self, include_all):
472 return self.parent.tree(include_all)
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000473
maruel@chromium.org0d812442010-08-10 12:41:08 +0000474 def subtree(self, include_all):
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000475 """Breadth first"""
maruel@chromium.orgc57e4f22010-07-22 21:37:46 +0000476 result = []
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000477 for d in self.dependencies:
478 if d.should_process or include_all:
maruel@chromium.org044f4e32010-07-22 21:59:57 +0000479 result.append(d)
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000480 for d in self.dependencies:
481 result.extend(d.subtree(include_all))
maruel@chromium.orgc57e4f22010-07-22 21:37:46 +0000482 return result
483
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000484 def get_custom_deps(self, name, url):
485 """Returns a custom deps if applicable."""
486 if self.parent:
487 url = self.parent.get_custom_deps(name, url)
488 # None is a valid return value to disable a dependency.
489 return self.custom_deps.get(name, url)
490
maruel@chromium.org861fd0f2010-07-23 03:05:05 +0000491 def file_list(self):
492 result = self._file_list[:]
493 for d in self.dependencies:
494 result.extend(d.file_list())
495 return result
496
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000497 def __str__(self):
498 out = []
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000499 for i in ('name', 'url', 'parsed_url', 'safesync_url', 'custom_deps',
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000500 'custom_vars', 'deps_hooks', '_file_list', 'should_process',
501 'processed', 'hooks_ran', 'deps_parsed', 'requirements'):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000502 # 'deps_file'
503 if self.__dict__[i]:
504 out.append('%s: %s' % (i, self.__dict__[i]))
505
506 for d in self.dependencies:
507 out.extend([' ' + x for x in str(d).splitlines()])
508 out.append('')
509 return '\n'.join(out)
510
511 def __repr__(self):
512 return '%s: %s' % (self.name, self.url)
513
maruel@chromium.orgbffb9042010-07-22 20:59:36 +0000514 def hierarchy(self):
maruel@chromium.orgbc2d2f92010-07-22 21:26:48 +0000515 """Returns a human-readable hierarchical reference to a Dependency."""
maruel@chromium.orgbffb9042010-07-22 20:59:36 +0000516 out = '%s(%s)' % (self.name, self.url)
517 i = self.parent
518 while i and i.name:
519 out = '%s(%s) -> %s' % (i.name, i.url, out)
520 i = i.parent
521 return out
522
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000523 def root_parent(self):
524 """Returns the root object, normally a GClient object."""
525 d = self
526 while d.parent:
527 d = d.parent
528 return d
529
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000530
531class GClient(Dependency):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000532 """Object that represent a gclient checkout. A tree of Dependency(), one per
533 solution or DEPS entry."""
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000534
535 DEPS_OS_CHOICES = {
536 "win32": "win",
537 "win": "win",
538 "cygwin": "win",
539 "darwin": "mac",
540 "mac": "mac",
541 "unix": "unix",
542 "linux": "unix",
543 "linux2": "unix",
544 }
545
546 DEFAULT_CLIENT_FILE_TEXT = ("""\
547solutions = [
548 { "name" : "%(solution_name)s",
549 "url" : "%(solution_url)s",
550 "custom_deps" : {
551 },
maruel@chromium.org73e21142010-07-05 13:32:01 +0000552 "safesync_url": "%(safesync_url)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000553 },
554]
555""")
556
557 DEFAULT_SNAPSHOT_SOLUTION_TEXT = ("""\
558 { "name" : "%(solution_name)s",
559 "url" : "%(solution_url)s",
560 "custom_deps" : {
maruel@chromium.org73e21142010-07-05 13:32:01 +0000561%(solution_deps)s },
562 "safesync_url": "%(safesync_url)s",
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000563 },
564""")
565
566 DEFAULT_SNAPSHOT_FILE_TEXT = ("""\
567# Snapshot generated with gclient revinfo --snapshot
568solutions = [
maruel@chromium.org73e21142010-07-05 13:32:01 +0000569%(solution_list)s]
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000570""")
571
572 def __init__(self, root_dir, options):
maruel@chromium.org0d812442010-08-10 12:41:08 +0000573 # Do not change previous behavior. Only solution level and immediate DEPS
574 # are processed.
575 self._recursion_limit = 2
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000576 Dependency.__init__(self, None, None, None, None, None, None, None, True)
maruel@chromium.org0d425922010-06-21 19:22:24 +0000577 self._options = options
maruel@chromium.org271375b2010-06-23 19:17:38 +0000578 if options.deps_os:
579 enforced_os = options.deps_os.split(',')
580 else:
581 enforced_os = [self.DEPS_OS_CHOICES.get(sys.platform, 'unix')]
582 if 'all' in enforced_os:
583 enforced_os = self.DEPS_OS_CHOICES.itervalues()
584 self._enforced_os = list(set(enforced_os))
585 self._root_dir = root_dir
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000586 self.config_content = None
587
588 def SetConfig(self, content):
589 assert self.dependencies == []
590 config_dict = {}
591 self.config_content = content
592 try:
593 exec(content, config_dict)
594 except SyntaxError, e:
maruel@chromium.org5990f9d2010-07-07 18:02:58 +0000595 gclient_utils.SyntaxErrorToError('.gclient', e)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000596 for s in config_dict.get('solutions', []):
maruel@chromium.org81843b82010-06-28 16:49:26 +0000597 try:
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000598 tree = dict((d.name, d) for d in self.tree(False))
599 if s['name'] in tree:
600 raise gclient_utils.Error(
601 'Dependency %s specified more than once in .gclient' % s['name'])
maruel@chromium.org81843b82010-06-28 16:49:26 +0000602 self.dependencies.append(Dependency(
603 self, s['name'], s['url'],
604 s.get('safesync_url', None),
605 s.get('custom_deps', {}),
maruel@chromium.org0d812442010-08-10 12:41:08 +0000606 s.get('custom_vars', {}),
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000607 None,
608 True))
maruel@chromium.org81843b82010-06-28 16:49:26 +0000609 except KeyError:
610 raise gclient_utils.Error('Invalid .gclient file. Solution is '
611 'incomplete: %s' % s)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000612 # .gclient can have hooks.
613 self.deps_hooks = config_dict.get('hooks', [])
maruel@chromium.org049bced2010-08-12 13:37:20 +0000614 self.deps_parsed = True
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000615
616 def SaveConfig(self):
617 gclient_utils.FileWrite(os.path.join(self.root_dir(),
618 self._options.config_filename),
619 self.config_content)
620
621 @staticmethod
622 def LoadCurrentConfig(options):
623 """Searches for and loads a .gclient file relative to the current working
624 dir. Returns a GClient object."""
maruel@chromium.org15804092010-09-02 17:07:37 +0000625 path = gclient_utils.FindGclientRoot(os.getcwd(), options.config_filename)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000626 if not path:
627 return None
628 client = GClient(path, options)
629 client.SetConfig(gclient_utils.FileRead(
630 os.path.join(path, options.config_filename)))
maruel@chromium.org15804092010-09-02 17:07:37 +0000631 return client
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000632
633 def SetDefaultConfig(self, solution_name, solution_url, safesync_url):
634 self.SetConfig(self.DEFAULT_CLIENT_FILE_TEXT % {
635 'solution_name': solution_name,
636 'solution_url': solution_url,
637 'safesync_url' : safesync_url,
638 })
639
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000640 def _SaveEntries(self):
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000641 """Creates a .gclient_entries file to record the list of unique checkouts.
642
643 The .gclient_entries file lives in the same directory as .gclient.
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000644 """
645 # Sometimes pprint.pformat will use {', sometimes it'll use { ' ... It
646 # makes testing a bit too fun.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000647 result = 'entries = {\n'
648 for entry in self.tree(False):
649 # Skip over File() dependencies as we can't version them.
650 if not isinstance(entry.parsed_url, self.FileImpl):
651 result += ' %s: %s,\n' % (pprint.pformat(entry.name),
652 pprint.pformat(entry.parsed_url))
653 result += '}\n'
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000654 file_path = os.path.join(self.root_dir(), self._options.entries_filename)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000655 logging.info(result)
656 gclient_utils.FileWrite(file_path, result)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000657
658 def _ReadEntries(self):
659 """Read the .gclient_entries file for the given client.
660
661 Returns:
662 A sequence of solution names, which will be empty if there is the
663 entries file hasn't been created yet.
664 """
665 scope = {}
666 filename = os.path.join(self.root_dir(), self._options.entries_filename)
667 if not os.path.exists(filename):
maruel@chromium.org73e21142010-07-05 13:32:01 +0000668 return {}
maruel@chromium.org5990f9d2010-07-07 18:02:58 +0000669 try:
670 exec(gclient_utils.FileRead(filename), scope)
671 except SyntaxError, e:
672 gclient_utils.SyntaxErrorToError(filename, e)
maruel@chromium.org9a66ddf2010-06-16 16:54:16 +0000673 return scope['entries']
674
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000675 def _EnforceRevisions(self):
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000676 """Checks for revision overrides."""
677 revision_overrides = {}
maruel@chromium.org307d1792010-05-31 20:03:13 +0000678 if self._options.head:
679 return revision_overrides
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000680 for s in self.dependencies:
681 if not s.safesync_url:
maruel@chromium.org307d1792010-05-31 20:03:13 +0000682 continue
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000683 handle = urllib.urlopen(s.safesync_url)
maruel@chromium.org307d1792010-05-31 20:03:13 +0000684 rev = handle.read().strip()
685 handle.close()
686 if len(rev):
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000687 self._options.revisions.append('%s@%s' % (s.name, rev))
maruel@chromium.org307d1792010-05-31 20:03:13 +0000688 if not self._options.revisions:
689 return revision_overrides
690 # --revision will take over safesync_url.
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000691 solutions_names = [s.name for s in self.dependencies]
maruel@chromium.org307d1792010-05-31 20:03:13 +0000692 index = 0
693 for revision in self._options.revisions:
694 if not '@' in revision:
695 # Support for --revision 123
696 revision = '%s@%s' % (solutions_names[index], revision)
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000697 sol, rev = revision.split('@', 1)
maruel@chromium.org307d1792010-05-31 20:03:13 +0000698 if not sol in solutions_names:
699 #raise gclient_utils.Error('%s is not a valid solution.' % sol)
700 print >> sys.stderr, ('Please fix your script, having invalid '
701 '--revision flags will soon considered an error.')
702 else:
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000703 revision_overrides[sol] = rev
maruel@chromium.org307d1792010-05-31 20:03:13 +0000704 index += 1
maruel@chromium.org918a9ae2010-05-28 15:50:30 +0000705 return revision_overrides
706
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000707 def RunOnDeps(self, command, args):
708 """Runs a command on each dependency in a client and its dependencies.
709
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000710 Args:
711 command: The command to use (e.g., 'status' or 'diff')
712 args: list of str - extra arguments to add to the command line.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000713 """
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000714 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +0000715 raise gclient_utils.Error('No solution specified')
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000716 revision_overrides = self._EnforceRevisions()
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000717 pm = None
maruel@chromium.org5b3f8852010-09-10 16:49:54 +0000718 # Disable progress for non-tty stdout.
maruel@chromium.orga116e7d2010-10-05 19:58:02 +0000719 if (command in ('update', 'revert') and sys.stdout.isatty() and not
720 self._options.verbose):
maruel@chromium.org049bced2010-08-12 13:37:20 +0000721 pm = Progress('Syncing projects', 1)
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000722 work_queue = gclient_utils.ExecutionQueue(self._options.jobs, pm)
maruel@chromium.org049bced2010-08-12 13:37:20 +0000723 for s in self.dependencies:
724 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +0000725 work_queue.flush(revision_overrides, command, args, options=self._options)
piman@chromium.org6f363722010-04-27 00:41:09 +0000726
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000727 # Once all the dependencies have been processed, it's now safe to run the
728 # hooks.
729 if not self._options.nohooks:
730 self.RunHooksRecursively(self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000731
732 if command == 'update':
ajwong@chromium.orgcdcee802009-06-23 15:30:42 +0000733 # Notify the user if there is an orphaned entry in their working copy.
734 # Only delete the directory if there are no changes in it, and
735 # delete_unversioned_trees is set to true.
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000736 entries = [i.name for i in self.tree(False)]
737 for entry, prev_url in self._ReadEntries().iteritems():
maruel@chromium.org04dd7de2010-10-14 13:25:49 +0000738 if not prev_url:
739 # entry must have been overridden via .gclient custom_deps
740 continue
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000741 # Fix path separator on Windows.
742 entry_fixed = entry.replace('/', os.path.sep)
maruel@chromium.org75a59272010-06-11 22:34:03 +0000743 e_dir = os.path.join(self.root_dir(), entry_fixed)
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000744 # Use entry and not entry_fixed there.
maruel@chromium.org0329e672009-05-13 18:41:04 +0000745 if entry not in entries and os.path.exists(e_dir):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000746 file_list = []
747 scm = gclient_scm.CreateSCM(prev_url, self.root_dir(), entry_fixed)
748 scm.status(self._options, [], file_list)
749 modified_files = file_list != []
msb@chromium.org83017012009-09-28 18:52:12 +0000750 if not self._options.delete_unversioned_trees or modified_files:
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000751 # There are modified files in this entry. Keep warning until
752 # removed.
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000753 print(('\nWARNING: \'%s\' is no longer part of this client. '
754 'It is recommended that you manually remove it.\n') %
maruel@chromium.orgc5e9aec2009-08-03 18:25:56 +0000755 entry_fixed)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000756 else:
757 # Delete the entry
maruel@chromium.org73e21142010-07-05 13:32:01 +0000758 print('\n________ deleting \'%s\' in \'%s\'' % (
759 entry_fixed, self.root_dir()))
maruel@chromium.org5f3eee32009-09-17 00:34:30 +0000760 gclient_utils.RemoveDirectory(e_dir)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000761 # record the current list of entries for next time
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000762 self._SaveEntries()
maruel@chromium.org17cdf762010-05-28 17:30:52 +0000763 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000764
765 def PrintRevInfo(self):
maruel@chromium.org54a07a22010-06-14 19:07:39 +0000766 if not self.dependencies:
maruel@chromium.org73e21142010-07-05 13:32:01 +0000767 raise gclient_utils.Error('No solution specified')
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000768 # Load all the settings.
maruel@chromium.org9e5317a2010-08-13 20:35:11 +0000769 work_queue = gclient_utils.ExecutionQueue(self._options.jobs, None)
maruel@chromium.org049bced2010-08-12 13:37:20 +0000770 for s in self.dependencies:
771 work_queue.enqueue(s)
maruel@chromium.org3742c842010-09-09 19:27:14 +0000772 work_queue.flush({}, None, [], options=self._options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000773
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000774 def GetURLAndRev(dep):
775 """Returns the revision-qualified SCM url for a Dependency."""
776 if dep.parsed_url is None:
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +0000777 return None
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000778 if isinstance(dep.parsed_url, self.FileImpl):
779 original_url = dep.parsed_url.file_location
780 else:
781 original_url = dep.parsed_url
nasser@codeaurora.org5d63eb82010-03-24 23:22:09 +0000782 url, _ = gclient_utils.SplitUrlRevision(original_url)
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000783 scm = gclient_scm.CreateSCM(original_url, self.root_dir(), dep.name)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000784 if not os.path.isdir(scm.checkout_path):
785 return None
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +0000786 return '%s@%s' % (url, scm.revinfo(self._options, [], None))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000787
maruel@chromium.orgbaa578e2010-07-12 17:36:59 +0000788 if self._options.snapshot:
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000789 new_gclient = ''
790 # First level at .gclient
791 for d in self.dependencies:
792 entries = {}
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000793 def GrabDeps(dep):
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000794 """Recursively grab dependencies."""
maruel@chromium.org6da25d02010-08-11 17:32:55 +0000795 for d in dep.dependencies:
796 entries[d.name] = GetURLAndRev(d)
797 GrabDeps(d)
maruel@chromium.orgdf2b3152010-07-21 17:35:24 +0000798 GrabDeps(d)
799 custom_deps = []
800 for k in sorted(entries.keys()):
801 if entries[k]:
802 # Quotes aren't escaped...
803 custom_deps.append(' \"%s\": \'%s\',\n' % (k, entries[k]))
804 else:
805 custom_deps.append(' \"%s\": None,\n' % k)
806 new_gclient += self.DEFAULT_SNAPSHOT_SOLUTION_TEXT % {
807 'solution_name': d.name,
808 'solution_url': d.url,
809 'safesync_url' : d.safesync_url or '',
810 'solution_deps': ''.join(custom_deps),
811 }
812 # Print the snapshot configuration file
813 print(self.DEFAULT_SNAPSHOT_FILE_TEXT % {'solution_list': new_gclient})
nasser@codeaurora.orgde8f3522010-03-11 23:47:44 +0000814 else:
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +0000815 entries = {}
816 for d in self.tree(False):
817 if self._options.actual:
818 entries[d.name] = GetURLAndRev(d)
819 else:
820 entries[d.name] = d.parsed_url
821 keys = sorted(entries.keys())
822 for x in keys:
maruel@chromium.orgce464892010-08-12 17:12:18 +0000823 print('%s: %s' % (x, entries[x]))
maruel@chromium.orgdde32ee2010-08-10 17:44:05 +0000824 logging.info(str(self))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000825
maruel@chromium.orgf50907b2010-08-12 17:05:48 +0000826 def ParseDepsFile(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000827 """No DEPS to parse for a .gclient file."""
maruel@chromium.org049bced2010-08-12 13:37:20 +0000828 raise gclient_utils.Error('Internal error')
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000829
maruel@chromium.org75a59272010-06-11 22:34:03 +0000830 def root_dir(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000831 """Root directory of gclient checkout."""
maruel@chromium.org75a59272010-06-11 22:34:03 +0000832 return self._root_dir
833
maruel@chromium.org271375b2010-06-23 19:17:38 +0000834 def enforced_os(self):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000835 """What deps_os entries that are to be parsed."""
maruel@chromium.org271375b2010-06-23 19:17:38 +0000836 return self._enforced_os
837
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000838 def recursion_limit(self):
839 """How recursive can each dependencies in DEPS file can load DEPS file."""
840 return self._recursion_limit
841
maruel@chromium.org0d812442010-08-10 12:41:08 +0000842 def tree(self, include_all):
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000843 """Returns a flat list of all the dependencies."""
maruel@chromium.org0d812442010-08-10 12:41:08 +0000844 return self.subtree(include_all)
maruel@chromium.orgd36fba82010-06-28 16:50:40 +0000845
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000846
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000847#### gclient commands.
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000848
849
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000850def CMDcleanup(parser, args):
851 """Cleans up all working copies.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +0000852
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000853Mostly svn-specific. Simply runs 'svn cleanup' for each module.
maruel@chromium.org79692d62010-05-14 18:57:13 +0000854"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000855 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
856 help='override deps for the specified (comma-separated) '
857 'platform(s); \'all\' will process all deps_os '
858 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000859 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +0000860 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000861 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000862 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000863 if options.verbose:
864 # Print out the .gclient file. This is longer than if we just printed the
865 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +0000866 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000867 return client.RunOnDeps('cleanup', args)
868
869
piman@chromium.org4b90e3a2010-07-01 20:28:26 +0000870@attr('usage', '[command] [args ...]')
871def CMDrecurse(parser, args):
872 """Operates on all the entries.
873
874 Runs a shell command on all entries.
875 """
876 # Stop parsing at the first non-arg so that these go through to the command
877 parser.disable_interspersed_args()
878 parser.add_option('-s', '--scm', action='append', default=[],
879 help='choose scm types to operate upon')
880 options, args = parser.parse_args(args)
881 root, entries = gclient_utils.GetGClientRootAndEntries()
882 scm_set = set()
883 for scm in options.scm:
884 scm_set.update(scm.split(','))
885
886 # Pass in the SCM type as an env variable
887 env = os.environ.copy()
888
889 for path, url in entries.iteritems():
890 scm = gclient_scm.GetScmName(url)
891 if scm_set and scm not in scm_set:
892 continue
maruel@chromium.org2b9aa8e2010-08-25 20:01:42 +0000893 cwd = os.path.normpath(os.path.join(root, path))
maruel@chromium.orgac610232010-10-13 14:01:31 +0000894 if scm:
895 env['GCLIENT_SCM'] = scm
896 if url:
897 env['GCLIENT_URL'] = url
898 gclient_utils.Popen(args, cwd=cwd, env=env).communicate()
899 return 0
piman@chromium.org4b90e3a2010-07-01 20:28:26 +0000900
901
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000902@attr('usage', '[url] [safesync url]')
903def CMDconfig(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +0000904 """Create a .gclient file in the current directory.
905
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000906This specifies the configuration for further commands. After update/sync,
maruel@chromium.org79692d62010-05-14 18:57:13 +0000907top-level DEPS files in each module are read to determine dependent
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000908modules to operate on as well. If optional [url] parameter is
maruel@chromium.org79692d62010-05-14 18:57:13 +0000909provided, then configuration is read from a specified Subversion server
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000910URL.
maruel@chromium.org79692d62010-05-14 18:57:13 +0000911"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000912 parser.add_option('--spec',
913 help='create a gclient file containing the provided '
914 'string. Due to Cygwin/Python brokenness, it '
915 'probably can\'t contain any newlines.')
916 parser.add_option('--name',
917 help='overrides the default name for the solution')
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000918 (options, args) = parser.parse_args(args)
maruel@chromium.org5fc2a332010-05-26 19:37:15 +0000919 if ((options.spec and args) or len(args) > 2 or
920 (not options.spec and not args)):
921 parser.error('Inconsistent arguments. Use either --spec or one or 2 args')
922
maruel@chromium.org0329e672009-05-13 18:41:04 +0000923 if os.path.exists(options.config_filename):
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000924 raise gclient_utils.Error('%s file already exists in the current directory'
maruel@chromium.orge3608df2009-11-10 20:22:57 +0000925 % options.config_filename)
maruel@chromium.org2806acc2009-05-15 12:33:34 +0000926 client = GClient('.', options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000927 if options.spec:
928 client.SetConfig(options.spec)
929 else:
maruel@chromium.org1ab7ffc2009-06-03 17:21:37 +0000930 base_url = args[0].rstrip('/')
iposva@chromium.org8cf7a392010-04-07 17:20:26 +0000931 if not options.name:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000932 name = base_url.split('/')[-1]
iposva@chromium.org8cf7a392010-04-07 17:20:26 +0000933 else:
934 # specify an alternate relpath for the given URL.
935 name = options.name
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000936 safesync_url = ''
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000937 if len(args) > 1:
938 safesync_url = args[1]
939 client.SetDefaultConfig(name, base_url, safesync_url)
940 client.SaveConfig()
maruel@chromium.org79692d62010-05-14 18:57:13 +0000941 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000942
943
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000944def CMDexport(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +0000945 """Wrapper for svn export for all managed directories."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000946 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
947 help='override deps for the specified (comma-separated) '
948 'platform(s); \'all\' will process all deps_os '
949 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000950 (options, args) = parser.parse_args(args)
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +0000951 if len(args) != 1:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000952 raise gclient_utils.Error('Need directory name')
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +0000953 client = GClient.LoadCurrentConfig(options)
954
955 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000956 raise gclient_utils.Error('client not configured; see \'gclient config\'')
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +0000957
958 if options.verbose:
959 # Print out the .gclient file. This is longer than if we just printed the
960 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +0000961 print(client.config_content)
phajdan.jr@chromium.org644aa0c2009-07-17 20:20:41 +0000962 return client.RunOnDeps('export', args)
963
maruel@google.comfb2b8eb2009-04-23 21:03:42 +0000964
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000965@attr('epilog', """Example:
966 gclient pack > patch.txt
967 generate simple patch for configured client and dependences
968""")
969def CMDpack(parser, args):
maruel@chromium.org79692d62010-05-14 18:57:13 +0000970 """Generate a patch which can be applied at the root of the tree.
maruel@chromium.orgddff62d2010-05-17 21:02:36 +0000971
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000972Internally, runs 'svn diff'/'git diff' on each checked out module and
maruel@chromium.org79692d62010-05-14 18:57:13 +0000973dependencies, and performs minimal postprocessing of the output. The
974resulting patch is printed to stdout and can be applied to a freshly
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000975checked out tree via 'patch -p0 < patchfile'.
maruel@chromium.org79692d62010-05-14 18:57:13 +0000976"""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000977 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
978 help='override deps for the specified (comma-separated) '
979 'platform(s); \'all\' will process all deps_os '
980 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000981 (options, args) = parser.parse_args(args)
kbr@google.comab318592009-09-04 00:54:55 +0000982 client = GClient.LoadCurrentConfig(options)
983 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000984 raise gclient_utils.Error('client not configured; see \'gclient config\'')
kbr@google.comab318592009-09-04 00:54:55 +0000985 if options.verbose:
986 # Print out the .gclient file. This is longer than if we just printed the
987 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +0000988 print(client.config_content)
kbr@google.comab318592009-09-04 00:54:55 +0000989 return client.RunOnDeps('pack', args)
990
991
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000992def CMDstatus(parser, args):
993 """Show modification status for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +0000994 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
995 help='override deps for the specified (comma-separated) '
996 'platform(s); \'all\' will process all deps_os '
997 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +0000998 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +0000999 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001000 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001001 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001002 if options.verbose:
1003 # Print out the .gclient file. This is longer than if we just printed the
1004 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001005 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001006 return client.RunOnDeps('status', args)
1007
1008
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001009@attr('epilog', """Examples:
maruel@chromium.org79692d62010-05-14 18:57:13 +00001010 gclient sync
1011 update files from SCM according to current configuration,
1012 *for modules which have changed since last update or sync*
1013 gclient sync --force
1014 update files from SCM according to current configuration, for
1015 all modules (useful for recovering files deleted from local copy)
1016 gclient sync --revision src@31000
1017 update src directory to r31000
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001018""")
1019def CMDsync(parser, args):
1020 """Checkout/update all modules."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001021 parser.add_option('-f', '--force', action='store_true',
1022 help='force update even for unchanged modules')
1023 parser.add_option('-n', '--nohooks', action='store_true',
1024 help='don\'t run hooks after the update is complete')
1025 parser.add_option('-r', '--revision', action='append',
1026 dest='revisions', metavar='REV', default=[],
1027 help='Enforces revision/hash for the solutions with the '
1028 'format src@rev. The src@ part is optional and can be '
1029 'skipped. -r can be used multiple times when .gclient '
1030 'has multiple solutions configured and will work even '
1031 'if the src@ part is skipped.')
1032 parser.add_option('-H', '--head', action='store_true',
1033 help='skips any safesync_urls specified in '
1034 'configured solutions and sync to head instead')
1035 parser.add_option('-D', '--delete_unversioned_trees', action='store_true',
1036 help='delete any unexpected unversioned trees '
1037 'that are in the checkout')
1038 parser.add_option('-R', '--reset', action='store_true',
1039 help='resets any local changes before updating (git only)')
1040 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1041 help='override deps for the specified (comma-separated) '
1042 'platform(s); \'all\' will process all deps_os '
1043 'references')
1044 parser.add_option('-m', '--manually_grab_svn_rev', action='store_true',
1045 help='Skip svn up whenever possible by requesting '
1046 'actual HEAD revision from the repository')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001047 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001048 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001049
1050 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001051 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001052
maruel@chromium.org307d1792010-05-31 20:03:13 +00001053 if options.revisions and options.head:
1054 # TODO(maruel): Make it a parser.error if it doesn't break any builder.
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001055 print('Warning: you cannot use both --head and --revision')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001056
1057 if options.verbose:
1058 # Print out the .gclient file. This is longer than if we just printed the
1059 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001060 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001061 return client.RunOnDeps('update', args)
1062
1063
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001064def CMDupdate(parser, args):
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001065 """Alias for the sync command. Deprecated."""
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001066 return CMDsync(parser, args)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001067
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001068def CMDdiff(parser, args):
1069 """Displays local diff for every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001070 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1071 help='override deps for the specified (comma-separated) '
1072 'platform(s); \'all\' will process all deps_os '
1073 'references')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001074 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001075 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001076 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001077 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001078 if options.verbose:
1079 # Print out the .gclient file. This is longer than if we just printed the
1080 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001081 print(client.config_content)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001082 return client.RunOnDeps('diff', args)
1083
1084
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001085def CMDrevert(parser, args):
1086 """Revert all modifications in every dependencies."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001087 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1088 help='override deps for the specified (comma-separated) '
1089 'platform(s); \'all\' will process all deps_os '
1090 'references')
1091 parser.add_option('-n', '--nohooks', action='store_true',
1092 help='don\'t run hooks after the revert is complete')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001093 (options, args) = parser.parse_args(args)
1094 # --force is implied.
1095 options.force = True
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001096 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001097 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001098 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001099 return client.RunOnDeps('revert', args)
1100
1101
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001102def CMDrunhooks(parser, args):
1103 """Runs hooks for files that have been modified in the local working copy."""
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001104 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1105 help='override deps for the specified (comma-separated) '
1106 'platform(s); \'all\' will process all deps_os '
1107 'references')
1108 parser.add_option('-f', '--force', action='store_true', default=True,
1109 help='Deprecated. No effect.')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001110 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001111 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001112 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001113 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001114 if options.verbose:
1115 # Print out the .gclient file. This is longer than if we just printed the
1116 # client dict, but more legible, and it might contain helpful comments.
maruel@chromium.org116704f2010-06-11 17:34:38 +00001117 print(client.config_content)
maruel@chromium.org5df6a462009-08-28 18:52:26 +00001118 options.force = True
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001119 options.nohooks = False
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001120 return client.RunOnDeps('runhooks', args)
1121
1122
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001123def CMDrevinfo(parser, args):
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001124 """Output revision info mapping for the client and its dependencies.
1125
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001126 This allows the capture of an overall 'revision' for the source tree that
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001127 can be used to reproduce the same tree in the future. It is only useful for
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001128 'unpinned dependencies', i.e. DEPS/deps references without a svn revision
1129 number or a git hash. A git branch name isn't 'pinned' since the actual
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001130 commit can change.
1131 """
1132 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1133 help='override deps for the specified (comma-separated) '
1134 'platform(s); \'all\' will process all deps_os '
1135 'references')
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001136 parser.add_option('-a', '--actual', action='store_true',
1137 help='gets the actual checked out revisions instead of the '
1138 'ones specified in the DEPS and .gclient files')
maruel@chromium.org9eda4112010-06-11 18:56:10 +00001139 parser.add_option('-s', '--snapshot', action='store_true',
1140 help='creates a snapshot .gclient file of the current '
maruel@chromium.orgb1e315f2010-08-11 18:44:50 +00001141 'version of all repositories to reproduce the tree, '
1142 'implies -a')
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001143 (options, args) = parser.parse_args(args)
maruel@chromium.org2806acc2009-05-15 12:33:34 +00001144 client = GClient.LoadCurrentConfig(options)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001145 if not client:
maruel@chromium.org0b6a0842010-06-15 14:34:19 +00001146 raise gclient_utils.Error('client not configured; see \'gclient config\'')
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001147 client.PrintRevInfo()
maruel@chromium.org79692d62010-05-14 18:57:13 +00001148 return 0
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001149
1150
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001151def Command(name):
1152 return getattr(sys.modules[__name__], 'CMD' + name, None)
1153
1154
1155def CMDhelp(parser, args):
1156 """Prints list of commands or help for a specific command."""
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001157 (_, args) = parser.parse_args(args)
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001158 if len(args) == 1:
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001159 return Main(args + ['--help'])
maruel@chromium.orgddff62d2010-05-17 21:02:36 +00001160 parser.print_help()
1161 return 0
1162
1163
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001164def GenUsage(parser, command):
1165 """Modify an OptParse object with the function's documentation."""
1166 obj = Command(command)
1167 if command == 'help':
1168 command = '<command>'
1169 # OptParser.description prefer nicely non-formatted strings.
1170 parser.description = re.sub('[\r\n ]{2,}', ' ', obj.__doc__)
1171 usage = getattr(obj, 'usage', '')
1172 parser.set_usage('%%prog %s [options] %s' % (command, usage))
1173 parser.epilog = getattr(obj, 'epilog', None)
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001174
1175
1176def Main(argv):
maruel@chromium.org5ca27692010-05-26 19:32:41 +00001177 """Doesn't parse the arguments here, just find the right subcommand to
1178 execute."""
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001179 try:
maruel@chromium.orge0de9cb2010-09-17 15:07:14 +00001180 # Make stdout auto-flush so buildbot doesn't kill us during lengthy
1181 # operations. Python as a strong tendency to buffer sys.stdout.
1182 sys.stdout = gclient_utils.MakeFileAutoFlush(sys.stdout)
maruel@chromium.org4ed34182010-09-17 15:57:47 +00001183 # Make stdout annotated with the thread ids.
1184 sys.stdout = gclient_utils.MakeFileAnnotated(sys.stdout)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001185 # Do it late so all commands are listed.
1186 CMDhelp.usage = ('\n\nCommands are:\n' + '\n'.join([
1187 ' %-10s %s' % (fn[3:], Command(fn[3:]).__doc__.split('\n')[0].strip())
1188 for fn in dir(sys.modules[__name__]) if fn.startswith('CMD')]))
1189 parser = optparse.OptionParser(version='%prog ' + __version__)
maruel@chromium.orgfac9d3d2010-09-10 20:38:49 +00001190 parser.add_option('-j', '--jobs', default=1, type='int',
maruel@chromium.org3b84d4c2010-09-10 18:02:43 +00001191 help='Specify how many SCM commands can run in parallel; '
1192 'default=%default')
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001193 parser.add_option('-v', '--verbose', action='count', default=0,
1194 help='Produces additional output for diagnostics. Can be '
1195 'used up to three times for more logging info.')
1196 parser.add_option('--gclientfile', dest='config_filename',
1197 default=os.environ.get('GCLIENT_FILE', '.gclient'),
1198 help='Specify an alternate %default file')
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001199 # Integrate standard options processing.
1200 old_parser = parser.parse_args
1201 def Parse(args):
1202 (options, args) = old_parser(args)
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001203 level = None
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001204 if options.verbose == 2:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001205 level = logging.INFO
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001206 elif options.verbose > 2:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001207 level = logging.DEBUG
1208 logging.basicConfig(level=level,
1209 format='%(module)s(%(lineno)d) %(funcName)s:%(message)s')
1210 options.entries_filename = options.config_filename + '_entries'
maruel@chromium.org9e5317a2010-08-13 20:35:11 +00001211 if options.jobs < 1:
1212 parser.error('--jobs must be 1 or higher')
maruel@chromium.orge3216c62010-07-08 03:31:43 +00001213
1214 # These hacks need to die.
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001215 if not hasattr(options, 'revisions'):
1216 # GClient.RunOnDeps expects it even if not applicable.
1217 options.revisions = []
1218 if not hasattr(options, 'head'):
1219 options.head = None
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001220 if not hasattr(options, 'nohooks'):
1221 options.nohooks = True
1222 if not hasattr(options, 'deps_os'):
1223 options.deps_os = None
maruel@chromium.orge3216c62010-07-08 03:31:43 +00001224 if not hasattr(options, 'manually_grab_svn_rev'):
1225 options.manually_grab_svn_rev = None
1226 if not hasattr(options, 'force'):
1227 options.force = None
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001228 return (options, args)
1229 parser.parse_args = Parse
1230 # We don't want wordwrapping in epilog (usually examples)
1231 parser.format_epilog = lambda _: parser.epilog or ''
1232 if argv:
1233 command = Command(argv[0])
1234 if command:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001235 # 'fix' the usage and the description now that we know the subcommand.
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001236 GenUsage(parser, argv[0])
1237 return command(parser, argv[1:])
1238 # Not a known command. Default to help.
1239 GenUsage(parser, 'help')
1240 return CMDhelp(parser, argv)
1241 except gclient_utils.Error, e:
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001242 print >> sys.stderr, 'Error: %s' % str(e)
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001243 return 1
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001244
1245
maruel@chromium.orgf0fc9912010-06-11 17:57:33 +00001246if '__main__' == __name__:
maruel@chromium.org6e29d572010-06-04 17:32:20 +00001247 sys.exit(Main(sys.argv[1:]))
maruel@google.comfb2b8eb2009-04-23 21:03:42 +00001248
1249# vim: ts=2:sw=2:tw=80:et: