Package qm :: Package test :: Package classes :: Module compiler
[hide private]
[frames] | no frames]

Source Code for Module qm.test.classes.compiler

  1  ######################################################################## 
  2  # 
  3  # File:   compiler.py 
  4  # Author: Mark Mitchell 
  5  # Date:   12/11/2001 
  6  # 
  7  # Contents: 
  8  #   Compiler 
  9  # 
 10  # Copyright (c) 2001, 2002, 2003 by CodeSourcery, LLC.  All rights reserved.  
 11  # 
 12  # For license terms see the file COPYING. 
 13  # 
 14  ######################################################################## 
 15   
 16  """Uniform interface to compilers. 
 17   
 18  This module contains the 'Compiler' class which is an abstract base 
 19  class providing a uniform interface to various compilers, such as the 
 20  GNU Compiler Collection and the Edison Design Group compilers.""" 
 21   
 22  ######################################################################## 
 23  # Imports 
 24  ######################################################################## 
 25   
 26  from   qm.executable import * 
 27  import os 
 28  import os.path 
 29  import qm 
 30  import StringIO 
 31  import re 
 32  import sys 
 33  if sys.platform != "win32": 
 34      import resource 
 35   
 36  ######################################################################## 
 37  # Classes 
 38  ######################################################################## 
 39   
