blob: 9eec5d8b73b0747098b3cea68e4c4b488a1d8300 [file] [log] [blame]
[email protected]fb2b8eb2009-04-23 21:03:421#!/usr/bin/python
[email protected]ba551772010-02-03 18:21:422# 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.
[email protected]fb2b8eb2009-04-23 21:03:425
[email protected]0b6a0842010-06-15 14:34:196"""Meta checkout manager supporting both Subversion and GIT.
[email protected]fb2b8eb2009-04-23 21:03:427
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
[email protected]67820ef2009-07-27 17:23:0024 working copy as a result of a "sync"/"update" or "revert" operation. This
[email protected]0b6a0842010-06-15 14:34:1925 can be prevented by using --nohooks (hooks run by default). Hooks can also
[email protected]5df6a462009-08-28 18:52:2626 be forced to run with the "runhooks" operation. If "sync" is run with
[email protected]fb2b8eb2009-04-23 21:03:4227 --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
[email protected]71b40682009-07-31 23:40:0941 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.
[email protected]fb2b8eb2009-04-23 21:03:4244
45 Example:
46 hooks = [
47 { "pattern": "\\.(gif|jpe?g|pr0n|png)$",
48 "action": ["python", "image_indexer.py", "--all"]},
49 ]
50"""
51
[email protected]9e5317a2010-08-13 20:35:1152__version__ = "0.6"
[email protected]fb2b8eb2009-04-23 21:03:4253
[email protected]9e5317a2010-08-13 20:35:1154import copy
[email protected]754960e2009-09-21 12:31:0555import logging
[email protected]fb2b8eb2009-04-23 21:03:4256import optparse
57import os
[email protected]621939b2010-08-10 20:12:0058import posixpath
[email protected]2e38de72009-09-28 17:04:4759import pprint
[email protected]fb2b8eb2009-04-23 21:03:4260import re
[email protected]4b90e3a2010-07-01 20:28:2661import subprocess
[email protected]fb2b8eb2009-04-23 21:03:4262import sys
[email protected]fb2b8eb2009-04-23 21:03:4263import urlparse
[email protected]fb2b8eb2009-04-23 21:03:4264import urllib
65
[email protected]ada4c652009-12-03 15:32:0166import breakpad
67
[email protected]5f3eee32009-09-17 00:34:3068import gclient_scm
69import gclient_utils
[email protected]1f7a3d12010-02-04 15:11:5070from third_party.repo.progress import Progress
[email protected]fb2b8eb2009-04-23 21:03:4271
[email protected]fb2b8eb2009-04-23 21:03:4272
[email protected]1f7d1182010-05-17 18:17:3873def 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
[email protected]e3da35f2010-03-09 21:40:4579
[email protected]fb2b8eb2009-04-23 21:03:4280
[email protected]fb2b8eb2009-04-23 21:03:4281## GClient implementation.
82
[email protected]fb2b8eb2009-04-23 21:03:4283
[email protected]116704f2010-06-11 17:34:3884class 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
[email protected]116704f2010-06-11 17:34:38101 class FileImpl(object):
102 """Used to implement the File('') syntax which lets you sync a single file
[email protected]e3216c62010-07-08 03:31:43103 from a SVN repo."""
[email protected]116704f2010-06-11 17:34:38104
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
[email protected]80cbe8b2010-08-13 13:53:07138class Dependency(GClientKeywords, gclient_utils.WorkItem):
[email protected]54a07a22010-06-14 19:07:39139 """Object that represents a dependency checkout."""
[email protected]9eda4112010-06-11 18:56:10140 DEPS_FILE = 'DEPS'
[email protected]0b6a0842010-06-15 14:34:19141
[email protected]0d812442010-08-10 12:41:08142 def __init__(self, parent, name, url, safesync_url, custom_deps,
[email protected]f50907b2010-08-12 17:05:48143 custom_vars, deps_file, should_process):
[email protected]54a07a22010-06-14 19:07:39144 GClientKeywords.__init__(self)
[email protected]6985efc2010-09-08 13:26:12145 gclient_utils.WorkItem.__init__(self)
[email protected]54a07a22010-06-14 19:07:39146 self.parent = parent
147 self.name = name
148 self.url = url
[email protected]df2b3152010-07-21 17:35:24149 self.parsed_url = None
[email protected]54a07a22010-06-14 19:07:39150 # 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 {}
[email protected]0b6a0842010-06-15 14:34:19154 self.deps_hooks = []
[email protected]54a07a22010-06-14 19:07:39155 self.dependencies = []
156 self.deps_file = deps_file or self.DEPS_FILE
[email protected]df2b3152010-07-21 17:35:24157 # A cache of the files affected by the current operation, necessary for
158 # hooks.
[email protected]861fd0f2010-07-23 03:05:05159 self._file_list = []
[email protected]85c2a192010-07-22 21:14:43160 # If it is not set to True, the dependency wasn't processed for its child
161 # dependency, i.e. its DEPS wasn't read.
[email protected]271375b2010-06-23 19:17:38162 self.deps_parsed = False
[email protected]f50907b2010-08-12 17:05:48163 # This dependency should be processed, i.e. checked out
164 self.should_process = should_process
[email protected]f3abb802010-08-10 17:19:56165 # 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
[email protected]621939b2010-08-10 20:12:00169 # 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)
[email protected]fb2b8eb2009-04-23 21:03:42175
[email protected]54a07a22010-06-14 19:07:39176 # 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
[email protected]df2b3152010-07-21 17:35:24188 def LateOverride(self, url):
[email protected]da7a1f92010-08-10 17:19:02189 """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()."""
[email protected]f50907b2010-08-12 17:05:48193 assert self.parsed_url == None or not self.should_process, self.parsed_url
[email protected]df2b3152010-07-21 17:35:24194 overriden_url = self.get_custom_deps(self.name, url)
195 if overriden_url != url:
[email protected]dde32ee2010-08-10 17:44:05196 logging.info('%s, %s was overriden to %s' % (self.name, url,
[email protected]da7a1f92010-08-10 17:19:02197 overriden_url))
198 return overriden_url
[email protected]df2b3152010-07-21 17:35:24199 elif isinstance(url, self.FromImpl):
200 ref = [dep for dep in self.tree(True) if url.module_name == dep.name]
[email protected]f3abb802010-08-10 17:19:56201 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.
[email protected]df2b3152010-07-21 17:35:24205 ref = ref[0]
[email protected]98d05fa2010-07-22 21:58:01206 sub_target = url.sub_target_name or self.name
[email protected]df2b3152010-07-21 17:35:24207 # Make sure the referenced dependency DEPS file is loaded and file the
208 # inner referenced dependency.
[email protected]f50907b2010-08-12 17:05:48209 ref.ParseDepsFile()
[email protected]df2b3152010-07-21 17:35:24210 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:
[email protected]f3abb802010-08-10 17:19:56216 raise gclient_utils.Error(
[email protected]dde32ee2010-08-10 17:44:05217 'Couldn\'t find %s in %s, referenced by %s\n%s' % (
218 sub_target, ref.name, self.name, str(self.root_parent())))
[email protected]df2b3152010-07-21 17:35:24219 # Call LateOverride() again.
[email protected]da7a1f92010-08-10 17:19:02220 parsed_url = found_dep.LateOverride(found_dep.url)
[email protected]dde32ee2010-08-10 17:44:05221 logging.info('%s, %s to %s' % (self.name, url, parsed_url))
[email protected]da7a1f92010-08-10 17:19:02222 return parsed_url
[email protected]df2b3152010-07-21 17:35:24223 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)
[email protected]da7a1f92010-08-10 17:19:02236 parsed_url = scm.FullUrlForRelativeUrl(url)
[email protected]df2b3152010-07-21 17:35:24237 else:
[email protected]da7a1f92010-08-10 17:19:02238 parsed_url = url
[email protected]dde32ee2010-08-10 17:44:05239 logging.info('%s, %s -> %s' % (self.name, url, parsed_url))
[email protected]da7a1f92010-08-10 17:19:02240 return parsed_url
[email protected]df2b3152010-07-21 17:35:24241 elif isinstance(url, self.FileImpl):
[email protected]da7a1f92010-08-10 17:19:02242 parsed_url = url
[email protected]dde32ee2010-08-10 17:44:05243 logging.info('%s, %s -> %s (File)' % (self.name, url, parsed_url))
[email protected]da7a1f92010-08-10 17:19:02244 return parsed_url
245 elif url is None:
246 return None
247 else:
248 raise gclient_utils.Error('Unkown url type')
[email protected]df2b3152010-07-21 17:35:24249
[email protected]f50907b2010-08-12 17:05:48250 def ParseDepsFile(self):
[email protected]271375b2010-06-23 19:17:38251 """Parses the DEPS file for this dependency."""
[email protected]f50907b2010-08-12 17:05:48252 assert self.processed == True
[email protected]df2b3152010-07-21 17:35:24253 if self.deps_parsed:
[email protected]dde32ee2010-08-10 17:44:05254 logging.debug('%s was already parsed' % self.name)
[email protected]df2b3152010-07-21 17:35:24255 return
[email protected]271375b2010-06-23 19:17:38256 self.deps_parsed = True
257 filepath = os.path.join(self.root_dir(), self.name, self.deps_file)
258 if not os.path.isfile(filepath):
[email protected]dde32ee2010-08-10 17:44:05259 logging.info('%s: No DEPS file found at %s' % (self.name, filepath))
[email protected]df2b3152010-07-21 17:35:24260 return
[email protected]271375b2010-06-23 19:17:38261 deps_content = gclient_utils.FileRead(filepath)
[email protected]dde32ee2010-08-10 17:44:05262 logging.debug(deps_content)
[email protected]0d425922010-06-21 19:22:24263
[email protected]271375b2010-06-23 19:17:38264 # 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 }
[email protected]5990f9d2010-07-07 18:02:58274 try:
275 exec(deps_content, global_scope, local_scope)
276 except SyntaxError, e:
277 gclient_utils.SyntaxErrorToError(filepath, e)
[email protected]271375b2010-06-23 19:17:38278 deps = local_scope.get('deps', {})
[email protected]fb2b8eb2009-04-23 21:03:42279 # load os specific dependencies if defined. these dependencies may
280 # override or extend the values defined by the 'deps' member.
[email protected]271375b2010-06-23 19:17:38281 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
[email protected]fb2b8eb2009-04-23 21:03:42286 # 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
[email protected]271375b2010-06-23 19:17:38293 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]
[email protected]fb2b8eb2009-04-23 21:03:42300
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.
[email protected]271375b2010-06-23 19:17:38304 use_relative_paths = local_scope.get('use_relative_paths', False)
305 if use_relative_paths:
[email protected]fb2b8eb2009-04-23 21:03:42306 rel_deps = {}
307 for d, url in deps.items():
308 # normpath is required to allow DEPS to use .. in their
309 # dependency local path.
[email protected]271375b2010-06-23 19:17:38310 rel_deps[os.path.normpath(os.path.join(self.name, d))] = url
311 deps = rel_deps
[email protected]fb2b8eb2009-04-23 21:03:42312
[email protected]df2b3152010-07-21 17:35:24313 # Convert the deps into real Dependency.
314 for name, url in deps.iteritems():
315 if name in [s.name for s in self.dependencies]:
[email protected]dde32ee2010-08-10 17:44:05316 raise gclient_utils.Error(
317 'The same name "%s" appears multiple times in the deps section' %
318 name)
[email protected]f50907b2010-08-12 17:05:48319 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()))
[email protected]0d812442010-08-10 12:41:08333 self.dependencies.append(Dependency(self, name, url, None, None, None,
[email protected]f50907b2010-08-12 17:05:48334 None, should_process))
[email protected]dde32ee2010-08-10 17:44:05335 logging.debug('Loaded: %s' % str(self))
[email protected]fb2b8eb2009-04-23 21:03:42336
[email protected]3742c842010-09-09 19:27:14337 def run(self, revision_overrides, command, args, work_queue, options):
[email protected]df2b3152010-07-21 17:35:24338 """Runs 'command' before parsing the DEPS in case it's a initial checkout
339 or a revert."""
[email protected]861fd0f2010-07-23 03:05:05340 assert self._file_list == []
[email protected]f50907b2010-08-12 17:05:48341 if not self.should_process:
342 return
[email protected]df2b3152010-07-21 17:35:24343 # 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)
[email protected]da7a1f92010-08-10 17:19:02347 self.parsed_url = self.LateOverride(self.url)
[email protected]df2b3152010-07-21 17:35:24348 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()],
[email protected]861fd0f2010-07-23 03:05:05358 self._file_list)
[email protected]df2b3152010-07-21 17:35:24359 else:
[email protected]9e5317a2010-08-13 20:35:11360 # Create a shallow copy to mutate revision.
361 options = copy.copy(options)
[email protected]df2b3152010-07-21 17:35:24362 options.revision = revision_overrides.get(self.name)
363 scm = gclient_scm.CreateSCM(self.parsed_url, self.root_dir(), self.name)
[email protected]861fd0f2010-07-23 03:05:05364 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]
[email protected]f3abb802010-08-10 17:19:56367 self.processed = True
[email protected]f50907b2010-08-12 17:05:48368 if self.recursion_limit() > 0:
[email protected]df2b3152010-07-21 17:35:24369 # Then we can parse the DEPS file.
[email protected]f50907b2010-08-12 17:05:48370 self.ParseDepsFile()
[email protected]621939b2010-08-10 20:12:00371 # 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
[email protected]049bced2010-08-12 13:37:20373 # src/foo. Yes, it's O(n^2)... It's important to do that before
374 # enqueueing them.
[email protected]621939b2010-08-10 20:12:00375 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
[email protected]df2b3152010-07-21 17:35:24382 # Parse the dependencies of this dependency.
383 for s in self.dependencies:
[email protected]049bced2010-08-12 13:37:20384 work_queue.enqueue(s)
[email protected]fb2b8eb2009-04-23 21:03:42385
[email protected]df2b3152010-07-21 17:35:24386 def RunHooksRecursively(self, options):
[email protected]049bced2010-08-12 13:37:20387 """Evaluates all hooks, running actions as needed. run()
[email protected]df2b3152010-07-21 17:35:24388 must have been called before to load the DEPS."""
[email protected]f50907b2010-08-12 17:05:48389 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
[email protected]dc7445d2010-07-09 21:05:29393 # If "--force" was specified, run all hooks regardless of what files have
[email protected]df2b3152010-07-21 17:35:24394 # changed.
[email protected]f50907b2010-08-12 17:05:48395 if self.deps_hooks:
[email protected]df2b3152010-07-21 17:35:24396 # 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.
[email protected]861fd0f2010-07-23 03:05:05408 file_list = self.file_list()
409 for i in range(len(file_list)):
[email protected]df2b3152010-07-21 17:35:24410 # It depends on the command being executed (like runhooks vs sync).
[email protected]861fd0f2010-07-23 03:05:05411 if not os.path.isabs(file_list[i]):
[email protected]df2b3152010-07-21 17:35:24412 continue
[email protected]dc7445d2010-07-09 21:05:29413
[email protected]df2b3152010-07-21 17:35:24414 prefix = os.path.commonprefix([self.root_dir().lower(),
[email protected]861fd0f2010-07-23 03:05:05415 file_list[i].lower()])
416 file_list[i] = file_list[i][len(prefix):]
[email protected]df2b3152010-07-21 17:35:24417
418 # Strip any leading path separators.
[email protected]861fd0f2010-07-23 03:05:05419 while (file_list[i].startswith('\\') or
420 file_list[i].startswith('/')):
421 file_list[i] = file_list[i][1:]
[email protected]df2b3152010-07-21 17:35:24422
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'])
[email protected]861fd0f2010-07-23 03:05:05427 matching_file_list = [f for f in file_list if pattern.search(f)]
[email protected]df2b3152010-07-21 17:35:24428 if matching_file_list:
429 self._RunHookAction(hook_dict, matching_file_list)
[email protected]f50907b2010-08-12 17:05:48430 for s in self.dependencies:
431 s.RunHooksRecursively(options)
[email protected]fb2b8eb2009-04-23 21:03:42432
[email protected]eaf61062010-07-07 18:42:39433 def _RunHookAction(self, hook_dict, matching_file_list):
434 """Runs the action from a single hook."""
[email protected]dde32ee2010-08-10 17:44:05435 # 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
[email protected]f3abb802010-08-10 17:19:56438 self.hooks_ran = True
[email protected]dde32ee2010-08-10 17:44:05439 logging.debug(hook_dict)
440 logging.debug(matching_file_list)
[email protected]eaf61062010-07-07 18:42:39441 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
[email protected]17d01792010-09-01 18:07:10452 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)
[email protected]eaf61062010-07-07 18:42:39461
[email protected]271375b2010-06-23 19:17:38462 def root_dir(self):
463 return self.parent.root_dir()
464
465 def enforced_os(self):
466 return self.parent.enforced_os()
467
[email protected]d36fba82010-06-28 16:50:40468 def recursion_limit(self):
469 return self.parent.recursion_limit() - 1
470
[email protected]0d812442010-08-10 12:41:08471 def tree(self, include_all):
472 return self.parent.tree(include_all)
[email protected]d36fba82010-06-28 16:50:40473
[email protected]0d812442010-08-10 12:41:08474 def subtree(self, include_all):
[email protected]f50907b2010-08-12 17:05:48475 """Breadth first"""
[email protected]c57e4f22010-07-22 21:37:46476 result = []
[email protected]f50907b2010-08-12 17:05:48477 for d in self.dependencies:
478 if d.should_process or include_all:
[email protected]044f4e32010-07-22 21:59:57479 result.append(d)
[email protected]f50907b2010-08-12 17:05:48480 for d in self.dependencies:
481 result.extend(d.subtree(include_all))
[email protected]c57e4f22010-07-22 21:37:46482 return result
483
[email protected]d36fba82010-06-28 16:50:40484 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
[email protected]861fd0f2010-07-23 03:05:05491 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
[email protected]d36fba82010-06-28 16:50:40497 def __str__(self):
498 out = []
[email protected]dde32ee2010-08-10 17:44:05499 for i in ('name', 'url', 'parsed_url', 'safesync_url', 'custom_deps',
[email protected]f50907b2010-08-12 17:05:48500 'custom_vars', 'deps_hooks', '_file_list', 'should_process',
501 'processed', 'hooks_ran', 'deps_parsed', 'requirements'):
[email protected]d36fba82010-06-28 16:50:40502 # '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
[email protected]bffb9042010-07-22 20:59:36514 def hierarchy(self):
[email protected]bc2d2f92010-07-22 21:26:48515 """Returns a human-readable hierarchical reference to a Dependency."""
[email protected]bffb9042010-07-22 20:59:36516 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
[email protected]dde32ee2010-08-10 17:44:05523 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
[email protected]9a66ddf2010-06-16 16:54:16530
531class GClient(Dependency):
[email protected]d36fba82010-06-28 16:50:40532 """Object that represent a gclient checkout. A tree of Dependency(), one per
533 solution or DEPS entry."""
[email protected]9a66ddf2010-06-16 16:54:16534
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 },
[email protected]73e21142010-07-05 13:32:01552 "safesync_url": "%(safesync_url)s",
[email protected]9a66ddf2010-06-16 16:54:16553 },
554]
555""")
556
557 DEFAULT_SNAPSHOT_SOLUTION_TEXT = ("""\
558 { "name" : "%(solution_name)s",
559 "url" : "%(solution_url)s",
560 "custom_deps" : {
[email protected]73e21142010-07-05 13:32:01561%(solution_deps)s },
562 "safesync_url": "%(safesync_url)s",
[email protected]9a66ddf2010-06-16 16:54:16563 },
564""")
565
566 DEFAULT_SNAPSHOT_FILE_TEXT = ("""\
567# Snapshot generated with gclient revinfo --snapshot
568solutions = [
[email protected]73e21142010-07-05 13:32:01569%(solution_list)s]
[email protected]9a66ddf2010-06-16 16:54:16570""")
571
572 def __init__(self, root_dir, options):
[email protected]0d812442010-08-10 12:41:08573 # Do not change previous behavior. Only solution level and immediate DEPS
574 # are processed.
575 self._recursion_limit = 2
[email protected]f50907b2010-08-12 17:05:48576 Dependency.__init__(self, None, None, None, None, None, None, None, True)
[email protected]0d425922010-06-21 19:22:24577 self._options = options
[email protected]271375b2010-06-23 19:17:38578 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
[email protected]9a66ddf2010-06-16 16:54:16586 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:
[email protected]5990f9d2010-07-07 18:02:58595 gclient_utils.SyntaxErrorToError('.gclient', e)
[email protected]9a66ddf2010-06-16 16:54:16596 for s in config_dict.get('solutions', []):
[email protected]81843b82010-06-28 16:49:26597 try:
[email protected]f50907b2010-08-12 17:05:48598 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'])
[email protected]81843b82010-06-28 16:49:26602 self.dependencies.append(Dependency(
603 self, s['name'], s['url'],
604 s.get('safesync_url', None),
605 s.get('custom_deps', {}),
[email protected]0d812442010-08-10 12:41:08606 s.get('custom_vars', {}),
[email protected]f50907b2010-08-12 17:05:48607 None,
608 True))
[email protected]81843b82010-06-28 16:49:26609 except KeyError:
610 raise gclient_utils.Error('Invalid .gclient file. Solution is '
611 'incomplete: %s' % s)
[email protected]9a66ddf2010-06-16 16:54:16612 # .gclient can have hooks.
613 self.deps_hooks = config_dict.get('hooks', [])
[email protected]049bced2010-08-12 13:37:20614 self.deps_parsed = True
[email protected]9a66ddf2010-06-16 16:54:16615
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."""
[email protected]15804092010-09-02 17:07:37625 path = gclient_utils.FindGclientRoot(os.getcwd(), options.config_filename)
[email protected]9a66ddf2010-06-16 16:54:16626 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)))
[email protected]15804092010-09-02 17:07:37631 return client
[email protected]9a66ddf2010-06-16 16:54:16632
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
[email protected]df2b3152010-07-21 17:35:24640 def _SaveEntries(self):
[email protected]9a66ddf2010-06-16 16:54:16641 """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.
[email protected]9a66ddf2010-06-16 16:54:16644 """
645 # Sometimes pprint.pformat will use {', sometimes it'll use { ' ... It
646 # makes testing a bit too fun.
[email protected]df2b3152010-07-21 17:35:24647 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'
[email protected]9a66ddf2010-06-16 16:54:16654 file_path = os.path.join(self.root_dir(), self._options.entries_filename)
[email protected]df2b3152010-07-21 17:35:24655 logging.info(result)
656 gclient_utils.FileWrite(file_path, result)
[email protected]9a66ddf2010-06-16 16:54:16657
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):
[email protected]73e21142010-07-05 13:32:01668 return {}
[email protected]5990f9d2010-07-07 18:02:58669 try:
670 exec(gclient_utils.FileRead(filename), scope)
671 except SyntaxError, e:
672 gclient_utils.SyntaxErrorToError(filename, e)
[email protected]9a66ddf2010-06-16 16:54:16673 return scope['entries']
674
[email protected]54a07a22010-06-14 19:07:39675 def _EnforceRevisions(self):
[email protected]918a9ae2010-05-28 15:50:30676 """Checks for revision overrides."""
677 revision_overrides = {}
[email protected]307d1792010-05-31 20:03:13678 if self._options.head:
679 return revision_overrides
[email protected]54a07a22010-06-14 19:07:39680 for s in self.dependencies:
681 if not s.safesync_url:
[email protected]307d1792010-05-31 20:03:13682 continue
[email protected]54a07a22010-06-14 19:07:39683 handle = urllib.urlopen(s.safesync_url)
[email protected]307d1792010-05-31 20:03:13684 rev = handle.read().strip()
685 handle.close()
686 if len(rev):
[email protected]54a07a22010-06-14 19:07:39687 self._options.revisions.append('%s@%s' % (s.name, rev))
[email protected]307d1792010-05-31 20:03:13688 if not self._options.revisions:
689 return revision_overrides
690 # --revision will take over safesync_url.
[email protected]54a07a22010-06-14 19:07:39691 solutions_names = [s.name for s in self.dependencies]
[email protected]307d1792010-05-31 20:03:13692 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)
[email protected]54a07a22010-06-14 19:07:39697 sol, rev = revision.split('@', 1)
[email protected]307d1792010-05-31 20:03:13698 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:
[email protected]918a9ae2010-05-28 15:50:30703 revision_overrides[sol] = rev
[email protected]307d1792010-05-31 20:03:13704 index += 1
[email protected]918a9ae2010-05-28 15:50:30705 return revision_overrides
706
[email protected]fb2b8eb2009-04-23 21:03:42707 def RunOnDeps(self, command, args):
708 """Runs a command on each dependency in a client and its dependencies.
709
[email protected]fb2b8eb2009-04-23 21:03:42710 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.
[email protected]fb2b8eb2009-04-23 21:03:42713 """
[email protected]54a07a22010-06-14 19:07:39714 if not self.dependencies:
[email protected]73e21142010-07-05 13:32:01715 raise gclient_utils.Error('No solution specified')
[email protected]54a07a22010-06-14 19:07:39716 revision_overrides = self._EnforceRevisions()
[email protected]df2b3152010-07-21 17:35:24717 pm = None
[email protected]5b3f8852010-09-10 16:49:54718 # Disable progress for non-tty stdout.
719 if command in ('update', 'revert') and sys.stdout.isatty():
[email protected]049bced2010-08-12 13:37:20720 pm = Progress('Syncing projects', 1)
[email protected]9e5317a2010-08-13 20:35:11721 work_queue = gclient_utils.ExecutionQueue(self._options.jobs, pm)
[email protected]049bced2010-08-12 13:37:20722 for s in self.dependencies:
723 work_queue.enqueue(s)
[email protected]3742c842010-09-09 19:27:14724 work_queue.flush(revision_overrides, command, args, options=self._options)
[email protected]6f363722010-04-27 00:41:09725
[email protected]df2b3152010-07-21 17:35:24726 # 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)
[email protected]fb2b8eb2009-04-23 21:03:42730
731 if command == 'update':
[email protected]cdcee802009-06-23 15:30:42732 # 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.
[email protected]df2b3152010-07-21 17:35:24735 entries = [i.name for i in self.tree(False)]
736 for entry, prev_url in self._ReadEntries().iteritems():
[email protected]c5e9aec2009-08-03 18:25:56737 # Fix path separator on Windows.
738 entry_fixed = entry.replace('/', os.path.sep)
[email protected]75a59272010-06-11 22:34:03739 e_dir = os.path.join(self.root_dir(), entry_fixed)
[email protected]c5e9aec2009-08-03 18:25:56740 # Use entry and not entry_fixed there.
[email protected]0329e672009-05-13 18:41:04741 if entry not in entries and os.path.exists(e_dir):
[email protected]df2b3152010-07-21 17:35:24742 file_list = []
743 scm = gclient_scm.CreateSCM(prev_url, self.root_dir(), entry_fixed)
744 scm.status(self._options, [], file_list)
745 modified_files = file_list != []
[email protected]83017012009-09-28 18:52:12746 if not self._options.delete_unversioned_trees or modified_files:
[email protected]c5e9aec2009-08-03 18:25:56747 # There are modified files in this entry. Keep warning until
748 # removed.
[email protected]d36fba82010-06-28 16:50:40749 print(('\nWARNING: \'%s\' is no longer part of this client. '
750 'It is recommended that you manually remove it.\n') %
[email protected]c5e9aec2009-08-03 18:25:56751 entry_fixed)
[email protected]fb2b8eb2009-04-23 21:03:42752 else:
753 # Delete the entry
[email protected]73e21142010-07-05 13:32:01754 print('\n________ deleting \'%s\' in \'%s\'' % (
755 entry_fixed, self.root_dir()))
[email protected]5f3eee32009-09-17 00:34:30756 gclient_utils.RemoveDirectory(e_dir)
[email protected]fb2b8eb2009-04-23 21:03:42757 # record the current list of entries for next time
[email protected]df2b3152010-07-21 17:35:24758 self._SaveEntries()
[email protected]17cdf762010-05-28 17:30:52759 return 0
[email protected]fb2b8eb2009-04-23 21:03:42760
761 def PrintRevInfo(self):
[email protected]54a07a22010-06-14 19:07:39762 if not self.dependencies:
[email protected]73e21142010-07-05 13:32:01763 raise gclient_utils.Error('No solution specified')
[email protected]df2b3152010-07-21 17:35:24764 # Load all the settings.
[email protected]9e5317a2010-08-13 20:35:11765 work_queue = gclient_utils.ExecutionQueue(self._options.jobs, None)
[email protected]049bced2010-08-12 13:37:20766 for s in self.dependencies:
767 work_queue.enqueue(s)
[email protected]3742c842010-09-09 19:27:14768 work_queue.flush({}, None, [], options=self._options)
[email protected]fb2b8eb2009-04-23 21:03:42769
[email protected]6da25d02010-08-11 17:32:55770 def GetURLAndRev(dep):
771 """Returns the revision-qualified SCM url for a Dependency."""
772 if dep.parsed_url is None:
[email protected]baa578e2010-07-12 17:36:59773 return None
[email protected]6da25d02010-08-11 17:32:55774 if isinstance(dep.parsed_url, self.FileImpl):
775 original_url = dep.parsed_url.file_location
776 else:
777 original_url = dep.parsed_url
[email protected]5d63eb82010-03-24 23:22:09778 url, _ = gclient_utils.SplitUrlRevision(original_url)
[email protected]6da25d02010-08-11 17:32:55779 scm = gclient_scm.CreateSCM(original_url, self.root_dir(), dep.name)
[email protected]df2b3152010-07-21 17:35:24780 if not os.path.isdir(scm.checkout_path):
781 return None
[email protected]baa578e2010-07-12 17:36:59782 return '%s@%s' % (url, scm.revinfo(self._options, [], None))
[email protected]fb2b8eb2009-04-23 21:03:42783
[email protected]baa578e2010-07-12 17:36:59784 if self._options.snapshot:
[email protected]df2b3152010-07-21 17:35:24785 new_gclient = ''
786 # First level at .gclient
787 for d in self.dependencies:
788 entries = {}
[email protected]6da25d02010-08-11 17:32:55789 def GrabDeps(dep):
[email protected]df2b3152010-07-21 17:35:24790 """Recursively grab dependencies."""
[email protected]6da25d02010-08-11 17:32:55791 for d in dep.dependencies:
792 entries[d.name] = GetURLAndRev(d)
793 GrabDeps(d)
[email protected]df2b3152010-07-21 17:35:24794 GrabDeps(d)
795 custom_deps = []
796 for k in sorted(entries.keys()):
797 if entries[k]:
798 # Quotes aren't escaped...
799 custom_deps.append(' \"%s\": \'%s\',\n' % (k, entries[k]))
800 else:
801 custom_deps.append(' \"%s\": None,\n' % k)
802 new_gclient += self.DEFAULT_SNAPSHOT_SOLUTION_TEXT % {
803 'solution_name': d.name,
804 'solution_url': d.url,
805 'safesync_url' : d.safesync_url or '',
806 'solution_deps': ''.join(custom_deps),
807 }
808 # Print the snapshot configuration file
809 print(self.DEFAULT_SNAPSHOT_FILE_TEXT % {'solution_list': new_gclient})
[email protected]de8f3522010-03-11 23:47:44810 else:
[email protected]b1e315f2010-08-11 18:44:50811 entries = {}
812 for d in self.tree(False):
813 if self._options.actual:
814 entries[d.name] = GetURLAndRev(d)
815 else:
816 entries[d.name] = d.parsed_url
817 keys = sorted(entries.keys())
818 for x in keys:
[email protected]ce464892010-08-12 17:12:18819 print('%s: %s' % (x, entries[x]))
[email protected]dde32ee2010-08-10 17:44:05820 logging.info(str(self))
[email protected]fb2b8eb2009-04-23 21:03:42821
[email protected]f50907b2010-08-12 17:05:48822 def ParseDepsFile(self):
[email protected]d36fba82010-06-28 16:50:40823 """No DEPS to parse for a .gclient file."""
[email protected]049bced2010-08-12 13:37:20824 raise gclient_utils.Error('Internal error')
[email protected]d36fba82010-06-28 16:50:40825
[email protected]75a59272010-06-11 22:34:03826 def root_dir(self):
[email protected]d36fba82010-06-28 16:50:40827 """Root directory of gclient checkout."""
[email protected]75a59272010-06-11 22:34:03828 return self._root_dir
829
[email protected]271375b2010-06-23 19:17:38830 def enforced_os(self):
[email protected]d36fba82010-06-28 16:50:40831 """What deps_os entries that are to be parsed."""
[email protected]271375b2010-06-23 19:17:38832 return self._enforced_os
833
[email protected]d36fba82010-06-28 16:50:40834 def recursion_limit(self):
835 """How recursive can each dependencies in DEPS file can load DEPS file."""
836 return self._recursion_limit
837
[email protected]0d812442010-08-10 12:41:08838 def tree(self, include_all):
[email protected]d36fba82010-06-28 16:50:40839 """Returns a flat list of all the dependencies."""
[email protected]0d812442010-08-10 12:41:08840 return self.subtree(include_all)
[email protected]d36fba82010-06-28 16:50:40841
[email protected]fb2b8eb2009-04-23 21:03:42842
[email protected]5ca27692010-05-26 19:32:41843#### gclient commands.
[email protected]fb2b8eb2009-04-23 21:03:42844
845
[email protected]5ca27692010-05-26 19:32:41846def CMDcleanup(parser, args):
847 """Cleans up all working copies.
[email protected]ddff62d2010-05-17 21:02:36848
[email protected]5ca27692010-05-26 19:32:41849Mostly svn-specific. Simply runs 'svn cleanup' for each module.
[email protected]79692d62010-05-14 18:57:13850"""
[email protected]0b6a0842010-06-15 14:34:19851 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
852 help='override deps for the specified (comma-separated) '
853 'platform(s); \'all\' will process all deps_os '
854 'references')
[email protected]5ca27692010-05-26 19:32:41855 (options, args) = parser.parse_args(args)
[email protected]2806acc2009-05-15 12:33:34856 client = GClient.LoadCurrentConfig(options)
[email protected]fb2b8eb2009-04-23 21:03:42857 if not client:
[email protected]0b6a0842010-06-15 14:34:19858 raise gclient_utils.Error('client not configured; see \'gclient config\'')
[email protected]fb2b8eb2009-04-23 21:03:42859 if options.verbose:
860 # Print out the .gclient file. This is longer than if we just printed the
861 # client dict, but more legible, and it might contain helpful comments.
[email protected]116704f2010-06-11 17:34:38862 print(client.config_content)
[email protected]fb2b8eb2009-04-23 21:03:42863 return client.RunOnDeps('cleanup', args)
864
865
[email protected]4b90e3a2010-07-01 20:28:26866@attr('usage', '[command] [args ...]')
867def CMDrecurse(parser, args):
868 """Operates on all the entries.
869
870 Runs a shell command on all entries.
871 """
872 # Stop parsing at the first non-arg so that these go through to the command
873 parser.disable_interspersed_args()
874 parser.add_option('-s', '--scm', action='append', default=[],
875 help='choose scm types to operate upon')
876 options, args = parser.parse_args(args)
877 root, entries = gclient_utils.GetGClientRootAndEntries()
878 scm_set = set()
879 for scm in options.scm:
880 scm_set.update(scm.split(','))
881
882 # Pass in the SCM type as an env variable
883 env = os.environ.copy()
884
885 for path, url in entries.iteritems():
886 scm = gclient_scm.GetScmName(url)
887 if scm_set and scm not in scm_set:
888 continue
[email protected]2b9aa8e2010-08-25 20:01:42889 cwd = os.path.normpath(os.path.join(root, path))
[email protected]4b90e3a2010-07-01 20:28:26890 env['GCLIENT_SCM'] = scm
891 env['GCLIENT_URL'] = url
[email protected]2b9aa8e2010-08-25 20:01:42892 subprocess.Popen(args, cwd=cwd, env=env).communicate()
[email protected]4b90e3a2010-07-01 20:28:26893
894
[email protected]5ca27692010-05-26 19:32:41895@attr('usage', '[url] [safesync url]')
896def CMDconfig(parser, args):
[email protected]ddff62d2010-05-17 21:02:36897 """Create a .gclient file in the current directory.
898
[email protected]5ca27692010-05-26 19:32:41899This specifies the configuration for further commands. After update/sync,
[email protected]79692d62010-05-14 18:57:13900top-level DEPS files in each module are read to determine dependent
[email protected]5ca27692010-05-26 19:32:41901modules to operate on as well. If optional [url] parameter is
[email protected]79692d62010-05-14 18:57:13902provided, then configuration is read from a specified Subversion server
[email protected]5ca27692010-05-26 19:32:41903URL.
[email protected]79692d62010-05-14 18:57:13904"""
[email protected]0b6a0842010-06-15 14:34:19905 parser.add_option('--spec',
906 help='create a gclient file containing the provided '
907 'string. Due to Cygwin/Python brokenness, it '
908 'probably can\'t contain any newlines.')
909 parser.add_option('--name',
910 help='overrides the default name for the solution')
[email protected]5ca27692010-05-26 19:32:41911 (options, args) = parser.parse_args(args)
[email protected]5fc2a332010-05-26 19:37:15912 if ((options.spec and args) or len(args) > 2 or
913 (not options.spec and not args)):
914 parser.error('Inconsistent arguments. Use either --spec or one or 2 args')
915
[email protected]0329e672009-05-13 18:41:04916 if os.path.exists(options.config_filename):
[email protected]0b6a0842010-06-15 14:34:19917 raise gclient_utils.Error('%s file already exists in the current directory'
[email protected]e3608df2009-11-10 20:22:57918 % options.config_filename)
[email protected]2806acc2009-05-15 12:33:34919 client = GClient('.', options)
[email protected]fb2b8eb2009-04-23 21:03:42920 if options.spec:
921 client.SetConfig(options.spec)
922 else:
[email protected]1ab7ffc2009-06-03 17:21:37923 base_url = args[0].rstrip('/')
[email protected]8cf7a392010-04-07 17:20:26924 if not options.name:
[email protected]0b6a0842010-06-15 14:34:19925 name = base_url.split('/')[-1]
[email protected]8cf7a392010-04-07 17:20:26926 else:
927 # specify an alternate relpath for the given URL.
928 name = options.name
[email protected]0b6a0842010-06-15 14:34:19929 safesync_url = ''
[email protected]fb2b8eb2009-04-23 21:03:42930 if len(args) > 1:
931 safesync_url = args[1]
932 client.SetDefaultConfig(name, base_url, safesync_url)
933 client.SaveConfig()
[email protected]79692d62010-05-14 18:57:13934 return 0
[email protected]fb2b8eb2009-04-23 21:03:42935
936
[email protected]5ca27692010-05-26 19:32:41937def CMDexport(parser, args):
[email protected]ddff62d2010-05-17 21:02:36938 """Wrapper for svn export for all managed directories."""
[email protected]0b6a0842010-06-15 14:34:19939 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
940 help='override deps for the specified (comma-separated) '
941 'platform(s); \'all\' will process all deps_os '
942 'references')
[email protected]5ca27692010-05-26 19:32:41943 (options, args) = parser.parse_args(args)
[email protected]644aa0c2009-07-17 20:20:41944 if len(args) != 1:
[email protected]0b6a0842010-06-15 14:34:19945 raise gclient_utils.Error('Need directory name')
[email protected]644aa0c2009-07-17 20:20:41946 client = GClient.LoadCurrentConfig(options)
947
948 if not client:
[email protected]0b6a0842010-06-15 14:34:19949 raise gclient_utils.Error('client not configured; see \'gclient config\'')
[email protected]644aa0c2009-07-17 20:20:41950
951 if options.verbose:
952 # Print out the .gclient file. This is longer than if we just printed the
953 # client dict, but more legible, and it might contain helpful comments.
[email protected]116704f2010-06-11 17:34:38954 print(client.config_content)
[email protected]644aa0c2009-07-17 20:20:41955 return client.RunOnDeps('export', args)
956
[email protected]fb2b8eb2009-04-23 21:03:42957
[email protected]5ca27692010-05-26 19:32:41958@attr('epilog', """Example:
959 gclient pack > patch.txt
960 generate simple patch for configured client and dependences
961""")
962def CMDpack(parser, args):
[email protected]79692d62010-05-14 18:57:13963 """Generate a patch which can be applied at the root of the tree.
[email protected]ddff62d2010-05-17 21:02:36964
[email protected]5ca27692010-05-26 19:32:41965Internally, runs 'svn diff'/'git diff' on each checked out module and
[email protected]79692d62010-05-14 18:57:13966dependencies, and performs minimal postprocessing of the output. The
967resulting patch is printed to stdout and can be applied to a freshly
[email protected]5ca27692010-05-26 19:32:41968checked out tree via 'patch -p0 < patchfile'.
[email protected]79692d62010-05-14 18:57:13969"""
[email protected]0b6a0842010-06-15 14:34:19970 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
971 help='override deps for the specified (comma-separated) '
972 'platform(s); \'all\' will process all deps_os '
973 'references')
[email protected]5ca27692010-05-26 19:32:41974 (options, args) = parser.parse_args(args)
[email protected]ab318592009-09-04 00:54:55975 client = GClient.LoadCurrentConfig(options)
976 if not client:
[email protected]0b6a0842010-06-15 14:34:19977 raise gclient_utils.Error('client not configured; see \'gclient config\'')
[email protected]ab318592009-09-04 00:54:55978 if options.verbose:
979 # Print out the .gclient file. This is longer than if we just printed the
980 # client dict, but more legible, and it might contain helpful comments.
[email protected]116704f2010-06-11 17:34:38981 print(client.config_content)
[email protected]ab318592009-09-04 00:54:55982 return client.RunOnDeps('pack', args)
983
984
[email protected]5ca27692010-05-26 19:32:41985def CMDstatus(parser, args):
986 """Show modification status for every dependencies."""
[email protected]0b6a0842010-06-15 14:34:19987 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
988 help='override deps for the specified (comma-separated) '
989 'platform(s); \'all\' will process all deps_os '
990 'references')
[email protected]5ca27692010-05-26 19:32:41991 (options, args) = parser.parse_args(args)
[email protected]2806acc2009-05-15 12:33:34992 client = GClient.LoadCurrentConfig(options)
[email protected]fb2b8eb2009-04-23 21:03:42993 if not client:
[email protected]0b6a0842010-06-15 14:34:19994 raise gclient_utils.Error('client not configured; see \'gclient config\'')
[email protected]fb2b8eb2009-04-23 21:03:42995 if options.verbose:
996 # Print out the .gclient file. This is longer than if we just printed the
997 # client dict, but more legible, and it might contain helpful comments.
[email protected]116704f2010-06-11 17:34:38998 print(client.config_content)
[email protected]fb2b8eb2009-04-23 21:03:42999 return client.RunOnDeps('status', args)
1000
1001
[email protected]5ca27692010-05-26 19:32:411002@attr('epilog', """Examples:
[email protected]79692d62010-05-14 18:57:131003 gclient sync
1004 update files from SCM according to current configuration,
1005 *for modules which have changed since last update or sync*
1006 gclient sync --force
1007 update files from SCM according to current configuration, for
1008 all modules (useful for recovering files deleted from local copy)
1009 gclient sync --revision src@31000
1010 update src directory to r31000
[email protected]5ca27692010-05-26 19:32:411011""")
1012def CMDsync(parser, args):
1013 """Checkout/update all modules."""
[email protected]0b6a0842010-06-15 14:34:191014 parser.add_option('-f', '--force', action='store_true',
1015 help='force update even for unchanged modules')
1016 parser.add_option('-n', '--nohooks', action='store_true',
1017 help='don\'t run hooks after the update is complete')
1018 parser.add_option('-r', '--revision', action='append',
1019 dest='revisions', metavar='REV', default=[],
1020 help='Enforces revision/hash for the solutions with the '
1021 'format src@rev. The src@ part is optional and can be '
1022 'skipped. -r can be used multiple times when .gclient '
1023 'has multiple solutions configured and will work even '
1024 'if the src@ part is skipped.')
1025 parser.add_option('-H', '--head', action='store_true',
1026 help='skips any safesync_urls specified in '
1027 'configured solutions and sync to head instead')
1028 parser.add_option('-D', '--delete_unversioned_trees', action='store_true',
1029 help='delete any unexpected unversioned trees '
1030 'that are in the checkout')
1031 parser.add_option('-R', '--reset', action='store_true',
1032 help='resets any local changes before updating (git only)')
1033 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1034 help='override deps for the specified (comma-separated) '
1035 'platform(s); \'all\' will process all deps_os '
1036 'references')
1037 parser.add_option('-m', '--manually_grab_svn_rev', action='store_true',
1038 help='Skip svn up whenever possible by requesting '
1039 'actual HEAD revision from the repository')
[email protected]5ca27692010-05-26 19:32:411040 (options, args) = parser.parse_args(args)
[email protected]2806acc2009-05-15 12:33:341041 client = GClient.LoadCurrentConfig(options)
[email protected]fb2b8eb2009-04-23 21:03:421042
1043 if not client:
[email protected]0b6a0842010-06-15 14:34:191044 raise gclient_utils.Error('client not configured; see \'gclient config\'')
[email protected]fb2b8eb2009-04-23 21:03:421045
[email protected]307d1792010-05-31 20:03:131046 if options.revisions and options.head:
1047 # TODO(maruel): Make it a parser.error if it doesn't break any builder.
[email protected]0b6a0842010-06-15 14:34:191048 print('Warning: you cannot use both --head and --revision')
[email protected]fb2b8eb2009-04-23 21:03:421049
1050 if options.verbose:
1051 # Print out the .gclient file. This is longer than if we just printed the
1052 # client dict, but more legible, and it might contain helpful comments.
[email protected]116704f2010-06-11 17:34:381053 print(client.config_content)
[email protected]fb2b8eb2009-04-23 21:03:421054 return client.RunOnDeps('update', args)
1055
1056
[email protected]5ca27692010-05-26 19:32:411057def CMDupdate(parser, args):
[email protected]ddff62d2010-05-17 21:02:361058 """Alias for the sync command. Deprecated."""
[email protected]5ca27692010-05-26 19:32:411059 return CMDsync(parser, args)
[email protected]fb2b8eb2009-04-23 21:03:421060
[email protected]5ca27692010-05-26 19:32:411061def CMDdiff(parser, args):
1062 """Displays local diff for every dependencies."""
[email protected]0b6a0842010-06-15 14:34:191063 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1064 help='override deps for the specified (comma-separated) '
1065 'platform(s); \'all\' will process all deps_os '
1066 'references')
[email protected]5ca27692010-05-26 19:32:411067 (options, args) = parser.parse_args(args)
[email protected]2806acc2009-05-15 12:33:341068 client = GClient.LoadCurrentConfig(options)
[email protected]fb2b8eb2009-04-23 21:03:421069 if not client:
[email protected]0b6a0842010-06-15 14:34:191070 raise gclient_utils.Error('client not configured; see \'gclient config\'')
[email protected]fb2b8eb2009-04-23 21:03:421071 if options.verbose:
1072 # Print out the .gclient file. This is longer than if we just printed the
1073 # client dict, but more legible, and it might contain helpful comments.
[email protected]116704f2010-06-11 17:34:381074 print(client.config_content)
[email protected]fb2b8eb2009-04-23 21:03:421075 return client.RunOnDeps('diff', args)
1076
1077
[email protected]5ca27692010-05-26 19:32:411078def CMDrevert(parser, args):
1079 """Revert all modifications in every dependencies."""
[email protected]0b6a0842010-06-15 14:34:191080 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1081 help='override deps for the specified (comma-separated) '
1082 'platform(s); \'all\' will process all deps_os '
1083 'references')
1084 parser.add_option('-n', '--nohooks', action='store_true',
1085 help='don\'t run hooks after the revert is complete')
[email protected]5ca27692010-05-26 19:32:411086 (options, args) = parser.parse_args(args)
1087 # --force is implied.
1088 options.force = True
[email protected]2806acc2009-05-15 12:33:341089 client = GClient.LoadCurrentConfig(options)
[email protected]fb2b8eb2009-04-23 21:03:421090 if not client:
[email protected]0b6a0842010-06-15 14:34:191091 raise gclient_utils.Error('client not configured; see \'gclient config\'')
[email protected]fb2b8eb2009-04-23 21:03:421092 return client.RunOnDeps('revert', args)
1093
1094
[email protected]5ca27692010-05-26 19:32:411095def CMDrunhooks(parser, args):
1096 """Runs hooks for files that have been modified in the local working copy."""
[email protected]0b6a0842010-06-15 14:34:191097 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1098 help='override deps for the specified (comma-separated) '
1099 'platform(s); \'all\' will process all deps_os '
1100 'references')
1101 parser.add_option('-f', '--force', action='store_true', default=True,
1102 help='Deprecated. No effect.')
[email protected]5ca27692010-05-26 19:32:411103 (options, args) = parser.parse_args(args)
[email protected]2806acc2009-05-15 12:33:341104 client = GClient.LoadCurrentConfig(options)
[email protected]fb2b8eb2009-04-23 21:03:421105 if not client:
[email protected]0b6a0842010-06-15 14:34:191106 raise gclient_utils.Error('client not configured; see \'gclient config\'')
[email protected]fb2b8eb2009-04-23 21:03:421107 if options.verbose:
1108 # Print out the .gclient file. This is longer than if we just printed the
1109 # client dict, but more legible, and it might contain helpful comments.
[email protected]116704f2010-06-11 17:34:381110 print(client.config_content)
[email protected]5df6a462009-08-28 18:52:261111 options.force = True
[email protected]5ca27692010-05-26 19:32:411112 options.nohooks = False
[email protected]fb2b8eb2009-04-23 21:03:421113 return client.RunOnDeps('runhooks', args)
1114
1115
[email protected]5ca27692010-05-26 19:32:411116def CMDrevinfo(parser, args):
[email protected]9eda4112010-06-11 18:56:101117 """Output revision info mapping for the client and its dependencies.
1118
[email protected]0b6a0842010-06-15 14:34:191119 This allows the capture of an overall 'revision' for the source tree that
[email protected]9eda4112010-06-11 18:56:101120 can be used to reproduce the same tree in the future. It is only useful for
[email protected]0b6a0842010-06-15 14:34:191121 'unpinned dependencies', i.e. DEPS/deps references without a svn revision
1122 number or a git hash. A git branch name isn't 'pinned' since the actual
[email protected]9eda4112010-06-11 18:56:101123 commit can change.
1124 """
1125 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1126 help='override deps for the specified (comma-separated) '
1127 'platform(s); \'all\' will process all deps_os '
1128 'references')
[email protected]b1e315f2010-08-11 18:44:501129 parser.add_option('-a', '--actual', action='store_true',
1130 help='gets the actual checked out revisions instead of the '
1131 'ones specified in the DEPS and .gclient files')
[email protected]9eda4112010-06-11 18:56:101132 parser.add_option('-s', '--snapshot', action='store_true',
1133 help='creates a snapshot .gclient file of the current '
[email protected]b1e315f2010-08-11 18:44:501134 'version of all repositories to reproduce the tree, '
1135 'implies -a')
[email protected]5ca27692010-05-26 19:32:411136 (options, args) = parser.parse_args(args)
[email protected]2806acc2009-05-15 12:33:341137 client = GClient.LoadCurrentConfig(options)
[email protected]fb2b8eb2009-04-23 21:03:421138 if not client:
[email protected]0b6a0842010-06-15 14:34:191139 raise gclient_utils.Error('client not configured; see \'gclient config\'')
[email protected]fb2b8eb2009-04-23 21:03:421140 client.PrintRevInfo()
[email protected]79692d62010-05-14 18:57:131141 return 0
[email protected]fb2b8eb2009-04-23 21:03:421142
1143
[email protected]5ca27692010-05-26 19:32:411144def Command(name):
1145 return getattr(sys.modules[__name__], 'CMD' + name, None)
1146
1147
1148def CMDhelp(parser, args):
1149 """Prints list of commands or help for a specific command."""
[email protected]6e29d572010-06-04 17:32:201150 (_, args) = parser.parse_args(args)
[email protected]ddff62d2010-05-17 21:02:361151 if len(args) == 1:
[email protected]5ca27692010-05-26 19:32:411152 return Main(args + ['--help'])
[email protected]ddff62d2010-05-17 21:02:361153 parser.print_help()
1154 return 0
1155
1156
[email protected]5ca27692010-05-26 19:32:411157def GenUsage(parser, command):
1158 """Modify an OptParse object with the function's documentation."""
1159 obj = Command(command)
1160 if command == 'help':
1161 command = '<command>'
1162 # OptParser.description prefer nicely non-formatted strings.
1163 parser.description = re.sub('[\r\n ]{2,}', ' ', obj.__doc__)
1164 usage = getattr(obj, 'usage', '')
1165 parser.set_usage('%%prog %s [options] %s' % (command, usage))
1166 parser.epilog = getattr(obj, 'epilog', None)
[email protected]fb2b8eb2009-04-23 21:03:421167
1168
1169def Main(argv):
[email protected]5ca27692010-05-26 19:32:411170 """Doesn't parse the arguments here, just find the right subcommand to
1171 execute."""
[email protected]6e29d572010-06-04 17:32:201172 try:
1173 # Do it late so all commands are listed.
1174 CMDhelp.usage = ('\n\nCommands are:\n' + '\n'.join([
1175 ' %-10s %s' % (fn[3:], Command(fn[3:]).__doc__.split('\n')[0].strip())
1176 for fn in dir(sys.modules[__name__]) if fn.startswith('CMD')]))
1177 parser = optparse.OptionParser(version='%prog ' + __version__)
[email protected]fac9d3d2010-09-10 20:38:491178 parser.add_option('-j', '--jobs', default=1, type='int',
[email protected]3b84d4c2010-09-10 18:02:431179 help='Specify how many SCM commands can run in parallel; '
1180 'default=%default')
[email protected]f0fc9912010-06-11 17:57:331181 parser.add_option('-v', '--verbose', action='count', default=0,
1182 help='Produces additional output for diagnostics. Can be '
1183 'used up to three times for more logging info.')
1184 parser.add_option('--gclientfile', dest='config_filename',
1185 default=os.environ.get('GCLIENT_FILE', '.gclient'),
1186 help='Specify an alternate %default file')
[email protected]6e29d572010-06-04 17:32:201187 # Integrate standard options processing.
1188 old_parser = parser.parse_args
1189 def Parse(args):
1190 (options, args) = old_parser(args)
[email protected]f0fc9912010-06-11 17:57:331191 level = None
[email protected]6e29d572010-06-04 17:32:201192 if options.verbose == 2:
[email protected]f0fc9912010-06-11 17:57:331193 level = logging.INFO
[email protected]6e29d572010-06-04 17:32:201194 elif options.verbose > 2:
[email protected]f0fc9912010-06-11 17:57:331195 level = logging.DEBUG
1196 logging.basicConfig(level=level,
1197 format='%(module)s(%(lineno)d) %(funcName)s:%(message)s')
1198 options.entries_filename = options.config_filename + '_entries'
[email protected]9e5317a2010-08-13 20:35:111199 if options.jobs < 1:
1200 parser.error('--jobs must be 1 or higher')
[email protected]db111f72010-09-08 13:36:531201 # Always autoflush so buildbot doesn't kill us during lengthy operations.
1202 options.stdout = gclient_utils.StdoutAutoFlush(sys.stdout)
[email protected]e3216c62010-07-08 03:31:431203
1204 # These hacks need to die.
[email protected]6e29d572010-06-04 17:32:201205 if not hasattr(options, 'revisions'):
1206 # GClient.RunOnDeps expects it even if not applicable.
1207 options.revisions = []
1208 if not hasattr(options, 'head'):
1209 options.head = None
[email protected]f0fc9912010-06-11 17:57:331210 if not hasattr(options, 'nohooks'):
1211 options.nohooks = True
1212 if not hasattr(options, 'deps_os'):
1213 options.deps_os = None
[email protected]e3216c62010-07-08 03:31:431214 if not hasattr(options, 'manually_grab_svn_rev'):
1215 options.manually_grab_svn_rev = None
1216 if not hasattr(options, 'force'):
1217 options.force = None
[email protected]6e29d572010-06-04 17:32:201218 return (options, args)
1219 parser.parse_args = Parse
1220 # We don't want wordwrapping in epilog (usually examples)
1221 parser.format_epilog = lambda _: parser.epilog or ''
1222 if argv:
1223 command = Command(argv[0])
1224 if command:
[email protected]f0fc9912010-06-11 17:57:331225 # 'fix' the usage and the description now that we know the subcommand.
[email protected]6e29d572010-06-04 17:32:201226 GenUsage(parser, argv[0])
1227 return command(parser, argv[1:])
1228 # Not a known command. Default to help.
1229 GenUsage(parser, 'help')
1230 return CMDhelp(parser, argv)
1231 except gclient_utils.Error, e:
[email protected]f0fc9912010-06-11 17:57:331232 print >> sys.stderr, 'Error: %s' % str(e)
[email protected]6e29d572010-06-04 17:32:201233 return 1
[email protected]fb2b8eb2009-04-23 21:03:421234
1235
[email protected]f0fc9912010-06-11 17:57:331236if '__main__' == __name__:
[email protected]6e29d572010-06-04 17:32:201237 sys.exit(Main(sys.argv[1:]))
[email protected]fb2b8eb2009-04-23 21:03:421238
1239# vim: ts=2:sw=2:tw=80:et: