#! /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"
]
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")