Skip to content

Commit

Permalink
compilers: detect: fix pre-processor scraping by defining language
Browse files Browse the repository at this point in the history
_get_gnu_compiler_defines and _get_clang_compiler_defines were broken
by not defining the language they used.

Neither GCC nor Clang infer the language based on the driver name which means
`self.defines` isn't populated correctly in compilers/cpp.py.

e.g.
```
 $ echo "" | g++ -E -dM - | grep -i cplus

 $ echo "" | g++ -x c++ -E -dM - | grep -i cplus
 #define __cplusplus 201703L
```

Fix that by passing '-cpp -x LANGUAGE' as a first pass. If it fails, try
again without '-cpp -x LANGUAGE' as before, as its portability isn't
certain. We do '-cpp' because during testing, I found Fortran needs this,
although per below, I had to drop Fortran in the end and leave it to the
fallback (existing) path.

Without this change, a63739d is only
partially effective. It works if the system has injected Clang options
via /etc/clang configuration files, but not by e.g. patching the driver
(or for GCC there too).

Unfortunately, we have to wimp out for Fortran and fallback to the
old method because you need the language standard (e.g. -x f95).
  • Loading branch information
thesamesam committed Jun 23, 2024
1 parent b56a319 commit 4ad792e
Show file tree
Hide file tree
Showing 2 changed files with 77 additions and 21 deletions.
91 changes: 70 additions & 21 deletions mesonbuild/compilers/detect.py
Original file line number Diff line number Diff line change
Expand Up @@ -340,7 +340,7 @@ def sanitize(p: T.Optional[str]) -> T.Optional[str]:
guess_gcc_or_lcc = None

if guess_gcc_or_lcc:
defines = _get_gnu_compiler_defines(compiler)
defines = _get_gnu_compiler_defines(compiler, lang)
if not defines:
popen_exceptions[join_args(compiler)] = 'no pre-processor defines'
continue
Expand Down Expand Up @@ -449,7 +449,7 @@ def sanitize(p: T.Optional[str]) -> T.Optional[str]:
if 'clang' in out or 'Clang' in out:
linker = None

defines = _get_clang_compiler_defines(compiler)
defines = _get_clang_compiler_defines(compiler, lang)

# Even if the for_machine is darwin, we could be using vanilla
# clang.
Expand Down Expand Up @@ -676,7 +676,7 @@ def detect_fortran_compiler(env: 'Environment', for_machine: MachineChoice) -> C
guess_gcc_or_lcc = 'lcc'

