OLD | NEW |
1 #!/usr/bin/python | 1 #!/usr/bin/python |
2 # Copyright (c) 2010 The Chromium Authors. All rights reserved. | 2 # 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 | 3 # Use of this source code is governed by a BSD-style license that can be |
4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
5 | 5 |
6 """Meta checkout manager supporting both Subversion and GIT. | 6 """Meta checkout manager supporting both Subversion and GIT. |
7 | 7 |
8 Files | 8 Files |
9 .gclient : Current client configuration, written by 'config' command. | 9 .gclient : Current client configuration, written by 'config' command. |
10 Format is a Python script defining 'solutions', a list whose | 10 Format is a Python script defining 'solutions', a list whose |
(...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
42 it will be removed from the list and the list will be extended | 42 it will be removed from the list and the list will be extended |
43 by the list of matching files. | 43 by the list of matching files. |
44 | 44 |
45 Example: | 45 Example: |
46 hooks = [ | 46 hooks = [ |
47 { "pattern": "\\.(gif|jpe?g|pr0n|png)$", | 47 { "pattern": "\\.(gif|jpe?g|pr0n|png)$", |
48 "action": ["python", "image_indexer.py", "--all"]}, | 48 "action": ["python", "image_indexer.py", "--all"]}, |
49 ] | 49 ] |
50 """ | 50 """ |
51 | 51 |
52 __version__ = "0.5" | 52 __version__ = "0.5.1" |
53 | 53 |
54 import logging | 54 import logging |
55 import optparse | 55 import optparse |
56 import os | 56 import os |
57 import posixpath | 57 import posixpath |
58 import pprint | 58 import pprint |
59 import re | 59 import re |
60 import subprocess | 60 import subprocess |
61 import sys | 61 import sys |
| 62 import threading |
62 import urlparse | 63 import urlparse |
63 import urllib | 64 import urllib |
64 | 65 |
65 import breakpad | 66 import breakpad |
66 | 67 |
67 import gclient_scm | 68 import gclient_scm |
68 import gclient_utils | 69 import gclient_utils |
69 from third_party.repo.progress import Progress | 70 from third_party.repo.progress import Progress |
70 | 71 |
71 | 72 |
72 def attr(attr, data): | 73 def attr(attr, data): |
73 """Sets an attribute on a function.""" | 74 """Sets an attribute on a function.""" |
74 def hook(fn): | 75 def hook(fn): |
75 setattr(fn, attr, data) | 76 setattr(fn, attr, data) |
76 return fn | 77 return fn |
77 return hook | 78 return hook |
78 | 79 |
79 | 80 |
80 ## GClient implementation. | 81 ## GClient implementation. |
81 | 82 |
| 83 class WorkItem(object): |
| 84 """One work item.""" |
| 85 requirements = [] |
| 86 name = None |
| 87 |
| 88 def run(self): |
| 89 pass |
| 90 |
| 91 |
| 92 class 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 |
82 | 187 |
83 class GClientKeywords(object): | 188 class GClientKeywords(object): |
84 class FromImpl(object): | 189 class FromImpl(object): |
85 """Used to implement the From() syntax.""" | 190 """Used to implement the From() syntax.""" |
86 | 191 |
87 def __init__(self, module_name, sub_target_name=None): | 192 def __init__(self, module_name, sub_target_name=None): |
88 """module_name is the dep module we want to include from. It can also be | 193 """module_name is the dep module we want to include from. It can also be |
89 the name of a subdirectory to include from. | 194 the name of a subdirectory to include from. |
90 | 195 |
91 sub_target_name is an optional parameter if the module name in the other | 196 sub_target_name is an optional parameter if the module name in the other |
(...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
127 | 232 |
128 def Lookup(self, var_name): | 233 def Lookup(self, var_name): |
129 """Implements the Var syntax.""" | 234 """Implements the Var syntax.""" |
130 if var_name in self._custom_vars: | 235 if var_name in self._custom_vars: |
131 return self._custom_vars[var_name] | 236 return self._custom_vars[var_name] |
132 elif var_name in self._local_scope.get("vars", {}): | 237 elif var_name in self._local_scope.get("vars", {}): |
133 return self._local_scope["vars"][var_name] | 238 return self._local_scope["vars"][var_name] |
134 raise gclient_utils.Error("Var is not defined: %s" % var_name) | 239 raise gclient_utils.Error("Var is not defined: %s" % var_name) |
135 | 240 |
136 | 241 |
137 class Dependency(GClientKeywords): | 242 class Dependency(GClientKeywords, WorkItem): |
138 """Object that represents a dependency checkout.""" | 243 """Object that represents a dependency checkout.""" |
139 DEPS_FILE = 'DEPS' | 244 DEPS_FILE = 'DEPS' |
140 | 245 |
141 def __init__(self, parent, name, url, safesync_url, custom_deps, | 246 def __init__(self, parent, name, url, safesync_url, custom_deps, |
142 custom_vars, deps_file): | 247 custom_vars, deps_file): |
143 GClientKeywords.__init__(self) | 248 GClientKeywords.__init__(self) |
144 self.parent = parent | 249 self.parent = parent |
145 self.name = name | 250 self.name = name |
146 self.url = url | 251 self.url = url |
147 self.parsed_url = None | 252 self.parsed_url = None |
(...skipping 165 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
313 deps = rel_deps | 418 deps = rel_deps |
314 | 419 |
315 # Convert the deps into real Dependency. | 420 # Convert the deps into real Dependency. |
316 for name, url in deps.iteritems(): | 421 for name, url in deps.iteritems(): |
317 if name in [s.name for s in self.dependencies]: | 422 if name in [s.name for s in self.dependencies]: |
318 raise gclient_utils.Error( | 423 raise gclient_utils.Error( |
319 'The same name "%s" appears multiple times in the deps section' % | 424 'The same name "%s" appears multiple times in the deps section' % |
320 name) | 425 name) |
321 self.dependencies.append(Dependency(self, name, url, None, None, None, | 426 self.dependencies.append(Dependency(self, name, url, None, None, None, |
322 None)) | 427 None)) |
323 # Sorting by name would in theory make the whole thing coherent, since | |
324 # subdirectories will be sorted after the parent directory, but that doens't | |
325 # work with From() that fetch from a dependency with a name being sorted | |
326 # later. But if this would be removed right now, many projects wouldn't be | |
327 # able to sync anymore. | |
328 self.dependencies.sort(key=lambda x: x.name) | |
329 logging.debug('Loaded: %s' % str(self)) | 428 logging.debug('Loaded: %s' % str(self)) |
330 | 429 |
331 def RunCommandRecursively(self, options, revision_overrides, | 430 def run(self, options, revision_overrides, command, args, work_queue): |
332 command, args, pm): | |
333 """Runs 'command' before parsing the DEPS in case it's a initial checkout | 431 """Runs 'command' before parsing the DEPS in case it's a initial checkout |
334 or a revert.""" | 432 or a revert.""" |
335 assert self._file_list == [] | 433 assert self._file_list == [] |
336 # When running runhooks, there's no need to consult the SCM. | 434 # When running runhooks, there's no need to consult the SCM. |
337 # All known hooks are expected to run unconditionally regardless of working | 435 # All known hooks are expected to run unconditionally regardless of working |
338 # copy state, so skip the SCM status check. | 436 # copy state, so skip the SCM status check. |
339 run_scm = command not in ('runhooks', None) | 437 run_scm = command not in ('runhooks', None) |
340 self.parsed_url = self.LateOverride(self.url) | 438 self.parsed_url = self.LateOverride(self.url) |
341 if run_scm and self.parsed_url: | 439 if run_scm and self.parsed_url: |
342 if isinstance(self.parsed_url, self.FileImpl): | 440 if isinstance(self.parsed_url, self.FileImpl): |
343 # Special support for single-file checkout. | 441 # Special support for single-file checkout. |
344 if not command in (None, 'cleanup', 'diff', 'pack', 'status'): | 442 if not command in (None, 'cleanup', 'diff', 'pack', 'status'): |
345 options.revision = self.parsed_url.GetRevision() | 443 options.revision = self.parsed_url.GetRevision() |
346 scm = gclient_scm.SVNWrapper(self.parsed_url.GetPath(), | 444 scm = gclient_scm.SVNWrapper(self.parsed_url.GetPath(), |
347 self.root_dir(), | 445 self.root_dir(), |
348 self.name) | 446 self.name) |
349 scm.RunCommand('updatesingle', options, | 447 scm.RunCommand('updatesingle', options, |
350 args + [self.parsed_url.GetFilename()], | 448 args + [self.parsed_url.GetFilename()], |
351 self._file_list) | 449 self._file_list) |
352 else: | 450 else: |
353 options.revision = revision_overrides.get(self.name) | 451 options.revision = revision_overrides.get(self.name) |
354 scm = gclient_scm.CreateSCM(self.parsed_url, self.root_dir(), self.name) | 452 scm = gclient_scm.CreateSCM(self.parsed_url, self.root_dir(), self.name) |
355 scm.RunCommand(command, options, args, self._file_list) | 453 scm.RunCommand(command, options, args, self._file_list) |
356 self._file_list = [os.path.join(self.name, f.strip()) | 454 self._file_list = [os.path.join(self.name, f.strip()) |
357 for f in self._file_list] | 455 for f in self._file_list] |
358 options.revision = None | 456 options.revision = None |
359 self.processed = True | 457 self.processed = True |
360 if pm: | |
361 # The + 1 comes from the fact that .gclient is considered a step in | |
362 # itself, .i.e. this code is called one time for the .gclient. This is not | |
363 # conceptually correct but it simplifies code. | |
364 pm._total = len(self.tree(False)) + 1 | |
365 pm.update() | |
366 if self.recursion_limit(): | 458 if self.recursion_limit(): |
367 # Then we can parse the DEPS file. | 459 # Then we can parse the DEPS file. |
368 self.ParseDepsFile(True) | 460 self.ParseDepsFile(True) |
369 if pm: | |
370 pm._total = len(self.tree(False)) + 1 | |
371 pm.update(0) | |
372 # Adjust the implicit dependency requirement; e.g. if a DEPS file contains | 461 # Adjust the implicit dependency requirement; e.g. if a DEPS file contains |
373 # both src/foo and src/foo/bar, src/foo/bar is implicitly dependent of | 462 # both src/foo and src/foo/bar, src/foo/bar is implicitly dependent of |
374 # src/foo. Yes, it's O(n^2)... | 463 # src/foo. Yes, it's O(n^2)... It's important to do that before |
| 464 # enqueueing them. |
375 for s in self.dependencies: | 465 for s in self.dependencies: |
376 for s2 in self.dependencies: | 466 for s2 in self.dependencies: |
377 if s is s2: | 467 if s is s2: |
378 continue | 468 continue |
379 if s.name.startswith(posixpath.join(s2.name, '')): | 469 if s.name.startswith(posixpath.join(s2.name, '')): |
380 s.requirements.append(s2.name) | 470 s.requirements.append(s2.name) |
381 | 471 |
382 # Parse the dependencies of this dependency. | 472 # Parse the dependencies of this dependency. |
383 for s in self.dependencies: | 473 for s in self.dependencies: |
384 # TODO(maruel): All these can run concurrently! No need for threads, | 474 work_queue.enqueue(s) |
385 # just buffer stdout&stderr on pipes and flush as they complete. | |
386 # Watch out for stdin. | |
387 s.RunCommandRecursively(options, revision_overrides, command, args, pm) | |
388 | 475 |
389 def RunHooksRecursively(self, options): | 476 def RunHooksRecursively(self, options): |
390 """Evaluates all hooks, running actions as needed. RunCommandRecursively() | 477 """Evaluates all hooks, running actions as needed. run() |
391 must have been called before to load the DEPS.""" | 478 must have been called before to load the DEPS.""" |
392 # If "--force" was specified, run all hooks regardless of what files have | 479 # If "--force" was specified, run all hooks regardless of what files have |
393 # changed. | 480 # changed. |
394 if self.deps_hooks and self.direct_reference: | 481 if self.deps_hooks and self.direct_reference: |
395 # TODO(maruel): If the user is using git or git-svn, then we don't know | 482 # TODO(maruel): If the user is using git or git-svn, then we don't know |
396 # what files have changed so we always run all hooks. It'd be nice to fix | 483 # what files have changed so we always run all hooks. It'd be nice to fix |
397 # that. | 484 # that. |
398 if (options.force or | 485 if (options.force or |
399 isinstance(self.parsed_url, self.FileImpl) or | 486 isinstance(self.parsed_url, self.FileImpl) or |
400 gclient_scm.GetScmName(self.parsed_url) in ('git', None) or | 487 gclient_scm.GetScmName(self.parsed_url) in ('git', None) or |
(...skipping 85 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
486 def file_list(self): | 573 def file_list(self): |
487 result = self._file_list[:] | 574 result = self._file_list[:] |
488 for d in self.dependencies: | 575 for d in self.dependencies: |
489 result.extend(d.file_list()) | 576 result.extend(d.file_list()) |
490 return result | 577 return result |
491 | 578 |
492 def __str__(self): | 579 def __str__(self): |
493 out = [] | 580 out = [] |
494 for i in ('name', 'url', 'parsed_url', 'safesync_url', 'custom_deps', | 581 for i in ('name', 'url', 'parsed_url', 'safesync_url', 'custom_deps', |
495 'custom_vars', 'deps_hooks', '_file_list', 'processed', | 582 'custom_vars', 'deps_hooks', '_file_list', 'processed', |
496 'hooks_ran', 'deps_parsed', 'requirements'): | 583 'hooks_ran', 'deps_parsed', 'requirements', 'direct_reference'): |
497 # 'deps_file' | 584 # 'deps_file' |
498 if self.__dict__[i]: | 585 if self.__dict__[i]: |
499 out.append('%s: %s' % (i, self.__dict__[i])) | 586 out.append('%s: %s' % (i, self.__dict__[i])) |
500 | 587 |
501 for d in self.dependencies: | 588 for d in self.dependencies: |
502 out.extend([' ' + x for x in str(d).splitlines()]) | 589 out.extend([' ' + x for x in str(d).splitlines()]) |
503 out.append('') | 590 out.append('') |
504 return '\n'.join(out) | 591 return '\n'.join(out) |
505 | 592 |
506 def __repr__(self): | 593 def __repr__(self): |
(...skipping 87 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
594 self, s['name'], s['url'], | 681 self, s['name'], s['url'], |
595 s.get('safesync_url', None), | 682 s.get('safesync_url', None), |
596 s.get('custom_deps', {}), | 683 s.get('custom_deps', {}), |
597 s.get('custom_vars', {}), | 684 s.get('custom_vars', {}), |
598 None)) | 685 None)) |
599 except KeyError: | 686 except KeyError: |
600 raise gclient_utils.Error('Invalid .gclient file. Solution is ' | 687 raise gclient_utils.Error('Invalid .gclient file. Solution is ' |
601 'incomplete: %s' % s) | 688 'incomplete: %s' % s) |
602 # .gclient can have hooks. | 689 # .gclient can have hooks. |
603 self.deps_hooks = config_dict.get('hooks', []) | 690 self.deps_hooks = config_dict.get('hooks', []) |
| 691 self.direct_reference = True |
| 692 self.deps_parsed = True |
604 | 693 |
605 def SaveConfig(self): | 694 def SaveConfig(self): |
606 gclient_utils.FileWrite(os.path.join(self.root_dir(), | 695 gclient_utils.FileWrite(os.path.join(self.root_dir(), |
607 self._options.config_filename), | 696 self._options.config_filename), |
608 self.config_content) | 697 self.config_content) |
609 | 698 |
610 @staticmethod | 699 @staticmethod |
611 def LoadCurrentConfig(options): | 700 def LoadCurrentConfig(options): |
612 """Searches for and loads a .gclient file relative to the current working | 701 """Searches for and loads a .gclient file relative to the current working |
613 dir. Returns a GClient object.""" | 702 dir. Returns a GClient object.""" |
(...skipping 84 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
698 | 787 |
699 Args: | 788 Args: |
700 command: The command to use (e.g., 'status' or 'diff') | 789 command: The command to use (e.g., 'status' or 'diff') |
701 args: list of str - extra arguments to add to the command line. | 790 args: list of str - extra arguments to add to the command line. |
702 """ | 791 """ |
703 if not self.dependencies: | 792 if not self.dependencies: |
704 raise gclient_utils.Error('No solution specified') | 793 raise gclient_utils.Error('No solution specified') |
705 revision_overrides = self._EnforceRevisions() | 794 revision_overrides = self._EnforceRevisions() |
706 pm = None | 795 pm = None |
707 if command == 'update' and not self._options.verbose: | 796 if command == 'update' and not self._options.verbose: |
708 pm = Progress('Syncing projects', len(self.tree(False)) + 1) | 797 pm = Progress('Syncing projects', 1) |
709 self.RunCommandRecursively(self._options, revision_overrides, | 798 work_queue = ExecutionQueue(pm) |
710 command, args, pm) | 799 for s in self.dependencies: |
711 if pm: | 800 work_queue.enqueue(s) |
712 pm.end() | 801 work_queue.flush(self._options, revision_overrides, command, args, |
| 802 work_queue) |
713 | 803 |
714 # Once all the dependencies have been processed, it's now safe to run the | 804 # Once all the dependencies have been processed, it's now safe to run the |
715 # hooks. | 805 # hooks. |
716 if not self._options.nohooks: | 806 if not self._options.nohooks: |
717 self.RunHooksRecursively(self._options) | 807 self.RunHooksRecursively(self._options) |
718 | 808 |
719 if command == 'update': | 809 if command == 'update': |
720 # Notify the user if there is an orphaned entry in their working copy. | 810 # Notify the user if there is an orphaned entry in their working copy. |
721 # Only delete the directory if there are no changes in it, and | 811 # Only delete the directory if there are no changes in it, and |
722 # delete_unversioned_trees is set to true. | 812 # delete_unversioned_trees is set to true. |
(...skipping 20 matching lines...) Expand all Loading... |
743 entry_fixed, self.root_dir())) | 833 entry_fixed, self.root_dir())) |
744 gclient_utils.RemoveDirectory(e_dir) | 834 gclient_utils.RemoveDirectory(e_dir) |
745 # record the current list of entries for next time | 835 # record the current list of entries for next time |
746 self._SaveEntries() | 836 self._SaveEntries() |
747 return 0 | 837 return 0 |
748 | 838 |
749 def PrintRevInfo(self): | 839 def PrintRevInfo(self): |
750 if not self.dependencies: | 840 if not self.dependencies: |
751 raise gclient_utils.Error('No solution specified') | 841 raise gclient_utils.Error('No solution specified') |
752 # Load all the settings. | 842 # Load all the settings. |
753 self.RunCommandRecursively(self._options, {}, None, [], None) | 843 work_queue = ExecutionQueue(None) |
| 844 for s in self.dependencies: |
| 845 work_queue.enqueue(s) |
| 846 work_queue.flush(self._options, {}, None, [], work_queue) |
754 | 847 |
755 def GetURLAndRev(dep): | 848 def GetURLAndRev(dep): |
756 """Returns the revision-qualified SCM url for a Dependency.""" | 849 """Returns the revision-qualified SCM url for a Dependency.""" |
757 if dep.parsed_url is None: | 850 if dep.parsed_url is None: |
758 return None | 851 return None |
759 if isinstance(dep.parsed_url, self.FileImpl): | 852 if isinstance(dep.parsed_url, self.FileImpl): |
760 original_url = dep.parsed_url.file_location | 853 original_url = dep.parsed_url.file_location |
761 else: | 854 else: |
762 original_url = dep.parsed_url | 855 original_url = dep.parsed_url |
763 url, _ = gclient_utils.SplitUrlRevision(original_url) | 856 url, _ = gclient_utils.SplitUrlRevision(original_url) |
(...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
802 keys = sorted(entries.keys()) | 895 keys = sorted(entries.keys()) |
803 for x in keys: | 896 for x in keys: |
804 line = '%s: %s' % (x, entries[x]) | 897 line = '%s: %s' % (x, entries[x]) |
805 if x is not keys[-1]: | 898 if x is not keys[-1]: |
806 line += ';' | 899 line += ';' |
807 print line | 900 print line |
808 logging.info(str(self)) | 901 logging.info(str(self)) |
809 | 902 |
810 def ParseDepsFile(self, direct_reference): | 903 def ParseDepsFile(self, direct_reference): |
811 """No DEPS to parse for a .gclient file.""" | 904 """No DEPS to parse for a .gclient file.""" |
812 self.direct_reference = True | 905 raise gclient_utils.Error('Internal error') |
813 self.deps_parsed = True | |
814 | 906 |
815 def root_dir(self): | 907 def root_dir(self): |
816 """Root directory of gclient checkout.""" | 908 """Root directory of gclient checkout.""" |
817 return self._root_dir | 909 return self._root_dir |
818 | 910 |
819 def enforced_os(self): | 911 def enforced_os(self): |
820 """What deps_os entries that are to be parsed.""" | 912 """What deps_os entries that are to be parsed.""" |
821 return self._enforced_os | 913 return self._enforced_os |
822 | 914 |
823 def recursion_limit(self): | 915 def recursion_limit(self): |
(...skipping 388 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1212 return CMDhelp(parser, argv) | 1304 return CMDhelp(parser, argv) |
1213 except gclient_utils.Error, e: | 1305 except gclient_utils.Error, e: |
1214 print >> sys.stderr, 'Error: %s' % str(e) | 1306 print >> sys.stderr, 'Error: %s' % str(e) |
1215 return 1 | 1307 return 1 |
1216 | 1308 |
1217 | 1309 |
1218 if '__main__' == __name__: | 1310 if '__main__' == __name__: |
1219 sys.exit(Main(sys.argv[1:])) | 1311 sys.exit(Main(sys.argv[1:])) |
1220 | 1312 |
1221 # vim: ts=2:sw=2:tw=80:et: | 1313 # vim: ts=2:sw=2:tw=80:et: |
OLD | NEW |