- add sources.
[platform/framework/web/crosswalk.git] / src / native_client_sdk / src / build_tools / verify_ppapi.py
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
6 """Helper script for PPAPI's PRESUBMIT.py to detect if additions or removals of
7 PPAPI interfaces have been propagated to the Native Client libraries (.dsc
8 files).
9
10 For example, if a user adds "ppapi/c/foo.h", we check that the interface has
11 been added to "native_client_sdk/src/libraries/ppapi/library.dsc".
12 """
13
14 import optparse
15 import os
16 import sys
17
18 from build_paths import PPAPI_DIR, SRC_DIR, SDK_LIBRARY_DIR
19 import parse_dsc
20
21
22 class VerifyException(Exception):
23   def __init__(self, lib_path, expected, unexpected):
24     self.expected = expected
25     self.unexpected = unexpected
26
27     msg = 'In %s:\n' % lib_path
28     if expected:
29       msg += '  these files are missing and should be added:\n'
30       for filename in sorted(expected):
31         msg += '    %s\n' % filename
32     if unexpected:
33       msg += '  these files no longer exist and should be removed:\n'
34       for filename in sorted(unexpected):
35         msg += '    %s\n' % filename
36
37     Exception.__init__(self, msg)
38
39
40 def PartitionFiles(filenames):
41   c_filenames = set()
42   cpp_filenames = set()
43   private_filenames = set()
44
45   for filename in filenames:
46     if os.path.splitext(filename)[1] not in ('.cc', '.h'):
47       continue
48
49     parts = filename.split(os.sep)
50     if 'private' in filename:
51       if 'flash' in filename:
52         continue
53       private_filenames.add(filename)
54     elif parts[0:2] == ['ppapi', 'c']:
55       if len(parts) >= 2 and parts[2] in ('documentation', 'trusted'):
56         continue
57       c_filenames.add(filename)
58     elif (parts[0:2] == ['ppapi', 'cpp'] or
59           parts[0:2] == ['ppapi', 'utility']):
60       if len(parts) >= 2 and parts[2] in ('documentation', 'trusted'):
61         continue
62       cpp_filenames.add(filename)
63     else:
64       continue
65
66   return {
67       'ppapi': c_filenames,
68       'ppapi_cpp': cpp_filenames,
69       'ppapi_cpp_private': private_filenames
70   }
71
72
73 def GetDirectoryList(directory_path, relative_to):
74   result = []
75   for root, _, files in os.walk(directory_path):
76     rel_root = os.path.relpath(root, relative_to)
77     if rel_root == '.':
78       rel_root = ''
79     for base_name in files:
80       result.append(os.path.join(rel_root, base_name))
81   return result
82
83
84 def GetDscSourcesAndHeaders(dsc):
85   result = []
86   for headers_info in dsc.get('HEADERS', []):
87     result.extend(headers_info['FILES'])
88   for targets_info in dsc.get('TARGETS', []):
89     result.extend(targets_info['SOURCES'])
90   return result
91
92
93 def GetChangedAndRemovedFilenames(modified_filenames, directory_list):
94   changed = set()
95   removed = set()
96   directory_list_set = set(directory_list)
97   for filename in modified_filenames:
98     if filename in directory_list_set:
99       # We can't know if a file was added (that would require knowing the
100       # previous state of the working directory). Instead, we assume that a
101       # changed file may have been added, and check it accordingly.
102       changed.add(filename)
103     else:
104       removed.add(filename)
105   return changed, removed
106
107
108 def GetDscFilenameFromLibraryName(lib_name):
109   return os.path.join(SDK_LIBRARY_DIR, lib_name, 'library.dsc')
110
111
112 def Verify(dsc_filename, dsc_sources_and_headers, changed_filenames,
113            removed_filenames):
114   expected_filenames = set()
115   unexpected_filenames = set()
116
117   for filename in changed_filenames:
118     basename = os.path.basename(filename)
119     if basename not in dsc_sources_and_headers:
120       expected_filenames.add(filename)
121
122   for filename in removed_filenames:
123     basename = os.path.basename(filename)
124     if basename in dsc_sources_and_headers:
125       unexpected_filenames.add(filename)
126
127   if expected_filenames or unexpected_filenames:
128     raise VerifyException(dsc_filename, expected_filenames,
129                           unexpected_filenames)
130
131
132 def VerifyOrPrintError(dsc_filename, dsc_sources_and_headers, changed_filenames,
133                        removed_filenames, is_private=False):
134   try:
135     Verify(dsc_filename, dsc_sources_and_headers, changed_filenames,
136            removed_filenames)
137   except VerifyException as e:
138     should_fail = True
139     if is_private and e.expected:
140       # For ppapi_cpp_private, we don't fail if there are expected filenames...
141       # we may not want to include them. We still want to fail if there are
142       # unexpected filenames, though.
143       sys.stderr.write('>>> WARNING: private interface files changed. '
144           'Should they be added to the Native Client SDK? <<<\n')
145       if not e.unexpected:
146         should_fail = False
147     sys.stderr.write(str(e) + '\n')
148     if should_fail:
149       return False
150   return True
151
152
153 def main(args):
154   usage = '%prog <file>...'
155   description = __doc__
156   parser = optparse.OptionParser(usage=usage, description=description)
157   args = parser.parse_args(args)[1]
158   if not args:
159     parser.error('Expected a PPAPI header or source file.')
160
161   retval = 0
162   lib_files = PartitionFiles(args)
163   directory_list = GetDirectoryList(PPAPI_DIR, relative_to=SRC_DIR)
164   for lib_name, filenames in lib_files.iteritems():
165     if not filenames:
166       continue
167
168     changed_filenames, removed_filenames = \
169         GetChangedAndRemovedFilenames(filenames, directory_list)
170
171     dsc_filename = GetDscFilenameFromLibraryName(lib_name)
172     dsc = parse_dsc.LoadProject(dsc_filename)
173     dsc_sources_and_headers = GetDscSourcesAndHeaders(dsc)
174
175     # Use the relative path to the .dsc to make the error messages shorter.
176     rel_dsc_filename = os.path.relpath(dsc_filename, SRC_DIR)
177     is_private = lib_name == 'ppapi_cpp_private'
178     if not VerifyOrPrintError(rel_dsc_filename, dsc_sources_and_headers,
179                               changed_filenames, removed_filenames,
180                               is_private=is_private):
181       retval = 1
182   return retval
183
184
185 if __name__ == '__main__':
186   sys.exit(main(sys.argv[1:]))