mesonbuild / meson

The Meson Build System
http://mesonbuild.com
Apache License 2.0
5.41k stars 1.55k forks source link

Improving Qt moc cross platform support #10665

Open AHSauge opened 1 year ago

AHSauge commented 1 year ago

As identified in #5730 and #7959, meson doesn't currently support cmake's automoc, and as a result Qt-Advanced-Docking-System can't be built as-is with meson. I've been making some attempts porting the build over to using meson directly. One aspect of automoc that seriously bit me badly is the automatic generation of moc_predefs.h and subsequent feed of that into moc via --include. This lead to moc-ing occurring with meson that didn't have the correct platform definitions. So while building on Linux, Q_OS_LINUX wouldn't be defined for instance. This is a problem since this project is relying on such defines to include and inherit certain objects from the right class. Without this in order, the application would crash. One could argue there are some unfortunate design choices along the way here, but this is the reality of the project.

While I can certainly understand the issue supporting cmake's automoc or replicating the full behaviour in meson, I do feel this very specific aspect of automoc might be worth considering. It would essentially be a dump of compiler pre-defines to moc_predefs.h (or some other fixed file name) and feeding it to moc via --include. From my perspective, this would be a one-time thing when one or more moc's are defined, and then subsequent builds can rely on this file already being generated.

The way I see it, the benefit of adding this would be that there's no need to fiddle with the library specific defines per platform (Q_OS_LINUX, Q_OS_MACOS etc.), nor know what the library requires to define them itself (__linux, __APPLE__ etc.). Not having to know or deal with this, would make it easier to write and support cross platform use cases.

Alternatively, if it's already possible to get meson to dump all the compiler predefines to a file, then documenting how to do that for Qt moc would of course also be a good alternative. While I'm more than happy to assist on this alternative, I'm not knowledgeable enough to know whether it's possible and if possible; how. If someone gives me some pointers, I'm happy to give it a shot.

eli-schwartz commented 1 year ago

To clarify, are we talking about the "auto" part of cmake automoc, or the "moc" part? :)

As per the previous tickets on the topic, Meson is not interested in automatically scanning sources to figure out which files need to be moc'ed, which is a configure-time concept that influences what build edges get created, must run on every single build (even no-op builds), unpredictably kicks into effect even when you create a new file but don't wire it up to the build yet, and means re-running configure if anything might have changed.

The golden rule is that Meson wants your meson.build files to fully describe which build steps are performed. It's really just another example of https://mesonbuild.com/FAQ.html#why-cant-i-specify-target-files-with-a-wildcard

...

Adding more functionality to your explicitly specified sources should not be a problem. There is no principled objection to adding an --include moc_predefs.h to each file that gets moc'ed.

If you're interested in working on this, the relevant logic is here. https://github.com/mesonbuild/meson/blob/0ec039d2597526c670f7e0478a7b5008e14ff474/mesonbuild/modules/qt.py#L441-L476

I assume that the list of pre-defines is statically known based on the host machine (the one the binary is being compiled to run on)? Are there any potential downsides to unconditionally passing it? Perhaps we can always create the predefs file once per build (as long as at least one moc file is used), and pass that one file to each moc whether it needs it or not?

AHSauge commented 1 year ago

We're talking about the "moc" part of automoc, so no scanning files (I'll leave that up to #5730 to sort out). Maybe I should have made this explicit, but I made this a new issue precisely because #5730 only talks about the "auto" part. What I'm describing here is or can be used per manually instantiated moc. From what I can tell from the documentation, CMake is exclusively doing this in context of automoc, but then again, I'm not entirely sure how you'd go about doing moc manually in CMake. Regardless of that, this would bridge some of the gap between CMake and meson on this area, and make it more straightforward to transition over with lower likelihood of hitting trouble.

Having looked a bit into the codebase, I do see fairly immediately an issue here; not all compilers have this available, at least not currently implemented in meson. For GCC and Clang, this seems to be all good. There are functions like https://github.com/mesonbuild/meson/blob/0ec039d2597526c670f7e0478a7b5008e14ff474/mesonbuild/compilers/detect.py#L1227 that detects it and feeds the result into the compiler object. If get_builtin_defines were to be added (similar to https://github.com/mesonbuild/meson/blob/0703ee0aef68e235c1e22d6448b79dfbbb5c8039/mesonbuild/compilers/compilers.py#L595), then it's easy enough to re-assemble the header (hypothetically one could of course just dump the non-processed output into a file, but to me that seems doomed to fall apart at some point). Once the file exist I can only assume it's quite trivial to feed that into moc; you've pretty much pointed me to the exact location for that. So for supported compilers, this shouldn't be too bad I think (queue future regret on that statement...).

So the questions I have is

  1. Would there be any objections to adding get_builtin_defines? Essentially, is the lack of this function only down to currently no need for it?
  2. Is there any particular reason why Intel/ICC doesn't have this detection? While I've never worked with it, the CMake code base indicates it's possible to extract the same information there too
  3. How do we neatly handle non-supported use cases?

To answer my last question: Personally I think it makes sense to make it possible to toggle this on/off, and although I would prefer it default on, I can understand that for backwards compatibility reasons it's appreciated to default this to off. Default to off would inherently make non-supported use cases easier to deal with as someone explicitly has to enable this feature and thus you can also explicitly say the compiler used isn't supported. I think that's probably the best approach?

The best alternative I see to this, would be to default on and automatically either turn it off or generate an empty file. Either way, it would be more silent and potentially hiding trouble.

Lastly to answer your questions:

I assume that the list of pre-defines is statically known based on the host machine (the one the binary is being compiled to run on)?

Yes, this would be my assumption as well.

Are there any potential downsides to unconditionally passing it?

Not that I'm aware of, but I would assume we should keep the use of the --include parameter down to when we know we can provide a useful file. That said, in theory providing an empty file is also acceptable. Based on the Qt documentation, all moc is doing is including that one particular file at the very beginning of the header you're trying to moc. I don't foresee that an empty file should cause problems, but famous last words I suppose.

Perhaps we can always create the predefs file once per build (as long as at least one moc file is used), and pass that one file to each moc whether it needs it or not?

Yes, this would be my preference as well. Make the file ones, feed it into one or more moc files. As far as I can tell, it doesn't matter where the file is either, so putting it in a fixed location is an option. This would be similar to what CMake is doing.