diff --git a/mesonbuild/compilers.py b/mesonbuild/compilers.py index bb733a7ec..68157bd99 100644 --- a/mesonbuild/compilers.py +++ b/mesonbuild/compilers.py @@ -12,6 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +import contextlib import subprocess, os.path import tempfile from .import mesonlib @@ -643,23 +644,50 @@ int main () {{ {1}; }}''' args = extra_args + self.get_no_optimization_args() return self.compiles(templ.format(hname, symbol, prefix), env, args, dependencies) - def compile(self, code, srcname, extra_args=None): + @contextlib.contextmanager + def compile(self, code, extra_args=None): if extra_args is None: extra_args = [] - commands = self.get_exelist() - commands.append(srcname) - commands += extra_args - mlog.debug('Running compile:') - mlog.debug('Command line: ', ' '.join(commands), '\n') - mlog.debug('Code:\n', code) - p = subprocess.Popen(commands, cwd=os.path.split(srcname)[0], stdout=subprocess.PIPE, stderr=subprocess.PIPE) - (stde, stdo) = p.communicate() - stde = stde.decode() - stdo = stdo.decode() - mlog.debug('Compiler stdout:\n', stdo) - mlog.debug('Compiler stderr:\n', stde) - os.remove(srcname) - return p + + try: + with tempfile.TemporaryDirectory() as tmpdirname: + if isinstance(code, str): + srcname = os.path.join(tmpdirname, + 'testfile.' + self.default_suffix) + with open(srcname, 'w') as ofile: + ofile.write(code) + elif isinstance(code, mesonlib.File): + srcname = code.fname + + # Extension only matters if running results; '.exe' is + # guaranteed to be executable on every platform. + output = os.path.join(tmpdirname, 'output.exe') + + commands = self.get_exelist() + commands.append(srcname) + commands += extra_args + commands += self.get_output_args(output) + mlog.debug('Running compile:') + mlog.debug('Working directory: ', tmpdirname) + mlog.debug('Command line: ', ' '.join(commands), '\n') + mlog.debug('Code:\n', code) + p = subprocess.Popen(commands, cwd=tmpdirname, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + (stde, stdo) = p.communicate() + stde = stde.decode() + stdo = stdo.decode() + mlog.debug('Compiler stdout:\n', stdo) + mlog.debug('Compiler stderr:\n', stde) + + p.input_name = srcname + p.output_name = output + yield p + except (PermissionError, OSError): + # On Windows antivirus programs and the like hold on to files so + # they can't be deleted. There's not much to do in this case. Also, + # catch OSError because the directory is then no longer empty. + pass def compiles(self, code, env, extra_args=None, dependencies=None): if extra_args is None: @@ -670,11 +698,6 @@ int main () {{ {1}; }}''' dependencies = [] elif not isinstance(dependencies, list): dependencies = [dependencies] - suflen = len(self.default_suffix) - (fd, srcname) = tempfile.mkstemp(suffix='.'+self.default_suffix) - os.close(fd) - with open(srcname, 'w') as ofile: - ofile.write(code) cargs = [a for d in dependencies for a in d.get_compile_args()] # Convert flags to the native type of the selected compiler args = self.unix_link_flags_to_native(cargs + extra_args) @@ -682,17 +705,8 @@ int main () {{ {1}; }}''' args += self.get_cross_extra_flags(env, compile=True, link=False) # We only want to compile; not link args += self.get_compile_only_args() - p = self.compile(code, srcname, args) - try: - trial = srcname[:-suflen] + 'o' - os.remove(trial) - except FileNotFoundError: - pass - try: - os.remove(srcname[:-suflen] + 'obj') - except FileNotFoundError: - pass - return p.returncode == 0 + with self.compile(code, args) as p: + return p.returncode == 0 def links(self, code, env, extra_args=None, dependencies=None): if extra_args is None: @@ -703,12 +717,6 @@ int main () {{ {1}; }}''' dependencies = [] elif not isinstance(dependencies, list): dependencies = [dependencies] - (fd, srcname) = tempfile.mkstemp(suffix='.'+self.default_suffix) - os.close(fd) - (fd, dstname) = tempfile.mkstemp() - os.close(fd) - with open(srcname, 'w') as ofile: - ofile.write(code) cargs = [a for d in dependencies for a in d.get_compile_args()] link_args = [a for d in dependencies for a in d.get_link_args()] # Convert flags to the native type of the selected compiler @@ -717,14 +725,8 @@ int main () {{ {1}; }}''' args += self.get_linker_debug_crt_args() # Read c_args/c_link_args/cpp_args/cpp_link_args/etc from the cross-info file (if needed) args += self.get_cross_extra_flags(env, compile=True, link=True) - # Arguments specifying the output filename - args += self.get_output_args(dstname) - p = self.compile(code, srcname, args) - try: - os.remove(dstname) - except FileNotFoundError: - pass - return p.returncode == 0 + with self.compile(code, args) as p: + return p.returncode == 0 def run(self, code, env, extra_args=None, dependencies=None): if extra_args is None: @@ -735,10 +737,6 @@ int main () {{ {1}; }}''' dependencies = [dependencies] if self.is_cross and self.exe_wrapper is None: raise CrossNoRunException('Can not run test applications in this cross environment.') - (fd, srcname) = tempfile.mkstemp(suffix='.'+self.default_suffix) - os.close(fd) - with open(srcname, 'w') as ofile: - ofile.write(code) cargs = [a for d in dependencies for a in d.get_compile_args()] link_args = [a for d in dependencies for a in d.get_link_args()] # Convert flags to the native type of the selected compiler @@ -747,48 +745,30 @@ int main () {{ {1}; }}''' args += self.get_linker_debug_crt_args() # Read c_link_args/cpp_link_args/etc from the cross-info file args += self.get_cross_extra_flags(env, compile=True, link=True) - # Create command list - exename = srcname + '.exe' # Is guaranteed to be executable on every platform. - commands = self.get_exelist() + args - commands.append(srcname) - commands += self.get_output_args(exename) - mlog.debug('Running code:\n\n', code) - mlog.debug('Command line:', ' '.join(commands)) - p = subprocess.Popen(commands, cwd=os.path.split(srcname)[0], stdout=subprocess.PIPE, stderr=subprocess.PIPE) - (stdo, stde) = p.communicate() - stde = stde.decode() - stdo = stdo.decode() - mlog.debug('Compiler stdout:\n') - mlog.debug(stdo) - mlog.debug('Compiler stderr:\n') - mlog.debug(stde) - os.remove(srcname) - if p.returncode != 0: - return RunResult(False) - if self.is_cross: - cmdlist = self.exe_wrapper + [exename] - else: - cmdlist = exename - try: - pe = subprocess.Popen(cmdlist, stdout=subprocess.PIPE, stderr=subprocess.PIPE) - except Exception as e: - mlog.debug('Could not run: %s (error: %s)\n' % (cmdlist, e)) - return RunResult(False) - - (so, se) = pe.communicate() + with self.compile(code, args) as p: + if p.returncode != 0: + mlog.debug('Could not compile test file %s: %d\n' % ( + p.input_name, + p.returncode)) + return RunResult(False) + if self.is_cross: + cmdlist = self.exe_wrapper + [p.output_name] + else: + cmdlist = p.output_name + try: + pe = subprocess.Popen(cmdlist, stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + except Exception as e: + mlog.debug('Could not run: %s (error: %s)\n' % (cmdlist, e)) + return RunResult(False) + + (so, se) = pe.communicate() so = so.decode() se = se.decode() mlog.debug('Program stdout:\n') mlog.debug(so) mlog.debug('Program stderr:\n') mlog.debug(se) - try: - os.remove(exename) - except PermissionError: - # On Windows antivirus programs and the like hold - # on to files so they can't be deleted. There's not - # much to do in this case. - pass return RunResult(True, pe.returncode, so, se) def cross_sizeof(self, element, prefix, env, extra_args=None, dependencies=None): diff --git a/mesonbuild/interpreter.py b/mesonbuild/interpreter.py index 1ce87b754..8435bb1e8 100644 --- a/mesonbuild/interpreter.py +++ b/mesonbuild/interpreter.py @@ -699,8 +699,12 @@ class CompilerHolder(InterpreterObject): def run_method(self, args, kwargs): if len(args) != 1: raise InterpreterException('Run method takes exactly one positional argument.') - check_stringlist(args) code = args[0] + if isinstance(code, mesonlib.File): + code = mesonlib.File.from_absolute_file( + code.rel_to_builddir(self.environment.source_dir)) + elif not isinstance(code, str): + raise InvalidArguments('Argument must be string or file.') testname = kwargs.get('name', '') if not isinstance(testname, str): raise InterpreterException('Testname argument must be a string.') @@ -820,8 +824,12 @@ class CompilerHolder(InterpreterObject): def compiles_method(self, args, kwargs): if len(args) != 1: raise InterpreterException('compiles method takes exactly one argument.') - check_stringlist(args) code = args[0] + if isinstance(code, mesonlib.File): + code = mesonlib.File.from_absolute_file( + code.rel_to_builddir(self.environment.source_dir)) + elif not isinstance(code, str): + raise InvalidArguments('Argument must be string or file.') testname = kwargs.get('name', '') if not isinstance(testname, str): raise InterpreterException('Testname argument must be a string.') @@ -839,8 +847,12 @@ class CompilerHolder(InterpreterObject): def links_method(self, args, kwargs): if len(args) != 1: raise InterpreterException('links method takes exactly one argument.') - check_stringlist(args) code = args[0] + if isinstance(code, mesonlib.File): + code = mesonlib.File.from_absolute_file( + code.rel_to_builddir(self.environment.source_dir)) + elif not isinstance(code, str): + raise InvalidArguments('Argument must be string or file.') testname = kwargs.get('name', '') if not isinstance(testname, str): raise InterpreterException('Testname argument must be a string.') diff --git a/test cases/common/33 try compile/invalid.c b/test cases/common/33 try compile/invalid.c new file mode 100644 index 000000000..b623bf8ca --- /dev/null +++ b/test cases/common/33 try compile/invalid.c @@ -0,0 +1,2 @@ +#include +void func() { printf("This won't work.\n"); } diff --git a/test cases/common/33 try compile/meson.build b/test cases/common/33 try compile/meson.build index beafb939f..bca82ce45 100644 --- a/test cases/common/33 try compile/meson.build +++ b/test cases/common/33 try compile/meson.build @@ -13,6 +13,14 @@ if compiler.compiles(code, name : 'should succeed') == false error('Compiler is fail.') endif +if compiler.compiles(files('valid.c'), name : 'should succeed') == false + error('Compiler is fail.') +endif + if compiler.compiles(breakcode, name : 'should fail') error('Compiler returned true on broken code.') endif + +if compiler.compiles(files('invalid.c'), name : 'should fail') + error('Compiler returned true on broken code.') +endif diff --git a/test cases/common/33 try compile/valid.c b/test cases/common/33 try compile/valid.c new file mode 100644 index 000000000..3757f5a6b --- /dev/null +++ b/test cases/common/33 try compile/valid.c @@ -0,0 +1,2 @@ +#include +void func() { printf("Something.\n"); } diff --git a/test cases/common/39 tryrun/error.c b/test cases/common/39 tryrun/error.c new file mode 100644 index 000000000..80eeb5de2 --- /dev/null +++ b/test cases/common/39 tryrun/error.c @@ -0,0 +1,3 @@ +int main(int argc, char **argv) { + return 1; +} diff --git a/test cases/common/39 tryrun/meson.build b/test cases/common/39 tryrun/meson.build index 78847c518..f5d07ab97 100644 --- a/test cases/common/39 tryrun/meson.build +++ b/test cases/common/39 tryrun/meson.build @@ -27,42 +27,50 @@ error_code = '''int main(int argc, char **argv) { no_compile_code = '''int main(int argc, char **argv) { ''' -ok = cc.run(ok_code, name : 'should succeed') -err = cc.run(error_code, name : 'should fail') -noc = cc.run(no_compile_code, name : 'does not compile') +INPUTS = [ + ['String', ok_code, error_code, no_compile_code], + ['File', files('ok.c'), files('error.c'), files('no_compile.c')], +] -if noc.compiled() - error('Compilation fail test failed.') -else - message('Fail detected properly.') -endif +foreach input : INPUTS + type = input[0] + ok = cc.run(input[1], name : type + ' should succeed') + err = cc.run(input[2], name : type + ' should fail') + noc = cc.run(input[3], name : type + ' does not compile') -if ok.compiled() - message('Compilation worked.') -else - error('Compilation did not work.') -endif + if noc.compiled() + error(type + ' compilation fail test failed.') + else + message(type + ' fail detected properly.') + endif -if ok.returncode() == 0 - message('Return code ok.') -else - error('Return code fail') -endif + if ok.compiled() + message(type + ' compilation worked.') + else + error(type + ' compilation did not work.') + endif -if err.returncode() == 1 - message('Bad return code ok.') -else - error('Bad return code fail.') -endif + if ok.returncode() == 0 + message(type + ' return code ok.') + else + error(type + ' return code fail') + endif -if ok.stdout().strip() == 'stdout' - message('Stdout ok.') -else - message('Bad stdout.') -endif + if err.returncode() == 1 + message(type + ' bad return code ok.') + else + error(type + ' bad return code fail.') + endif -if ok.stderr().strip() == 'stderr' - message('Stderr ok.') -else - message('Bad stderr.') -endif + if ok.stdout().strip() == 'stdout' + message(type + ' stdout ok.') + else + message(type + ' bad stdout.') + endif + + if ok.stderr().strip() == 'stderr' + message(type + ' stderr ok.') + else + message(type + ' bad stderr.') + endif +endforeach diff --git a/test cases/common/39 tryrun/no_compile.c b/test cases/common/39 tryrun/no_compile.c new file mode 100644 index 000000000..86b67acb1 --- /dev/null +++ b/test cases/common/39 tryrun/no_compile.c @@ -0,0 +1 @@ +int main(int argc, char **argv) { diff --git a/test cases/common/39 tryrun/ok.c b/test cases/common/39 tryrun/ok.c new file mode 100644 index 000000000..4111c742c --- /dev/null +++ b/test cases/common/39 tryrun/ok.c @@ -0,0 +1,6 @@ +#include +int main(int argc, char **argv) { + printf("%s\n", "stdout"); + fprintf(stderr, "%s\n", "stderr"); + return 0; +}