Skip to content
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

PoC: qt using auto-generated bindings #17270

Draft
wants to merge 6 commits into
base: master
Choose a base branch
from
Draft

PoC: qt using auto-generated bindings #17270

wants to merge 6 commits into from

Conversation

arnetheduck
Copy link
Member

@arnetheduck arnetheduck commented Feb 11, 2025

This PR uses status-desktop as a "big enough" playground to experiment with a new set of bindings for Qt that are automatically generated from the Qt source code (similar to how PySide and other "modern" Qt bindings are done) - these bindings would serve as a more solid starting point for future qt/qml-based applications in Nim (and potentially other languages near and dear to us).

The bindings themselves first expose Qt to C and then C to Nim - although Nim is used here, the C bindings can also be construed as a stand-alone exposition of Qt that can be imported to other languages than Nim, using C FFI as the lingua franca (which is a lot easier to work with than C++, in binding code).

This PoC is here mainly to show the feasibility of the approach and as such does not make any actual changes to the desktop code - instead, compatibilty is kept via a "DOtherSide" emulation layer implemented entirely in Nim using the automatically generated bindings - the PoC also doesn't touch the various status-specific extensions and hacks to DOtherSide which probably deserve another home (StatusQ?) - that said, most of these status-fork-specifc extensions could now be removed because they are available natively from the automatic bindings (QSettings, signal connections etc).

Obviously, to benefit fully from the bindings, one would have to do work on the DSL itself as well which would drastically reduce the amount of boilerplate needed to interact with qt (in declaring properties, signals etc) - a trivial example is a property definition with slots and change signals that could be reduced to:

QtObject:
  type MyObject = ...
    name {.property.}: string

QObject instances would of course no longer have to be wrapped in QVariant etc.

The compatibiltiy layer would allow any nimqml-based application to transition gradually / doesn't require any up-front porting effort.

As-is, the PoC already demonstrates a few things:

  • build system simplification - the new bindings don't require DOtherSide to be built and linked separately (the build system is kept for the status extensions, not the DOtherSide code itself) - instead, the bindings are built as part of the Nim application (sourcing Qt installation information from pkg-config) simplifying cross-compilation (ie building on android) and zero-to-hero story for using Qt in Nim. The same technique could trivially be applied to StatusQ and DOtherSide as well.
  • ABI-compatible generation of QMetaObject data at compile time - the current nimqml bindings use a hybrid of dynamic metadata and dotherside-based C++ code which has a number of downsides, the most obvious being the difficulty to expose QObject-based properties from Nim to QML - the DOtherSide bindings also cannot fully interact with threaded signals, Qt-registered instantiation/copying, Qml registration etc. With full access to the Qt API and full integration with QMetaObject, this becomes not only feasible but also future-proof (relying on the API/ABI stability guarantees of Qt itself)
  • support for all inheritance-based Qt types (todo: multiple inheritance) - in the automatically generated bindings, virtual functions are available for overriding, static dispatch to base class implementations are exposed etc
  • signals and slots can be connected using type-safe lambda expressions (instead of string-based connect methods) - this support can also be extended to QObject-derived types in Nim - across language borders (C++/QML/Js), string-based conncetions continue to be supported.
  • full access to most/all QVariant types
  • efficient / small binaries - only the parts of qt that are actually used in the application result in binding code being generated/compiled/linked (unlike dotherside etc which build/link an entire set of bindings) - for QMetaObject, this binding code is almost exactly the same as moc would generate (this is how the ABI compatibiltiy "happens"), without a separate moc program (nim macros / compile-time introspection ftw).

The binding approach serves as a complement to anything developed natively in C++ for Qt, ie this approach extends rather than replaces the ability to use native code - the generator that creates the bindings can similarly be used with hand-written C++ code that follows Qt conventions (not part of the poc though) - just like shiboken can be used to generate PySide extensions.

Tested this on linux mainly for now - likely some small adjustments are needed for other platforms.

What does the PR do

Affected areas

Architecture compliance

Screenshot of functionality (including design for comparison)

  • I've checked the design and this PR matches it

This PR uses status-desktop as a "big enough" playground to experiment
with a new set of bindings for Qt that are automatically generated from
the Qt source code (similar to how PySide and other "modern" Qt bindings
are done) - these bindings would serve as a more solid starting point
for future qt/qml-based applications in Nim (and potentially other
languages near and dear to us).

