mirror of
https://github.com/mappu/miqt.git
synced 2024-12-23 01:18:37 +00:00
238 lines
13 KiB
Markdown
238 lines
13 KiB
Markdown
![](doc/logo.svg)
|
||
|
||
![](https://img.shields.io/badge/License-MIT-green)
|
||
[![Go Reference](https://pkg.go.dev/badge/github.com/mappu/miqt/qt.svg)](https://pkg.go.dev/github.com/mappu/miqt/qt)
|
||
[![GitHub Actions CI](https://github.com/mappu/miqt/actions/workflows/miqt.yml/badge.svg?branch=master)](https://github.com/mappu/miqt/actions)
|
||
[![Go Report Card](https://goreportcard.com/badge/github.com/mappu/miqt)](https://goreportcard.com/report/github.com/mappu/miqt)
|
||
![](doc/made_in_nz.svg "Made in New Zealand")
|
||
|
||
# MIQT
|
||
|
||
MIQT is MIT-licensed Qt bindings for Go.
|
||
|
||
This is a straightforward binding of the Qt 5.15 / Qt 6.4+ API using CGO. You must have a working Qt C++ development toolchain to use this Go binding.
|
||
|
||
These bindings were newly started in August 2024. The bindings are functional for all of QtCore, QtGui, and QtWidgets, and there is a uic/rcc implementation. But, the bindings may be immature in some ways. Please try out the bindings and raise issues if you have trouble.
|
||
|
||
## Supported platforms
|
||
|
||
|OS|Arch|Linkage|Status
|
||
|---|---|---|---
|
||
|Linux|x86_64|Static or Dynamic (.so)|✅ Works
|
||
|Windows|x86_64|Static or Dynamic (.dll)|✅ Works
|
||
|Android|ARM64|Dynamic (bundled in .apk package)|✅ Works
|
||
|macOS|x86_64|Static or Dynamic (.dylib)|Should work, [not tested](https://github.com/mappu/miqt/issues/2)
|
||
|macOS|ARM64|Static or Dynamic (.dylib)|Should work, [not tested](https://github.com/mappu/miqt/issues/2)
|
||
|
||
## License
|
||
|
||
The MIQT Go bindings are licensed under the MIT license.
|
||
|
||
You must also meet your Qt license obligations.
|
||
|
||
## Made with MIQT
|
||
|
||
- [mdoutliner](https://github.com/mappu/miqt/tree/master/examples/mdoutliner), Markdown Outliner sample application
|
||
- [qbolt](https://code.ivysaur.me/qbolt), a graphical database manager for BoltDB
|
||
- Raise an issue or PR to have your app listed here!
|
||
|
||
## FAQ
|
||
|
||
### Q1. Why are the binaries so big?
|
||
|
||
Make sure to compile with `go build -ldflags "-s -w"`. This reduces the `helloworld` example from 43MB to 6MB.
|
||
|
||
Then, it's possible to reduce the size further with `upx --best` to 2MB or `upx --lzma` to 1.4MB.
|
||
|
||
### Q2. Can I release a proprietary, commercial app with this binding?
|
||
|
||
Yes. You must also meet your Qt license obligations: either use Qt dynamically-linked dll/so/dylib files under the LGPL, or, purchase a Qt commercial license for static linking.
|
||
|
||
### Q3. Why does it take so long to compile?
|
||
|
||
The first time MIQT is used, your `go build` would take about 10 minutes. But after that, any `go build` is very fast.
|
||
|
||
If you are compiling your app within a Dockerfile, you could cache the build step by running `go install github.com/mappu/miqt/qt`.
|
||
|
||
If you are compiling your app with a one-shot `docker run` command, the compile speed can be improved if you also bind-mount the Docker container's `GOCACHE` directory: `-v $(pwd)/container-build-cache:/root/.cache/go-build`
|
||
|
||
See also [issue #8](https://github.com/mappu/miqt/issues/8).
|
||
|
||
### Q4. How does this compare to other Qt bindings?
|
||
|
||
MIQT is a clean-room binding that does not use any code from other Qt bindings.
|
||
|
||
- [therecipe/qt](https://github.com/therecipe/qt) is the most mature Qt binding for Go.
|
||
- It works by making IPC calls to a separate C++ binary downloaded at runtime from a site under the maintainer's control. This may be less performant than calling Qt directly.
|
||
- Because of the LGPL license, it's [extremely difficult to make a proprietary app](https://github.com/therecipe/qt/wiki/FAQ#can-i-make-a-proprietary-app-with-this-binding-). See also their [issue 259](https://github.com/therecipe/qt/issues/259).
|
||
- [kitech/qt.go](https://github.com/kitech/qt.go) is another mature Qt binding for Go.
|
||
- Unfortunately, it's also using the LGPL license.
|
||
- [go-qamel/qamel](https://github.com/go-qamel/qamel) is an MIT-licensed Qt binding for Go.
|
||
- Unfortunately, it only supports QML, not Qt Widgets.
|
||
|
||
### Q5. How does the MIQT Go API differ from the official Qt C++ API?
|
||
|
||
Most functions are implemented 1:1. [The Qt documentation](https://doc.qt.io/qt-5/classes.html) should be used.
|
||
|
||
The `QByteArray`, `QString`, `QList<T>`, and `QVector<T>` types are projected as plain Go `[]byte`, `string`, and `[]T`. Therefore, you can't call any of QByteArray/QString/QList/QVector's helper methods, you must use some Go equivalent method instead.
|
||
|
||
- Go strings are internally converted to QString using `QString::fromUtf8`. Therefore, the Go string must be UTF-8 to avoid [mojibake](https://en.wikipedia.org/wiki/Mojibake). If the Go string contains binary data, the conversion would corrupt such bytes into U+FFFD (<28>). On return to Go space, this becomes `\xEF\xBF\xBD`.
|
||
|
||
Where Qt returns a C++ object by value (e.g. `QSize`), the binding may have moved it to the heap, and in Go this may be represented as a pointer type. In such cases, a Go finalizer is added to automatically delete the heap object. This means code using MIQT can look basically similar to the Qt C++ equivalent code.
|
||
|
||
The `connect(sourceObject, sourceSignal, targetObject, targetSlot)` is projected as `targetObject.onSourceSignal(func()...)`.
|
||
|
||
Qt class inherited types are projected as a Go embedded struct. For example, to pass a `var myLabel *qt.QLabel` to a function taking only the `*qt.QWidget` base class, write `myLabel.QWidget`.
|
||
|
||
- When a Qt subclass adds a method overload (e.g. `QMenu::addAction(QString)` vs `QWidget::addAction(QAction*)`), the base class version is shadowed and can only be called via `myQMenu.QWidget.AddAction(QAction*)`.
|
||
|
||
Some C++ idioms that were difficult to project were omitted from the binding. But, this can be improved in the future.
|
||
|
||
### Q6. Can I use Qt Designer and the Qt Resource system?
|
||
|
||
![](doc/architecture-uic.png)
|
||
|
||
MIQT has a custom implementation of Qt `uic` and `rcc` tools, to allow using [Qt Designer](https://doc.qt.io/qt-5/qtdesigner-manual.html) for form design and resource management. After running the `miqt-uic` and `miqt-rcc` tools once, you can rebuild any changes using the convenient `go generate` command.
|
||
|
||
### Q7. How can I point MIQT to use a custom Qt install location?
|
||
|
||
MIQT uses `pkg-config` to find all used Qt libraries. Every Qt library should have a definition file in `.pc` format, which provides CGO with the necessary `CXXFLAGS`/`LDFLAGS`. Your Qt development environment already included the necessary `.pc` definition files.
|
||
|
||
You can use the `PKG_CONFIG_PATH` environment variable to override where CGO looks for `.pc` files. [Read more »](pkg-config/README.md)
|
||
|
||
### Q8. How can I upgrade a MIQT app from Qt 5 to Qt 6?
|
||
|
||
The import path changes from `github.com/mappu/miqt/qt` to `github.com/mappu/miqt/qt6`, but most basic classes are the same.
|
||
|
||
You can replace the import path in two ways:
|
||
1. Add a go.mod directive: Run `go mod edit -replace github.com/mappu/miqt/qt=github.com/mappu/miqt/qt6`
|
||
2. Or, update all imports: Run `find . -type f -name .go -exec sed -i 's_"github.com/mappu/miqt/qt"_qt "github.com/mappu/miqt/qt6"_' {} \;`
|
||
|
||
### Q9. How can I add bindings for another Qt library?
|
||
|
||
1. Git clone this repository
|
||
2. In `docker/genbindings.Dockerfile`, add your library's headers and pkg-config file.
|
||
- If your library does not include a pkg-config file, [you must create one.](pkg-config/README.md)
|
||
3. Patch `cmd/genbindings/config-libraries.go` to add a new `generate` block for your target library
|
||
4. Run `genbindings` to regenerate all bindings
|
||
- The first run must populate clang ASTs into a cache directory and may be slower, but it is fast afterwards
|
||
5. Add a `cflags.go` file to the generated binding directory
|
||
- It should have a `#cgo pkg-config: LibraryName` stanza and any extra flags (e.g. `--std=c++17`) that are required but not system-specific
|
||
6. Try to use the new binding within the repo, by adding an example in the `examples/libraries` directory
|
||
7. Commit the generated bindings
|
||
- You can then use your forked MIQT repo with `replace` inside `go.mod`
|
||
- Or, [open a Pull Request](https://github.com/mappu/miqt/compare) to add the library to MIQT
|
||
|
||
## Building
|
||
|
||
### Linux (native)
|
||
|
||
*Tested with Debian 12 / Qt 5.15 / Qt 6.4 / GCC 12*
|
||
|
||
For dynamic linking, with the system Qt (Qt 5):
|
||
|
||
```bash
|
||
apt install qtbase5-dev build-essential
|
||
go build -ldflags '-s -w'
|
||
```
|
||
|
||
For dynamic linking, with the system Qt (Qt 6):
|
||
|
||
```bash
|
||
apt install qt6-base-dev build-essential
|
||
go build -ldflags '-s -w'
|
||
```
|
||
|
||
### Windows (native)
|
||
|
||
*Tested with Fsu0413 Qt 5.15 / Clang 18.1 native compilation*
|
||
|
||
1. Install Go from [the official website](https://go.dev/dl/).
|
||
2. Install some Qt toolchain and its matching GCC or Clang compiler (MSVC is not compatible with CGO).
|
||
- You can use [official Qt binaries](https://www.qt.io/) or any LGPL rebuild.
|
||
- Example: Download and extract the following into some shared `C:\dev\rootfs`:
|
||
- [Qt and matching GCC or Clang toolchain](https://build-qt.fsu0413.me/5.15-series/5.15.11-for-windows/index.html#windows-mingw-llvm)
|
||
- [pkg-config](https://sourceforge.net/projects/pkgconfiglite/files/0.28-1/)
|
||
3. Configure environment variables to allow it to be used:
|
||
|
||
```powershell
|
||
$env:CGO_ENABLED = 1
|
||
$env:CC = 'C:\dev\rootfs\bin\clang.exe'
|
||
$env:CXX = 'C:\dev\rootfs\bin\clang++.exe'
|
||
$env:PKG_CONFIG = 'C:\dev\rootfs\bin\pkg-config.exe'
|
||
$env:CGO_CXXFLAGS = '-Wno-ignored-attributes -D_Bool=bool' # Clang 18 recommendation
|
||
```
|
||
|
||
4. Run `go build -ldflags "-s -w -H windowsgui"`
|
||
|
||
### Windows (MSYS2)
|
||
|
||
*Tested with MSYS2 UCRT64 Qt 5.15 / GCC 14*
|
||
|
||
For dynamic linking:
|
||
|
||
```bash
|
||
pacman -S mingw-w64-ucrt-x86_64-{go,gcc,qt5-base,pkg-config}
|
||
GOROOT=/ucrt64/lib/go go build -ldflags "-s -w -H windowsgui"
|
||
```
|
||
|
||
- Note: the MSYS2 `qt5-base` package [links against `libicu`](https://github.com/msys2/MINGW-packages/blob/master/mingw-w64-qt5-base/PKGBUILD#L241), whereas the Fsu0413 Qt packages do not. When using MSYS2, your distribution size including `.dll` files will be larger.
|
||
|
||
For static linking:
|
||
|
||
Static linking is also available by installing the `mingw-w64-ucrt-x86_64-qt5-static` package and building with `--tags=windowsqtstatic`. The static build will also be smaller as it [does not link to `libicu`](https://github.com/msys2/MINGW-packages/blob/master/mingw-w64-qt5-static/PKGBUILD#L280).
|
||
|
||
### Windows (Docker)
|
||
|
||
*Tested with MXE Qt 5.15 / MXE GCC 5 under cross-compilation*
|
||
|
||
For static linking:
|
||
|
||
1. Build the necessary docker container for cross-compilation:
|
||
- `docker build -t miqt/win64-cross:latest -f docker/win64-cross-go1.23-qt5.15-static.Dockerfile .`
|
||
2. Build your application:
|
||
- `docker run --rm -v $(pwd):/src -w /src miqt/win64-cross:latest go build -buildvcs=false --tags=windowsqtstatic -ldflags '-s -w -H windowsgui'`
|
||
|
||
For dynamic linking:
|
||
|
||
1. Build the necessary docker container for cross-compilation:
|
||
- `docker build -t miqt/win64-dynamic:latest -f docker/win64-cross-go1.23-qt5.15-dynamic.Dockerfile .`
|
||
2. Build your application:
|
||
- `docker run --rm -v $(pwd):/src -w /src miqt/win64-dynamic:latest go build -buildvcs=false -ldflags '-s -w -H windowsgui'`
|
||
3. Copy necessary Qt LGPL libraries and plugin files.
|
||
|
||
See FAQ Q3 for advice about docker performance.
|
||
|
||
To add an icon and other properties to the .exe, you can use [the go-winres tool](https://github.com/tc-hib/go-winres). See the `examples/windowsmanifest` for details.
|
||
|
||
### Android (Docker)
|
||
|
||
*Tested with Raymii Qt 5.15 / Android SDK 31 / Android NDK 22*
|
||
|
||
Miqt supports compiling for Android. Some extra steps are required to bridge the Java, C++, Go worlds.
|
||
|
||
![](doc/android-architecture.png)
|
||
|
||
1. Modify your main function to [support `c-shared` build mode](https://pkg.go.dev/cmd/go#hdr-Build_modes).
|
||
- Package `main` must have an empty `main` function.
|
||
- Rename your `main` function to `AndroidMain` and add a comment `//export AndroidMain`.
|
||
- Ensure to `import "C"`.
|
||
- Check `examples/android` to see how to support both Android and desktop platforms.
|
||
2. Build the necessary docker container for cross-compilation:
|
||
- `docker build -t miqt/android:latest -f docker/android-armv8a-go1.23-qt5.15-dynamic.Dockerfile .`
|
||
3. Build your application as `.so` format:
|
||
- `docker run --rm -v $(pwd):/src -w /src miqt/android:latest go build -buildmode c-shared -ldflags "-s -w -extldflags -Wl,-soname,my_go_app.so" -o android-build/libs/arm64-v8a/my_go_app.so`
|
||
4. Build the Qt linking stub:
|
||
- `docker run --rm -v $(pwd):/src -w /src miqt/android:latest android-stub-gen.sh my_go_app.so AndroidMain android-build/libs/arm64-v8a/libRealAppName_arm64-v8a.so`
|
||
- The linking stub is needed because Qt for Android will itself only call a function named `main`, but `c-shared` can't create one.
|
||
5. Build the [androiddeployqt](https://doc.qt.io/qt-6/android-deploy-qt-tool.html) configuration file:
|
||
- `docker run --rm -v $(pwd):/src -w /src miqt/android:latest android-mktemplate.sh RealAppName deployment-settings.json`
|
||
6. Build the android package:
|
||
- `docker run --rm -v $(pwd):/src -w /src miqt/android:latest androiddeployqt --input ./deployment-settings.json --output ./android-build/`
|
||
- By default, the resulting `.apk` is generated at `android-build/build/outputs/apk/debug/android-build-debug.apk`.
|
||
- You can build in release mode by adding `--release`
|
||
|
||
See FAQ Q3 for advice about docker performance.
|
||
|
||
For repeated builds, only steps 3 and 6 are needed. If you customize the `AndroidManifest.xml` file or images, they will be used for the next `androiddeployqt` run.
|