#!/usr/bin/env python # pymake # Copyright (C) 2001 Pascal Vincent # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # 3. The name of the authors may not be used to endorse or promote # products derived from this software without specific prior written # permission. # # THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR # IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES # OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN # NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED # TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # # This file is part of the PLearn library. For more information on the PLearn # library, go to the PLearn Web site at www.plearn.org import os, sys, re, string, glob, socket, time, whrandom, select, shutil, fnmatch,math from stat import * from types import * from popen2 import * print "pymake 2.0 [ (C) 2001, Pascal Vincent. This is free software distributed under a BSD type license. Report problems to vincentp@iro.umontreal.ca ]" # initialise a few variables from the environment platform = sys.platform if platform=='linux2': linux_type = os.uname()[4] if linux_type == 'ppc': platform = 'linux-ppc' elif linux_type =='x86_64': platform = 'linux-x86_64' else: platform = 'linux-i386' homedir = os.environ['HOME'] if platform=='win32': homedir = 'R:/' myhostname = socket.gethostname() pos = string.find(myhostname,'.') if pos>=0: myhostname = myhostname[0:pos] # QT specific stuff. If this is true, then qt's include dir and library are added for compilation and linkage useqt = 0 qtdir='' # get the option arguments args = sys.argv[:] del args[0] # ignore program name # initialize a few variables to their default values... # these may be overridden by the config file default_compiler = 'g++' default_linker = 'g++' sourcedirs = [] mandatory_includedirs = [] linkeroptions_tail = '-lstdc++ -lm' options_choices = [] nprocesses_per_processor = 1 rshcommand = 'rsh ' compileflags = '' #objspolicy=1 -> for dir/truc.cc object file in dir/platform_opt/truc.o (objects file are in a directory corresponding to the cc file directory) #objspolicy=2 -> for dir/truc.cc object file in objsdir/platform_opt/truc.o (all objects file are in the same directory) objspolicy=1 objsdir='' # some variables used when creating a dll... default_wrapper = 'dllwrap' dllwrap_basic_options = '--driver-name=c++ --add-stdcall-alias' # nice default command and value nice_command = 'env nice -n' nice_value = '10' verbose=3 # a few useful functions def convertWinToLinux(path): """in windows, replace all directory separator \\ by / (for use in MinGW)""" if platform=='win32': return string.replace(path,"\\","/") else: return path def join(dir,file,file2="",file3=""): """simply call os.path.join and convertWinToLinux""" if file2=='': res = convertWinToLinux(os.path.join(dir, file)) elif file3=='': res = convertWinToLinux(os.path.join(dir, file, file2)) else: res = convertWinToLinux(os.path.join(dir, file, file2, file3)) return res def abspath(mypath): """returns the absolute path of the file, with a hack to remove the leading /export/ that causes problems at DIRO""" p = convertWinToLinux(os.path.abspath(mypath)) # python 5.1 does not have abspath rmprefix = '/export/' lenprefix = len(rmprefix) if len(p)>lenprefix: if p[0:lenprefix]==rmprefix : p = '/'+p[lenprefix:] return p def lsdirs(basedir): """returns the recursive list of all subdirectories of the given directory, excluding OBJS and CVS directories and those whose name starts with a dot. The first element of the returned list is the basedir""" if not os.path.isdir(basedir): return [] dirs = [ basedir ] for dname in os.listdir(basedir): if dname!='CVS' and dname!='.svn' and dname!='OBJS' and dname[0]!='.': dname = join(basedir,dname) if os.path.isdir(dname): dirs.append(dname) dirs.extend(lsdirs(dname)) return dirs def appendunique(l, l2): """appends the elements of list l2 to list l, but only those that are not already in l""" for e in l2: if e not in l: l.append(e) def unique(l): """returns the elements of l but without duplicates""" lfilt = [] for e in l: if e not in lfilt: lfilt.append(e) return lfilt # This is used for buffering mtime calls mtime_map = {} def forget_mtime(filepath): del mtime_map[filepath] def mtime(filepath): "a buffered os.path.getmtime" "returns 0 if the file does not exist" if not mtime_map.has_key(filepath): if os.path.exists(filepath): mtime_map[filepath] = os.path.getmtime(filepath) else: mtime_map[filepath] = 0 return mtime_map[filepath] # preparing to read config file pymake_options_defs = {} class PymakeOption: def __init__(self, name, description, compiler, compileroptions, linker, linkeroptions): self.name = name self.description = description self.compiler = compiler self.compileroptions = compileroptions self.linker = linker self.linkeroptions = linkeroptions # adds a posible option to the pymake_options_defs def pymakeOption( name, description, compiler='', compileroptions='', linker='', linkeroptions='' ): pymake_options_defs[name] = PymakeOption(name, description, compiler, compileroptions, linker, linkeroptions) optional_libraries_defs = [] class OptionalLibrary: def __init__(self, name, includedirs, triggers, linkeroptions, compileroptions ): self.name = name self.includedirs = includedirs self.triggers = triggers self.linkeroptions = linkeroptions self.compileroptions = compileroptions def is_triggered_by(self, include): """returns true if this particular include command is supposed to trigger this library""" for trigger in self.triggers: if fnmatch.fnmatch(include,trigger): # print 'OPTIONAL LIBRARY: ' + self.name + ' TRIGGERED BY TRIGGER ' + trigger return 1 if include[0]!='/': # look for it in includedirs for incldir in self.includedirs: if os.path.isfile(join(incldir,include)): # print 'file exists!: ' + join(incldir,include) # print 'INCLUDE ' + include + ' TRIGGERED BY INCDIR ' + incldir return 1 return 0 # adds a library to the optional_libraries_defs def optionalLibrary( name, linkeroptions, includedirs=[], triggers='', compileroptions='' ): if type(includedirs) != ListType: includedirs = [ includedirs ] if type(triggers) != ListType: triggers = [ triggers ] optional_libraries_defs.append( OptionalLibrary(name, includedirs, triggers, linkeroptions, compileroptions) ) default_config_text = r""" # List of directories in which to look for .h includes and corresponding .cc files to compile and link with your program # (no need to include the current directory, it is implicit) #sourcedirs = [] # directories other than those in sourcedirs, that will be added as list of includes (-I...) to all compilations #mandatory_includedirs = [] # Add available external libraries In the order in which the linkeroptions # must appear on the linker command line (typically most basic libraries # last) If you do not give any specific triggers, any included .h file # found in the specified includedirs will trigger the library Triggers can # be a list of includes that will trigger the use of the library, and they # can have wildcards (such as ['GreatLibInc/*.h','Magick*.h'] for instance) # optionalLibrary( name = 'lapack', # triggers = 'Lapackincl/*.h', # linkeroptions = '-llapack' ) # What linker options to put always after those from the optional libraries #linkeroptions_tail = '-lstdc++ -lm' # List of lists of mutually exclusive pymake options. # First option that appears in each group is the default, and is assumed if you don't specify any option from that group #options_choices = [ # [ 'g++', 'CC'], # [ 'dbg', 'opt'] #] # Description of options, and associated settings # name and description are mandatory (description will appear in usage display of pymake) # you can then specify any one or more of compiler, compileroptions, linker, linkeroptions #pymakeOption( name = 'g++', # description = 'use g++ compiler and linker', # compiler = 'g++', # compileroptions = '-pedantic-errors', # linker = 'g++' ) #pymakeOption( name = 'CC', # description = 'use CC compiler and linker', # compiler = 'CC', # linker = 'CC' ) #pymakeOption( name = 'dbg', # description = 'debug mode (defines BOUNDCHECK)', # compileroptions = '-Wall -g -DBOUNDCHECK' ) #pymakeOption( name = 'opt', # description = 'optimized mode', # compileroptions = '-Wall -O9 -funroll-loops -finline -fomit-frame-pointer -fstrength-reduce -ffast-math -fPIC' ) """ def locateconfigfile(file,configdir,config_text=''): # first look in the current directory then look in its parents directory = convertWinToLinux(configdir) while os.path.isdir(directory) and os.access(directory,os.W_OK) and directory!='/': fpath = join(directory,'.pymake',file); modelpath = join(directory,'pymake.'+file+'.model') if os.path.isfile(modelpath) and mtime(modelpath)>mtime(fpath): print '*****************************************************' if os.path.isfile(fpath): print '* FOUND A MORE RECENT MODEL FILE: ' + modelpath + ' ***' print '* SAVING PREVIOUS CONFIG FILE AS: ' + fpath+'.bak ' os.rename(fpath, fpath+'.bak') print '* COPYING MODEL FILE ' + modelpath + ' TO ' + fpath print '* PLEASE ADAPT THE CONFIGURATION TO YOUR NEEDS' print '*****************************************************' try: os.makedirs(join(directory,'.pymake')) except: pass shutil.copyfile(modelpath,fpath) if os.path.isfile(fpath): return fpath directory = abspath(join(directory,'..')) # nothing was found in current directory or its parents, let's look in the homedir fpath = join(homedir,'.pymake',file) if os.path.isfile(fpath): return fpath else: if config_text: print '*****************************************************' print '* COULD NOT LOCATE ANY PYMAKE '+file+' CONFIGURATION FILE.' print '* CREATING A DEFAULT CONFIG FILE IN ' + fpath print '* PLEASE ADAPT THE CONFIGURATION TO YOUR NEEDS' print '*****************************************************' try: os.makedirs(join(homedir,'.pymake')) except: pass f = open(fpath,'w') f.write(config_text) f.close() return fpath return '' def printusage(): print 'Usage: pymake [options] ' print 'Where targets are .cc file names or base names or directories' print 'And options are a combination of the following: ' for choice in options_choices: print ' * One of ' + string.join(map(lambda s: '-'+s, choice),', ') + ' (default is -' + choice[0] + ') where:' for item in choice: print ' -'+item + ': ' + pymake_options_defs[item].description print print 'Other possible options are:' print " -graph: if this is given, targets won't be compiled or linked. Instead their" print " dependency graph will be generated in targetname.dot and targetname.ps files." print ' -force: forces recompilation of all necessary files, even if they are up to date.' print ' -local: don\'t use parallel compilation, even if there is a .pymake/.hosts file.' print ' -clean: removes all OBJS subrdirectories recursively in all given directories' print ' -checkobj: will report all source files that don\'t have *any* correponding .o in OBJS.' print ' This processes recursively from given directories and is typically called' print ' after a pymake -clean, and a pymake . to get the list of files that failed to compile.' print ' -so: create a shared object (.so) instead of an executable file.' print ' -dll: create a dll instead of an executable file.' print ' It probably works ONLY on Windows with MinGW installed.' print print """The configuration file 'config' for pymake is searched for first in the .pymake subdirectory of the current directory, then similarly in the .pymake subdirectory of parent directories of the current directory, and as last resort in the .pymake subdirectory of your homedir. Also, if there is a pymake.config.model in the directory from which a .pymake/config is searched, and the pymake.config.model is more recent than the .pymake/config (or the .pymake/config does not exist) then that 'model' will be copied to the .pymake/config file. In adition to the 'config' file, your .pymake directories can contain files that list machines (one per line) for launching parallel compilations. Those files are to be called .hosts, where indicates the architecture and system of the machines it lists. A good place for this file is in the .pymake of your home directory, as these are likely to be comon to all projects. Note that you can easily override the compileroptions (defined in your config file) for a particular .cc file. Just create a ..override_compile_options file (along your .cc file) in which you put a python dictionary defining what options to override and how. Ex: If a non time-critical file takes too long to compile in optimized mode (or bugs), you can override its optimized flags by writing the following in a corresponding ..override_compile_options file: { 'opt': '-Wall -g', 'opt_boundcheck': '-Wall -g -DBOUNDCHECK' } """ if len(args)==0: printusage() sys.exit() ####################################################### # special calling options, that don't actually compile anything ####################################################### def rmOBJS(arg, dirpath, names): if os.path.basename(dirpath) == 'OBJS': print 'Removing', dirpath shutil.rmtree(dirpath) def reportMissingObj(arg, dirpath, names): if 'OBJS' in names: names.remove('OBJS') if 'CVS' in names: names.remove('CVS') for fname in names: fpath = join(dirpath,fname) if os.path.isfile(fpath): basename, ext = os.path.splitext(fname) if ext in ['.cc','.c','.C','.cpp','.CC']: foundobj = 0 # found the .o file? for f in glob.glob(join(dirpath,'OBJS','*',basename+'.o')): if os.path.isfile(f): foundobj = 1 if not foundobj: print fpath ######## Regular pymake processing optionargs = [] # will contain all the -... options, with the leading '-' removed otherargs = [] # will contain all other arguments to pymake (should be file or directory names) #JF #for arg in args: # if arg[0]=='-': # optionargs.append(arg[1:]) # else: # otherargs.append(arg) i=0 linkname = '' while i < len(args): if args[i] == '-o': linkname = args[i+1] i = i + 1 elif args[i][0]=='-': optionargs.append(args[i][1:]) else: otherargs.append(args[i]) i = i + 1 #/JF print '*** Current platform is: ' + platform print if 'link' in optionargs: force_link = 1; optionargs.remove('link') else: force_link = 0; if 'clean' in optionargs: if len(optionargs)!=1 or len(otherargs)==0: print 'BAD ARGUMENTS: with -clean, specify one or more directories to clean, but no other -option' else: print '>> Removing the following OBJS directories: ' for dirname in otherargs: os.path.walk(dirname,rmOBJS,'') sys.exit() if 'checkobj' in optionargs: # report .cc files without any corresponding .o file in OBJS if len(optionargs)!=1 or len(otherargs)==0: print 'BAD ARGUMENTS: with -checkobj, specify one or more directories to check, but no other -option' else: print '>> The following files do not have *any* corresponding .o in OBJS:' for dirname in otherargs: os.path.walk(abspath(dirname),reportMissingObj, '') sys.exit() force_recompilation = 0 # force recompilation of everything? if 'force' in optionargs: force_recompilation = 1 optionargs.remove('force') SPC = 0 if 'spc' in optionargs : SPC = 1 plearndir = os.environ.get('PLEARNDIR') if not plearndir: print 'Error: First set the PLEARNDIR environnement variable in order to get the pyfreemachines script' sys.exit(); else: try: os.makedirs(join(homedir,'.pymake')) except: pass print '*** Running pymake using Smart Parallel Compilation' cmd=plearndir+'/scripts/pyfreemachines '+join(homedir,'.pymake','SPC.hosts'); # print cmd os.system(cmd) optionargs.remove('spc') local_compilation = 0 # do we want to do everything locally? if platform=='win32': local_compilation = 1 # in windows, we ALWAYS work locally if 'local' in optionargs: local_compilation = 1 optionargs.remove('local') create_so = 0 # do we want to create a .so instead of an executable file if 'so' in optionargs: create_so = 1 optionargs.remove('so') create_dll = 0 # do we want to create a dll instead of an executable file if 'dll' in optionargs: create_dll = 1 optionargs.remove('dll') if create_so and create_dll: print 'Error: cannot create a DLL and a Shared Object at the same time. Remove "-dll" or "-so" option.' sys.exit() #if options_choices[0][0] == 'icc' if 'icc' in optionargs: usingIntelCompiler = 1 #optionargs.remove('icc') else: usingIntelCompiler = 0 temp_objs=0 if 'tmp' in optionargs: objspolicy = 2 temp_objs=1 local_compilation = 1 objsdir = '/tmp/OBJS' optionargs.remove('tmp') else: temp_objs = 0 # fill the list of options from the optionargs, adding necessary default options (if no option of a group choice was specified) def getOptions(options_choices,optionargs): options = [] for choice in options_choices: nmatches = 0 for item in choice: if item in optionargs: optionargs.remove(item) if nmatches<1: options.append(item) else: print 'PROBLEM: options ' + item + ' and ' + options[-1] + ' are mutually exclusive. Ignoring ' + item nmatches = nmatches+1 if nmatches<1: options.append(choice[0]) if optionargs: # there are remaining optionargs print 'Invalid options: ' + string.join(map(lambda s: '-'+s, optionargs)) printusage() sys.exit() return options list_of_hosts = [] if local_compilation: list_of_hosts = ['localhost'] else: if SPC: hostspath = locateconfigfile('SPC.hosts',homedir,'localhost'); else: hostspath = locateconfigfile(platform+'.hosts',os.getcwd(),'localhost') if hostspath: print '*** Parallel compilation using list of hosts from file: ' + hostspath f = open(hostspath,'r') list_of_hosts = filter(lambda s: s and s[0]!='#', map(string.strip,f.readlines())) f.close() else: list_of_hosts = ['localhost'] print '*** No hosts file found: no parallel compilation, using localhost' print ' (create a '+platform + '.hosts file in your .pymake directory to list hosts for parallel compilation.)' # We could just do random.shuffle(list) but that's not in Python 1.5.2 def shuffle(list): l = len(list) for i in range(0,l-1): j = whrandom.randint(i+1,l-1) list[i], list[j] = list[j], list[i] # randomize order of list_of_hosts shuffle(list_of_hosts) # replicate list nprocesses_per_processor times list_of_hosts = nprocesses_per_processor * list_of_hosts # rmccomment_regexp = re.compile(r'([^/])\/\*([^\*]|\*(?!\/))*\*\/',re.M) rmcppcomment_regexp = re.compile(r'//[^\n]*\n',re.M) def remove_c_comments(text): result = '' prevpos = 0 while 1: pos = string.find(text,'/*',prevpos) if pos<0: result = result + text[prevpos:] break result = result + text[prevpos:pos] prevpos = string.find(text,'*/',pos+2) if prevpos<0: result = result + text[pos:] break prevpos = prevpos + 2 return result; def remove_comments(text): text = rmcppcomment_regexp.sub('\n',text) text = remove_c_comments(text) return text class FileInfo: """ This class is specifically designed to hold all useful information about .cc and .h files. Contains the following fields: - mtime: last modification time of this file - filepath: full absolute path of file - filedir: dirtectory part of filepath - filebase: file basename part of filepath - fileext: file extension - is_ccfile: true if this file has a .cc or similar extension, false if it has a .h or similar extension - includes_from_sourcedirs: list of FileInfo of the files that this one includes found in the sourcedirs - triggered_libraries: list of OptionalLibrary objects triggered by this file's includes QT specific: - isqt : (for .cc files) true if this file is the brother .cc of a .h that 'hasqobject==true', or if for this .cc 'hasqobject==true' - hasqobject : true if file contains a Q_OBJECT declaration (in which case we need to preprocess it with moc) - mocfilename : the name of the associated moc file, if appropriate, that is, if hasqobject is true - corresponding_moc_cc : the fileinfo of the .cc generated by moc'ing the file .h files will also have the following fields: - corresponding_ccfile: FileInfo of corresponding .cc file if any, 0 otherwise .cc files will also have the following fields - corresponding_ofile: full path of corresponding .o file - hasmain: true if the sourcefile has a main function If hasmain is true, then the following field will also be set: - corresponding_output: full path of corresponding executable (in the same directory as the obj) If create_dll is true, then the following fields will be set: - corresponding_output: full path of corresponding dll file (in the same directory as the source file) - corresponding_def_file: full path of corresponding def file (in the same directory as the source file) If create_so is true, then the following field will be set: - corresponding_output: full path of corresponding .so file (in the same directory as the source obj) These attributes should not be accessed directly, but through their get method - depmtime (optionally built by a call to get_depmtime()) - ccfiles_to_link list containing the FileInfos of all .cc files whose .o must be linked to produce the executable When launching a compilation, the following are also set: - hostname: name of host on which the compilation has been launched - launched: a Popen3 object for the launched process - errormsgs: a list of error messages taken from the launched process' stdout - warningmsgs: a list of warning messages taken from the launched process' stderr """ # static patterns useful for parsing the file include_regexp = re.compile(r'^\s*#include\s*(?:"|<)([^\s\"\>]+)(?:"|>)',re.M) qobject_regexp = re.compile(r'\bQ\_OBJECT\b',re.M) #hasmain_regexp = re.compile(r'\b(main|mpi_master_main)\s*\(',re.M) hasmain_regexp = re.compile(r'\b(main|IMPLEMENT_APP)\s*\(',re.M) def __init__(self,filepath): if not os.path.exists(filepath): raise "Couldn't find file " + filepath self.filepath = filepath def parse_file(self): """ Parses the file, and sets self.includes_from_sourcedirs, self.triggered_libraries and self.hasmain""" #print "Parsing " + self.filepath f = open(self.filepath,"r") text = f.read() f.close() text = remove_comments(text) self.includes_from_sourcedirs = [] self.triggered_libraries = [] for includefile in FileInfo.include_regexp.findall(text): #print 'Considering include ' + includefile for optlib in optional_libraries_defs: if optlib.is_triggered_by(includefile) and not optlib in self.triggered_libraries: self.triggered_libraries.append(optlib) # print optlib.name + ' TRIGGERED BY INCLUDE ' + includefile + ' IN ' + self.filepath fullpath = join(self.filedir, includefile) if os.path.isfile(fullpath): self.includes_from_sourcedirs.append(abspath(fullpath)) else: for srcdir in mandatory_includedirs: fullpath = join(srcdir, includefile) if os.path.isfile(fullpath): self.includes_from_sourcedirs.append(abspath(fullpath)) self.hasmain = FileInfo.hasmain_regexp.search(text) if platform!='win32': self.hasqobject = FileInfo.qobject_regexp.search(text) else: self.hasqobject = False self.mocfilename = "moc_"+self.filebase+".cpp" def mocFile(self): """QT specific : if moc_'filename'.cpp is older than 'filename'.h, then moc 'filename'.h (generates) moc_'filename'.cpp""" if qtdir == '': print """\nError : The qtdir variable is unset but the file """+self.filepath+""" seems to use QT (a Q_OBJECT declaration was found). In your .pymake/config file, assign the qtdir variable to the path to Trolltech's QT installation. Example : qtdir = '/usr/lib/qt3/'""" sys.exit() if (os.path.isfile(self.mocfilename)) and self.get_depmtime() <= os.path.getmtime(self.mocfilename): return 0 os.system(qtdir+"bin/moc "+self.filepath+" -o "+self.mocfilename) def analyseFile(self): global useqt self.mtime = os.path.getmtime(self.filepath) self.filedir, fname = os.path.split(self.filepath) self.filebase, self.fileext = os.path.splitext(fname) # Parse the file to get includes and check if there's a main() self.parse_file() # transform the list of includes from a list of file names to a list of correspondinf FileInfo self.includes_from_sourcedirs = map(file_info,self.includes_from_sourcedirs) if self.fileext in ['.h','.H','.hpp']: self.is_ccfile = 0 # get the corresponding .cc file's FileInfo (if there is such a file) self.corresponding_ccfile = 0 for newext in ['.cc','.c','.C','.cpp','.CC']: ccpath = join(self.filedir, self.filebase + newext) if mtime(ccpath): self.corresponding_ccfile = file_info(ccpath) break elif self.fileext in ['.cc','.c','.C','.cpp','.CC']: self.is_ccfile = 1 if objspolicy == 1: self.corresponding_ofile = join(self.filedir, objsdir, self.filebase+'.o') elif objspolicy == 2: self.corresponding_ofile = join(objsdir, self.filebase+'.o') #print self.corresponding_ofile if self.hasmain: self.corresponding_output = join(self.filedir, objsdir, self.filebase) if create_dll: self.corresponding_output = join(self.filedir, self.filebase+'.dll') self.corresponding_def_file = join(self.filedir, self.filebase+'.def') if create_so: self.corresponding_output = join(self.filedir, objsdir, 'lib'+self.filebase+'.so') elif self.fileext in ['.xpm']: self.is_ccfile = 0 self.corresponding_ccfile = 0 else: raise 'Attempting to build a FileInfo from a file that is not a .cc or .h or similar file : '+self.filedir+"/"+self.filebase+self.fileext if self.hasqobject: useqt = 1 self.mocFile() self.corresponding_moc_cc = file_info(self.mocfilename) def collect_ccfiles_to_link(self, ccfiles_to_link, visited_hfiles): """completes the ccfiles_to_link list by appending the FileInfo for all .cc files related to this file through includes and .cc files corresponding to .h files""" if self.is_ccfile: if self not in ccfiles_to_link: ccfiles_to_link.append(self) # print self.filebase,self.fileext for include in self.includes_from_sourcedirs: #print self.filebase include.collect_ccfiles_to_link(ccfiles_to_link, visited_hfiles) else: # it's a .h file if self not in visited_hfiles: visited_hfiles.append(self) if self.corresponding_ccfile: self.corresponding_ccfile.collect_ccfiles_to_link(ccfiles_to_link, visited_hfiles) if self.hasqobject: self.corresponding_moc_cc.collect_ccfiles_to_link(ccfiles_to_link, visited_hfiles) for include in self.includes_from_sourcedirs: include.collect_ccfiles_to_link(ccfiles_to_link, visited_hfiles) def get_ccfiles_to_link(self): """returns the list of FileInfos of all .cc files that need to be linked together to produce the corresponding_output""" if not hasattr(self,"ccfiles_to_link"): #if not self.hasmain or not self.is_ccfile: if (not self.hasmain and not create_dll and not create_so) or not self.is_ccfile: raise "called get_ccfiles_to_link on a file that is not a .cc file or that does not contain a main()" self.ccfiles_to_link = [] visited_hfiles = [] self.collect_ccfiles_to_link(self.ccfiles_to_link,visited_hfiles) return self.ccfiles_to_link def collect_optional_libraries(self, optlibs, visited_files): # print 'collecting optional libraries for file', self.filebase+self.fileext if self not in visited_files: # print 'ENTERING' visited_files.append(self) if hasattr(self,'optional_libraries'): # print 'hasattr: ', map(lambda(x): x.name, self.optional_libraries) appendunique(optlibs, self.optional_libraries) else: # print 'appending triggered libraries' appendunique(optlibs, self.triggered_libraries) for include in self.includes_from_sourcedirs: # print 'lookin up include', include.filebase+include.fileext include.collect_optional_libraries(optlibs, visited_files) def get_optional_libraries(self): """returns the list of all OptionalLibrary that this file triggers directly or indirectly""" if not hasattr(self,'optional_libraries'): optlibs = [] self.collect_optional_libraries(optlibs, []) self.optional_libraries = optlibs return self.optional_libraries def get_all_optional_libraries_to_link(self): """returns the list of all optional libraries to link with this .cc""" # reorder self.optional_libraries in the order in which they appear in optional_libraries_defs # self.optional_libraries = [ x for x in optional_libraries_defs if x in self.optional_libraries ] # old-fashioned python that will work on troll (xsm): optlibs = [] for ccfile in self.get_ccfiles_to_link(): appendunique(optlibs, ccfile.get_optional_libraries()) sol= [] for x in optional_libraries_defs: if x in optlibs: sol = sol + [x] return sol # def get_optional_libraries(self): # """returns the list of *all* OptionalLibrary that need to be linked with any program that uses this file, in the right order""" # print "go(",self.filebase+self.fileext,")" # if not hasattr(self,'optional_libraries'): # self.optional_libraries = [] # appendunique(self.optional_libraries, self.triggered_libraries) # if not self.is_ccfile and self.corresponding_ccfile: # print self.filebase+self.fileext, " moving into corresponding .cc file" # appendunique(self.optional_libraries, self.corresponding_ccfile.get_optional_libraries()) # print self.filebase+self.fileext, " moving into INCLUDES: ", map( lambda(x): x.filebase, self.includes_from_sourcedirs) # for include in self.includes_from_sourcedirs: # appendunique(self.optional_libraries, include.get_optional_libraries()) # # reorder self.optional_libraries in the order in which they appear in optional_libraries_defs # # self.optional_libraries = [ x for x in optional_libraries_defs if x in self.optional_libraries ] # # old-fashioned python that will work on troll (xsm): # sol= [] # for x in optional_libraries_defs: # if x in self.optional_libraries: # sol = sol + [x] # self.optional_libraries = sol # print "optlib(",self.filebase+self.fileext, " = ", map(lambda(x): x.name, self.optional_libraries) # return self.optional_libraries def get_optional_libraries_compileroptions(self): compopts = [] for optlib in self.get_optional_libraries(): compopts.append(optlib.compileroptions) # print '+> ', optlib.name, optlib.compileroptions return compopts def get_optional_libraries_includedirs(self): incldirs = [] for optlib in self.get_optional_libraries(): appendunique(incldirs, optlib.includedirs) # print '+> ', optlib.name, optlib.includedirs if useqt: appendunique(incldirs, [ qtdir+"include/" ] ) return incldirs def get_optional_libraries_linkeroptions(self): linkeroptions = '' for optlib in self.get_all_optional_libraries_to_link(): linkeroptions = linkeroptions + optlib.linkeroptions + ' ' if useqt: if os.path.exists(qtdir + 'lib/libqt.so'): linkeroptions = linkeroptions + '-L'+qtdir + 'lib/ -lqt' else: linkeroptions = linkeroptions + '-L'+qtdir + 'lib/ -lqt-mt' return linkeroptions # as of date 09/07/2004 # no need to -lXi -lXext -lX11 to compile PLearn # removing following code to speed up linking # if needed, this should go in an option in pymake.config.model # Martin # if platform=='win32': # return linkeroptions # else: # return linkeroptions + ' -L/usr/X11R6/lib/ -lXi -lXext -lX11' def get_depmtime(self): "returns the single latest last modification time of" "this file and all its .h dependencies through includes" if not hasattr(self,"depmtime"): # YB DEBUGGING self.mtime = os.path.getmtime(self.filepath) # self.depmtime = self.mtime for includefile in self.includes_from_sourcedirs: depmtime = includefile.get_depmtime() if depmtime>self.depmtime: self.depmtime = depmtime #print "time of ",includefile.filepath," = ",depmtime return self.depmtime def corresponding_ofile_is_up_to_date(self): """returns true if the corresponding .o file is up to date, false if it needs recompiling.""" try: if not os.path.exists(self.corresponding_ofile): # print "path of ",self.corresponding_ofile," does not exist!" return 0 else: t1 = self.get_depmtime() t2 = os.path.getmtime(self.corresponding_ofile) #print "t1 = source of ",self.filepath," time = ",t1 #print "t2 = object ",self.corresponding_ofile," time = ",t2 return t1 <= t2 except OSError: # OSError: [Errno 116] Stale NFS file handle print "OSError... (probably NFS latency); Will be retrying" return 0 def corresponding_output_is_up_to_date(self): """returns true if the corresponding executable is up to date, false if it needs rebuilding""" if not os.path.exists(self.corresponding_output): return 0 else: exec_mtime = os.path.getmtime(self.corresponding_output) ccfilelist = self.get_ccfiles_to_link() for ccfile in ccfilelist: if not os.path.exists(ccfile.corresponding_ofile): return 0 ofile_mtime = os.path.getmtime(ccfile.corresponding_ofile) if ccfile.get_depmtime()>ofile_mtime or ofile_mtime>exec_mtime: return 0 return 1 def nfs_wait_for_corresponding_ofile(self): """wait until nfs catches up....""" while not self.corresponding_ofile_is_up_to_date(): time.sleep(0.1) #no active wait (was 'pass') -xsm def nfs_wait_for_all_linkable_ofiles(self): for ccfile in self.get_ccfiles_to_link(): ccfile.nfs_wait_for_corresponding_ofile() def make_objs_dir(self): """ makes the OBJS dir and subdirectory if they don't already exist, to hold the corresponding_ofile and corresponding_output """ odir = join(self.filedir, objsdir) if not os.path.isdir(odir): # make sure the OBJS dir exists, otherwise the command will fail os.makedirs(odir) def make_symbolic_link(self, linkname): """ recreates the symbolic link in filedir pointing to the executable in subdirectory objsdir """ #If the second argument, which is the user defined path and name for the symbolic link, #is specified then the symbolic link will have this path and name, and the object files will be #placed in the OBJS subdirectory of the directory where the source file is. if linkname == '': symlinktarget = join(self.filedir,self.filebase) else: symlinktarget = linkname if os.path.islink(symlinktarget) or os.path.exists(symlinktarget): os.remove(symlinktarget) if linkname == '': os.symlink(join(objsdir,self.filebase),symlinktarget) else: os.symlink(join(self.filedir+"/"+objsdir,self.filebase),symlinktarget) def compile_command(self): """returns the command line necessary to compile this .cc file""" fname = join(self.filedir,'.'+self.filebase+'.override_compile_options') options_override = {} if os.path.isfile(fname): f = open(fname,'r') options_override = eval(f.read()) f.close() compiler = default_compiler compileroptions = "" for opt in options: optdef = pymake_options_defs[opt] if options_override.has_key(opt): compileroptions = compileroptions+ ' ' + options_override[opt] else: compileroptions = compileroptions + ' ' + optdef.compileroptions if optdef.compiler: compiler = optdef.compiler optlib_compopt = self.get_optional_libraries_compileroptions(); # print optlib_compopt if platform == 'win32' or nice_value == '0': nice = '' else: nice = nice_command + nice_value + ' ' command = nice + compiler + ' ' + compileflags + ' ' + compileroptions + ' ' + string.join(optlib_compopt, ' ') includedirs = [] + mandatory_includedirs + self.get_optional_libraries_includedirs() if includedirs: command = command + ' -I' + string.join(includedirs,' -I') command = command + ' -o ' + self.corresponding_ofile + ' -c ' + self.filepath return command def launch_compilation(self,hostname): """Launches the compilation of this .cc file on given host using rsh. The call returns the pid of the launched process. This will create a field 'launched', which is a Popen3 object, and 'hostname' to remember on which host it has been launched. """ # We do an 'echo $?' at the end because rsh doesn't transmit the status byte correctly. quotedcommand = "'cd " + self.filedir + "; " + self.compile_command() + "; echo $?'" self.hostname = hostname if hostname=='localhost' or hostname==myhostname: if platform=='win32': self.launched = popen3('sh -c ' + quotedcommand, -1, 't') else: self.shcommand = 'sh -c ' + quotedcommand self.launched = Popen3(self.shcommand, 1, 10000) else: self.shcommand = rshcommand + ' ' + self.hostname + ' ' + quotedcommand self.launched = Popen3(self.shcommand, 1, 10000) if verbose>=2: print '[ LAUNCHED',self.filebase+self.fileext,'on',self.hostname,']' if verbose>=3: print quotedcommand def update_compilation_messages(self): """This method should be called whenever you sense activity on the self.launched streams, it will fill errormsgs and warningmsgs""" if platform=='win32': self.errormsgs.extend(self.launched[0].readlines()) self.warningmsgs.extend(self.launched[2].readlines()) else: self.errormsgs.extend(self.launched.fromchild.readlines()) self.warningmsgs.extend(self.launched.childerr.readlines()) def finished_compilation(self): """You should call this when the compilation process has just finished This will print the warnings and error messages of the compilation process, create the compilation_status field to the appropriate value, and delete the launched field, freeing the ressources used by the pipe. """ if verbose>=2: print '[ FINISHED',self.filebase+self.fileext,'on',self.hostname,']' warningmsgs='' errormsgs = '' if platform=='win32': warningmsgs = self.launched[2].readlines() # print warnings errormsgs = self.launched[0].readlines() else: warningmsgs = self.launched.childerr.readlines() # print warnings errormsgs = self.launched.fromchild.readlines() if errormsgs: self.compilation_status = int(errormsgs[-1]) # last line was an echo $? (because rsh doesn't transmit the status byte correctly) if verbose>=4: print 'RETURN STATUS: ', self.compilation_status del errormsgs[-1] msg = warningmsgs + errormsgs if msg: print self.filebase+self.fileext,':', string.join(msg,'') if platform!='win32': try: self.launched.wait() # we don't like defunct and zombies around, do we? except OSError: pass # don't know why, but sometimes i get a OSError: [Errno 10] No child processes # don't know if this is useful, but anyway... if platform=='win32': self.launched[1].close() self.launched[0].close() self.launched[2].close() else: self.launched.tochild.close() self.launched.fromchild.close() self.launched.childerr.close() del self.launched # free up everything def failed_ccfiles_to_link(self): """returns a list of names of the ccfiles_to_link that we tried to recompile but failed""" failures = [] for ccfile in self.get_ccfiles_to_link(): if hasattr(ccfile,"compilation_status"): if ccfile.compilation_status!=0: failures.append(ccfile.filepath) return failures def dotid(self): """returns a string suitable as an ID for the dot graph language""" return self.filebase+'_'+self.fileext[1:] def filename(self): """returns the filename (without the directory part) of the current node""" return self.filebase+self.fileext def build_dependency_graph(self, dotfile, visited_files): if self not in visited_files: visited_files.append(self) if self.is_ccfile: dotfile.write(self.dotid()+'[shape="box",label="'+self.filename()+'",fontsize=10,height=0.2,width=0.4,fontname="Helvetica",color="darkgreen",style="filled",fontcolor="white"];\n') else: # it's a .h file dotfile.write(self.dotid()+'[shape="box",label="'+self.filename()+'",fontsize=10,height=0.2,width=0.4,fontname="Helvetica",color="blue4",style="filled",fontcolor="white"];\n') if self.corresponding_ccfile: self.corresponding_ccfile.build_dependency_graph(dotfile, visited_files) dotfile.write(self.dotid()+' -> '+self.corresponding_ccfile.dotid()+' [dir=none,color="darkgreen",fontsize=10,style="dashed",fontname="Helvetica"];\n') for include in self.includes_from_sourcedirs: include.build_dependency_graph(dotfile, visited_files) dotfile.write(self.dotid()+' -> '+include.dotid()+' [dir=forward,color="blue4",fontsize=10,style="solid",fontname="Helvetica"];\n') for lib in self.triggered_libraries: if lib not in visited_files: visited_files.append(lib) dotfile.write(lib.name+'[shape="box",label="'+lib.name+'",fontsize=10,height=0.2,width=0.4,fontname="Helvetica",color="red3",style="filled",fontcolor="white"];\n') dotfile.write(self.dotid()+' -> '+lib.name+' [dir=forward,color="red3",fontsize=10,style="solid",fontname="Helvetica"];\n') print self.filename(),'->',lib.name def save_dependency_graph(self, dotfilename): """Will save the dependency graph originating from this node into the dotfilename file This file should end in .dot The dot program can be used to generate a postscript file from it. The dependency graph considers include dependencies, as well as linkage dependencies, and triggered optional libraries. This allows one to see why all the files are compiled and linked together by pymake. """ dotfile = open(dotfilename,'w') dotfile.write(""" digraph G { edge [fontname="Helvetica",fontsize=10,labelfontname="Helvetica",labelfontsize=10]; node [fontname="Helvetica",fontsize=10,shape=record]; """) visited_files = [] self.build_dependency_graph(dotfile, visited_files) dotfile.write("}\n") dotfile.close() def link_command(self): """returns the command for making executable target from the .o files in objsfilelist""" objsfilelist = [] for ccfile in self.get_ccfiles_to_link(): # print ccfile.filebase objsfilelist.append(ccfile.corresponding_ofile) linker = default_linker linkeroptions = "" so_options = "" if create_so: so_options = " -shared " for opt in options: optdef = pymake_options_defs[opt] if optdef.linkeroptions: linkeroptions = linkeroptions + ' ' + optdef.linkeroptions if optdef.linker: linker = optdef.linker command = linker + so_options + ' -o ' + self.corresponding_output + ' ' + string.join(objsfilelist,' ') + ' ' + self.get_optional_libraries_linkeroptions() + ' ' + linkeroptions + ' ' + linkeroptions_tail # print "CALLED link_command WITH optional_libraries_linkeroptions of: ", self.get_optional_libraries_linkeroptions() return command def launch_linking(self): corresponding_output = self.corresponding_output self.corresponding_output = self.corresponding_output+".new" try: os.remove(self.corresponding_output) except: pass command = self.link_command() if verbose>=3: print command os.system(command) if not os.path.exists(self.corresponding_output): print "Link command failed to create file " + self.corresponding_output else: try: os.remove(corresponding_output) except: pass os.rename(self.corresponding_output, corresponding_output) if verbose>=2: print "Successfully created " + self.corresponding_output + " and renamed it to " + corresponding_output self.corresponding_output = corresponding_output def dll_command(self): """returns the command for making dll target from the .o files in objsfilelist and the corresponding .def file""" objsfilelist = [] for ccfile in self.get_ccfiles_to_link(): objsfilelist.append(ccfile.corresponding_ofile) dll_wrapper = default_wrapper linkeroptions = "" if self.corresponding_def_file: linkeroptions = '--def='+self.corresponding_def_file linkeroptions = linkeroptions + ' ' + dllwrap_basic_options command = dll_wrapper + ' ' + linkeroptions + ' -o ' + self.corresponding_output + ' ' + string.join(objsfilelist,' ') return command def launch_dll_wrapping(self): command = self.dll_command() print command os.system(command) file_info_map = {} def file_info(filepath): "returns a FileInfo object for this file" "parsing the file if necessary, and remembering the result for later buffered reuse" filepath = abspath(filepath) if file_info_map.has_key(filepath): return file_info_map[filepath] else: new_file_info = FileInfo(filepath) file_info_map[filepath] = new_file_info new_file_info.analyseFile() return new_file_info def get_ccpath_from_noncc_path(filepath): "returns the path to the .cc (or similar extension) corresponding" "to the given .h (or similar extension) file if it exists" "or None if there is no such file" (base,ext) = os.path.splitext(filepath) for newext in [".cc",".cpp",".C",".c",".CC"]: ccpath = base + newext if mtime(ccpath): return ccpath return None def isccfile(filepath): (base,ext) = os.path.splitext(filepath) return ext in ['.cc','.CC','.cpp','.c','.C'] def get_ccfiles_to_compile_and_link(target, ccfiles_to_compile, executables_to_link,linkname): """A target can be a .cc file, a binary target, or a directory. The function updates (by appending to them) ccfiles_to_compile and ofiles_to_link ccfiles_to_compile is a dictionary containing FileInfo of all .cc files to compile executables_to_link is a dictionary containing FileInfo of all .cc files containing a main whose corresponding executable should be made.""" target = abspath(target) # get absolute path if os.path.basename(target)[0] == '.': # ignore files and directories starting with a dot return if os.path.isdir(target): if os.path.basename(target) not in ['OBJS','CVS']: # skip OBJS and CVS directories print "Entering " + target for direntry in os.listdir(target): newtarget = join(target,direntry) if os.path.isdir(newtarget) or isccfile(newtarget): get_ccfiles_to_compile_and_link(newtarget,ccfiles_to_compile,executables_to_link, linkname) else: if isccfile(target): cctarget = target else: cctarget = get_ccpath_from_noncc_path(target) if not cctarget: print "Warning: bad target", target else: info = file_info(cctarget) if info.hasmain or create_dll or create_so: if not force_link and not force_recompilation and info.corresponding_output_is_up_to_date() and not create_dll and not create_so: info.make_symbolic_link(linkname) # remake the correct symbolic link print 'Target',info.filebase,'is up to date.' else: executables_to_link[info] = 1 for ccfile in info.get_ccfiles_to_link(): if force_recompilation or not ccfile.corresponding_ofile_is_up_to_date(): #print ccfile.filebase ccfiles_to_compile[ccfile] = 1 elif force_recompilation or not info.corresponding_ofile_is_up_to_date(): ccfiles_to_compile[info] = 1 # remove to permit pymake dir1/truc1.cc dir2/truc2.cc using dir1/.pymake/config for truc1.cc and dir2/.pymake/config for truc1.cc # for example to compile LisaPLearn and PLearn with the same command #def pymake(list_of_targets, linkname): def parallel_compile(files_to_compile): """files_to_compile is a list of FileInfo of .cc files""" hostnum = 0 outs = {} # a dictionary indexed by the stdout of the process launched by popen, and containing the corresponding FileInfo object errs = {} # a dictionary indexed by the stderrs of the process launched by popen, and containing the corresponding FileInfo object for ccfile in files_to_compile: ccfile.make_objs_dir() # make sure the OBJS dir exists, otherwise the command will fail #print len(outs) if len(outs) < len(list_of_hosts):# len(outs) number of launched compilation if len(files_to_compile)==1: # if there's just one file to compile, we compile it locally to avoid a long "Waiting for NFS to catch up!" hostname = 'localhost' else: hostname = list_of_hosts[hostnum] ccfile.launch_compilation(hostname) outs[ccfile.launched.fromchild] = ccfile errs[ccfile.launched.childerr] = ccfile hostnum = hostnum + 1 else: # all processes are busy, wait for something to finish... iwtd, owtd, ewtd = select.select(outs.keys()+errs.keys(), [], []) f = iwtd[0] if errs.has_key(f): info = errs[f] elif outs.has_key(f): info = outs[f] del errs[info.launched.childerr] del outs[info.launched.fromchild] info.finished_compilation() # print error messages, warnings, and get failure/success status hostname = info.hostname ccfile.launch_compilation(hostname) outs[ccfile.launched.fromchild] = ccfile errs[ccfile.launched.childerr] = ccfile # Now wait until everybody is finished while outs: if len(outs)<=10: if verbose>=2: print '[ STILL WAITING FOR:',string.join(map( lambda(f): f.filebase+f.fileext + ' on ' + f.hostname, outs.values()), ', '),']' iwtd, owtd, ewtd = select.select(outs.keys()+errs.keys(), [], []) f = iwtd[0] if errs.has_key(f): info = errs[f] elif outs.has_key(f): info = outs[f] del errs[info.launched.childerr] del outs[info.launched.fromchild] info.finished_compilation() # print error messages, warnings, and get failure/success status def win32_parallel_compile(files_to_compile): """files_to_compile is a list of FileInfo of .cc files""" for ccfile in files_to_compile: ccfile.make_objs_dir() # make sure the OBJS dir exists, otherwise the command will fail ccfile.launch_compilation('localhost') ccfile.finished_compilation() # print error messages, warnings, and get failure/success status # well this one is not 'parallel' right now... def sequential_link(executables_to_link, linkname): """executables_to_link is a list of FileInfo of .cc files that contain a main() and whose corresponding executable should be made""" for ccfile in executables_to_link: failures = ccfile.failed_ccfiles_to_link() if failures: print '[ Executable target',ccfile.filebase,'not remade because of previous compilation errors. ]' print ' Errors were while compiling files:' print ' '+string.join(failures,'\n ') else: print 'Waiting for NFS to catch up...' ccfile.nfs_wait_for_all_linkable_ofiles() if verbose>=2: print '[ LINKING',ccfile.filebase,']' ccfile.launch_linking() if platform!='win32' and not create_so: ccfile.make_symbolic_link(linkname) def sequential_dll(target_file_info): """target_file_info is a FileInfo of .cc file whose corresponding dll should be made""" failures = target_file_info.failed_ccfiles_to_link() if failures: print '[ Executable target',target_file_info.filebase,'not remade because of previous compilation errors. ]' print ' Errors were while compiling files:' print ' '+string.join(failures,'\n ') else: print 'Waiting for NFS to catch up...' target_file_info.nfs_wait_for_all_linkable_ofiles() print '[ CREATING DLL',target_file_info.filebase+'.dll',']' target_file_info.launch_dll_wrapping() ## hack for compiling in /tmp and others users can overwrite the file def mychmod(path,mode): #print path try: os.chmod(path,mode) except: pass (head, tail) = os.path.split(path) if tail: mychmod(head,mode) # MAIN PROGRAM for target in otherargs: ccfiles_to_compile = {} executables_to_link = {} if os.path.isdir(target): target_path = os.path.abspath(target) else: target_path = os.path.dirname(os.path.abspath(target)) configpath = locateconfigfile('config',target_path,default_config_text) execfile(configpath) #print options_choices # remove duplicates from sourcedirs sourcedirs = unique(sourcedirs) if 'graph' in optionargs: # We just want to generate dependency graphs cctarget = target if not isccfile(target): cctarget = get_ccpath_from_noncc_path(target) info = file_info(cctarget) print 'Generating dependency graph in '+target+'.dot ...' info.save_dependency_graph(target+'.dot') print 'Generating dependency graph view in '+target+'.ps ...' os.system('dot -T ps '+target+'.dot > '+target+'.ps') else: # We want to compile and possibly link options = getOptions(options_choices,optionargs) # Building name of object subdirectory if objspolicy== 1: objsdir = join('OBJS', platform + '__' + string.join(options,'_')) elif objspolicy == 2: objsdir = join(objsdir,platform + '__' + string.join(options,'_')) print '*** Running pymake on '+os.path.basename(target)+' using configuration file: ' + configpath print '*** Running pymake on '+os.path.basename(target)+' using options: ' + string.join(map(lambda o: '-'+o, options)) print '++++ Computing dependencies of '+target+' ...' get_ccfiles_to_compile_and_link(target, ccfiles_to_compile, executables_to_link, linkname) print '++++ Compiling...' if platform=='win32': win32_parallel_compile(ccfiles_to_compile.keys()) else: parallel_compile(ccfiles_to_compile.keys()) if force_link or (executables_to_link.keys() and not create_dll): print '++++ Linking', string.join(map(lambda x: x.filebase, executables_to_link.keys())) sequential_link(executables_to_link.keys(),linkname) if create_dll: print '++++ Creating DLL of', target sequential_dll(file_info(target)) if temp_objs: os.system('chmod 777 --silent '+objsdir+'/*') mychmod(objsdir,33279)