#! /usr/bin/python2 import os.path import sys import shlex import re import subprocess import shutil import pickle import multiprocessing def find_pound_include (line, use_outside, use_slash): inc = re.findall (ur"^\s*#\s*include\s*\"(.+?)\"", line) if len(inc) == 1: nm = inc[0] if use_outside or os.path.exists (nm): if use_slash or '/' not in nm: return nm return "" def find_system_include (line): inc = re.findall (ur"^\s*#\s*include\s*<(.+?)>", line) if len(inc) == 1: return inc[0] return "" def find_pound_define (line): inc = re.findall (ur"^\s*#\s*define ([A-Za-z0-9_]+)", line) if len(inc) != 0: if len(inc) > 1: print "What? more than 1 match in #define??" print inc sys.exit(5) return inc[0]; return "" def is_pound_if (line): inc = re.findall ("^\s*#\s*if\s", line) if not inc: inc = re.findall ("^\s*#\s*if[n]?def\s", line) if inc: return True return False def is_pound_endif (line): inc = re.findall ("^\s*#\s*endif", line) if inc: return True return False def find_pound_if (line): inc = re.findall (ur"^\s*#\s*if\s+(.*)", line) if len(inc) == 0: inc = re.findall (ur"^\s*#\s*elif\s+(.*)", line) if len(inc) > 0: inc2 = re.findall (ur"defined\s*\((.+?)\)", inc[0]) inc3 = re.findall (ur"defined\s+([a-zA-Z0-9_]+)", inc[0]) for yy in inc3: inc2.append (yy) return inc2 else: inc = re.findall (ur"^\s*#\s*ifdef\s(.*)", line) if len(inc) == 0: inc = re.findall (ur"^\s*#\s*ifndef\s(.*)", line) if len(inc) > 0: inc2 = re.findall ("[A-Za-z_][A-Za-z_0-9]*", inc[0]) return inc2 if len(inc) == 0: return list () print "WTF. more than one line returned for find_pound_if" print inc sys.exit(5) # IINFO - this is a vector of include information. It consists of 7 elements. # [0] - base name of the file # [1] - path leading to this file. # [2] - orderd list of all headers directly included by this file. # [3] - Ordered list of any headers included within condionally compiled code. # headers files are expected to have all includes one level deep due to # the omnipresent guards at the top of the file. # [4] - List of all macros which are consumed (used) within this file. # [5] - list of all macros which may be defined in this file. # [6] - The source code for this file, if cached. # [7] - line number info for any headers in the source file. Indexed by base # name, returning the line the include is on. empty_iinfo = ("", "", list(), list(), list(), list(), list()) # This function will process a file and extract interesting information. # DO_MACROS indicates whether macros defined and used should be recorded. # KEEP_SRC indicates the source for the file should be cached. def process_include_info (filen, do_macros, keep_src): header = False if not os.path.exists (filen): return empty_iinfo sfile = open (filen, "r"); data = sfile.readlines() sfile.close() # Ignore the initial #ifdef HEADER_H in header files if filen[-2:] == ".h": nest = -1 header = True else: nest = 0 macout = list () macin = list() incl = list() cond_incl = list() src_line = { } guard = "" for line in (data): if is_pound_if (line): nest += 1 elif is_pound_endif (line): nest -= 1 nm = find_pound_include (line, True, True) if nm != "" and nm not in incl and nm[-2:] == ".h": incl.append (nm) if nest > 0: cond_incl.append (nm) if keep_src: src_line[nm] = line continue if do_macros: d = find_pound_define (line) if d: if d not in macout: macout.append (d); continue d = find_pound_if (line) if d: # The first #if in a header file should be the guard if header and len (d) == 1 and guard == "": if d[0][-2:] == "_H": guard = d else: guard = "Guess there was no guard..." else: for mac in d: if mac != "defined" and mac not in macin: macin.append (mac); if not keep_src: data = list() return (os.path.basename (filen), os.path.dirname (filen), incl, cond_incl, macin, macout, data, src_line) # Extract header info, but no macros or source code. def process_ii (filen): return process_include_info (filen, False, False) # Extract header information, and collect macro information. def process_ii_macro (filen): return process_include_info (filen, True, False) # Extract header information, cache the source lines. def process_ii_src (filen): return process_include_info (filen, False, True) # Extract header information, coolewc macro info and cache the source lines. def process_ii_macro_src (filen): return process_include_info (filen, True, True) def ii_base (iinfo): return iinfo[0] def ii_path (iinfo): return iinfo[1] def ii_include_list (iinfo): return iinfo[2] def ii_include_list_cond (iinfo): return iinfo[3] def ii_include_list_non_cond (iinfo): l = ii_include_list (iinfo) for n in ii_include_list_cond (iinfo): l.remove (n) return l def ii_macro_consume (iinfo): return iinfo[4] def ii_macro_define (iinfo): return iinfo[5] def ii_src (iinfo): return iinfo[6] def ii_src_line (iinfo): return iinfo[7] def ii_read (fname): f = open (fname, 'rb') incl = pickle.load (f) consumes = pickle.load (f) defines = pickle.load (f) obj = (fname,fname,incl,list(), list(), consumes, defines, list(), list()) return obj def ii_write (fname, obj): f = open (fname, 'wb') pickle.dump (obj[2], f) pickle.dump (obj[4], f) pickle.dump (obj[5], f) f.close () # execute a system command which returns file names def execute_command (command): files = list() f = os.popen (command) for x in f: if x[0:2] == "./": fn = x.rstrip()[2:] else: fn = x.rstrip() files.append(fn) return files # Try to locate a build directory from PATH def find_gcc_bld_dir (path): blddir = "" # Look for blddir/gcc/tm.h command = "find " + path + " -mindepth 2 -maxdepth 3 -name tm.h" files = execute_command (command) for y in files: p = os.path.dirname (y) if os.path.basename (p) == "gcc": blddir = p break # If not found, try looking a bit deeper # Dont look this deep initially because a lot of cross target builds may show # up in the list before a native build... but those are better than nothing. if not blddir: command = "find " + path + " -mindepth 3 -maxdepth 5 -name tm.h" files = execute_command (command) for y in files: p = os.path.dirname (y) if os.path.basename (p) == "gcc": blddir = p break return blddir # Find files matching pattern NAME, return in a list. # CURRENT is True if you want to include the current directory # DEEPER is True if you want to search 3 levels below the current directory # any files with testsuite diurectories are ignored def find_gcc_files (name, current, deeper): files = list() command = "" if current: if not deeper: command = "find -maxdepth 1 -name " + name + " -not -path \"./testsuite/*\"" else: command = "find -maxdepth 4 -name " + name + " -not -path \"./testsuite/*\"" else: if deeper: command = "find -maxdepth 4 -mindepth 2 -name " + name + " -not -path \"./testsuite/*\"" if command != "": files = execute_command (command) return files # find the list of unique include names found in a file. def find_unique_include_list_src (data): found = list () for line in data: d = find_pound_include (line, True, True) if d and d not in found and d[-2:] == ".h": found.append (d) return found # find the list of unique include names found in a file. def find_unique_include_list (filen): data = open (filen).read().splitlines() return find_unique_include_list_src (data) # Create the macin, macout, and incl vectors for a file FILEN. # macin are the macros that are used in #if* conditional expressions # macout are the macros which are #defined # incl is the list of incluide files encountered # returned as a tuple of the filename followed by the triplet of lists # (filen, macin, macout, incl) def create_macro_in_out (filen): sfile = open (filen, "r"); data = sfile.readlines() sfile.close() macout = list () macin = list() incl = list() for line in (data): d = find_pound_define (line) if d != "": if d not in macout: macout.append (d); continue d = find_pound_if (line) if len(d) != 0: for mac in d: if mac != "defined" and mac not in macin: macin.append (mac); continue nm = find_pound_include (line, True, True) if nm != "" and nm not in incl: incl.append (nm) return (filen, macin, macout, incl) # create the macro information for filen, and create .macin, .macout, and .incl # files. Return the created macro tuple. def create_include_data_files (filen): macros = create_macro_in_out (filen) depends = macros[1] defines = macros[2] incls = macros[3] disp_message = filen if len (defines) > 0: disp_message = disp_message + " " + str(len (defines)) + " #defines" dfile = open (filen + ".macout", "w") for x in defines: dfile.write (x + "\n") dfile.close () if len (depends) > 0: disp_message = disp_message + " " + str(len (depends)) + " #if dependencies" dfile = open (filen + ".macin", "w") for x in depends: dfile.write (x + "\n") dfile.close () if len (incls) > 0: disp_message = disp_message + " " + str(len (incls)) + " #includes" dfile = open (filen + ".incl", "w") for x in incls: dfile.write (x + "\n") dfile.close () return macros # extract data for include file name_h and enter it into the dictionary. # this does not change once read in. use_requires is True if you want to # prime the values with already created .requires and .provides files. def get_include_data (name_h, use_requires): macin = list() macout = list() incl = list () if use_requires and os.path.exists (name_h + ".requires"): macin = open (name_h + ".requires").read().splitlines() elif os.path.exists (name_h + ".macin"): macin = open (name_h + ".macin").read().splitlines() if use_requires and os.path.exists (name_h + ".provides"): macout = open (name_h + ".provides").read().splitlines() elif os.path.exists (name_h + ".macout"): macout = open (name_h + ".macout").read().splitlines() if os.path.exists (name_h + ".incl"): incl = open (name_h + ".incl").read().splitlines() if len(macin) == 0 and len(macout) == 0 and len(incl) == 0: return () data = ( name_h, macin, macout, incl ) return data # find FIND in src, and replace it with the list of headers in REPLACE. # Remove any duplicates of FIND in REPLACE, and if some of the REPLACE # headers occur earlier in the include chain, leave them. # Return the new SRC only if anything changed. def find_replace_include (find, replace, src): res = list() seen = { } anything = False for line in src: inc = find_pound_include (line, True, True) if inc == find: for y in replace: if seen.get(y) == None: res.append("#include \""+y+"\"\n") seen[y] = True if y != find: anything = True # if find isnt in the replacement list, then we are deleting FIND, so changes. if find not in replace: anything = True else: if inc in replace: if seen.get(inc) == None: res.append (line) seen[inc] = True else: res.append (line) if (anything): return res else: return list() # pass in a require and provide dictionary to be read in. def read_require_provides (require, provide): if not os.path.exists ("require-provide.master"): print "require-provide.master file is not available. please run data collection." sys.exit(1) incl_list = open("require-provide.master").read().splitlines() for f in incl_list: if os.path.exists (f+".requires"): require[os.path.basename (f)] = open (f + ".requires").read().splitlines() else: require[os.path.basename (f)] = list () if os.path.exists (f+".provides"): provide[os.path.basename (f)] = open (f + ".provides").read().splitlines() else: provide [os.path.basename (f)] = list () def build_include_list (filen): include_files = list() sfile = open (filen, "r") data = sfile.readlines() sfile.close() for line in data: nm = find_pound_include (line, False, False) if nm != "" and nm[-2:] == ".h": if nm not in include_files: include_files.append(nm) return include_files def build_reverse_include_list (filen): include_files = list() sfile = open (filen, "r") data = sfile.readlines() sfile.close() for line in reversed(data): nm = find_pound_include (line, False, False) if nm != "": if nm not in include_files: include_files.append(nm) return include_files # Get compilation return code, and compensate for a warning that we want to # consider an error when it comes to inlined templates. def get_make_rc (rc, output): rc = rc % 1280 if rc == 0: # This is not considered an error during compilation of an individual file, # but it will cause an error during link if it isn't defined. If this # warning is seen during compiling a file, make it a build error so we # don't remove the header. h = re.findall ("warning: inline function.*used but never defined", output) if len(h) != 0: rc = 1 return rc; def get_make_output (build_dir, make_opt): devnull = open('/dev/null', 'w') at_a_time = multiprocessing.cpu_count() * 2 make = "make -j"+str(at_a_time)+ " " if build_dir != "": command = "cd " + build_dir +"; " + make + make_opt else: command = make + make_opt process = subprocess.Popen(command, stdout=devnull, stderr=subprocess.PIPE, shell=True) output = process.communicate(); rc = get_make_rc (process.returncode, output[1]) return (rc , output[1]) def spawn_makes (command_list): devnull = open('/dev/null', 'w') rc = (0,"", "") proc_res = list() text = " Trying target builds : " for command_pair in command_list: tname = command_pair[0] command = command_pair[1] text += tname + ", " c = subprocess.Popen(command, bufsize=-1, stdout=devnull, stderr=subprocess.PIPE, shell=True) proc_res.append ((c, tname)) print text[:-2] for p in proc_res: output = p[0].communicate() ret = (get_make_rc (p[0].returncode, output[1]), output[1], p[1]) if (ret[0] != 0): # Just record the first one. if rc[0] == 0: rc = ret; return rc def get_make_output_parallel (targ_list, make_opt, at_a_time): command = list() targname = list() if at_a_time == 0: at_a_time = multiprocessing.cpu_count() * 2 proc_res = [0] * at_a_time for x in targ_list: if make_opt[-2:] == ".o": s = "cd " + x[1] + "/gcc/; make " + make_opt else: s = "cd " + x[1] +"; make " + make_opt command.append ((x[0],s)) num = len(command) rc = (0,"", "") loops = num // at_a_time if (loops > 0): for idx in range (loops): ret = spawn_makes (command[idx*at_a_time:(idx+1)*at_a_time]) if ret[0] != 0: rc = ret break if (rc[0] == 0): leftover = num % at_a_time if (leftover > 0): ret = spawn_makes (command[-leftover:]) if ret[0] != 0: rc = ret return rc def readwholefile (src_file): sfile = open (src_file, "r") src_data = sfile.readlines() sfile.close() return src_data