OLD | NEW |
1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
2 # Copyright 2013 The Chromium Authors. All rights reserved. | 2 # Copyright 2013 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 """Traverses the source tree, parses all found DEPS files, and constructs | 6 """Traverses the source tree, parses all found DEPS files, and constructs |
7 a dependency rule table to be used by subclasses. | 7 a dependency rule table to be used by subclasses. |
8 | 8 |
9 The format of the deps file: | 9 The format of the deps file: |
10 | 10 |
(...skipping 57 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
68 so you can modify or remove it using the normal include rules. | 68 so you can modify or remove it using the normal include rules. |
69 | 69 |
70 The rules are processed in order. This means you can explicitly allow a higher | 70 The rules are processed in order. This means you can explicitly allow a higher |
71 directory and then take away permissions from sub-parts, or the reverse. | 71 directory and then take away permissions from sub-parts, or the reverse. |
72 | 72 |
73 Note that all directory separators must be slashes (Unix-style) and not | 73 Note that all directory separators must be slashes (Unix-style) and not |
74 backslashes. All directories should be relative to the source root and use | 74 backslashes. All directories should be relative to the source root and use |
75 only lowercase. | 75 only lowercase. |
76 """ | 76 """ |
77 | 77 |
| 78 import copy |
78 import os | 79 import os |
79 import subprocess | 80 import subprocess |
80 import copy | |
81 | 81 |
82 from rules import Rule, Rules | 82 from rules import Rule, Rules |
83 | 83 |
84 | 84 |
85 # Variable name used in the DEPS file to add or subtract include files from | 85 # Variable name used in the DEPS file to add or subtract include files from |
86 # the module-level deps. | 86 # the module-level deps. |
87 INCLUDE_RULES_VAR_NAME = 'include_rules' | 87 INCLUDE_RULES_VAR_NAME = 'include_rules' |
88 | 88 |
89 # Variable name used in the DEPS file to add or subtract include files | 89 # Variable name used in the DEPS file to add or subtract include files |
90 # from module-level deps specific to files whose basename (last | 90 # from module-level deps specific to files whose basename (last |
(...skipping 18 matching lines...) Expand all Loading... |
109 def __init__(self, | 109 def __init__(self, |
110 base_directory=None, | 110 base_directory=None, |
111 verbose=False, | 111 verbose=False, |
112 being_tested=False, | 112 being_tested=False, |
113 ignore_temp_rules=False, | 113 ignore_temp_rules=False, |
114 ignore_specific_rules=False): | 114 ignore_specific_rules=False): |
115 """Creates a new DepsBuilder. | 115 """Creates a new DepsBuilder. |
116 | 116 |
117 Args: | 117 Args: |
118 base_directory: OS-compatible path to root of checkout, e.g. C:\chr\src. | 118 base_directory: OS-compatible path to root of checkout, e.g. C:\chr\src. |
119 verbose: Set to true for debug output. | 119 verbose: Set to True for debug output. |
120 being_tested: Set to true to ignore the DEPS file at tools/checkdeps/DEPS. | 120 being_tested: Set to True to ignore the DEPS file at tools/checkdeps/DEPS. |
121 ignore_temp_rules: Ignore rules that start with Rule.TEMP_ALLOW ("!"). | 121 ignore_temp_rules: Ignore rules that start with Rule.TEMP_ALLOW ("!"). |
122 """ | 122 """ |
123 base_directory = (base_directory or | 123 base_directory = (base_directory or |
124 os.path.join(os.path.dirname(__file__), '..', '..')) | 124 os.path.join(os.path.dirname(__file__), '..', '..')) |
125 self.base_directory = os.path.abspath(base_directory) | 125 self.base_directory = os.path.abspath(base_directory) |
126 self.verbose = verbose | 126 self.verbose = verbose |
127 self._under_test = being_tested | 127 self._under_test = being_tested |
128 self._ignore_temp_rules = ignore_temp_rules | 128 self._ignore_temp_rules = ignore_temp_rules |
129 self._ignore_specific_rules = ignore_specific_rules | 129 self._ignore_specific_rules = ignore_specific_rules |
130 | 130 |
(...skipping 15 matching lines...) Expand all Loading... |
146 from the "specific_include_rules" section of DEPS. | 146 from the "specific_include_rules" section of DEPS. |
147 cur_dir: The current directory, normalized path. We will create an | 147 cur_dir: The current directory, normalized path. We will create an |
148 implicit rule that allows inclusion from this directory. | 148 implicit rule that allows inclusion from this directory. |
149 | 149 |
150 Returns: A new set of rules combining the existing_rules with the other | 150 Returns: A new set of rules combining the existing_rules with the other |
151 arguments. | 151 arguments. |
152 """ | 152 """ |
153 rules = copy.deepcopy(existing_rules) | 153 rules = copy.deepcopy(existing_rules) |
154 | 154 |
155 # First apply the implicit "allow" rule for the current directory. | 155 # First apply the implicit "allow" rule for the current directory. |
156 if cur_dir.startswith( | 156 norm_base_dir = NormalizePath(os.path.normpath(self.base_directory)) |
157 NormalizePath(os.path.normpath(self.base_directory))): | 157 if not cur_dir.startswith(norm_base_dir): |
158 relative_dir = cur_dir[len(self.base_directory) + 1:] | 158 raise Exception( |
| 159 'Internal error: base directory is not at the beginning for\n' |
| 160 ' %s and base dir\n' |
| 161 ' %s' % (cur_dir, norm_base_dir)) |
| 162 relative_dir = os.path.relpath(cur_dir, norm_base_dir) |
159 | 163 |
160 source = relative_dir | 164 # Make the help string a little more meaningful. |
161 if len(source) == 0: | 165 source = relative_dir or 'top level' |
162 source = 'top level' # Make the help string a little more meaningful. | 166 rules.AddRule('+' + relative_dir, |
163 rules.AddRule('+' + relative_dir, | 167 relative_dir, |
164 relative_dir, | 168 'Default rule for ' + source) |
165 'Default rule for ' + source) | |
166 else: | |
167 raise Exception('Internal error: base directory is not at the beginning' + | |
168 ' for\n %s and base dir\n %s' % | |
169 (cur_dir, self.base_directory)) | |
170 | 169 |
171 def ApplyOneRule(rule_str, cur_dir, dependee_regexp=None): | 170 def ApplyOneRule(rule_str, cur_dir, dependee_regexp=None): |
172 """Deduces a sensible description for the rule being added, and | 171 """Deduces a sensible description for the rule being added, and |
173 adds the rule with its description to |rules|. | 172 adds the rule with its description to |rules|. |
174 | 173 |
175 If we are ignoring temporary rules, this function does nothing | 174 If we are ignoring temporary rules, this function does nothing |
176 for rules beginning with the Rule.TEMP_ALLOW character. | 175 for rules beginning with the Rule.TEMP_ALLOW character. |
177 """ | 176 """ |
178 if self._ignore_temp_rules and rule_str.startswith(Rule.TEMP_ALLOW): | 177 if self._ignore_temp_rules and rule_str.startswith(Rule.TEMP_ALLOW): |
179 return | 178 return |
180 | 179 |
181 rule_block_name = 'include_rules' | 180 rule_block_name = 'include_rules' |
182 if dependee_regexp: | 181 if dependee_regexp: |
183 rule_block_name = 'specific_include_rules' | 182 rule_block_name = 'specific_include_rules' |
184 if not relative_dir: | 183 if relative_dir: |
| 184 rule_description = relative_dir + "'s %s" % rule_block_name |
| 185 else: |
185 rule_description = 'the top level %s' % rule_block_name | 186 rule_description = 'the top level %s' % rule_block_name |
186 else: | |
187 rule_description = relative_dir + "'s %s" % rule_block_name | |
188 rules.AddRule(rule_str, relative_dir, rule_description, dependee_regexp) | 187 rules.AddRule(rule_str, relative_dir, rule_description, dependee_regexp) |
189 | 188 |
190 # Apply the additional explicit rules. | 189 # Apply the additional explicit rules. |
191 for (_, rule_str) in enumerate(includes): | 190 for rule_str in includes: |
192 ApplyOneRule(rule_str, cur_dir) | 191 ApplyOneRule(rule_str, cur_dir) |
193 | 192 |
194 # Finally, apply the specific rules. | 193 # Finally, apply the specific rules. |
195 if not self._ignore_specific_rules: | 194 if not self._ignore_specific_rules: |
196 for regexp, specific_rules in specific_includes.iteritems(): | 195 for regexp, specific_rules in specific_includes.iteritems(): |
197 for rule_str in specific_rules: | 196 for rule_str in specific_rules: |
198 ApplyOneRule(rule_str, cur_dir, regexp) | 197 ApplyOneRule(rule_str, cur_dir, regexp) |
199 | 198 |
200 return rules | 199 return rules |
201 | 200 |
(...skipping 13 matching lines...) Expand all Loading... |
215 | 214 |
216 Returns: A tuple containing: (1) the combined set of rules to apply to the | 215 Returns: A tuple containing: (1) the combined set of rules to apply to the |
217 sub-tree, and (2) a list of all subdirectories that should NOT be | 216 sub-tree, and (2) a list of all subdirectories that should NOT be |
218 checked, as specified in the DEPS file (if any). | 217 checked, as specified in the DEPS file (if any). |
219 """ | 218 """ |
220 norm_dir_name = NormalizePath(dir_name) | 219 norm_dir_name = NormalizePath(dir_name) |
221 | 220 |
222 # Check for a .svn directory in this directory or check this directory is | 221 # Check for a .svn directory in this directory or check this directory is |
223 # contained in git source direcotries. This will tell us if it's a source | 222 # contained in git source direcotries. This will tell us if it's a source |
224 # directory and should be checked. | 223 # directory and should be checked. |
225 if not (os.path.exists(os.path.join(dir_name, ".svn")) or | 224 if not (os.path.exists(os.path.join(dir_name, '.svn')) or |
226 (norm_dir_name in self.git_source_directories)): | 225 (norm_dir_name in self.git_source_directories)): |
227 return (None, []) | 226 return None, [] |
228 | 227 |
229 # Check the DEPS file in this directory. | 228 # Check the DEPS file in this directory. |
230 if self.verbose: | 229 if self.verbose: |
231 print 'Applying rules from', dir_name | 230 print 'Applying rules from', dir_name |
232 def FromImpl(_unused, _unused2): | 231 def FromImpl(*_): |
233 pass # NOP function so "From" doesn't fail. | 232 pass # NOP function so "From" doesn't fail. |
234 | 233 |
235 def FileImpl(_unused): | 234 def FileImpl(_): |
236 pass # NOP function so "File" doesn't fail. | 235 pass # NOP function so "File" doesn't fail. |
237 | 236 |
238 class _VarImpl: | 237 class _VarImpl: |
239 def __init__(self, local_scope): | 238 def __init__(self, local_scope): |
240 self._local_scope = local_scope | 239 self._local_scope = local_scope |
241 | 240 |
242 def Lookup(self, var_name): | 241 def Lookup(self, var_name): |
243 """Implements the Var syntax.""" | 242 """Implements the Var syntax.""" |
244 if var_name in self._local_scope.get('vars', {}): | 243 try: |
245 return self._local_scope['vars'][var_name] | 244 return self._local_scope['vars'][var_name] |
246 raise Exception('Var is not defined: %s' % var_name) | 245 except KeyError: |
| 246 raise Exception('Var is not defined: %s' % var_name) |
247 | 247 |
248 local_scope = {} | 248 local_scope = {} |
249 global_scope = { | 249 global_scope = { |
250 'File': FileImpl, | 250 'File': FileImpl, |
251 'From': FromImpl, | 251 'From': FromImpl, |
252 'Var': _VarImpl(local_scope).Lookup, | 252 'Var': _VarImpl(local_scope).Lookup, |
253 } | 253 } |
254 deps_file = os.path.join(dir_name, 'DEPS') | 254 deps_file = os.path.join(dir_name, 'DEPS') |
255 | 255 |
256 # The second conditional here is to disregard the | 256 # The second conditional here is to disregard the |
257 # tools/checkdeps/DEPS file while running tests. This DEPS file | 257 # tools/checkdeps/DEPS file while running tests. This DEPS file |
258 # has a skip_child_includes for 'testdata' which is necessary for | 258 # has a skip_child_includes for 'testdata' which is necessary for |
259 # running production tests, since there are intentional DEPS | 259 # running production tests, since there are intentional DEPS |
260 # violations under the testdata directory. On the other hand when | 260 # violations under the testdata directory. On the other hand when |
261 # running tests, we absolutely need to verify the contents of that | 261 # running tests, we absolutely need to verify the contents of that |
262 # directory to trigger those intended violations and see that they | 262 # directory to trigger those intended violations and see that they |
263 # are handled correctly. | 263 # are handled correctly. |
264 if os.path.isfile(deps_file) and ( | 264 if os.path.isfile(deps_file) and ( |
265 not self._under_test or not os.path.split(dir_name)[1] == 'checkdeps'): | 265 not self._under_test or not os.path.basename(dir_name) == 'checkdeps'): |
266 execfile(deps_file, global_scope, local_scope) | 266 execfile(deps_file, global_scope, local_scope) |
267 elif self.verbose: | 267 elif self.verbose: |
268 print ' No deps file found in', dir_name | 268 print ' No deps file found in', dir_name |
269 | 269 |
270 # Even if a DEPS file does not exist we still invoke ApplyRules | 270 # Even if a DEPS file does not exist we still invoke ApplyRules |
271 # to apply the implicit "allow" rule for the current directory | 271 # to apply the implicit "allow" rule for the current directory |
272 include_rules = local_scope.get(INCLUDE_RULES_VAR_NAME, []) | 272 include_rules = local_scope.get(INCLUDE_RULES_VAR_NAME, []) |
273 specific_include_rules = local_scope.get(SPECIFIC_INCLUDE_RULES_VAR_NAME, | 273 specific_include_rules = local_scope.get(SPECIFIC_INCLUDE_RULES_VAR_NAME, |
274 {}) | 274 {}) |
275 skip_subdirs = local_scope.get(SKIP_SUBDIRS_VAR_NAME, []) | 275 skip_subdirs = local_scope.get(SKIP_SUBDIRS_VAR_NAME, []) |
(...skipping 27 matching lines...) Expand all Loading... |
303 """ | 303 """ |
304 norm_dir_path = NormalizePath(dir_path) | 304 norm_dir_path = NormalizePath(dir_path) |
305 | 305 |
306 if not norm_dir_path.startswith( | 306 if not norm_dir_path.startswith( |
307 NormalizePath(os.path.normpath(self.base_directory))): | 307 NormalizePath(os.path.normpath(self.base_directory))): |
308 dir_path = os.path.join(self.base_directory, dir_path) | 308 dir_path = os.path.join(self.base_directory, dir_path) |
309 norm_dir_path = NormalizePath(dir_path) | 309 norm_dir_path = NormalizePath(dir_path) |
310 | 310 |
311 parent_dir = os.path.dirname(dir_path) | 311 parent_dir = os.path.dirname(dir_path) |
312 parent_rules = None | 312 parent_rules = None |
313 if not norm_dir_path in self.directory_rules: | 313 if norm_dir_path not in self.directory_rules: |
314 parent_rules = self.GetDirectoryRules(parent_dir) | 314 parent_rules = self.GetDirectoryRules(parent_dir) |
315 | 315 |
316 # We need to check for an entry for our dir_path again, in case we | 316 # We need to check for an entry for our dir_path again, in case we |
317 # are at a path e.g. A/B/C where A/B/DEPS specifies the C | 317 # are at a path e.g. A/B/C where A/B/DEPS specifies the C |
318 # subdirectory to be skipped; in this case, the invocation to | 318 # subdirectory to be skipped; in this case, the invocation to |
319 # GetDirectoryRules(parent_dir) has already filled in an entry for | 319 # GetDirectoryRules(parent_dir) has already filled in an entry for |
320 # A/B/C. | 320 # A/B/C. |
321 if not norm_dir_path in self.directory_rules: | 321 if norm_dir_path not in self.directory_rules: |
322 if not parent_rules: | 322 if parent_rules: |
| 323 self._ApplyDirectoryRulesAndSkipSubdirs(parent_rules, dir_path) |
| 324 else: |
323 # If the parent directory should be skipped, then the current | 325 # If the parent directory should be skipped, then the current |
324 # directory should also be skipped. | 326 # directory should also be skipped. |
325 self.directory_rules[norm_dir_path] = None | 327 self.directory_rules[norm_dir_path] = None |
326 else: | |
327 self._ApplyDirectoryRulesAndSkipSubdirs(parent_rules, dir_path) | |
328 return self.directory_rules[norm_dir_path] | 328 return self.directory_rules[norm_dir_path] |
329 | 329 |
330 def _AddGitSourceDirectories(self): | 330 def _AddGitSourceDirectories(self): |
331 """Adds any directories containing sources managed by git to | 331 """Adds any directories containing sources managed by git to |
332 self.git_source_directories. | 332 self.git_source_directories. |
333 """ | 333 """ |
334 if not os.path.exists(os.path.join(self.base_directory, '.git')): | 334 if not os.path.exists(os.path.join(self.base_directory, '.git')): |
335 return | 335 return |
336 | 336 |
337 popen_out = os.popen('cd %s && git ls-files --full-name .' % | 337 popen_out = os.popen('cd %s && git ls-files --full-name .' % |
338 subprocess.list2cmdline([self.base_directory])) | 338 subprocess.list2cmdline([self.base_directory])) |
339 for line in popen_out.readlines(): | 339 for line in popen_out.readlines(): |
340 dir_name = os.path.join(self.base_directory, os.path.dirname(line)) | 340 dir_name = os.path.join(self.base_directory, os.path.dirname(line)) |
341 # Add the directory as well as all the parent directories. Use | 341 # Add the directory as well as all the parent directories. Use |
342 # forward slashes and lower case to normalize paths. | 342 # forward slashes and lower case to normalize paths. |
343 while dir_name != self.base_directory: | 343 while dir_name != self.base_directory: |
344 self.git_source_directories.add(NormalizePath(dir_name)) | 344 self.git_source_directories.add(NormalizePath(dir_name)) |
345 dir_name = os.path.dirname(dir_name) | 345 dir_name = os.path.dirname(dir_name) |
346 self.git_source_directories.add(NormalizePath(self.base_directory)) | 346 self.git_source_directories.add(NormalizePath(self.base_directory)) |
OLD | NEW |