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