123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976 |
- #! /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 <techtonik@gmail.com> *
- #——————————————————————————————————————————————————————————————————————————————————————————————————————————————————————*
- 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
- #——————————————————————————————————————————————————————————————————————————————————————————————————————————————————————*
|