The bindings themselves first expose Qt to C and then C to Nim -
although Nim is used here, the C bindings can also be construed as a
stand-alone exposition of Qt that can be imported to other languages
than Nim, using `C` FFI as the lingua franca (which is a lot easier to
work with than C++).

This PoC is here mainly to show the feasibility of the approach and as
such does not make any actual changes to the desktop code - instead,
compatibilty is kept via a "DOtherSide" emulation layer implemented
entirely in Nim using the automatically generated bindings - the PoC
also doesn't touch the various status-specific extensions and hacks to
DOtherSide which probably deserve another home (StatusQ?) - that said,
most of these status-fork-specifc extensions could now be removed
because they are available natively from the automatic bindings
(QSettings, signal connections etc).

Obviously, to benefit fully from the bindings, one would have to do work
on the DSL itself as well which would drastically reduce the amount of
boilerplate needed to interact with qt (in declaring properties, signals
etc) - a trivial example is a property definition with slots and change
signals that could be reduced to:

```
QtObject:
  type MyObject = ...
    name {.property.}: string
```

QObject instances would of course no longer have to be wrapped in
QVariant etc.

The compatibiltiy layer would allow any nimqml-based application to
transition gradually / doesn't require any up-front porting effort.

As-is, the PoC already demonstrates a few things:

* build system simplification - the new bindings don't require
`DOtherSide` to be built and linked separately (the build system is kept
for the status extensions, not the DOtherSide code itself) - instead,
the bindings are built as part of the Nim application (sourcing Qt
installation information from `pkg-config`) simplifying
cross-compilation (ie building on android) and zero-to-hero story for
using Qt in Nim. The same technique could trivially be applied to
`StatusQ` and `DOtherSide` as well.
* ABI-compatible generation of QMetaObject data at compile time - the
current nimqml bindings use a hybrid of dynamic metadata and
dotherside-based C++ code which has a number of downsides, the most
obvious being the difficulty to expose QObject-based properties from Nim
to QML - the DOtherSide bindings also cannot fully interact with
threaded signals, Qt-registered instantiation/copying, Qml registration
etc. With full access to the Qt API and full integration with
QMetaObject, this becomes not only feasible but also future-proof
(relying on the API/ABI stability guarantees of Qt itself)
* support for all inheritance-based Qt types (todo: multiple
inheritance) - in the automatically generated bindings, virtual
functions are available for overriding, static dispatch to base class
implementations are exposed etc
* signals and slots can be connected using type-safe lambda expressions
(instead of string-based connect methods) - this support can also be
extended to QObject-derived types in Nim - across language borders
(C++/QML/Js), string-based conncetions continue to be supported.
* full access to most/all QVariant types
* efficient / small binaries - only the parts of qt that are actually
used in the application result in binding code being
generated/compiled/linked (unlike dotherside etc which build/link an
entire set of bindings) - for QMetaObject, this binding code is almost
exactly the same as `moc` would generate (this is how the ABI
compatibiltiy "happens"), without a separate `moc` program (nim macros /
compile-time introspection ftw).

The binding approach serves as a complement to anything developed
natively in C++ for Qt, ie this approach extends rather than replaces
the ability to use native code - the generator that creates the bindings
can similarly be used with hand-written C++ code that follows Qt
conventions (not part of the poc though) - just like `shiboken` can be
used to generate PySide extensions.

Tested this on linux mainly for now - likely some small adjustments are
needed for other platforms.
@status-im-auto
Copy link
Member

status-im-auto commented Feb 11, 2025

Jenkins Builds

