Partially revert sanity checking refactor This broke Cython checking, which was allowed by causing it to be disabled in CI. This is a squash of three patches: - Revert "compilers/asm: Implement infrastructure for sanity checking" This reverts commit 08221c6d1fc4a6afe780a479bbc7e40899242601. - Revert "compilers/asm: Store the C Compiler inside the Assembly Compilers" This reverts commit 8847c938dd1c9e2c6e64e3050eb58f7ec54fccb3. - Revert "compilers: refactor sanity checking code" This reverts commit 806289a5d27958a084bc6cba41b7cf9ccee4ecf4.
diff --git a/mesonbuild/compilers/asm.py b/mesonbuild/compilers/asm.py index 9eda37a..94439be 100644 --- a/mesonbuild/compilers/asm.py +++ b/mesonbuild/compilers/asm.py
@@ -4,15 +4,14 @@ import textwrap import typing as T -from .. import mlog -from ..mesonlib import EnvironmentException, Popen_safe, join_args, get_meson_command +from ..mesonlib import EnvironmentException, get_meson_command from ..options import OptionKey from .compilers import Compiler from .mixins.metrowerks import MetrowerksCompiler, mwasmarm_instruction_set_args, mwasmeppc_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 @@ -27,10 +26,6 @@ } -class _CheckUnimplementedException(Exception): - pass - - class ASMCompiler(Compiler): """Shared base class for all ASM Compilers (Assemblers)""" @@ -38,66 +33,12 @@ _SUPPORTED_ARCHES: T.Set[str] = set() def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, - for_machine: 'MachineChoice', info: 'MachineInfo', compiler: Compiler, + for_machine: MachineChoice, info: MachineInfo, + linker: T.Optional[DynamicLinker] = None, full_version: T.Optional[str] = None, is_cross: bool = False): - assert compiler.linker is not None, 'for mypy' if self._SUPPORTED_ARCHES and info.cpu_family not in self._SUPPORTED_ARCHES: 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, compiler.linker, full_version, is_cross) - self._compiler = compiler - - def _sanity_check_compile_args(self, env: Environment, sourcename: str, binname: str) -> T.List[str]: - # TODO: fallback back to this is always wrong, it means that the - # concrete implementation is missing the sanity check implementation - return [] - - def _sanity_check_filenames(self) -> T.Tuple[str, str]: - src, bin = super()._sanity_check_filenames() - bin = '{}.obj'.format(os.path.splitext(bin)[0]) - return src, bin - - def _sanity_check_source_code(self) -> str: - raise _CheckUnimplementedException() - - def _run_sanity_check(self, env: Environment, cmdlist: T.List[str], work_dir: str) -> None: - # This is a bit of a hack - return - - def sanity_check(self, work_dir: str, env: Environment) -> None: - try: - super().sanity_check(work_dir, env) - except _CheckUnimplementedException: - if self.info.kernel: - name = self.info.kernel - if self.info.subsystem: - name = f'{name} {self.info.subsystem}' - else: - name = self.info.system - mlog.warning( - f'Missing {self.id} sanity check code for {name}.', - 'You can help by providing such an implementation', - fatal=False, once=True) - return - - # This is the object from the compilation - src = self._sanity_check_filenames()[1] - bin = self._compiler._sanity_check_filenames()[1] - - cmdlist = self._compiler._sanity_check_compile_args(env, src, bin) - - pc, stdo, stde = Popen_safe(cmdlist, cwd=work_dir) - mlog.debug('Sanity check linker command line:', join_args(cmdlist)) - mlog.debug('Sanity check linker stdout:') - mlog.debug(stdo) - mlog.debug('-----\nSanity check linker stderr:') - mlog.debug(stde) - mlog.debug('-----') - if pc.returncode != 0: - raise EnvironmentError( - f'Compiler {self._compiler.name_string()} could not link an object from the {self.name_string()} assembler') - - # This is also a hack - return super()._run_sanity_check(env, [os.path.join(work_dir, bin)], work_dir) + super().__init__(ccache, exelist, version, for_machine, info, linker, full_version, is_cross) class NasmCompiler(ASMCompiler): @@ -116,9 +57,10 @@ _SUPPORTED_ARCHES = {'x86', 'x86_64'} def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, - for_machine: 'MachineChoice', info: 'MachineInfo', compiler: Compiler, + for_machine: 'MachineChoice', info: 'MachineInfo', + linker: T.Optional['DynamicLinker'] = None, full_version: T.Optional[str] = None, is_cross: bool = False): - super().__init__(ccache, exelist, version, for_machine, info, compiler, full_version, is_cross) + super().__init__(ccache, exelist, version, for_machine, info, linker, full_version, is_cross) self.links_with_msvc = False if 'link' in self.linker.id: self.base_options.add(OptionKey('b_vscrt')) @@ -361,9 +303,10 @@ _SUPPORTED_ARCHES = {'c6000'} def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, - for_machine: MachineChoice, info: MachineInfo, compiler: Compiler, + for_machine: MachineChoice, info: MachineInfo, + linker: T.Optional[DynamicLinker] = None, full_version: T.Optional[str] = None, is_cross: bool = False): - ASMCompiler.__init__(self, ccache, exelist, version, for_machine, info, compiler, full_version, is_cross) + ASMCompiler.__init__(self, ccache, exelist, version, for_machine, info, linker, full_version, is_cross) TICompiler.__init__(self) def needs_static_linker(self) -> bool: @@ -383,9 +326,10 @@ language = 'nasm' def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, - for_machine: 'MachineChoice', info: 'MachineInfo', compiler: Compiler, + for_machine: 'MachineChoice', info: 'MachineInfo', + linker: T.Optional['DynamicLinker'] = None, full_version: T.Optional[str] = None, is_cross: bool = False): - ASMCompiler.__init__(self, ccache, exelist, version, for_machine, info, compiler, full_version, is_cross) + ASMCompiler.__init__(self, ccache, exelist, version, for_machine, info, linker, full_version, is_cross) MetrowerksCompiler.__init__(self) self.warn_args: T.Dict[str, T.List[str]] = {
diff --git a/mesonbuild/compilers/c.py b/mesonbuild/compilers/c.py index a0786d5..424b612 100644 --- a/mesonbuild/compilers/c.py +++ b/mesonbuild/compilers/c.py
@@ -76,8 +76,9 @@ def get_no_stdinc_args(self) -> T.List[str]: return ['-nostdinc'] - def _sanity_check_source_code(self) -> str: - return 'int main(void) { int class=0; return class; }\n' + 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 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 4c28eb2..624226d 100644 --- a/mesonbuild/compilers/compilers.py +++ b/mesonbuild/compilers/compilers.py
@@ -1196,7 +1196,8 @@ def name_string(self) -> str: return ' '.join(self.exelist) - def sanity_check(self, work_dir: str, env: Environment) -> None: + @abc.abstractmethod + def sanity_check(self, work_dir: str, environment: 'Environment') -> None: """Check that this compiler actually works. This should provide a simple compile/link test. Something as simple as: @@ -1204,103 +1205,24 @@ 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. - - :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)) + 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)) 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 8da1004..fa032ec 100644 --- a/mesonbuild/compilers/cpp.py +++ b/mesonbuild/compilers/cpp.py
@@ -86,8 +86,9 @@ def get_no_stdlib_link_args(self) -> T.List[str]: return ['-nostdlib++'] - def _sanity_check_source_code(self) -> str: - return 'class breakCCompiler;int main(void) { return 0; }\n' + 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 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 e515c33..4bbddeb 100644 --- a/mesonbuild/compilers/cs.py +++ b/mesonbuild/compilers/cs.py
@@ -3,10 +3,11 @@ from __future__ import annotations -import os.path +import os.path, subprocess import textwrap import typing as T +from ..mesonlib import EnvironmentException from ..linkers import RSPFileSyntax from .compilers import Compiler @@ -82,21 +83,26 @@ def get_pch_name(self, header_name: str) -> str: return '' - def _sanity_check_source_code(self) -> str: - return textwrap.dedent(''' - public class Sanity { - static public void Main () { + 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_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]: + ''')) + 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()) if self.runner: - return [self.runner] + command - return command + 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) def needs_static_linker(self) -> bool: return False
diff --git a/mesonbuild/compilers/cuda.py b/mesonbuild/compilers/cuda.py index a9e6a76..7e050f1 100644 --- a/mesonbuild/compilers/cuda.py +++ b/mesonbuild/compilers/cuda.py
@@ -5,14 +5,15 @@ 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, is_windows, LibType, version_compare + EnvironmentException, Popen_safe, + is_windows, LibType, version_compare ) from .compilers import Compiler, CompileCheckMode @@ -186,7 +187,6 @@ 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,36 +499,55 @@ 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_source_code(self) -> str: - return r''' - #include <cuda_runtime.h> - #include <stdio.h> + 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)) - __global__ void kernel (void) {} + sname = 'sanitycheckcuda.cu' + code = r''' + #include <cuda_runtime.h> + #include <stdio.h> - 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); - } + __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) - def _sanity_check_compile_args(self, env: Environment, sourcename: str, binname: str) -> T.List[str]: + # 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 = [] + # Disable warnings, compile with statically-linked runtime for minimum # reliance on the system. - flags = ['-w', '-cudart', 'static', sourcename] + flags += ['-w', '-cudart', 'static', source_name] # Use the -ccbin option, if available, even during sanity checking. # Otherwise, on systems where CUDA does not support the default compiler, @@ -543,30 +562,33 @@ # 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(binname) + flags += self.get_output_args(binary_name) - return self.exelist + flags + # 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.') - 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') + # Run sanity check (if possible) + if self.is_cross: 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)) + cmdlist = self.exelist + ['--run', f'"{binary_name}"'] try: - 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: + stdo, stde = self.run_sanity_check(env, cmdlist, work_dir) + except EnvironmentException: 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 2814ca0..50bb465 100644 --- a/mesonbuild/compilers/cython.py +++ b/mesonbuild/compilers/cython.py
@@ -1,14 +1,13 @@ # 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 version_compare +from ..mesonlib import EnvironmentException, version_compare from .compilers import Compiler if T.TYPE_CHECKING: @@ -50,22 +49,16 @@ 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 4df8f65..9f662ad 100644 --- a/mesonbuild/compilers/d.py +++ b/mesonbuild/compilers/d.py
@@ -5,6 +5,7 @@ import os.path import re +import subprocess import typing as T from .. import mesonlib @@ -437,11 +438,24 @@ full_version=full_version, is_cross=is_cross) self.arch = arch - def _sanity_check_source_code(self) -> str: - return 'void main() { }' + 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_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] + 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 needs_static_linker(self) -> bool: return True
diff --git a/mesonbuild/compilers/detect.py b/mesonbuild/compilers/detect.py index 483a584..a0ae810 100644 --- a/mesonbuild/compilers/detect.py +++ b/mesonbuild/compilers/detect.py
@@ -22,7 +22,6 @@ import typing as T if T.TYPE_CHECKING: - from .asm import ASMCompiler from .compilers import Compiler from .c import CCompiler from .cpp import CPPCompiler @@ -1336,20 +1335,20 @@ if 'NASM' in output: comp_class = NasmCompiler env.add_lang_args(comp_class.language, comp_class, for_machine) - return comp_class([], comp, version, for_machine, info, cc, is_cross=is_cross) + return comp_class([], comp, version, for_machine, info, cc.linker, is_cross=is_cross) elif 'yasm' in output: comp_class = YasmCompiler env.add_lang_args(comp_class.language, comp_class, for_machine) - return comp_class([], comp, version, for_machine, info, cc, is_cross=is_cross) + return comp_class([], comp, version, for_machine, info, cc.linker, is_cross=is_cross) elif 'Metrowerks' in output or 'Freescale' in output: if 'ARM' in output: comp_class_mwasmarm = MetrowerksAsmCompilerARM env.add_lang_args(comp_class_mwasmarm.language, comp_class_mwasmarm, for_machine) - return comp_class_mwasmarm([], comp, version, for_machine, info, cc, is_cross=is_cross) + return comp_class_mwasmarm([], comp, version, for_machine, info, cc.linker, is_cross=is_cross) else: comp_class_mwasmeppc = MetrowerksAsmCompilerEmbeddedPowerPC env.add_lang_args(comp_class_mwasmeppc.language, comp_class_mwasmeppc, for_machine) - return comp_class_mwasmeppc([], comp, version, for_machine, info, cc, is_cross=is_cross) + return comp_class_mwasmeppc([], comp, version, for_machine, info, cc.linker, is_cross=is_cross) _handle_exceptions(popen_exceptions, compilers) raise EnvironmentException('Unreachable code (exception to make mypy happy)') @@ -1365,7 +1364,7 @@ info = env.machines[for_machine] from .asm import MasmCompiler, MasmARMCompiler - comp_class: T.Type[ASMCompiler] + comp_class: T.Type[Compiler] if info.cpu_family == 'x86': comp = ['ml'] comp_class = MasmCompiler @@ -1390,7 +1389,7 @@ output = Popen_safe(comp + [arg])[2] version = search_version(output) env.add_lang_args(comp_class.language, comp_class, for_machine) - return comp_class([], comp, version, for_machine, info, cc, is_cross=is_cross) + return comp_class([], comp, version, for_machine, info, cc.linker, is_cross=is_cross) except OSError as e: popen_exceptions[' '.join(comp + [arg])] = e _handle_exceptions(popen_exceptions, [comp])
diff --git a/mesonbuild/compilers/fortran.py b/mesonbuild/compilers/fortran.py index 2853ee2..6f4f3d2 100644 --- a/mesonbuild/compilers/fortran.py +++ b/mesonbuild/compilers/fortran.py
@@ -3,7 +3,6 @@ from __future__ import annotations -import textwrap import typing as T import functools import os @@ -62,12 +61,10 @@ largs = env.coredata.get_external_link_args(self.for_machine, self.language) return cargs, largs - def _sanity_check_source_code(self) -> str: - return textwrap.dedent(''' - PROGRAM MAIN - PRINT *, "Fortran compilation is working." - END - ''') + 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 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 13e4847..47d2ac9 100644 --- a/mesonbuild/compilers/java.py +++ b/mesonbuild/compilers/java.py
@@ -6,6 +6,7 @@ import os import os.path import shutil +import subprocess import textwrap import typing as T @@ -71,32 +72,33 @@ return parameter_list - 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]: + 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.') runner = shutil.which(self.javarunner) - if runner is None: + if runner: + cmdlist = [runner, '-cp', '.', obj] + self.run_sanity_check(environment, cmdlist, work_dir, use_exe_wrapper_for_cross=False) + else: 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 bd139d2..a492fff 100644 --- a/mesonbuild/compilers/mixins/clike.py +++ b/mesonbuild/compilers/mixins/clike.py
@@ -268,15 +268,50 @@ def gen_import_library_args(self, implibname: str) -> T.List[str]: return self.linker.import_library_args(implibname) - 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) + 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) extra_flags = cargs + self.linker_to_compiler_args(largs) - # 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 + + # 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) 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 b6deddd..d013417 100644 --- a/mesonbuild/compilers/objc.py +++ b/mesonbuild/compilers/objc.py
@@ -48,8 +48,9 @@ def get_display_language() -> str: return 'Objective-C' - def _sanity_check_source_code(self) -> str: - return '#import<stddef.h>\nint main(void) { return 0; }\n' + 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 form_compileropt_key(self, basename: str) -> OptionKey: if basename == 'std':
diff --git a/mesonbuild/compilers/objcpp.py b/mesonbuild/compilers/objcpp.py index e59d327..441428b 100644 --- a/mesonbuild/compilers/objcpp.py +++ b/mesonbuild/compilers/objcpp.py
@@ -50,8 +50,9 @@ def get_display_language() -> str: return 'Objective-C++' - def _sanity_check_source_code(self) -> str: - return '#import<stdio.h>\nclass MyClass;int main(void) { return 0; }\n' + 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 get_options(self) -> MutableKeyedOptionDictType: opts = super().get_options()
diff --git a/mesonbuild/compilers/rust.py b/mesonbuild/compilers/rust.py index 07faba8..d0f9266 100644 --- a/mesonbuild/compilers/rust.py +++ b/mesonbuild/compilers/rust.py
@@ -107,43 +107,44 @@ def needs_static_linker(self) -> bool: return False - 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 {} - } - ''') - def sanity_check(self, work_dir: str, environment: Environment) -> None: - super().sanity_check(work_dir, environment) - source_name = self._sanity_check_filenames()[0] + source_name = os.path.join(work_dir, 'sanity.rs') + output_name = os.path.join(work_dir, 'rusttest.exe') + cmdlist = self.exelist.copy() + + 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.') 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 94e565a..4ad3aff 100644 --- a/mesonbuild/compilers/swift.py +++ b/mesonbuild/compilers/swift.py
@@ -177,21 +177,22 @@ return parameter_list - 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 + 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) if self.is_cross: - cmdlist.extend(self.get_compile_only_args()) + extra_flags += self.get_compile_only_args() else: - 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.")' + 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) 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 230a773..bbaefed 100644 --- a/mesonbuild/compilers/vala.py +++ b/mesonbuild/compilers/vala.py
@@ -8,7 +8,7 @@ from .. import mlog from .. import mesonlib -from ..mesonlib import version_compare, LibType +from ..mesonlib import EnvironmentException, version_compare, LibType from ..options import OptionKey from .compilers import CompileCheckMode, Compiler @@ -99,25 +99,18 @@ return parameter_list - 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)) + 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) if self.is_cross: - cmdlist.extend(self.get_compile_only_args()) + extra_flags += self.get_compile_only_args() else: - 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) + 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) 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 b746fa8..72dbfdf 100644 --- a/mesonbuild/dependencies/base.py +++ b/mesonbuild/dependencies/base.py
@@ -55,11 +55,8 @@ def get_output_args(self, outputname: str) -> T.List[str]: return [] - 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 sanity_check(self, work_dir: str, environment: 'Environment') -> None: + return None def __getattr__(self, item: str) -> T.Any: if item.startswith('__'):