if guess_gcc_or_lcc:
defines = _get_gnu_compiler_defines(compiler)
defines = _get_gnu_compiler_defines(compiler, 'fortran')
if not defines:
popen_exceptions[join_args(compiler)] = 'no pre-processor defines'
continue
Expand Down Expand Up @@ -843,7 +843,7 @@ def _detect_objc_or_objcpp_compiler(env: 'Environment', lang: str, for_machine:
continue
version = search_version(out)
if 'Free Software Foundation' in out:
defines = _get_gnu_compiler_defines(compiler)
defines = _get_gnu_compiler_defines(compiler, lang)
if not defines:
popen_exceptions[join_args(compiler)] = 'no pre-processor defines'
continue
Expand All @@ -855,7 +855,7 @@ def _detect_objc_or_objcpp_compiler(env: 'Environment', lang: str, for_machine:
defines, linker=linker)
if 'clang' in out:
linker = None
defines = _get_clang_compiler_defines(compiler)
defines = _get_clang_compiler_defines(compiler, lang)
if not defines:
popen_exceptions[join_args(compiler)] = 'no pre-processor defines'
continue
Expand Down Expand Up @@ -1329,19 +1329,43 @@ def detect_masm_compiler(env: 'Environment', for_machine: MachineChoice) -> Comp
# GNU/Clang defines and version
# =============================

def _get_gnu_compiler_defines(compiler: T.List[str]) -> T.Dict[str, str]:
def _get_gnu_compiler_defines(compiler: T.List[str], lang: str) -> T.Dict[str, str]:
"""
Get the list of GCC pre-processor defines
"""
from .mixins.gnu import _LANG_MAP as gnu_LANG_MAP

def _try_obtain_compiler_defines(args: T.List[str]) -> str:
mlog.debug(f'Running command: {join_args(args)}')
p, output, error = Popen_safe(compiler + args, write='', stdin=subprocess.PIPE)
if p.returncode != 0:
raise EnvironmentException('Unable to get gcc pre-processor defines:\n'
f'Compiler stdout:\n{output}\n-----\n'
f'Compiler stderr:\n{error}\n-----\n')
return output

# Arguments to output compiler pre-processor defines to stdout
# gcc, g++, and gfortran all support these arguments
args = compiler + ['-E', '-dM', '-']
mlog.debug(f'Running command: {join_args(args)}')
p, output, error = Popen_safe(args, write='', stdin=subprocess.PIPE)
if p.returncode != 0:
raise EnvironmentException('Unable to detect gcc pre-processor defines:\n'
f'Compiler stdout:\n{output}\n-----\n'
f'Compiler stderr:\n{error}\n-----\n')
baseline_test_args = ['-E', '-dM', '-']
try:
# We assume that when _get_gnu_compiler_defines is called, it's
# close enough to a GCCish compiler so we reuse the _LANG_MAP
# from the GCC mixin. This isn't a dangerous assumption because
# we fallback if the detection fails anyway.

# We might not have a match for Fortran, so fallback to detection
# based on the driver.
lang = gnu_LANG_MAP[lang]

# The compiler may not infer the target language based on the driver name
# so first, try with '-cpp -x lang', then fallback without given it's less
# portable. We try with '-cpp' as GCC needs it for Fortran at least, and
# it seems to do no harm.
output = _try_obtain_compiler_defines(['-cpp', '-x', lang] + baseline_test_args)
except (EnvironmentException, KeyError):
mlog.debug(f'pre-processor extraction using -cpp -x {lang} failed, falling back w/o lang')
output = _try_obtain_compiler_defines(baseline_test_args)

# Parse several lines of the type:
# `#define ___SOME_DEF some_value`
# and extract `___SOME_DEF`
Expand All @@ -1358,17 +1382,42 @@ def _get_gnu_compiler_defines(compiler: T.List[str]) -> T.Dict[str, str]:
defines[rest[0]] = rest[1]
return defines

def _get_clang_compiler_defines(compiler: T.List[str]) -> T.Dict[str, str]:
def _get_clang_compiler_defines(compiler: T.List[str], lang: str) -> T.Dict[str, str]:
"""
Get the list of Clang pre-processor defines
"""
args = compiler + ['-E', '-dM', '-']
mlog.debug(f'Running command: {join_args(args)}')
p, output, error = Popen_safe(args, write='', stdin=subprocess.PIPE)
if p.returncode != 0:
raise EnvironmentException('Unable to get clang pre-processor defines:\n'
f'Compiler stdout:\n{output}\n-----\n'
f'Compiler stderr:\n{error}\n-----\n')
from .mixins.clang import _LANG_MAP as clang_LANG_MAP

def _try_obtain_compiler_defines(args: T.List[str]) -> str:
mlog.debug(f'Running command: {join_args(args)}')
p, output, error = Popen_safe(compiler + args, write='', stdin=subprocess.PIPE)
if p.returncode != 0:
raise EnvironmentException('Unable to get clang pre-processor defines:\n'
f'Compiler stdout:\n{output}\n-----\n'
f'Compiler stderr:\n{error}\n-----\n')
return output

# Arguments to output compiler pre-processor defines to stdout
baseline_test_args = ['-E', '-dM', '-']
try:
# We assume that when _get_clang_compiler_defines is called, it's
# close enough to a Clangish compiler so we reuse the _LANG_MAP
# from the Clang mixin. This isn't a dangerous assumption because
# we fallback if the detection fails anyway.

# We might not have a match for Fortran, so fallback to detection
# based on the driver.
lang = clang_LANG_MAP[lang]

# The compiler may not infer the target language based on the driver name
# so first, try with '-cpp -x lang', then fallback without given it's less
# portable. We try with '-cpp' as GCC needs it for Fortran at least, and
# it seems to do no harm.
output = _try_obtain_compiler_defines(['-cpp', '-x', lang] + baseline_test_args)
except (EnvironmentException, KeyError):
mlog.debug(f'pre-processor extraction using -cpp -x {lang} failed, falling back w/o lang')
output = _try_obtain_compiler_defines(baseline_test_args)

defines: T.Dict[str, str] = {}
for line in output.split('\n'):
if not line:
Expand Down
7 changes: 7 additions & 0 deletions mesonbuild/compilers/mixins/clang.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,13 @@
's': ['-Oz'],
}

_LANG_MAP = {
'c': 'c',
'cpp': 'c++',
'objc': 'objective-c',
'objcpp': 'objective-c++',
}

class ClangCompiler(GnuLikeCompiler):

id = 'clang'
Expand Down

0 comments on commit 4ad792e

Please sign in to comment.