15b6bfdb9SJed Brownfrom __future__ import absolute_import 2179860b2SJed Brownimport args 3179860b2SJed Brownimport sys 4179860b2SJed Brownimport os 59561296dSJacob Faibussowitschimport textwrap 6179860b2SJed Brown 7179860b2SJed Brown# Ugly stuff to have curses called ONLY once, instead of for each 8179860b2SJed Brown# new Configure object created (and flashing the screen) 9179860b2SJed Brownglobal LineWidth 10179860b2SJed Brownglobal RemoveDirectory 11179860b2SJed Brownglobal backupRemoveDirectory 12179860b2SJed BrownLineWidth = -1 13179860b2SJed BrownRemoveDirectory = os.path.join(os.getcwd(),'') 14179860b2SJed BrownbackupRemoveDirectory = '' 15179860b2SJed Brown 16179860b2SJed Brownclass Logger(args.ArgumentProcessor): 17179860b2SJed Brown '''This class creates a shared log and provides methods for writing to it''' 18179860b2SJed Brown defaultLog = None 19179860b2SJed Brown defaultOut = sys.stdout 20179860b2SJed Brown 21179860b2SJed Brown def __init__(self, clArgs = None, argDB = None, log = None, out = defaultOut, debugLevel = None, debugSections = None, debugIndent = None): 22179860b2SJed Brown args.ArgumentProcessor.__init__(self, clArgs, argDB) 23179860b2SJed Brown self.logName = None 24179860b2SJed Brown self.log = log 25179860b2SJed Brown self.out = out 26179860b2SJed Brown self.debugLevel = debugLevel 27179860b2SJed Brown self.debugSections = debugSections 28179860b2SJed Brown self.debugIndent = debugIndent 299561296dSJacob Faibussowitsch self.dividerLength = 93 30179860b2SJed Brown self.getRoot() 31179860b2SJed Brown return 32179860b2SJed Brown 33179860b2SJed Brown def __getstate__(self): 34179860b2SJed Brown '''We do not want to pickle the default log stream''' 35179860b2SJed Brown d = args.ArgumentProcessor.__getstate__(self) 36b59d1e59SMatthew G. Knepley if 'logBkp' in d: 37b59d1e59SMatthew G. Knepley del d['logBkp'] 38179860b2SJed Brown if 'log' in d: 39179860b2SJed Brown if d['log'] is Logger.defaultLog: 40179860b2SJed Brown del d['log'] 41179860b2SJed Brown else: 42179860b2SJed Brown d['log'] = None 43179860b2SJed Brown if 'out' in d: 44179860b2SJed Brown if d['out'] is Logger.defaultOut: 45179860b2SJed Brown del d['out'] 46179860b2SJed Brown else: 47179860b2SJed Brown d['out'] = None 48179860b2SJed Brown return d 49179860b2SJed Brown 50179860b2SJed Brown def __setstate__(self, d): 51179860b2SJed Brown '''We must create the default log stream''' 52179860b2SJed Brown args.ArgumentProcessor.__setstate__(self, d) 53179860b2SJed Brown if not 'log' in d: 54179860b2SJed Brown self.log = self.createLog(None) 55179860b2SJed Brown if not 'out' in d: 56179860b2SJed Brown self.out = Logger.defaultOut 57179860b2SJed Brown self.__dict__.update(d) 58179860b2SJed Brown return 59179860b2SJed Brown 60179860b2SJed Brown def setupArguments(self, argDB): 61179860b2SJed Brown '''Setup types in the argument database''' 62179860b2SJed Brown import nargs 63179860b2SJed Brown 64179860b2SJed Brown argDB = args.ArgumentProcessor.setupArguments(self, argDB) 6503e6d329SSatish Balay argDB.setType('log', nargs.Arg(None, 'buildsystem.log', 'The filename for the log')) 66179860b2SJed Brown argDB.setType('logAppend', nargs.ArgBool(None, 0, 'The flag determining whether we backup or append to the current log', isTemporary = 1)) 67179860b2SJed Brown argDB.setType('debugLevel', nargs.ArgInt(None, 3, 'Integer 0 to 4, where a higher level means more detail', 0, 5)) 68179860b2SJed Brown argDB.setType('debugSections', nargs.Arg(None, [], 'Message types to print, e.g. [compile,link,hg,install]')) 69179860b2SJed Brown argDB.setType('debugIndent', nargs.Arg(None, ' ', 'The string used for log indentation')) 70179860b2SJed Brown argDB.setType('scrollOutput', nargs.ArgBool(None, 0, 'Flag to allow output to scroll rather than overwriting a single line')) 71179860b2SJed Brown argDB.setType('noOutput', nargs.ArgBool(None, 0, 'Flag to suppress output to the terminal')) 72179860b2SJed Brown return argDB 73179860b2SJed Brown 74179860b2SJed Brown def setup(self): 75179860b2SJed Brown '''Setup the terminal output and filtering flags''' 76179860b2SJed Brown self.log = self.createLog(self.logName, self.log) 77179860b2SJed Brown args.ArgumentProcessor.setup(self) 78179860b2SJed Brown 79179860b2SJed Brown if self.argDB['noOutput']: 80179860b2SJed Brown self.out = None 81179860b2SJed Brown if self.debugLevel is None: 82179860b2SJed Brown self.debugLevel = self.argDB['debugLevel'] 83179860b2SJed Brown if self.debugSections is None: 84179860b2SJed Brown self.debugSections = self.argDB['debugSections'] 85179860b2SJed Brown if self.debugIndent is None: 86179860b2SJed Brown self.debugIndent = self.argDB['debugIndent'] 87179860b2SJed Brown return 88179860b2SJed Brown 89179860b2SJed Brown def checkLog(self, logName): 90179860b2SJed Brown import nargs 91179860b2SJed Brown import os 92179860b2SJed Brown 93179860b2SJed Brown if logName is None: 94179860b2SJed Brown logName = nargs.Arg.findArgument('log', self.clArgs) 95179860b2SJed Brown if logName is None: 96179860b2SJed Brown if not self.argDB is None and 'log' in self.argDB: 97179860b2SJed Brown logName = self.argDB['log'] 98179860b2SJed Brown else: 99179860b2SJed Brown logName = 'default.log' 100179860b2SJed Brown self.logName = logName 101179860b2SJed Brown self.logExists = os.path.exists(self.logName) 102179860b2SJed Brown return self.logExists 103179860b2SJed Brown 104179860b2SJed Brown def createLog(self, logName, initLog = None): 105179860b2SJed Brown '''Create a default log stream, unless initLog is given''' 106179860b2SJed Brown import nargs 107179860b2SJed Brown 108179860b2SJed Brown if not initLog is None: 109179860b2SJed Brown log = initLog 110179860b2SJed Brown else: 111179860b2SJed Brown if Logger.defaultLog is None: 112179860b2SJed Brown appendArg = nargs.Arg.findArgument('logAppend', self.clArgs) 113179860b2SJed Brown if self.checkLog(logName): 114179860b2SJed Brown if not self.argDB is None and ('logAppend' in self.argDB and self.argDB['logAppend']) or (not appendArg is None and bool(appendArg)): 115c6ef1b5bSJed Brown Logger.defaultLog = open(self.logName, 'a') 116179860b2SJed Brown else: 117179860b2SJed Brown try: 118179860b2SJed Brown import os 119179860b2SJed Brown 120179860b2SJed Brown os.rename(self.logName, self.logName+'.bkp') 121c6ef1b5bSJed Brown Logger.defaultLog = open(self.logName, 'w') 122179860b2SJed Brown except OSError: 12315ac2963SJed Brown sys.stdout.write('WARNING: Cannot backup log file, appending instead.\n') 124c6ef1b5bSJed Brown Logger.defaultLog = open(self.logName, 'a') 125179860b2SJed Brown else: 126c6ef1b5bSJed Brown Logger.defaultLog = open(self.logName, 'w') 127179860b2SJed Brown log = Logger.defaultLog 128179860b2SJed Brown return log 129179860b2SJed Brown 130179860b2SJed Brown def closeLog(self): 131179860b2SJed Brown '''Closes the log file''' 132179860b2SJed Brown self.log.close() 133179860b2SJed Brown 134a75b4e77SMatthew G. Knepley def saveLog(self): 135dc0f114dSBarry Smith if self.debugLevel <= 3: return 1362d964b9fSJed Brown import io 137a75b4e77SMatthew G. Knepley self.logBkp = self.log 1382d964b9fSJed Brown self.log = io.StringIO() 139a75b4e77SMatthew G. Knepley 140a75b4e77SMatthew G. Knepley def restoreLog(self): 141dc0f114dSBarry Smith if self.debugLevel <= 3: return 142a75b4e77SMatthew G. Knepley s = self.log.getvalue() 143a75b4e77SMatthew G. Knepley self.log.close() 144a75b4e77SMatthew G. Knepley self.log = self.logBkp 145a75b4e77SMatthew G. Knepley del(self.logBkp) 146a75b4e77SMatthew G. Knepley return s 147a75b4e77SMatthew G. Knepley 148179860b2SJed Brown def getLinewidth(self): 149179860b2SJed Brown global LineWidth 150179860b2SJed Brown if not hasattr(self, '_linewidth'): 151179860b2SJed Brown if self.out is None or not self.out.isatty() or self.argDB['scrollOutput']: 152179860b2SJed Brown self._linewidth = -1 153179860b2SJed Brown else: 154179860b2SJed Brown if LineWidth == -1: 155179860b2SJed Brown try: 156179860b2SJed Brown import curses 157179860b2SJed Brown 158179860b2SJed Brown try: 159179860b2SJed Brown curses.setupterm() 160179860b2SJed Brown (y, self._linewidth) = curses.initscr().getmaxyx() 161179860b2SJed Brown curses.endwin() 162179860b2SJed Brown except curses.error: 163179860b2SJed Brown self._linewidth = -1 164179860b2SJed Brown except: 165179860b2SJed Brown self._linewidth = -1 166179860b2SJed Brown LineWidth = self._linewidth 167179860b2SJed Brown else: 168179860b2SJed Brown self._linewidth = LineWidth 169179860b2SJed Brown return self._linewidth 170179860b2SJed Brown def setLinewidth(self, linewidth): 171179860b2SJed Brown self._linewidth = linewidth 172179860b2SJed Brown return 173179860b2SJed Brown linewidth = property(getLinewidth, setLinewidth, doc = 'The maximum number of characters per log line') 174179860b2SJed Brown 175179860b2SJed Brown def checkWrite(self, f, debugLevel, debugSection, writeAll = 0): 176179860b2SJed Brown '''Check whether the log line should be written 177179860b2SJed Brown - If writeAll is true, return true 178179860b2SJed Brown - If debugLevel >= current level, and debugSection in current section or sections is empty, return true''' 179179860b2SJed Brown if not isinstance(debugLevel, int): 180179860b2SJed Brown raise RuntimeError('Debug level must be an integer: '+str(debugLevel)) 181179860b2SJed Brown if f is None: 182179860b2SJed Brown return False 183179860b2SJed Brown if writeAll: 184179860b2SJed Brown return True 185179860b2SJed Brown if self.debugLevel >= debugLevel and (not len(self.debugSections) or debugSection in self.debugSections): 186179860b2SJed Brown return True 187179860b2SJed Brown return False 188179860b2SJed Brown 189*ce040abeSJacob Faibussowitsch def checkANSIEscapeSequences(self, ostream): 190*ce040abeSJacob Faibussowitsch """ 191*ce040abeSJacob Faibussowitsch Return True if the stream supports ANSI escape sequences, False otherwise 192*ce040abeSJacob Faibussowitsch """ 193*ce040abeSJacob Faibussowitsch try: 194*ce040abeSJacob Faibussowitsch # _io.TextIoWrapper use 'name' attribute to store the file name 195*ce040abeSJacob Faibussowitsch key = ostream.name 196*ce040abeSJacob Faibussowitsch except AttributeError: 197*ce040abeSJacob Faibussowitsch return False 198*ce040abeSJacob Faibussowitsch 199*ce040abeSJacob Faibussowitsch try: 200*ce040abeSJacob Faibussowitsch return self._ansi_esc_seq_cache[key] 201*ce040abeSJacob Faibussowitsch except KeyError: 202*ce040abeSJacob Faibussowitsch pass # have not processed this stream before 203*ce040abeSJacob Faibussowitsch except AttributeError: 204*ce040abeSJacob Faibussowitsch # have never done this before 205*ce040abeSJacob Faibussowitsch self._ansi_esc_seq_cache = {} 206*ce040abeSJacob Faibussowitsch 207*ce040abeSJacob Faibussowitsch is_a_tty = hasattr(ostream,'isatty') and ostream.isatty() 208*ce040abeSJacob Faibussowitsch return self._ansi_esc_seq_cache.setdefault(key,is_a_tty and ( 209*ce040abeSJacob Faibussowitsch sys.platform != 'win32' or os.environ.get('TERM','').startswith(('xterm','ANSI')) or 210*ce040abeSJacob Faibussowitsch # Windows Terminal supports VT codes. 211*ce040abeSJacob Faibussowitsch 'WT_SESSION' in os.environ or 212*ce040abeSJacob Faibussowitsch # Microsoft Visual Studio Code's built-in terminal supports colors. 213*ce040abeSJacob Faibussowitsch os.environ.get('TERM_PROGRAM') == 'vscode' 214*ce040abeSJacob Faibussowitsch )) 215*ce040abeSJacob Faibussowitsch 216179860b2SJed Brown def logIndent(self, debugLevel = -1, debugSection = None, comm = None): 217179860b2SJed Brown '''Write the proper indentation to the log streams''' 218179860b2SJed Brown import traceback 219179860b2SJed Brown 220179860b2SJed Brown indentLevel = len(traceback.extract_stack())-5 221179860b2SJed Brown for writeAll, f in enumerate([self.out, self.log]): 222179860b2SJed Brown if self.checkWrite(f, debugLevel, debugSection, writeAll): 223179860b2SJed Brown if not comm is None: 224179860b2SJed Brown f.write('[') 225179860b2SJed Brown f.write(str(comm.rank())) 226179860b2SJed Brown f.write(']') 227179860b2SJed Brown for i in range(indentLevel): 228179860b2SJed Brown f.write(self.debugIndent) 229179860b2SJed Brown return 230179860b2SJed Brown 231179860b2SJed Brown def logBack(self): 232179860b2SJed Brown '''Backup the current line if we are not scrolling output''' 233d1b3ee28SJacob Faibussowitsch if self.out is not None and self.linewidth > 0: 234179860b2SJed Brown self.out.write('\r') 235179860b2SJed Brown return 236179860b2SJed Brown 237179860b2SJed Brown def logClear(self): 238179860b2SJed Brown '''Clear the current line if we are not scrolling output''' 239*ce040abeSJacob Faibussowitsch out,lw = self.out,self.linewidth 240*ce040abeSJacob Faibussowitsch if out is not None and lw > 0: 241*ce040abeSJacob Faibussowitsch out.write('\r\033[K' if self.checkANSIEscapeSequences(out) else ' '*lw) 242*ce040abeSJacob Faibussowitsch try: 243*ce040abeSJacob Faibussowitsch out.flush() 244*ce040abeSJacob Faibussowitsch except AttributeError: 245*ce040abeSJacob Faibussowitsch pass 246179860b2SJed Brown return 247179860b2SJed Brown 248*ce040abeSJacob Faibussowitsch def logPrintDivider(self, single = False, length = None, **kwargs): 249*ce040abeSJacob Faibussowitsch if length is None: 250*ce040abeSJacob Faibussowitsch length = self.dividerLength 251*ce040abeSJacob Faibussowitsch kwargs.setdefault('rmDir',False) 252*ce040abeSJacob Faibussowitsch kwargs.setdefault('indent',False) 253*ce040abeSJacob Faibussowitsch kwargs.setdefault('forceScroll',False) 254*ce040abeSJacob Faibussowitsch kwargs.setdefault('forceNewLine',True) 255*ce040abeSJacob Faibussowitsch divider = ('-' if single else '=')*length 256d1b3ee28SJacob Faibussowitsch return self.logPrint(divider, **kwargs) 257179860b2SJed Brown 258d1b3ee28SJacob Faibussowitsch def logPrintWarning(self, msg, title = None, **kwargs): 259d1b3ee28SJacob Faibussowitsch if title is None: 260d1b3ee28SJacob Faibussowitsch title = 'WARNING' 261d1b3ee28SJacob Faibussowitsch return self.logPrintBox(msg,title='***** {} *****'.format(title),**kwargs) 262d1b3ee28SJacob Faibussowitsch 263d1b3ee28SJacob Faibussowitsch def logPrintBox(self, msg, debugLevel = -1, debugSection = 'screen', indent = 1, comm = None, rmDir = 1, prefix = None, title = None): 264*ce040abeSJacob Faibussowitsch def center_wrap(banner,text,length = None,**kwargs): 2651edc1b4bSJacob Faibussowitsch def center_line(line): 266*ce040abeSJacob Faibussowitsch return line.center(length).rstrip() 2671edc1b4bSJacob Faibussowitsch 268*ce040abeSJacob Faibussowitsch if length is None: 269*ce040abeSJacob Faibussowitsch length = self.dividerLength 270*ce040abeSJacob Faibussowitsch kwargs.setdefault('break_on_hyphens',False) 271*ce040abeSJacob Faibussowitsch kwargs.setdefault('break_long_words',False) 272*ce040abeSJacob Faibussowitsch kwargs.setdefault('width',length-2) 273*ce040abeSJacob Faibussowitsch kwargs.setdefault('initial_indent',prefix) 274*ce040abeSJacob Faibussowitsch kwargs.setdefault('subsequent_indent',prefix) 275*ce040abeSJacob Faibussowitsch wrapped = [ 276*ce040abeSJacob Faibussowitsch line for para in text.splitlines() for line in textwrap.wrap(textwrap.dedent(para),**kwargs) 277*ce040abeSJacob Faibussowitsch ] 2781edc1b4bSJacob Faibussowitsch if len(wrapped) == 1: 279d1b3ee28SJacob Faibussowitsch # center-justify single lines, and remove the bogus prefix 280d1b3ee28SJacob Faibussowitsch wrapped[0] = center_line(wrapped[0].lstrip()) 2811edc1b4bSJacob Faibussowitsch if banner: 2821edc1b4bSJacob Faibussowitsch # add the banner 2831edc1b4bSJacob Faibussowitsch wrapped.insert(0,center_line(banner)) 2841edc1b4bSJacob Faibussowitsch return '\n'.join(wrapped) 2851edc1b4bSJacob Faibussowitsch 2861edc1b4bSJacob Faibussowitsch 2879561296dSJacob Faibussowitsch if prefix is None: 2889561296dSJacob Faibussowitsch prefix = ' '*2 2899561296dSJacob Faibussowitsch 290d1b3ee28SJacob Faibussowitsch if rmDir: 291*ce040abeSJacob Faibussowitsch rmDir = center_wrap(title,self.logStripDirectory(msg)) 292*ce040abeSJacob Faibussowitsch msg = center_wrap(title,msg) 293179860b2SJed Brown self.logClear() 294*ce040abeSJacob Faibussowitsch self.logPrintDivider(debugLevel = debugLevel, debugSection = debugSection) 295*ce040abeSJacob Faibussowitsch self.logPrint(msg, debugLevel = debugLevel, debugSection = debugSection, rmDir = rmDir, forceNewLine = True, forceScroll = True, indent = 0) 296*ce040abeSJacob Faibussowitsch self.logPrintDivider(debugLevel = debugLevel, debugSection = debugSection) 297179860b2SJed Brown return 298179860b2SJed Brown 299d1b3ee28SJacob Faibussowitsch def logStripDirectory(self,msg): 300d1b3ee28SJacob Faibussowitsch return msg.replace(RemoveDirectory,'') 301d1b3ee28SJacob Faibussowitsch 302179860b2SJed Brown def logClearRemoveDirectory(self): 303179860b2SJed Brown global RemoveDirectory 304179860b2SJed Brown global backupRemoveDirectory 305179860b2SJed Brown backupRemoveDirectory = RemoveDirectory 306179860b2SJed Brown RemoveDirectory = '' 307179860b2SJed Brown 308179860b2SJed Brown def logResetRemoveDirectory(self): 309179860b2SJed Brown global RemoveDirectory 310179860b2SJed Brown global backupRemoveDirectory 311179860b2SJed Brown RemoveDirectory = backupRemoveDirectory 312179860b2SJed Brown 313179860b2SJed Brown 314d15db81bSBarry Smith def logWrite(self, msg, debugLevel = -1, debugSection = None, forceScroll = 0, rmDir = 1): 315179860b2SJed Brown '''Write the message to the log streams''' 316b5f71184SBarry Smith '''Generally goes to the file but not the screen''' 317dc0f114dSBarry Smith if not msg: return 318179860b2SJed Brown for writeAll, f in enumerate([self.out, self.log]): 319179860b2SJed Brown if self.checkWrite(f, debugLevel, debugSection, writeAll): 320d1b3ee28SJacob Faibussowitsch if rmDir: 321d1b3ee28SJacob Faibussowitsch if isinstance(rmDir,str): 322*ce040abeSJacob Faibussowitsch clean_msg = rmDir 323d1b3ee28SJacob Faibussowitsch else: 324*ce040abeSJacob Faibussowitsch clean_msg = self.logStripDirectory(msg) 325*ce040abeSJacob Faibussowitsch else: 326*ce040abeSJacob Faibussowitsch clean_msg = msg 327*ce040abeSJacob Faibussowitsch if not forceScroll and not writeAll and self.linewidth > 0: 328*ce040abeSJacob Faibussowitsch self.logClear() 329*ce040abeSJacob Faibussowitsch for ms in clean_msg.splitlines(): 330d1b3ee28SJacob Faibussowitsch f.write(ms[:self.linewidth]) 331179860b2SJed Brown else: 332179860b2SJed Brown if not debugSection is None and not debugSection == 'screen' and len(msg): 333179860b2SJed Brown f.write(str(debugSection)) 334179860b2SJed Brown f.write(': ') 335*ce040abeSJacob Faibussowitsch f.write(msg if writeAll else clean_msg) 336179860b2SJed Brown if hasattr(f, 'flush'): 337179860b2SJed Brown f.flush() 338179860b2SJed Brown return 339179860b2SJed Brown 340d1b3ee28SJacob Faibussowitsch def logPrint(self, msg, debugLevel = -1, debugSection = None, indent = 1, comm = None, forceScroll = 0, rmDir = 1, forceNewLine = False): 341179860b2SJed Brown '''Write the message to the log streams with proper indentation and a newline''' 342b5f71184SBarry Smith '''Generally goes to the file and the screen''' 343179860b2SJed Brown if indent: 344179860b2SJed Brown self.logIndent(debugLevel, debugSection, comm) 345d15db81bSBarry Smith self.logWrite(msg, debugLevel, debugSection, forceScroll = forceScroll, rmDir = rmDir) 346179860b2SJed Brown for writeAll, f in enumerate([self.out, self.log]): 347179860b2SJed Brown if self.checkWrite(f, debugLevel, debugSection, writeAll): 348d1b3ee28SJacob Faibussowitsch if forceNewLine or writeAll or self.linewidth < 0: 349179860b2SJed Brown f.write('\n') 350179860b2SJed Brown return 351179860b2SJed Brown 352179860b2SJed Brown 353179860b2SJed Brown def getRoot(self): 354179860b2SJed Brown '''Return the directory containing this module 355179860b2SJed Brown - This has the problem that when we reload a module of the same name, this gets screwed up 356179860b2SJed Brown Therefore, we call it in the initializer, and stash it''' 357179860b2SJed Brown #print ' In getRoot' 358179860b2SJed Brown #print hasattr(self, '__root') 359179860b2SJed Brown #print ' done checking' 360179860b2SJed Brown if not hasattr(self, '__root'): 361179860b2SJed Brown import os 362179860b2SJed Brown import sys 363179860b2SJed Brown 364179860b2SJed Brown # Work around a bug with pdb in 2.3 365179860b2SJed Brown if hasattr(sys.modules[self.__module__], '__file__') and not os.path.basename(sys.modules[self.__module__].__file__) == 'pdb.py': 366179860b2SJed Brown self.__root = os.path.abspath(os.path.dirname(sys.modules[self.__module__].__file__)) 367179860b2SJed Brown else: 368179860b2SJed Brown self.__root = os.getcwd() 369179860b2SJed Brown #print ' Exiting getRoot' 370179860b2SJed Brown return self.__root 371179860b2SJed Brown def setRoot(self, root): 372179860b2SJed Brown self.__root = root 373179860b2SJed Brown return 374179860b2SJed Brown root = property(getRoot, setRoot, doc = 'The directory containing this module') 375