#!/usr/bin/python3 import os,sys,math import subprocess """ Copyright Aaron M. Schinder, 2011 - MIT/BSD License This script contains a bunch of helper functions for generating simple, imperative, hopefully transparent build scripts using the python language (and nothing else). I just want the script to do the compiling and linking operations I want it to do in the order I want it to do it in, finding every relevant source file. That's it. That's what I want in a build system. """ def flist(pth,**kwargs): """ flist - list all files in a given directory pth optional arguments: recurse - (T/F): Whether to recursively search for files in directory tree exts - (list): A list of file extensions to search for, otherwise all files normpath (T/F): whether to normalize path variables after filelist = flist(pth,**kwargs): """ flst = [] if(not('recurse' in kwargs)): recurse_ = False else: recurse_ = kwargs['recurse'] if(not('exts' in kwargs)): filterexts_ = False else: filterexts_ = True exts = kwargs['exts'] if(not('normpath' in kwargs)): normpath_ = True else: normpath_ = kwargs['normpath'] if(not('linuxpath' in kwargs)): linuxpath_ = False else: linuxpath_ = kwargs['linuxpath'] if(not('followlinks' in kwargs)): followlinks_ = False else: followlinks_ = kwargs['followlinks'] dirlist = [] rawlist = os.listdir(pth) for F in rawlist: F2 = os.path.join(pth,F) if(os.path.isdir(F2)): b = (followlinks_) or ((not followlinks_) and not(os.path.islink(F2))) if(b): if((F2!=".")&(F2!="..")): dirlist.append(F2) elif(os.path.isfile(F2)): flst.append(F2) #Recurse through directories if(recurse_): for D in dirlist: lst = flist(D,**kwargs) for L in lst: flst.append(L) #Postprocess: #Filter out all extensions except the selected ext list if(filterexts_): flst = filterexts(flst,exts) #Normalize filename path according to os if(normpath_): flst2 = list(flst) for I in range(0,len(flst2)): flst[I] = os.path.normpath(flst2[I]) #If linuxpath, convert all \\ to / #if(linuxpath_): # flst2 = list(flst) # for I in range(0,len(flst2)): # flst[I] = linuxpath(flst2[I]) return flst def filterexts(flst,exts): """ Filters by extensions in a list of files flst = def filterexts(flst,exts): """ flst2 = [] if(isinstance(exts,str)): exts = list([exts]) for F in flst: b = False for ext in exts: if(ext[0]!='.'): ext = '.'+ext F2 = os.path.splitext(F) if(len(F2)>=2): ex = F2[1] if(len(ex)>0): if(ex[0]!='.'): ex = '.'+ex if(ex==ext): b = True if(b): flst2.append(F) return flst2 #Find a file fname, starting in pth and recursing #Used for finding library files to link def findfile(fname,pth,**kwargs): fullfname = "" flst = flist(pth,recurse=True) for F in flst: F2 = os.path.split(F)[1] if(F2 == fname): fullfname = F return fullfname def replaceext(fname,ext): fname2 = "" if(len(ext)>0): if(ext[0]!='.'): ext = '.'+ext fname2 = os.path.splitext(fname)[0]+ext else: fname2 = os.path.splitext(fname)[0] return fname2 def replaceexts(fnamelist,ext): """Takes a list of filenames and returns a list with the extensions replaced by ext """ fname2list = [] for F in fnamelist: F2 = replaceext(F,ext) fname2list.append(F2) return fname2list def except_contains(lst1,exc): """ Takes a list of file names lst1, and removes filenams that match the list of exceptions exc. Returns a list without the exceptions. """ lst2 = [] for item in lst1: b = 1 for item2 in exc: fsplit = os.path.split(item) fn = fsplit[len(fsplit)-1] if(fn==item2): b = 0 break if(b==1): lst2.append(item) return lst2 def list_to_sss(lst): """List of strings to space-seperated-string""" lout = "" for I in range(0,len(lst)-1): lout = lout + lst[I] + " " if(len(lst)>0): lout = lout + lst[len(lst)-1] return lout ########################## ##System Call Procedures## ########################## def callproc(cmd, **kwargs): if(not('logfile' in kwargs)): use_lf = False else: logfile = kwargs['logfile'] if(logfile!=""): fp = open(kwargs['logfile'],'a+') use_lf = True else: use_lf = False if(not('echo' in kwargs)): echo = True else: echo = kwargs['echo'] if(echo): print(cmd) #encoding/deconding to/from bytes is necessary to use the subprocess command #in python3.7 #However, only do this in linux if(sys.platform!='win32'): cmd2 = cmd.encode(encoding='utf-8') else: cmd2 = cmd proc = subprocess.Popen(cmd2,stderr = subprocess.STDOUT, stdout=subprocess.PIPE, shell=True) (out, err) = proc.communicate() out = out.decode(encoding='utf-8') if(echo): print(out) #print(err); if(use_lf): fp.writelines(cmd+'\n') fp.writelines(out+'\n') if(use_lf): fp.close() ############################################# ## Compiler, Archive, and Linker Functions ## ############################################# #MSVC compiler wrapper def msvc_compile(compilername, srcfile, **kwargs): if(not('include' in kwargs)): include = '' else: include = kwargs['include'] if(isinstance(include,list)): include = list_to_sss(include) if(not('flags' in kwargs)): flags = '' else: flags = kwargs['flags'] if(isinstance(flags,list)): flags = list_to_sss(flags) if(not('objext' in kwargs)): objext = '.obj' else: objext = kwargs['objext'] if(not('srcfileflag' in kwargs)): srcfileflag = '/c' else: srcfileflag = kwargs['srcfileflag'] if(not('outfileflag' in kwargs)): outfileflag = '/Fo:' else: outfileflag = kwargs['outfileflag'] if(not('logfile' in kwargs)): logfile = "" else: logfile = kwargs['logfile'] outfile = replaceext(srcfile,objext) ln = compilername+" "+flags+" "+" "+srcfileflag+" "+srcfile+" "+outfileflag+'"'+outfile+'"' ln = ln + " " + include callproc(ln,echo=True,logfile=logfile) return #MSVC compiler wrapper def msvc_compile_list(compiler,srclist,**kwargs): for S in srclist: msvc_compile(compiler,S,**kwargs) return #gnu-style compiler compile: Should work with gcc, g++, gfortran def gs_compile(compiler,srcfile,**kwargs): if(not('include' in kwargs)): include = '' else: include = kwargs['include'] if(isinstance(include,list)): include = list_to_sss(include) if(not('flags' in kwargs)): flags = '' else: flags = kwargs['flags'] if(isinstance(flags,list)): flags = list_to_sss(flags) if(not('objext' in kwargs)): objext = '.o' else: objext = kwargs['objext'] if(not('srcfileflag' in kwargs)): srcfileflag = '-c' else: srcfileflag = kwargs['srcfileflag'] if(not('outfileflag' in kwargs)): outfileflag = '-o' else: outfileflag = kwargs['outfileflag'] if(not('logfile' in kwargs)): logfile = "" else: logfile = kwargs['logfile'] if(not('smartcompile' in kwargs)): _smartcompile = True else: _smartcompile = kwargs['smartcompile'] #Do I want to make this thing this general? # if(not(_smartcompile) or smartcompile(srcfile,objext)): # outfile = replaceext(srcfile,objext) # ln = compiler+" "+flags+" " + outfileflag+" "+outfile+" "+srcfileflag+" "+srcfile # ln = ln + " " + include # callproc(ln,echo=True,logfile=logfile) outfile = replaceext(srcfile,objext) ln = compiler+" "+flags+" " + outfileflag+" "+outfile+" "+srcfileflag+" "+srcfile ln = ln + " " + include callproc(ln,echo=True,logfile=logfile) return def gs_compile_list(compiler,srclist,**kwargs): for S in srclist: gs_compile(compiler,S,**kwargs) return def gs_compile_all(compiler,srcdir,srcexts,**kwargs): if(not('recurse' in kwargs)): recurse = True else: recurse = kwargs['recurse'] srcfils = flist(srcdir,exts=srcexts,recurse=recurse) for S in srcfils: gs_compile(compiler,S,**kwargs) return def gs_link_all(linker,srcpath,target,**kwargs): if(not('objext' in kwargs)): objext = '.o' else: objext = kwargs['objext'] if(not('recurse' in kwargs)): recurse = True else: recurse = kwargs['recurse'] objfils = flist(srcpath,exts=objext,recurse=recurse) oflst = list_to_sss(objfils) gs_link_list(linker,oflst,target,**kwargs) return def gs_link_list(linker,objlist,target,**kwargs): if(not('objext' in kwargs)): objext = '.o' else: objext = kwargs['objext'] if(not('libdir' in kwargs)): libdir = '' else: libdir = kwargs['libdir'] if(not('staticlibs' in kwargs)): staticlibs = '' else: staticlibs = kwargs['staticlibs'] if(not('libflags' in kwargs)): libflags = '' else: libflags = kwargs['libflags'] if(not('linkerflags' in kwargs)): linkerflags = '' else: linkerflags = kwargs['linkerflags'] if(not('recurse' in kwargs)): recurse = True else: recurse = kwargs['recurse'] if(not('logfile' in kwargs)): logfile = '' else: logfile = kwargs['logfile'] ln = linker+" -o "+target+" "+libdir ln = ln+" "+objlist+" "+staticlibs+" "+libflags+" "+linkerflags callproc(ln,logfile=logfile) return def msvc_link_list(objlist,target,**kwargs): linker = 'link' if(not('objext' in kwargs)): objext = '.obj' else: objext = kwargs['objext'] if(not('libdir' in kwargs)): libdir = '' else: libdir = kwargs['libdir'] if(not('staticlibs' in kwargs)): staticlibs = '' else: staticlibs = kwargs['staticlibs'] if(not('libflags' in kwargs)): libflags = '' else: libflags = kwargs['libflags'] if(not('linkerflags' in kwargs)): linkerflags = '' else: linkerflags = kwargs['linkerflags'] if(not('recurse' in kwargs)): recurse = True else: recurse = kwargs['recurse'] if(not('logfile' in kwargs)): logfile = '' else: logfile = kwargs['logfile'] ln = linker+" "+libdir ln = ln+" "+objlist+" "+staticlibs+" "+linkerflags ln = ln+" /out:"+target+" "+libflags callproc(ln,logfile=logfile) return def ar_all(srcpath,arname,**kwargs): if(not('recurse' in kwargs)): recurse = True else: recurse = kwargs['recurse'] if(not('objext' in kwargs)): objext = '.o' else: objext = kwargs['objext'] objlist = flist(srcpath,exts=objext,recurse=recurse) ar_list(objlist,arname,**kwargs) return def msvc_lib_list(objlist,arname,**kwargs): objlist2 = list_to_sss(objlist) ln = "lib "+objlist2+" /out:"+arname callproc(ln) return def ar_list(objlist,arname,**kwargs): objlist2 = list_to_sss(objlist) ln = "ar cr "+ arname+" "+objlist2 callproc(ln) return def ar_add_list(objlist,arname,**kwargs): objlist2 = list_to_sss(objlist) ln = "ar t "+arname+" "+objlist2 callproc(ln) return ##################################### ## Incremental Compilation Library ## ##################################### #silently read lines from a text file if exists def readtextlines(fname): txtlns = [] if(not os.path.isfile(fname)): return txtlns try: fp = open(fname,"r") except: return txtlns ln = " " while(ln!=""): ln = fp.readline() txtlns.append(ln) fp.close() return txtlns def getincludefnfrage(includeline): fnfrag = "" I1 = -1 I2 = -1 for I in range(0,len(includeline)): if(I1<0 and (includeline[I]=='<' or includeline[I]=='"')): I1 = I if(I1>=0 and (includeline[I]=='>' or includeline[I]=='"')): I2 = I break if(I1>=0 and I2>=0): fnfrag = includeline[I1+1:I2] return fnfrag #Returns the name of the source file fname (if it exists) #and all included filenames def getsrcandincludes(fname, incdirs): flist = [] if(os.path.isfile(fname)): flist.append(fname) Ilist = 0 while(Ilist=0): fnfrag = getincludefnfrage(lns[J]) for K in range(0,len(incdirs)): tfn = os.path.join(incdirs[K],fnfrag) if(os.path.isfile(tfn)): flist.append(tfn) break Ilist = Ilist + 1 return flist #Returns the name of the object file associated with the source file #within the object store folder (if it exists) def getobjfile(fname,objstore,objext = ".o"): fret = "" f1 = os.path.split(fname)[1] f2 = f1 while(os.path.splitext(f2)[1]!=""): f2 = os.path.splitext(f2)[0] objext = objext.strip('.') f3 = os.path.join(objstore,"{}.{}".format(f2,objext)) if(os.path.exists(f3)): fret = f3 return fret def getsrctimes(fname, incdirs): ftimes = [] flst = getsrcandincludes(fname, incdirs) for I in range(0,len(flst)): f = flst[I] mt = os.path.getmtime(f) ftimes.append(mt) return ftimes def getobjtime(fname,objstore,objext=".o"): ret = -1 fret = getobjfile(fname,objstore,objext) if(fret!=""): ret = os.path.getmtime(fret) return ret #Decide whether or not to compile source file def decidecompile(fname,**kwargs): ret = True if(not os.path.isfile(fname)): ret = False return ret ##unpack kwargs if("searchincdirs" in kwargs): incdirs = kwargs["searchincdirs"] else: incdirs = ["./include"] if("objext" in kwargs): objext = kwargs["objext"] else: objext = ".o" if("objstore" in kwargs): objstore = kwargs["objstore"] else: objstore = "./objstore" srclist = getsrcandincludes(fname,incdirs) srctlist = getsrctimes(fname,incdirs) obj = getobjfile(fname,objstore,objext) objt = getobjtime(fname,objstore,objext) if(obj!=""): ret = False for I in range(0,len(srctlist)): if(srctlist[I]>objt): ret = True break return ret def gs_incremental_compile(compiler,srcfile,**kwargs): if(not('include' in kwargs)): include = '' else: include = kwargs['include'] if(isinstance(include,list)): include = list_to_sss(include) if(not('flags' in kwargs)): flags = '' else: flags = kwargs['flags'] if(isinstance(flags,list)): flags = list_to_sss(flags) if(not('objext' in kwargs)): objext = '.o' else: objext = kwargs['objext'] if(not('srcfileflag' in kwargs)): srcfileflag = '-c' else: srcfileflag = kwargs['srcfileflag'] if(not('outfileflag' in kwargs)): outfileflag = '-o' else: outfileflag = kwargs['outfileflag'] if(not('logfile' in kwargs)): logfile = "" else: logfile = kwargs['logfile'] if(not('smartcompile' in kwargs)): _smartcompile = True else: _smartcompile = kwargs['smartcompile'] #incrementalcompile if("searchincdirs" in kwargs): incdirs = kwargs["searchincdirs"] else: incdirs = ["./include"] if("objstore" in kwargs): objstore = kwargs["objstore"] else: objstore = "./objstore" #Do I want to make this thing this general? docompile = decidecompile(srcfile,**kwargs) if(docompile): f1 = os.path.split(srcfile)[1] f2 = f1 while(os.path.splitext(f2)[1]!=""): f2 = os.path.splitext(f2)[0] outfile = os.path.join(objstore,"{}{}".format(f2,objext)) ln = compiler+" "+flags+" " + outfileflag+" "+outfile+" "+srcfileflag+" "+srcfile ln = ln + " " + include callproc(ln,echo=True,logfile=logfile) return def msvc_incremental_compile(compiler,srcfile,**kwargs): if(not('include' in kwargs)): include = '' else: include = kwargs['include'] if(isinstance(include,list)): include = list_to_sss(include) if(not('flags' in kwargs)): flags = '' else: flags = kwargs['flags'] if(isinstance(flags,list)): flags = list_to_sss(flags) if(not('objext' in kwargs)): objext = '.obj' else: objext = kwargs['objext'] if(not('srcfileflag' in kwargs)): srcfileflag = '/c' else: srcfileflag = kwargs['srcfileflag'] if(not('outfileflag' in kwargs)): outfileflag = '/Fo' else: outfileflag = kwargs['outfileflag'] if(not('logfile' in kwargs)): logfile = "" else: logfile = kwargs['logfile'] #incrementalcompile if("searchincdirs" in kwargs): incdirs = kwargs["searchincdirs"] else: incdirs = ["./include"] if("objstore" in kwargs): objstore = kwargs["objstore"] else: objstore = "./objstore" #Do I want to make this thing this general? docompile = decidecompile(srcfile,**kwargs) if(docompile): f1 = os.path.split(srcfile)[1] f2 = f1 while(os.path.splitext(f2)[1]!=""): f2 = os.path.splitext(f2)[0] outfile = os.path.join(objstore,"{}{}".format(f2,objext)) outfile = os.path.normpath(outfile) ln = compiler+" "+flags+" "+srcfileflag+" "+srcfile+" "+ outfileflag+'"'+outfile+'"' ln = ln + " " + include callproc(ln,echo=True,logfile=logfile) return def gs_incremental_compile_list(compiler,srclist,**kwargs): for s in srclist: gs_incremental_compile(compiler,s,**kwargs) return def msvc_incremental_compile_list(compiler,srclist,**kwargs): for s in srclist: msvc_incremental_compile(compiler,s,**kwargs) return ####################### ## Main Script Tests ## ####################### def testtimes(args): if(len(args)>=2): flist = getsrcandincludes(args[1],["./include"]) ftlist = getsrctimes(args[1],["./include"]) for I in range(0,len(flist)): print("{}\t\t{}".format(flist[I],ftlist[I])) print("associated obj file:") fobj = getobjfile(args[1],"./objstore") ftobj = getobjtime(args[1],"./objstore") if(fobj!=""): print("{}\t\t{}".format(fobj,ftobj)) else: print("none found") cflag = decidecompile(args[1]) print("compile? : {}".format(cflag)) return # if(__name__ == "__main__"): # args = sys.argv # testtimes(args)