-
Notifications
You must be signed in to change notification settings - Fork 1.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Major rewrite of the rewriter and the static introspection tool #12277
base: master
Are you sure you want to change the base?
Major rewrite of the rewriter and the static introspection tool #12277
Conversation
ed089ca
to
25c3cf2
Compare
Thank you for working on this. I will try to have a deeper look and to test it soon. For the tests, I think the best thing to do is to ensure that each bug you found is covered by a test. I wonder if this could be splitted into multiple commits, to ease understanding what each part fixes, especially for trivial fixes that are not directly part of the refactor. |
I should have done that everytime it found a bug. I cannot do it now, since I forgot most of the bugs.
I will do that for "trivial fixes that are not directly part of the refactor" , but the rest will still be in one big commit since it is hard/impossible to split. |
7425a57
to
b594817
Compare
I splitted the trivial fixes intro seperate commits. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just did a quick pass on python syntax. I didn't tried the functionality yet.
return SymbolNode(Token('', '', 0, 0, 0, (0, 0), val)) | ||
|
||
class DataflowDAG: | ||
src_to_tgts: T.DefaultDict[T.Union[BaseNode, UnknownValue], T.Set[T.Union[BaseNode, UnknownValue]]] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is a lot of T.Union[BaseNode, UnknownValue]
. Maybe should you create a type for that? e.g. NodeOrUnknown = T.Union[BaseNode, UnknownValue]
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm against that. It is less readable and only marginally shorter.
mesonbuild/ast/interpreter.py
Outdated
active = set(srcs) | ||
while True: | ||
if reverse: | ||
new: T.Set[T.Union[BaseNode, UnknownValue]] = set() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The new
set is created on both branches of the if
. I think you could initialize it before the if
.
mesonbuild/mintro.py
Outdated
@@ -388,7 +370,7 @@ def list_deps_from_source(intr: IntrospectionInterpreter) -> T.List[T.Dict[str, | |||
'has_fallback', | |||
'conditional', | |||
] | |||
result += [{k: v for k, v in i.items() if k in keys}] | |||
result += [{k: v for k, v in i.__dict__.items() if k in keys}] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This __dict__
seems a code smell... Is it just because mypy do not detect the type of i
correctly?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you could rewrite this as:
result += [{k: getattr(i, k) for k in keys}]
|
||
if 'meson.build' in [os.path.basename(options.builddir), options.builddir]: | ||
# TODO: This if clause is undocumented. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will you do it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That is out of scope for this PR. Also, I don't like the command line api here:
I think that meson introspect meson.build --targets
should be changed to one of these:
meson static-introspect . --targets
meson static-introspect --targets
meson rewriter --targets
I don't like the fact that meson introspect meson.build --targets
and meson introspect builddir --targets
are radically different things, but the cli makes it look like they are two just two ways for meson to find the correct directory.
mesonbuild/mintro.py
Outdated
|
||
if 'meson.build' in [os.path.basename(options.builddir), options.builddir]: | ||
# TODO: This if clause is undocumented. | ||
if os.path.basename(options.builddir) == 'meson.build': |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is environment.build_filename
constant. Maybe should you use it?
mesonbuild/mintro.py
Outdated
if 'meson.build' in [os.path.basename(options.builddir), options.builddir]: | ||
# TODO: This if clause is undocumented. | ||
if os.path.basename(options.builddir) == 'meson.build': | ||
sourcedir = '.' if options.builddir == 'meson.build' else options.builddir[:-11] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
and use -len(environment.build_filename)
here...
mesonbuild/mintro.py
Outdated
res = [root_dir / i['subdir'] / x for x in res] | ||
res = [x.resolve() for x in res] | ||
return res | ||
def list_targets_from_source(intr: IntrospectionInterpreter) -> T.Any: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of making this Any
, it could at least be T.List[T.Dict[str, object]]
mesonbuild/mintro.py
Outdated
@@ -388,7 +370,7 @@ def list_deps_from_source(intr: IntrospectionInterpreter) -> T.List[T.Dict[str, | |||
'has_fallback', | |||
'conditional', | |||
] | |||
result += [{k: v for k, v in i.items() if k in keys}] | |||
result += [{k: v for k, v in i.__dict__.items() if k in keys}] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you could rewrite this as:
result += [{k: getattr(i, k) for k in keys}]
mesonbuild/ast/printer.py
Outdated
# ignores ParanthesizedNode, the binding power of the inner node is | ||
# relevant. | ||
return precedence_level(node.inner) | ||
raise TypeError |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Usually you would use RuntimeError
, but we have MesonBugError
(or maybe it's MesonBugException
?). I think we should use that here.
if 'native' in kwargs: | ||
native = kwargs.get('native', False) | ||
self._add_languages(args, required, MachineChoice.BUILD if native else MachineChoice.HOST) | ||
else: | ||
for for_machine in [MachineChoice.BUILD, MachineChoice.HOST]: | ||
self._add_languages(args, required, for_machine) | ||
return UnknownValue() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The explanation for UnknownValue
was for cases where we couldn't know what would be returned. I'm not sure why I follow that add_languages
returns UnknownValue
and not bool
. If it returns it will always return a bool
(or it will abort, but for the purpose of the rewriter that doesn't really matter, does it?)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
var = add_languages('rust', required: false)
message(var)
prints true
on some machines and false
on others.
So func_add_languages
has to return UnknownValue
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It returns a boolean? I'm just not understanding here, if add_languages()
, which returns a bool
cannot be statically determined, then what can? There are no functions I know of that always return the same value in every case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You seem confused, let me clear that up:
You know that if you run meson setup builddir
, the contents of the resulting builddir
-directory depend on
- The contents of meson.build
- The machine you are using.
If for example both you and I clone the same project that is using meson, and we both run meson setup builddir
, those two directories are not (necessarily) identical if we have different machines. This is not a bug, this is intentional. Therefore, if we both run meson introspect builddir
, we (might) get different results.
But if we both clone the same project and run meson introspect meson.build
we get the same result. If we don't, that is a bug. In other words, the job of ast/introspection.py
is to know what happens if we run meson setup
on a different, unknown machine. In other words, ast/introspection.py
has full knowledge about the contents of meson.build
, but no knowledge about the build/host/target machine. Let's say meson.build contains:
srcs = ['1.c']
srcs += files('2.c')
if 3+4 == 7
srcs += '3.c'
endif
if build_machine.system == 'linux'
srcs += 'linux-specific.c'
endif
if add_languages('rust', required: false)
srcs += 'rust.rs'
endif
executable('foo', srcs)
If I run meson introspect meson.build
on my machine, the job of meson is to figure out what sources belong to the foo-executable file if you run meson setup
on your machine.
It knows that the foo-executable contains the sources 1.c and 2.c. It does not know that it contains '3.c', since I was too lazy to implement that. There is no way for it to know whether the foo-executable contains the sources 'linux-specific.c' or 'rust.rs', since a program running on my machine cannot know whether your machine has a rust compiler installed.
@@ -505,7 +507,7 @@ def evaluate_indexing(self, node: mparser.IndexNode) -> InterpreterObject: | |||
|
|||
def function_call(self, node: mparser.FunctionNode) -> T.Optional[InterpreterObject]: | |||
func_name = node.func_name.value | |||
(h_posargs, h_kwargs) = self.reduce_arguments(node.args) | |||
(h_posargs, h_kwargs) = self.reduce_arguments(node.args, include_unknown_args = True) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No spaces around the =
operator
ee79d23
to
79661ac
Compare
Cloned this branch, ran
Python 3.11.5. |
Confirmed and on my way to fix it. |
Fixed in b98292e |
If a string contains '\n' we need to use triple quotes, since the parser will reject it if we use single quotes.
The rewriter and the static introspection tool used to be very broken, now it is *less* broken. The most important changes are: 1. We now have class UnknownValue for more explicit handling of situations that are too complex/impossible. 2. If you write ``` var = 'foo' name = var var = 'bar' executable(name, 'foo.c') ``` the tool now knows that the name of the executable is foo and not bar. See dataflow_dag and node_to_runtime_value for details on how we do this. Fixes mesonbuild#11763
b98292e
to
d5a95d6
Compare
Co-authored-by: Jouke Witteveen <j.witteveen@gmail.com>
Any update? |
The rewriter and the static introspection tool used to be very broken, now it is less broken.
The most important changes are:
class UnknownValue
for more explicit handling of situations that are too complex/impossible.the tool now knows that the name of the executable is
foo
and notbar
. Seedataflow_dag
andnode_to_runtime_value
for details on how we do this.To test my work I wrote a script that:
git clone
's a couple of big projects using meson (e.g. systemd).meson introspect meson.build --targets
andmeson introspect build_folder --targets
are not contradicting each other.meson introspect meson.build --targets
is the same for two different versions of meson (the one we want to test and a known good version).meson introspect meson.build --targets
and checks ifdoes not crash and produces the expected output.
I think this script is very useful (it found a ton of bugs), but I do not know where to put it, so it currently only exists on my machine. It is too slow (1 hour iirc, haven't measured) to run it in the CI pipeline. In the docs you write
Is this testing (partially) automated? If so, could we merge my script with it?
@bruchar1 @kcgen Afaik you are one of the few people using the rewriter/static introspection tool in production. Could you test your usecases and voice your opinion?
@eli-schwartz You promised me this in a mail: