#! /usr/bin/env python # -*- coding: UTF-8 -*- #——————————————————————————————————————————————————————————————————————————————————————————————————————————————————————* # Releases * #——————————————————————————————————————————————————————————————————————————————————————————————————————————————————————* # 1.0: march 18th, 2015 # first release # 2.0: october 2th, 2015 # added several target definition for rules # 2.1: october 5th, 2015 # added checking routine formal argument run-time types # 2.2: october 24th, 2015 # changed subprocess.Popen to subprocess.call in runCommand # added command tool checking using 'find_executable' function # added optional argument to Make class initializer to log command utility tool path # 2.3: april 16th, 2016 # added advance percentage # 3.0: may 30th, 2016 # compatibility with Python 3: # print xyz ---> print (xyz) # change isinstance function arguments ---> function argumentIsString # subprocess.call: add "universal_newlines=True" argument # added test (job.mReturnCode != None) lines 727 and 739 # 3.1: may 26th, 2018 # Added tolerance in secondary dependency file syntax: # #——————————————————————————————————————————————————————————————————————————————————————————————————————————————————————* # http://www.diveintopython3.net/porting-code-to-python-3-with-2to3.html import subprocess, sys, os, copy import urllib, shutil, subprocess import platform, json, operator import threading, types, traceback if sys.version_info >= (2, 6) : import multiprocessing #——————————————————————————————————————————————————————————————————————————————————————————————————————————————————————* # find_executable * # From: * # https://gist.github.com/4368898 * # Public domain code by anatoly techtonik * #——————————————————————————————————————————————————————————————————————————————————————————————————————————————————————* def find_executable(executable, path=None): """Try to find 'executable' in the directories listed in 'path' (a string listing directories separated by 'os.pathsep'; defaults to os.environ['PATH']). Returns the complete filename or None if not found """ if path is None: path = os.environ['PATH'] paths = path.split(os.pathsep) extlist = [''] if os.name == 'os2': (base, ext) = os.path.splitext(executable) # executable files on OS/2 can have an arbitrary extension, but # .exe is automatically appended if no dot is present in the name if not ext: executable = executable + ".exe" elif sys.platform == 'win32': pathext = os.environ['PATHEXT'].lower().split(os.pathsep) (base, ext) = os.path.splitext(executable) if ext.lower() not in pathext: extlist = pathext for ext in extlist: execname = executable + ext if os.path.isfile(execname): return execname else: for p in paths: f = os.path.join(p, execname) if os.path.isfile(f): return f else: return None #——————————————————————————————————————————————————————————————————————————————————————————————————————————————————————* # processorCount * #——————————————————————————————————————————————————————————————————————————————————————————————————————————————————————* def processorCount () : if sys.version_info >= (2, 6) : coreCount = multiprocessing.cpu_count () else: coreCount = 1 return coreCount #——————————————————————————————————————————————————————————————————————————————————————————————————————————————————————* # argumentIsString * #——————————————————————————————————————————————————————————————————————————————————————————————————————————————————————* def argumentIsString (argument) : if sys.version_info < (3,0): return isinstance (argument, types.StringTypes) else: return type (argument) is str #——————————————————————————————————————————————————————————————————————————————————————————————————————————————————————* # FOR PRINTING IN COLOR * #——————————————————————————————————————————————————————————————————————————————————————————————————————————————————————* def BLACK () : return '\033[90m' #······················································································································* def RED () : return '\033[91m' #······················································································································* def GREEN () : return '\033[92m' #······················································································································* def YELLOW () : return '\033[93m' #······················································································································* def BLUE () : return '\033[94m' #······················································································································* def MAGENTA () : return '\033[95m' #······················································································································* def CYAN () : return '\033[96m' #······················································································································* def WHITE () : return '\033[97m' #······················································································································* def ENDC () : return '\033[0m' #······················································································································* def BOLD () : return '\033[1m' #······················································································································* def UNDERLINE () : return '\033[4m' #······················································································································* def BLINK () : return '\033[5m' #······················································································································* def BOLD_BLUE () : return BOLD () + BLUE () #······················································································································* def BOLD_GREEN () : return BOLD () + GREEN () #······················································································································* def BOLD_RED () : return BOLD () + RED () #——————————————————————————————————————————————————————————————————————————————————————————————————————————————————————* # runHiddenCommand * #——————————————————————————————————————————————————————————————————————————————————————————————————————————————————————* def runHiddenCommand (cmd, logUtilityTool=False) : executable = find_executable (cmd [0]) if executable == None: print (BOLD_RED () + "*** Cannot find '" + cmd[0] + "' executable ***" + ENDC ()) sys.exit (1) if logUtilityTool: print ("Utility tool is '" + executable + "'") result = "" childProcess = subprocess.Popen (cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True) while True: line = childProcess.stdout.readline () if line != "": result += line else: childProcess.wait () if childProcess.returncode != 0 : sys.exit (childProcess.returncode) return result #——————————————————————————————————————————————————————————————————————————————————————————————————————————————————————* # runCommand * #——————————————————————————————————————————————————————————————————————————————————————————————————————————————————————* def runCommand (cmd, title, showCommand, logUtilityTool) : if title != "": print (BOLD_BLUE () + title + ENDC ()) if (title == "") or showCommand : cmdAsString = "" for s in cmd: if (s == "") or (s.find (" ") >= 0): cmdAsString += '"' + s + '" ' else: cmdAsString += s + ' ' print (cmdAsString) executable = find_executable (cmd [0]) if executable == None: print (BOLD_RED () + "*** Cannot find '" + cmd[0] + "' executable ***" + ENDC ()) sys.exit (1) if logUtilityTool: print ("Utility tool is '" + executable + "'") returncode = subprocess.call (cmd) if returncode != 0 : sys.exit (returncode) #——————————————————————————————————————————————————————————————————————————————————————————————————————————————————————* # runInThread * #——————————————————————————————————————————————————————————————————————————————————————————————————————————————————————* def runInThread (job, displayLock, terminationSemaphore): executable = find_executable (job.mCommand [0]) if executable == None: print (BOLD_RED () + "*** Cannot find '" + job.mCommand[0] + "' executable ***" + ENDC ()) job.mReturnCode = 1 terminationSemaphore.release () else: if job.mLogUtilityTool : print ("Utility tool is '" + executable + "'") childProcess = subprocess.Popen (job.mCommand, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True) while True: line = childProcess.stdout.readline () if line != "": job.mOutputLines.append (line) displayLock.acquire () sys.stdout.write (line) # Print without newline displayLock.release () else: childProcess.wait () job.mReturnCode = childProcess.returncode terminationSemaphore.release () break #——————————————————————————————————————————————————————————————————————————————————————————————————————————————————————* # modificationDateForFile * #——————————————————————————————————————————————————————————————————————————————————————————————————————————————————————* def modificationDateForFile (dateCacheDictionary, file): absFilePath = os.path.abspath (file) if absFilePath in dateCacheDictionary : return dateCacheDictionary [absFilePath] elif not os.path.exists (absFilePath): date = sys.float_info.max # Very far in future dateCacheDictionary [absFilePath] = date return date else: date = os.path.getmtime (absFilePath) dateCacheDictionary [absFilePath] = date return date #——————————————————————————————————————————————————————————————————————————————————————————————————————————————————————* # class PostCommand * #——————————————————————————————————————————————————————————————————————————————————————————————————————————————————————* class PostCommand: mCommand = [] mTitle = "" #····················································································································* def __init__ (self, title = ""): self.mCommand = [] self.mTitle = title #——————————————————————————————————————————————————————————————————————————————————————————————————————————————————————* # class Job * #——————————————————————————————————————————————————————————————————————————————————————————————————————————————————————* class Job: mTargets = [] mCommand = [] mTitle = "" mRequiredFiles = [] mPostCommands = [] mReturnCode = None mPriority = 0 mState = 0 # 0: waiting for execution mOutputLines = [] mOpenSourceOnError = False # Do not try to open source file on error mLogUtilityTool = False #····················································································································* def __init__ (self, targets, requiredFiles, command, postCommands, priority, title, openSourceOnError, logUtilityTool): self.mTargets = copy.deepcopy (targets) self.mCommand = copy.deepcopy (command) self.mRequiredFiles = copy.deepcopy (requiredFiles) self.mTitle = copy.deepcopy (title) self.mPostCommands = copy.deepcopy (postCommands) self.mPriority = priority self.mOutputLines = [] self.mOpenSourceOnError = openSourceOnError self.mLogUtilityTool = logUtilityTool #····················································································································* def run (self, displayLock, terminationSemaphore, showCommand, progressString): displayLock.acquire () if self.mTitle != "": print (progressString + BOLD_BLUE () + self.mTitle + ENDC ()) if (self.mTitle == "") or showCommand : cmdAsString = "" for s in self.mCommand: if (s == "") or (s.find (" ") >= 0): cmdAsString += '"' + s + '" ' else: cmdAsString += s + ' ' print (progressString + cmdAsString) displayLock.release () thread = threading.Thread (target=runInThread, args=(self, displayLock, terminationSemaphore)) thread.start() #····················································································································* def runPostCommand (self, displayLock, terminationSemaphore, showCommand): postCommand = self.mPostCommands [0] self.mCommand = postCommand.mCommand displayLock.acquire () print (BOLD_BLUE () + postCommand.mTitle + ENDC ()) if showCommand: cmdAsString = "" for s in self.mCommand: if (s == "") or (s.find (" ") >= 0): cmdAsString += '"' + s + '" ' else: cmdAsString += s + ' ' print (cmdAsString) displayLock.release () thread = threading.Thread (target=runInThread, args=(self, displayLock, terminationSemaphore)) thread.start() #——————————————————————————————————————————————————————————————————————————————————————————————————————————————————————* # class Rule * #——————————————————————————————————————————————————————————————————————————————————————————————————————————————————————* class Rule: mTargets = [] mDependences = [] mCommand = [] mSecondaryMostRecentModificationDate = 0.0 # Far in the past mTitle = "" mPostCommands = [] mPriority = 0 mDeleteTargetOnError = False # No operation on error mCleanOperation = 0 # No operation on clean mOpenSourceOnError = False # Do not try to open source file on error #····················································································································* def __init__ (self, targets, title = ""): # print ("Rule '" + title + "'") if not type (targets) is list: print (BOLD_RED () + "*** Rule type instanciation: first argument 'targets' is not a list ***" + ENDC ()) traceback.print_stack () sys.exit (1) else: for aTarget in targets: # print (" Target '" + aTarget + "'") if not argumentIsString (aTarget): print (BOLD_RED () + "*** Rule type instanciation: an element of first argument 'targets' is not a string ***" + ENDC ()) traceback.print_stack () sys.exit (1) if not argumentIsString (title): print (BOLD_RED () + "*** Rule type instanciation: second argument 'title' is not a string ***" + ENDC ()) traceback.print_stack () sys.exit (1) self.mTargets = copy.deepcopy (targets) self.mDependences = [] self.mCommand = [] self.mSecondaryMostRecentModificationDate = 0.0 self.mPostCommands = [] self.mPriority = 0 self.mDeleteTargetOnError = False # No operation on error self.mOpenSourceOnError = False # Do not try to open source file on error self.mCleanOperation = 0 # No operation on clean if title == "": self.mTitle = "Building" for s in targets: self.mTitle += " " + s else: self.mTitle = copy.deepcopy (title) #····················································································································* def deleteTargetFileOnClean (self): self.mCleanOperation = 1 #····················································································································* def deleteTargetDirectoryOnClean (self): self.mCleanOperation = 2 #····················································································································* def enterSecondaryDependanceFile (self, secondaryDependanceFile, make): if not argumentIsString (secondaryDependanceFile): print (BOLD_RED () + "*** Rule.enterSecondaryDependanceFile: 'secondaryDependanceFile' argument is not a string ***" + ENDC ()) traceback.print_stack () sys.exit (1) if make.mSelectedGoal != "clean": filePath = os.path.abspath (secondaryDependanceFile) if not os.path.exists (filePath): self.mSecondaryMostRecentModificationDate = sys.float_info.max # Very far in future else: f = open (filePath, "r") s = f.read () f.close () s = s.replace ("\\ ", "\x01") # Replace escaped spaces by \0x01 s = s.replace ("\\\n", "") # Suppress \ at the end of lines liste = s.split ("\n\n") # print ("DEP " + secondaryDependanceFile) for s in liste: # print ("S " + s) components = s.split (':') # print (str (len (components))) #target = components [0].replace ("\x01", " ") #print ("------- Optional dependency rules for target '" + target + "'") #print ("Secondary target '" + target + "'") if len (components) > 1 : for src in components [1].split (): secondarySource = src.replace ("\x01", " ") # print (" SECONDARY SOURCE '" + secondarySource + "'") modifDate = modificationDateForFile (make.mModificationDateDictionary, secondarySource) if self.mSecondaryMostRecentModificationDate < modifDate : self.mSecondaryMostRecentModificationDate = modifDate #print (BOLD_BLUE () + str (modifDate) + ENDC ()) #——————————————————————————————————————————————————————————————————————————————————————————————————————————————————————* # class Make * #——————————————————————————————————————————————————————————————————————————————————————————————————————————————————————* class Make: mRuleList = [] mJobList = [] mErrorCount = 0 mModificationDateDictionary = {} mGoals = {} mSelectedGoal = "" mLinuxTextEditor = "" mMacTextEditor = "" mSimulateClean = False mLogUtilityTool = True mShowProgressString = True #····················································································································* def __init__ (self, goal, logUtilityTool=False): if not argumentIsString (goal): print (BOLD_RED () + "*** Make instanciation: 'goal' argument is not a string ***" + ENDC ()) traceback.print_stack () sys.exit (1) self.mRuleList = [] self.mJobList = [] self.mErrorCount = 0 self.mModificationDateDictionary = {} self.mGoals = {} self.mSelectedGoal = goal self.mLinuxTextEditor = "gEdit" self.mMacTextEditor = "TextEdit" self.mLogUtilityTool = logUtilityTool #····················································································································* def doNotShowProgressString (self) : self.mShowProgressString = False #····················································································································* def addRule (self, rule): if not isinstance (rule, Rule): print (BOLD_RED () + "*** Make.addRule: 'rule' argument is not an instance of Rule type ***" + ENDC ()) traceback.print_stack () sys.exit (1) self.mRuleList.append (copy.deepcopy (rule)) #····················································································································* def printRules (self): print (BOLD_BLUE () + "--- Print " + str (len (self.mRuleList)) + " rule" + ("s" if len (self.mRuleList) > 1 else "") + " ---" + ENDC ()) for rule in self.mRuleList: message = "" for s in rule.mTargets: message += " \"" + s + "\"" print (BOLD_GREEN () + "Target:" + message + ENDC ()) for dep in rule.mDependences: print (" Dependence: \"" + dep + "\"") s = " Command: " for cmd in rule.mCommand: s += " \"" + cmd + "\"" print (s) print (" Title: \"" + rule.mTitle + "\"") print (" Delete target on error: " + ("yes" if rule.mDeleteTargetOnError else "no")) cleanOp = "none" if rule.mCleanOperation == 1: cleanOp = "delete target file(s)" elif rule.mCleanOperation == 2: dirSet = set () for s in rule.mTargets: path = os.path.dirname (s) if path != "": dirSet.add (path) cleanOp = "delete target directory:" for s in dirSet: cleanOp += " \"" + s + "\"" print (" Clean operation: " + cleanOp) index = 0 for postCommand in rule.mPostCommands: index = index + 1 s = " Post command " + str (index) + ": " for cmd in postCommand.mCommand: s += " \"" + cmd + "\"" print (s) print (" Title: \"" + postCommand.mTitle + "\"") print (BOLD_BLUE () + "--- End of print rule ---" + ENDC ()) #····················································································································* def writeRuleDependancesInDotFile (self, dotFileName): s = "digraph G {\n" s += " node [fontname=courier]\n" arrowSet = set () for rule in self.mRuleList: for target in rule.mTargets: s += ' "' + target + '" [shape=rectangle]\n' for dep in rule.mDependences: arrowSet.add (' "' + target + '" -> "' + dep + '"\n') for arrow in arrowSet: s += arrow s += "}\n" f = open (dotFileName, "w") f.write (s) f.close () #····················································································································* def checkRules (self): if self.mErrorCount == 0: ruleList = copy.deepcopy (self.mRuleList) index = 0 looping = True #--- loop on rules while looping: looping = False while index < len (ruleList): aRule = ruleList [index] index = index + 1 #--- Check dependance files have rule for building, or does exist depIdx = 0 while depIdx < len (aRule.mDependences): dep = aRule.mDependences [depIdx] depIdx = depIdx + 1 hasBuildRule = False for r in ruleList: for target in r.mTargets: if dep == target: hasBuildRule = True break if not hasBuildRule: looping = True if not os.path.exists (os.path.abspath (dep)): self.mErrorCount = self.mErrorCount + 1 print (BOLD_RED () + "Check rules error: '" + dep + "' does not exist, and there is no rule for building it." + ENDC ()) depIdx = depIdx - 1 aRule.mDependences.pop (depIdx) #--- Rule with no dependances if len (aRule.mDependences) == 0 : looping = True index = index - 1 ruleList.pop (index) idx = 0 while idx < len (ruleList): r = ruleList [idx] idx = idx + 1 for target in aRule.mTargets: while r.mDependences.count (target) > 0 : r.mDependences.remove (target) #--- Error if rules remain if len (ruleList) > 0: self.mErrorCount = self.mErrorCount + 1 print (BOLD_RED () + "Check rules error; circulary dependances between:" + ENDC ()) for aRule in ruleList: targetList = "" for target in aRule.mTargets: targetList += " '" + aRule.mTarget + "'" print (BOLD_RED () + " - " + targetList + ", depends from:" + ENDC ()) for dep in aRule.mDependences: print (BOLD_RED () + " '" + dep + "'" + ENDC ()) #····················································································································* def existsJobForTarget (self, target): for job in self.mJobList: for aTarget in job.mTargets: if aTarget == target: return True return False #····················································································································* def makeJob (self, target): # Return a bool indicating wheither the target should be built #--- If there are errors, return immediatly if self.mErrorCount != 0: return False #--- Target already in job list ? if self.existsJobForTarget (target): return True # yes, return target will be built #--- Find a rule for making the target absTarget = os.path.abspath (target) rule = None matchCount = 0 for r in self.mRuleList: for aTarget in r.mTargets: if target == aTarget: matchCount = matchCount + 1 rule = r if matchCount == 0: absTarget = os.path.abspath (target) if not os.path.exists (absTarget): print (BOLD_RED () + "No rule for making '" + target + "'" + ENDC ()) self.mErrorCount = self.mErrorCount + 1 return False # Error or target exists, and no rule for building it elif matchCount > 1: print (BOLD_RED () + str (matchCount) + " rules for making '" + target + "'" + ENDC ()) self.mErrorCount = self.mErrorCount + 1 return False # Error #--- Target file does not exist, and 'rule' variable indicates how build it appendToJobList = not os.path.exists (absTarget) #--- Build primary dependences jobDependenceFiles = [] for dependence in rule.mDependences: willBeBuilt = self.makeJob (dependence) if willBeBuilt: jobDependenceFiles.append (dependence) appendToJobList = True #--- Check primary file modification dates if not appendToJobList: targetDateModification = os.path.getmtime (absTarget) for source in rule.mDependences: sourceDateModification = os.path.getmtime (source) if targetDateModification < sourceDateModification: appendToJobList = True break #--- Check for secondary dependancy files if not appendToJobList: targetDateModification = os.path.getmtime (absTarget) if targetDateModification < rule.mSecondaryMostRecentModificationDate: appendToJobList = True #--- Append to job list if appendToJobList: self.mJobList.append (Job ( rule.mTargets, jobDependenceFiles, rule.mCommand, rule.mPostCommands, rule.mPriority, rule.mTitle, rule.mOpenSourceOnError, self.mLogUtilityTool )) #--- Return return appendToJobList #····················································································································* #Job state # 0: waiting # 1:running # 2: waiting for executing post command # 3:executing for executing post command # 4: completed def runJobs (self, maxConcurrentJobs, showCommand): if self.mErrorCount == 0: if len (self.mJobList) == 0: print (BOLD_BLUE () + "Nothing to make." + ENDC ()) else: #--- Sort jobs following their priorities self.mJobList = sorted (self.mJobList, key=operator.attrgetter("mPriority"), reverse=True) #--- Run if maxConcurrentJobs <= 0: maxConcurrentJobs = processorCount () - maxConcurrentJobs jobCount = 0 ; terminationSemaphore = threading.Semaphore (0) displayLock = threading.Lock () loop = True returnCode = 0 totalJobCount = float (len (self.mJobList)) launchedJobCount = 0.0 while loop: #--- Launch jobs in parallel for job in self.mJobList: if (returnCode == 0) and (jobCount < maxConcurrentJobs): if (job.mState == 0) and (len (job.mRequiredFiles) == 0): #--- Create target directory if does not exist for aTarget in job.mTargets: absTargetDirectory = os.path.dirname (os.path.abspath (aTarget)) if not os.path.exists (absTargetDirectory): displayLock.acquire () runCommand ( ["mkdir", "-p", os.path.dirname (aTarget)], "Making \"" + os.path.dirname (aTarget) + "\" directory", showCommand, job.mLogUtilityTool ) displayLock.release () #--- Progress string launchedJobCount += 1.0 if self.mShowProgressString: progressString = "[{0:3d}%] ".format (int (100.0 * launchedJobCount / totalJobCount)) else: progressString = "" #--- Run job job.run (displayLock, terminationSemaphore, showCommand, progressString) jobCount = jobCount + 1 job.mState = 1 # Means is running elif job.mState == 2: # Waiting for executing post command job.mReturnCode = None # Means post command not terminated job.runPostCommand (displayLock, terminationSemaphore, showCommand) jobCount = jobCount + 1 job.mState = 3 # Means post command is running #--- Wait for a job termination #print ("wait " + str (jobCount) + " " + str (len (self.mJobList))) terminationSemaphore.acquire () #--- Checks for terminated jobs index = 0 while index < len (self.mJobList): job = self.mJobList [index] index = index + 1 if (job.mState == 1) and (job.mReturnCode != None) and (job.mReturnCode == 0) : # Terminated without error jobCount = jobCount - 1 for aTarget in job.mTargets: if not os.path.exists (os.path.abspath (aTarget)): # Warning: target does not exist displayLock.acquire () print (MAGENTA () + BOLD () + "Warning: target \"" + aTarget + "\" was not created by rule execution." + ENDC ()) displayLock.release () if len (job.mPostCommands) > 0: job.mState = 2 # Ready to execute next post command else: job.mState = 4 # Completed index = index - 1 # For removing job from list elif (job.mState == 1) and (job.mReturnCode != None) and (job.mReturnCode > 0) : # terminated with error : exit jobCount = jobCount - 1 job.mState = 4 # Means Terminated index = index - 1 # For removing job from list if job.mOpenSourceOnError: for line in job.mOutputLines: components = line.split (':') if (len (components) > 1) and os.path.exists (os.path.abspath (components [0])) : if sys.platform == "darwin": os.system ("open -a \"" + self.mMacTextEditor + "\" \"" + components [0] + "\"") elif sys.platform == "linux2": os.system ("\"" + self.mLinuxTextEditor + "\" \"" + components [0] + "\"") elif (job.mState == 3) and (job.mReturnCode == 0): # post command is terminated without error jobCount = jobCount - 1 job.mPostCommands.pop (0) # Remove completed post command if len (job.mPostCommands) > 0: job.mState = 2 # Ready to execute next post command else: job.mState = 4 # Completed index = index - 1 # For removing job from list elif (job.mState == 3) and (job.mReturnCode > 0): # post command is terminated with error jobCount = jobCount - 1 job.mState = 4 # Completed index = index - 1 # For removing job from list elif job.mState == 4: # Completed: delete job index = index - 1 self.mJobList.pop (index) # Remove terminated job #displayLock.acquire () #print ("Completed '" + job.mTitle + "'") #--- Remove dependences from this job idx = 0 while idx < len (self.mJobList): aJob = self.mJobList [idx] idx = idx + 1 for aTarget in job.mTargets: while aJob.mRequiredFiles.count (aTarget) > 0 : aJob.mRequiredFiles.remove (aTarget) #print (" Removed from '" + aJob.mTitle + "': " + str (len (aJob.mRequiredFiles))) #displayLock.release () #--- Signal error ? if (job.mReturnCode > 0) and (returnCode == 0): self.mErrorCount = self.mErrorCount + 1 print (BOLD_RED () + "Return code: " + str (job.mReturnCode) + ENDC ()) if (returnCode == 0) and (jobCount > 0) : print ("Wait for job termination...") returnCode = job.mReturnCode loop = (len (self.mJobList) > 0) if (returnCode == 0) else (jobCount > 0) #····················································································································* def searchFileInRelativeDirectories (self, file, directoryList): # returns "" if not found, register error matchCount = 0 result = "" for sourceDir in directoryList: sourcePath = sourceDir + "/" + file if os.path.exists (os.path.abspath (sourcePath)): matchCount = matchCount + 1 prefix = os.path.commonprefix ([os.getcwd (), sourcePath]) result = sourcePath [len (prefix):] if result [0] == '/' : result = result [1:] if matchCount == 0: print (BOLD_RED () + "Cannot find '" + file + "'" + ENDC ()) self.mErrorCount = self.mErrorCount + 1 elif matchCount > 1: print (BOLD_RED () + str (matchCount) + " source files for making '" + file + "'" + ENDC ()) self.mErrorCount = self.mErrorCount + 1 result = "" return result #····················································································································* def searchFileInDirectories (self, file, directoryList): # returns "" if not found, register error matchCount = 0 result = "" for sourceDir in directoryList: sourcePath = sourceDir + "/" + file if os.path.exists (os.path.abspath (sourcePath)): matchCount = matchCount + 1 result = sourcePath if matchCount == 0: print (BOLD_RED () + "Cannot find '" + file + "'" + ENDC ()) self.mErrorCount = self.mErrorCount + 1 elif matchCount > 1: print (BOLD_RED () + str (matchCount) + " source files for making '" + file + "'" + ENDC ()) self.mErrorCount = self.mErrorCount + 1 result = "" return result #····················································································································* def addGoal (self, goal, targetList, message): if not argumentIsString (goal): print (BOLD_RED () + "*** Make.addGoal: 'goal' first argument is not a string ***" + ENDC ()) traceback.print_stack () sys.exit (1) if not type (targetList) is list: print (BOLD_RED () + "*** Make.addGoal: 'targetList' second argument is not a list ***" + ENDC ()) traceback.print_stack () sys.exit (1) else: for aTarget in targetList: if not argumentIsString (aTarget): print (BOLD_RED () + "*** Make.addGoal: an element of 'targetList' second argument 'targets' is not a string ***" + ENDC ()) traceback.print_stack () sys.exit (1) if not argumentIsString (message): print (BOLD_RED () + "*** Make.addGoal: 'message' third argument is not a string ***" + ENDC ()) traceback.print_stack () sys.exit (1) if (goal in self.mGoals) or (goal == "clean") : self.enterError ("The '" + goal + "' goal is already defined") else: self.mGoals [goal] = (targetList, message) #print ('%s' % ', '.join(map(str, self.mGoals))) #····················································································································* def printGoals (self): print (BOLD_BLUE () + "--- Print " + str (len (self.mGoals)) + " goal" + ("s" if len (self.mGoals) > 1 else "") + " ---" + ENDC ()) for goalKey in self.mGoals.keys (): print (BOLD_GREEN () + "Goal: \"" + goalKey + "\"" + ENDC ()) (targetList, message) = self.mGoals [goalKey] for target in targetList: print (" Target: \"" + target + "\"") print (" Message: \"" + message + "\"") print (BOLD_BLUE () + "--- End of print goals ---" + ENDC ()) #····················································································································* def runGoal (self, maxConcurrentJobs, showCommand): if not isinstance (maxConcurrentJobs, int): print (BOLD_RED () + "*** Make.runGoal: 'maxConcurrentJobs' first argument is not an integer ***" + ENDC ()) traceback.print_stack () sys.exit (1) if not isinstance (showCommand, bool): print (BOLD_RED () + "*** Make.runGoal: 'showCommand' second argument is not a boolean ***" + ENDC ()) traceback.print_stack () sys.exit (1) if self.mSelectedGoal in self.mGoals : (targetList, message) = self.mGoals [self.mSelectedGoal] for target in targetList: self.makeJob (target) self.runJobs (maxConcurrentJobs, showCommand) if self.mErrorCount > 0: for rule in self.mRuleList: for aTarget in rule.mTargets: if rule.mDeleteTargetOnError and os.path.exists (os.path.abspath (aTarget)): runCommand (["rm", aTarget], "Delete \"" + aTarget + "\" on error", showCommand, self.mLogUtilityTool) elif self.mSelectedGoal == "clean" : filesToRemoveList = [] directoriesToRemoveSet = set () for rule in self.mRuleList: if rule.mCleanOperation == 1: # Delete target for aTarget in rule.mTargets: filesToRemoveList.append (aTarget) elif rule.mCleanOperation == 2: # Delete target directories for aTarget in rule.mTargets: dirPath = os.path.dirname (aTarget) if dirPath == "": filesToRemoveList.append (aTarget) else: directoriesToRemoveSet.add (dirPath) for dir in directoriesToRemoveSet: if os.path.exists (os.path.abspath (dir)): if self.mSimulateClean: print (MAGENTA () + BOLD () + "Simulated clean command: " + ENDC () + "rm -fr '" + dir + "'") else: runCommand (["rm", "-fr", dir], "Removing \"" + dir + "\"", showCommand, self.mLogUtilityTool) for file in filesToRemoveList: if os.path.exists (os.path.abspath (file)): if self.mSimulateClean: print (MAGENTA () + BOLD () + "Simulated clean command: " + ENDC () + "rm -f '" + file + "'") else: runCommand (["rm", "-f", file], "Deleting \"" + file + "\"", showCommand, self.mLogUtilityTool) else: errorMessage = "The '" + self.mSelectedGoal + "' goal is not defined; defined goals:" for key in self.mGoals: (targetList, message) = self.mGoals [key] errorMessage += "\n '" + key + "': " + message print (BOLD_RED () + errorMessage + ENDC ()) self.mErrorCount = self.mErrorCount + 1 #····················································································································* def simulateClean (self): self.mSimulateClean = True #····················································································································* def enterError (self, message): print (BOLD_RED () + message + ENDC ()) self.mErrorCount = self.mErrorCount + 1 #····················································································································* def printErrorCountAndExitOnError (self): if self.mErrorCount == 1: print (BOLD_RED () + "1 error." + ENDC ()) sys.exit (1) elif self.mErrorCount > 1: print (BOLD_RED () + str (self.mErrorCount) + " errors." + ENDC ()) sys.exit (1) #····················································································································* def printErrorCount (self): if self.mErrorCount == 1: print (BOLD_RED () + "1 error." + ENDC ()) elif self.mErrorCount > 1: print (BOLD_RED () + str (self.mErrorCount) + " errors." + ENDC ()) #····················································································································* def errorCount (self): return self.mErrorCount #——————————————————————————————————————————————————————————————————————————————————————————————————————————————————————*