comparison contrib/header-tools/reduce-headers @ 111:04ced10e8804

gcc 7
author kono
date Fri, 27 Oct 2017 22:46:09 +0900
parents
children 1830386684a0
comparison
equal deleted inserted replaced
68:561a7518be6b 111:04ced10e8804
1 #! /usr/bin/python2
2 import os.path
3 import sys
4 import shlex
5 import re
6 import tempfile
7 import copy
8
9 from headerutils import *
10
11 requires = { }
12 provides = { }
13
14 no_remove = [ "system.h", "coretypes.h", "config.h" , "bconfig.h", "backend.h" ]
15
16 # These targets are the ones which provide "coverage". Typically, if any
17 # target is going to fail compilation, it's one of these. This was determined
18 # during the initial runs of reduce-headers... On a full set of target builds,
19 # every failure which occured was triggered by one of these.
20 # This list is used during target-list construction simply to put any of these
21 # *first* in the candidate list, increasing the probability that a failure is
22 # found quickly.
23 target_priority = [
24 "aarch64-linux-gnu",
25 "arm-netbsdelf",
26 "c6x-elf",
27 "epiphany-elf",
28 "hppa2.0-hpux10.1",
29 "i686-mingw32crt",
30 "i686-pc-msdosdjgpp",
31 "mipsel-elf",
32 "powerpc-eabisimaltivec",
33 "rs6000-ibm-aix5.1.0",
34 "sh-superh-elf",
35 "sparc64-elf",
36 "spu-elf"
37 ]
38
39
40 target_dir = ""
41 build_dir = ""
42 ignore_list = list()
43 target_builds = list()
44
45 target_dict = { }
46 header_dict = { }
47 search_path = [ ".", "../include", "../libcpp/include" ]
48
49 remove_count = { }
50
51
52 # Given a header name, normalize it. ie. cp/cp-tree.h could be in gcc, while
53 # the same header could be referenced from within the cp subdirectory as
54 # just cp-tree.h
55 # for now, just assume basenames are unique
56
57 def normalize_header (header):
58 return os.path.basename (header)
59
60
61 # Adds a header file and its sub-includes to the global dictionary if they
62 # aren't already there. Specify s_path since different build directories may
63 # append themselves on demand to the global list.
64 # return entry for the specified header, knowing all sub entries are completed
65
66 def get_header_info (header, s_path):
67 global header_dict
68 global empty_iinfo
69 process_list = list ()
70 location = ""
71 bname = ""
72 bname_iinfo = empty_iinfo
73 for path in s_path:
74 if os.path.exists (path + "/" + header):
75 location = path + "/" + header
76 break
77
78 if location:
79 bname = normalize_header (location)
80 if header_dict.get (bname):
81 bname_iinfo = header_dict[bname]
82 loc2 = ii_path (bname_iinfo)+ "/" + bname
83 if loc2[:2] == "./":
84 loc2 = loc2[2:]
85 if location[:2] == "./":
86 location = location[2:]
87 if loc2 != location:
88 # Don't use the cache if it isnt the right one.
89 bname_iinfo = process_ii_macro (location)
90 return bname_iinfo
91
92 bname_iinfo = process_ii_macro (location)
93 header_dict[bname] = bname_iinfo
94 # now decend into the include tree
95 for i in ii_include_list (bname_iinfo):
96 get_header_info (i, s_path)
97 else:
98 # if the file isnt in the source directories, look in the build and target
99 # directories. If it is here, then aggregate all the versions.
100 location = build_dir + "/gcc/" + header
101 build_inc = target_inc = False
102 if os.path.exists (location):
103 build_inc = True
104 for x in target_dict:
105 location = target_dict[x] + "/gcc/" + header
106 if os.path.exists (location):
107 target_inc = True
108 break
109
110 if (build_inc or target_inc):
111 bname = normalize_header(header)
112 defines = set()
113 consumes = set()
114 incl = set()
115 if build_inc:
116 iinfo = process_ii_macro (build_dir + "/gcc/" + header)
117 defines = set (ii_macro_define (iinfo))
118 consumes = set (ii_macro_consume (iinfo))
119 incl = set (ii_include_list (iinfo))
120
121 if (target_inc):
122 for x in target_dict:
123 location = target_dict[x] + "/gcc/" + header
124 if os.path.exists (location):
125 iinfo = process_ii_macro (location)
126 defines.update (ii_macro_define (iinfo))
127 consumes.update (ii_macro_consume (iinfo))
128 incl.update (ii_include_list (iinfo))
129
130 bname_iinfo = (header, "build", list(incl), list(), list(consumes), list(defines), list(), list())
131
132 header_dict[bname] = bname_iinfo
133 for i in incl:
134 get_header_info (i, s_path)
135
136 return bname_iinfo
137
138
139 # return a list of all headers brought in by this header
140 def all_headers (fname):
141 global header_dict
142 headers_stack = list()
143 headers_list = list()
144 if header_dict.get (fname) == None:
145 return list ()
146 for y in ii_include_list (header_dict[fname]):
147 headers_stack.append (y)
148
149 while headers_stack:
150 h = headers_stack.pop ()
151 hn = normalize_header (h)
152 if hn not in headers_list:
153 headers_list.append (hn)
154 if header_dict.get(hn):
155 for y in ii_include_list (header_dict[hn]):
156 if normalize_header (y) not in headers_list:
157 headers_stack.append (y)
158
159 return headers_list
160
161
162
163
164 # Search bld_dir for all target tuples, confirm that they have a build path with
165 # bld_dir/target-tuple/gcc, and build a dictionary of build paths indexed by
166 # target tuple..
167
168 def build_target_dict (bld_dir, just_these):
169 global target_dict
170 target_doct = { }
171 error = False
172 if os.path.exists (bld_dir):
173 if just_these:
174 ls = just_these
175 else:
176 ls = os.listdir(bld_dir)
177 for t in ls:
178 if t.find("-") != -1:
179 target = t.strip()
180 tpath = bld_dir + "/" + target
181 if not os.path.exists (tpath + "/gcc"):
182 print "Error: gcc build directory for target " + t + " Does not exist: " + tpath + "/gcc"
183 error = True
184 else:
185 target_dict[target] = tpath
186
187 if error:
188 target_dict = { }
189
190 def get_obj_name (src_file):
191 if src_file[-2:] == ".c":
192 return src_file.replace (".c", ".o")
193 elif src_file[-3:] == ".cc":
194 return src_file.replace (".cc", ".o")
195 return ""
196
197 def target_obj_exists (target, obj_name):
198 global target_dict
199 # look in a subdir if src has a subdir, then check gcc base directory.
200 if target_dict.get(target):
201 obj = target_dict[target] + "/gcc/" + obj_name
202 if not os.path.exists (obj):
203 obj = target_dict[target] + "/gcc/" + os.path.basename(obj_name)
204 if os.path.exists (obj):
205 return True
206 return False
207
208 # Given a src file, return a list of targets which may build this file.
209 def find_targets (src_file):
210 global target_dict
211 targ_list = list()
212 obj_name = get_obj_name (src_file)
213 if not obj_name:
214 print "Error: " + src_file + " - Cannot determine object name."
215 return list()
216
217 # Put the high priority targets which tend to trigger failures first
218 for target in target_priority:
219 if target_obj_exists (target, obj_name):
220 targ_list.append ((target, target_dict[target]))
221
222 for target in target_dict:
223 if target not in target_priority and target_obj_exists (target, obj_name):
224 targ_list.append ((target, target_dict[target]))
225
226 return targ_list
227
228
229 def try_to_remove (src_file, h_list, verbose):
230 global target_dict
231 global header_dict
232 global build_dir
233
234 # build from scratch each time
235 header_dict = { }
236 summary = ""
237 rmcount = 0
238
239 because = { }
240 src_info = process_ii_macro_src (src_file)
241 src_data = ii_src (src_info)
242 if src_data:
243 inclist = ii_include_list_non_cond (src_info)
244 # work is done if there are no includes to check
245 if not inclist:
246 return src_file + ": No include files to attempt to remove"
247
248 # work on the include list in reverse.
249 inclist.reverse()
250
251 # Get the target list
252 targ_list = list()
253 targ_list = find_targets (src_file)
254
255 spath = search_path
256 if os.path.dirname (src_file):
257 spath.append (os.path.dirname (src_file))
258
259 hostbuild = True
260 if src_file.find("config/") != -1:
261 # config files dont usually build on the host
262 hostbuild = False
263 obn = get_obj_name (os.path.basename (src_file))
264 if obn and os.path.exists (build_dir + "/gcc/" + obn):
265 hostbuild = True
266 if not target_dict:
267 summary = src_file + ": Target builds are required for config files. None found."
268 print summary
269 return summary
270 if not targ_list:
271 summary =src_file + ": Cannot find any targets which build this file."
272 print summary
273 return summary
274
275 if hostbuild:
276 # confirm it actually builds before we do anything
277 print "Confirming source file builds"
278 res = get_make_output (build_dir + "/gcc", "all")
279 if res[0] != 0:
280 message = "Error: " + src_file + " does not build currently."
281 summary = src_file + " does not build on host."
282 print message
283 print res[1]
284 if verbose:
285 verbose.write (message + "\n")
286 verbose.write (res[1]+ "\n")
287 return summary
288
289 src_requires = set (ii_macro_consume (src_info))
290 for macro in src_requires:
291 because[macro] = src_file
292 header_seen = list ()
293
294 os.rename (src_file, src_file + ".bak")
295 src_orig = copy.deepcopy (src_data)
296 src_tmp = copy.deepcopy (src_data)
297
298 try:
299 # process the includes from bottom to top. This is because we know that
300 # later includes have are known to be needed, so any dependency from this
301 # header is a true dependency
302 for inc_file in inclist:
303 inc_file_norm = normalize_header (inc_file)
304
305 if inc_file in no_remove:
306 continue
307 if len (h_list) != 0 and inc_file_norm not in h_list:
308 continue
309 if inc_file_norm[0:3] == "gt-":
310 continue
311 if inc_file_norm[0:6] == "gtype-":
312 continue
313 if inc_file_norm.replace(".h",".c") == os.path.basename(src_file):
314 continue
315
316 lookfor = ii_src_line(src_info)[inc_file]
317 src_tmp.remove (lookfor)
318 message = "Trying " + src_file + " without " + inc_file
319 print message
320 if verbose:
321 verbose.write (message + "\n")
322 out = open(src_file, "w")
323 for line in src_tmp:
324 out.write (line)
325 out.close()
326
327 keep = False
328 if hostbuild:
329 res = get_make_output (build_dir + "/gcc", "all")
330 else:
331 res = (0, "")
332
333 rc = res[0]
334 message = "Passed Host build"
335 if (rc != 0):
336 # host build failed
337 message = "Compilation failed:\n";
338 keep = True
339 else:
340 if targ_list:
341 objfile = get_obj_name (src_file)
342 t1 = targ_list[0]
343 if objfile and os.path.exists(t1[1] +"/gcc/"+objfile):
344 res = get_make_output_parallel (targ_list, objfile, 0)
345 else:
346 res = get_make_output_parallel (targ_list, "all-gcc", 0)
347 rc = res[0]
348 if rc != 0:
349 message = "Compilation failed on TARGET : " + res[2]
350 keep = True
351 else:
352 message = "Passed host and target builds"
353
354 if keep:
355 print message + "\n"
356
357 if (rc != 0):
358 if verbose:
359 verbose.write (message + "\n");
360 verbose.write (res[1])
361 verbose.write ("\n");
362 if os.path.exists (inc_file):
363 ilog = open(inc_file+".log","a")
364 ilog.write (message + " for " + src_file + ":\n\n");
365 ilog.write ("============================================\n");
366 ilog.write (res[1])
367 ilog.write ("\n");
368 ilog.close()
369 if os.path.exists (src_file):
370 ilog = open(src_file+".log","a")
371 ilog.write (message + " for " +inc_file + ":\n\n");
372 ilog.write ("============================================\n");
373 ilog.write (res[1])
374 ilog.write ("\n");
375 ilog.close()
376
377 # Given a sequence where :
378 # #include "tm.h"
379 # #include "target.h" // includes tm.h
380
381 # target.h was required, and when attempting to remove tm.h we'd see that
382 # all the macro defintions are "required" since they all look like:
383 # #ifndef HAVE_blah
384 # #define HAVE_blah
385 # endif
386
387 # when target.h was found to be required, tm.h will be tagged as included.
388 # so when we get this far, we know we dont have to check the macros for
389 # tm.h since we know it is already been included.
390
391 if inc_file_norm not in header_seen:
392 iinfo = get_header_info (inc_file, spath)
393 newlist = all_headers (inc_file_norm)
394 if ii_path(iinfo) == "build" and not target_dict:
395 keep = True
396 text = message + " : Will not remove a build file without some targets."
397 print text
398 ilog = open(src_file+".log","a")
399 ilog.write (text +"\n")
400 ilog.write ("============================================\n");
401 ilog.close()
402 ilog = open("reduce-headers-kept.log","a")
403 ilog.write (src_file + " " + text +"\n")
404 ilog.close()
405 else:
406 newlist = list()
407 if not keep and inc_file_norm not in header_seen:
408 # now look for any macro requirements.
409 for h in newlist:
410 if not h in header_seen:
411 if header_dict.get(h):
412 defined = ii_macro_define (header_dict[h])
413 for dep in defined:
414 if dep in src_requires and dep not in ignore_list:
415 keep = True;
416 text = message + ", but must keep " + inc_file + " because it provides " + dep
417 if because.get(dep) != None:
418 text = text + " Possibly required by " + because[dep]
419 print text
420 ilog = open(inc_file+".log","a")
421 ilog.write (because[dep]+": Requires [dep] in "+src_file+"\n")
422 ilog.write ("============================================\n");
423 ilog.close()
424 ilog = open(src_file+".log","a")
425 ilog.write (text +"\n")
426 ilog.write ("============================================\n");
427 ilog.close()
428 ilog = open("reduce-headers-kept.log","a")
429 ilog.write (src_file + " " + text +"\n")
430 ilog.close()
431 if verbose:
432 verbose.write (text + "\n")
433
434 if keep:
435 # add all headers 'consumes' to src_requires list, and mark as seen
436 for h in newlist:
437 if not h in header_seen:
438 header_seen.append (h)
439 if header_dict.get(h):
440 consume = ii_macro_consume (header_dict[h])
441 for dep in consume:
442 if dep not in src_requires:
443 src_requires.add (dep)
444 if because.get(dep) == None:
445 because[dep] = inc_file
446
447 src_tmp = copy.deepcopy (src_data)
448 else:
449 print message + " --> removing " + inc_file + "\n"
450 rmcount += 1
451 if verbose:
452 verbose.write (message + " --> removing " + inc_file + "\n")
453 if remove_count.get(inc_file) == None:
454 remove_count[inc_file] = 1
455 else:
456 remove_count[inc_file] += 1
457 src_data = copy.deepcopy (src_tmp)
458 except:
459 print "Interuption: restoring original file"
460 out = open(src_file, "w")
461 for line in src_orig:
462 out.write (line)
463 out.close()
464 raise
465
466 # copy current version, since it is the "right" one now.
467 out = open(src_file, "w")
468 for line in src_data:
469 out.write (line)
470 out.close()
471
472 # Try a final host bootstrap build to make sure everything is kosher.
473 if hostbuild:
474 res = get_make_output (build_dir, "all")
475 rc = res[0]
476 if (rc != 0):
477 # host build failed! return to original version
478 print "Error: " + src_file + " Failed to bootstrap at end!!! restoring."
479 print " Bad version at " + src_file + ".bad"
480 os.rename (src_file, src_file + ".bad")
481 out = open(src_file, "w")
482 for line in src_orig:
483 out.write (line)
484 out.close()
485 return src_file + ": failed to build after reduction. Restored original"
486
487 if src_data == src_orig:
488 summary = src_file + ": No change."
489 else:
490 summary = src_file + ": Reduction performed, "+str(rmcount)+" includes removed."
491 print summary
492 return summary
493
494 only_h = list ()
495 ignore_cond = False
496
497 usage = False
498 src = list()
499 only_targs = list ()
500 for x in sys.argv[1:]:
501 if x[0:2] == "-b":
502 build_dir = x[2:]
503 elif x[0:2] == "-f":
504 fn = normalize_header (x[2:])
505 if fn not in only_h:
506 only_h.append (fn)
507 elif x[0:2] == "-h":
508 usage = True
509 elif x[0:2] == "-d":
510 ignore_cond = True
511 elif x[0:2] == "-D":
512 ignore_list.append(x[2:])
513 elif x[0:2] == "-T":
514 only_targs.append(x[2:])
515 elif x[0:2] == "-t":
516 target_dir = x[2:]
517 elif x[0] == "-":
518 print "Error: Unrecognized option " + x
519 usgae = True
520 else:
521 if not os.path.exists (x):
522 print "Error: specified file " + x + " does not exist."
523 usage = True
524 else:
525 src.append (x)
526
527 if target_dir:
528 build_target_dict (target_dir, only_targs)
529
530 if build_dir == "" and target_dir == "":
531 print "Error: Must specify a build directory, and/or a target directory."
532 usage = True
533
534 if build_dir and not os.path.exists (build_dir):
535 print "Error: specified build directory does not exist : " + build_dir
536 usage = True
537
538 if target_dir and not os.path.exists (target_dir):
539 print "Error: specified target directory does not exist : " + target_dir
540 usage = True
541
542 if usage:
543 print "Attempts to remove extraneous include files from source files."
544 print " "
545 print "Should be run from the main gcc source directory, and works on a target"
546 print "directory, as we attempt to make the 'all' target."
547 print " "
548 print "By default, gcc-reorder-includes is run on each file before attempting"
549 print "to remove includes. this removes duplicates and puts some headers in a"
550 print "canonical ordering"
551 print " "
552 print "The build directory should be ready to compile via make. Time is saved"
553 print "if the build is already complete, so that only changes need to be built."
554 print " "
555 print "Usage: [options] file1.c [file2.c] ... [filen.c]"
556 print " -bdir : the root build directory to attempt buiding .o files."
557 print " -tdir : the target build directory"
558 print " -d : Ignore conditional macro dependencies."
559 print " "
560 print " -Dmacro : Ignore a specific macro for dependencies"
561 print " -Ttarget : Only consider target in target directory."
562 print " -fheader : Specifies a specific .h file to be considered."
563 print " "
564 print " -D, -T, and -f can be specified mulitple times and are aggregated."
565 print " "
566 print " The original file will be in filen.bak"
567 print " "
568 sys.exit (0)
569
570 if only_h:
571 print "Attempting to remove only these files:"
572 for x in only_h:
573 print x
574 print " "
575
576 logfile = open("reduce-headers.log","w")
577
578 for x in src:
579 msg = try_to_remove (x, only_h, logfile)
580 ilog = open("reduce-headers.sum","a")
581 ilog.write (msg + "\n")
582 ilog.close()
583
584 ilog = open("reduce-headers.sum","a")
585 ilog.write ("===============================================================\n")
586 for x in remove_count:
587 msg = x + ": Removed " + str(remove_count[x]) + " times."
588 print msg
589 logfile.write (msg + "\n")
590 ilog.write (msg + "\n")
591
592
593
594
595