diff options
| author | Dylan Baker <dylan@pnwbakers.com> | 2025-07-03 15:38:32 -0700 |
|---|---|---|
| committer | Dylan Baker <dylan@pnwbakers.com> | 2025-10-06 09:03:07 -0700 |
| commit | 806289a5d27958a084bc6cba41b7cf9ccee4ecf4 (patch) | |
| tree | d6193d9aa67a6fb26e539d8b936f58aea19f2958 | |
| parent | be50d0e23737dc0fc5f074a291644d7fde39ef7b (diff) | |
| download | meson-806289a5d27958a084bc6cba41b7cf9ccee4ecf4.tar.gz | |
compilers: refactor sanity checking code
The goal is to reduce code duplication, and allow each language to
implement as little as possible to get good checking. The main
motivation is that half of the checks are fragile, as they add the work
directory to the paths of the generated files they want to use. This
works when run inside mesonmain because we always have an absolute build
directory, but when put into run_project_tests.py it doesn't work
because that gives a relative build directory.
| -rw-r--r-- | mesonbuild/compilers/asm.py | 7 | ||||
| -rw-r--r-- | mesonbuild/compilers/c.py | 5 | ||||
| -rw-r--r-- | mesonbuild/compilers/compilers.py | 100 | ||||
| -rw-r--r-- | mesonbuild/compilers/cpp.py | 5 | ||||
| -rw-r--r-- | mesonbuild/compilers/cs.py | 34 | ||||
| -rw-r--r-- | mesonbuild/compilers/cuda.py | 114 | ||||
| -rw-r--r-- | mesonbuild/compilers/cython.py | 23 | ||||
| -rw-r--r-- | mesonbuild/compilers/d.py | 22 | ||||
| -rw-r--r-- | mesonbuild/compilers/fortran.py | 11 | ||||
| -rw-r--r-- | mesonbuild/compilers/java.py | 40 | ||||
| -rw-r--r-- | mesonbuild/compilers/mixins/clike.py | 51 | ||||
| -rw-r--r-- | mesonbuild/compilers/objc.py | 5 | ||||
| -rw-r--r-- | mesonbuild/compilers/objcpp.py | 5 | ||||
| -rw-r--r-- | mesonbuild/compilers/rust.py | 63 | ||||
| -rw-r--r-- | mesonbuild/compilers/swift.py | 27 | ||||
| -rw-r--r-- | mesonbuild/compilers/vala.py | 29 | ||||
| -rw-r--r-- | mesonbuild/dependencies/base.py | 7 |
17 files changed, 284 insertions, 264 deletions
diff --git a/mesonbuild/compilers/asm.py b/mesonbuild/compilers/asm.py index d4af77f89..5f065e193 100644 --- a/mesonbuild/compilers/asm.py +++ b/mesonbuild/compilers/asm.py @@ -10,6 +10,7 @@ from .mixins.metrowerks import MetrowerksCompiler, mwasmarm_instruction_set_args from .mixins.ti import TICompiler if T.TYPE_CHECKING: + from ..environment import Environment from ..linkers.linkers import DynamicLinker from ..mesonlib import MachineChoice from ..envconfig import MachineInfo @@ -39,6 +40,12 @@ class ASMCompiler(Compiler): raise EnvironmentException(f'ASM Compiler {self.id} does not support building for {info.cpu_family} CPU family.') super().__init__(ccache, exelist, version, for_machine, info, linker, full_version, is_cross) + def _sanity_check_compile_args(self, env: Environment, sourcename: str, binname: str) -> T.List[str]: + return [] + + def _sanity_check_source_code(self) -> str: + return '' + class NasmCompiler(ASMCompiler): language = 'nasm' diff --git a/mesonbuild/compilers/c.py b/mesonbuild/compilers/c.py index 424b61251..a0786d55c 100644 --- a/mesonbuild/compilers/c.py +++ b/mesonbuild/compilers/c.py @@ -76,9 +76,8 @@ class CCompiler(CLikeCompiler, Compiler): def get_no_stdinc_args(self) -> T.List[str]: return ['-nostdinc'] - def sanity_check(self, work_dir: str, environment: 'Environment') -> None: - code = 'int main(void) { int class=0; return class; }\n' - return self._sanity_check_impl(work_dir, environment, 'sanitycheckc.c', code) + def _sanity_check_source_code(self) -> str: + return 'int main(void) { int class=0; return class; }\n' def has_header_symbol(self, hname: str, symbol: str, prefix: str, env: 'Environment', *, diff --git a/mesonbuild/compilers/compilers.py b/mesonbuild/compilers/compilers.py index 624226d33..4c28eb2fd 100644 --- a/mesonbuild/compilers/compilers.py +++ b/mesonbuild/compilers/compilers.py @@ -1196,8 +1196,7 @@ class Compiler(HoldableObject, metaclass=abc.ABCMeta): def name_string(self) -> str: return ' '.join(self.exelist) - @abc.abstractmethod - def sanity_check(self, work_dir: str, environment: 'Environment') -> None: + def sanity_check(self, work_dir: str, env: Environment) -> None: """Check that this compiler actually works. This should provide a simple compile/link test. Something as simple as: @@ -1205,24 +1204,103 @@ class Compiler(HoldableObject, metaclass=abc.ABCMeta): main(): return 0 ``` is good enough here. + + :param work_dir: A directory to put temporary artifacts + :param env: The :class:`environment.Environment` instance to use with + this check + :raises mesonlib.EnvironmentException: If building the binary fails + :raises mesonlib.EnvironmentException: If running the binary is attempted and fails """ + sourcename, binname = self._sanity_check_filenames() + cmdlist = self._sanity_check_compile_args(env, sourcename, binname) + + with open(os.path.join(work_dir, sourcename), 'w', encoding='utf-8') as f: + f.write(self._sanity_check_source_code()) + + pc, stdo, stde = mesonlib.Popen_safe(cmdlist, cwd=work_dir) + mlog.debug('Sanity check compiler command line:', mesonlib.join_args(cmdlist)) + mlog.debug('Sanity check compile stdout:') + mlog.debug(stdo) + mlog.debug('-----\nSanity check compile stderr:') + mlog.debug(stde) + mlog.debug('-----') + if pc.returncode != 0: + raise mesonlib.EnvironmentException(f'Compiler {self.name_string()} cannot compile programs.') + + self._run_sanity_check(env, [os.path.join(work_dir, binname)], work_dir) + + def _sanity_check_filenames(self) -> T.Tuple[str, str]: + """Generate the name of the source and binary file for the sanity check. + + The returned names should be just the names of the files with + extensions, but no paths. + + :return: A tuple of (sourcename, binaryname) + """ + default_ext = lang_suffixes[self.language][0] + template = f'sanity_check_for_{self.language}' + sourcename = f'{template}.{default_ext}' + binaryname = f'{template}{"_cross" if self.is_cross else ""}.exe' + return sourcename, binaryname + + @abc.abstractmethod + def _sanity_check_compile_args(self, env: Environment, sourcename: str, binname: str) -> T.List[str]: + """Get arguments to run compiler for sanity check. - def run_sanity_check(self, environment: Environment, cmdlist: T.List[str], work_dir: str, use_exe_wrapper_for_cross: bool = True) -> T.Tuple[str, str]: - # Run sanity check - if self.is_cross and use_exe_wrapper_for_cross: - if not environment.has_exe_wrapper(): - # Can't check if the binaries run so we have to assume they do - return ('', '') - cmdlist = environment.exe_wrapper.get_command() + cmdlist - mlog.debug('Running test binary command: ', mesonlib.join_args(cmdlist)) + :param env: The :class:`environment.Environment` instance to use + :param sourcename: the name of the source file to generate + :param binname: the name of the binary file to generate + :return: a list of strings to pass to :func:`subprocess.run` or equivalent + """ + + @abc.abstractmethod + def _sanity_check_source_code(self) -> str: + """Get the source code to run for a sanity check + + :return: A string to be written into a file and ran. + """ + + def _sanity_check_run_with_exe_wrapper(self, env: Environment, command: T.List[str]) -> T.List[str]: + """Wrap the binary to run in the test with the exe_wrapper if necessary + + Languages that do no want to use an exe_wrapper (or always want to use + some kind of wrapper) should override this method + + :param env: the :class:`environment.Environment` instance to use + :param command: The string list of commands to run + :return: The list of commands wrapped by the exe_wrapper if it is needed, otherwise the original commands + """ + if self.is_cross and env.has_exe_wrapper(): + assert env.exe_wrapper is not None, 'for mypy' + return env.exe_wrapper.get_command() + command + return command + + def _run_sanity_check(self, env: Environment, cmdlist: T.List[str], work_dir: str) -> None: + """Run a sanity test binary + + :param env: the :class:`environment.Environment` instance to use + :param cmdlist: A list of strings to pass to :func:`subprocess.run` or equivalent to run the test + :param work_dir: A directory to place temporary artifacts + :raises mesonlib.EnvironmentException: If the binary cannot be run or if it returns a non-zero exit code + """ + # Can't check binaries, so we have to assume they work + if self.is_cross and not env.has_exe_wrapper(): + mlog.debug('Cannot run cross check') + return + + cmdlist = self._sanity_check_run_with_exe_wrapper(env, cmdlist) + mlog.debug('Sanity check built target output for', self.for_machine, self.language, 'compiler') + mlog.debug(' -- Running test binary command: ', mesonlib.join_args(cmdlist)) try: pe, stdo, stde = Popen_safe_logged(cmdlist, 'Sanity check', cwd=work_dir) + mlog.debug(' -- stdout:\n', stdo) + mlog.debug(' -- stderr:\n', stde) + mlog.debug(' -- returncode:', pe.returncode) except Exception as e: raise mesonlib.EnvironmentException(f'Could not invoke sanity check executable: {e!s}.') if pe.returncode != 0: raise mesonlib.EnvironmentException(f'Executables created by {self.language} compiler {self.name_string()} are not runnable.') - return stdo, stde def split_shlib_to_parts(self, fname: str) -> T.Tuple[T.Optional[str], str]: return None, fname diff --git a/mesonbuild/compilers/cpp.py b/mesonbuild/compilers/cpp.py index fa032ec79..8da100483 100644 --- a/mesonbuild/compilers/cpp.py +++ b/mesonbuild/compilers/cpp.py @@ -86,9 +86,8 @@ class CPPCompiler(CLikeCompiler, Compiler): def get_no_stdlib_link_args(self) -> T.List[str]: return ['-nostdlib++'] - def sanity_check(self, work_dir: str, environment: 'Environment') -> None: - code = 'class breakCCompiler;int main(void) { return 0; }\n' - return self._sanity_check_impl(work_dir, environment, 'sanitycheckcpp.cc', code) + def _sanity_check_source_code(self) -> str: + return 'class breakCCompiler;int main(void) { return 0; }\n' def get_compiler_check_args(self, mode: CompileCheckMode) -> T.List[str]: # -fpermissive allows non-conforming code to compile which is necessary diff --git a/mesonbuild/compilers/cs.py b/mesonbuild/compilers/cs.py index 4bbddeb20..e515c338f 100644 --- a/mesonbuild/compilers/cs.py +++ b/mesonbuild/compilers/cs.py @@ -3,11 +3,10 @@ from __future__ import annotations -import os.path, subprocess +import os.path import textwrap import typing as T -from ..mesonlib import EnvironmentException from ..linkers import RSPFileSyntax from .compilers import Compiler @@ -83,26 +82,21 @@ class CsCompiler(BasicLinkerIsCompilerMixin, Compiler): def get_pch_name(self, header_name: str) -> str: return '' - def sanity_check(self, work_dir: str, environment: 'Environment') -> None: - src = 'sanity.cs' - obj = 'sanity.exe' - source_name = os.path.join(work_dir, src) - with open(source_name, 'w', encoding='utf-8') as ofile: - ofile.write(textwrap.dedent(''' - public class Sanity { - static public void Main () { - } + def _sanity_check_source_code(self) -> str: + return textwrap.dedent(''' + public class Sanity { + static public void Main () { } - ''')) - pc = subprocess.Popen(self.exelist + self.get_always_args() + [src], cwd=work_dir) - pc.wait() - if pc.returncode != 0: - raise EnvironmentException('C# compiler %s cannot compile programs.' % self.name_string()) + } + ''') + + def _sanity_check_compile_args(self, env: Environment, sourcename: str, binname: str) -> T.List[str]: + return self.exelist + self.get_always_args() + [sourcename] + self.get_output_args(binname) + + def _sanity_check_run_with_exe_wrapper(self, env: Environment, command: T.List[str]) -> T.List[str]: if self.runner: - cmdlist = [self.runner, obj] - else: - cmdlist = [os.path.join(work_dir, obj)] - self.run_sanity_check(environment, cmdlist, work_dir, use_exe_wrapper_for_cross=False) + return [self.runner] + command + return command def needs_static_linker(self) -> bool: return False diff --git a/mesonbuild/compilers/cuda.py b/mesonbuild/compilers/cuda.py index 7e050f140..a9e6a7669 100644 --- a/mesonbuild/compilers/cuda.py +++ b/mesonbuild/compilers/cuda.py @@ -5,15 +5,14 @@ from __future__ import annotations import enum -import os.path import string import typing as T from .. import options from .. import mlog +from .. import mesonlib from ..mesonlib import ( - EnvironmentException, Popen_safe, - is_windows, LibType, version_compare + EnvironmentException, is_windows, LibType, version_compare ) from .compilers import Compiler, CompileCheckMode @@ -187,6 +186,7 @@ class CudaCompiler(Compiler): host_compiler: Compiler, info: 'MachineInfo', linker: T.Optional['DynamicLinker'] = None, full_version: T.Optional[str] = None): + self.detected_cc = '' super().__init__(ccache, exelist, version, for_machine, info, linker=linker, full_version=full_version, is_cross=is_cross) self.host_compiler = host_compiler self.base_options = host_compiler.base_options @@ -499,55 +499,36 @@ class CudaCompiler(Compiler): def thread_link_flags(self, environment: 'Environment') -> T.List[str]: return self._to_host_flags(self.host_compiler.thread_link_flags(environment), Phase.LINKER) - def sanity_check(self, work_dir: str, env: 'Environment') -> None: - mlog.debug('Sanity testing ' + self.get_display_language() + ' compiler:', ' '.join(self.exelist)) - mlog.debug('Is cross compiler: %s.' % str(self.is_cross)) - - sname = 'sanitycheckcuda.cu' - code = r''' - #include <cuda_runtime.h> - #include <stdio.h> - - __global__ void kernel (void) {} - - int main(void){ - struct cudaDeviceProp prop; - int count, i; - cudaError_t ret = cudaGetDeviceCount(&count); - if(ret != cudaSuccess){ - fprintf(stderr, "%d\n", (int)ret); - }else{ - for(i=0;i<count;i++){ - if(cudaGetDeviceProperties(&prop, i) == cudaSuccess){ - fprintf(stdout, "%d.%d\n", prop.major, prop.minor); + def _sanity_check_source_code(self) -> str: + return r''' + #include <cuda_runtime.h> + #include <stdio.h> + + __global__ void kernel (void) {} + + int main(void){ + struct cudaDeviceProp prop; + int count, i; + cudaError_t ret = cudaGetDeviceCount(&count); + if(ret != cudaSuccess){ + fprintf(stderr, "%d\n", (int)ret); + }else{ + for(i=0;i<count;i++){ + if(cudaGetDeviceProperties(&prop, i) == cudaSuccess){ + fprintf(stdout, "%d.%d\n", prop.major, prop.minor); + } } } + fflush(stderr); + fflush(stdout); + return 0; } - fflush(stderr); - fflush(stdout); - return 0; - } - ''' - binname = sname.rsplit('.', 1)[0] - binname += '_cross' if self.is_cross else '' - source_name = os.path.join(work_dir, sname) - binary_name = os.path.join(work_dir, binname + '.exe') - with open(source_name, 'w', encoding='utf-8') as ofile: - ofile.write(code) - - # The Sanity Test for CUDA language will serve as both a sanity test - # and a native-build GPU architecture detection test, useful later. - # - # For this second purpose, NVCC has very handy flags, --run and - # --run-args, that allow one to run an application with the - # environment set up properly. Of course, this only works for native - # builds; For cross builds we must still use the exe_wrapper (if any). - self.detected_cc = '' - flags = [] + ''' + def _sanity_check_compile_args(self, env: Environment, sourcename: str, binname: str) -> T.List[str]: # Disable warnings, compile with statically-linked runtime for minimum # reliance on the system. - flags += ['-w', '-cudart', 'static', source_name] + flags = ['-w', '-cudart', 'static', sourcename] # Use the -ccbin option, if available, even during sanity checking. # Otherwise, on systems where CUDA does not support the default compiler, @@ -562,33 +543,30 @@ class CudaCompiler(Compiler): # a ton of compiler flags to differentiate between # arm and x86_64. So just compile. flags += self.get_compile_only_args() - flags += self.get_output_args(binary_name) - - # Compile sanity check - cmdlist = self.exelist + flags - mlog.debug('Sanity check compiler command line: ', ' '.join(cmdlist)) - pc, stdo, stde = Popen_safe(cmdlist, cwd=work_dir) - mlog.debug('Sanity check compile stdout: ') - mlog.debug(stdo) - mlog.debug('-----\nSanity check compile stderr:') - mlog.debug(stde) - mlog.debug('-----') - if pc.returncode != 0: - raise EnvironmentException(f'Compiler {self.name_string()} cannot compile programs.') - - # Run sanity check (if possible) - if self.is_cross: + flags += self.get_output_args(binname) + + return self.exelist + flags + + def _run_sanity_check(self, env: Environment, cmdlist: T.List[str], work_dir: str) -> None: + # Can't check binaries, so we have to assume they work + if self.is_cross and not env.has_exe_wrapper(): + mlog.debug('Cannot run cross check') return - cmdlist = self.exelist + ['--run', f'"{binary_name}"'] + cmdlist = self._sanity_check_run_with_exe_wrapper(env, cmdlist) + mlog.debug('Sanity check built target output for', self.for_machine, self.language, 'compiler') + mlog.debug(' -- Running test binary command: ', mesonlib.join_args(cmdlist)) try: - stdo, stde = self.run_sanity_check(env, cmdlist, work_dir) - except EnvironmentException: + pe, stdo, stde = mesonlib.Popen_safe_logged(cmdlist, 'Sanity check', cwd=work_dir) + mlog.debug(' -- stdout:\n', stdo) + mlog.debug(' -- stderr:\n', stde) + mlog.debug(' -- returncode:', pe.returncode) + except Exception as e: + raise EnvironmentException(f'Could not invoke sanity check executable: {e!s}.') + + if pe.returncode != 0: raise EnvironmentException(f'Executables created by {self.language} compiler {self.name_string()} are not runnable.') - # Interpret the result of the sanity test. - # As mentioned above, it is not only a sanity test but also a GPU - # architecture detection test. if stde == '': self.detected_cc = stdo diff --git a/mesonbuild/compilers/cython.py b/mesonbuild/compilers/cython.py index 50bb4652b..2814ca0d6 100644 --- a/mesonbuild/compilers/cython.py +++ b/mesonbuild/compilers/cython.py @@ -1,13 +1,14 @@ # SPDX-License-Identifier: Apache-2.0 # Copyright © 2021-2025 Intel Corporation -from __future__ import annotations """Abstraction for Cython language compilers.""" +from __future__ import annotations +import os import typing as T from .. import options -from ..mesonlib import EnvironmentException, version_compare +from ..mesonlib import version_compare from .compilers import Compiler if T.TYPE_CHECKING: @@ -49,16 +50,22 @@ class CythonCompiler(Compiler): def get_depfile_suffix(self) -> str: return 'dep' - def sanity_check(self, work_dir: str, environment: 'Environment') -> None: - code = 'print("hello world")' - with self.cached_compile(code, environment.coredata) as p: - if p.returncode != 0: - raise EnvironmentException(f'Cython compiler {self.id!r} cannot compile programs') - def get_pic_args(self) -> T.List[str]: # We can lie here, it's fine return [] + def _sanity_check_source_code(self) -> str: + return 'print("Hello world")' + + def _sanity_check_compile_args(self, env: Environment, sourcename: str, binname: str) -> T.List[str]: + return self.exelist + self.get_always_args() + self.get_output_args(binname) + [sourcename] + + def _run_sanity_check(self, env: Environment, cmdlist: T.List[str], work_dir: str) -> None: + # Cython will do a Cython -> C -> Exe, so the output file will actually have + # the name of the C compiler. + # TODO: find a way to not make this so hacky + return super()._run_sanity_check(env, [os.path.join(work_dir, 'sanity_check_for_c.exe')], work_dir) + def compute_parameters_with_absolute_paths(self, parameter_list: T.List[str], build_dir: str) -> T.List[str]: new: T.List[str] = [] diff --git a/mesonbuild/compilers/d.py b/mesonbuild/compilers/d.py index 9f662add3..4df8f6570 100644 --- a/mesonbuild/compilers/d.py +++ b/mesonbuild/compilers/d.py @@ -5,7 +5,6 @@ from __future__ import annotations import os.path import re -import subprocess import typing as T from .. import mesonlib @@ -438,24 +437,11 @@ class DCompiler(Compiler): full_version=full_version, is_cross=is_cross) self.arch = arch - def sanity_check(self, work_dir: str, environment: 'Environment') -> None: - source_name = os.path.join(work_dir, 'sanity.d') - output_name = os.path.join(work_dir, 'dtest') - with open(source_name, 'w', encoding='utf-8') as ofile: - ofile.write('''void main() { }''') + def _sanity_check_source_code(self) -> str: + return 'void main() { }' - compile_cmdlist = self.exelist + self.get_output_args(output_name) + self._get_target_arch_args() + [source_name] - - # If cross-compiling, we can't run the sanity check, only compile it. - if self.is_cross and not environment.has_exe_wrapper(): - compile_cmdlist += self.get_compile_only_args() - - pc = subprocess.Popen(compile_cmdlist, cwd=work_dir) - pc.wait() - if pc.returncode != 0: - raise EnvironmentException('D compiler %s cannot compile programs.' % self.name_string()) - - stdo, stde = self.run_sanity_check(environment, [output_name], work_dir) + def _sanity_check_compile_args(self, env: Environment, sourcename: str, binname: str) -> T.List[str]: + return self.exelist + self.get_output_args(binname) + self._get_target_arch_args() + [sourcename] def needs_static_linker(self) -> bool: return True diff --git a/mesonbuild/compilers/fortran.py b/mesonbuild/compilers/fortran.py index 6f4f3d2c1..2853ee24c 100644 --- a/mesonbuild/compilers/fortran.py +++ b/mesonbuild/compilers/fortran.py @@ -3,6 +3,7 @@ from __future__ import annotations +import textwrap import typing as T import functools import os @@ -61,10 +62,12 @@ class FortranCompiler(CLikeCompiler, Compiler): largs = env.coredata.get_external_link_args(self.for_machine, self.language) return cargs, largs - def sanity_check(self, work_dir: str, environment: 'Environment') -> None: - source_name = 'sanitycheckf.f' - code = ' PROGRAM MAIN\n PRINT *, "Fortran compilation is working."\n END\n' - return self._sanity_check_impl(work_dir, environment, source_name, code) + def _sanity_check_source_code(self) -> str: + return textwrap.dedent(''' + PROGRAM MAIN + PRINT *, "Fortran compilation is working." + END + ''') def get_optimization_args(self, optimization_level: str) -> T.List[str]: return gnu_optimization_args[optimization_level] diff --git a/mesonbuild/compilers/java.py b/mesonbuild/compilers/java.py index 47d2ac9cd..13e48475c 100644 --- a/mesonbuild/compilers/java.py +++ b/mesonbuild/compilers/java.py @@ -6,7 +6,6 @@ from __future__ import annotations import os import os.path import shutil -import subprocess import textwrap import typing as T @@ -72,33 +71,32 @@ class JavaCompiler(BasicLinkerIsCompilerMixin, Compiler): return parameter_list - def sanity_check(self, work_dir: str, environment: 'Environment') -> None: - src = 'SanityCheck.java' - obj = 'SanityCheck' - source_name = os.path.join(work_dir, src) - with open(source_name, 'w', encoding='utf-8') as ofile: - ofile.write(textwrap.dedent( - '''class SanityCheck { - public static void main(String[] args) { - int i; - } - } - ''')) - pc = subprocess.Popen(self.exelist + [src], cwd=work_dir) - pc.wait() - if pc.returncode != 0: - raise EnvironmentException(f'Java compiler {self.name_string()} cannot compile programs.') + def _sanity_check_filenames(self) -> T.Tuple[str, str]: + sup = super()._sanity_check_filenames() + return sup[0], 'SanityCheck' + + def _sanity_check_compile_args(self, env: Environment, sourcename: str, binname: str) -> T.List[str]: + return self.exelist + self.get_always_args() + [sourcename] + + def _sanity_check_run_with_exe_wrapper(self, env: Environment, command: T.List[str]) -> T.List[str]: runner = shutil.which(self.javarunner) - if runner: - cmdlist = [runner, '-cp', '.', obj] - self.run_sanity_check(environment, cmdlist, work_dir, use_exe_wrapper_for_cross=False) - else: + if runner is None: m = "Java Virtual Machine wasn't found, but it's needed by Meson. " \ "Please install a JRE.\nIf you have specific needs where this " \ "requirement doesn't make sense, please open a bug at " \ "https://github.com/mesonbuild/meson/issues/new and tell us " \ "all about it." raise EnvironmentException(m) + return [runner, '-cp', '.', os.path.basename(command[0])] + + def _sanity_check_source_code(self) -> str: + return textwrap.dedent( + '''class SanityCheck { + public static void main(String[] args) { + int i; + } + } + ''') def needs_static_linker(self) -> bool: return False diff --git a/mesonbuild/compilers/mixins/clike.py b/mesonbuild/compilers/mixins/clike.py index a492fffed..bd139d21c 100644 --- a/mesonbuild/compilers/mixins/clike.py +++ b/mesonbuild/compilers/mixins/clike.py @@ -268,50 +268,15 @@ class CLikeCompiler(Compiler): def gen_import_library_args(self, implibname: str) -> T.List[str]: return self.linker.import_library_args(implibname) - def _sanity_check_impl(self, work_dir: str, environment: 'Environment', - sname: str, code: str) -> None: - mlog.debug('Sanity testing ' + self.get_display_language() + ' compiler:', mesonlib.join_args(self.exelist)) - mlog.debug(f'Is cross compiler: {self.is_cross!s}.') - - source_name = os.path.join(work_dir, sname) - binname = sname.rsplit('.', 1)[0] - mode = CompileCheckMode.LINK - if self.is_cross: - binname += '_cross' - if not environment.has_exe_wrapper(): - # Linking cross built C/C++ apps is painful. You can't really - # tell if you should use -nostdlib or not and for example - # on OSX the compiler binary is the same but you need - # a ton of compiler flags to differentiate between - # arm and x86_64. So just compile. - mode = CompileCheckMode.COMPILE - cargs, largs = self._get_basic_compiler_args(environment, mode) + def _sanity_check_compile_args(self, env: Environment, sourcename: str, binname: str) -> T.List[str]: + # Cross-compiling is hard. For example, you might need -nostdlib, or to pass --target, etc. + mode = CompileCheckMode.COMPILE if self.is_cross and not env.has_exe_wrapper() else CompileCheckMode.LINK + cargs, largs = self._get_basic_compiler_args(env, mode) extra_flags = cargs + self.linker_to_compiler_args(largs) - - # Is a valid executable output for all toolchains and platforms - binname += '.exe' - # Write binary check source - binary_name = os.path.join(work_dir, binname) - with open(source_name, 'w', encoding='utf-8') as ofile: - ofile.write(code) - # Compile sanity check - # NOTE: extra_flags must be added at the end. On MSVC, it might contain a '/link' argument - # after which all further arguments will be passed directly to the linker - cmdlist = self.exelist + [sname] + self.get_output_args(binname) + extra_flags - pc, stdo, stde = mesonlib.Popen_safe(cmdlist, cwd=work_dir) - mlog.debug('Sanity check compiler command line:', mesonlib.join_args(cmdlist)) - mlog.debug('Sanity check compile stdout:') - mlog.debug(stdo) - mlog.debug('-----\nSanity check compile stderr:') - mlog.debug(stde) - mlog.debug('-----') - if pc.returncode != 0: - raise mesonlib.EnvironmentException(f'Compiler {self.name_string()} cannot compile programs.') - self.run_sanity_check(environment, [binary_name], work_dir) - - def sanity_check(self, work_dir: str, environment: 'Environment') -> None: - code = 'int main(void) { int class=0; return class; }\n' - return self._sanity_check_impl(work_dir, environment, 'sanitycheckc.c', code) + # It is important that extra_flags is last as it may contain `/link` + # directives, MSVC-compatible compilers will pass all arguments after + # that to the linker + return self.exelist + [sourcename] + self.get_output_args(binname) + extra_flags def check_header(self, hname: str, prefix: str, env: 'Environment', *, extra_args: T.Union[None, T.List[str], T.Callable[['CompileCheckMode'], T.List[str]]] = None, diff --git a/mesonbuild/compilers/objc.py b/mesonbuild/compilers/objc.py index d013417fc..b6deddde9 100644 --- a/mesonbuild/compilers/objc.py +++ b/mesonbuild/compilers/objc.py @@ -48,9 +48,8 @@ class ObjCCompiler(CLikeCompiler, Compiler): def get_display_language() -> str: return 'Objective-C' - def sanity_check(self, work_dir: str, environment: 'Environment') -> None: - code = '#import<stddef.h>\nint main(void) { return 0; }\n' - return self._sanity_check_impl(work_dir, environment, 'sanitycheckobjc.m', code) + def _sanity_check_source_code(self) -> str: + return '#import<stddef.h>\nint main(void) { return 0; }\n' def form_compileropt_key(self, basename: str) -> OptionKey: if basename == 'std': diff --git a/mesonbuild/compilers/objcpp.py b/mesonbuild/compilers/objcpp.py index 441428b2f..e59d32708 100644 --- a/mesonbuild/compilers/objcpp.py +++ b/mesonbuild/compilers/objcpp.py @@ -50,9 +50,8 @@ class ObjCPPCompiler(CLikeCompiler, Compiler): def get_display_language() -> str: return 'Objective-C++' - def sanity_check(self, work_dir: str, environment: 'Environment') -> None: - code = '#import<stdio.h>\nclass MyClass;int main(void) { return 0; }\n' - return self._sanity_check_impl(work_dir, environment, 'sanitycheckobjcpp.mm', code) + def _sanity_check_source_code(self) -> str: + return '#import<stdio.h>\nclass MyClass;int main(void) { return 0; }\n' def get_options(self) -> MutableKeyedOptionDictType: opts = super().get_options() diff --git a/mesonbuild/compilers/rust.py b/mesonbuild/compilers/rust.py index d0f92668a..07faba840 100644 --- a/mesonbuild/compilers/rust.py +++ b/mesonbuild/compilers/rust.py @@ -107,44 +107,43 @@ class RustCompiler(Compiler): def needs_static_linker(self) -> bool: return False - def sanity_check(self, work_dir: str, environment: Environment) -> None: - source_name = os.path.join(work_dir, 'sanity.rs') - output_name = os.path.join(work_dir, 'rusttest.exe') + def _sanity_check_compile_args(self, env: Environment, sourcename: str, binname: str) -> T.List[str]: cmdlist = self.exelist.copy() + assert self.linker is not None, 'for mypy' + if self.info.kernel == 'none' and 'ld.' in self.linker.id: + cmdlist.extend(['-C', 'link-arg=-nostartfiles']) + cmdlist.extend(self.get_output_args(binname)) + cmdlist.append(sourcename) + return cmdlist + + def _sanity_check_source_code(self) -> str: + if self.info.kernel != 'none': + return textwrap.dedent( + '''fn main() { + } + ''') + return textwrap.dedent( + '''#![no_std] + #![no_main] + #[no_mangle] + pub fn _start() { + } + #[panic_handler] + fn panic(_info: &core::panic::PanicInfo) -> ! { + loop {} + } + ''') - with open(source_name, 'w', encoding='utf-8') as ofile: - # If machine kernel is not `none`, try to compile a dummy program. - # If 'none', this is likely a `no-std`(i.e. bare metal) project. - if self.info.kernel != 'none': - ofile.write(textwrap.dedent( - '''fn main() { - } - ''')) - else: - # If rustc linker is gcc, add `-nostartfiles` - if 'ld.' in self.linker.id: - cmdlist.extend(['-C', 'link-arg=-nostartfiles']) - ofile.write(textwrap.dedent( - '''#![no_std] - #![no_main] - #[no_mangle] - pub fn _start() { - } - #[panic_handler] - fn panic(_info: &core::panic::PanicInfo) -> ! { - loop {} - } - ''')) - - cmdlist.extend(['-o', output_name, source_name]) - pc, stdo, stde = Popen_safe_logged(cmdlist, cwd=work_dir) - if pc.returncode != 0: - raise EnvironmentException(f'Rust compiler {self.name_string()} cannot compile programs.') + def sanity_check(self, work_dir: str, environment: Environment) -> None: + super().sanity_check(work_dir, environment) + source_name = self._sanity_check_filenames()[0] self._native_static_libs(work_dir, source_name) - self.run_sanity_check(environment, [output_name], work_dir) def _native_static_libs(self, work_dir: str, source_name: str) -> None: # Get libraries needed to link with a Rust staticlib + if self.native_static_libs: + return + cmdlist = self.exelist + ['--crate-type', 'staticlib', '--print', 'native-static-libs', source_name] p, stdo, stde = Popen_safe_logged(cmdlist, cwd=work_dir) if p.returncode != 0: diff --git a/mesonbuild/compilers/swift.py b/mesonbuild/compilers/swift.py index 4ad3affb5..94e565a7c 100644 --- a/mesonbuild/compilers/swift.py +++ b/mesonbuild/compilers/swift.py @@ -177,22 +177,21 @@ class SwiftCompiler(Compiler): return parameter_list - def sanity_check(self, work_dir: str, environment: 'Environment') -> None: - src = 'swifttest.swift' - source_name = os.path.join(work_dir, src) - output_name = os.path.join(work_dir, 'swifttest') - extra_flags: T.List[str] = [] - extra_flags += environment.coredata.get_external_args(self.for_machine, self.language) + def _sanity_check_compile_args(self, env: Environment, sourcename: str, binname: str) -> T.List[str]: + cmdlist = self.exelist.copy() + # TODO: I can't test this, but it doesn't seem right if self.is_cross: - extra_flags += self.get_compile_only_args() + cmdlist.extend(self.get_compile_only_args()) else: - extra_flags += environment.coredata.get_external_link_args(self.for_machine, self.language) - with open(source_name, 'w', encoding='utf-8') as ofile: - ofile.write('''print("Swift compilation is working.") -''') - pc = subprocess.Popen(self.exelist + extra_flags + ['-emit-executable', '-o', output_name, src], cwd=work_dir) - pc.wait() - self.run_sanity_check(environment, [output_name], work_dir) + cmdlist.extend(env.coredata.get_external_link_args(self.for_machine, self.language)) + cmdlist.extend(self.get_std_exe_link_args()) + cmdlist.extend(self.get_output_args(binname)) + cmdlist.append(sourcename) + + return cmdlist + + def _sanity_check_source_code(self) -> str: + return 'print("Swift compilation is working.")' def get_debug_args(self, is_debug: bool) -> T.List[str]: return clike_debug_args[is_debug] diff --git a/mesonbuild/compilers/vala.py b/mesonbuild/compilers/vala.py index bbaefedb6..230a7739b 100644 --- a/mesonbuild/compilers/vala.py +++ b/mesonbuild/compilers/vala.py @@ -8,7 +8,7 @@ import typing as T from .. import mlog from .. import mesonlib -from ..mesonlib import EnvironmentException, version_compare, LibType +from ..mesonlib import version_compare, LibType from ..options import OptionKey from .compilers import CompileCheckMode, Compiler @@ -99,18 +99,25 @@ class ValaCompiler(Compiler): return parameter_list - def sanity_check(self, work_dir: str, environment: 'Environment') -> None: - code = 'class MesonSanityCheck : Object { }' - extra_flags: T.List[str] = [] - extra_flags += environment.coredata.get_external_args(self.for_machine, self.language) + def _sanity_check_source_code(self) -> str: + return 'public static int main() { return 0; }' + + def _sanity_check_compile_args(self, env: Environment, sourcename: str, binname: str) -> T.List[str]: + cmdlist = self.exelist.copy() + cmdlist.extend(env.coredata.get_external_args(self.for_machine, self.language)) if self.is_cross: - extra_flags += self.get_compile_only_args() + cmdlist.extend(self.get_compile_only_args()) else: - extra_flags += environment.coredata.get_external_link_args(self.for_machine, self.language) - with self.cached_compile(code, environment.coredata, extra_args=extra_flags, mode=CompileCheckMode.COMPILE) as p: - if p.returncode != 0: - msg = f'Vala compiler {self.name_string()!r} cannot compile programs' - raise EnvironmentException(msg) + cmdlist.extend(env.coredata.get_external_link_args(self.for_machine, self.language)) + cmdlist.extend(self.get_output_args(binname)) + cmdlist.append(sourcename) + return cmdlist + + def _run_sanity_check(self, env: Environment, cmdlist: T.List[str], work_dir: str) -> None: + # Vala will do a Vala -> C -> Exe, so the output file will actually have + # the name of the C compiler. + # TODO: find a way to not make this so hacky + return super()._run_sanity_check(env, [os.path.join(work_dir, 'sanity_check_for_c.exe')], work_dir) def find_library(self, libname: str, env: 'Environment', extra_dirs: T.List[str], libtype: LibType = LibType.PREFER_SHARED, lib_prefix_warning: bool = True, ignore_system_dirs: bool = False) -> T.Optional[T.List[str]]: diff --git a/mesonbuild/dependencies/base.py b/mesonbuild/dependencies/base.py index 72dbfdf2d..b746fa8e6 100644 --- a/mesonbuild/dependencies/base.py +++ b/mesonbuild/dependencies/base.py @@ -55,8 +55,11 @@ class MissingCompiler(_MissingCompilerBase): def get_output_args(self, outputname: str) -> T.List[str]: return [] - def sanity_check(self, work_dir: str, environment: 'Environment') -> None: - return None + def _sanity_check_compile_args(self, env: Environment, sourcename: str, binname: str) -> T.List[str]: + return [] + + def _sanity_check_source_code(self) -> str: + return '' def __getattr__(self, item: str) -> T.Any: if item.startswith('__'): |
