4 # Look for and removes unnecessary includes in .cpp or .c files
5 # Run from wireshark source folder as e.g.,
6 # ./tools/delete_includes.py --build-folder ~/wireshark-build/ --folder epan/dissectors/
8 # Wireshark - Network traffic analyzer
9 # By Gerald Combs <gerald@wireshark.org>
10 # Copyright 1998 Gerald Combs
12 # SPDX-License-Identifier: GPL-2.0-or-later
21 from pathlib
import Path
24 # Try to exit soon after Ctrl-C is pressed.
27 def signal_handler(sig
, frame
):
30 print('You pressed Ctrl+C - exiting')
32 signal
.signal(signal
.SIGINT
, signal_handler
)
34 # For text colouring/highlighting.
50 # Controls which dissector files should be checked. If no args given, will just
51 # scan whole epan/dissectors folder.
52 parser
= argparse
.ArgumentParser(description
='Check calls in dissectors')
54 parser
.add_argument('--build-folder', action
='store', required
=True,
55 help='specify individual dissector file to test')
56 parser
.add_argument('--file', action
='append',
57 help='specify individual dissector file to test')
58 parser
.add_argument('--folder', action
='store', default
=os
.path
.join('epan', 'dissectors'),
59 help='specify folder to test, relative to current/wireshark folder')
60 parser
.add_argument('--commits', action
='store',
61 help='last N commits to check')
62 parser
.add_argument('--open', action
='store_true',
63 help='check open files')
64 parser
.add_argument('--first-file', action
='store',
65 help='first file in folder to test')
66 parser
.add_argument('--last-file', action
='store',
67 help='last file in folder to test')
68 args
= parser
.parse_args()
71 test_folder
= os
.path
.join(os
.getcwd(), args
.folder
)
74 # Usually only building one module, so no -j benefit?
75 make_command
= ['cmake', '--build', args
.build_folder
]
76 if sys
.platform
.startswith('win'):
77 make_command
+= ['--config', 'RelWithDebInfo']
81 # A list of header files that it is not safe to uninclude, as doing so
82 # has been seen to cause link failures against implemented functions...
83 # TODO: some of these could probably be removed on more permissive platforms.
92 # These are probably mostly redundant in that they are now covered by the check
93 # for 'self-includes'...
106 self
.files_examined
= 0
107 self
.includes_tested
= 0
108 self
.includes_deleted
= 0
109 self
.files_not_built_list
= []
110 self
.generated_files_ignored
= []
111 self
.includes_to_keep_kept
= 0
113 def showSummary(self
):
117 print('files examined: %d' % self
.files_examined
)
118 print('includes tested: %d' % self
.includes_tested
)
119 print('includes deleted: %d' % self
.includes_deleted
)
120 print('files not built: %d' % len(self
.files_not_built_list
))
121 for abandoned_file
in self
.files_not_built_list
:
122 print(' %s' % abandoned_file
)
123 print('generated files not tested: %d' % len(self
.generated_files_ignored
))
124 for generated_file
in self
.generated_files_ignored
:
125 print(' %s' % generated_file
)
126 print('includes kept as not safe to remove: %d' % self
.includes_to_keep_kept
)
131 # We want to confirm that this file is actually built as part of the build.
132 # To do this, add some nonsense to the front of the file and confirm that the
133 # build then fails. If it doesn't, won't want to remove #includes from that file!
134 def test_file_is_built(filename
):
135 print('test_file_is_built(', filename
, ')')
136 temp_filename
= filename
+ '.tmp'
138 f_read
= open(filename
, 'r')
139 write_filename
= filename
+ '.new'
140 f_write
= open(write_filename
, 'w')
141 # Write the file with nonsense at start.
142 f_write
.write('NO WAY THIS FILE BUILDS!!!!!')
143 # Copy remaining lines as-is.
148 # Backup file, and do this build with the one we wrote.
149 shutil
.copy(filename
, temp_filename
)
150 shutil
.copy(write_filename
, filename
)
153 result
= subprocess
.call(make_command
)
154 # Restore proper file & delete temp files
155 shutil
.copy(temp_filename
, filename
)
156 os
.remove(temp_filename
)
157 os
.remove(write_filename
)
160 # Build succeeded so this file wasn't in it
163 # Build failed so this file *is* part of it
167 # Function to test removal of each #include from a file in turn.
168 # At the end, only those that appear to be needed will be left.
169 def test_file(filename
):
172 print('\n------------------------------')
173 print(bcolors
.OKBLUE
, bcolors
.BOLD
, 'Testing', filename
, bcolors
.ENDC
)
175 temp_filename
= filename
+ '.tmp'
177 # Test if file seems to be part of the build.
178 is_built
= test_file_is_built(filename
)
180 print(bcolors
.WARNING
, '***** File not used in build, so ignore!!!!', bcolors
.ENDC
)
181 # TODO: should os.path.join with root before adding?
182 stats
.files_not_built_list
.append(filename
)
185 print('This file is part of the build')
187 # OK, we are going to test removing includes from this file.
188 tested_line_number
= 0
190 # Don't want to delete 'self-includes', so prepare filename.
191 module_name
= Path(filename
).stem
192 module_header
= module_name
+ '.h'
194 # Loop around, finding all possible include lines to comment out
199 have_deleted_line
= False
202 # Open read & write files
203 f_read
= open(filename
, 'r')
204 write_filename
= filename
+ '.new'
205 f_write
= open(write_filename
, 'w')
207 # Walk the file again looking for another place to comment out an include
212 this_line_deleted
= False
214 # Maintain view of how many #if or #ifdefs we are in.
215 # Don't want to remove any includes that may not be active in this build.
216 if line
.startswith('#if'):
217 hash_if_level
= hash_if_level
+ 1
219 if line
.startswith('#endif'):
220 if hash_if_level
> 1:
221 hash_if_level
= hash_if_level
- 1
223 # Consider deleting this line have haven't already reached.
224 if (not have_deleted_line
and (tested_line_number
< this_line_number
)):
226 # Test line for starting with #include, and eligible for deletion.
227 if line
.startswith('#include ') and hash_if_level
== 0 and line
.find(module_header
) == -1:
228 # Check that this isn't a header file that known unsafe to uninclude.
229 allowed_to_delete
= True
230 for entry
in includes_to_keep
:
231 if line
.find(entry
) != -1:
232 allowed_to_delete
= False
233 stats
.includes_to_keep_kept
+= 1
236 if allowed_to_delete
:
237 # OK, actually doing it.
238 have_deleted_line
= True
239 this_line_deleted
= True
240 tested_line_number
= this_line_number
242 # Write line to output file, unless this very one was deleted.
243 if not this_line_deleted
:
245 this_line_number
= this_line_number
+ 1
251 # If we commented out a line, try to build file without it.
252 if (have_deleted_line
):
253 # Test a build. 0 means success, others are failures.
254 shutil
.copy(filename
, temp_filename
)
255 shutil
.copy(write_filename
, filename
)
258 result
= subprocess
.call(make_command
)
260 print(bcolors
.OKGREEN
+bcolors
.BOLD
+ 'Good build' + bcolors
.ENDC
)
261 # Line was eliminated so decrement line counter
262 tested_line_number
= tested_line_number
- 1
263 # Inc successes counter
264 stats
.includes_deleted
+= 1
265 # Good - promote this version by leaving it here!
267 # Occasionally fails so delete this file each time.
268 # TODO: this is very particular to dissector target...
269 if sys
.argv
[1] == 'dissectors':
270 os
.remove(os
.path
.join(args
.build_folder
, 'vc100.pdb'))
272 print(bcolors
.FAIL
+bcolors
.BOLD
+ 'Bad build' + bcolors
.ENDC
)
273 # Never mind, go back to previous building version
274 shutil
.copy(temp_filename
, filename
)
276 # Inc counter of tried
277 stats
.includes_tested
+= 1
280 # Reached the end of the file without making changes, so nothing doing.
281 # Delete temporary files
282 if os
.path
.isfile(temp_filename
):
283 os
.remove(temp_filename
)
284 if os
.path
.isfile(write_filename
):
285 os
.remove(write_filename
)
288 # Test for whether a the given file is under source control
289 def under_version_control(filename
):
290 # TODO: git command to see if under version control. Check retcode of 'git log <filename>' ?
293 # Test for whether the given file was automatically generated.
294 def generated_file(filename
):
295 # Special known case.
296 if filename
== 'register.c':
300 f_read
= open(filename
, 'r')
303 # The comment to say that its generated is near the top, so give up once
304 # get a few lines down.
305 if lines_tested
> 10:
308 if (line
.find('Generated automatically') != -1 or
309 line
.find('Generated Automatically') != -1 or
310 line
.find('Autogenerated from') != -1 or
311 line
.find('is autogenerated') != -1 or
312 line
.find('automatically generated by Pidl') != -1 or
313 line
.find('Created by: The Qt Meta Object Compiler') != -1 or
314 line
.find('This file was generated') != -1 or
315 line
.find('This filter was automatically generated') != -1 or
316 line
.find('This file is auto generated, do not edit!') != -1):
320 lines_tested
= lines_tested
+ 1
322 # OK, looks like a hand-written file!
326 def isBuildableFile(filename
):
327 return filename
.endswith('.c') or filename
.endswith('.cpp')
330 def findFilesInFolder(folder
, recursive
=False):
334 for root
, subfolders
, files
in os
.walk(folder
):
338 f
= os
.path
.join(root
, f
)
339 dissector_files
.append(f
)
341 for f
in sorted(os
.listdir(folder
)):
344 filename
= os
.path
.join(folder
, f
)
345 dissector_files
.append(filename
)
347 return [x
for x
in filter(isBuildableFile
, dissector_files
)]
350 ######################################################################################
351 # MAIN PROGRAM STARTS HERE
352 ######################################################################################
354 # Work out which files we want to look at.
357 # Add specified file(s)
359 if not os
.path
.isfile(f
):
360 print('Chosen file', f
, 'does not exist.')
365 # Add all files from a given folder.
367 if not os
.path
.isdir(folder
):
368 print('Folder', folder
, 'not found!')
370 # Find files from folder.
371 print('Looking for files in', folder
)
372 files
= findFilesInFolder(folder
, recursive
=False)
375 # If first-file/last-file are given, will need to trim files accordingly
377 idx
= files
.index(args
.first_file
)
379 print('first-file entry', args
.first_file
, 'not in list of files to be checked')
385 idx
= files
.index(args
.last_file
)
387 print('last-file entry', args
.last_file
, 'not in list of files to be checked')
390 files
= files
[:idx
+1]
393 # Confirm that the build is currently passing, if not give up now.
394 print(bcolors
.OKBLUE
,bcolors
.BOLD
,
395 'Doing an initial build to check we have a stable base.',
397 result
= subprocess
.call(make_command
)
399 print(bcolors
.FAIL
, bcolors
.BOLD
, 'Initial build failed - give up now!!!!', bcolors
.ENDC
)
405 for filename
in files
:
407 # Want to filter out generated files that are not checked in.
408 if not generated_file(filename
) and under_version_control(filename
):
413 stats
.files_examined
+= 1
415 if generated_file(filename
):
416 reason
= 'generated file...'
417 if not under_version_control(filename
):
418 reason
= 'not under source control'
419 print('Ignoring %s: %s' % (filename
, reason
))
423 # Show summary stats of run