40 -class CompilerExecutable(RedirectedExecutable):
41 """A 'CompilerExecutable' is a 'Compiler' that is being run.""" 42
43 - def _InitializeChild(self):
44 """Initialize the child process. 45 46 After 'fork' is called this method is invoked to give the 47 child a chance to initialize itself. '_InitializeParent' will 48 already have been called in the parent process.""" 49 50 # Disable compiler core dumps. 51 if sys.platform != "win32": 52 resource.setrlimit(resource.RLIMIT_CORE, (0, 0)) 53 # Do whatever the base class version would otherwise do. 54 RedirectedExecutable._InitializeChild(self)
55 56
57 - def _StdinPipe(self):
58 """Return a pipe to which to redirect the standard input. 59 60 returns -- A pipe, or 'None' if the standard input should be 61 closed in the child.""" 62 63 # The compiler should not need the standard input. 64 return None
65 66
67 - def _StderrPipe(self):
68 """Return a pipe to which to redirect the standard input. 69 70 returns -- A pipe, or 'None'. If 'None' is returned, but 71 '_StdoutPipe' returns a pipe, then the standard error and 72 standard output will both be redirected to that pipe. However, 73 if '_StdoutPipe' also returns 'None', then the standard error 74 will be closed in the child.""" 75 76 # The standard output and standard error are combined. 77 return None
78 79 80
81 -class Compiler:
82 """A 'Compiler' compiles and links source files.""" 83 84 MODE_PREPROCESS = 'preprocess' 85 """Preprocess the source files, but do not compile them.""" 86 87 MODE_COMPILE = 'compile' 88 """Compile the source files, but do not assemble them.""" 89 90 MODE_ASSEMBLE = 'assemble' 91 """Compile the source files, but do not link them.""" 92 93 MODE_LINK = 'link' 94 """Compile and link the source files.""" 95 96 modes = [ MODE_COMPILE, MODE_ASSEMBLE, MODE_LINK, MODE_PREPROCESS ] 97 """The available compilation modes.""" 98
99 - def __init__(self, path, options=None, ldflags=None):
100 """Construct a new 'Compiler'. 101 102 'path' -- A string giving the location of the compiler 103 executable. 104 105 'options' -- A list of strings indicating options to the 106 compiler, or 'None' if there are no options. 107 108 'ldflags' -- A list of strings indicating ld flags to the 109 compiler, or 'None' if there are no flags.""" 110 111 self._path = path 112 self.SetOptions(options or []) 113 self.SetLDFlags(ldflags or [])
114 115
116 - def Compile(self, mode, files, dir, options = [], ldflags = [], 117 output = None, timeout = -1):
118 """Compile the 'files'. 119 120 'mode' -- The compilation mode (one of the 'Compiler.modes') 121 that should be used to compile the 'files'. 122 123 'files' -- A sequence of strings giving the names of source 124 files (including, in general, assembly files, object files, 125 and libraries) that should be compiled. 126 127 'dir' -- The directory in which to run the compiler. 128 129 'options' -- A sequence of strings indicating additional 130 options that should be provided to the compiler. 131 132 'ldflags' -- A sequence of strings indicating additional 133 linker flags that should be provided to the compiler, if 134 linking is done. 135 136 'output' -- The name of the file should be created by the 137 compilation. If 'None', the compiler will use a default 138 value. 139 140 'timeout' -- The maximum number of seconds the compiler is 141 permitted to run. If 'timeout' is -1, the compiler is 142 permitted to run forever. 143 144 returns -- A tuple '(status, output)'. The 'status' is the 145 exit status returned by the compiler, as indicated by 146 'waitpid'. The 'output' is a string containing the standard 147 outpt and standard errror generated by the compiler.""" 148 149 # Get the command to use. 150 command = self.GetCompilationCommand(mode, files, options, 151 ldflags, output) 152 # Invoke the compiler. 153 return self.ExecuteCommand(dir, command, timeout)
154 155
156 - def ExecuteCommand(self, dir, command, timeout = -1):
157 """Execute 'command' in 'dir'. 158 159 'dir' -- The directory in which to execute the command. 160 161 'command' -- A sequence of strings, as returned by 162 'GetCompilationCommand'. 163 164 'timeout' -- The maximum number of seconds the compiler is 165 permitted to run. If 'timeout' is -1, the compiler is 166 permitted to run forever. 167 168 returns -- A tuple '(status, output)'. The 'status' is the 169 exit status returned by the compiler, as indicated by 170 'waitpid'. The 'output' is a string containing the standard 171 output and standard errror generated by the compiler.""" 172 173 # Invoke the compiler. 174 executable = CompilerExecutable(timeout) 175 status = executable.Run(command, dir = dir) 176 # Return all of the information. 177 return (status, executable.stdout)
178 179
180 - def GetCompilationCommand(self, mode, files, options=[], 181 ldflags = [], output=None):
182 """Return the appropriate command for compiling 'files'. 183 184 'mode' -- The compilation mode (one of the 'Compiler.modes') 185 that should be used to compile the 'files'. 186 187 'files' -- A sequence of strings giving the names of source 188 files (including, in general, assembly files, object files, 189 and libraries) that should be compiled. 190 191 'options' -- A sequence of strings indicating additional 192 options that should be provided to the compiler. 193 194 'ldflags' -- A sequence of strings indicating additional 195 linker flags that should be provided to the compiler, if 196 linking is done. 197 198 'output' -- The name of the file should be created by the 199 compilation. If 'None', the compiler will use a default 200 value. (In some cases there may be multiple outputs. For 201 example, when generating multiple object files from multiple 202 source files, the compiler will create a variety of objects.) 203 204 returns -- A sequence of strings indicating the arguments, 205 including 'argv[0]', for the compilation command.""" 206 207 # Start with the path to the compiler. 208 command = [self.GetPath()] 209 # Add switches indicating the compilation mode, if appropriate. 210 command += self._GetModeSwitches(mode) 211 # Add the options that should be used with every compilation. 212 command += self._options 213 # Add the options that apply to this compilation. 214 command += options 215 # Set the output file. 216 if output: 217 command += ["-o", output] 218 # Add the input files. 219 command += files 220 if mode == Compiler.MODE_LINK: 221 command += ldflags 222 command += self.GetLDFlags() 223 224 return command
225 226
227 - def ParseOutput(self, output, ignore_regexps = ()):
228 """Turn the 'output' into a sqeuence of 'Diagnostic's. 229 230 'output' -- A string containing the compiler's output. 231 232 'ignore_regexps' -- A sequence of regular expressions. If a 233 diagnostic message matches one of these regular expressions, 234 it will be ignored. 235 236 returns -- A list of 'Diagnostic's corresponding to the 237 messages indicated in 'output', in the order that they were 238 emitted.""" 239 240 raise NotImplementedError
241 242
243 - def GetPath(self):
244 """Return the location of the executable. 245 246 returns -- A string giving the location of the executable. 247 This location is the one that was specified as the 'path' 248 argument to '__init__'.""" 249 250 return self._path
251 252
253 - def GetOptions(self):
254 """Return the list of compilation options. 255 256 returns -- A list of strings giving the compilation options 257 specified when the 'Compiler' was constructed.""" 258 259 return self._options
260 261
262 - def SetOptions(self, options):
263 """Reset the list of compiler options. 264 265 'options' -- A list of strings indicating options to the 266 compiler, or 'None' if there are no options.""" 267 268 self._options = options
269 270
271 - def GetLDFlags(self):
272 """Return the list of link options. 273 274 returns -- A list of strings giving the link options 275 specified when the 'Compiler' was constructed.""" 276 277 return self._ldflags
278 279
280 - def SetLDFlags(self, ldflags):
281 """Reset the list of link options. 282 283 'ldflags' -- A list of strings indicating options to the 284 linker, or 'None' if there are no flags.""" 285 286 self._ldflags = ldflags
287 288
289 - def GetExecutableExtension(self):
290 """Return the extension for executables. 291 292 returns -- The extension (including leading '.', if 293 applicable) for executable files created by this compiler.""" 294 295 if sys.platform == "win32": 296 return ".exe" 297 else: 298 return ""
299 300
301 - def GetObjectExtension(self):
302 """Return the extension for object files. 303 304 returns -- The extension (including leading '.', if 305 applicable) for object files created by this compiler.""" 306 307 if sys.platform == "win32": 308 return ".obj" 309 else: 310 return ".o"
311 312
313 - def _GetModeSwitches(self, mode):
314 """Return the compilation switches for the compilation 'mode'. 315 316 'mode' -- The compilation mode (one of 'Compiler.modes'). 317 318 returns -- A sequence of strings indicating the switches that 319 are used to indicate the compilation mode.""" 320 321 if mode == self.MODE_PREPROCESS: 322 return ["-E"] 323 elif mode == self.MODE_COMPILE: 324 return ["-S"] 325 elif mode == self.MODE_ASSEMBLE: 326 return ["-c"] 327 328 # Other modes require no special option. 329 return []
330 331 332
333 -class SourcePosition:
334 """A 'SourcePosition' indicates a location in source code. 335 336 A 'SourcePosition' consists of: 337 338 - A file name. The file name is a string. It may be an absolute 339 or relative path. If no file name is available, the file name 340 is the empty string. 341 342 - A line number, indexed from one. If no line number is 343 available, the line number is zero. 344 345 - A column number, indexed from one. If no column number is 346 available, the column nubmer is zero.""" 347
348 - def __init__(self, file, line, column):
349 """Construct a new 'SourcePosition'. 350 351 'file' -- The file name. 352 353 'line' -- The line number, indexed from one. If no line numer 354 is availble, use zero for this parameter. 355 356 'column' -- The column number, indexed from one. If no column 357 number is available, use zero for this parameter.""" 358 359 self.file = file 360 self.line = line 361 self.column = column
362 363
364 - def __str__(self):
365 """Return a textual representation of this 'SourcePosition'. 366 367 returns -- A string representing this 'SourcePosition'""" 368 369 result = '' 370 if self.file: 371 result = result + '"%s"' % os.path.split(self.file)[0] 372 if self.line: 373 if self.file: 374 result = result + ', ' 375 result = result + 'line %d' % self.line 376 if self.column: 377 result = result + ': %d' % self.column 378 379 return result
380 381 382
383 -class Diagnostic:
384 """A 'Diagnostic' is a message issued by a compiler. 385 386 Each 'Diagnostic' has the following attributes: 387 388 - The source position that the compiler associates with the 389 diagnostic. 390 391 - The severity of the diagnostic. 392 393 - The message issued by the compiler. 394 395 A 'Diagnostic' may either be an actual diagnostic emitted by a 396 compiler, or it may be the pattern for a diagnostic that might be 397 emitted. In the latter case, the message is a regular expression 398 indicating the message that should be emitted.""" 399
400 - def __init__(self, source_position, severity, message):
401 """Construct a new 'Diagnostic'. 402 403 'source_position' -- A 'SourcePosition' indicating where the 404 diagnostic was issued. For an expected diagnostic, 'None' 405 indicates that the position does not matter. 406 407 'severity' -- A string indicating the severity of the 408 diagnostic. For an expected diagnostic, 'None' indicates 409 that the severity does not matter. 410 411 'message' -- For an emitted diagnostic, a string indicating 412 the message produced by the compiler. For an expected 413 diagnostic, a string giving a regular expression indicating 414 the message that might be emitted. For an expected 415 diagnostic, 'None' indicates that the message does not 416 matter.""" 417 418 self.source_position = source_position 419 self.severity = severity 420 self.message = message
421 422
423 - def __str__(self):
424 """Return an informal representation of this 'Diagnostic'. 425 426 returns -- A string representing this 'Diagnostic'.""" 427 428 if self.source_position: 429 source_position_string = str(self.source_position) 430 else: 431 source_position_string = "<no source position>" 432 433 if self.severity: 434 severity_string = self.severity 435 else: 436 severity_string = "<no severity>" 437 438 if self.message: 439 message_string = self.message 440 else: 441 message_string = "<no message>" 442 443 return '%s: %s: %s' % (source_position_string, 444 severity_string, 445 message_string)
446 447 448 ######################################################################## 449 # Compilers 450 ######################################################################## 451
452 -class GCC(Compiler):
453 """A 'GCC' is a GNU Compiler Collection compiler.""" 454 455 _severities = [ 'warning', 'error' ] 456 """The diagnostic severities generated by the compiler. Order 457 matters; the order given here is the order that the 458 '_severity_regexps' will be tried.""" 459 460 _severity_regexps = { 461 'warning' : 462 re.compile('^(?P<file>[^:]*):((?P<line>[^:]*):)?' 463 '(\s*(?P<column>[0-9]+):)? ' 464 'warning: (?P<message>.*)$'), 465 'error': 466 re.compile('^(?P<file>[^:]*):((?P<line>[^:]*):)?' 467 '(\s*(?P<column>[0-9]+):)? ' 468 '(?P<message>.*)$') 469 } 470 """A map from severities to compiled regular expressions. If the 471 regular expression matches a line in the compiler output, then that 472 line indicates a diagnostic with the indicated severity.""" 473 474 _internal_error_regexp = re.compile('Internal (compiler )?error') 475 """A compiled regular expression. When an error message is matched 476 by this regular expression, the error message indicates an 477 internal error in the compiler.""" 478 479 MODE_PRECOMPILE = "precompile" 480 """Precompile a header file.""" 481 482 modes = Compiler.modes + [MODE_PRECOMPILE] 483
484 - def ParseOutput(self, output, ignore_regexps = ()):
485 """Return the 'Diagnostic's indicated in the 'output'. 486 487 'output' -- A string giving the output from the compiler. 488 489 'ignore_regexps' -- A sequence of regular expressions. If a 490 diagnostic message matches one of these regular expressions, 491 it will be ignored. 492 493 returns -- A list of 'Diagnostic's corresponding to the 494 messages indicated in 'output', in the order that they were 495 emitted.""" 496 497 # Assume there were no diagnostics. 498 diagnostics = [] 499 # Create a file object containing the 'output'. 500 f = StringIO.StringIO(output) 501 # Reall all of the output, line by line. 502 for line in f.readlines(): 503 for severity in self._severities: 504 match = self._severity_regexps[severity].match(line) 505 # If it does not look like an error message, skip it. 506 if not match: 507 continue 508 509 # Some error messages are ignored. 510 ignore = 0 511 for ignore_regexp in ignore_regexps: 512 if ignore_regexp.match(match.group()): 513 ignore = 1 514 break 515 if ignore: 516 continue 517 518 # An internal error is an error that indicates that 519 # the compiler crashed. 520 message = match.group('message') 521 if (severity == 'error' 522 and self._internal_error_regexp.search(message)): 523 severity = 'internal_error' 524 525 # If there is no line number, then we will not be 526 # able to convert it to an integer. 527 try: 528 line_number = int(match.group('line')) 529 except: 530 line_number = 0 531 532 # See if there is a column number. 533 try: 534 column_number = int(match.group('column')) 535 except: 536 column_number = 0 537 538 source_position = SourcePosition(match.group('file'), 539 line_number, 540 column_number) 541 diagnostic = Diagnostic(source_position, 542 severity, 543 message) 544 diagnostics.append(diagnostic) 545 break 546 547 return diagnostics
548 549 550
551 -class EDG(Compiler):
552 """An 'EDG' is an Edison Design Group compiler.""" 553 554 __diagnostic_regexp = re.compile('^"(?P<file>.*)", line (?P<line>.*): ' 555 '(?P<severity>.*): (?P<message>.*)$') 556
557 - def ParseOutput(self, output, ignore_regexps = ()):
558 """Return the 'Diagnostic's indicated in the 'output'. 559 560 'output' -- A string giving the output from the compiler. 561 562 'ignore_regexps' -- A sequence of regular expressions. If a 563 diagnostic message matches one of these regular expressions, 564 it will be ignored. 565 566 returns -- A list of 'Diagnostic's corresponding to the 567 messages indicated in 'output', in the order that they were 568 emitted.""" 569 570 # Assume there were no diagnostics. 571 diagnostics = [] 572 # Create a file object containing the 'output'. 573 f = StringIO.StringIO(output) 574 # Reall all of the output, line by line. 575 for line in f.readlines(): 576 match = self.__diagnostic_regexp.match(line) 577 if match: 578 source_position = SourcePosition(match.group('file'), 579 int(match.group('line')), 580 0) 581 diagnostic = Diagnostic(source_position, 582 match.group('severity'), 583 match.group('message')) 584 diagnostics.append(diagnostic) 585 586 587 return diagnostics
588