diff --git a/mesonbuild/compilers/asm.py b/mesonbuild/compilers/asm.py index c298933a1861..ff4825beec12 100644 --- a/mesonbuild/compilers/asm.py +++ b/mesonbuild/compilers/asm.py @@ -11,9 +11,9 @@ from .mixins.ti import TICompiler if T.TYPE_CHECKING: + from ..environment import Environment from ..linkers.linkers import DynamicLinker from ..mesonlib import MachineChoice - from ..environment import Environment nasm_optimization_args: T.Dict[str, T.List[str]] = { 'plain': [], @@ -44,6 +44,10 @@ def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, def sanity_check(self, work_dir: str) -> None: return None + def _sanity_check_source_code(self) -> str: + # TODO: Stub implementation to be replaced in future patch + return '' + class NasmCompiler(ASMCompiler): language = 'nasm' diff --git a/mesonbuild/compilers/c.py b/mesonbuild/compilers/c.py index 7143d9e92d7a..4d05ab9c4a8b 100644 --- a/mesonbuild/compilers/c.py +++ b/mesonbuild/compilers/c.py @@ -74,9 +74,8 @@ def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, for_ def get_no_stdinc_args(self) -> T.List[str]: return ['-nostdinc'] - def sanity_check(self, work_dir: str) -> None: - code = 'int main(void) { int class=0; return class; }\n' - return self._sanity_check_impl(work_dir, '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, *, extra_args: T.Union[None, T.List[str], T.Callable[['CompileCheckMode'], T.List[str]]] = None, diff --git a/mesonbuild/compilers/compilers.py b/mesonbuild/compilers/compilers.py index 2d813eb9b1a5..dc2c598849ec 100644 --- a/mesonbuild/compilers/compilers.py +++ b/mesonbuild/compilers/compilers.py @@ -1210,7 +1210,6 @@ def get_has_func_attribute_extra_args(self, name: str) -> T.List[str]: def name_string(self) -> str: return ' '.join(self.exelist) - @abc.abstractmethod def sanity_check(self, work_dir: str) -> None: """Check that this compiler actually works. @@ -1219,24 +1218,152 @@ def sanity_check(self, work_dir: str) -> None: main(): return 0 ``` is good enough here. + + :param work_dir: A directory to put temporary artifacts + :raises mesonlib.EnvironmentException: If building the binary fails + :raises mesonlib.EnvironmentException: If running the binary is attempted and fails """ + sourcename, transpiled, binname = self._sanity_check_filenames() + + with open(os.path.join(work_dir, sourcename), 'w', encoding='utf-8') as f: + f.write(self._sanity_check_source_code()) + + if transpiled: + cmdlist, linker_args = self._sanity_check_compile_args(sourcename, transpiled) + cmdlist.extend(linker_args) + pc, stdo, stde = mesonlib.Popen_safe(cmdlist, cwd=work_dir) + mlog.debug('Sanity check transpiler command line:', mesonlib.join_args(cmdlist)) + mlog.debug('Sanity check transpiler stdout:') + mlog.debug(stdo) + mlog.debug('-----\nSanity check transpiler stderr:') + mlog.debug(stde) + mlog.debug('-----') + if pc.returncode != 0: + raise mesonlib.EnvironmentException(f'Compiler {self.name_string()} cannot transpile programs.') + + comp_lang = SUFFIX_TO_LANG[transpiled.rsplit('.', maxsplit=1)[1]] + comp = self.environment.coredata.compilers[self.for_machine].get(comp_lang) + if not comp: + raise mesonlib.MesonBugException(f'Need a {comp_lang} compiler for {self.language} compiler test, but one doesnt exist') + + cmdlist, linker_args = self._transpiled_sanity_check_compile_args(comp, transpiled, binname) + + mlog.debug('checking compilation of transpiled source:') + else: + cmdlist, linker_args = self._sanity_check_compile_args(sourcename, binname) + + cmdlist.extend(linker_args) + 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([os.path.join(work_dir, binname)], work_dir) + + def _sanity_check_filenames(self) -> T.Tuple[str, T.Optional[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. + + The return value consists of a source name, a transpiled source name (if + there is one), and the final binary name. + + :return: A tuple of (sourcename, transpiled, binaryname) + """ + default_ext = lang_suffixes[self.language][0] + template = f'sanity_check_for_{self.language}' + sourcename = f'{template}.{default_ext}' + cross_or_not = "_cross" if self.is_cross else "" + binaryname = f'{template}{cross_or_not}.exe' + return sourcename, None, binaryname + + def _transpiled_sanity_check_compile_args( + self, compiler: Compiler, sourcename: str, binname: str + ) -> T.Tuple[T.List[str], T.List[str]]: + """Get arguments to run compiler for sanity check. + + By default this will just return the compile arguments for the compiler in question. + + Linker arguments are separated from compiler arguments because some + compilers do not allow linker and compiler arguments to be mixed together + + Overriding this is useful when needing to change the kind of output + produced, or adding extra arguments. + + :param compiler: The :class:`Compiler` that is used for compiling the transpiled sources + :param sourcename: the name of the source file to generate + :param binname: the name of the binary file to generate + :return: a tuple of arguments, the first is the executable and compiler + arguments, the second is linker arguments + """ + return compiler._sanity_check_compile_args(sourcename, binname) + + def _sanity_check_compile_args(self, sourcename: str, binname: str + ) -> T.Tuple[T.List[str], T.List[str]]: + """Get arguments to run compiler for sanity check. + + Linker arguments are separated from compiler arguments because some + compilers do not allow linker and compiler arguments to be mixed together - def run_sanity_check(self, 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 self.environment.has_exe_wrapper(): - # Can't check if the binaries run so we have to assume they do - return ('', '') - cmdlist = self.environment.exe_wrapper.get_command() + cmdlist - mlog.debug('Running test binary command: ', mesonlib.join_args(cmdlist)) + :param sourcename: the name of the source file to generate + :param binname: the name of the binary file to generate + :return: a tuple of arguments, the first is the executable and compiler + arguments, the second is linker arguments + """ + return self.exelist_no_ccache + self.get_always_args() + self.get_output_args(binname) + [sourcename], [] + + @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, 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 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 self.environment.has_exe_wrapper(): + assert self.environment.exe_wrapper is not None, 'for mypy' + return self.environment.exe_wrapper.get_command() + command + return command + + def _run_sanity_check(self, cmdlist: T.List[str], work_dir: str) -> None: + """Run a sanity test binary + + :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 self.environment.has_exe_wrapper(): + mlog.debug('Cannot run cross check') + return + + cmdlist = self._sanity_check_run_with_exe_wrapper(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 bdf60f6c23a2..8d8c0cd42017 100644 --- a/mesonbuild/compilers/cpp.py +++ b/mesonbuild/compilers/cpp.py @@ -84,9 +84,8 @@ def get_no_stdinc_args(self) -> T.List[str]: def get_no_stdlib_link_args(self) -> T.List[str]: return ['-nostdlib++'] - def sanity_check(self, work_dir: str) -> None: - code = 'class breakCCompiler;int main(void) { return 0; }\n' - return self._sanity_check_impl(work_dir, '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 cacd9ee47d41..863e240897c4 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 @@ -82,26 +81,18 @@ def get_pch_use_args(self, pch_dir: str, header: str) -> T.List[str]: def get_pch_name(self, header_name: str) -> str: return '' - def sanity_check(self, work_dir: str) -> 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_run_with_exe_wrapper(self, 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(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 cf0fceca5340..70246915dcd5 100644 --- a/mesonbuild/compilers/cuda.py +++ b/mesonbuild/compilers/cuda.py @@ -5,17 +5,12 @@ from __future__ import annotations import enum -import os.path import string import typing as T from .. import options -from .. import mlog -from ..mesonlib import ( - EnvironmentException, Popen_safe, - is_windows, LibType, version_compare -) -from .compilers import Compiler, CompileCheckMode +from ..mesonlib import is_windows, LibType, version_compare +from .compilers import Compiler, CompileCheckMode, CrossNoRunException if T.TYPE_CHECKING: from ..build import BuildTarget @@ -185,6 +180,7 @@ def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, for_ host_compiler: Compiler, env: Environment, linker: T.Optional['DynamicLinker'] = None, full_version: T.Optional[str] = None): + self.detected_cc = '' super().__init__(ccache, exelist, version, for_machine, env, linker=linker, full_version=full_version) self.host_compiler = host_compiler self.base_options = host_compiler.base_options @@ -497,55 +493,46 @@ def needs_static_linker(self) -> bool: def thread_link_flags(self) -> T.List[str]: return self._to_host_flags(self.host_compiler.thread_link_flags(), Phase.LINKER) - def sanity_check(self, work_dir: str) -> 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 - #include - - __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 None: + super().init_from_options() + try: + res = self.run(self._sanity_check_source_code()) + if res.returncode == 0: + self.detected_cc = res.stdout.strip() + except CrossNoRunException: + pass + + def _sanity_check_source_code(self) -> str: + return r''' + #include + #include + + __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 T.Tuple[T.List[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, @@ -560,35 +547,9 @@ def sanity_check(self, work_dir: str) -> None: # 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: - return - - cmdlist = self.exelist + ['--run', f'"{binary_name}"'] - try: - stdo, stde = self.run_sanity_check(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 + flags += self.get_output_args(binname) + + return self.exelist + flags, [] def has_header_symbol(self, hname: str, symbol: str, prefix: str, *, extra_args: T.Union[None, T.List[str], T.Callable[[CompileCheckMode], T.List[str]]] = None, diff --git a/mesonbuild/compilers/cython.py b/mesonbuild/compilers/cython.py index b33147e34368..9bc14c10c1fd 100644 --- a/mesonbuild/compilers/cython.py +++ b/mesonbuild/compilers/cython.py @@ -1,13 +1,15 @@ # 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 .. import mlog +from ..mesonlib import version_compare, EnvironmentException from .compilers import Compiler if T.TYPE_CHECKING: @@ -48,16 +50,59 @@ def get_dependency_gen_args(self, outtarget: str, outfile: str) -> T.List[str]: def get_depfile_suffix(self) -> str: return 'dep' - def sanity_check(self, work_dir: str) -> None: - code = 'print("hello world")' - with self.cached_compile(code) 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_filenames(self) -> T.Tuple[str, T.Optional[str], str]: + sourcename, _, binname = super()._sanity_check_filenames() + + lang = self.get_compileropt_value('language', None) + assert isinstance(lang, str) + + # This is almost certainly not good enough + ext = 'dll' if self.environment.machines[self.for_machine].is_windows() else 'so' + + return (sourcename, f'{os.path.splitext(sourcename)[0]}.{lang}', + f'{os.path.splitext(binname)[0]}.{ext}') + + def _transpiled_sanity_check_compile_args( + self, compiler: Compiler, sourcename: str, binname: str + ) -> T.Tuple[T.List[str], T.List[str]]: + version = self.get_compileropt_value('version', None) + assert isinstance(version, str) + + from ..dependencies import find_external_dependency + with mlog.no_logging(): + dep = find_external_dependency(f'python{version}', self.environment, {'required': False}) + if not dep.found(): + raise EnvironmentException( + 'Cython requires python3 dependency for link testing, but it could not be found') + + args, largs = super()._transpiled_sanity_check_compile_args(compiler, sourcename, binname) + args.extend(compiler.get_pic_args()) + args.extend(dep.get_all_compile_args()) + + largs.extend(dep.get_all_link_args()) + largs.extend(compiler.get_std_shared_lib_link_args()) + largs.extend(compiler.get_allow_undefined_link_args()) + return args, largs + + def _sanity_check_compile_args(self, sourcename: str, binname: str + ) -> T.Tuple[T.List[str], T.List[str]]: + args, largs = super()._sanity_check_compile_args(sourcename, binname) + args.extend(self.get_option_compile_args(None)) + return args, largs + + def _sanity_check_source_code(self) -> str: + return 'def func():\n print("Hello world")' + + def _run_sanity_check(self, cmdlist: T.List[str], work_dir: str) -> None: + # XXX: this is a punt + # This means we transpile the Cython .pyx file into C or C++, and we + # link it, but we don't actually attempt to run it. + return + 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 dd120947dd52..bfeac85b8e02 100644 --- a/mesonbuild/compilers/d.py +++ b/mesonbuild/compilers/d.py @@ -5,7 +5,6 @@ import os.path import re -import subprocess import typing as T from .. import mesonlib @@ -436,24 +435,14 @@ def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoic full_version=full_version) self.arch = arch - def sanity_check(self, work_dir: str) -> 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 self.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([output_name], work_dir) + def _sanity_check_compile_args(self, sourcename: str, binname: str + ) -> T.Tuple[T.List[str], T.List[str]]: + args, largs = super()._sanity_check_compile_args(sourcename, binname) + largs.extend(self._get_target_arch_args()) + return args, largs def needs_static_linker(self) -> bool: return True diff --git a/mesonbuild/compilers/fortran.py b/mesonbuild/compilers/fortran.py index 7654b3ffab49..3e14123d967d 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 @@ -59,10 +60,12 @@ def _get_basic_compiler_args(self, mode: CompileCheckMode) -> T.Tuple[T.List[str largs = self.environment.coredata.get_external_link_args(self.for_machine, self.language) return cargs, largs - def sanity_check(self, work_dir: str) -> None: - source_name = 'sanitycheckf.f' - code = ' PROGRAM MAIN\n PRINT *, "Fortran compilation is working."\n END\n' - return self._sanity_check_impl(work_dir, 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 f60bc6bbb50a..8f9671f89cac 100644 --- a/mesonbuild/compilers/java.py +++ b/mesonbuild/compilers/java.py @@ -6,7 +6,6 @@ import os import os.path import shutil -import subprocess import textwrap import typing as T @@ -71,33 +70,38 @@ def compute_parameters_with_absolute_paths(self, parameter_list: T.List[str], return parameter_list - def sanity_check(self, work_dir: str) -> 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, T.Optional[str], str]: + sup = super()._sanity_check_filenames() + return sup[0], None, 'SanityCheck' + + def _sanity_check_run_with_exe_wrapper(self, command: T.List[str]) -> T.List[str]: runner = shutil.which(self.javarunner) - if runner: - cmdlist = [runner, '-cp', '.', obj] - self.run_sanity_check(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) + basedir = os.path.basename(command[0]) + return [runner, '-cp', basedir, basedir] + + def _sanity_check_source_code(self) -> str: + return textwrap.dedent( + '''class SanityCheck { + public static void main(String[] args) { + int i; + } + } + ''') + + def sanity_check(self, work_dir: str) -> None: + # Older versions of Java (At least 1.8), don't create this directory and + # error when it doesn't exist. Newer versions (11 at least), doesn't have + # this issue. + fname = self._sanity_check_filenames()[2] + os.makedirs(os.path.join(work_dir, fname), exist_ok=True) + return super().sanity_check(work_dir) def needs_static_linker(self) -> bool: return False diff --git a/mesonbuild/compilers/mixins/clike.py b/mesonbuild/compilers/mixins/clike.py index e6637cb1c525..1409f9a8fb74 100644 --- a/mesonbuild/compilers/mixins/clike.py +++ b/mesonbuild/compilers/mixins/clike.py @@ -266,49 +266,14 @@ def gen_export_dynamic_link_args(self) -> T.List[str]: 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, 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 self.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(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([binary_name], work_dir) - - def sanity_check(self, work_dir: str) -> None: - code = 'int main(void) { int class=0; return class; }\n' - return self._sanity_check_impl(work_dir, 'sanitycheckc.c', code) + def _sanity_check_compile_args(self, sourcename: str, binname: str + ) -> T.Tuple[T.List[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 self.environment.has_exe_wrapper() else CompileCheckMode.LINK + cargs, b_largs = self._get_basic_compiler_args(mode) + largs = self.linker_to_compiler_args(b_largs) + s_args, s_largs = super()._sanity_check_compile_args(sourcename, binname) + return s_args + cargs, s_largs + largs def check_header(self, hname: str, prefix: str, *, 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 d62113b1bda5..7b196ccb9bb7 100644 --- a/mesonbuild/compilers/objc.py +++ b/mesonbuild/compilers/objc.py @@ -47,9 +47,8 @@ def get_options(self) -> MutableKeyedOptionDictType: def get_display_language() -> str: return 'Objective-C' - def sanity_check(self, work_dir: str) -> None: - code = '#import\nint main(void) { return 0; }\n' - return self._sanity_check_impl(work_dir, 'sanitycheckobjc.m', code) + def _sanity_check_source_code(self) -> str: + return '#import\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 927ca7e0b62c..ad047d78626b 100644 --- a/mesonbuild/compilers/objcpp.py +++ b/mesonbuild/compilers/objcpp.py @@ -48,9 +48,8 @@ def make_option_name(self, key: OptionKey) -> str: def get_display_language() -> str: return 'Objective-C++' - def sanity_check(self, work_dir: str) -> None: - code = '#import\nclass MyClass;int main(void) { return 0; }\n' - return self._sanity_check_impl(work_dir, 'sanitycheckobjcpp.mm', code) + def _sanity_check_source_code(self) -> str: + return '#import\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 ab0706d26554..0d2ecc9d5e5d 100644 --- a/mesonbuild/compilers/rust.py +++ b/mesonbuild/compilers/rust.py @@ -122,44 +122,45 @@ def init_from_options(self) -> None: def needs_static_linker(self) -> bool: return False - def sanity_check(self, work_dir: str) -> 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, sourcename: str, binname: str + ) -> T.Tuple[T.List[str], T.List[str]]: cmdlist = self.exelist.copy() + largs: T.List[str] = [] + assert self.linker is not None, 'for mypy' + if self.info.kernel == 'none' and 'ld.' in self.linker.id: + largs.extend(rustc_link_args(['-nostartfiles'])) + cmdlist.extend(self.get_output_args(binname)) + cmdlist.append(sourcename) + return cmdlist, largs + + 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) -> None: + super().sanity_check(work_dir) + source_name = self._sanity_check_filenames()[0] self._native_static_libs(work_dir, source_name) - self.run_sanity_check([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 d5f87a23649f..fad6912261ae 100644 --- a/mesonbuild/compilers/swift.py +++ b/mesonbuild/compilers/swift.py @@ -175,22 +175,25 @@ def compute_parameters_with_absolute_paths(self, parameter_list: T.List[str], return parameter_list - def sanity_check(self, work_dir: str) -> 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 += self.environment.coredata.get_external_args(self.for_machine, self.language) + def _sanity_check_compile_args(self, sourcename: str, binname: str + ) -> T.Tuple[T.List[str], T.List[str]]: + args = self.exelist.copy() + largs: T.List[str] = [] + + # TODO: I can't test this, but it doesn't seem right if self.is_cross: - extra_flags += self.get_compile_only_args() + args.extend(self.get_compile_only_args()) else: - extra_flags += self.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([output_name], work_dir) + largs.extend(self.environment.coredata.get_external_link_args(self.for_machine, self.language)) + args.extend(self.get_output_args(binname)) + args.append(sourcename) + + largs.extend(self.get_std_exe_link_args()) + + return args, largs + + 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 575cf400623b..53985c92a9d9 100644 --- a/mesonbuild/compilers/vala.py +++ b/mesonbuild/compilers/vala.py @@ -8,7 +8,7 @@ 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 @@ -31,6 +31,7 @@ def __init__(self, exelist: T.List[str], version: str, for_machine: MachineChoic self.base_options = {OptionKey('b_colorout')} self.force_link = False self._has_color_support = version_compare(self.version, '>=0.37.1') + self._has_posix_profile = version_compare(self.version, '>= 0.44') def needs_static_linker(self) -> bool: return False # Because compiles into C. @@ -106,18 +107,41 @@ def compute_parameters_with_absolute_paths(self, parameter_list: T.List[str], return parameter_list - def sanity_check(self, work_dir: str) -> None: - code = 'class MesonSanityCheck : Object { }' - extra_flags: T.List[str] = [] - extra_flags += self.environment.coredata.get_external_args(self.for_machine, self.language) - if self.is_cross: - extra_flags += self.get_compile_only_args() - else: - extra_flags += self.environment.coredata.get_external_link_args(self.for_machine, self.language) - with self.cached_compile(code, 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 _sanity_check_source_code(self) -> str: + return 'public static int main() { return 0; }' + + def _sanity_check_compile_args(self, sourcename: str, binname: str + ) -> T.Tuple[T.List[str], T.List[str]]: + args, largs = super()._sanity_check_compile_args(sourcename, binname) + if self._has_posix_profile: + # This removes the glib requirement. Posix and libc are equivalent, + # but posix is available in older versions of valac + args.append('--profile=posix') + return args, largs + + def _transpiled_sanity_check_compile_args( + self, compiler: Compiler, sourcename: str, binname: str + ) -> T.Tuple[T.List[str], T.List[str]]: + args, largs = super()._transpiled_sanity_check_compile_args(compiler, sourcename, binname) + if self._has_posix_profile: + return args, largs + + # If valac is too old for the posix profile then we need to find goobject-2.0 for linking. + from ..dependencies import find_external_dependency + with mlog.no_logging(): + dep = find_external_dependency('gobject-2.0', self.environment, + {'required': False, 'native': self.for_machine}) + if not dep.found(): + raise mesonlib.EnvironmentException( + 'Valac < 0.44 requires gobject-2.0 for link testing, bit it could not be found.') + + args.extend(dep.get_all_compile_args()) + largs.extend(dep.get_all_link_args()) + return args, largs + + def _sanity_check_filenames(self) -> T.Tuple[str, T.Optional[str], str]: + sourcename, _, binname = super()._sanity_check_filenames() + return sourcename, f'{os.path.splitext(sourcename)[0]}.c', binname def find_library(self, libname: str, 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 4536746d940f..7fe8d427272d 100644 --- a/mesonbuild/dependencies/base.py +++ b/mesonbuild/dependencies/base.py @@ -93,8 +93,8 @@ def get_optimization_args(self, optimization_level: str) -> T.List[str]: def get_output_args(self, outputname: str) -> T.List[str]: return [] - def sanity_check(self, work_dir: str) -> None: - return None + def _sanity_check_source_code(self) -> str: + return '' def __getattr__(self, item: str) -> T.Any: if item.startswith('__'): diff --git a/run_project_tests.py b/run_project_tests.py index 62fe73ac3486..b3195dbbdcad 100755 --- a/run_project_tests.py +++ b/run_project_tests.py @@ -37,7 +37,7 @@ from mesonbuild import mesonlib from mesonbuild import mlog from mesonbuild import mtest -from mesonbuild.compilers import compiler_from_language +from mesonbuild.compilers import detect_compiler_for from mesonbuild.build import ConfigurationData from mesonbuild.mesonlib import MachineChoice, Popen_safe, TemporaryDirectoryWinProof, setup_vsenv from mesonbuild.mlog import blue, bold, cyan, green, red, yellow, normal_green @@ -136,11 +136,11 @@ def __init__(self, raw: T.Dict[str, str]): # split on '' will return [''], we want an empty list though self.version = [] - def get_path(self, compiler: str, env: environment.Environment) -> T.Optional[Path]: + def get_path(self, compiler: compilers.Compiler, env: environment.Environment) -> T.Optional[Path]: p = Path(self.path) - canonical_compiler = compiler - if ((compiler in ['clang-cl', 'intel-cl']) or - (env.machines.host.is_windows() and compiler in {'pgi', 'dmd', 'ldc'})): + canonical_compiler = compiler.get_id() + if ((canonical_compiler in ['clang-cl', 'intel-cl']) or + (env.machines.host.is_windows() and canonical_compiler in {'pgi', 'dmd', 'ldc'})): canonical_compiler = 'msvc' python_suffix = python.info['suffix'] @@ -231,7 +231,7 @@ def get_path(self, compiler: str, env: environment.Environment) -> T.Optional[Pa elif self.typ in {'implib', 'implibempty'}: if env.machines.host.is_windows() and canonical_compiler == 'msvc': # only MSVC doesn't generate empty implibs - if self.typ == 'implibempty' and compiler == 'msvc': + if self.typ == 'implibempty' and compiler.get_id() == 'msvc': return None return p.parent / (re.sub(r'^lib', '', p.name) + '.lib') elif env.machines.host.is_windows() or env.machines.host.is_cygwin(): @@ -245,7 +245,7 @@ def get_path(self, compiler: str, env: environment.Environment) -> T.Optional[Pa return p - def get_paths(self, compiler: str, env: environment.Environment, installdir: Path) -> T.List[Path]: + def get_paths(self, compiler: compilers.Compiler, env: environment.Environment, installdir: Path) -> T.List[Path]: p = self.get_path(compiler, env) if not p: return [] @@ -311,8 +311,8 @@ def __lt__(self, other: object) -> bool: do_debug = under_ci or print_debug no_meson_log_msg = 'No meson-log.txt found.' -host_c_compiler: T.Optional[str] = None compiler_id_map: T.Dict[str, str] = {} +all_compilers: mesonlib.PerMachine[T.Dict[str, T.Optional[compilers.Compiler]]] = mesonlib.PerMachine({}, {}) tool_vers_map: T.Dict[str, str] = {} compile_commands: T.List[str] @@ -345,7 +345,7 @@ def stop_handler(signal: int, frame: T.Optional['FrameType']) -> None: signal.signal(signal.SIGTERM, stop_handler) def setup_commands(optbackend: str) -> None: - global do_debug, backend, backend_flags + global backend, backend_flags global compile_commands, clean_commands, test_commands, install_commands, uninstall_commands backend, backend_flags = guess_backend(optbackend, shutil.which('msbuild')) compile_commands, clean_commands, test_commands, install_commands, \ @@ -393,9 +393,15 @@ def platform_fix_name(fname: str, canonical_compiler: str, env: environment.Envi def validate_install(test: TestDef, installdir: Path, env: environment.Environment) -> str: ret_msg = '' expected_raw: T.List[Path] = [] + c_compiler = all_compilers.host.get('c') + + # We cannot do validation without a C compiler for the host + if c_compiler is None: + return None + for i in test.installed_files: try: - expected_raw += i.get_paths(host_c_compiler, env, installdir) + expected_raw += i.get_paths(c_compiler, env, installdir) except RuntimeError as err: ret_msg += f'Expected path error: {err}\n' expected = {x: False for x in expected_raw} @@ -629,7 +635,7 @@ class GlobalState(T.NamedTuple): backend: 'Backend' backend_flags: T.List[str] - host_c_compiler: T.Optional[str] + all_compilers: mesonlib.PerMachine[T.Dict[str, T.Optional[compilers.Compiler]]] = mesonlib.PerMachine({}, {}) def run_test(test: TestDef, extra_args: T.List[str], @@ -637,9 +643,9 @@ def run_test(test: TestDef, use_tmp: bool, state: T.Optional[GlobalState] = None) -> T.Optional[TestResult]: # Unpack the global state - global compile_commands, clean_commands, test_commands, install_commands, uninstall_commands, backend, backend_flags, host_c_compiler + global compile_commands, clean_commands, test_commands, install_commands, uninstall_commands, backend, backend_flags, all_compilers if state is not None: - compile_commands, clean_commands, test_commands, install_commands, uninstall_commands, backend, backend_flags, host_c_compiler = state + compile_commands, clean_commands, test_commands, install_commands, uninstall_commands, backend, backend_flags, all_compilers = state # Store that this is a worker process global is_worker_process is_worker_process = True @@ -961,53 +967,8 @@ def gather_tests(testdir: Path, stdout_mandatory: bool, only: T.List[str], skip_ return sorted(all_tests) -def have_d_compiler() -> bool: - if shutil.which("ldc2"): - return True - elif shutil.which("ldc"): - return True - elif shutil.which("gdc"): - return True - elif shutil.which("dmd"): - # The Windows installer sometimes produces a DMD install - # that exists but segfaults every time the compiler is run. - # Don't know why. Don't know how to fix. Skip in this case. - cp = subprocess.run(['dmd', '--version'], - capture_output=True) - if cp.stdout == b'': - return False - return True - return False - -def have_objc_compiler(use_tmp: bool) -> bool: - return have_working_compiler('objc', use_tmp) - -def have_objcpp_compiler(use_tmp: bool) -> bool: - return have_working_compiler('objcpp', use_tmp) - -def have_cython_compiler(use_tmp: bool) -> bool: - return have_working_compiler('cython', use_tmp) - -def have_working_compiler(lang: str, use_tmp: bool) -> bool: - with TemporaryDirectoryWinProof(prefix='b ', dir=None if use_tmp else '.') as build_dir: - env = environment.Environment('', build_dir, get_fake_options('/')) - try: - compiler = compiler_from_language(env, lang, MachineChoice.HOST) - except mesonlib.MesonException: - return False - if not compiler: - return False - env.coredata.process_compiler_options(lang, compiler, '') - try: - compiler.sanity_check(env.get_scratch_dir()) - except mesonlib.MesonException: - return False - return True - def have_java() -> bool: - if shutil.which('javac') and shutil.which('java'): - return True - return False + return all_compilers.host['java'] is not None and shutil.which('java') is not None def skip_dont_care(t: TestDef) -> bool: # Everything is optional when not running on CI @@ -1028,9 +989,12 @@ def skip_csharp(backend: Backend) -> bool: return True if not shutil.which('resgen'): return True - if shutil.which('mcs'): + comp = all_compilers.host['cs'] + if comp is None: + return True + if comp.id == 'mono': return False - if shutil.which('csc'): + if comp.id == 'csc': # Only support VS2017 for now. Earlier versions fail # under CI in mysterious ways. try: @@ -1045,33 +1009,6 @@ def skip_csharp(backend: Backend) -> bool: return not stdo.startswith(b'2.') return True -# In Azure some setups have a broken rustc that will error out -# on all compilation attempts. - -def has_broken_rustc() -> bool: - dirname = Path('brokenrusttest') - if dirname.exists(): - mesonlib.windows_proof_rmtree(dirname.as_posix()) - dirname.mkdir() - sanity_file = dirname / 'sanity.rs' - sanity_file.write_text('fn main() {\n}\n', encoding='utf-8') - pc = subprocess.run(['rustc', '-o', 'sanity.exe', 'sanity.rs'], - cwd=dirname.as_posix(), - stdout = subprocess.DEVNULL, - stderr = subprocess.DEVNULL) - mesonlib.windows_proof_rmtree(dirname.as_posix()) - return pc.returncode != 0 - -def should_skip_rust(backend: Backend) -> bool: - if not shutil.which('rustc'): - return True - if backend is not Backend.ninja: - return True - if mesonlib.is_windows(): - if has_broken_rustc(): - return True - return False - def should_skip_wayland() -> bool: if mesonlib.is_windows() or mesonlib.is_osx(): return True @@ -1092,14 +1029,6 @@ def detect_tests_to_run(only: T.Dict[str, T.List[str]], use_tmp: bool) -> T.List tests to run """ - skip_fortran = not(shutil.which('gfortran') or - shutil.which('flang-new') or - shutil.which('flang') or - shutil.which('pgfortran') or - shutil.which('nagfor') or - shutil.which('ifort') or - shutil.which('ifx')) - skip_cmake = ((os.environ.get('compiler') == 'msvc2015' and under_ci) or 'cmake' not in tool_vers_map or not mesonlib.version_compare(tool_vers_map['cmake'], '>=3.14')) @@ -1127,16 +1056,16 @@ def __init__(self, category: str, subdir: str, skip: bool = False, stdout_mandat TestCategory('platform-android', 'android', not mesonlib.is_android()), TestCategory('java', 'java', backend is not Backend.ninja or not have_java()), TestCategory('C#', 'csharp', skip_csharp(backend)), - TestCategory('vala', 'vala', backend is not Backend.ninja or not shutil.which(os.environ.get('VALAC', 'valac'))), - TestCategory('cython', 'cython', backend is not Backend.ninja or not have_cython_compiler(options.use_tmpdir)), - TestCategory('rust', 'rust', should_skip_rust(backend)), - TestCategory('d', 'd', backend is not Backend.ninja or not have_d_compiler()), - TestCategory('objective c', 'objc', backend not in (Backend.ninja, Backend.xcode) or not have_objc_compiler(options.use_tmpdir)), - TestCategory('objective c++', 'objcpp', backend not in (Backend.ninja, Backend.xcode) or not have_objcpp_compiler(options.use_tmpdir)), - TestCategory('fortran', 'fortran', skip_fortran or backend != Backend.ninja), - TestCategory('swift', 'swift', backend not in (Backend.ninja, Backend.xcode) or not shutil.which('swiftc')), + TestCategory('vala', 'vala', backend is not Backend.ninja or all_compilers.host['vala'] is None), + TestCategory('cython', 'cython', backend is not Backend.ninja or all_compilers.host['cython'] is None), + TestCategory('rust', 'rust', backend is not Backend.ninja or all_compilers.host['rust'] is None), + TestCategory('d', 'd', backend is not Backend.ninja or all_compilers.host['d'] is None), + TestCategory('objective c', 'objc', backend not in (Backend.ninja, Backend.xcode) or all_compilers.host['objc'] is None), + TestCategory('objective c++', 'objcpp', backend not in (Backend.ninja, Backend.xcode) or all_compilers.host['objcpp'] is None), + TestCategory('fortran', 'fortran', backend != Backend.ninja or all_compilers.host['fortran'] is None), + TestCategory('swift', 'swift', backend not in (Backend.ninja, Backend.xcode) or all_compilers.host['swift'] is None), # CUDA tests on Windows: use Ninja backend: python run_project_tests.py --only cuda --backend ninja - TestCategory('cuda', 'cuda', backend not in (Backend.ninja, Backend.xcode) or not shutil.which('nvcc')), + TestCategory('cuda', 'cuda', backend not in (Backend.ninja, Backend.xcode) or all_compilers.host['cuda'] is None), TestCategory('python3', 'python3', backend is not Backend.ninja or 'python3' not in sys.executable), TestCategory('python', 'python'), TestCategory('fpga', 'fpga', shutil.which('yosys') is None), @@ -1152,9 +1081,7 @@ def __init__(self, category: str, subdir: str, skip: bool = False, stdout_mandat assert categories == ALL_TESTS, 'argparse("--only", choices=ALL_TESTS) need to be updated to match all_tests categories' if only: - for key in only.keys(): - assert key in categories, f'key `{key}` is not a recognized category' - all_tests = [t for t in all_tests if t.category in only.keys()] + all_tests = [t for t in all_tests if t.category in only] gathered_tests = [(t.category, gather_tests(Path('test cases', t.subdir), t.stdout_mandatory, only[t.category], t.skip), t.skip) for t in all_tests] return gathered_tests @@ -1229,7 +1156,6 @@ def _run_tests(all_tests: T.List[T.Tuple[str, T.List[TestDef], bool]], use_tmp: bool, num_workers: int, logfile: T.TextIO) -> T.Tuple[int, int, int]: - global stop, host_c_compiler xmlname = log_name_base + '.xml' junit_root = ET.Element('testsuites') conf_time: float = 0 @@ -1242,7 +1168,7 @@ def _run_tests(all_tests: T.List[T.Tuple[str, T.List[TestDef], bool]], print(f'\nRunning tests with {num_workers} workers') # Pack the global state - state = GlobalState(compile_commands, clean_commands, test_commands, install_commands, uninstall_commands, backend, backend_flags, host_c_compiler) + state = GlobalState(compile_commands, clean_commands, test_commands, install_commands, uninstall_commands, backend, backend_flags, all_compilers) executor = ProcessPoolExecutor(max_workers=num_workers) futures: T.List[RunFutureUnion] = [] @@ -1445,7 +1371,6 @@ def tqdm_print(*args: mlog.TV_Loggable, sep: str = ' ') -> None: return passing_tests, failing_tests, skipped_tests def check_meson_commands_work(use_tmpdir: bool, extra_args: T.List[str]) -> None: - global backend, compile_commands, test_commands, install_commands testdir = PurePath('test cases', 'common', '1 trivial').as_posix() meson_commands = mesonlib.python_command + [get_meson_script()] with TemporaryDirectoryWinProof(prefix='b ', dir=None if use_tmpdir else '.') as build_dir: @@ -1476,45 +1401,40 @@ def check_meson_commands_work(use_tmpdir: bool, extra_args: T.List[str]) -> None def detect_system_compiler(options: 'CompilerArgumentType') -> None: - global host_c_compiler, compiler_id_map - fake_opts = get_fake_options('/') if options.cross_file: fake_opts.cross_file = [options.cross_file] if options.native_file: fake_opts.native_file = [options.native_file] - env = environment.Environment('', '', fake_opts) - print_compilers(env, MachineChoice.HOST) + machines = [MachineChoice.HOST] if options.cross_file: - print_compilers(env, MachineChoice.BUILD) + machines.append(MachineChoice.BUILD) - for lang in sorted(compilers.all_languages): - try: - comp = compiler_from_language(env, lang, MachineChoice.HOST) - # note compiler id for later use with test.json matrix - compiler_id_map[lang] = comp.get_id() - except mesonlib.MesonException: - comp = None - - # note C compiler for later use by platform_fix_name() - if lang == 'c': - if comp: - host_c_compiler = comp.get_id() - else: - raise RuntimeError("Could not find C compiler.") + with tempfile.TemporaryDirectory(prefix='b_', dir=None if options.use_tmpdir else '.') as d: + env = environment.Environment('', d, fake_opts) + for machine in machines: + for lang in sorted(compilers.all_languages, key=compilers.sort_clink): + try: + comp = detect_compiler_for(env, lang, machine, False, '') + except mesonlib.MesonException: + comp = None + all_compilers[machine][lang] = comp + print_compilers(MachineChoice.HOST) + if all_compilers.build: + print_compilers(MachineChoice.BUILD) -def print_compilers(env: 'Environment', machine: MachineChoice) -> None: + +def print_compilers(machine: MachineChoice) -> None: print() print(f'{machine.get_lower_case_name()} machine compilers') print() - for lang in sorted(compilers.all_languages): - try: - comp = compiler_from_language(env, lang, machine) + for lang, comp in all_compilers[machine].items(): + if comp is not None: details = '{:<10} {} {}'.format('[' + comp.get_id() + ']', ' '.join(comp.get_exelist()), comp.get_version_string()) - except mesonlib.MesonException: + else: details = '[not found]' print(f'{lang:<7}: {details}') @@ -1657,7 +1577,7 @@ def setup_symlinks() -> None: help='Stop running if test case fails') parser.add_argument('--no-unittests', action='store_true', help='Not used, only here to simplify run_tests.py') - parser.add_argument('--only', default=[], + parser.add_argument('--only', default=[], choices=ALL_TESTS, help='name of test(s) to run, in format "category[/name]" where category is one of: ' + ', '.join(ALL_TESTS), nargs='+') parser.add_argument('-v', default=False, action='store_true', help='Verbose mode') diff --git a/unittests/allplatformstests.py b/unittests/allplatformstests.py index 1304658daeb9..1a078960a73d 100644 --- a/unittests/allplatformstests.py +++ b/unittests/allplatformstests.py @@ -2508,9 +2508,9 @@ def test_templates(self): if ninja is None: raise SkipTest('This test currently requires ninja. Fix this once "meson build" works.') - langs = ['c'] + langs = [] env = get_fake_env() - for l in ['cpp', 'cs', 'cuda', 'd', 'fortran', 'java', 'objc', 'objcpp', 'rust', 'vala']: + for l in ['c', 'cpp', 'cs', 'cuda', 'd', 'fortran', 'java', 'objc', 'objcpp', 'rust', 'vala']: try: comp = detect_compiler_for(env, l, MachineChoice.HOST, True, '') with tempfile.TemporaryDirectory() as d: