Skip to content
Draft
Show file tree
Hide file tree
Changes from 4 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
126 changes: 126 additions & 0 deletions mesonbuild/arguments.py
Original file line number Diff line number Diff line change
@@ -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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think an enable member went missing here.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops, I the enable docstring is a copy-paste error. There is not -Wno-error=... in any compiler AFAIK, so no need to represent it.

Copy link
Member

@eli-schwartz eli-schwartz Jul 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/any/every/ ?

So I guess yeah, if it can't be universally represented it might as well be opaque whenever it appears.

Copy link
Member Author

@dcbaker dcbaker Jul 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Originally I had Warning() with both enable and error fields, but realized that you could end up with the invalid state of error disabled. I guess we could leave it and have some kind of validation pass later, but I prefer to not be able to model invalid state if possible.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

-Wno-error is valid at least on gcc and clang. Given f.c:

int main()
{
	puts("aa");
}

you have:

$ gcc f.c -Wno-error=implicit-function-declaration -std=c99
f.c: In function ‘main’:
f.c:3:9: warning: implicit declaration of function ‘puts’ [-Wimplicit-function-declaration]


$ clang f.c -Wno-error=implicit-function-declaration -std=c99
f.c:3:2: warning: call to undeclared function 'puts'; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration]

Without the flag you get an error with both compilers.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting, GCC does not document that on their warnings page, just -W, -Wnoand-Werror=`

Since it does exist I'll fold it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

?

-Werror=
    Make the specified  warning into an error. The specifier for a warning is appended; for
    example -Werror=switch turns the warnings controlled by -Wswitch into errors. This switch
    takes a negative form, to be used to negate -Werror for specific warnings; for example
   -Wno-error=switch makes -Wswitch warnings not be errors, even when -Werror is in effect.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe you could have an enum DISABLED, DEFAULT_LEVEL, WARNING, ERROR corresponding to -Wno-, -W, -Wno-error=, -Werror=.

Copy link
Member Author

@dcbaker dcbaker Jul 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I was looking at the htmlized version of the info page, It's probably there but not readily obvious



@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:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For warnings we have enable=True, why not the same for Define?

Copy link
Contributor

@bonzini bonzini Jul 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In fact since (EDIT: fixed) -Dfoo and -Dfoo=1 are the same, could Undefine('foo') be replaced by Define('foo', value=None)?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They are NOT the same, one sets it to implicitly 1 and the other, explicitly the null string.

For both, ifdef says "yes it is defined" and redefining may result in compiler warnings.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I really want to make it impossible to represent invalid state. So we basically have three distinct cases:
set define to value
set define to nothing
remove define

So... would representing it as:

class Define:
    name: str
    value: str | bool

work correctly?

You'd have value = False mean -Ufoo value = True mean -Dfoo and value = mean -Dfoo=<str>?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right, sorry. I meant that -Dfoo and -Dfoo=1 could be canonicalized to a single form (Define('foo', '1') in the abstract form, whereas the concrete form could be either) and value=None could be used for undefining.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe, but would that mean the user sets -Dfoo=1 and we translate to -Dfoo (or vice versa)?

Copy link
Contributor

@bonzini bonzini Jul 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes... OTOH you do want to collapse -Dfoo -Dfoo=1 to just one of them. Setting value='1' would also make __eq__ easier to implement.

Maybe some kind of have_equals: bool could be added to make round-trip conversion possible, and it would be ignored by __eq__.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know that we have to guarantee we pass exactly the same arguments as long as we pass equivalent arguments. Especially since we already attempt to do some level of de-duplication. I'm perfectly fine with -Dfoo= becoming -Dfoo=1.


"""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
1 change: 1 addition & 0 deletions mesonbuild/build.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down
17 changes: 17 additions & 0 deletions mesonbuild/compilers/compilers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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],
Expand Down
54 changes: 54 additions & 0 deletions mesonbuild/compilers/mixins/gnu.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import subprocess
import typing as T

from ... import arguments
from ... import mesonlib
from ... import mlog
from ...options import OptionKey, UserStdOption
Expand Down Expand Up @@ -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))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Defines can be spelled on the command line as -D KEY=value (with the space after the -D switch) meaning that they are seen as two arguments, not as one. Thus, I think that arguments parsing needs to be more sophisticated. Maybe using something like argparse may make the code simpler.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's possible we will have to do something more sophisticated before this can land, right now my biggest concern is getting to the point that i can prove that the IR can do everything we currently do with the POSIX style string parsing, and solve some of the difficult problems we can't solve easily. So, for the moment I'd rather not get hung up here, but I will leave these opened to keep them in mind.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is also an issue we'll need to resolve for Rust because --cfgs must be passed with a space in them.

elif arg.startswith('-U'):
ret.append(arguments.Undefine(arg.removeprefix('-U')))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The same is true for -U.

elif arg.startswith('-l'):
ret.append(arguments.LinkerSearch(arg.removeprefix('-l')))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

search?

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):
"""
Expand Down
7 changes: 7 additions & 0 deletions mesonbuild/dependencies/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down