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

Side by Side Diff: checkdeps/README.md

Issue 2799113002: Move DEPS file docs into a README.md (Closed)
Patch Set: wording Created 3 years, 8 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
« no previous file with comments | « no previous file | checkdeps/builddeps.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 # DEPS Files
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
4 # found in the LICENSE file.
5 2
6 """Traverses the source tree, parses all found DEPS files, and constructs 3 DEPS files specify which files the sources in a directory tree may include.
7 a dependency rule table to be used by subclasses.
8 4
9 The format of the deps file: 5 ## File format
10 6
11 First you have the normal module-level deps. These are the ones used by 7 First you have the normal module-level deps. These are the ones used by
12 gclient. An example would be: 8 gclient. An example would be:
13 9
14 deps = { 10 ```
15 "base":"http://foo.bar/trunk/base" 11 deps = {
16 } 12 "base":"http://foo.bar/trunk/base"
13 }
14 ```
17 15
18 DEPS files not in the top-level of a module won't need this. Then you 16 DEPS files not in the top-level of a module won't need this. Then you have any
19 have any additional include rules. You can add (using "+") or subtract 17 additional include rules. You can add (using `+`) or subtract (using `-`) from
20 (using "-") from the previously specified rules (including 18 the previously specified rules (including module-level deps). You can also
21 module-level deps). You can also specify a path that is allowed for 19 specify a path that is allowed for now but that we intend to remove, using `!`;
22 now but that we intend to remove, using "!"; this is treated the same 20 this is treated the same as `+` when `check_deps` is run by our bots, but a
23 as "+" when check_deps is run by our bots, but a presubmit step will 21 presubmit step will show a warning if you add a new include of a file that is
24 show a warning if you add a new include of a file that is only allowed 22 only allowed by `!`.
25 by "!".
26 23
27 Note that for .java files, there is currently no difference between 24 Note that for .java files, there is currently no difference between `+` and
28 "+" and "!", even in the presubmit step. 25 `!`, even in the presubmit step.
29 26
30 include_rules = [ 27 ```
31 # Code should be able to use base (it's specified in the module-level 28 include_rules = [
32 # deps above), but nothing in "base/evil" because it's evil. 29 # Code should be able to use base (it's specified in the module-level
33 "-base/evil", 30 # deps above), but nothing in "base/evil" because it's evil.
31 "-base/evil",
34 32
35 # But this one subdirectory of evil is OK. 33 # But this one subdirectory of evil is OK.
36 "+base/evil/not", 34 "+base/evil/not",
37 35
38 # And it can include files from this other directory even though there is 36 # And it can include files from this other directory even though there is
39 # no deps rule for it. 37 # no deps rule for it.
40 "+tools/crime_fighter", 38 "+tools/crime_fighter",
41 39
42 # This dependency is allowed for now but work is ongoing to remove it, 40 # This dependency is allowed for now but work is ongoing to remove it,
43 # so you shouldn't add further dependencies on it. 41 # so you shouldn't add further dependencies on it.
44 "!base/evil/ok_for_now.h", 42 "!base/evil/ok_for_now.h",
45 ] 43 ]
44 ```
46 45
47 If you have certain include rules that should only be applied for some 46 If you have certain include rules that should only be applied for some files
48 files within this directory and subdirectories, you can write a 47 within this directory and subdirectories, you can write a section named
49 section named specific_include_rules that is a hash map of regular 48 `specific_include_rules` that is a hash map of regular expressions to the list
50 expressions to the list of rules that should apply to files matching 49 of rules that should apply to files matching them. Note that such rules will
51 them. Note that such rules will always be applied before the rules 50 always be applied before the rules from `include_rules` have been applied, but
52 from 'include_rules' have been applied, but the order in which rules 51 the order in which rules associated with different regular expressions is
53 associated with different regular expressions is applied is arbitrary. 52 applied is arbitrary.
54 53
55 specific_include_rules = { 54 ```
56 ".*_(unit|browser|api)test\.cc": [ 55 specific_include_rules = {
57 "+libraries/testsupport", 56 ".*_(unit|browser|api)test\.cc": [
58 ], 57 "+libraries/testsupport",
59 } 58 ],
59 }
60 ```
61
62 # Directory structure
60 63
61 DEPS files may be placed anywhere in the tree. Each one applies to all 64 DEPS files may be placed anywhere in the tree. Each one applies to all
62 subdirectories, where there may be more DEPS files that provide additions or 65 subdirectories, where there may be more DEPS files that provide additions or
63 subtractions for their own sub-trees. 66 subtractions for their own sub-trees.
64 67
65 There is an implicit rule for the current directory (where the DEPS file lives) 68 There is an implicit rule for the current directory (where the DEPS file lives)
66 and all of its subdirectories. This prevents you from having to explicitly 69 and all of its subdirectories. This prevents you from having to explicitly
67 allow the current directory everywhere. This implicit rule is applied first, 70 allow the current directory everywhere. This implicit rule is applied first, so
68 so you can modify or remove it using the normal include rules. 71 you can modify or remove it using the normal include rules.
69 72
70 The rules are processed in order. This means you can explicitly allow a higher 73 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. 74 directory and then take away permissions from sub-parts, or the reverse.
72 75
73 Note that all directory separators must be slashes (Unix-style) and not 76 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 77 backslashes. All directories should be relative to the source root and use
75 only lowercase. 78 only lowercase.
76 """
77
78 import copy
79 import os.path
80 import posixpath
81 import subprocess
82
83 from rules import Rule, Rules
84
85
86 # Variable name used in the DEPS file to add or subtract include files from
87 # the module-level deps.
88 INCLUDE_RULES_VAR_NAME = 'include_rules'
89
90 # Variable name used in the DEPS file to add or subtract include files
91 # from module-level deps specific to files whose basename (last
92 # component of path) matches a given regular expression.
93 SPECIFIC_INCLUDE_RULES_VAR_NAME = 'specific_include_rules'
94
95 # Optionally present in the DEPS file to list subdirectories which should not
96 # be checked. This allows us to skip third party code, for example.
97 SKIP_SUBDIRS_VAR_NAME = 'skip_child_includes'
98
99
100 class DepsBuilderError(Exception):
101 """Base class for exceptions in this module."""
102 pass
103
104
105 def NormalizePath(path):
106 """Returns a path normalized to how we write DEPS rules and compare paths."""
107 return os.path.normcase(path).replace(os.path.sep, posixpath.sep)
108
109
110 def _GitSourceDirectories(base_directory):
111 """Returns set of normalized paths to subdirectories containing sources
112 managed by git."""
113 base_dir_norm = NormalizePath(base_directory)
114 git_source_directories = set([base_dir_norm])
115
116 git_cmd = 'git.bat' if os.name == 'nt' else 'git'
117 git_ls_files_cmd = [git_cmd, 'ls-files']
118 # FIXME: Use a context manager in Python 3.2+
119 popen = subprocess.Popen(git_ls_files_cmd,
120 stdout=subprocess.PIPE,
121 bufsize=1, # line buffering, since read by line
122 cwd=base_directory)
123 try:
124 try:
125 for line in popen.stdout:
126 dir_path = os.path.join(base_directory, os.path.dirname(line))
127 dir_path_norm = NormalizePath(dir_path)
128 # Add the directory as well as all the parent directories,
129 # stopping once we reach an already-listed directory.
130 while dir_path_norm not in git_source_directories:
131 git_source_directories.add(dir_path_norm)
132 dir_path_norm = posixpath.dirname(dir_path_norm)
133 finally:
134 popen.stdout.close()
135 finally:
136 popen.wait()
137
138 return git_source_directories
139
140
141 class DepsBuilder(object):
142 """Parses include_rules from DEPS files."""
143
144 def __init__(self,
145 base_directory=None,
146 extra_repos=[],
147 verbose=False,
148 being_tested=False,
149 ignore_temp_rules=False,
150 ignore_specific_rules=False):
151 """Creates a new DepsBuilder.
152
153 Args:
154 base_directory: local path to root of checkout, e.g. C:\chr\src.
155 verbose: Set to True for debug output.
156 being_tested: Set to True to ignore the DEPS file at tools/checkdeps/DEPS.
157 ignore_temp_rules: Ignore rules that start with Rule.TEMP_ALLOW ("!").
158 """
159 base_directory = (base_directory or
160 os.path.join(os.path.dirname(__file__),
161 os.path.pardir, os.path.pardir))
162 self.base_directory = os.path.abspath(base_directory) # Local absolute path
163 self.extra_repos = extra_repos
164 self.verbose = verbose
165 self._under_test = being_tested
166 self._ignore_temp_rules = ignore_temp_rules
167 self._ignore_specific_rules = ignore_specific_rules
168 self._git_source_directories = None
169
170 if os.path.exists(os.path.join(base_directory, '.git')):
171 self.is_git = True
172 elif os.path.exists(os.path.join(base_directory, '.svn')):
173 self.is_git = False
174 else:
175 raise DepsBuilderError("%s is not a repository root" % base_directory)
176
177 # Map of normalized directory paths to rules to use for those
178 # directories, or None for directories that should be skipped.
179 # Normalized is: absolute, lowercase, / for separator.
180 self.directory_rules = {}
181 self._ApplyDirectoryRulesAndSkipSubdirs(Rules(), self.base_directory)
182
183 def _ApplyRules(self, existing_rules, includes, specific_includes,
184 cur_dir_norm):
185 """Applies the given include rules, returning the new rules.
186
187 Args:
188 existing_rules: A set of existing rules that will be combined.
189 include: The list of rules from the "include_rules" section of DEPS.
190 specific_includes: E.g. {'.*_unittest\.cc': ['+foo', '-blat']} rules
191 from the "specific_include_rules" section of DEPS.
192 cur_dir_norm: The current directory, normalized path. We will create an
193 implicit rule that allows inclusion from this directory.
194
195 Returns: A new set of rules combining the existing_rules with the other
196 arguments.
197 """
198 rules = copy.deepcopy(existing_rules)
199
200 # First apply the implicit "allow" rule for the current directory.
201 base_dir_norm = NormalizePath(self.base_directory)
202 if not cur_dir_norm.startswith(base_dir_norm):
203 raise Exception(
204 'Internal error: base directory is not at the beginning for\n'
205 ' %s and base dir\n'
206 ' %s' % (cur_dir_norm, base_dir_norm))
207 relative_dir = posixpath.relpath(cur_dir_norm, base_dir_norm)
208
209 # Make the help string a little more meaningful.
210 source = relative_dir or 'top level'
211 rules.AddRule('+' + relative_dir,
212 relative_dir,
213 'Default rule for ' + source)
214
215 def ApplyOneRule(rule_str, dependee_regexp=None):
216 """Deduces a sensible description for the rule being added, and
217 adds the rule with its description to |rules|.
218
219 If we are ignoring temporary rules, this function does nothing
220 for rules beginning with the Rule.TEMP_ALLOW character.
221 """
222 if self._ignore_temp_rules and rule_str.startswith(Rule.TEMP_ALLOW):
223 return
224
225 rule_block_name = 'include_rules'
226 if dependee_regexp:
227 rule_block_name = 'specific_include_rules'
228 if relative_dir:
229 rule_description = relative_dir + "'s %s" % rule_block_name
230 else:
231 rule_description = 'the top level %s' % rule_block_name
232 rules.AddRule(rule_str, relative_dir, rule_description, dependee_regexp)
233
234 # Apply the additional explicit rules.
235 for rule_str in includes:
236 ApplyOneRule(rule_str)
237
238 # Finally, apply the specific rules.
239 if self._ignore_specific_rules:
240 return rules
241
242 for regexp, specific_rules in specific_includes.iteritems():
243 for rule_str in specific_rules:
244 ApplyOneRule(rule_str, regexp)
245
246 return rules
247
248 def _ApplyDirectoryRules(self, existing_rules, dir_path_local_abs):
249 """Combines rules from the existing rules and the new directory.
250
251 Any directory can contain a DEPS file. Top-level DEPS files can contain
252 module dependencies which are used by gclient. We use these, along with
253 additional include rules and implicit rules for the given directory, to
254 come up with a combined set of rules to apply for the directory.
255
256 Args:
257 existing_rules: The rules for the parent directory. We'll add-on to these.
258 dir_path_local_abs: The directory path that the DEPS file may live in (if
259 it exists). This will also be used to generate the
260 implicit rules. This is a local path.
261
262 Returns: A 2-tuple of:
263 (1) the combined set of rules to apply to the sub-tree,
264 (2) a list of all subdirectories that should NOT be checked, as specified
265 in the DEPS file (if any).
266 Subdirectories are single words, hence no OS dependence.
267 """
268 dir_path_norm = NormalizePath(dir_path_local_abs)
269
270 # Check the DEPS file in this directory.
271 if self.verbose:
272 print 'Applying rules from', dir_path_local_abs
273 def FromImpl(*_):
274 pass # NOP function so "From" doesn't fail.
275
276 def FileImpl(_):
277 pass # NOP function so "File" doesn't fail.
278
279 class _VarImpl:
280 def __init__(self, local_scope):
281 self._local_scope = local_scope
282
283 def Lookup(self, var_name):
284 """Implements the Var syntax."""
285 try:
286 return self._local_scope['vars'][var_name]
287 except KeyError:
288 raise Exception('Var is not defined: %s' % var_name)
289
290 local_scope = {}
291 global_scope = {
292 'File': FileImpl,
293 'From': FromImpl,
294 'Var': _VarImpl(local_scope).Lookup,
295 }
296 deps_file_path = os.path.join(dir_path_local_abs, 'DEPS')
297
298 # The second conditional here is to disregard the
299 # tools/checkdeps/DEPS file while running tests. This DEPS file
300 # has a skip_child_includes for 'testdata' which is necessary for
301 # running production tests, since there are intentional DEPS
302 # violations under the testdata directory. On the other hand when
303 # running tests, we absolutely need to verify the contents of that
304 # directory to trigger those intended violations and see that they
305 # are handled correctly.
306 if os.path.isfile(deps_file_path) and not (
307 self._under_test and
308 os.path.basename(dir_path_local_abs) == 'checkdeps'):
309 execfile(deps_file_path, global_scope, local_scope)
310 elif self.verbose:
311 print ' No deps file found in', dir_path_local_abs
312
313 # Even if a DEPS file does not exist we still invoke ApplyRules
314 # to apply the implicit "allow" rule for the current directory
315 include_rules = local_scope.get(INCLUDE_RULES_VAR_NAME, [])
316 specific_include_rules = local_scope.get(SPECIFIC_INCLUDE_RULES_VAR_NAME,
317 {})
318 skip_subdirs = local_scope.get(SKIP_SUBDIRS_VAR_NAME, [])
319
320 return (self._ApplyRules(existing_rules, include_rules,
321 specific_include_rules, dir_path_norm),
322 skip_subdirs)
323
324 def _ApplyDirectoryRulesAndSkipSubdirs(self, parent_rules,
325 dir_path_local_abs):
326 """Given |parent_rules| and a subdirectory |dir_path_local_abs| of the
327 directory that owns the |parent_rules|, add |dir_path_local_abs|'s rules to
328 |self.directory_rules|, and add None entries for any of its
329 subdirectories that should be skipped.
330 """
331 directory_rules, excluded_subdirs = self._ApplyDirectoryRules(
332 parent_rules, dir_path_local_abs)
333 dir_path_norm = NormalizePath(dir_path_local_abs)
334 self.directory_rules[dir_path_norm] = directory_rules
335 for subdir in excluded_subdirs:
336 subdir_path_norm = posixpath.join(dir_path_norm, subdir)
337 self.directory_rules[subdir_path_norm] = None
338
339 def GetAllRulesAndFiles(self, dir_name=None):
340 """Yields (rules, filenames) for each repository directory with DEPS rules.
341
342 This walks the directory tree while staying in the repository. Specify
343 |dir_name| to walk just one directory and its children; omit |dir_name| to
344 walk the entire repository.
345
346 Yields:
347 Two-element (rules, filenames) tuples. |rules| is a rules.Rules object
348 for a directory, and |filenames| is a list of the absolute local paths
349 of all files in that directory.
350 """
351 if self.is_git and self._git_source_directories is None:
352 self._git_source_directories = _GitSourceDirectories(self.base_directory)
353 for repo in self.extra_repos:
354 repo_path = os.path.join(self.base_directory, repo)
355 self._git_source_directories.update(_GitSourceDirectories(repo_path))
356
357 # Collect a list of all files and directories to check.
358 files_to_check = []
359 if dir_name and not os.path.isabs(dir_name):
360 dir_name = os.path.join(self.base_directory, dir_name)
361 dirs_to_check = [dir_name or self.base_directory]
362 while dirs_to_check:
363 current_dir = dirs_to_check.pop()
364
365 # Check that this directory is part of the source repository. This
366 # prevents us from descending into third-party code or directories
367 # generated by the build system.
368 if self.is_git:
369 if NormalizePath(current_dir) not in self._git_source_directories:
370 continue
371 elif not os.path.exists(os.path.join(current_dir, '.svn')):
372 continue
373
374 current_dir_rules = self.GetDirectoryRules(current_dir)
375
376 if not current_dir_rules:
377 continue # Handle the 'skip_child_includes' case.
378
379 current_dir_contents = sorted(os.listdir(current_dir))
380 file_names = []
381 sub_dirs = []
382 for file_name in current_dir_contents:
383 full_name = os.path.join(current_dir, file_name)
384 if os.path.isdir(full_name):
385 sub_dirs.append(full_name)
386 else:
387 file_names.append(full_name)
388 dirs_to_check.extend(reversed(sub_dirs))
389
390 yield (current_dir_rules, file_names)
391
392 def GetDirectoryRules(self, dir_path_local):
393 """Returns a Rules object to use for the given directory, or None
394 if the given directory should be skipped.
395
396 Also modifies |self.directory_rules| to store the Rules.
397 This takes care of first building rules for parent directories (up to
398 |self.base_directory|) if needed, which may add rules for skipped
399 subdirectories.
400
401 Args:
402 dir_path_local: A local path to the directory you want rules for.
403 Can be relative and unnormalized. It is the caller's responsibility
404 to ensure that this is part of the repository rooted at
405 |self.base_directory|.
406 """
407 if os.path.isabs(dir_path_local):
408 dir_path_local_abs = dir_path_local
409 else:
410 dir_path_local_abs = os.path.join(self.base_directory, dir_path_local)
411 dir_path_norm = NormalizePath(dir_path_local_abs)
412
413 if dir_path_norm in self.directory_rules:
414 return self.directory_rules[dir_path_norm]
415
416 parent_dir_local_abs = os.path.dirname(dir_path_local_abs)
417 parent_rules = self.GetDirectoryRules(parent_dir_local_abs)
418 # We need to check for an entry for our dir_path again, since
419 # GetDirectoryRules can modify entries for subdirectories, namely setting
420 # to None if they should be skipped, via _ApplyDirectoryRulesAndSkipSubdirs.
421 # For example, if dir_path == 'A/B/C' and A/B/DEPS specifies that the C
422 # subdirectory be skipped, GetDirectoryRules('A/B') will fill in the entry
423 # for 'A/B/C' as None.
424 if dir_path_norm in self.directory_rules:
425 return self.directory_rules[dir_path_norm]
426
427 if parent_rules:
428 self._ApplyDirectoryRulesAndSkipSubdirs(parent_rules, dir_path_local_abs)
429 else:
430 # If the parent directory should be skipped, then the current
431 # directory should also be skipped.
432 self.directory_rules[dir_path_norm] = None
433 return self.directory_rules[dir_path_norm]
OLDNEW
« no previous file with comments | « no previous file | checkdeps/builddeps.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698