xref: /openbsd-src/gnu/llvm/clang/utils/check_cfc/check_cfc.py (revision e5dd70708596ae51455a0ffa086a00c5b29f8583)
1*e5dd7070Spatrick#!/usr/bin/env python
2*e5dd7070Spatrick
3*e5dd7070Spatrick"""Check CFC - Check Compile Flow Consistency
4*e5dd7070Spatrick
5*e5dd7070SpatrickThis is a compiler wrapper for testing that code generation is consistent with
6*e5dd7070Spatrickdifferent compilation processes. It checks that code is not unduly affected by
7*e5dd7070Spatrickcompiler options or other changes which should not have side effects.
8*e5dd7070Spatrick
9*e5dd7070SpatrickTo use:
10*e5dd7070Spatrick-Ensure that the compiler under test (i.e. clang, clang++) is on the PATH
11*e5dd7070Spatrick-On Linux copy this script to the name of the compiler
12*e5dd7070Spatrick   e.g. cp check_cfc.py clang && cp check_cfc.py clang++
13*e5dd7070Spatrick-On Windows use setup.py to generate check_cfc.exe and copy that to clang.exe
14*e5dd7070Spatrick and clang++.exe
15*e5dd7070Spatrick-Enable the desired checks in check_cfc.cfg (in the same directory as the
16*e5dd7070Spatrick wrapper)
17*e5dd7070Spatrick   e.g.
18*e5dd7070Spatrick[Checks]
19*e5dd7070Spatrickdash_g_no_change = true
20*e5dd7070Spatrickdash_s_no_change = false
21*e5dd7070Spatrick
22*e5dd7070Spatrick-The wrapper can be run using its absolute path or added to PATH before the
23*e5dd7070Spatrick compiler under test
24*e5dd7070Spatrick   e.g. export PATH=<path to check_cfc>:$PATH
25*e5dd7070Spatrick-Compile as normal. The wrapper intercepts normal -c compiles and will return
26*e5dd7070Spatrick non-zero if the check fails.
27*e5dd7070Spatrick   e.g.
28*e5dd7070Spatrick$ clang -c test.cpp
29*e5dd7070SpatrickCode difference detected with -g
30*e5dd7070Spatrick--- /tmp/tmp5nv893.o
31*e5dd7070Spatrick+++ /tmp/tmp6Vwjnc.o
32*e5dd7070Spatrick@@ -1 +1 @@
33*e5dd7070Spatrick-   0:       48 8b 05 51 0b 20 00    mov    0x200b51(%rip),%rax
34*e5dd7070Spatrick+   0:       48 39 3d 51 0b 20 00    cmp    %rdi,0x200b51(%rip)
35*e5dd7070Spatrick
36*e5dd7070Spatrick-To run LNT with Check CFC specify the absolute path to the wrapper to the --cc
37*e5dd7070Spatrick and --cxx options
38*e5dd7070Spatrick   e.g.
39*e5dd7070Spatrick   lnt runtest nt --cc <path to check_cfc>/clang \\
40*e5dd7070Spatrick           --cxx <path to check_cfc>/clang++ ...
41*e5dd7070Spatrick
42*e5dd7070SpatrickTo add a new check:
43*e5dd7070Spatrick-Create a new subclass of WrapperCheck
44*e5dd7070Spatrick-Implement the perform_check() method. This should perform the alternate compile
45*e5dd7070Spatrick and do the comparison.
46*e5dd7070Spatrick-Add the new check to check_cfc.cfg. The check has the same name as the
47*e5dd7070Spatrick subclass.
48*e5dd7070Spatrick"""
49*e5dd7070Spatrick
50*e5dd7070Spatrickfrom __future__ import absolute_import, division, print_function
51*e5dd7070Spatrick
52*e5dd7070Spatrickimport imp
53*e5dd7070Spatrickimport os
54*e5dd7070Spatrickimport platform
55*e5dd7070Spatrickimport shutil
56*e5dd7070Spatrickimport subprocess
57*e5dd7070Spatrickimport sys
58*e5dd7070Spatrickimport tempfile
59*e5dd7070Spatricktry:
60*e5dd7070Spatrick    import configparser
61*e5dd7070Spatrickexcept ImportError:
62*e5dd7070Spatrick    import ConfigParser as configparser
63*e5dd7070Spatrickimport io
64*e5dd7070Spatrick
65*e5dd7070Spatrickimport obj_diff
66*e5dd7070Spatrick
67*e5dd7070Spatrickdef is_windows():
68*e5dd7070Spatrick    """Returns True if running on Windows."""
69*e5dd7070Spatrick    return platform.system() == 'Windows'
70*e5dd7070Spatrick
71*e5dd7070Spatrickclass WrapperStepException(Exception):
72*e5dd7070Spatrick    """Exception type to be used when a step other than the original compile
73*e5dd7070Spatrick    fails."""
74*e5dd7070Spatrick    def __init__(self, msg, stdout, stderr):
75*e5dd7070Spatrick        self.msg = msg
76*e5dd7070Spatrick        self.stdout = stdout
77*e5dd7070Spatrick        self.stderr = stderr
78*e5dd7070Spatrick
79*e5dd7070Spatrickclass WrapperCheckException(Exception):
80*e5dd7070Spatrick    """Exception type to be used when a comparison check fails."""
81*e5dd7070Spatrick    def __init__(self, msg):
82*e5dd7070Spatrick        self.msg = msg
83*e5dd7070Spatrick
84*e5dd7070Spatrickdef main_is_frozen():
85*e5dd7070Spatrick    """Returns True when running as a py2exe executable."""
86*e5dd7070Spatrick    return (hasattr(sys, "frozen") or # new py2exe
87*e5dd7070Spatrick            hasattr(sys, "importers") or # old py2exe
88*e5dd7070Spatrick            imp.is_frozen("__main__")) # tools/freeze
89*e5dd7070Spatrick
90*e5dd7070Spatrickdef get_main_dir():
91*e5dd7070Spatrick    """Get the directory that the script or executable is located in."""
92*e5dd7070Spatrick    if main_is_frozen():
93*e5dd7070Spatrick        return os.path.dirname(sys.executable)
94*e5dd7070Spatrick    return os.path.dirname(sys.argv[0])
95*e5dd7070Spatrick
96*e5dd7070Spatrickdef remove_dir_from_path(path_var, directory):
97*e5dd7070Spatrick    """Remove the specified directory from path_var, a string representing
98*e5dd7070Spatrick    PATH"""
99*e5dd7070Spatrick    pathlist = path_var.split(os.pathsep)
100*e5dd7070Spatrick    norm_directory = os.path.normpath(os.path.normcase(directory))
101*e5dd7070Spatrick    pathlist = [x for x in pathlist if os.path.normpath(
102*e5dd7070Spatrick        os.path.normcase(x)) != norm_directory]
103*e5dd7070Spatrick    return os.pathsep.join(pathlist)
104*e5dd7070Spatrick
105*e5dd7070Spatrickdef path_without_wrapper():
106*e5dd7070Spatrick    """Returns the PATH variable modified to remove the path to this program."""
107*e5dd7070Spatrick    scriptdir = get_main_dir()
108*e5dd7070Spatrick    path = os.environ['PATH']
109*e5dd7070Spatrick    return remove_dir_from_path(path, scriptdir)
110*e5dd7070Spatrick
111*e5dd7070Spatrickdef flip_dash_g(args):
112*e5dd7070Spatrick    """Search for -g in args. If it exists then return args without. If not then
113*e5dd7070Spatrick    add it."""
114*e5dd7070Spatrick    if '-g' in args:
115*e5dd7070Spatrick        # Return args without any -g
116*e5dd7070Spatrick        return [x for x in args if x != '-g']
117*e5dd7070Spatrick    else:
118*e5dd7070Spatrick        # No -g, add one
119*e5dd7070Spatrick        return args + ['-g']
120*e5dd7070Spatrick
121*e5dd7070Spatrickdef derive_output_file(args):
122*e5dd7070Spatrick    """Derive output file from the input file (if just one) or None
123*e5dd7070Spatrick    otherwise."""
124*e5dd7070Spatrick    infile = get_input_file(args)
125*e5dd7070Spatrick    if infile is None:
126*e5dd7070Spatrick        return None
127*e5dd7070Spatrick    else:
128*e5dd7070Spatrick        return '{}.o'.format(os.path.splitext(infile)[0])
129*e5dd7070Spatrick
130*e5dd7070Spatrickdef get_output_file(args):
131*e5dd7070Spatrick    """Return the output file specified by this command or None if not
132*e5dd7070Spatrick    specified."""
133*e5dd7070Spatrick    grabnext = False
134*e5dd7070Spatrick    for arg in args:
135*e5dd7070Spatrick        if grabnext:
136*e5dd7070Spatrick            return arg
137*e5dd7070Spatrick        if arg == '-o':
138*e5dd7070Spatrick            # Specified as a separate arg
139*e5dd7070Spatrick            grabnext = True
140*e5dd7070Spatrick        elif arg.startswith('-o'):
141*e5dd7070Spatrick            # Specified conjoined with -o
142*e5dd7070Spatrick            return arg[2:]
143*e5dd7070Spatrick    assert grabnext == False
144*e5dd7070Spatrick
145*e5dd7070Spatrick    return None
146*e5dd7070Spatrick
147*e5dd7070Spatrickdef is_output_specified(args):
148*e5dd7070Spatrick    """Return true is output file is specified in args."""
149*e5dd7070Spatrick    return get_output_file(args) is not None
150*e5dd7070Spatrick
151*e5dd7070Spatrickdef replace_output_file(args, new_name):
152*e5dd7070Spatrick    """Replaces the specified name of an output file with the specified name.
153*e5dd7070Spatrick    Assumes that the output file name is specified in the command line args."""
154*e5dd7070Spatrick    replaceidx = None
155*e5dd7070Spatrick    attached = False
156*e5dd7070Spatrick    for idx, val in enumerate(args):
157*e5dd7070Spatrick        if val == '-o':
158*e5dd7070Spatrick            replaceidx = idx + 1
159*e5dd7070Spatrick            attached = False
160*e5dd7070Spatrick        elif val.startswith('-o'):
161*e5dd7070Spatrick            replaceidx = idx
162*e5dd7070Spatrick            attached = True
163*e5dd7070Spatrick
164*e5dd7070Spatrick    if replaceidx is None:
165*e5dd7070Spatrick        raise Exception
166*e5dd7070Spatrick    replacement = new_name
167*e5dd7070Spatrick    if attached == True:
168*e5dd7070Spatrick        replacement = '-o' + new_name
169*e5dd7070Spatrick    args[replaceidx] = replacement
170*e5dd7070Spatrick    return args
171*e5dd7070Spatrick
172*e5dd7070Spatrickdef add_output_file(args, output_file):
173*e5dd7070Spatrick    """Append an output file to args, presuming not already specified."""
174*e5dd7070Spatrick    return args + ['-o', output_file]
175*e5dd7070Spatrick
176*e5dd7070Spatrickdef set_output_file(args, output_file):
177*e5dd7070Spatrick    """Set the output file within the arguments. Appends or replaces as
178*e5dd7070Spatrick    appropriate."""
179*e5dd7070Spatrick    if is_output_specified(args):
180*e5dd7070Spatrick        args = replace_output_file(args, output_file)
181*e5dd7070Spatrick    else:
182*e5dd7070Spatrick        args = add_output_file(args, output_file)
183*e5dd7070Spatrick    return args
184*e5dd7070Spatrick
185*e5dd7070SpatrickgSrcFileSuffixes = ('.c', '.cpp', '.cxx', '.c++', '.cp', '.cc')
186*e5dd7070Spatrick
187*e5dd7070Spatrickdef get_input_file(args):
188*e5dd7070Spatrick    """Return the input file string if it can be found (and there is only
189*e5dd7070Spatrick    one)."""
190*e5dd7070Spatrick    inputFiles = list()
191*e5dd7070Spatrick    for arg in args:
192*e5dd7070Spatrick        testarg = arg
193*e5dd7070Spatrick        quotes = ('"', "'")
194*e5dd7070Spatrick        while testarg.endswith(quotes):
195*e5dd7070Spatrick            testarg = testarg[:-1]
196*e5dd7070Spatrick        testarg = os.path.normcase(testarg)
197*e5dd7070Spatrick
198*e5dd7070Spatrick        # Test if it is a source file
199*e5dd7070Spatrick        if testarg.endswith(gSrcFileSuffixes):
200*e5dd7070Spatrick            inputFiles.append(arg)
201*e5dd7070Spatrick    if len(inputFiles) == 1:
202*e5dd7070Spatrick        return inputFiles[0]
203*e5dd7070Spatrick    else:
204*e5dd7070Spatrick        return None
205*e5dd7070Spatrick
206*e5dd7070Spatrickdef set_input_file(args, input_file):
207*e5dd7070Spatrick    """Replaces the input file with that specified."""
208*e5dd7070Spatrick    infile = get_input_file(args)
209*e5dd7070Spatrick    if infile:
210*e5dd7070Spatrick        infile_idx = args.index(infile)
211*e5dd7070Spatrick        args[infile_idx] = input_file
212*e5dd7070Spatrick        return args
213*e5dd7070Spatrick    else:
214*e5dd7070Spatrick        # Could not find input file
215*e5dd7070Spatrick        assert False
216*e5dd7070Spatrick
217*e5dd7070Spatrickdef is_normal_compile(args):
218*e5dd7070Spatrick    """Check if this is a normal compile which will output an object file rather
219*e5dd7070Spatrick    than a preprocess or link. args is a list of command line arguments."""
220*e5dd7070Spatrick    compile_step = '-c' in args
221*e5dd7070Spatrick    # Bitcode cannot be disassembled in the same way
222*e5dd7070Spatrick    bitcode = '-flto' in args or '-emit-llvm' in args
223*e5dd7070Spatrick    # Version and help are queries of the compiler and override -c if specified
224*e5dd7070Spatrick    query = '--version' in args or '--help' in args
225*e5dd7070Spatrick    # Options to output dependency files for make
226*e5dd7070Spatrick    dependency = '-M' in args or '-MM' in args
227*e5dd7070Spatrick    # Check if the input is recognised as a source file (this may be too
228*e5dd7070Spatrick    # strong a restriction)
229*e5dd7070Spatrick    input_is_valid = bool(get_input_file(args))
230*e5dd7070Spatrick    return compile_step and not bitcode and not query and not dependency and input_is_valid
231*e5dd7070Spatrick
232*e5dd7070Spatrickdef run_step(command, my_env, error_on_failure):
233*e5dd7070Spatrick    """Runs a step of the compilation. Reports failure as exception."""
234*e5dd7070Spatrick    # Need to use shell=True on Windows as Popen won't use PATH otherwise.
235*e5dd7070Spatrick    p = subprocess.Popen(command, stdout=subprocess.PIPE,
236*e5dd7070Spatrick                         stderr=subprocess.PIPE, env=my_env, shell=is_windows())
237*e5dd7070Spatrick    (stdout, stderr) = p.communicate()
238*e5dd7070Spatrick    if p.returncode != 0:
239*e5dd7070Spatrick        raise WrapperStepException(error_on_failure, stdout, stderr)
240*e5dd7070Spatrick
241*e5dd7070Spatrickdef get_temp_file_name(suffix):
242*e5dd7070Spatrick    """Get a temporary file name with a particular suffix. Let the caller be
243*e5dd7070Spatrick    responsible for deleting it."""
244*e5dd7070Spatrick    tf = tempfile.NamedTemporaryFile(suffix=suffix, delete=False)
245*e5dd7070Spatrick    tf.close()
246*e5dd7070Spatrick    return tf.name
247*e5dd7070Spatrick
248*e5dd7070Spatrickclass WrapperCheck(object):
249*e5dd7070Spatrick    """Base class for a check. Subclass this to add a check."""
250*e5dd7070Spatrick    def __init__(self, output_file_a):
251*e5dd7070Spatrick        """Record the base output file that will be compared against."""
252*e5dd7070Spatrick        self._output_file_a = output_file_a
253*e5dd7070Spatrick
254*e5dd7070Spatrick    def perform_check(self, arguments, my_env):
255*e5dd7070Spatrick        """Override this to perform the modified compilation and required
256*e5dd7070Spatrick        checks."""
257*e5dd7070Spatrick        raise NotImplementedError("Please Implement this method")
258*e5dd7070Spatrick
259*e5dd7070Spatrickclass dash_g_no_change(WrapperCheck):
260*e5dd7070Spatrick    def perform_check(self, arguments, my_env):
261*e5dd7070Spatrick        """Check if different code is generated with/without the -g flag."""
262*e5dd7070Spatrick        output_file_b = get_temp_file_name('.o')
263*e5dd7070Spatrick
264*e5dd7070Spatrick        alternate_command = list(arguments)
265*e5dd7070Spatrick        alternate_command = flip_dash_g(alternate_command)
266*e5dd7070Spatrick        alternate_command = set_output_file(alternate_command, output_file_b)
267*e5dd7070Spatrick        run_step(alternate_command, my_env, "Error compiling with -g")
268*e5dd7070Spatrick
269*e5dd7070Spatrick        # Compare disassembly (returns first diff if differs)
270*e5dd7070Spatrick        difference = obj_diff.compare_object_files(self._output_file_a,
271*e5dd7070Spatrick                                                   output_file_b)
272*e5dd7070Spatrick        if difference:
273*e5dd7070Spatrick            raise WrapperCheckException(
274*e5dd7070Spatrick                "Code difference detected with -g\n{}".format(difference))
275*e5dd7070Spatrick
276*e5dd7070Spatrick        # Clean up temp file if comparison okay
277*e5dd7070Spatrick        os.remove(output_file_b)
278*e5dd7070Spatrick
279*e5dd7070Spatrickclass dash_s_no_change(WrapperCheck):
280*e5dd7070Spatrick    def perform_check(self, arguments, my_env):
281*e5dd7070Spatrick        """Check if compiling to asm then assembling in separate steps results
282*e5dd7070Spatrick        in different code than compiling to object directly."""
283*e5dd7070Spatrick        output_file_b = get_temp_file_name('.o')
284*e5dd7070Spatrick
285*e5dd7070Spatrick        alternate_command = arguments + ['-via-file-asm']
286*e5dd7070Spatrick        alternate_command = set_output_file(alternate_command, output_file_b)
287*e5dd7070Spatrick        run_step(alternate_command, my_env,
288*e5dd7070Spatrick                 "Error compiling with -via-file-asm")
289*e5dd7070Spatrick
290*e5dd7070Spatrick        # Compare if object files are exactly the same
291*e5dd7070Spatrick        exactly_equal = obj_diff.compare_exact(self._output_file_a, output_file_b)
292*e5dd7070Spatrick        if not exactly_equal:
293*e5dd7070Spatrick            # Compare disassembly (returns first diff if differs)
294*e5dd7070Spatrick            difference = obj_diff.compare_object_files(self._output_file_a,
295*e5dd7070Spatrick                                                       output_file_b)
296*e5dd7070Spatrick            if difference:
297*e5dd7070Spatrick                raise WrapperCheckException(
298*e5dd7070Spatrick                    "Code difference detected with -S\n{}".format(difference))
299*e5dd7070Spatrick
300*e5dd7070Spatrick            # Code is identical, compare debug info
301*e5dd7070Spatrick            dbgdifference = obj_diff.compare_debug_info(self._output_file_a,
302*e5dd7070Spatrick                                                        output_file_b)
303*e5dd7070Spatrick            if dbgdifference:
304*e5dd7070Spatrick                raise WrapperCheckException(
305*e5dd7070Spatrick                    "Debug info difference detected with -S\n{}".format(dbgdifference))
306*e5dd7070Spatrick
307*e5dd7070Spatrick            raise WrapperCheckException("Object files not identical with -S\n")
308*e5dd7070Spatrick
309*e5dd7070Spatrick        # Clean up temp file if comparison okay
310*e5dd7070Spatrick        os.remove(output_file_b)
311*e5dd7070Spatrick
312*e5dd7070Spatrickif __name__ == '__main__':
313*e5dd7070Spatrick    # Create configuration defaults from list of checks
314*e5dd7070Spatrick    default_config = """
315*e5dd7070Spatrick[Checks]
316*e5dd7070Spatrick"""
317*e5dd7070Spatrick
318*e5dd7070Spatrick    # Find all subclasses of WrapperCheck
319*e5dd7070Spatrick    checks = [cls.__name__ for cls in vars()['WrapperCheck'].__subclasses__()]
320*e5dd7070Spatrick
321*e5dd7070Spatrick    for c in checks:
322*e5dd7070Spatrick        default_config += "{} = false\n".format(c)
323*e5dd7070Spatrick
324*e5dd7070Spatrick    config = configparser.RawConfigParser()
325*e5dd7070Spatrick    config.readfp(io.BytesIO(default_config))
326*e5dd7070Spatrick    scriptdir = get_main_dir()
327*e5dd7070Spatrick    config_path = os.path.join(scriptdir, 'check_cfc.cfg')
328*e5dd7070Spatrick    try:
329*e5dd7070Spatrick        config.read(os.path.join(config_path))
330*e5dd7070Spatrick    except:
331*e5dd7070Spatrick        print("Could not read config from {}, "
332*e5dd7070Spatrick              "using defaults.".format(config_path))
333*e5dd7070Spatrick
334*e5dd7070Spatrick    my_env = os.environ.copy()
335*e5dd7070Spatrick    my_env['PATH'] = path_without_wrapper()
336*e5dd7070Spatrick
337*e5dd7070Spatrick    arguments_a = list(sys.argv)
338*e5dd7070Spatrick
339*e5dd7070Spatrick    # Prevent infinite loop if called with absolute path.
340*e5dd7070Spatrick    arguments_a[0] = os.path.basename(arguments_a[0])
341*e5dd7070Spatrick
342*e5dd7070Spatrick    # Sanity check
343*e5dd7070Spatrick    enabled_checks = [check_name
344*e5dd7070Spatrick                      for check_name in checks
345*e5dd7070Spatrick                      if config.getboolean('Checks', check_name)]
346*e5dd7070Spatrick    checks_comma_separated = ', '.join(enabled_checks)
347*e5dd7070Spatrick    print("Check CFC, checking: {}".format(checks_comma_separated))
348*e5dd7070Spatrick
349*e5dd7070Spatrick    # A - original compilation
350*e5dd7070Spatrick    output_file_orig = get_output_file(arguments_a)
351*e5dd7070Spatrick    if output_file_orig is None:
352*e5dd7070Spatrick        output_file_orig = derive_output_file(arguments_a)
353*e5dd7070Spatrick
354*e5dd7070Spatrick    p = subprocess.Popen(arguments_a, env=my_env, shell=is_windows())
355*e5dd7070Spatrick    p.communicate()
356*e5dd7070Spatrick    if p.returncode != 0:
357*e5dd7070Spatrick        sys.exit(p.returncode)
358*e5dd7070Spatrick
359*e5dd7070Spatrick    if not is_normal_compile(arguments_a) or output_file_orig is None:
360*e5dd7070Spatrick        # Bail out here if we can't apply checks in this case.
361*e5dd7070Spatrick        # Does not indicate an error.
362*e5dd7070Spatrick        # Maybe not straight compilation (e.g. -S or --version or -flto)
363*e5dd7070Spatrick        # or maybe > 1 input files.
364*e5dd7070Spatrick        sys.exit(0)
365*e5dd7070Spatrick
366*e5dd7070Spatrick    # Sometimes we generate files which have very long names which can't be
367*e5dd7070Spatrick    # read/disassembled. This will exit early if we can't find the file we
368*e5dd7070Spatrick    # expected to be output.
369*e5dd7070Spatrick    if not os.path.isfile(output_file_orig):
370*e5dd7070Spatrick        sys.exit(0)
371*e5dd7070Spatrick
372*e5dd7070Spatrick    # Copy output file to a temp file
373*e5dd7070Spatrick    temp_output_file_orig = get_temp_file_name('.o')
374*e5dd7070Spatrick    shutil.copyfile(output_file_orig, temp_output_file_orig)
375*e5dd7070Spatrick
376*e5dd7070Spatrick    # Run checks, if they are enabled in config and if they are appropriate for
377*e5dd7070Spatrick    # this command line.
378*e5dd7070Spatrick    current_module = sys.modules[__name__]
379*e5dd7070Spatrick    for check_name in checks:
380*e5dd7070Spatrick        if config.getboolean('Checks', check_name):
381*e5dd7070Spatrick            class_ = getattr(current_module, check_name)
382*e5dd7070Spatrick            checker = class_(temp_output_file_orig)
383*e5dd7070Spatrick            try:
384*e5dd7070Spatrick                checker.perform_check(arguments_a, my_env)
385*e5dd7070Spatrick            except WrapperCheckException as e:
386*e5dd7070Spatrick                # Check failure
387*e5dd7070Spatrick                print("{} {}".format(get_input_file(arguments_a), e.msg), file=sys.stderr)
388*e5dd7070Spatrick
389*e5dd7070Spatrick                # Remove file to comply with build system expectations (no
390*e5dd7070Spatrick                # output file if failed)
391*e5dd7070Spatrick                os.remove(output_file_orig)
392*e5dd7070Spatrick                sys.exit(1)
393*e5dd7070Spatrick
394*e5dd7070Spatrick            except WrapperStepException as e:
395*e5dd7070Spatrick                # Compile step failure
396*e5dd7070Spatrick                print(e.msg, file=sys.stderr)
397*e5dd7070Spatrick                print("*** stdout ***", file=sys.stderr)
398*e5dd7070Spatrick                print(e.stdout, file=sys.stderr)
399*e5dd7070Spatrick                print("*** stderr ***", file=sys.stderr)
400*e5dd7070Spatrick                print(e.stderr, file=sys.stderr)
401*e5dd7070Spatrick
402*e5dd7070Spatrick                # Remove file to comply with build system expectations (no
403*e5dd7070Spatrick                # output file if failed)
404*e5dd7070Spatrick                os.remove(output_file_orig)
405*e5dd7070Spatrick                sys.exit(1)
406