596 lines
19 KiB
Plaintext
596 lines
19 KiB
Plaintext
|
#! /usr/bin/python2
|
||
|
import os.path
|
||
|
import sys
|
||
|
import shlex
|
||
|
import re
|
||
|
import tempfile
|
||
|
import copy
|
||
|
|
||
|
from headerutils import *
|
||
|
|
||
|
requires = { }
|
||
|
provides = { }
|
||
|
|
||
|
no_remove = [ "system.h", "coretypes.h", "config.h" , "bconfig.h", "backend.h" ]
|
||
|
|
||
|
# These targets are the ones which provide "coverage". Typically, if any
|
||
|
# target is going to fail compilation, it's one of these. This was determined
|
||
|
# during the initial runs of reduce-headers... On a full set of target builds,
|
||
|
# every failure which occured was triggered by one of these.
|
||
|
# This list is used during target-list construction simply to put any of these
|
||
|
# *first* in the candidate list, increasing the probability that a failure is
|
||
|
# found quickly.
|
||
|
target_priority = [
|
||
|
"aarch64-linux-gnu",
|
||
|
"arm-netbsdelf",
|
||
|
"c6x-elf",
|
||
|
"epiphany-elf",
|
||
|
"hppa2.0-hpux10.1",
|
||
|
"i686-mingw32crt",
|
||
|
"i686-pc-msdosdjgpp",
|
||
|
"mipsel-elf",
|
||
|
"powerpc-eabisimaltivec",
|
||
|
"rs6000-ibm-aix5.1.0",
|
||
|
"sh-superh-elf",
|
||
|
"sparc64-elf",
|
||
|
"spu-elf"
|
||
|
]
|
||
|
|
||
|
|
||
|
target_dir = ""
|
||
|
build_dir = ""
|
||
|
ignore_list = list()
|
||
|
target_builds = list()
|
||
|
|
||
|
target_dict = { }
|
||
|
header_dict = { }
|
||
|
search_path = [ ".", "../include", "../libcpp/include" ]
|
||
|
|
||
|
remove_count = { }
|
||
|
|
||
|
|
||
|
# Given a header name, normalize it. ie. cp/cp-tree.h could be in gcc, while
|
||
|
# the same header could be referenced from within the cp subdirectory as
|
||
|
# just cp-tree.h
|
||
|
# for now, just assume basenames are unique
|
||
|
|
||
|
def normalize_header (header):
|
||
|
return os.path.basename (header)
|
||
|
|
||
|
|
||
|
# Adds a header file and its sub-includes to the global dictionary if they
|
||
|
# aren't already there. Specify s_path since different build directories may
|
||
|
# append themselves on demand to the global list.
|
||
|
# return entry for the specified header, knowing all sub entries are completed
|
||
|
|
||
|
def get_header_info (header, s_path):
|
||
|
global header_dict
|
||
|
global empty_iinfo
|
||
|
process_list = list ()
|
||
|
location = ""
|
||
|
bname = ""
|
||
|
bname_iinfo = empty_iinfo
|
||
|
for path in s_path:
|
||
|
if os.path.exists (path + "/" + header):
|
||
|
location = path + "/" + header
|
||
|
break
|
||
|
|
||
|
if location:
|
||
|
bname = normalize_header (location)
|
||
|
if header_dict.get (bname):
|
||
|
bname_iinfo = header_dict[bname]
|
||
|
loc2 = ii_path (bname_iinfo)+ "/" + bname
|
||
|
if loc2[:2] == "./":
|
||
|
loc2 = loc2[2:]
|
||
|
if location[:2] == "./":
|
||
|
location = location[2:]
|
||
|
if loc2 != location:
|
||
|
# Don't use the cache if it isnt the right one.
|
||
|
bname_iinfo = process_ii_macro (location)
|
||
|
return bname_iinfo
|
||
|
|
||
|
bname_iinfo = process_ii_macro (location)
|
||
|
header_dict[bname] = bname_iinfo
|
||
|
# now decend into the include tree
|
||
|
for i in ii_include_list (bname_iinfo):
|
||
|
get_header_info (i, s_path)
|
||
|
else:
|
||
|
# if the file isnt in the source directories, look in the build and target
|
||
|
# directories. If it is here, then aggregate all the versions.
|
||
|
location = build_dir + "/gcc/" + header
|
||
|
build_inc = target_inc = False
|
||
|
if os.path.exists (location):
|
||
|
build_inc = True
|
||
|
for x in target_dict:
|
||
|
location = target_dict[x] + "/gcc/" + header
|
||
|
if os.path.exists (location):
|
||
|
target_inc = True
|
||
|
break
|
||
|
|
||
|
if (build_inc or target_inc):
|
||
|
bname = normalize_header(header)
|
||
|
defines = set()
|
||
|
consumes = set()
|
||
|
incl = set()
|
||
|
if build_inc:
|
||
|
iinfo = process_ii_macro (build_dir + "/gcc/" + header)
|
||
|
defines = set (ii_macro_define (iinfo))
|
||
|
consumes = set (ii_macro_consume (iinfo))
|
||
|
incl = set (ii_include_list (iinfo))
|
||
|
|
||
|
if (target_inc):
|
||
|
for x in target_dict:
|
||
|
location = target_dict[x] + "/gcc/" + header
|
||
|
if os.path.exists (location):
|
||
|
iinfo = process_ii_macro (location)
|
||
|
defines.update (ii_macro_define (iinfo))
|
||
|
consumes.update (ii_macro_consume (iinfo))
|
||
|
incl.update (ii_include_list (iinfo))
|
||
|
|
||
|
bname_iinfo = (header, "build", list(incl), list(), list(consumes), list(defines), list(), list())
|
||
|
|
||
|
header_dict[bname] = bname_iinfo
|
||
|
for i in incl:
|
||
|
get_header_info (i, s_path)
|
||
|
|
||
|
return bname_iinfo
|
||
|
|
||
|
|
||
|
# return a list of all headers brought in by this header
|
||
|
def all_headers (fname):
|
||
|
global header_dict
|
||
|
headers_stack = list()
|
||
|
headers_list = list()
|
||
|
if header_dict.get (fname) == None:
|
||
|
return list ()
|
||
|
for y in ii_include_list (header_dict[fname]):
|
||
|
headers_stack.append (y)
|
||
|
|
||
|
while headers_stack:
|
||
|
h = headers_stack.pop ()
|
||
|
hn = normalize_header (h)
|
||
|
if hn not in headers_list:
|
||
|
headers_list.append (hn)
|
||
|
if header_dict.get(hn):
|
||
|
for y in ii_include_list (header_dict[hn]):
|
||
|
if normalize_header (y) not in headers_list:
|
||
|
headers_stack.append (y)
|
||
|
|
||
|
return headers_list
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
# Search bld_dir for all target tuples, confirm that they have a build path with
|
||
|
# bld_dir/target-tuple/gcc, and build a dictionary of build paths indexed by
|
||
|
# target tuple..
|
||
|
|
||
|
def build_target_dict (bld_dir, just_these):
|
||
|
global target_dict
|
||
|
target_doct = { }
|
||
|
error = False
|
||
|
if os.path.exists (bld_dir):
|
||
|
if just_these:
|
||
|
ls = just_these
|
||
|
else:
|
||
|
ls = os.listdir(bld_dir)
|
||
|
for t in ls:
|
||
|
if t.find("-") != -1:
|
||
|
target = t.strip()
|
||
|
tpath = bld_dir + "/" + target
|
||
|
if not os.path.exists (tpath + "/gcc"):
|
||
|
print "Error: gcc build directory for target " + t + " Does not exist: " + tpath + "/gcc"
|
||
|
error = True
|
||
|
else:
|
||
|
target_dict[target] = tpath
|
||
|
|
||
|
if error:
|
||
|
target_dict = { }
|
||
|
|
||
|
def get_obj_name (src_file):
|
||
|
if src_file[-2:] == ".c":
|
||
|
return src_file.replace (".c", ".o")
|
||
|
elif src_file[-3:] == ".cc":
|
||
|
return src_file.replace (".cc", ".o")
|
||
|
return ""
|
||
|
|
||
|
def target_obj_exists (target, obj_name):
|
||
|
global target_dict
|
||
|
# look in a subdir if src has a subdir, then check gcc base directory.
|
||
|
if target_dict.get(target):
|
||
|
obj = target_dict[target] + "/gcc/" + obj_name
|
||
|
if not os.path.exists (obj):
|
||
|
obj = target_dict[target] + "/gcc/" + os.path.basename(obj_name)
|
||
|
if os.path.exists (obj):
|
||
|
return True
|
||
|
return False
|
||
|
|
||
|
# Given a src file, return a list of targets which may build this file.
|
||
|
def find_targets (src_file):
|
||
|
global target_dict
|
||
|
targ_list = list()
|
||
|
obj_name = get_obj_name (src_file)
|
||
|
if not obj_name:
|
||
|
print "Error: " + src_file + " - Cannot determine object name."
|
||
|
return list()
|
||
|
|
||
|
# Put the high priority targets which tend to trigger failures first
|
||
|
for target in target_priority:
|
||
|
if target_obj_exists (target, obj_name):
|
||
|
targ_list.append ((target, target_dict[target]))
|
||
|
|
||
|
for target in target_dict:
|
||
|
if target not in target_priority and target_obj_exists (target, obj_name):
|
||
|
targ_list.append ((target, target_dict[target]))
|
||
|
|
||
|
return targ_list
|
||
|
|
||
|
|
||
|
def try_to_remove (src_file, h_list, verbose):
|
||
|
global target_dict
|
||
|
global header_dict
|
||
|
global build_dir
|
||
|
|
||
|
# build from scratch each time
|
||
|
header_dict = { }
|
||
|
summary = ""
|
||
|
rmcount = 0
|
||
|
|
||
|
because = { }
|
||
|
src_info = process_ii_macro_src (src_file)
|
||
|
src_data = ii_src (src_info)
|
||
|
if src_data:
|
||
|
inclist = ii_include_list_non_cond (src_info)
|
||
|
# work is done if there are no includes to check
|
||
|
if not inclist:
|
||
|
return src_file + ": No include files to attempt to remove"
|
||
|
|
||
|
# work on the include list in reverse.
|
||
|
inclist.reverse()
|
||
|
|
||
|
# Get the target list
|
||
|
targ_list = list()
|
||
|
targ_list = find_targets (src_file)
|
||
|
|
||
|
spath = search_path
|
||
|
if os.path.dirname (src_file):
|
||
|
spath.append (os.path.dirname (src_file))
|
||
|
|
||
|
hostbuild = True
|
||
|
if src_file.find("config/") != -1:
|
||
|
# config files dont usually build on the host
|
||
|
hostbuild = False
|
||
|
obn = get_obj_name (os.path.basename (src_file))
|
||
|
if obn and os.path.exists (build_dir + "/gcc/" + obn):
|
||
|
hostbuild = True
|
||
|
if not target_dict:
|
||
|
summary = src_file + ": Target builds are required for config files. None found."
|
||
|
print summary
|
||
|
return summary
|
||
|
if not targ_list:
|
||
|
summary =src_file + ": Cannot find any targets which build this file."
|
||
|
print summary
|
||
|
return summary
|
||
|
|
||
|
if hostbuild:
|
||
|
# confirm it actually builds before we do anything
|
||
|
print "Confirming source file builds"
|
||
|
res = get_make_output (build_dir + "/gcc", "all")
|
||
|
if res[0] != 0:
|
||
|
message = "Error: " + src_file + " does not build currently."
|
||
|
summary = src_file + " does not build on host."
|
||
|
print message
|
||
|
print res[1]
|
||
|
if verbose:
|
||
|
verbose.write (message + "\n")
|
||
|
verbose.write (res[1]+ "\n")
|
||
|
return summary
|
||
|
|
||
|
src_requires = set (ii_macro_consume (src_info))
|
||
|
for macro in src_requires:
|
||
|
because[macro] = src_file
|
||
|
header_seen = list ()
|
||
|
|
||
|
os.rename (src_file, src_file + ".bak")
|
||
|
src_orig = copy.deepcopy (src_data)
|
||
|
src_tmp = copy.deepcopy (src_data)
|
||
|
|
||
|
try:
|
||
|
# process the includes from bottom to top. This is because we know that
|
||
|
# later includes have are known to be needed, so any dependency from this
|
||
|
# header is a true dependency
|
||
|
for inc_file in inclist:
|
||
|
inc_file_norm = normalize_header (inc_file)
|
||
|
|
||
|
if inc_file in no_remove:
|
||
|
continue
|
||
|
if len (h_list) != 0 and inc_file_norm not in h_list:
|
||
|
continue
|
||
|
if inc_file_norm[0:3] == "gt-":
|
||
|
continue
|
||
|
if inc_file_norm[0:6] == "gtype-":
|
||
|
continue
|
||
|
if inc_file_norm.replace(".h",".c") == os.path.basename(src_file):
|
||
|
continue
|
||
|
|
||
|
lookfor = ii_src_line(src_info)[inc_file]
|
||
|
src_tmp.remove (lookfor)
|
||
|
message = "Trying " + src_file + " without " + inc_file
|
||
|
print message
|
||
|
if verbose:
|
||
|
verbose.write (message + "\n")
|
||
|
out = open(src_file, "w")
|
||
|
for line in src_tmp:
|
||
|
out.write (line)
|
||
|
out.close()
|
||
|
|
||
|
keep = False
|
||
|
if hostbuild:
|
||
|
res = get_make_output (build_dir + "/gcc", "all")
|
||
|
else:
|
||
|
res = (0, "")
|
||
|
|
||
|
rc = res[0]
|
||
|
message = "Passed Host build"
|
||
|
if (rc != 0):
|
||
|
# host build failed
|
||
|
message = "Compilation failed:\n";
|
||
|
keep = True
|
||
|
else:
|
||
|
if targ_list:
|
||
|
objfile = get_obj_name (src_file)
|
||
|
t1 = targ_list[0]
|
||
|
if objfile and os.path.exists(t1[1] +"/gcc/"+objfile):
|
||
|
res = get_make_output_parallel (targ_list, objfile, 0)
|
||
|
else:
|
||
|
res = get_make_output_parallel (targ_list, "all-gcc", 0)
|
||
|
rc = res[0]
|
||
|
if rc != 0:
|
||
|
message = "Compilation failed on TARGET : " + res[2]
|
||
|
keep = True
|
||
|
else:
|
||
|
message = "Passed host and target builds"
|
||
|
|
||
|
if keep:
|
||
|
print message + "\n"
|
||
|
|
||
|
if (rc != 0):
|
||
|
if verbose:
|
||
|
verbose.write (message + "\n");
|
||
|
verbose.write (res[1])
|
||
|
verbose.write ("\n");
|
||
|
if os.path.exists (inc_file):
|
||
|
ilog = open(inc_file+".log","a")
|
||
|
ilog.write (message + " for " + src_file + ":\n\n");
|
||
|
ilog.write ("============================================\n");
|
||
|
ilog.write (res[1])
|
||
|
ilog.write ("\n");
|
||
|
ilog.close()
|
||
|
if os.path.exists (src_file):
|
||
|
ilog = open(src_file+".log","a")
|
||
|
ilog.write (message + " for " +inc_file + ":\n\n");
|
||
|
ilog.write ("============================================\n");
|
||
|
ilog.write (res[1])
|
||
|
ilog.write ("\n");
|
||
|
ilog.close()
|
||
|
|
||
|
# Given a sequence where :
|
||
|
# #include "tm.h"
|
||
|
# #include "target.h" // includes tm.h
|
||
|
|
||
|
# target.h was required, and when attempting to remove tm.h we'd see that
|
||
|
# all the macro defintions are "required" since they all look like:
|
||
|
# #ifndef HAVE_blah
|
||
|
# #define HAVE_blah
|
||
|
# endif
|
||
|
|
||
|
# when target.h was found to be required, tm.h will be tagged as included.
|
||
|
# so when we get this far, we know we dont have to check the macros for
|
||
|
# tm.h since we know it is already been included.
|
||
|
|
||
|
if inc_file_norm not in header_seen:
|
||
|
iinfo = get_header_info (inc_file, spath)
|
||
|
newlist = all_headers (inc_file_norm)
|
||
|
if ii_path(iinfo) == "build" and not target_dict:
|
||
|
keep = True
|
||
|
text = message + " : Will not remove a build file without some targets."
|
||
|
print text
|
||
|
ilog = open(src_file+".log","a")
|
||
|
ilog.write (text +"\n")
|
||
|
ilog.write ("============================================\n");
|
||
|
ilog.close()
|
||
|
ilog = open("reduce-headers-kept.log","a")
|
||
|
ilog.write (src_file + " " + text +"\n")
|
||
|
ilog.close()
|
||
|
else:
|
||
|
newlist = list()
|
||
|
if not keep and inc_file_norm not in header_seen:
|
||
|
# now look for any macro requirements.
|
||
|
for h in newlist:
|
||
|
if not h in header_seen:
|
||
|
if header_dict.get(h):
|
||
|
defined = ii_macro_define (header_dict[h])
|
||
|
for dep in defined:
|
||
|
if dep in src_requires and dep not in ignore_list:
|
||
|
keep = True;
|
||
|
text = message + ", but must keep " + inc_file + " because it provides " + dep
|
||
|
if because.get(dep) != None:
|
||
|
text = text + " Possibly required by " + because[dep]
|
||
|
print text
|
||
|
ilog = open(inc_file+".log","a")
|
||
|
ilog.write (because[dep]+": Requires [dep] in "+src_file+"\n")
|
||
|
ilog.write ("============================================\n");
|
||
|
ilog.close()
|
||
|
ilog = open(src_file+".log","a")
|
||
|
ilog.write (text +"\n")
|
||
|
ilog.write ("============================================\n");
|
||
|
ilog.close()
|
||
|
ilog = open("reduce-headers-kept.log","a")
|
||
|
ilog.write (src_file + " " + text +"\n")
|
||
|
ilog.close()
|
||
|
if verbose:
|
||
|
verbose.write (text + "\n")
|
||
|
|
||
|
if keep:
|
||
|
# add all headers 'consumes' to src_requires list, and mark as seen
|
||
|
for h in newlist:
|
||
|
if not h in header_seen:
|
||
|
header_seen.append (h)
|
||
|
if header_dict.get(h):
|
||
|
consume = ii_macro_consume (header_dict[h])
|
||
|
for dep in consume:
|
||
|
if dep not in src_requires:
|
||
|
src_requires.add (dep)
|
||
|
if because.get(dep) == None:
|
||
|
because[dep] = inc_file
|
||
|
|
||
|
src_tmp = copy.deepcopy (src_data)
|
||
|
else:
|
||
|
print message + " --> removing " + inc_file + "\n"
|
||
|
rmcount += 1
|
||
|
if verbose:
|
||
|
verbose.write (message + " --> removing " + inc_file + "\n")
|
||
|
if remove_count.get(inc_file) == None:
|
||
|
remove_count[inc_file] = 1
|
||
|
else:
|
||
|
remove_count[inc_file] += 1
|
||
|
src_data = copy.deepcopy (src_tmp)
|
||
|
except:
|
||
|
print "Interuption: restoring original file"
|
||
|
out = open(src_file, "w")
|
||
|
for line in src_orig:
|
||
|
out.write (line)
|
||
|
out.close()
|
||
|
raise
|
||
|
|
||
|
# copy current version, since it is the "right" one now.
|
||
|
out = open(src_file, "w")
|
||
|
for line in src_data:
|
||
|
out.write (line)
|
||
|
out.close()
|
||
|
|
||
|
# Try a final host bootstrap build to make sure everything is kosher.
|
||
|
if hostbuild:
|
||
|
res = get_make_output (build_dir, "all")
|
||
|
rc = res[0]
|
||
|
if (rc != 0):
|
||
|
# host build failed! return to original version
|
||
|
print "Error: " + src_file + " Failed to bootstrap at end!!! restoring."
|
||
|
print " Bad version at " + src_file + ".bad"
|
||
|
os.rename (src_file, src_file + ".bad")
|
||
|
out = open(src_file, "w")
|
||
|
for line in src_orig:
|
||
|
out.write (line)
|
||
|
out.close()
|
||
|
return src_file + ": failed to build after reduction. Restored original"
|
||
|
|
||
|
if src_data == src_orig:
|
||
|
summary = src_file + ": No change."
|
||
|
else:
|
||
|
summary = src_file + ": Reduction performed, "+str(rmcount)+" includes removed."
|
||
|
print summary
|
||
|
return summary
|
||
|
|
||
|
only_h = list ()
|
||
|
ignore_cond = False
|
||
|
|
||
|
usage = False
|
||
|
src = list()
|
||
|
only_targs = list ()
|
||
|
for x in sys.argv[1:]:
|
||
|
if x[0:2] == "-b":
|
||
|
build_dir = x[2:]
|
||
|
elif x[0:2] == "-f":
|
||
|
fn = normalize_header (x[2:])
|
||
|
if fn not in only_h:
|
||
|
only_h.append (fn)
|
||
|
elif x[0:2] == "-h":
|
||
|
usage = True
|
||
|
elif x[0:2] == "-d":
|
||
|
ignore_cond = True
|
||
|
elif x[0:2] == "-D":
|
||
|
ignore_list.append(x[2:])
|
||
|
elif x[0:2] == "-T":
|
||
|
only_targs.append(x[2:])
|
||
|
elif x[0:2] == "-t":
|
||
|
target_dir = x[2:]
|
||
|
elif x[0] == "-":
|
||
|
print "Error: Unrecognized option " + x
|
||
|
usgae = True
|
||
|
else:
|
||
|
if not os.path.exists (x):
|
||
|
print "Error: specified file " + x + " does not exist."
|
||
|
usage = True
|
||
|
else:
|
||
|
src.append (x)
|
||
|
|
||
|
if target_dir:
|
||
|
build_target_dict (target_dir, only_targs)
|
||
|
|
||
|
if build_dir == "" and target_dir == "":
|
||
|
print "Error: Must specify a build directory, and/or a target directory."
|
||
|
usage = True
|
||
|
|
||
|
if build_dir and not os.path.exists (build_dir):
|
||
|
print "Error: specified build directory does not exist : " + build_dir
|
||
|
usage = True
|
||
|
|
||
|
if target_dir and not os.path.exists (target_dir):
|
||
|
print "Error: specified target directory does not exist : " + target_dir
|
||
|
usage = True
|
||
|
|
||
|
if usage:
|
||
|
print "Attempts to remove extraneous include files from source files."
|
||
|
print " "
|
||
|
print "Should be run from the main gcc source directory, and works on a target"
|
||
|
print "directory, as we attempt to make the 'all' target."
|
||
|
print " "
|
||
|
print "By default, gcc-reorder-includes is run on each file before attempting"
|
||
|
print "to remove includes. this removes duplicates and puts some headers in a"
|
||
|
print "canonical ordering"
|
||
|
print " "
|
||
|
print "The build directory should be ready to compile via make. Time is saved"
|
||
|
print "if the build is already complete, so that only changes need to be built."
|
||
|
print " "
|
||
|
print "Usage: [options] file1.c [file2.c] ... [filen.c]"
|
||
|
print " -bdir : the root build directory to attempt buiding .o files."
|
||
|
print " -tdir : the target build directory"
|
||
|
print " -d : Ignore conditional macro dependencies."
|
||
|
print " "
|
||
|
print " -Dmacro : Ignore a specific macro for dependencies"
|
||
|
print " -Ttarget : Only consider target in target directory."
|
||
|
print " -fheader : Specifies a specific .h file to be considered."
|
||
|
print " "
|
||
|
print " -D, -T, and -f can be specified mulitple times and are aggregated."
|
||
|
print " "
|
||
|
print " The original file will be in filen.bak"
|
||
|
print " "
|
||
|
sys.exit (0)
|
||
|
|
||
|
if only_h:
|
||
|
print "Attempting to remove only these files:"
|
||
|
for x in only_h:
|
||
|
print x
|
||
|
print " "
|
||
|
|
||
|
logfile = open("reduce-headers.log","w")
|
||
|
|
||
|
for x in src:
|
||
|
msg = try_to_remove (x, only_h, logfile)
|
||
|
ilog = open("reduce-headers.sum","a")
|
||
|
ilog.write (msg + "\n")
|
||
|
ilog.close()
|
||
|
|
||
|
ilog = open("reduce-headers.sum","a")
|
||
|
ilog.write ("===============================================================\n")
|
||
|
for x in remove_count:
|
||
|
msg = x + ": Removed " + str(remove_count[x]) + " times."
|
||
|
print msg
|
||
|
logfile.write (msg + "\n")
|
||
|
ilog.write (msg + "\n")
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|