verify_filelist.py revision 7dbb3d5cf0c15f500944d211057644d6a2f37371
1#!/usr/bin/env python
2# Copyright (c) 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
6import optparse
7import os
8import re
9import sys
10
11from build_paths import SDK_SRC_DIR
12
13# Add SDK make tools scripts to the python path.
14sys.path.append(os.path.join(SDK_SRC_DIR, 'tools'))
15
16import getos
17
18VALID_PLATFORMS = ['linux', 'mac', 'win']
19PLATFORM_PREFIX_RE = re.compile(r'^\[([^\]]*)\](.*)$')
20
21class ParseException(Exception):
22  def __init__(self, filename, line, message):
23    Exception.__init__(self)
24    self.filename = filename
25    self.line = line
26    self.message = message
27
28  def __str__(self):
29    return '%s:%d: %s' % (self.filename, self.line, self.message)
30
31
32def SplitPattern(pattern):
33  match = PLATFORM_PREFIX_RE.match(pattern)
34  if not match:
35    return pattern, []
36
37  # platform-specific line
38  platforms = match.group(1).split(',')
39
40  # If this platform is included, strip the [...] part.
41  pattern = match.group(2)
42  return pattern, platforms
43
44
45class VerifyException(Exception):
46  pass
47
48class Rules(object):
49  def __init__(self, filename, platform=None, contents=None):
50    self.glob_prefixes = []
51    self.exact_filenames = set()
52    self.filename = filename
53    self.platform = platform or getos.GetPlatform()
54    self.exe_ext = '.exe' if self.platform == 'win' else ''
55
56    if self.platform not in VALID_PLATFORMS:
57      raise ParseException(self.filename, 1,
58                           'Unknown platform %s' % self.platform)
59
60    if not contents:
61      with open(filename) as f:
62        contents = f.read()
63
64    for line_no, rule in enumerate(contents.split('\n')):
65      rule = rule.strip()
66      if rule:
67        self.ParsePattern(line_no + 1, rule)
68
69  def ParsePattern(self, line_no, pattern):
70    pattern, platforms = SplitPattern(pattern)
71    if platforms:
72      unknown_platforms = set(platforms) - set(VALID_PLATFORMS)
73      if unknown_platforms:
74        msg = 'Unknown platform(s) %s.' % (
75            ', '.join('"%s"' % platform for platform in unknown_platforms))
76        raise ParseException(self.filename, line_no, msg)
77      if self.platform not in platforms:
78        return
79
80    pattern = pattern.replace('${PLATFORM}', self.platform)
81    pattern = pattern.replace('${EXE_EXT}', self.exe_ext)
82
83    if '*' in pattern:
84      # glob pattern
85      # We only support * at the end.
86      if pattern.find('*') != len(pattern) - 1:
87        msg = '* is only allowed at the end of the line.'
88        raise ParseException(self.filename, line_no, msg)
89
90      # Remove the *
91      pattern = pattern[:-1]
92      self.glob_prefixes.append(pattern)
93    else:
94      self.exact_filenames.add(pattern)
95
96  def VerifyDirectoryList(self, directory_list):
97    exact_filenames_used = set()
98    glob_prefixes_used = set()
99    expected_globs = set()
100    expected_filenames = set()
101    unexpected_filenames = set()
102
103    for filename in directory_list:
104      if os.path.sep != '/':
105        filename = filename.replace(os.path.sep, '/')
106      if filename in self.exact_filenames:
107        exact_filenames_used.add(filename)
108        continue
109
110      # glob pattern
111      found_prefix = False
112      for prefix in self.glob_prefixes:
113        if filename.startswith(prefix):
114          glob_prefixes_used.add(prefix)
115          found_prefix = True
116          break
117
118      if not found_prefix:
119        unexpected_filenames.add(filename)
120
121    if len(exact_filenames_used) != len(self.exact_filenames):
122      # We looped through the directory list, so if the lengths are unequal, it
123      # must be that we expected something that isn't there.
124      expected_filenames = self.exact_filenames - exact_filenames_used
125
126    if len(glob_prefixes_used) != self.glob_prefixes:
127      expected_globs = set(self.glob_prefixes) - glob_prefixes_used
128
129    if expected_filenames or unexpected_filenames or expected_globs:
130      msg = ''
131      if unexpected_filenames:
132        msg += '>>> Unexpected filenames: <<<\n%s\n' % (
133            '\n'.join(sorted(unexpected_filenames)))
134      if expected_filenames:
135        msg += '>>> Expected filenames: <<<\n%s\n' % (
136            '\n'.join(sorted(expected_filenames)))
137      if expected_globs:
138        msg += '>>> Expected 1+ files in these directories: <<< \n%s\n' % (
139            '\n'.join(sorted(expected_globs)))
140      raise VerifyException(msg)
141
142
143def GetDirectoryList(directory_path):
144  result = []
145  for root, _, files in os.walk(directory_path):
146    rel_root = os.path.relpath(root, directory_path)
147    if rel_root == '.':
148      rel_root = ''
149    for base_name in files:
150      result.append(os.path.join(rel_root, base_name))
151  return result
152
153
154def Verify(rule_path, directory_path, platform=None):
155  rules = Rules(rule_path, platform=platform)
156  directory_list = GetDirectoryList(directory_path)
157  rules.VerifyDirectoryList(directory_list)
158
159
160def SortFile(rule_path):
161  with open(rule_path) as infile:
162    lines = infile.readlines()
163
164  def compare(line1, line2):
165    line1 = SplitPattern(line1)[0].lower()
166    line2 = SplitPattern(line2)[0].lower()
167    return cmp(line1, line2)
168
169  lines.sort(compare)
170  with open(rule_path, 'w') as output:
171    for line in lines:
172      output.write(line)
173
174
175def main(args):
176  parser = optparse.OptionParser(usage='%prog <rule file> <directory>')
177  parser.add_option('-p', '--platform',
178      help='Test with this platform, instead of the system\'s platform')
179  parser.add_option('-s', '--sort', action='store_true',
180      help='Sort the file list in place, rather than verifying the contents.')
181  options, args = parser.parse_args(args)
182
183  if options.sort:
184    if not args:
185      parser.error('Expected rule file.')
186    SortFile(args[0])
187    return 0
188
189  if len(args) != 2:
190    parser.error('Expected rule file and directory.')
191
192  rule_path, directory_path = args
193  if options.platform:
194    if options.platform not in VALID_PLATFORMS:
195      parser.error('Unknown platform: %s' % options.platform)
196    platform = options.platform
197  else:
198    platform = getos.GetPlatform()
199
200  try:
201    return Verify(rule_path, directory_path, platform)
202  except ParseException, e:
203    print >> sys.stderr, 'Error parsing rules:\n', e
204    return 1
205  except VerifyException, e:
206    print >> sys.stderr, 'Error verifying file list:\n', e
207    return 1
208  return 0
209
210
211if __name__ == '__main__':
212  sys.exit(main(sys.argv[1:]))
213