Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions mesonbuild/compilers/asm.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
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
Expand Down Expand Up @@ -39,6 +40,13 @@ def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str,
raise EnvironmentException(f'ASM Compiler {self.id} does not support building for {info.cpu_family} CPU family.')
super().__init__(ccache, exelist, version, for_machine, info, linker, full_version, is_cross)

def sanity_check(self, work_dir: str, env: Environment) -> None:
return

def _sanity_check_source_code(self) -> str:
# TODO: Stub implementation to be replaced in future patch
return ''


class NasmCompiler(ASMCompiler):
language = 'nasm'
Expand Down
5 changes: 2 additions & 3 deletions mesonbuild/compilers/c.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,9 +76,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, environment: 'Environment') -> None:
code = 'int main(void) { int class=0; return class; }\n'
return self._sanity_check_impl(work_dir, environment, 'sanitycheckc.c', code)
def _sanity_check_source_code(self) -> str:
return 'int main(void) { int class=0; return class; }\n'

def has_header_symbol(self, hname: str, symbol: str, prefix: str,
env: 'Environment', *,
Expand Down
145 changes: 134 additions & 11 deletions mesonbuild/compilers/compilers.py
Original file line number Diff line number Diff line change
Expand Up @@ -1196,33 +1196,156 @@ 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, environment: 'Environment') -> None:
def sanity_check(self, work_dir: str, env: Environment) -> None:
"""Check that this compiler actually works.

This should provide a simple compile/link test. Something as simple as:
```python
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, transpiled, binname = self._sanity_check_filenames(env)

with open(os.path.join(work_dir, sourcename), 'w', encoding='utf-8') as f:
f.write(self._sanity_check_source_code())

if transpiled:
cmdlist = self._sanity_check_compile_args(env, sourcename, transpiled)
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.')

sourcename = transpiled
comp_lang = SUFFIX_TO_LANG[transpiled.rsplit('.', maxsplit=1)[1]]
comp = env.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 = self._transpiled_sanity_check_compile_args(comp, env, transpiled, binname)

mlog.debug('checking compilation of transpiled source:')
else:
cmdlist = self._sanity_check_compile_args(env, sourcename, binname)

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, env: Environment) -> 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.

:param env: The :class:`environment.Environment` instance to use
: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, env: Environment,
sourcename: str, binname: 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.

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 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
"""
return compiler._sanity_check_compile_args(env, sourcename, binname)

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))
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
"""
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, env: Environment, command: T.List[str]) -> T.List[str]:
"""Wrap the binary to run in the test with the exe_wrapper if necessary

Languages that do no want to use an exe_wrapper (or always want to use
some kind of wrapper) should override this method

:param env: the :class:`environment.Environment` instance to use
:param command: The string list of commands to run
:return: The list of commands wrapped by the exe_wrapper if it is needed, otherwise the original commands
"""
if self.is_cross and env.has_exe_wrapper():
assert env.exe_wrapper is not None, 'for mypy'
return env.exe_wrapper.get_command() + command
return command

def _run_sanity_check(self, env: Environment, cmdlist: T.List[str], work_dir: str) -> None:
"""Run a sanity test binary

:param env: the :class:`environment.Environment` instance to use
:param cmdlist: A list of strings to pass to :func:`subprocess.run` or equivalent to run the test
:param work_dir: A directory to place temporary artifacts
:raises mesonlib.EnvironmentException: If the binary cannot be run or if it returns a non-zero exit code
"""
# Can't check binaries, so we have to assume they work
if self.is_cross and not env.has_exe_wrapper():
mlog.debug('Cannot run cross check')
return

cmdlist = self._sanity_check_run_with_exe_wrapper(env, cmdlist)
mlog.debug('Sanity check built target output for', self.for_machine, self.language, 'compiler')
mlog.debug(' -- Running test binary command: ', mesonlib.join_args(cmdlist))
try:
pe, stdo, stde = Popen_safe_logged(cmdlist, 'Sanity check', cwd=work_dir)
mlog.debug(' -- stdout:\n', stdo)
mlog.debug(' -- stderr:\n', stde)
mlog.debug(' -- returncode:', pe.returncode)
except Exception as e:
raise mesonlib.EnvironmentException(f'Could not invoke sanity check executable: {e!s}.')

if pe.returncode != 0:
raise mesonlib.EnvironmentException(f'Executables created by {self.language} compiler {self.name_string()} are not runnable.')
return stdo, stde

def split_shlib_to_parts(self, fname: str) -> T.Tuple[T.Optional[str], str]:
return None, fname
Expand Down
5 changes: 2 additions & 3 deletions mesonbuild/compilers/cpp.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,9 +86,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, environment: 'Environment') -> None:
code = 'class breakCCompiler;int main(void) { return 0; }\n'
return self._sanity_check_impl(work_dir, environment, 'sanitycheckcpp.cc', code)
def _sanity_check_source_code(self) -> str:
return 'class breakCCompiler;int main(void) { return 0; }\n'

def get_compiler_check_args(self, mode: CompileCheckMode) -> T.List[str]:
# -fpermissive allows non-conforming code to compile which is necessary
Expand Down
31 changes: 11 additions & 20 deletions mesonbuild/compilers/cs.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -83,26 +82,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, environment: 'Environment') -> None:
src = 'sanity.cs'
obj = 'sanity.exe'
source_name = os.path.join(work_dir, src)
with open(source_name, 'w', encoding='utf-8') as ofile:
ofile.write(textwrap.dedent('''
public class Sanity {
static public void Main () {
}
def _sanity_check_source_code(self) -> str:
return textwrap.dedent('''
public class Sanity {
static public void Main () {
}
'''))
pc = subprocess.Popen(self.exelist + self.get_always_args() + [src], cwd=work_dir)
pc.wait()
if pc.returncode != 0:
raise EnvironmentException('C# compiler %s cannot compile programs.' % self.name_string())
}
''')

def _sanity_check_run_with_exe_wrapper(self, env: Environment, command: T.List[str]) -> T.List[str]:
if self.runner:
cmdlist = [self.runner, obj]
else:
cmdlist = [os.path.join(work_dir, obj)]
self.run_sanity_check(environment, cmdlist, work_dir, use_exe_wrapper_for_cross=False)
return [self.runner] + command
return command

def needs_static_linker(self) -> bool:
return False
Expand Down
Loading
Loading