1
2
3
4
5
6
7
8
9
10
11
12
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
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
38
39
41 """A 'CompilerExecutable' is a 'Compiler' that is being run."""
42
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
51 if sys.platform != "win32":
52 resource.setrlimit(resource.RLIMIT_CORE, (0, 0))
53
54 RedirectedExecutable._InitializeChild(self)
55
56
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
64 return None
65
66
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
77 return None
78
79
80
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
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
150 command = self.GetCompilationCommand(mode, files, options,
151 ldflags, output)
152
153 return self.ExecuteCommand(dir, command, timeout)
154
155
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
174 executable = CompilerExecutable(timeout)
175 status = executable.Run(command, dir = dir)
176
177 return (status, executable.stdout)
178
179
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
208 command = [self.GetPath()]
209
210 command += self._GetModeSwitches(mode)
211
212 command += self._options
213
214 command += options
215
216 if output:
217 command += ["-o", output]
218
219 command += files
220 if mode == Compiler.MODE_LINK:
221 command += ldflags
222 command += self.GetLDFlags()
223
224 return command
225
226
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
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
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
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
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
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
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
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
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
329 return []
330
331
332
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
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
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
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
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
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
498 diagnostics = []
499
500 f = StringIO.StringIO(output)
501
502 for line in f.readlines():
503 for severity in self._severities:
504 match = self._severity_regexps[severity].match(line)
505
506 if not match:
507 continue
508
509
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
519
520 message = match.group('message')
521 if (severity == 'error'
522 and self._internal_error_regexp.search(message)):
523 severity = 'internal_error'
524
525
526
527 try:
528 line_number = int(match.group('line'))
529 except:
530 line_number = 0
531
532
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
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
571 diagnostics = []
572
573 f = StringIO.StringIO(output)
574
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