1# DExTer : Debugging Experience Tester 2# ~~~~~~ ~ ~~ ~ ~~ 3# 4# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 5# See https://llvm.org/LICENSE.txt for license information. 6# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 7"""Deals with the processing execution of shell or batch build scripts.""" 8 9import os 10import subprocess 11import unittest 12 13from dex.dextIR import BuilderIR 14from dex.utils import Timer 15from dex.utils.Exceptions import BuildScriptException 16 17 18def _quotify(text): 19 if '"' in text or ' ' not in text: 20 return text 21 return '"{}"'.format(text) 22 23 24def _get_script_environment(source_files, compiler_options, 25 linker_options, executable_file): 26 27 source_files = [_quotify(f) for f in source_files] 28 object_files = [ 29 _quotify('{}.o'.format(os.path.basename(f))) for f in source_files 30 ] 31 source_indexes = ['{:02d}'.format(i + 1) for i in range(len(source_files))] 32 33 env_variables = {} 34 env_variables['SOURCE_INDEXES'] = ' '.join(source_indexes) 35 env_variables['SOURCE_FILES'] = ' '.join(source_files) 36 env_variables['OBJECT_FILES'] = ' '.join(object_files) 37 env_variables['LINKER_OPTIONS'] = linker_options 38 39 for i, _ in enumerate(source_files): 40 index = source_indexes[i] 41 env_variables['SOURCE_FILE_{}'.format(index)] = source_files[i] 42 env_variables['OBJECT_FILE_{}'.format(index)] = object_files[i] 43 env_variables['COMPILER_OPTIONS_{}'.format(index)] = compiler_options[i] 44 45 env_variables['EXECUTABLE_FILE'] = executable_file 46 47 return env_variables 48 49 50def run_external_build_script(context, script_path, source_files, 51 compiler_options, linker_options, 52 executable_file): 53 """Build an executable using a builder script. 54 55 The executable is saved to `context.working_directory.path`. 56 57 Returns: 58 ( stdout (str), stderr (str), builder (BuilderIR) ) 59 """ 60 61 builderIR = BuilderIR( 62 name=context.options.builder, 63 cflags=compiler_options, 64 ldflags=linker_options, 65 ) 66 assert len(source_files) == len(compiler_options), (source_files, 67 compiler_options) 68 69 script_environ = _get_script_environment(source_files, compiler_options, 70 linker_options, executable_file) 71 env = dict(os.environ) 72 env.update(script_environ) 73 try: 74 with Timer('running build script'): 75 process = subprocess.Popen( 76 [script_path], 77 cwd=context.working_directory.path, 78 env=env, 79 stdout=subprocess.PIPE, 80 stderr=subprocess.PIPE) 81 out, err = process.communicate() 82 returncode = process.returncode 83 out = out.decode('utf-8') 84 err = err.decode('utf-8') 85 if returncode != 0: 86 raise BuildScriptException( 87 '{}: failed with returncode {}.\nstdout:\n{}\n\nstderr:\n{}\n'. 88 format(script_path, returncode, out, err), 89 script_error=err) 90 return out, err, builderIR 91 except OSError as e: 92 raise BuildScriptException('{}: {}'.format(e.strerror, script_path)) 93 94 95class TestBuilder(unittest.TestCase): 96 def test_get_script_environment(self): 97 source_files = ['a.a', 'b.b'] 98 compiler_options = ['-option1 value1', '-option2 value2'] 99 linker_options = '-optionX valueX' 100 executable_file = 'exe.exe' 101 env = _get_script_environment(source_files, compiler_options, 102 linker_options, executable_file) 103 104 assert env['SOURCE_FILES'] == 'a.a b.b' 105 assert env['OBJECT_FILES'] == 'a.a.o b.b.o' 106 107 assert env['SOURCE_INDEXES'] == '01 02' 108 assert env['LINKER_OPTIONS'] == '-optionX valueX' 109 110 assert env['SOURCE_FILE_01'] == 'a.a' 111 assert env['SOURCE_FILE_02'] == 'b.b' 112 113 assert env['OBJECT_FILE_01'] == 'a.a.o' 114 assert env['OBJECT_FILE_02'] == 'b.b.o' 115 116 assert env['EXECUTABLE_FILE'] == 'exe.exe' 117 118 assert env['COMPILER_OPTIONS_01'] == '-option1 value1' 119 assert env['COMPILER_OPTIONS_02'] == '-option2 value2' 120