blob: 4f27747ad571d1ba89b015eef7940d68d1cfa86f [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]f50907b2010-08-12 17:05:4852__version__ = "0.5.2"
[email protected]fb2b8eb2009-04-23 21:03:4253
[email protected]754960e2009-09-21 12:31:0554import logging
[email protected]fb2b8eb2009-04-23 21:03:4255import optparse
56import os
[email protected]621939b2010-08-10 20:12:0057import posixpath
[email protected]2e38de72009-09-28 17:04:4758import pprint
[email protected]fb2b8eb2009-04-23 21:03:4259import re
[email protected]4b90e3a2010-07-01 20:28:2660import subprocess
[email protected]fb2b8eb2009-04-23 21:03:4261import sys
[email protected]049bced2010-08-12 13:37:2062import threading
[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]049bced2010-08-12 13:37:2083class WorkItem(object):
84 """One work item."""
85 requirements = []
86 name = None
87
88 def run(self):
89 pass
90
91
92class ExecutionQueue(object):
93 """Dependencies sometime needs to be run out of order due to From() keyword.
94
95 This class manages that all the required dependencies are run before running
96 each one.
97
98 Methods of this class are multithread safe.
99 """
100 def __init__(self, progress):
101 self.lock = threading.Lock()
102 # List of Dependency.
103 self.queued = []
104 # List of strings representing each Dependency.name that was run.
105 self.ran = []
106 # List of items currently running.
107 self.running = []
108 self.progress = progress
109 if self.progress:
110 self.progress.update()
111
112 def enqueue(self, d):
113 """Enqueue one Dependency to be executed later once its requirements are
114 satisfied.
115 """
116 assert isinstance(d, WorkItem)
117 try:
118 self.lock.acquire()
119 self.queued.append(d)
120 total = len(self.queued) + len(self.ran) + len(self.running)
121 finally:
122 self.lock.release()
123 if self.progress:
124 self.progress._total = total + 1
125 self.progress.update(0)
126
127 def flush(self, *args, **kwargs):
128 """Runs all enqueued items until all are executed."""
129 while self._run_one_item(*args, **kwargs):
130 pass
131 queued = []
132 running = []
133 try:
134 self.lock.acquire()
135 if self.queued:
136 queued = self.queued
137 self.queued = []
138 if self.running:
139 running = self.running
140 self.running = []
141 finally:
142 self.lock.release()
143 if self.progress:
144 self.progress.end()
145 if queued:
146 raise gclient_utils.Error('Entries still queued: %s' % str(queued))
147 if running:
148 raise gclient_utils.Error('Entries still queued: %s' % str(running))
149
150 def _run_one_item(self, *args, **kwargs):
151 """Removes one item from the queue that has all its requirements completed
152 and execute it.
153
154 Returns False if no item could be run.
155 """
156 i = 0
157 d = None
158 try:
159 self.lock.acquire()
160 while i != len(self.queued) and not d:
161 d = self.queued.pop(i)
162 for r in d.requirements:
163 if not r in self.ran:
164 self.queued.insert(i, d)
165 d = None
166 break
167 i += 1
168 if not d:
169 return False
170 self.running.append(d)
171 finally:
172 self.lock.release()
173 d.run(*args, **kwargs)
174 try:
175 self.lock.acquire()
176 # TODO(maruel): http://crbug.com/51711
177 #assert not d.name in self.ran
178 if not d.name in self.ran:
179 self.ran.append(d.name)
180 self.running.remove(d)
181 if self.progress:
182 self.progress.update(1)
183 finally:
184 self.lock.release()
185 return True
186
[email protected]fb2b8eb2009-04-23 21:03:42187
[email protected]116704f2010-06-11 17:34:38188class GClientKeywords(object):
189 class FromImpl(object):
190 """Used to implement the From() syntax."""
191
192 def __init__(self, module_name, sub_target_name=None):
193 """module_name is the dep module we want to include from. It can also be
194 the name of a subdirectory to include from.
195
196 sub_target_name is an optional parameter if the module name in the other
197 DEPS file is different. E.g., you might want to map src/net to net."""
198 self.module_name = module_name
199 self.sub_target_name = sub_target_name
200
201 def __str__(self):
202 return 'From(%s, %s)' % (repr(self.module_name),
203 repr(self.sub_target_name))
204
[email protected]116704f2010-06-11 17:34:38205 class FileImpl(object):
206 """Used to implement the File('') syntax which lets you sync a single file
[email protected]e3216c62010-07-08 03:31:43207 from a SVN repo."""
[email protected]116704f2010-06-11 17:34:38208
209 def __init__(self, file_location):
210 self.file_location = file_location
211
212 def __str__(self):
213 return 'File("%s")' % self.file_location
214
215 def GetPath(self):
216 return os.path.split(self.file_location)[0]
217
218 def GetFilename(self):
219 rev_tokens = self.file_location.split('@')
220 return os.path.split(rev_tokens[0])[1]
221
222 def GetRevision(self):
223 rev_tokens = self.file_location.split('@')
224 if len(rev_tokens) > 1:
225 return rev_tokens[1]
226 return None
227
228 class VarImpl(object):
229 def __init__(self, custom_vars, local_scope):
230 self._custom_vars = custom_vars
231 self._local_scope = local_scope
232
233 def Lookup(self, var_name):
234 """Implements the Var syntax."""
235 if var_name in self._custom_vars:
236 return self._custom_vars[var_name]
237 elif var_name in self._local_scope.get("vars", {}):
238 return self._local_scope["vars"][var_name]
239 raise gclient_utils.Error("Var is not defined: %s" % var_name)
240
241
[email protected]049bced2010-08-12 13:37:20242class Dependency(GClientKeywords, WorkItem):
[email protected]54a07a22010-06-14 19:07:39243 """Object that represents a dependency checkout."""
[email protected]9eda4112010-06-11 18:56:10244 DEPS_FILE = 'DEPS'
[email protected]0b6a0842010-06-15 14:34:19245
[email protected]0d812442010-08-10 12:41:08246 def __init__(self, parent, name, url, safesync_url, custom_deps,
[email protected]f50907b2010-08-12 17:05:48247 custom_vars, deps_file, should_process):
[email protected]54a07a22010-06-14 19:07:39248 GClientKeywords.__init__(self)
249 self.parent = parent
250 self.name = name
251 self.url = url
[email protected]df2b3152010-07-21 17:35:24252 self.parsed_url = None
[email protected]54a07a22010-06-14 19:07:39253 # These 2 are only set in .gclient and not in DEPS files.
254 self.safesync_url = safesync_url
255 self.custom_vars = custom_vars or {}
256 self.custom_deps = custom_deps or {}
[email protected]0b6a0842010-06-15 14:34:19257 self.deps_hooks = []
[email protected]54a07a22010-06-14 19:07:39258 self.dependencies = []
259 self.deps_file = deps_file or self.DEPS_FILE
[email protected]df2b3152010-07-21 17:35:24260 # A cache of the files affected by the current operation, necessary for
261 # hooks.
[email protected]861fd0f2010-07-23 03:05:05262 self._file_list = []
[email protected]85c2a192010-07-22 21:14:43263 # If it is not set to True, the dependency wasn't processed for its child
264 # dependency, i.e. its DEPS wasn't read.
[email protected]271375b2010-06-23 19:17:38265 self.deps_parsed = False
[email protected]f50907b2010-08-12 17:05:48266 # This dependency should be processed, i.e. checked out
267 self.should_process = should_process
[email protected]f3abb802010-08-10 17:19:56268 # This dependency has been processed, i.e. checked out
269 self.processed = False
270 # This dependency had its hook run
271 self.hooks_ran = False
[email protected]621939b2010-08-10 20:12:00272 # Required dependencies to run before running this one:
273 self.requirements = []
274 if self.parent and self.parent.name:
275 self.requirements.append(self.parent.name)
276 if isinstance(self.url, self.FromImpl):
277 self.requirements.append(self.url.module_name)
[email protected]fb2b8eb2009-04-23 21:03:42278
[email protected]54a07a22010-06-14 19:07:39279 # Sanity checks
280 if not self.name and self.parent:
281 raise gclient_utils.Error('Dependency without name')
282 if not isinstance(self.url,
283 (basestring, self.FromImpl, self.FileImpl, None.__class__)):
284 raise gclient_utils.Error('dependency url must be either a string, None, '
285 'File() or From() instead of %s' %
286 self.url.__class__.__name__)
287 if '/' in self.deps_file or '\\' in self.deps_file:
288 raise gclient_utils.Error('deps_file name must not be a path, just a '
289 'filename. %s' % self.deps_file)
290
[email protected]df2b3152010-07-21 17:35:24291 def LateOverride(self, url):
[email protected]da7a1f92010-08-10 17:19:02292 """Resolves the parsed url from url.
293
294 Manages From() keyword accordingly. Do not touch self.parsed_url nor
295 self.url because it may called with other urls due to From()."""
[email protected]f50907b2010-08-12 17:05:48296 assert self.parsed_url == None or not self.should_process, self.parsed_url
[email protected]df2b3152010-07-21 17:35:24297 overriden_url = self.get_custom_deps(self.name, url)
298 if overriden_url != url:
[email protected]dde32ee2010-08-10 17:44:05299 logging.info('%s, %s was overriden to %s' % (self.name, url,
[email protected]da7a1f92010-08-10 17:19:02300 overriden_url))
301 return overriden_url
[email protected]df2b3152010-07-21 17:35:24302 elif isinstance(url, self.FromImpl):
303 ref = [dep for dep in self.tree(True) if url.module_name == dep.name]
[email protected]f3abb802010-08-10 17:19:56304 if not ref:
305 raise gclient_utils.Error('Failed to find one reference to %s. %s' % (
306 url.module_name, ref))
307 # It may happen that len(ref) > 1 but it's no big deal.
[email protected]df2b3152010-07-21 17:35:24308 ref = ref[0]
[email protected]98d05fa2010-07-22 21:58:01309 sub_target = url.sub_target_name or self.name
[email protected]df2b3152010-07-21 17:35:24310 # Make sure the referenced dependency DEPS file is loaded and file the
311 # inner referenced dependency.
[email protected]f50907b2010-08-12 17:05:48312 ref.ParseDepsFile()
[email protected]df2b3152010-07-21 17:35:24313 found_dep = None
314 for d in ref.dependencies:
315 if d.name == sub_target:
316 found_dep = d
317 break
318 if not found_dep:
[email protected]f3abb802010-08-10 17:19:56319 raise gclient_utils.Error(
[email protected]dde32ee2010-08-10 17:44:05320 'Couldn\'t find %s in %s, referenced by %s\n%s' % (
321 sub_target, ref.name, self.name, str(self.root_parent())))
[email protected]df2b3152010-07-21 17:35:24322 # Call LateOverride() again.
[email protected]da7a1f92010-08-10 17:19:02323 parsed_url = found_dep.LateOverride(found_dep.url)
[email protected]dde32ee2010-08-10 17:44:05324 logging.info('%s, %s to %s' % (self.name, url, parsed_url))
[email protected]da7a1f92010-08-10 17:19:02325 return parsed_url
[email protected]df2b3152010-07-21 17:35:24326 elif isinstance(url, basestring):
327 parsed_url = urlparse.urlparse(url)
328 if not parsed_url[0]:
329 # A relative url. Fetch the real base.
330 path = parsed_url[2]
331 if not path.startswith('/'):
332 raise gclient_utils.Error(
333 'relative DEPS entry \'%s\' must begin with a slash' % url)
334 # Create a scm just to query the full url.
335 parent_url = self.parent.parsed_url
336 if isinstance(parent_url, self.FileImpl):
337 parent_url = parent_url.file_location
338 scm = gclient_scm.CreateSCM(parent_url, self.root_dir(), None)
[email protected]da7a1f92010-08-10 17:19:02339 parsed_url = scm.FullUrlForRelativeUrl(url)
[email protected]df2b3152010-07-21 17:35:24340 else:
[email protected]da7a1f92010-08-10 17:19:02341 parsed_url = url
[email protected]dde32ee2010-08-10 17:44:05342 logging.info('%s, %s -> %s' % (self.name, url, parsed_url))
[email protected]da7a1f92010-08-10 17:19:02343 return parsed_url
[email protected]df2b3152010-07-21 17:35:24344 elif isinstance(url, self.FileImpl):
[email protected]da7a1f92010-08-10 17:19:02345 parsed_url = url
[email protected]dde32ee2010-08-10 17:44:05346 logging.info('%s, %s -> %s (File)' % (self.name, url, parsed_url))
[email protected]da7a1f92010-08-10 17:19:02347 return parsed_url
348 elif url is None:
349 return None
350 else:
351 raise gclient_utils.Error('Unkown url type')
[email protected]df2b3152010-07-21 17:35:24352
[email protected]f50907b2010-08-12 17:05:48353 def ParseDepsFile(self):
[email protected]271375b2010-06-23 19:17:38354 """Parses the DEPS file for this dependency."""
[email protected]f50907b2010-08-12 17:05:48355 assert self.processed == True
[email protected]df2b3152010-07-21 17:35:24356 if self.deps_parsed:
[email protected]dde32ee2010-08-10 17:44:05357 logging.debug('%s was already parsed' % self.name)
[email protected]df2b3152010-07-21 17:35:24358 return
[email protected]271375b2010-06-23 19:17:38359 self.deps_parsed = True
360 filepath = os.path.join(self.root_dir(), self.name, self.deps_file)
361 if not os.path.isfile(filepath):
[email protected]dde32ee2010-08-10 17:44:05362 logging.info('%s: No DEPS file found at %s' % (self.name, filepath))
[email protected]df2b3152010-07-21 17:35:24363 return
[email protected]271375b2010-06-23 19:17:38364 deps_content = gclient_utils.FileRead(filepath)
[email protected]dde32ee2010-08-10 17:44:05365 logging.debug(deps_content)
[email protected]0d425922010-06-21 19:22:24366
[email protected]271375b2010-06-23 19:17:38367 # Eval the content.
368 # One thing is unintuitive, vars= {} must happen before Var() use.
369 local_scope = {}
370 var = self.VarImpl(self.custom_vars, local_scope)
371 global_scope = {
372 'File': self.FileImpl,
373 'From': self.FromImpl,
374 'Var': var.Lookup,
375 'deps_os': {},
376 }
[email protected]5990f9d2010-07-07 18:02:58377 try:
378 exec(deps_content, global_scope, local_scope)
379 except SyntaxError, e:
380 gclient_utils.SyntaxErrorToError(filepath, e)
[email protected]271375b2010-06-23 19:17:38381 deps = local_scope.get('deps', {})
[email protected]fb2b8eb2009-04-23 21:03:42382 # load os specific dependencies if defined. these dependencies may
383 # override or extend the values defined by the 'deps' member.
[email protected]271375b2010-06-23 19:17:38384 if 'deps_os' in local_scope:
385 for deps_os_key in self.enforced_os():
386 os_deps = local_scope['deps_os'].get(deps_os_key, {})
387 if len(self.enforced_os()) > 1:
388 # Ignore any conflict when including deps for more than one
[email protected]fb2b8eb2009-04-23 21:03:42389 # platform, so we collect the broadest set of dependencies available.
390 # We may end up with the wrong revision of something for our
391 # platform, but this is the best we can do.
392 deps.update([x for x in os_deps.items() if not x[0] in deps])
393 else:
394 deps.update(os_deps)
395
[email protected]271375b2010-06-23 19:17:38396 self.deps_hooks.extend(local_scope.get('hooks', []))
397
398 # If a line is in custom_deps, but not in the solution, we want to append
399 # this line to the solution.
400 for d in self.custom_deps:
401 if d not in deps:
402 deps[d] = self.custom_deps[d]
[email protected]fb2b8eb2009-04-23 21:03:42403
404 # If use_relative_paths is set in the DEPS file, regenerate
405 # the dictionary using paths relative to the directory containing
406 # the DEPS file.
[email protected]271375b2010-06-23 19:17:38407 use_relative_paths = local_scope.get('use_relative_paths', False)
408 if use_relative_paths:
[email protected]fb2b8eb2009-04-23 21:03:42409 rel_deps = {}
410 for d, url in deps.items():
411 # normpath is required to allow DEPS to use .. in their
412 # dependency local path.
[email protected]271375b2010-06-23 19:17:38413 rel_deps[os.path.normpath(os.path.join(self.name, d))] = url
414 deps = rel_deps
[email protected]fb2b8eb2009-04-23 21:03:42415
[email protected]df2b3152010-07-21 17:35:24416 # Convert the deps into real Dependency.
417 for name, url in deps.iteritems():
418 if name in [s.name for s in self.dependencies]:
[email protected]dde32ee2010-08-10 17:44:05419 raise gclient_utils.Error(
420 'The same name "%s" appears multiple times in the deps section' %
421 name)
[email protected]f50907b2010-08-12 17:05:48422 should_process = self.recursion_limit() > 0 and self.should_process
423 if should_process:
424 tree = dict((d.name, d) for d in self.tree(False))
425 if name in tree:
426 if url == tree[name].url:
427 logging.info('Won\'t process duplicate dependency %s' % tree[name])
428 # In theory we could keep it as a shadow of the other one. In
429 # practice, simply ignore it.
430 #should_process = False
431 continue
432 else:
433 raise gclient_utils.Error(
434 'Dependency %s specified more than once:\n %s\nvs\n %s' %
435 (name, tree[name].hierarchy(), self.hierarchy()))
[email protected]0d812442010-08-10 12:41:08436 self.dependencies.append(Dependency(self, name, url, None, None, None,
[email protected]f50907b2010-08-12 17:05:48437 None, should_process))
[email protected]dde32ee2010-08-10 17:44:05438 logging.debug('Loaded: %s' % str(self))
[email protected]fb2b8eb2009-04-23 21:03:42439
[email protected]049bced2010-08-12 13:37:20440 def run(self, options, revision_overrides, command, args, work_queue):
[email protected]df2b3152010-07-21 17:35:24441 """Runs 'command' before parsing the DEPS in case it's a initial checkout
442 or a revert."""
[email protected]861fd0f2010-07-23 03:05:05443 assert self._file_list == []
[email protected]f50907b2010-08-12 17:05:48444 if not self.should_process:
445 return
[email protected]df2b3152010-07-21 17:35:24446 # When running runhooks, there's no need to consult the SCM.
447 # All known hooks are expected to run unconditionally regardless of working
448 # copy state, so skip the SCM status check.
449 run_scm = command not in ('runhooks', None)
[email protected]da7a1f92010-08-10 17:19:02450 self.parsed_url = self.LateOverride(self.url)
[email protected]df2b3152010-07-21 17:35:24451 if run_scm and self.parsed_url:
452 if isinstance(self.parsed_url, self.FileImpl):
453 # Special support for single-file checkout.
454 if not command in (None, 'cleanup', 'diff', 'pack', 'status'):
455 options.revision = self.parsed_url.GetRevision()
456 scm = gclient_scm.SVNWrapper(self.parsed_url.GetPath(),
457 self.root_dir(),
458 self.name)
459 scm.RunCommand('updatesingle', options,
460 args + [self.parsed_url.GetFilename()],
[email protected]861fd0f2010-07-23 03:05:05461 self._file_list)
[email protected]df2b3152010-07-21 17:35:24462 else:
463 options.revision = revision_overrides.get(self.name)
464 scm = gclient_scm.CreateSCM(self.parsed_url, self.root_dir(), self.name)
[email protected]861fd0f2010-07-23 03:05:05465 scm.RunCommand(command, options, args, self._file_list)
466 self._file_list = [os.path.join(self.name, f.strip())
467 for f in self._file_list]
[email protected]df2b3152010-07-21 17:35:24468 options.revision = None
[email protected]f3abb802010-08-10 17:19:56469 self.processed = True
[email protected]f50907b2010-08-12 17:05:48470 if self.recursion_limit() > 0:
[email protected]df2b3152010-07-21 17:35:24471 # Then we can parse the DEPS file.
[email protected]f50907b2010-08-12 17:05:48472 self.ParseDepsFile()
[email protected]621939b2010-08-10 20:12:00473 # Adjust the implicit dependency requirement; e.g. if a DEPS file contains
474 # both src/foo and src/foo/bar, src/foo/bar is implicitly dependent of
[email protected]049bced2010-08-12 13:37:20475 # src/foo. Yes, it's O(n^2)... It's important to do that before
476 # enqueueing them.
[email protected]621939b2010-08-10 20:12:00477 for s in self.dependencies:
478 for s2 in self.dependencies:
479 if s is s2:
480 continue
481 if s.name.startswith(posixpath.join(s2.name, '')):
482 s.requirements.append(s2.name)
483
[email protected]df2b3152010-07-21 17:35:24484 # Parse the dependencies of this dependency.
485 for s in self.dependencies:
[email protected]049bced2010-08-12 13:37:20486 work_queue.enqueue(s)
[email protected]fb2b8eb2009-04-23 21:03:42487
[email protected]df2b3152010-07-21 17:35:24488 def RunHooksRecursively(self, options):
[email protected]049bced2010-08-12 13:37:20489 """Evaluates all hooks, running actions as needed. run()
[email protected]df2b3152010-07-21 17:35:24490 must have been called before to load the DEPS."""
[email protected]f50907b2010-08-12 17:05:48491 assert self.hooks_ran == False
492 if not self.should_process or self.recursion_limit() <= 0:
493 # Don't run the hook when it is above recursion_limit.
494 return
[email protected]dc7445d2010-07-09 21:05:29495 # If "--force" was specified, run all hooks regardless of what files have
[email protected]df2b3152010-07-21 17:35:24496 # changed.
[email protected]f50907b2010-08-12 17:05:48497 if self.deps_hooks:
[email protected]df2b3152010-07-21 17:35:24498 # TODO(maruel): If the user is using git or git-svn, then we don't know
499 # what files have changed so we always run all hooks. It'd be nice to fix
500 # that.
501 if (options.force or
502 isinstance(self.parsed_url, self.FileImpl) or
503 gclient_scm.GetScmName(self.parsed_url) in ('git', None) or
504 os.path.isdir(os.path.join(self.root_dir(), self.name, '.git'))):
505 for hook_dict in self.deps_hooks:
506 self._RunHookAction(hook_dict, [])
507 else:
508 # TODO(phajdan.jr): We should know exactly when the paths are absolute.
509 # Convert all absolute paths to relative.
[email protected]861fd0f2010-07-23 03:05:05510 file_list = self.file_list()
511 for i in range(len(file_list)):
[email protected]df2b3152010-07-21 17:35:24512 # It depends on the command being executed (like runhooks vs sync).
[email protected]861fd0f2010-07-23 03:05:05513 if not os.path.isabs(file_list[i]):
[email protected]df2b3152010-07-21 17:35:24514 continue
[email protected]dc7445d2010-07-09 21:05:29515
[email protected]df2b3152010-07-21 17:35:24516 prefix = os.path.commonprefix([self.root_dir().lower(),
[email protected]861fd0f2010-07-23 03:05:05517 file_list[i].lower()])
518 file_list[i] = file_list[i][len(prefix):]
[email protected]df2b3152010-07-21 17:35:24519
520 # Strip any leading path separators.
[email protected]861fd0f2010-07-23 03:05:05521 while (file_list[i].startswith('\\') or
522 file_list[i].startswith('/')):
523 file_list[i] = file_list[i][1:]
[email protected]df2b3152010-07-21 17:35:24524
525 # Run hooks on the basis of whether the files from the gclient operation
526 # match each hook's pattern.
527 for hook_dict in self.deps_hooks:
528 pattern = re.compile(hook_dict['pattern'])
[email protected]861fd0f2010-07-23 03:05:05529 matching_file_list = [f for f in file_list if pattern.search(f)]
[email protected]df2b3152010-07-21 17:35:24530 if matching_file_list:
531 self._RunHookAction(hook_dict, matching_file_list)
[email protected]f50907b2010-08-12 17:05:48532 for s in self.dependencies:
533 s.RunHooksRecursively(options)
[email protected]fb2b8eb2009-04-23 21:03:42534
[email protected]eaf61062010-07-07 18:42:39535 def _RunHookAction(self, hook_dict, matching_file_list):
536 """Runs the action from a single hook."""
[email protected]dde32ee2010-08-10 17:44:05537 # A single DEPS file can specify multiple hooks so this function can be
538 # called multiple times on a single Dependency.
539 #assert self.hooks_ran == False
[email protected]f3abb802010-08-10 17:19:56540 self.hooks_ran = True
[email protected]dde32ee2010-08-10 17:44:05541 logging.debug(hook_dict)
542 logging.debug(matching_file_list)
[email protected]eaf61062010-07-07 18:42:39543 command = hook_dict['action'][:]
544 if command[0] == 'python':
545 # If the hook specified "python" as the first item, the action is a
546 # Python script. Run it by starting a new copy of the same
547 # interpreter.
548 command[0] = sys.executable
549
550 if '$matching_files' in command:
551 splice_index = command.index('$matching_files')
552 command[splice_index:splice_index + 1] = matching_file_list
553
554 # Use a discrete exit status code of 2 to indicate that a hook action
555 # failed. Users of this script may wish to treat hook action failures
556 # differently from VC failures.
557 return gclient_utils.SubprocessCall(command, self.root_dir(), fail_status=2)
558
[email protected]271375b2010-06-23 19:17:38559 def root_dir(self):
560 return self.parent.root_dir()
561
562 def enforced_os(self):
563 return self.parent.enforced_os()
564
[email protected]d36fba82010-06-28 16:50:40565 def recursion_limit(self):
566 return self.parent.recursion_limit() - 1
567
[email protected]0d812442010-08-10 12:41:08568 def tree(self, include_all):
569 return self.parent.tree(include_all)
[email protected]d36fba82010-06-28 16:50:40570
[email protected]0d812442010-08-10 12:41:08571 def subtree(self, include_all):
[email protected]f50907b2010-08-12 17:05:48572 """Breadth first"""
[email protected]c57e4f22010-07-22 21:37:46573 result = []
[email protected]f50907b2010-08-12 17:05:48574 for d in self.dependencies:
575 if d.should_process or include_all:
[email protected]044f4e32010-07-22 21:59:57576 result.append(d)
[email protected]f50907b2010-08-12 17:05:48577 for d in self.dependencies:
578 result.extend(d.subtree(include_all))
[email protected]c57e4f22010-07-22 21:37:46579 return result
580
[email protected]d36fba82010-06-28 16:50:40581 def get_custom_deps(self, name, url):
582 """Returns a custom deps if applicable."""
583 if self.parent:
584 url = self.parent.get_custom_deps(name, url)
585 # None is a valid return value to disable a dependency.
586 return self.custom_deps.get(name, url)
587
[email protected]861fd0f2010-07-23 03:05:05588 def file_list(self):
589 result = self._file_list[:]
590 for d in self.dependencies:
591 result.extend(d.file_list())
592 return result
593
[email protected]d36fba82010-06-28 16:50:40594 def __str__(self):
595 out = []
[email protected]dde32ee2010-08-10 17:44:05596 for i in ('name', 'url', 'parsed_url', 'safesync_url', 'custom_deps',
[email protected]f50907b2010-08-12 17:05:48597 'custom_vars', 'deps_hooks', '_file_list', 'should_process',
598 'processed', 'hooks_ran', 'deps_parsed', 'requirements'):
[email protected]d36fba82010-06-28 16:50:40599 # 'deps_file'
600 if self.__dict__[i]:
601 out.append('%s: %s' % (i, self.__dict__[i]))
602
603 for d in self.dependencies:
604 out.extend([' ' + x for x in str(d).splitlines()])
605 out.append('')
606 return '\n'.join(out)
607
608 def __repr__(self):
609 return '%s: %s' % (self.name, self.url)
610
[email protected]bffb9042010-07-22 20:59:36611 def hierarchy(self):
[email protected]bc2d2f92010-07-22 21:26:48612 """Returns a human-readable hierarchical reference to a Dependency."""
[email protected]bffb9042010-07-22 20:59:36613 out = '%s(%s)' % (self.name, self.url)
614 i = self.parent
615 while i and i.name:
616 out = '%s(%s) -> %s' % (i.name, i.url, out)
617 i = i.parent
618 return out
619
[email protected]dde32ee2010-08-10 17:44:05620 def root_parent(self):
621 """Returns the root object, normally a GClient object."""
622 d = self
623 while d.parent:
624 d = d.parent
625 return d
626
[email protected]9a66ddf2010-06-16 16:54:16627
628class GClient(Dependency):
[email protected]d36fba82010-06-28 16:50:40629 """Object that represent a gclient checkout. A tree of Dependency(), one per
630 solution or DEPS entry."""
[email protected]9a66ddf2010-06-16 16:54:16631
632 DEPS_OS_CHOICES = {
633 "win32": "win",
634 "win": "win",
635 "cygwin": "win",
636 "darwin": "mac",
637 "mac": "mac",
638 "unix": "unix",
639 "linux": "unix",
640 "linux2": "unix",
641 }
642
643 DEFAULT_CLIENT_FILE_TEXT = ("""\
644solutions = [
645 { "name" : "%(solution_name)s",
646 "url" : "%(solution_url)s",
647 "custom_deps" : {
648 },
[email protected]73e21142010-07-05 13:32:01649 "safesync_url": "%(safesync_url)s",
[email protected]9a66ddf2010-06-16 16:54:16650 },
651]
652""")
653
654 DEFAULT_SNAPSHOT_SOLUTION_TEXT = ("""\
655 { "name" : "%(solution_name)s",
656 "url" : "%(solution_url)s",
657 "custom_deps" : {
[email protected]73e21142010-07-05 13:32:01658%(solution_deps)s },
659 "safesync_url": "%(safesync_url)s",
[email protected]9a66ddf2010-06-16 16:54:16660 },
661""")
662
663 DEFAULT_SNAPSHOT_FILE_TEXT = ("""\
664# Snapshot generated with gclient revinfo --snapshot
665solutions = [
[email protected]73e21142010-07-05 13:32:01666%(solution_list)s]
[email protected]9a66ddf2010-06-16 16:54:16667""")
668
669 def __init__(self, root_dir, options):
[email protected]0d812442010-08-10 12:41:08670 # Do not change previous behavior. Only solution level and immediate DEPS
671 # are processed.
672 self._recursion_limit = 2
[email protected]f50907b2010-08-12 17:05:48673 Dependency.__init__(self, None, None, None, None, None, None, None, True)
[email protected]0d425922010-06-21 19:22:24674 self._options = options
[email protected]271375b2010-06-23 19:17:38675 if options.deps_os:
676 enforced_os = options.deps_os.split(',')
677 else:
678 enforced_os = [self.DEPS_OS_CHOICES.get(sys.platform, 'unix')]
679 if 'all' in enforced_os:
680 enforced_os = self.DEPS_OS_CHOICES.itervalues()
681 self._enforced_os = list(set(enforced_os))
682 self._root_dir = root_dir
[email protected]9a66ddf2010-06-16 16:54:16683 self.config_content = None
684
685 def SetConfig(self, content):
686 assert self.dependencies == []
687 config_dict = {}
688 self.config_content = content
689 try:
690 exec(content, config_dict)
691 except SyntaxError, e:
[email protected]5990f9d2010-07-07 18:02:58692 gclient_utils.SyntaxErrorToError('.gclient', e)
[email protected]9a66ddf2010-06-16 16:54:16693 for s in config_dict.get('solutions', []):
[email protected]81843b82010-06-28 16:49:26694 try:
[email protected]f50907b2010-08-12 17:05:48695 tree = dict((d.name, d) for d in self.tree(False))
696 if s['name'] in tree:
697 raise gclient_utils.Error(
698 'Dependency %s specified more than once in .gclient' % s['name'])
[email protected]81843b82010-06-28 16:49:26699 self.dependencies.append(Dependency(
700 self, s['name'], s['url'],
701 s.get('safesync_url', None),
702 s.get('custom_deps', {}),
[email protected]0d812442010-08-10 12:41:08703 s.get('custom_vars', {}),
[email protected]f50907b2010-08-12 17:05:48704 None,
705 True))
[email protected]81843b82010-06-28 16:49:26706 except KeyError:
707 raise gclient_utils.Error('Invalid .gclient file. Solution is '
708 'incomplete: %s' % s)
[email protected]9a66ddf2010-06-16 16:54:16709 # .gclient can have hooks.
710 self.deps_hooks = config_dict.get('hooks', [])
[email protected]049bced2010-08-12 13:37:20711 self.direct_reference = True
712 self.deps_parsed = True
[email protected]9a66ddf2010-06-16 16:54:16713
714 def SaveConfig(self):
715 gclient_utils.FileWrite(os.path.join(self.root_dir(),
716 self._options.config_filename),
717 self.config_content)
718
719 @staticmethod
720 def LoadCurrentConfig(options):
721 """Searches for and loads a .gclient file relative to the current working
722 dir. Returns a GClient object."""
723 path = gclient_utils.FindGclientRoot(os.getcwd(), options.config_filename)
724 if not path:
725 return None
726 client = GClient(path, options)
727 client.SetConfig(gclient_utils.FileRead(
728 os.path.join(path, options.config_filename)))
729 return client
730
731 def SetDefaultConfig(self, solution_name, solution_url, safesync_url):
732 self.SetConfig(self.DEFAULT_CLIENT_FILE_TEXT % {
733 'solution_name': solution_name,
734 'solution_url': solution_url,
735 'safesync_url' : safesync_url,
736 })
737
[email protected]df2b3152010-07-21 17:35:24738 def _SaveEntries(self):
[email protected]9a66ddf2010-06-16 16:54:16739 """Creates a .gclient_entries file to record the list of unique checkouts.
740
741 The .gclient_entries file lives in the same directory as .gclient.
[email protected]9a66ddf2010-06-16 16:54:16742 """
743 # Sometimes pprint.pformat will use {', sometimes it'll use { ' ... It
744 # makes testing a bit too fun.
[email protected]df2b3152010-07-21 17:35:24745 result = 'entries = {\n'
746 for entry in self.tree(False):
747 # Skip over File() dependencies as we can't version them.
748 if not isinstance(entry.parsed_url, self.FileImpl):
749 result += ' %s: %s,\n' % (pprint.pformat(entry.name),
750 pprint.pformat(entry.parsed_url))
751 result += '}\n'
[email protected]9a66ddf2010-06-16 16:54:16752 file_path = os.path.join(self.root_dir(), self._options.entries_filename)
[email protected]df2b3152010-07-21 17:35:24753 logging.info(result)
754 gclient_utils.FileWrite(file_path, result)
[email protected]9a66ddf2010-06-16 16:54:16755
756 def _ReadEntries(self):
757 """Read the .gclient_entries file for the given client.
758
759 Returns:
760 A sequence of solution names, which will be empty if there is the
761 entries file hasn't been created yet.
762 """
763 scope = {}
764 filename = os.path.join(self.root_dir(), self._options.entries_filename)
765 if not os.path.exists(filename):
[email protected]73e21142010-07-05 13:32:01766 return {}
[email protected]5990f9d2010-07-07 18:02:58767 try:
768 exec(gclient_utils.FileRead(filename), scope)
769 except SyntaxError, e:
770 gclient_utils.SyntaxErrorToError(filename, e)
[email protected]9a66ddf2010-06-16 16:54:16771 return scope['entries']
772
[email protected]54a07a22010-06-14 19:07:39773 def _EnforceRevisions(self):
[email protected]918a9ae2010-05-28 15:50:30774 """Checks for revision overrides."""
775 revision_overrides = {}
[email protected]307d1792010-05-31 20:03:13776 if self._options.head:
777 return revision_overrides
[email protected]54a07a22010-06-14 19:07:39778 for s in self.dependencies:
779 if not s.safesync_url:
[email protected]307d1792010-05-31 20:03:13780 continue
[email protected]54a07a22010-06-14 19:07:39781 handle = urllib.urlopen(s.safesync_url)
[email protected]307d1792010-05-31 20:03:13782 rev = handle.read().strip()
783 handle.close()
784 if len(rev):
[email protected]54a07a22010-06-14 19:07:39785 self._options.revisions.append('%s@%s' % (s.name, rev))
[email protected]307d1792010-05-31 20:03:13786 if not self._options.revisions:
787 return revision_overrides
788 # --revision will take over safesync_url.
[email protected]54a07a22010-06-14 19:07:39789 solutions_names = [s.name for s in self.dependencies]
[email protected]307d1792010-05-31 20:03:13790 index = 0
791 for revision in self._options.revisions:
792 if not '@' in revision:
793 # Support for --revision 123
794 revision = '%s@%s' % (solutions_names[index], revision)
[email protected]54a07a22010-06-14 19:07:39795 sol, rev = revision.split('@', 1)
[email protected]307d1792010-05-31 20:03:13796 if not sol in solutions_names:
797 #raise gclient_utils.Error('%s is not a valid solution.' % sol)
798 print >> sys.stderr, ('Please fix your script, having invalid '
799 '--revision flags will soon considered an error.')
800 else:
[email protected]918a9ae2010-05-28 15:50:30801 revision_overrides[sol] = rev
[email protected]307d1792010-05-31 20:03:13802 index += 1
[email protected]918a9ae2010-05-28 15:50:30803 return revision_overrides
804
[email protected]fb2b8eb2009-04-23 21:03:42805 def RunOnDeps(self, command, args):
806 """Runs a command on each dependency in a client and its dependencies.
807
[email protected]fb2b8eb2009-04-23 21:03:42808 Args:
809 command: The command to use (e.g., 'status' or 'diff')
810 args: list of str - extra arguments to add to the command line.
[email protected]fb2b8eb2009-04-23 21:03:42811 """
[email protected]54a07a22010-06-14 19:07:39812 if not self.dependencies:
[email protected]73e21142010-07-05 13:32:01813 raise gclient_utils.Error('No solution specified')
[email protected]54a07a22010-06-14 19:07:39814 revision_overrides = self._EnforceRevisions()
[email protected]df2b3152010-07-21 17:35:24815 pm = None
[email protected]d2e92562010-04-27 01:55:18816 if command == 'update' and not self._options.verbose:
[email protected]049bced2010-08-12 13:37:20817 pm = Progress('Syncing projects', 1)
818 work_queue = ExecutionQueue(pm)
819 for s in self.dependencies:
820 work_queue.enqueue(s)
821 work_queue.flush(self._options, revision_overrides, command, args,
822 work_queue)
[email protected]6f363722010-04-27 00:41:09823
[email protected]df2b3152010-07-21 17:35:24824 # Once all the dependencies have been processed, it's now safe to run the
825 # hooks.
826 if not self._options.nohooks:
827 self.RunHooksRecursively(self._options)
[email protected]fb2b8eb2009-04-23 21:03:42828
829 if command == 'update':
[email protected]cdcee802009-06-23 15:30:42830 # Notify the user if there is an orphaned entry in their working copy.
831 # Only delete the directory if there are no changes in it, and
832 # delete_unversioned_trees is set to true.
[email protected]df2b3152010-07-21 17:35:24833 entries = [i.name for i in self.tree(False)]
834 for entry, prev_url in self._ReadEntries().iteritems():
[email protected]c5e9aec2009-08-03 18:25:56835 # Fix path separator on Windows.
836 entry_fixed = entry.replace('/', os.path.sep)
[email protected]75a59272010-06-11 22:34:03837 e_dir = os.path.join(self.root_dir(), entry_fixed)
[email protected]c5e9aec2009-08-03 18:25:56838 # Use entry and not entry_fixed there.
[email protected]0329e672009-05-13 18:41:04839 if entry not in entries and os.path.exists(e_dir):
[email protected]df2b3152010-07-21 17:35:24840 file_list = []
841 scm = gclient_scm.CreateSCM(prev_url, self.root_dir(), entry_fixed)
842 scm.status(self._options, [], file_list)
843 modified_files = file_list != []
[email protected]83017012009-09-28 18:52:12844 if not self._options.delete_unversioned_trees or modified_files:
[email protected]c5e9aec2009-08-03 18:25:56845 # There are modified files in this entry. Keep warning until
846 # removed.
[email protected]d36fba82010-06-28 16:50:40847 print(('\nWARNING: \'%s\' is no longer part of this client. '
848 'It is recommended that you manually remove it.\n') %
[email protected]c5e9aec2009-08-03 18:25:56849 entry_fixed)
[email protected]fb2b8eb2009-04-23 21:03:42850 else:
851 # Delete the entry
[email protected]73e21142010-07-05 13:32:01852 print('\n________ deleting \'%s\' in \'%s\'' % (
853 entry_fixed, self.root_dir()))
[email protected]5f3eee32009-09-17 00:34:30854 gclient_utils.RemoveDirectory(e_dir)
[email protected]fb2b8eb2009-04-23 21:03:42855 # record the current list of entries for next time
[email protected]df2b3152010-07-21 17:35:24856 self._SaveEntries()
[email protected]17cdf762010-05-28 17:30:52857 return 0
[email protected]fb2b8eb2009-04-23 21:03:42858
859 def PrintRevInfo(self):
[email protected]54a07a22010-06-14 19:07:39860 if not self.dependencies:
[email protected]73e21142010-07-05 13:32:01861 raise gclient_utils.Error('No solution specified')
[email protected]df2b3152010-07-21 17:35:24862 # Load all the settings.
[email protected]049bced2010-08-12 13:37:20863 work_queue = ExecutionQueue(None)
864 for s in self.dependencies:
865 work_queue.enqueue(s)
866 work_queue.flush(self._options, {}, None, [], work_queue)
[email protected]fb2b8eb2009-04-23 21:03:42867
[email protected]6da25d02010-08-11 17:32:55868 def GetURLAndRev(dep):
869 """Returns the revision-qualified SCM url for a Dependency."""
870 if dep.parsed_url is None:
[email protected]baa578e2010-07-12 17:36:59871 return None
[email protected]6da25d02010-08-11 17:32:55872 if isinstance(dep.parsed_url, self.FileImpl):
873 original_url = dep.parsed_url.file_location
874 else:
875 original_url = dep.parsed_url
[email protected]5d63eb82010-03-24 23:22:09876 url, _ = gclient_utils.SplitUrlRevision(original_url)
[email protected]6da25d02010-08-11 17:32:55877 scm = gclient_scm.CreateSCM(original_url, self.root_dir(), dep.name)
[email protected]df2b3152010-07-21 17:35:24878 if not os.path.isdir(scm.checkout_path):
879 return None
[email protected]baa578e2010-07-12 17:36:59880 return '%s@%s' % (url, scm.revinfo(self._options, [], None))
[email protected]fb2b8eb2009-04-23 21:03:42881
[email protected]baa578e2010-07-12 17:36:59882 if self._options.snapshot:
[email protected]df2b3152010-07-21 17:35:24883 new_gclient = ''
884 # First level at .gclient
885 for d in self.dependencies:
886 entries = {}
[email protected]6da25d02010-08-11 17:32:55887 def GrabDeps(dep):
[email protected]df2b3152010-07-21 17:35:24888 """Recursively grab dependencies."""
[email protected]6da25d02010-08-11 17:32:55889 for d in dep.dependencies:
890 entries[d.name] = GetURLAndRev(d)
891 GrabDeps(d)
[email protected]df2b3152010-07-21 17:35:24892 GrabDeps(d)
893 custom_deps = []
894 for k in sorted(entries.keys()):
895 if entries[k]:
896 # Quotes aren't escaped...
897 custom_deps.append(' \"%s\": \'%s\',\n' % (k, entries[k]))
898 else:
899 custom_deps.append(' \"%s\": None,\n' % k)
900 new_gclient += self.DEFAULT_SNAPSHOT_SOLUTION_TEXT % {
901 'solution_name': d.name,
902 'solution_url': d.url,
903 'safesync_url' : d.safesync_url or '',
904 'solution_deps': ''.join(custom_deps),
905 }
906 # Print the snapshot configuration file
907 print(self.DEFAULT_SNAPSHOT_FILE_TEXT % {'solution_list': new_gclient})
[email protected]de8f3522010-03-11 23:47:44908 else:
[email protected]b1e315f2010-08-11 18:44:50909 entries = {}
910 for d in self.tree(False):
911 if self._options.actual:
912 entries[d.name] = GetURLAndRev(d)
913 else:
914 entries[d.name] = d.parsed_url
915 keys = sorted(entries.keys())
916 for x in keys:
917 line = '%s: %s' % (x, entries[x])
918 if x is not keys[-1]:
[email protected]df2b3152010-07-21 17:35:24919 line += ';'
920 print line
[email protected]dde32ee2010-08-10 17:44:05921 logging.info(str(self))
[email protected]fb2b8eb2009-04-23 21:03:42922
[email protected]f50907b2010-08-12 17:05:48923 def ParseDepsFile(self):
[email protected]d36fba82010-06-28 16:50:40924 """No DEPS to parse for a .gclient file."""
[email protected]049bced2010-08-12 13:37:20925 raise gclient_utils.Error('Internal error')
[email protected]d36fba82010-06-28 16:50:40926
[email protected]75a59272010-06-11 22:34:03927 def root_dir(self):
[email protected]d36fba82010-06-28 16:50:40928 """Root directory of gclient checkout."""
[email protected]75a59272010-06-11 22:34:03929 return self._root_dir
930
[email protected]271375b2010-06-23 19:17:38931 def enforced_os(self):
[email protected]d36fba82010-06-28 16:50:40932 """What deps_os entries that are to be parsed."""
[email protected]271375b2010-06-23 19:17:38933 return self._enforced_os
934
[email protected]d36fba82010-06-28 16:50:40935 def recursion_limit(self):
936 """How recursive can each dependencies in DEPS file can load DEPS file."""
937 return self._recursion_limit
938
[email protected]0d812442010-08-10 12:41:08939 def tree(self, include_all):
[email protected]d36fba82010-06-28 16:50:40940 """Returns a flat list of all the dependencies."""
[email protected]0d812442010-08-10 12:41:08941 return self.subtree(include_all)
[email protected]d36fba82010-06-28 16:50:40942
[email protected]fb2b8eb2009-04-23 21:03:42943
[email protected]5ca27692010-05-26 19:32:41944#### gclient commands.
[email protected]fb2b8eb2009-04-23 21:03:42945
946
[email protected]5ca27692010-05-26 19:32:41947def CMDcleanup(parser, args):
948 """Cleans up all working copies.
[email protected]ddff62d2010-05-17 21:02:36949
[email protected]5ca27692010-05-26 19:32:41950Mostly svn-specific. Simply runs 'svn cleanup' for each module.
[email protected]79692d62010-05-14 18:57:13951"""
[email protected]0b6a0842010-06-15 14:34:19952 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
953 help='override deps for the specified (comma-separated) '
954 'platform(s); \'all\' will process all deps_os '
955 'references')
[email protected]5ca27692010-05-26 19:32:41956 (options, args) = parser.parse_args(args)
[email protected]2806acc2009-05-15 12:33:34957 client = GClient.LoadCurrentConfig(options)
[email protected]fb2b8eb2009-04-23 21:03:42958 if not client:
[email protected]0b6a0842010-06-15 14:34:19959 raise gclient_utils.Error('client not configured; see \'gclient config\'')
[email protected]fb2b8eb2009-04-23 21:03:42960 if options.verbose:
961 # Print out the .gclient file. This is longer than if we just printed the
962 # client dict, but more legible, and it might contain helpful comments.
[email protected]116704f2010-06-11 17:34:38963 print(client.config_content)
[email protected]fb2b8eb2009-04-23 21:03:42964 return client.RunOnDeps('cleanup', args)
965
966
[email protected]4b90e3a2010-07-01 20:28:26967@attr('usage', '[command] [args ...]')
968def CMDrecurse(parser, args):
969 """Operates on all the entries.
970
971 Runs a shell command on all entries.
972 """
973 # Stop parsing at the first non-arg so that these go through to the command
974 parser.disable_interspersed_args()
975 parser.add_option('-s', '--scm', action='append', default=[],
976 help='choose scm types to operate upon')
977 options, args = parser.parse_args(args)
978 root, entries = gclient_utils.GetGClientRootAndEntries()
979 scm_set = set()
980 for scm in options.scm:
981 scm_set.update(scm.split(','))
982
983 # Pass in the SCM type as an env variable
984 env = os.environ.copy()
985
986 for path, url in entries.iteritems():
987 scm = gclient_scm.GetScmName(url)
988 if scm_set and scm not in scm_set:
989 continue
990 dir = os.path.normpath(os.path.join(root, path))
991 env['GCLIENT_SCM'] = scm
992 env['GCLIENT_URL'] = url
993 subprocess.Popen(args, cwd=dir, env=env).communicate()
994
995
[email protected]5ca27692010-05-26 19:32:41996@attr('usage', '[url] [safesync url]')
997def CMDconfig(parser, args):
[email protected]ddff62d2010-05-17 21:02:36998 """Create a .gclient file in the current directory.
999
[email protected]5ca27692010-05-26 19:32:411000This specifies the configuration for further commands. After update/sync,
[email protected]79692d62010-05-14 18:57:131001top-level DEPS files in each module are read to determine dependent
[email protected]5ca27692010-05-26 19:32:411002modules to operate on as well. If optional [url] parameter is
[email protected]79692d62010-05-14 18:57:131003provided, then configuration is read from a specified Subversion server
[email protected]5ca27692010-05-26 19:32:411004URL.
[email protected]79692d62010-05-14 18:57:131005"""
[email protected]0b6a0842010-06-15 14:34:191006 parser.add_option('--spec',
1007 help='create a gclient file containing the provided '
1008 'string. Due to Cygwin/Python brokenness, it '
1009 'probably can\'t contain any newlines.')
1010 parser.add_option('--name',
1011 help='overrides the default name for the solution')
[email protected]5ca27692010-05-26 19:32:411012 (options, args) = parser.parse_args(args)
[email protected]5fc2a332010-05-26 19:37:151013 if ((options.spec and args) or len(args) > 2 or
1014 (not options.spec and not args)):
1015 parser.error('Inconsistent arguments. Use either --spec or one or 2 args')
1016
[email protected]0329e672009-05-13 18:41:041017 if os.path.exists(options.config_filename):
[email protected]0b6a0842010-06-15 14:34:191018 raise gclient_utils.Error('%s file already exists in the current directory'
[email protected]e3608df2009-11-10 20:22:571019 % options.config_filename)
[email protected]2806acc2009-05-15 12:33:341020 client = GClient('.', options)
[email protected]fb2b8eb2009-04-23 21:03:421021 if options.spec:
1022 client.SetConfig(options.spec)
1023 else:
[email protected]1ab7ffc2009-06-03 17:21:371024 base_url = args[0].rstrip('/')
[email protected]8cf7a392010-04-07 17:20:261025 if not options.name:
[email protected]0b6a0842010-06-15 14:34:191026 name = base_url.split('/')[-1]
[email protected]8cf7a392010-04-07 17:20:261027 else:
1028 # specify an alternate relpath for the given URL.
1029 name = options.name
[email protected]0b6a0842010-06-15 14:34:191030 safesync_url = ''
[email protected]fb2b8eb2009-04-23 21:03:421031 if len(args) > 1:
1032 safesync_url = args[1]
1033 client.SetDefaultConfig(name, base_url, safesync_url)
1034 client.SaveConfig()
[email protected]79692d62010-05-14 18:57:131035 return 0
[email protected]fb2b8eb2009-04-23 21:03:421036
1037
[email protected]5ca27692010-05-26 19:32:411038def CMDexport(parser, args):
[email protected]ddff62d2010-05-17 21:02:361039 """Wrapper for svn export for all managed directories."""
[email protected]0b6a0842010-06-15 14:34:191040 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')
[email protected]5ca27692010-05-26 19:32:411044 (options, args) = parser.parse_args(args)
[email protected]644aa0c2009-07-17 20:20:411045 if len(args) != 1:
[email protected]0b6a0842010-06-15 14:34:191046 raise gclient_utils.Error('Need directory name')
[email protected]644aa0c2009-07-17 20:20:411047 client = GClient.LoadCurrentConfig(options)
1048
1049 if not client:
[email protected]0b6a0842010-06-15 14:34:191050 raise gclient_utils.Error('client not configured; see \'gclient config\'')
[email protected]644aa0c2009-07-17 20:20:411051
1052 if options.verbose:
1053 # Print out the .gclient file. This is longer than if we just printed the
1054 # client dict, but more legible, and it might contain helpful comments.
[email protected]116704f2010-06-11 17:34:381055 print(client.config_content)
[email protected]644aa0c2009-07-17 20:20:411056 return client.RunOnDeps('export', args)
1057
[email protected]fb2b8eb2009-04-23 21:03:421058
[email protected]5ca27692010-05-26 19:32:411059@attr('epilog', """Example:
1060 gclient pack > patch.txt
1061 generate simple patch for configured client and dependences
1062""")
1063def CMDpack(parser, args):
[email protected]79692d62010-05-14 18:57:131064 """Generate a patch which can be applied at the root of the tree.
[email protected]ddff62d2010-05-17 21:02:361065
[email protected]5ca27692010-05-26 19:32:411066Internally, runs 'svn diff'/'git diff' on each checked out module and
[email protected]79692d62010-05-14 18:57:131067dependencies, and performs minimal postprocessing of the output. The
1068resulting patch is printed to stdout and can be applied to a freshly
[email protected]5ca27692010-05-26 19:32:411069checked out tree via 'patch -p0 < patchfile'.
[email protected]79692d62010-05-14 18:57:131070"""
[email protected]0b6a0842010-06-15 14:34:191071 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1072 help='override deps for the specified (comma-separated) '
1073 'platform(s); \'all\' will process all deps_os '
1074 'references')
[email protected]5ca27692010-05-26 19:32:411075 (options, args) = parser.parse_args(args)
[email protected]ab318592009-09-04 00:54:551076 client = GClient.LoadCurrentConfig(options)
1077 if not client:
[email protected]0b6a0842010-06-15 14:34:191078 raise gclient_utils.Error('client not configured; see \'gclient config\'')
[email protected]ab318592009-09-04 00:54:551079 if options.verbose:
1080 # Print out the .gclient file. This is longer than if we just printed the
1081 # client dict, but more legible, and it might contain helpful comments.
[email protected]116704f2010-06-11 17:34:381082 print(client.config_content)
[email protected]ab318592009-09-04 00:54:551083 return client.RunOnDeps('pack', args)
1084
1085
[email protected]5ca27692010-05-26 19:32:411086def CMDstatus(parser, args):
1087 """Show modification status for every dependencies."""
[email protected]0b6a0842010-06-15 14:34:191088 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1089 help='override deps for the specified (comma-separated) '
1090 'platform(s); \'all\' will process all deps_os '
1091 'references')
[email protected]5ca27692010-05-26 19:32:411092 (options, args) = parser.parse_args(args)
[email protected]2806acc2009-05-15 12:33:341093 client = GClient.LoadCurrentConfig(options)
[email protected]fb2b8eb2009-04-23 21:03:421094 if not client:
[email protected]0b6a0842010-06-15 14:34:191095 raise gclient_utils.Error('client not configured; see \'gclient config\'')
[email protected]fb2b8eb2009-04-23 21:03:421096 if options.verbose:
1097 # Print out the .gclient file. This is longer than if we just printed the
1098 # client dict, but more legible, and it might contain helpful comments.
[email protected]116704f2010-06-11 17:34:381099 print(client.config_content)
[email protected]fb2b8eb2009-04-23 21:03:421100 return client.RunOnDeps('status', args)
1101
1102
[email protected]5ca27692010-05-26 19:32:411103@attr('epilog', """Examples:
[email protected]79692d62010-05-14 18:57:131104 gclient sync
1105 update files from SCM according to current configuration,
1106 *for modules which have changed since last update or sync*
1107 gclient sync --force
1108 update files from SCM according to current configuration, for
1109 all modules (useful for recovering files deleted from local copy)
1110 gclient sync --revision src@31000
1111 update src directory to r31000
[email protected]5ca27692010-05-26 19:32:411112""")
1113def CMDsync(parser, args):
1114 """Checkout/update all modules."""
[email protected]0b6a0842010-06-15 14:34:191115 parser.add_option('-f', '--force', action='store_true',
1116 help='force update even for unchanged modules')
1117 parser.add_option('-n', '--nohooks', action='store_true',
1118 help='don\'t run hooks after the update is complete')
1119 parser.add_option('-r', '--revision', action='append',
1120 dest='revisions', metavar='REV', default=[],
1121 help='Enforces revision/hash for the solutions with the '
1122 'format src@rev. The src@ part is optional and can be '
1123 'skipped. -r can be used multiple times when .gclient '
1124 'has multiple solutions configured and will work even '
1125 'if the src@ part is skipped.')
1126 parser.add_option('-H', '--head', action='store_true',
1127 help='skips any safesync_urls specified in '
1128 'configured solutions and sync to head instead')
1129 parser.add_option('-D', '--delete_unversioned_trees', action='store_true',
1130 help='delete any unexpected unversioned trees '
1131 'that are in the checkout')
1132 parser.add_option('-R', '--reset', action='store_true',
1133 help='resets any local changes before updating (git only)')
1134 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1135 help='override deps for the specified (comma-separated) '
1136 'platform(s); \'all\' will process all deps_os '
1137 'references')
1138 parser.add_option('-m', '--manually_grab_svn_rev', action='store_true',
1139 help='Skip svn up whenever possible by requesting '
1140 'actual HEAD revision from the repository')
[email protected]5ca27692010-05-26 19:32:411141 (options, args) = parser.parse_args(args)
[email protected]2806acc2009-05-15 12:33:341142 client = GClient.LoadCurrentConfig(options)
[email protected]fb2b8eb2009-04-23 21:03:421143
1144 if not client:
[email protected]0b6a0842010-06-15 14:34:191145 raise gclient_utils.Error('client not configured; see \'gclient config\'')
[email protected]fb2b8eb2009-04-23 21:03:421146
[email protected]307d1792010-05-31 20:03:131147 if options.revisions and options.head:
1148 # TODO(maruel): Make it a parser.error if it doesn't break any builder.
[email protected]0b6a0842010-06-15 14:34:191149 print('Warning: you cannot use both --head and --revision')
[email protected]fb2b8eb2009-04-23 21:03:421150
1151 if options.verbose:
1152 # Print out the .gclient file. This is longer than if we just printed the
1153 # client dict, but more legible, and it might contain helpful comments.
[email protected]116704f2010-06-11 17:34:381154 print(client.config_content)
[email protected]fb2b8eb2009-04-23 21:03:421155 return client.RunOnDeps('update', args)
1156
1157
[email protected]5ca27692010-05-26 19:32:411158def CMDupdate(parser, args):
[email protected]ddff62d2010-05-17 21:02:361159 """Alias for the sync command. Deprecated."""
[email protected]5ca27692010-05-26 19:32:411160 return CMDsync(parser, args)
[email protected]fb2b8eb2009-04-23 21:03:421161
[email protected]5ca27692010-05-26 19:32:411162def CMDdiff(parser, args):
1163 """Displays local diff for every dependencies."""
[email protected]0b6a0842010-06-15 14:34:191164 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1165 help='override deps for the specified (comma-separated) '
1166 'platform(s); \'all\' will process all deps_os '
1167 'references')
[email protected]5ca27692010-05-26 19:32:411168 (options, args) = parser.parse_args(args)
[email protected]2806acc2009-05-15 12:33:341169 client = GClient.LoadCurrentConfig(options)
[email protected]fb2b8eb2009-04-23 21:03:421170 if not client:
[email protected]0b6a0842010-06-15 14:34:191171 raise gclient_utils.Error('client not configured; see \'gclient config\'')
[email protected]fb2b8eb2009-04-23 21:03:421172 if options.verbose:
1173 # Print out the .gclient file. This is longer than if we just printed the
1174 # client dict, but more legible, and it might contain helpful comments.
[email protected]116704f2010-06-11 17:34:381175 print(client.config_content)
[email protected]fb2b8eb2009-04-23 21:03:421176 return client.RunOnDeps('diff', args)
1177
1178
[email protected]5ca27692010-05-26 19:32:411179def CMDrevert(parser, args):
1180 """Revert all modifications in every dependencies."""
[email protected]0b6a0842010-06-15 14:34:191181 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1182 help='override deps for the specified (comma-separated) '
1183 'platform(s); \'all\' will process all deps_os '
1184 'references')
1185 parser.add_option('-n', '--nohooks', action='store_true',
1186 help='don\'t run hooks after the revert is complete')
[email protected]5ca27692010-05-26 19:32:411187 (options, args) = parser.parse_args(args)
1188 # --force is implied.
1189 options.force = True
[email protected]2806acc2009-05-15 12:33:341190 client = GClient.LoadCurrentConfig(options)
[email protected]fb2b8eb2009-04-23 21:03:421191 if not client:
[email protected]0b6a0842010-06-15 14:34:191192 raise gclient_utils.Error('client not configured; see \'gclient config\'')
[email protected]fb2b8eb2009-04-23 21:03:421193 return client.RunOnDeps('revert', args)
1194
1195
[email protected]5ca27692010-05-26 19:32:411196def CMDrunhooks(parser, args):
1197 """Runs hooks for files that have been modified in the local working copy."""
[email protected]0b6a0842010-06-15 14:34:191198 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1199 help='override deps for the specified (comma-separated) '
1200 'platform(s); \'all\' will process all deps_os '
1201 'references')
1202 parser.add_option('-f', '--force', action='store_true', default=True,
1203 help='Deprecated. No effect.')
[email protected]5ca27692010-05-26 19:32:411204 (options, args) = parser.parse_args(args)
[email protected]2806acc2009-05-15 12:33:341205 client = GClient.LoadCurrentConfig(options)
[email protected]fb2b8eb2009-04-23 21:03:421206 if not client:
[email protected]0b6a0842010-06-15 14:34:191207 raise gclient_utils.Error('client not configured; see \'gclient config\'')
[email protected]fb2b8eb2009-04-23 21:03:421208 if options.verbose:
1209 # Print out the .gclient file. This is longer than if we just printed the
1210 # client dict, but more legible, and it might contain helpful comments.
[email protected]116704f2010-06-11 17:34:381211 print(client.config_content)
[email protected]5df6a462009-08-28 18:52:261212 options.force = True
[email protected]5ca27692010-05-26 19:32:411213 options.nohooks = False
[email protected]fb2b8eb2009-04-23 21:03:421214 return client.RunOnDeps('runhooks', args)
1215
1216
[email protected]5ca27692010-05-26 19:32:411217def CMDrevinfo(parser, args):
[email protected]9eda4112010-06-11 18:56:101218 """Output revision info mapping for the client and its dependencies.
1219
[email protected]0b6a0842010-06-15 14:34:191220 This allows the capture of an overall 'revision' for the source tree that
[email protected]9eda4112010-06-11 18:56:101221 can be used to reproduce the same tree in the future. It is only useful for
[email protected]0b6a0842010-06-15 14:34:191222 'unpinned dependencies', i.e. DEPS/deps references without a svn revision
1223 number or a git hash. A git branch name isn't 'pinned' since the actual
[email protected]9eda4112010-06-11 18:56:101224 commit can change.
1225 """
1226 parser.add_option('--deps', dest='deps_os', metavar='OS_LIST',
1227 help='override deps for the specified (comma-separated) '
1228 'platform(s); \'all\' will process all deps_os '
1229 'references')
[email protected]b1e315f2010-08-11 18:44:501230 parser.add_option('-a', '--actual', action='store_true',
1231 help='gets the actual checked out revisions instead of the '
1232 'ones specified in the DEPS and .gclient files')
[email protected]9eda4112010-06-11 18:56:101233 parser.add_option('-s', '--snapshot', action='store_true',
1234 help='creates a snapshot .gclient file of the current '
[email protected]b1e315f2010-08-11 18:44:501235 'version of all repositories to reproduce the tree, '
1236 'implies -a')
[email protected]5ca27692010-05-26 19:32:411237 (options, args) = parser.parse_args(args)
[email protected]2806acc2009-05-15 12:33:341238 client = GClient.LoadCurrentConfig(options)
[email protected]fb2b8eb2009-04-23 21:03:421239 if not client:
[email protected]0b6a0842010-06-15 14:34:191240 raise gclient_utils.Error('client not configured; see \'gclient config\'')
[email protected]fb2b8eb2009-04-23 21:03:421241 client.PrintRevInfo()
[email protected]79692d62010-05-14 18:57:131242 return 0
[email protected]fb2b8eb2009-04-23 21:03:421243
1244
[email protected]5ca27692010-05-26 19:32:411245def Command(name):
1246 return getattr(sys.modules[__name__], 'CMD' + name, None)
1247
1248
1249def CMDhelp(parser, args):
1250 """Prints list of commands or help for a specific command."""
[email protected]6e29d572010-06-04 17:32:201251 (_, args) = parser.parse_args(args)
[email protected]ddff62d2010-05-17 21:02:361252 if len(args) == 1:
[email protected]5ca27692010-05-26 19:32:411253 return Main(args + ['--help'])
[email protected]ddff62d2010-05-17 21:02:361254 parser.print_help()
1255 return 0
1256
1257
[email protected]5ca27692010-05-26 19:32:411258def GenUsage(parser, command):
1259 """Modify an OptParse object with the function's documentation."""
1260 obj = Command(command)
1261 if command == 'help':
1262 command = '<command>'
1263 # OptParser.description prefer nicely non-formatted strings.
1264 parser.description = re.sub('[\r\n ]{2,}', ' ', obj.__doc__)
1265 usage = getattr(obj, 'usage', '')
1266 parser.set_usage('%%prog %s [options] %s' % (command, usage))
1267 parser.epilog = getattr(obj, 'epilog', None)
[email protected]fb2b8eb2009-04-23 21:03:421268
1269
1270def Main(argv):
[email protected]5ca27692010-05-26 19:32:411271 """Doesn't parse the arguments here, just find the right subcommand to
1272 execute."""
[email protected]6e29d572010-06-04 17:32:201273 try:
1274 # Do it late so all commands are listed.
1275 CMDhelp.usage = ('\n\nCommands are:\n' + '\n'.join([
1276 ' %-10s %s' % (fn[3:], Command(fn[3:]).__doc__.split('\n')[0].strip())
1277 for fn in dir(sys.modules[__name__]) if fn.startswith('CMD')]))
1278 parser = optparse.OptionParser(version='%prog ' + __version__)
[email protected]f0fc9912010-06-11 17:57:331279 parser.add_option('-v', '--verbose', action='count', default=0,
1280 help='Produces additional output for diagnostics. Can be '
1281 'used up to three times for more logging info.')
1282 parser.add_option('--gclientfile', dest='config_filename',
1283 default=os.environ.get('GCLIENT_FILE', '.gclient'),
1284 help='Specify an alternate %default file')
[email protected]6e29d572010-06-04 17:32:201285 # Integrate standard options processing.
1286 old_parser = parser.parse_args
1287 def Parse(args):
1288 (options, args) = old_parser(args)
[email protected]f0fc9912010-06-11 17:57:331289 level = None
[email protected]6e29d572010-06-04 17:32:201290 if options.verbose == 2:
[email protected]f0fc9912010-06-11 17:57:331291 level = logging.INFO
[email protected]6e29d572010-06-04 17:32:201292 elif options.verbose > 2:
[email protected]f0fc9912010-06-11 17:57:331293 level = logging.DEBUG
1294 logging.basicConfig(level=level,
1295 format='%(module)s(%(lineno)d) %(funcName)s:%(message)s')
1296 options.entries_filename = options.config_filename + '_entries'
[email protected]e3216c62010-07-08 03:31:431297
1298 # These hacks need to die.
[email protected]6e29d572010-06-04 17:32:201299 if not hasattr(options, 'revisions'):
1300 # GClient.RunOnDeps expects it even if not applicable.
1301 options.revisions = []
1302 if not hasattr(options, 'head'):
1303 options.head = None
[email protected]f0fc9912010-06-11 17:57:331304 if not hasattr(options, 'nohooks'):
1305 options.nohooks = True
1306 if not hasattr(options, 'deps_os'):
1307 options.deps_os = None
[email protected]e3216c62010-07-08 03:31:431308 if not hasattr(options, 'manually_grab_svn_rev'):
1309 options.manually_grab_svn_rev = None
1310 if not hasattr(options, 'force'):
1311 options.force = None
[email protected]6e29d572010-06-04 17:32:201312 return (options, args)
1313 parser.parse_args = Parse
1314 # We don't want wordwrapping in epilog (usually examples)
1315 parser.format_epilog = lambda _: parser.epilog or ''
1316 if argv:
1317 command = Command(argv[0])
1318 if command:
[email protected]f0fc9912010-06-11 17:57:331319 # 'fix' the usage and the description now that we know the subcommand.
[email protected]6e29d572010-06-04 17:32:201320 GenUsage(parser, argv[0])
1321 return command(parser, argv[1:])
1322 # Not a known command. Default to help.
1323 GenUsage(parser, 'help')
1324 return CMDhelp(parser, argv)
1325 except gclient_utils.Error, e:
[email protected]f0fc9912010-06-11 17:57:331326 print >> sys.stderr, 'Error: %s' % str(e)
[email protected]6e29d572010-06-04 17:32:201327 return 1
[email protected]fb2b8eb2009-04-23 21:03:421328
1329
[email protected]f0fc9912010-06-11 17:57:331330if '__main__' == __name__:
[email protected]6e29d572010-06-04 17:32:201331 sys.exit(Main(sys.argv[1:]))
[email protected]fb2b8eb2009-04-23 21:03:421332
1333# vim: ts=2:sw=2:tw=80:et: