Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(302)

Side by Side Diff: gclient.py

Issue 17742004: Add custom hooks. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/tools/depot_tools/
Patch Set: Created 7 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « no previous file | tests/gclient_test.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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
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
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
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
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
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
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:
OLDNEW
« no previous file with comments | « no previous file | tests/gclient_test.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698