OLD | NEW |
1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. | 2 # Copyright (c) 2012 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 |
11 entries each are maps binding the strings "name" and "url" | 11 entries each are maps binding the strings "name" and "url" |
12 to strings specifying the name and location of the client | 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 | 13 module, as well as "custom_deps" to a map similar to the deps |
14 file below. | 14 section of the DEPS file below, as well as "custom_hooks" to |
| 15 a list similar to the hooks sections of the DEPS file below. |
15 .gclient_entries : A cache constructed by 'update' command. Format is a | 16 .gclient_entries : A cache constructed by 'update' command. Format is a |
16 Python script defining 'entries', a list of the names | 17 Python script defining 'entries', a list of the names |
17 of all modules in the client | 18 of all modules in the client |
18 <module>/DEPS : Python script defining var 'deps' as a map from each requisite | 19 <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 submodule name to a URL where it can be found (via one SCM) |
20 | 21 |
21 Hooks | 22 Hooks |
22 .gclient and DEPS files may optionally contain a list named "hooks" to | 23 .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 | 24 allow custom actions to be performed based on files that have changed in the |
24 working copy as a result of a "sync"/"update" or "revert" operation. This | 25 working copy as a result of a "sync"/"update" or "revert" operation. This |
25 can be prevented by using --nohooks (hooks run by default). Hooks can also | 26 can be prevented by using --nohooks (hooks run by default). Hooks can also |
26 be forced to run with the "runhooks" operation. If "sync" is run with | 27 be forced to run with the "runhooks" operation. If "sync" is run with |
27 --force, all known hooks will run regardless of the state of the working | 28 --force, all known but not suppressed hooks will run regardless of the state |
28 copy. | 29 of the working copy. |
29 | 30 |
30 Each item in a "hooks" list is a dict, containing these two keys: | 31 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 "pattern" The associated value is a string containing a regular |
32 expression. When a file whose pathname matches the expression | 33 expression. When a file whose pathname matches the expression |
33 is checked out, updated, or reverted, the hook's "action" will | 34 is checked out, updated, or reverted, the hook's "action" will |
34 run. | 35 run. |
35 "action" A list describing a command to run along with its arguments, if | 36 "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 any. An action command will run at most one time per gclient |
37 invocation, regardless of how many files matched the pattern. | 38 invocation, regardless of how many files matched the pattern. |
38 The action is executed in the same directory as the .gclient | 39 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 file. If the first item in the list is the string "python", |
40 the current Python interpreter (sys.executable) will be used | 41 the current Python interpreter (sys.executable) will be used |
41 to run the command. If the list contains string "$matching_files" | 42 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 it will be removed from the list and the list will be extended |
43 by the list of matching files. | 44 by the list of matching files. |
| 45 "name" An optional string specifying the group to which a hook belongs |
| 46 for overriding and organizing. |
44 | 47 |
45 Example: | 48 Example: |
46 hooks = [ | 49 hooks = [ |
47 { "pattern": "\\.(gif|jpe?g|pr0n|png)$", | 50 { "pattern": "\\.(gif|jpe?g|pr0n|png)$", |
48 "action": ["python", "image_indexer.py", "--all"]}, | 51 "action": ["python", "image_indexer.py", "--all"]}, |
| 52 { "pattern": ".", |
| 53 "name": "gyp", |
| 54 "action": ["python", "src/build/gyp_chromium"]}, |
49 ] | 55 ] |
50 | 56 |
51 Specifying a target OS | 57 Specifying a target OS |
52 An optional key named "target_os" may be added to a gclient file to specify | 58 An optional key named "target_os" may be added to a gclient file to specify |
53 one or more additional operating systems that should be considered when | 59 one or more additional operating systems that should be considered when |
54 processing the deps_os dict of a DEPS file. | 60 processing the deps_os dict of a DEPS file. |
55 | 61 |
56 Example: | 62 Example: |
57 target_os = [ "android" ] | 63 target_os = [ "android" ] |
58 | 64 |
(...skipping 93 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
152 return self._custom_vars[var_name] | 158 return self._custom_vars[var_name] |
153 elif var_name in self._local_scope.get("vars", {}): | 159 elif var_name in self._local_scope.get("vars", {}): |
154 return self._local_scope["vars"][var_name] | 160 return self._local_scope["vars"][var_name] |
155 raise gclient_utils.Error("Var is not defined: %s" % var_name) | 161 raise gclient_utils.Error("Var is not defined: %s" % var_name) |
156 | 162 |
157 | 163 |
158 class DependencySettings(GClientKeywords): | 164 class DependencySettings(GClientKeywords): |
159 """Immutable configuration settings.""" | 165 """Immutable configuration settings.""" |
160 def __init__( | 166 def __init__( |
161 self, parent, url, safesync_url, managed, custom_deps, custom_vars, | 167 self, parent, url, safesync_url, managed, custom_deps, custom_vars, |
162 deps_file, should_process): | 168 custom_hooks, deps_file, should_process): |
163 GClientKeywords.__init__(self) | 169 GClientKeywords.__init__(self) |
164 | 170 |
165 # These are not mutable: | 171 # These are not mutable: |
166 self._parent = parent | 172 self._parent = parent |
167 self._safesync_url = safesync_url | 173 self._safesync_url = safesync_url |
168 self._deps_file = deps_file | 174 self._deps_file = deps_file |
169 self._url = url | 175 self._url = url |
170 # 'managed' determines whether or not this dependency is synced/updated by | 176 # 'managed' determines whether or not this dependency is synced/updated by |
171 # gclient after gclient checks it out initially. The difference between | 177 # gclient after gclient checks it out initially. The difference between |
172 # 'managed' and 'should_process' is that the user specifies 'managed' via | 178 # 'managed' and 'should_process' is that the user specifies 'managed' via |
173 # the --unmanaged command-line flag or a .gclient config, where | 179 # the --unmanaged command-line flag or a .gclient config, where |
174 # 'should_process' is dynamically set by gclient if it goes over its | 180 # 'should_process' is dynamically set by gclient if it goes over its |
175 # recursion limit and controls gclient's behavior so it does not misbehave. | 181 # recursion limit and controls gclient's behavior so it does not misbehave. |
176 self._managed = managed | 182 self._managed = managed |
177 self._should_process = should_process | 183 self._should_process = should_process |
178 # This is a mutable value that overrides the normal recursion limit for this | 184 # This is a mutable value that overrides the normal recursion limit for this |
179 # dependency. It is read from the actual DEPS file so cannot be set on | 185 # dependency. It is read from the actual DEPS file so cannot be set on |
180 # class instantiation. | 186 # class instantiation. |
181 self.recursion_override = None | 187 self.recursion_override = None |
182 # This is a mutable value which has the list of 'target_os' OSes listed in | 188 # This is a mutable value which has the list of 'target_os' OSes listed in |
183 # the current deps file. | 189 # the current deps file. |
184 self.local_target_os = None | 190 self.local_target_os = None |
185 | 191 |
186 # These are only set in .gclient and not in DEPS files. | 192 # These are only set in .gclient and not in DEPS files. |
187 self._custom_vars = custom_vars or {} | 193 self._custom_vars = custom_vars or {} |
188 self._custom_deps = custom_deps or {} | 194 self._custom_deps = custom_deps or {} |
| 195 self._custom_hooks = custom_hooks or [] |
189 | 196 |
190 # TODO(iannucci): Remove this when all masters are correctly substituting | 197 # TODO(iannucci): Remove this when all masters are correctly substituting |
191 # the new blink url. | 198 # the new blink url. |
192 if (self._custom_vars.get('webkit_trunk', '') == | 199 if (self._custom_vars.get('webkit_trunk', '') == |
193 'svn://svn-mirror.golo.chromium.org/webkit-readonly/trunk'): | 200 'svn://svn-mirror.golo.chromium.org/webkit-readonly/trunk'): |
194 new_url = 'svn://svn-mirror.golo.chromium.org/blink/trunk' | 201 new_url = 'svn://svn-mirror.golo.chromium.org/blink/trunk' |
195 print 'Overwriting Var("webkit_trunk") with %s' % new_url | 202 print 'Overwriting Var("webkit_trunk") with %s' % new_url |
196 self._custom_vars['webkit_trunk'] = new_url | 203 self._custom_vars['webkit_trunk'] = new_url |
197 | 204 |
198 # Post process the url to remove trailing slashes. | 205 # Post process the url to remove trailing slashes. |
(...skipping 41 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
240 | 247 |
241 @property | 248 @property |
242 def custom_vars(self): | 249 def custom_vars(self): |
243 return self._custom_vars.copy() | 250 return self._custom_vars.copy() |
244 | 251 |
245 @property | 252 @property |
246 def custom_deps(self): | 253 def custom_deps(self): |
247 return self._custom_deps.copy() | 254 return self._custom_deps.copy() |
248 | 255 |
249 @property | 256 @property |
| 257 def custom_hooks(self): |
| 258 return self._custom_hooks[:] |
| 259 |
| 260 @property |
250 def url(self): | 261 def url(self): |
251 return self._url | 262 return self._url |
252 | 263 |
253 @property | 264 @property |
254 def recursion_limit(self): | 265 def recursion_limit(self): |
255 """Returns > 0 if this dependency is not too recursed to be processed.""" | 266 """Returns > 0 if this dependency is not too recursed to be processed.""" |
256 if self.recursion_override is not None: | 267 if self.recursion_override is not None: |
257 return self.recursion_override | 268 return self.recursion_override |
258 return max(self.parent.recursion_limit - 1, 0) | 269 return max(self.parent.recursion_limit - 1, 0) |
259 | 270 |
260 @property | 271 @property |
261 def target_os(self): | 272 def target_os(self): |
262 if self.local_target_os is not None: | 273 if self.local_target_os is not None: |
263 return tuple(set(self.local_target_os).union(self.parent.target_os)) | 274 return tuple(set(self.local_target_os).union(self.parent.target_os)) |
264 else: | 275 else: |
265 return self.parent.target_os | 276 return self.parent.target_os |
266 | 277 |
267 def get_custom_deps(self, name, url): | 278 def get_custom_deps(self, name, url): |
268 """Returns a custom deps if applicable.""" | 279 """Returns a custom deps if applicable.""" |
269 if self.parent: | 280 if self.parent: |
270 url = self.parent.get_custom_deps(name, url) | 281 url = self.parent.get_custom_deps(name, url) |
271 # None is a valid return value to disable a dependency. | 282 # None is a valid return value to disable a dependency. |
272 return self.custom_deps.get(name, url) | 283 return self.custom_deps.get(name, url) |
273 | 284 |
274 | 285 |
275 class Dependency(gclient_utils.WorkItem, DependencySettings): | 286 class Dependency(gclient_utils.WorkItem, DependencySettings): |
276 """Object that represents a dependency checkout.""" | 287 """Object that represents a dependency checkout.""" |
277 | 288 |
278 def __init__(self, parent, name, url, safesync_url, managed, custom_deps, | 289 def __init__(self, parent, name, url, safesync_url, managed, custom_deps, |
279 custom_vars, deps_file, should_process): | 290 custom_vars, custom_hooks, deps_file, should_process): |
280 gclient_utils.WorkItem.__init__(self, name) | 291 gclient_utils.WorkItem.__init__(self, name) |
281 DependencySettings.__init__( | 292 DependencySettings.__init__( |
282 self, parent, url, safesync_url, managed, custom_deps, custom_vars, | 293 self, parent, url, safesync_url, managed, custom_deps, custom_vars, |
283 deps_file, should_process) | 294 custom_hooks, deps_file, should_process) |
284 | 295 |
285 # This is in both .gclient and DEPS files: | 296 # This is in both .gclient and DEPS files: |
286 self._deps_hooks = [] | 297 self._deps_hooks = [] |
287 | 298 |
288 # Calculates properties: | 299 # Calculates properties: |
289 self._parsed_url = None | 300 self._parsed_url = None |
290 self._dependencies = [] | 301 self._dependencies = [] |
291 # A cache of the files affected by the current operation, necessary for | 302 # A cache of the files affected by the current operation, necessary for |
292 # hooks. | 303 # hooks. |
293 self._file_list = [] | 304 self._file_list = [] |
(...skipping 227 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
521 # normpath is required to allow DEPS to use .. in their | 532 # normpath is required to allow DEPS to use .. in their |
522 # dependency local path. | 533 # dependency local path. |
523 rel_deps[os.path.normpath(os.path.join(self.name, d))] = url | 534 rel_deps[os.path.normpath(os.path.join(self.name, d))] = url |
524 deps = rel_deps | 535 deps = rel_deps |
525 | 536 |
526 # Convert the deps into real Dependency. | 537 # Convert the deps into real Dependency. |
527 deps_to_add = [] | 538 deps_to_add = [] |
528 for name, url in deps.iteritems(): | 539 for name, url in deps.iteritems(): |
529 should_process = self.recursion_limit and self.should_process | 540 should_process = self.recursion_limit and self.should_process |
530 deps_to_add.append(Dependency( | 541 deps_to_add.append(Dependency( |
531 self, name, url, None, None, None, None, | 542 self, name, url, None, None, None, None, None, |
532 self.deps_file, should_process)) | 543 self.deps_file, should_process)) |
533 deps_to_add.sort(key=lambda x: x.name) | 544 deps_to_add.sort(key=lambda x: x.name) |
534 self.add_dependencies_and_close(deps_to_add, local_scope.get('hooks', [])) | 545 |
| 546 # override named sets of hooks by the custom hooks |
| 547 hooks_to_run = [] |
| 548 hook_names_to_suppress = [c.get('name', '') for c in self.custom_hooks] |
| 549 for hook in local_scope.get('hooks', []): |
| 550 if hook.get('name', '') not in hook_names_to_suppress: |
| 551 hooks_to_run.append(hook) |
| 552 |
| 553 # add the replacements and any additions |
| 554 for hook in self.custom_hooks: |
| 555 if 'action' in hook: |
| 556 hooks_to_run.append(hook) |
| 557 |
| 558 self.add_dependencies_and_close(deps_to_add, hooks_to_run) |
535 logging.info('ParseDepsFile(%s) done' % self.name) | 559 logging.info('ParseDepsFile(%s) done' % self.name) |
536 | 560 |
537 def add_dependencies_and_close(self, deps_to_add, hooks): | 561 def add_dependencies_and_close(self, deps_to_add, hooks): |
538 """Adds the dependencies, hooks and mark the parsing as done.""" | 562 """Adds the dependencies, hooks and mark the parsing as done.""" |
539 for dep in deps_to_add: | 563 for dep in deps_to_add: |
540 if dep.verify_validity(): | 564 if dep.verify_validity(): |
541 self.add_dependency(dep) | 565 self.add_dependency(dep) |
542 self._mark_as_parsed(hooks) | 566 self._mark_as_parsed(hooks) |
543 | 567 |
544 def maybeGetParentRevision( | 568 def maybeGetParentRevision( |
(...skipping 369 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
914 DEFAULT_SNAPSHOT_FILE_TEXT = ("""\ | 938 DEFAULT_SNAPSHOT_FILE_TEXT = ("""\ |
915 # Snapshot generated with gclient revinfo --snapshot | 939 # Snapshot generated with gclient revinfo --snapshot |
916 solutions = [ | 940 solutions = [ |
917 %(solution_list)s] | 941 %(solution_list)s] |
918 """) | 942 """) |
919 | 943 |
920 def __init__(self, root_dir, options): | 944 def __init__(self, root_dir, options): |
921 # Do not change previous behavior. Only solution level and immediate DEPS | 945 # Do not change previous behavior. Only solution level and immediate DEPS |
922 # are processed. | 946 # are processed. |
923 self._recursion_limit = 2 | 947 self._recursion_limit = 2 |
924 Dependency.__init__(self, None, None, None, None, True, None, None, | 948 Dependency.__init__(self, None, None, None, None, True, None, None, None, |
925 'unused', True) | 949 'unused', True) |
926 self._options = options | 950 self._options = options |
927 if options.deps_os: | 951 if options.deps_os: |
928 enforced_os = options.deps_os.split(',') | 952 enforced_os = options.deps_os.split(',') |
929 else: | 953 else: |
930 enforced_os = [self.DEPS_OS_CHOICES.get(sys.platform, 'unix')] | 954 enforced_os = [self.DEPS_OS_CHOICES.get(sys.platform, 'unix')] |
931 if 'all' in enforced_os: | 955 if 'all' in enforced_os: |
932 enforced_os = self.DEPS_OS_CHOICES.itervalues() | 956 enforced_os = self.DEPS_OS_CHOICES.itervalues() |
933 self._enforced_os = tuple(set(enforced_os)) | 957 self._enforced_os = tuple(set(enforced_os)) |
934 self._root_dir = root_dir | 958 self._root_dir = root_dir |
(...skipping 21 matching lines...) Expand all Loading... |
956 | 980 |
957 deps_to_add = [] | 981 deps_to_add = [] |
958 for s in config_dict.get('solutions', []): | 982 for s in config_dict.get('solutions', []): |
959 try: | 983 try: |
960 deps_to_add.append(Dependency( | 984 deps_to_add.append(Dependency( |
961 self, s['name'], s['url'], | 985 self, s['name'], s['url'], |
962 s.get('safesync_url', None), | 986 s.get('safesync_url', None), |
963 s.get('managed', True), | 987 s.get('managed', True), |
964 s.get('custom_deps', {}), | 988 s.get('custom_deps', {}), |
965 s.get('custom_vars', {}), | 989 s.get('custom_vars', {}), |
| 990 s.get('custom_hooks', []), |
966 s.get('deps_file', 'DEPS'), | 991 s.get('deps_file', 'DEPS'), |
967 True)) | 992 True)) |
968 except KeyError: | 993 except KeyError: |
969 raise gclient_utils.Error('Invalid .gclient file. Solution is ' | 994 raise gclient_utils.Error('Invalid .gclient file. Solution is ' |
970 'incomplete: %s' % s) | 995 'incomplete: %s' % s) |
971 self.add_dependencies_and_close(deps_to_add, config_dict.get('hooks', [])) | 996 self.add_dependencies_and_close(deps_to_add, config_dict.get('hooks', [])) |
972 logging.info('SetConfig() done') | 997 logging.info('SetConfig() done') |
973 | 998 |
974 def SaveConfig(self): | 999 def SaveConfig(self): |
975 gclient_utils.FileWrite(os.path.join(self.root_dir, | 1000 gclient_utils.FileWrite(os.path.join(self.root_dir, |
(...skipping 831 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1807 except (gclient_utils.Error, subprocess2.CalledProcessError), e: | 1832 except (gclient_utils.Error, subprocess2.CalledProcessError), e: |
1808 print >> sys.stderr, 'Error: %s' % str(e) | 1833 print >> sys.stderr, 'Error: %s' % str(e) |
1809 return 1 | 1834 return 1 |
1810 | 1835 |
1811 | 1836 |
1812 if '__main__' == __name__: | 1837 if '__main__' == __name__: |
1813 fix_encoding.fix_encoding() | 1838 fix_encoding.fix_encoding() |
1814 sys.exit(Main(sys.argv[1:])) | 1839 sys.exit(Main(sys.argv[1:])) |
1815 | 1840 |
1816 # vim: ts=2:sw=2:tw=80:et: | 1841 # vim: ts=2:sw=2:tw=80:et: |
OLD | NEW |