Click to see older builds (28)
Commit #️⃣ Finished (UTC) Duration Platform Result
5697137 #1 2025-02-11 12:43:07 ~6 min macos/aarch64 📄log
✖️ 5697137 #1 2025-02-11 12:45:17 ~8 min tests/nim 📄log
5697137 #1 2025-02-11 12:48:51 ~12 min linux-nix/x86_64 📄log
5697137 #1 2025-02-11 12:49:46 ~13 min linux/x86_64 📄log
✔️ 5697137 #1 2025-02-11 12:51:02 ~14 min tests/ui 📄log
5697137 #1 2025-02-11 12:53:14 ~16 min macos/x86_64 📄log
5697137 #1 2025-02-11 12:55:04 ~18 min windows/x86_64 📄log
✔️ 8221972 #2 2025-02-11 13:13:40 ~5 min macos/aarch64 🍎dmg
✖️ 8221972 #2 2025-02-11 13:17:21 ~9 min tests/nim 📄log
8221972 #2 2025-02-11 13:20:05 ~11 min linux/x86_64 📄log
8221972 #2 2025-02-11 13:21:48 ~13 min tests/ui 📄log
✔️ 8221972 #2 2025-02-11 13:22:07 ~13 min macos/x86_64 🍎dmg
8221972 #2 2025-02-11 13:24:25 ~16 min windows/x86_64 📄log
✔️ 8221972 #2 2025-02-11 13:27:06 ~18 min linux-nix/x86_64 📦tgz
✔️ 4521092 #3 2025-02-11 13:51:41 ~5 min macos/aarch64 🍎dmg
✖️ 4521092 #3 2025-02-11 13:55:09 ~8 min tests/nim 📄log
4521092 #3 2025-02-11 13:58:19 ~12 min tests/ui 📄log
4521092 #3 2025-02-11 13:58:30 ~12 min linux/x86_64 📄log
✔️ 4521092 #3 2025-02-11 13:59:43 ~13 min macos/x86_64 🍎dmg
4521092 #3 2025-02-11 14:02:35 ~16 min windows/x86_64 📄log
✔️ 4521092 #3 2025-02-11 14:05:04 ~18 min linux-nix/x86_64 📦tgz
✔️ 94d7e0d #4 2025-02-11 14:15:22 ~5 min macos/aarch64 🍎dmg
✖️ 94d7e0d #4 2025-02-11 14:24:06 ~14 min tests/nim 📄log
✔️ 94d7e0d #4 2025-02-11 14:24:39 ~14 min macos/x86_64 🍎dmg
94d7e0d #4 2025-02-11 14:27:25 ~17 min linux/x86_64 📄log
94d7e0d #4 2025-02-11 14:28:20 ~18 min windows/x86_64 📄log
✔️ 94d7e0d #4 2025-02-11 14:31:44 ~21 min tests/ui 📄log
✔️ 94d7e0d #4 2025-02-11 14:35:51 ~25 min linux-nix/x86_64 📦tgz
Commit #️⃣ Finished (UTC) Duration Platform Result
✔️ 1267096 #5 2025-02-11 15:48:07 ~5 min macos/aarch64 🍎dmg
✖️ 1267096 #5 2025-02-11 15:51:43 ~9 min tests/nim 📄log
✔️ 1267096 #5 2025-02-11 15:54:45 ~12 min tests/ui 📄log
1267096 #5 2025-02-11 15:55:02 ~12 min linux/x86_64 📄log
✔️ 1267096 #5 2025-02-11 15:56:06 ~13 min macos/x86_64 🍎dmg
1267096 #5 2025-02-11 15:56:52 ~14 min windows/x86_64 📄log
✔️ 1267096 #5 2025-02-11 16:02:16 ~19 min linux-nix/x86_64 📦tgz
a0f1b4f #6 2025-02-11 19:35:55 ~6 min macos/aarch64 📄log
✖️ a0f1b4f #6 2025-02-11 19:38:11 ~9 min tests/nim 📄log
a0f1b4f #6 2025-02-11 19:42:00 ~12 min macos/x86_64 📄log
✔️ a0f1b4f #6 2025-02-11 19:43:00 ~13 min tests/ui 📄log
a0f1b4f #6 2025-02-11 19:43:53 ~14 min linux-nix/x86_64 📄log
a0f1b4f #6 2025-02-11 19:44:28 ~15 min linux/x86_64 📄log
a0f1b4f #6 2025-02-11 19:51:18 ~22 min windows/x86_64 📄log

@arnetheduck
Copy link
Member Author

On a side note, Qt does not allow using the same class name for different QObject-derived instances because property registrations are done using the class name as lookup key - the new integration with qmetaobject causes some problems in status desktop because there are a lot of qobjects named View etc .. the code works around this quirk by adding a number to any conflict which works but may be surprising (similar to what qml does with its views).

@arnetheduck
Copy link
Member Author

arnetheduck commented Feb 11, 2025

Note to self: linux fails to compile due to the usage of connect with explicit receiver, available from v5.15.3: qt/qtbase@32d4e43 while tests here run 5.15.2

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants