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