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 class VerifyException(Exception):
23 def __init__(self
, lib_path
, expected
, unexpected
):
24 self
.expected
= expected
25 self
.unexpected
= unexpected
27 msg
= 'In %s:\n' % lib_path
29 msg
+= ' these files are missing and should be added:\n'
30 for filename
in sorted(expected
):
31 msg
+= ' %s\n' % filename
33 msg
+= ' these files no longer exist and should be removed:\n'
34 for filename
in sorted(unexpected
):
35 msg
+= ' %s\n' % filename
37 Exception.__init
__(self
, msg
)
40 def PartitionFiles(filenames
):
43 private_filenames
= set()
45 for filename
in filenames
:
46 if os
.path
.splitext(filename
)[1] not in ('.cc', '.h'):
49 parts
= filename
.split(os
.sep
)
50 if 'private' in filename
:
51 if 'flash' in filename
:
53 private_filenames
.add(filename
)
54 elif parts
[0:2] == ['ppapi', 'c']:
55 if len(parts
) >= 2 and parts
[2] in ('documentation', 'trusted'):
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'):
62 cpp_filenames
.add(filename
)
68 'ppapi_cpp': cpp_filenames
,
69 'ppapi_cpp_private': private_filenames
73 def GetDirectoryList(directory_path
, relative_to
):
75 for root
, _
, files
in os
.walk(directory_path
):
76 rel_root
= os
.path
.relpath(root
, relative_to
)
79 for base_name
in files
:
80 result
.append(os
.path
.join(rel_root
, base_name
))
84 def GetDscSourcesAndHeaders(dsc
):
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'])
93 def GetChangedAndRemovedFilenames(modified_filenames
, directory_list
):
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
)
104 removed
.add(filename
)
105 return changed
, removed
108 def GetDscFilenameFromLibraryName(lib_name
):
109 return os
.path
.join(SDK_LIBRARY_DIR
, lib_name
, 'library.dsc')
112 def Verify(dsc_filename
, dsc_sources_and_headers
, changed_filenames
,
114 expected_filenames
= set()
115 unexpected_filenames
= set()
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
)
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
)
127 if expected_filenames
or unexpected_filenames
:
128 raise VerifyException(dsc_filename
, expected_filenames
,
129 unexpected_filenames
)
132 def VerifyOrPrintError(dsc_filename
, dsc_sources_and_headers
, changed_filenames
,
133 removed_filenames
, is_private
=False):
135 Verify(dsc_filename
, dsc_sources_and_headers
, changed_filenames
,
137 except VerifyException
as e
:
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')
147 sys
.stderr
.write(str(e
) + '\n')
154 usage
= '%prog <file>...'
155 description
= __doc__
156 parser
= optparse
.OptionParser(usage
=usage
, description
=description
)
157 args
= parser
.parse_args(args
)[1]
159 parser
.error('Expected a PPAPI header or source file.')
162 lib_files
= PartitionFiles(args
)
163 directory_list
= GetDirectoryList(PPAPI_DIR
, relative_to
=SRC_DIR
)
164 for lib_name
, filenames
in lib_files
.iteritems():
168 changed_filenames
, removed_filenames
= \
169 GetChangedAndRemovedFilenames(filenames
, directory_list
)
171 dsc_filename
= GetDscFilenameFromLibraryName(lib_name
)
172 dsc
= parse_dsc
.LoadProject(dsc_filename
)
173 dsc_sources_and_headers
= GetDscSourcesAndHeaders(dsc
)
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
):
185 if __name__
== '__main__':
186 sys
.exit(main(sys
.argv
[1:]))