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.
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
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".
18 from build_paths
import PPAPI_DIR
, SRC_DIR
, SDK_LIBRARY_DIR
22 # Add a file to this list if it should not be added to a .dsc file; i.e. if it
23 # should not be included in the Native Client SDK. This will silence the
26 # Some examples of files that should not be added to the SDK are: Dev and
27 # Private interfaces that are either not available to NaCl plugins or are only
28 # available to Flash or other privileged plugins.
34 class VerifyException(Exception):
35 def __init__(self
, lib_path
, expected
, unexpected
):
36 self
.expected
= expected
37 self
.unexpected
= unexpected
39 msg
= 'In %s:\n' % lib_path
41 msg
+= ' these files are missing and should be added:\n'
42 for filename
in sorted(expected
):
43 msg
+= ' %s\n' % filename
45 msg
+= ' these files no longer exist and should be removed:\n'
46 for filename
in sorted(unexpected
):
47 msg
+= ' %s\n' % filename
49 Exception.__init
__(self
, msg
)
52 def PartitionFiles(filenames
):
55 private_filenames
= set()
57 for filename
in filenames
:
58 if os
.path
.splitext(filename
)[1] not in ('.cc', '.h'):
61 parts
= filename
.split(os
.sep
)
62 basename
= os
.path
.basename(filename
)
63 if basename
in IGNORED_FILES
:
66 if 'private' in filename
:
67 if 'flash' in filename
:
69 private_filenames
.add(filename
)
70 elif parts
[0:2] == ['ppapi', 'c']:
71 if len(parts
) >= 2 and parts
[2] in ('documentation', 'trusted'):
73 c_filenames
.add(filename
)
74 elif (parts
[0:2] == ['ppapi', 'cpp'] or
75 parts
[0:2] == ['ppapi', 'utility']):
76 if len(parts
) >= 2 and parts
[2] in ('documentation', 'trusted'):
78 cpp_filenames
.add(filename
)
84 'ppapi_cpp': cpp_filenames
,
85 'ppapi_cpp_private': private_filenames
89 def GetDirectoryList(directory_path
, relative_to
):
91 for root
, _
, files
in os
.walk(directory_path
):
92 rel_root
= os
.path
.relpath(root
, relative_to
)
95 for base_name
in files
:
96 result
.append(os
.path
.join(rel_root
, base_name
))
100 def GetDscSourcesAndHeaders(dsc
):
102 for headers_info
in dsc
.get('HEADERS', []):
103 result
.extend(headers_info
['FILES'])
104 for targets_info
in dsc
.get('TARGETS', []):
105 result
.extend(targets_info
['SOURCES'])
109 def GetChangedAndRemovedFilenames(modified_filenames
, directory_list
):
112 directory_list_set
= set(directory_list
)
113 for filename
in modified_filenames
:
114 if filename
in directory_list_set
:
115 # We can't know if a file was added (that would require knowing the
116 # previous state of the working directory). Instead, we assume that a
117 # changed file may have been added, and check it accordingly.
118 changed
.add(filename
)
120 removed
.add(filename
)
121 return changed
, removed
124 def GetDscFilenameFromLibraryName(lib_name
):
125 return os
.path
.join(SDK_LIBRARY_DIR
, lib_name
, 'library.dsc')
128 def Verify(dsc_filename
, dsc_sources_and_headers
, changed_filenames
,
130 expected_filenames
= set()
131 unexpected_filenames
= set()
133 for filename
in changed_filenames
:
134 basename
= os
.path
.basename(filename
)
135 if basename
not in dsc_sources_and_headers
:
136 expected_filenames
.add(filename
)
138 for filename
in removed_filenames
:
139 basename
= os
.path
.basename(filename
)
140 if basename
in dsc_sources_and_headers
:
141 unexpected_filenames
.add(filename
)
143 if expected_filenames
or unexpected_filenames
:
144 raise VerifyException(dsc_filename
, expected_filenames
,
145 unexpected_filenames
)
148 def VerifyOrPrintError(dsc_filename
, dsc_sources_and_headers
, changed_filenames
,
149 removed_filenames
, is_private
=False):
151 Verify(dsc_filename
, dsc_sources_and_headers
, changed_filenames
,
153 except VerifyException
as e
:
155 if is_private
and e
.expected
:
156 # For ppapi_cpp_private, we don't fail if there are expected filenames...
157 # we may not want to include them. We still want to fail if there are
158 # unexpected filenames, though.
159 sys
.stderr
.write('>>> WARNING: private interface files changed. '
160 'Should they be added to the Native Client SDK? <<<\n')
163 sys
.stderr
.write(str(e
) + '\n')
170 usage
= '%prog <file>...'
171 description
= __doc__
172 parser
= optparse
.OptionParser(usage
=usage
, description
=description
)
173 args
= parser
.parse_args(args
)[1]
175 parser
.error('Expected a PPAPI header or source file.')
178 lib_files
= PartitionFiles(args
)
179 directory_list
= GetDirectoryList(PPAPI_DIR
, relative_to
=SRC_DIR
)
180 for lib_name
, filenames
in lib_files
.iteritems():
184 changed_filenames
, removed_filenames
= \
185 GetChangedAndRemovedFilenames(filenames
, directory_list
)
187 dsc_filename
= GetDscFilenameFromLibraryName(lib_name
)
188 dsc
= parse_dsc
.LoadProject(dsc_filename
)
189 dsc_sources_and_headers
= GetDscSourcesAndHeaders(dsc
)
191 # Use the relative path to the .dsc to make the error messages shorter.
192 rel_dsc_filename
= os
.path
.relpath(dsc_filename
, SRC_DIR
)
193 is_private
= lib_name
== 'ppapi_cpp_private'
194 if not VerifyOrPrintError(rel_dsc_filename
, dsc_sources_and_headers
,
195 changed_filenames
, removed_filenames
,
196 is_private
=is_private
):
201 if __name__
== '__main__':
202 sys
.exit(main(sys
.argv
[1:]))