14 KiB
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) | ✅ Works |
macOS | ARM64 | Static or Dynamic (.dylib) | Should work, not tested |
License
The MIQT Go bindings are licensed under the MIT license.
You must also meet your Qt license obligations.
Made with MIQT
- mdoutliner, Markdown Outliner sample application
- 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.
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 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. See also their issue 259.
- kitech/qt.go is another mature Qt binding for Go.
- Unfortunately, it's also using the LGPL license.
- 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 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. 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)
vsQWidget::addAction(QAction*)
), the base class version is shadowed and can only be called viamyQMenu.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?
MIQT has a custom implementation of Qt uic
and rcc
tools, to allow using Qt Designer 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 »
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:
- Add a go.mod directive: Run
go mod edit -replace github.com/mappu/miqt/qt=github.com/mappu/miqt/qt6
- 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?
- Git clone this repository
- 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.
- Patch
cmd/genbindings/config-libraries.go
to add a newgenerate
block for your target library - 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
- 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
- It should have a
- Try to use the new binding within the repo, by adding an example in the
examples/libraries
directory - Commit the generated bindings
- You can then use your forked MIQT repo with
replace
insidego.mod
- Or, open a Pull Request to add the library to MIQT
- You can then use your forked MIQT repo with
Building
Linux (native)
Tested with Debian 12 / Qt 5.15 / Qt 6.4 / GCC 12
Tested with Fedora 40 / Qt 6.7 / GCC 14
For dynamic linking, with the system Qt (Qt 5):
apt install qtbase5-dev build-essential # Debian / Ubuntu
go build -ldflags '-s -w'
For dynamic linking, with the system Qt (Qt 6):
apt install qt6-base-dev build-essential # Debian / Ubuntu
dnf install qt6-qtbase-devel golang # Fedora
go build -ldflags '-s -w'
Windows (native)
Tested with Fsu0413 Qt 5.15 / Clang 18.1 native compilation
- Install Go from go.dev.
- Install some Qt toolchain and its matching GCC or Clang compiler (MSVC is not compatible with CGO).
- You can use official Qt binaries or any LGPL rebuild.
- Example: Download and extract the following into some shared
C:\dev\rootfs
: - Qt and matching GCC or Clang toolchain
- pkg-config
- Configure environment variables to allow it to be used:
$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
- Run
go build -ldflags "-s -w -H windowsgui"
Windows (MSYS2)
Tested with MSYS2 UCRT64 Qt 5.15 / Qt 6.7 / GCC 14
Install MSYS2 from msys2.org.
For dynamic linking:
# Install Go and C++ toolchains
pacman -S mingw-w64-ucrt-x86_64-{go,gcc,pkg-config}
export GOROOT=/ucrt64/lib/go # Needed only if this is the first time installing Go in MSYS2. Otherwise it would be automatically applied when opening a new Bash terminal.
# Install Qt
pacman -S mingw-w64-ucrt-x86_64-qt5-base # For Qt 5
pacman -S mingw-w64-ucrt-x86_64-qt6-base # For Qt 6
go build -ldflags "-s -w -H windowsgui"
- Note: the MSYS2
qt5-base
package is built to uselibicu
, whereas the Fsu0413 Qt packages are not. ICU is included by default with Windows 10 1703 and later. If you are targeting older versions of Windows, then 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
.
Windows (Docker)
Tested with MXE Qt 5.15 / MXE GCC 5 under cross-compilation
For static linking:
- 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 .
- Build your application:
docker run --rm -v $(pwd):/src -w /src miqt/win64-cross:latest go build --tags=windowsqtstatic -ldflags '-s -w -H windowsgui'
For dynamic linking:
- 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 .
- Build your application:
docker run --rm -v $(pwd):/src -w /src miqt/win64-dynamic:latest go build -ldflags '-s -w -H windowsgui'
- 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. See the examples/windowsmanifest
for details.
macOS (Homebrew)
Tested with macOS 12.6 "Monterey" x86_64 / Go 1.23 / Qt 5.15 / Apple Clang 14.0
Install Homebrew from brew.sh.
For dynamic linking:
xcode-select --install
brew install golang
brew install pkg-config
brew install qt@5
go build -ldflags '-s -w'
Installing qt@5
from Homebrew may be very slow if Homebrew chooses to do a from-source build instead of a binary Bottle build, particularly owing to QtWebEngine (Chromium).
macOS (Docker)
Tested with osxcross 14.5 / Go 1.19 / MacPorts Qt 5.15 / Debian Clang 14.0
For dynamic linking:
- Build the necessary docker container for cross-compilation:
docker build -t miqt/osxcross:latest -f docker/macos-cross-x86_64-sdk14.5-go1.19-qt5.15-dynamic.Dockerfile .
- Build your application:
docker run --rm -v $(pwd):/src -w /src miqt/osxcross:latest go build -ldflags '-s -w'
- Copy necessary Qt LGPL libraries and plugin files.
See FAQ Q3 for advice about docker performance.
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.
- Modify your main function to support
c-shared
build mode.- Package
main
must have an emptymain
function. - Rename your
main
function toAndroidMain
and add a comment//export AndroidMain
. - Ensure to
import "C"
. - Check
examples/android
to see how to support both Android and desktop platforms.
- Package
- Build the necessary docker container for cross-compilation:
docker build -t miqt/android:latest -f docker/android-armv8a-go1.23-qt5.15-dynamic.Dockerfile .
- 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
- 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
, butc-shared
can't create one.
- Build the androiddeployqt configuration file:
docker run --rm -v $(pwd):/src -w /src miqt/android:latest android-mktemplate.sh RealAppName deployment-settings.json
- 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 atandroid-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.