diff --git a/mesonbuild/arguments.py b/mesonbuild/arguments.py new file mode 100644 index 000000000000..f4be1c1341f2 --- /dev/null +++ b/mesonbuild/arguments.py @@ -0,0 +1,126 @@ +# SPDX-License-Identifier: Apache-2.0 +# Copyright © 2025 Intel Corporation + +"""An abstract IR for compile arguments. + +This provides us with a way to pass arguments around in a non-compiler specific +way internally, and lower them into a non-abstract format when writing them in +the backend. +""" + +from __future__ import annotations +import dataclasses +import typing as T + +if T.TYPE_CHECKING: + from typing_extensions import TypeAlias + + # A Union of all Argument types + Argument: TypeAlias = T.Union[ + 'Opaque', 'Warning', 'Error', 'Define', 'Undefine', 'LinkerSearch', + 'LinkLibrary', 'Rpath', + ] + + +@dataclasses.dataclass +class Opaque: + + """An opaque argument. + + This is an argument of unknown type, and Meson will do nothing but proxy it + through, except to apply a prefix to the value if it thinks it's necessary. + + :param value: + """ + + value: str + + +@dataclasses.dataclass +class Warning: + + """A compiler warning. + + :param target: The warning to enable or disable. This will be stored as + given (ie, we don't try to convert between compilers). + :param enable: If true then enable the warning, otherwise suppress it. + """ + + target: str + enable: bool + + +@dataclasses.dataclass +class Error: + + """A compiler error. + + :param target: The warning to enable or disable. This will be stored as + given (ie, we don't try to convert between compilers). + """ + + target: str + + +@dataclasses.dataclass +class Define: + + """A pre-processor define. + + :param target: The value to define. + :param value: An optional value to set the define to. If undefined them the + value will be defined with no value. + """ + + target: str + value: T.Optional[str] + + +@dataclasses.dataclass +class Undefine: + + """A pre-processor undefine. + + :param target: The value to define. + """ + + target: str + + +@dataclasses.dataclass +class LinkerSearch: + + """A location for the linker to search for libraries. + + :param path: The path to search. + """ + + path: str + + +@dataclasses.dataclass +class LinkLibrary: + + """A library to link with. + + :param name: The name of the library to link. + :param absolute: If the path is an absolute path + """ + + name: str + absolute: bool = False + + +class Rpath: + + """A runtime-path to add to a linked library. + + :param path: the path to add + """ + + path: str + + +# TODO: rpath +# TODO: rust cfgs +# TODO: other flags we might handle differently and want to convert like lto, sanitizers, etc diff --git a/mesonbuild/backend/backends.py b/mesonbuild/backend/backends.py index ed57a4c1bf66..085050ffe05d 100644 --- a/mesonbuild/backend/backends.py +++ b/mesonbuild/backend/backends.py @@ -2108,7 +2108,7 @@ def compiler_to_generator_args(self, target: build.BuildTarget, commands += compiler.get_compile_only_args() # Add per-target compile args, f.ex, `c_args : ['-DFOO']`. We set these # near the end since these are supposed to override everything else. - commands += self.escape_extra_args(target.get_extra_args(compiler.get_language())) + commands += self.escape_extra_args(compiler.make_arguments_concrete(target.get_extra_args(compiler.get_language()))) # Do not escape this one, it is interpreted by the build system # (Xcode considers these as variables to expand at build time) if extras is not None: diff --git a/mesonbuild/build.py b/mesonbuild/build.py index 72d376d172dd..69f803719c50 100644 --- a/mesonbuild/build.py +++ b/mesonbuild/build.py @@ -1,5 +1,6 @@ # SPDX-License-Identifier: Apache-2.0 # Copyright 2012-2017 The Meson development team +# Copyright © 2017-2025 Intel Corporation from __future__ import annotations from collections import defaultdict, deque, OrderedDict @@ -15,6 +16,7 @@ import textwrap import typing as T +from . import arguments from . import coredata from . import dependencies from . import mlog @@ -40,6 +42,7 @@ from . import environment from ._typing import ImmutableListProtocol + from .arguments import Argument from .backend.backends import Backend from .compilers import Compiler from .interpreter.interpreter import SourceOutputs, Interpreter @@ -719,7 +722,11 @@ def __init__( # as Vala which generates .vapi and .h besides the compiled output. self.outputs = [self.filename] self.pch: T.Dict[str, T.List[str]] = {} - self.extra_args: T.DefaultDict[str, T.List[str]] = kwargs.get('language_args', defaultdict(list)) + self.extra_args: T.DefaultDict[str, T.List[Argument]] = defaultdict(list) + if (lang_args := kwargs.get('language_args')) is not None: + for lang, args in lang_args.items(): + if args: + self.extra_args[lang].extend(self.compilers[lang].make_arguments_abstract(args)) self.sources: T.List[File] = [] # If the same source is defined multiple times, use it only once. self.seen_sources: T.Set[File] = set() @@ -741,6 +748,7 @@ def __init__( # 1. Preexisting objects provided by the user with the `objects:` kwarg # 2. Compiled objects created by and extracted from another target self.process_objectlist(objects) + self.link_args: T.List[Argument] = [] self.process_kwargs(kwargs) self.missing_languages = self.process_compilers() @@ -1149,17 +1157,19 @@ def process_kwargs(self, kwargs): self.vala_vapi = kwargs.get('vala_vapi', self.name + '.vapi') self.vala_gir = kwargs.get('vala_gir', None) - self.link_args = extract_as_list(kwargs, 'link_args') - for i in self.link_args: - if not isinstance(i, str): - raise InvalidArguments('Link_args arguments must be strings.') - for l in self.link_args: - if '-Wl,-rpath' in l or l.startswith('-rpath'): - mlog.warning(textwrap.dedent('''\ - Please do not define rpath with a linker argument, use install_rpath - or build_rpath properties instead. - This will become a hard error in a future Meson release. - ''')) + if (largs := kwargs.get('link_args')) is not None: + for larg in largs: + if not isinstance(larg, str): + raise InvalidArguments('link_args arguments must be strings.') + linker = self.get_clink_dynamic_linker_and_stdlibs()[0] + # XXX: need a method because of compiler-is-linker + self.link_args.extend(linker.linker.make_arguments_abstract(largs)) + if any(isinstance(arg, arguments.Rpath) for arg in self.link_args): + mlog.warning(textwrap.dedent('''\ + Please do not define rpath with a linker argument, use install_rpath + or build_rpath properties instead. + This will become a hard error in a future Meson release. + ''')) self.process_link_depends(kwargs.get('link_depends', [])) # Target-specific include dirs must be added BEFORE include dirs from # internal deps (added inside self.add_deps()) to override them. @@ -1293,7 +1303,7 @@ def get_debug_filename(self) -> T.Optional[str]: def get_outputs(self) -> T.List[str]: return self.outputs - def get_extra_args(self, language: str) -> T.List[str]: + def get_extra_args(self, language: str) -> T.List[Argument]: return self.extra_args[language] @lru_cache(maxsize=None) diff --git a/mesonbuild/compilers/compilers.py b/mesonbuild/compilers/compilers.py index 037692255fcc..813c4915ac3a 100644 --- a/mesonbuild/compilers/compilers.py +++ b/mesonbuild/compilers/compilers.py @@ -25,6 +25,7 @@ if T.TYPE_CHECKING: from .. import coredata + from ..arguments import Argument from ..build import BuildTarget, DFeatures from ..options import MutableKeyedOptionDictType from ..envconfig import MachineInfo @@ -1418,6 +1419,22 @@ def _update_language_stds(self, opts: MutableKeyedOptionDictType, value: T.List[ value = ['none'] + value std.choices = value + @abc.abstractmethod + def make_arguments_abstract(self, args: T.List[str]) -> T.List[Argument]: + """Convert concrete (string) arguments into abstract ones. + + :param args: The list of string arguments to convert + :return: A list of Arguments. + """ + + @abc.abstractmethod + def make_arguments_concrete(self, args: T.List[Argument]) -> T.List[str]: + """Convert abstract Arguments into a list of strings for this compiler. + + :param args: The Arguments to convert. + :return: A string list of arguments. + """ + def get_global_options(lang: str, comp: T.Type[Compiler], diff --git a/mesonbuild/compilers/mixins/gnu.py b/mesonbuild/compilers/mixins/gnu.py index ddcd14a15b0b..fc7a8284602f 100644 --- a/mesonbuild/compilers/mixins/gnu.py +++ b/mesonbuild/compilers/mixins/gnu.py @@ -14,6 +14,7 @@ import subprocess import typing as T +from ... import arguments from ... import mesonlib from ... import mlog from ...options import OptionKey, UserStdOption @@ -539,6 +540,59 @@ def get_preprocess_to_file_args(self) -> T.List[str]: lang = gnu_lang_map.get(self.language, 'assembler-with-cpp') return self.get_preprocess_only_args() + [f'-x{lang}'] + def make_arguments_abstract(self, args: T.List[str]) -> T.List[arguments.Argument]: + ret: T.List[arguments.Argument] = [] + + for arg in args: + if arg.startswith('-D'): + v: T.Optional[str] + arg = arg.removeprefix('-D') + if '=' in arg: + k, v = arg.split('=') + else: + k, v = arg, None + ret.append(arguments.Define(k, v)) + elif arg.startswith('-U'): + ret.append(arguments.Undefine(arg.removeprefix('-U'))) + elif arg.startswith('-l'): + ret.append(arguments.LinkerSearch(arg.removeprefix('-l'))) + elif arg.startswith('-Werror='): + ret.append(arguments.Error(arg.removeprefix('-Werror='))) + elif arg.startswith('-Wno-'): + ret.append(arguments.Warning(arg.removeprefix('-Wno-'), False)) + elif arg.startswith('-W'): + ret.append(arguments.Warning(arg.removeprefix('-W'), True)) + else: + ret.append(arguments.Opaque(arg)) + + return ret + + def make_arguments_concrete(self, args: T.List[arguments.Argument]) -> T.List[str]: + ret: T.List[str] = [] + + for arg in args: + match arg: + case arguments.Define(name, None): + ret.append(f'-D{name}') + case arguments.Define(name, value): + ret.append(f'-D{name}={value}') + case arguments.Undefine(name): + ret.append(f'-U{name}') + case arguments.Error(name): + ret.append(f'-Werror={name}') + case arguments.Warning(name, True): + ret.append(f'-W{name}') + case arguments.Warning(name, False): + ret.append(f'-Wno-{name}') + case arguments.LinkerSearch(name): + ret.append(f'-L{name}') + case arguments.LinkLibrary(name): + ret.append(f'-l{name}') + case arguments.Opaque(value): + ret.append(value) + + return ret + class GnuCompiler(GnuLikeCompiler): """ diff --git a/mesonbuild/dependencies/base.py b/mesonbuild/dependencies/base.py index 732bae544800..20113c0876d2 100644 --- a/mesonbuild/dependencies/base.py +++ b/mesonbuild/dependencies/base.py @@ -21,6 +21,7 @@ #from ..interpreterbase import FeatureDeprecated, FeatureNew if T.TYPE_CHECKING: + from ..arguments import Argument from ..compilers.compilers import Compiler from ..environment import Environment from ..interpreterbase import FeatureCheckBase @@ -66,6 +67,12 @@ def __getattr__(self, item: str) -> T.Any: def __bool__(self) -> bool: return False + def make_arguments_abstract(self, args: T.List[str]) -> T.List[Argument]: + return [] + + def make_arguments_concrete(self, args: T.List[Argument]) -> T.List[str]: + return [] + class DependencyMethods(Enum): # Auto means to use whatever dependency checking mechanisms in whatever order meson thinks is best. diff --git a/mesonbuild/linkers/linkers.py b/mesonbuild/linkers/linkers.py index d81892b7a668..230eb5e12617 100644 --- a/mesonbuild/linkers/linkers.py +++ b/mesonbuild/linkers/linkers.py @@ -1,6 +1,6 @@ # SPDX-License-Identifier: Apache-2.0 # Copyright 2012-2022 The Meson development team -# Copyright © 2023 Intel Corporation +# Copyright © 2023-2025 Intel Corporation from __future__ import annotations @@ -10,11 +10,13 @@ import re from .base import ArLikeLinker, RSPFileSyntax +from .. import arguments from .. import mesonlib from ..mesonlib import EnvironmentException, MesonException from ..arglist import CompilerArgs if T.TYPE_CHECKING: + from ..arguments import Argument from ..environment import Environment from ..mesonlib import MachineChoice from ..build import BuildTarget @@ -314,6 +316,22 @@ def get_command_to_archive_shlib(self) -> T.List[str]: #Only used by AIX. return [] + @abc.abstractmethod + def make_arguments_abstract(self, args: T.List[str]) -> T.List[Argument]: + """Convert concrete (string) arguments into abstract ones. + + :param args: The list of string arguments to convert + :return: A list of Arguments. + """ + + @abc.abstractmethod + def make_arguments_concrete(self, args: T.List[Argument]) -> T.List[str]: + """Convert abstract Arguments into a list of strings for this compiler. + + :param args: The Arguments to convert. + :return: A string list of arguments. + """ + if T.TYPE_CHECKING: StaticLinkerBase = StaticLinker @@ -611,6 +629,37 @@ def get_search_args(self, dirname: str) -> T.List[str]: def sanitizer_args(self, value: T.List[str]) -> T.List[str]: return [] + def make_arguments_abstract(self, args: T.List[str]) -> T.List[Argument]: + ret: T.List[Argument] = [] + + for arg in args: + if arg.startswith('-L'): + ret.append(arguments.LinkerSearch(arg.removeprefix('-L'))) + elif arg.startswith('-l'): + ret.append(arguments.LinkLibrary(arg.removeprefix('-l'))) + elif arg.startswith('-Wl,rpath'): + ret.append(arguments.Rpath(arg.removeprefix('-Wl,rpath='))) + elif os.path.exists(arg): + ret.append(arguments.LinkLibrary(arg, True)) + else: + ret.append(arguments.Opaque(arg)) + + return ret + + def make_arguments_concrete(self, args: T.List[Argument]) -> T.List[str]: + ret: T.List[str] = [] + + for arg in args: + match arg: + case arguments.LinkerSearch(path): + ret.append(f'-L{path}') + case arguments.LinkLibrary(path): + ret.append(f'-l{path}') + case arguments.Opaque(value): + ret.append(value) + + return ret + class GnuLikeDynamicLinkerMixin(DynamicLinkerBase):