mirror of
https://github.com/mappu/miqt.git
synced 2024-12-21 08:28:37 +00:00
miqt: android support
This commit is contained in:
parent
b0fe164bb5
commit
239ed883c2
4
.gitignore
vendored
4
.gitignore
vendored
@ -15,4 +15,6 @@ examples/helloworld/helloworld.exe
|
||||
examples/mdoutliner/mdoutliner
|
||||
examples/mdoutliner/mdoutliner.exe
|
||||
examples/windowsmanifest/windowsmanifest
|
||||
examples/windowsmanifest/windowsmanifest.exe
|
||||
examples/windowsmanifest/windowsmanifest.exe
|
||||
|
||||
android-build
|
43
README.md
43
README.md
@ -19,8 +19,9 @@ These bindings were newly started in August 2024. The bindings are functional fo
|
||||
|
||||
|Platform|Linkage|Status
|
||||
|---|---|---
|
||||
|Linux|Static, Dynamic (.so)|✅ Works<br>- Tested with Debian 12 / Qt 5.15 / GCC 12
|
||||
|Windows|Static, Dynamic (.dll)|✅ Works<br>- Tested with MXE Qt 5.15 / MXE GCC 5 under cross-compilation<br>- Tested with Fsu0413 Qt 5.15 / Clang 18.1 native compilation
|
||||
|Linux x86_64|Static, Dynamic (.so)|✅ Works<br>- Tested with Debian 12 / Qt 5.15 / GCC 12
|
||||
|Windows x86_64|Static, Dynamic (.dll)|✅ Works<br>- Tested with MXE Qt 5.15 / MXE GCC 5 under cross-compilation<br>- Tested with Fsu0413 Qt 5.15 / Clang 18.1 native compilation
|
||||
|Android ARM64|Dynamic bundled in package|✅ Works<br>- Tested with Raymii Qt 5.15 / Android SDK 31 / Android NDK 22
|
||||
|macOS x86_64|Static, Dynamic (.dylib)|Should work, [not tested](https://github.com/mappu/miqt/issues/2)
|
||||
|macOS ARM64|Static, Dynamic (.dylib)|[Blocked by #11](https://github.com/mappu/miqt/issues/11)
|
||||
|
||||
@ -44,7 +45,11 @@ Yes. You must also meet your Qt license obligations: either use Qt dynamically-l
|
||||
|
||||
### Q3. Why does it take so long to compile?
|
||||
|
||||
The first time the Qt bindings are compiled takes a long time. After this, it's fast. In a Dockerfile, you could cache the build step by running `go install github.com/mappu/miqt`.
|
||||
The first time the Qt bindings are compiled takes a long time. After this, it's fast.
|
||||
|
||||
If you are compiling your app within a Dockerfile, you could cache the build step by running `go install github.com/mappu/miqt`.
|
||||
|
||||
If you are compiling your app with a `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).
|
||||
|
||||
@ -91,7 +96,7 @@ For dynamically-linked builds (closed-source or open source 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.
|
||||
|
||||
For repeated builds, 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 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.
|
||||
|
||||
@ -114,3 +119,33 @@ $env:CGO_CXXFLAGS = '-Wno-ignored-attributes -D_Bool=bool' # Clang 18 recommenda
|
||||
```
|
||||
|
||||
4. Run `go build -ldflags "-s -w -H windowsgui"`
|
||||
|
||||
### Q9. How can I compile for Android?
|
||||
|
||||
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 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 "-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 Q3 for advice about docker performance.
|
||||
|
||||
For repeated builds, if you customize the `AndroidManifest.xml` file or images, they will be used for the next `androiddeployqt` run.
|
||||
|
||||
|
47
cmd/android-mktemplate/android-mktemplate.sh
Executable file
47
cmd/android-mktemplate/android-mktemplate.sh
Executable file
@ -0,0 +1,47 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# android-mktemplate generates a template json file suitable for use with the
|
||||
# androiddeployqt tool.
|
||||
|
||||
set -eu
|
||||
|
||||
# QT_PATH is already pre-set in our docker container environment. Includes trailing slash.
|
||||
QT_PATH=${QT_PATH:-/usr/local/Qt-5.15.13/}
|
||||
|
||||
main() {
|
||||
|
||||
if [[ $# -ne 2 ]] ; then
|
||||
echo "Usage: android-mktemplate.sh appname output.json" >&2
|
||||
exit 1
|
||||
fi
|
||||
local ARG_APPNAME="$1"
|
||||
local ARG_DESTFILE="$2"
|
||||
|
||||
# Available fields are documented in the template file at
|
||||
# @ref /usr/local/Qt-5.15.13/mkspecs/features/android/android_deployment_settings.prf
|
||||
cat > "${ARG_DESTFILE}" <<EOF
|
||||
{
|
||||
"_description": "Generated by miqt/android-mktemplate",
|
||||
"application-binary": "${ARG_APPNAME}",
|
||||
"architectures": {
|
||||
"arm64-v8a" : "aarch64-linux-android"
|
||||
},
|
||||
|
||||
"android-extra-libs": "/opt/android_openssl/ssl_1.1/arm64-v8a/libssl_1_1.so,/opt/android_openssl/ssl_1.1/arm64-v8a/libcrypto_1_1.so",
|
||||
|
||||
"android-min-sdk-version": "23",
|
||||
"android-target-sdk-version": "30",
|
||||
"ndk": "/opt/android-sdk/ndk/22.1.7171670",
|
||||
"ndk-host": "linux-x86_64",
|
||||
"qt": "${QT_PATH}",
|
||||
"sdk": "/opt/android-sdk",
|
||||
"sdkBuildToolsRevision": "30.0.2",
|
||||
"stdcpp-path": "/opt/android-sdk/ndk/22.1.7171670/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/lib/",
|
||||
"tool-prefix": "llvm",
|
||||
"toolchain-prefix": "llvm",
|
||||
"useLLVM": true
|
||||
}
|
||||
EOF
|
||||
}
|
||||
|
||||
main "$@"
|
84
cmd/android-stub-gen/android-stub-gen.sh
Executable file
84
cmd/android-stub-gen/android-stub-gen.sh
Executable file
@ -0,0 +1,84 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# android-stub-gen generates a .so that runs an exported function from another
|
||||
# so file. This is because Qt for Android always tries to run `main`, but Go's
|
||||
# c-shared build mode cannot export a function named `main`.
|
||||
|
||||
set -eu
|
||||
|
||||
# QT_PATH is already pre-set in our docker container environment. Includes trailing slash.
|
||||
QT_PATH=${QT_PATH:-/usr/local/Qt-5.15.13/}
|
||||
|
||||
main() {
|
||||
|
||||
if [[ $# -ne 3 ]] ; then
|
||||
echo "Usage: android-gen-stub.sh src.so function-name dest.so" >&2
|
||||
exit 1
|
||||
fi
|
||||
local ARG_SOURCE_SOFILE="$1"
|
||||
local ARG_FUNCTIONNAME="$2"
|
||||
local ARG_DEST_SOFILE="$3"
|
||||
|
||||
local tmpdir=$(mktemp -d)
|
||||
trap "rm -r ${tmpdir}" EXIT
|
||||
|
||||
echo "- Using temporary directory: ${tmpdir}"
|
||||
echo "- Found Qt path: ${QT_PATH}"
|
||||
|
||||
echo "Generating stub..."
|
||||
|
||||
cat > $tmpdir/miqtstub.cpp <<EOF
|
||||
#include <android/log.h>
|
||||
#include <dlfcn.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
typedef void goMainFunc_t();
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
__android_log_print(ANDROID_LOG_VERBOSE, "miqt_stub", "Starting up");
|
||||
|
||||
void* handle = dlopen("$(basename "$ARG_SOURCE_SOFILE")", RTLD_LAZY);
|
||||
if (handle == NULL) {
|
||||
__android_log_print(ANDROID_LOG_VERBOSE, "miqt_stub", "miqt_stub: null handle opening so: %s", dlerror());
|
||||
exit(1);
|
||||
}
|
||||
|
||||
void* goMain = dlsym(handle, "${ARG_FUNCTIONNAME}");
|
||||
if (goMain == NULL) {
|
||||
__android_log_print(ANDROID_LOG_VERBOSE, "miqt_stub", "miqt_stub: null handle looking for function: %s", dlerror());
|
||||
exit(1);
|
||||
}
|
||||
|
||||
__android_log_print(ANDROID_LOG_VERBOSE, "miqt_stub", "miqt_stub: Found target, calling");
|
||||
|
||||
// Cast to function pointer and call
|
||||
goMainFunc_t* f = (goMainFunc_t*)goMain;
|
||||
f();
|
||||
|
||||
__android_log_print(ANDROID_LOG_VERBOSE, "miqt_stub", "miqt_stub: Target function returned");
|
||||
return 0;
|
||||
}
|
||||
|
||||
EOF
|
||||
|
||||
# Compile
|
||||
# Link with Qt libraries so that androiddeployqt detects us as being the
|
||||
# main shared library
|
||||
$CXX -shared \
|
||||
-ldl \
|
||||
-llog \
|
||||
${QT_PATH}plugins/platforms/libplugins_platforms_qtforandroid_arm64-v8a.so \
|
||||
${QT_PATH}lib/libQt5Widgets_arm64-v8a.so /usr/local/Qt-5.15.13/lib/libQt5Gui_arm64-v8a.so \
|
||||
${QT_PATH}lib/libQt5Core_arm64-v8a.so \
|
||||
${QT_PATH}lib/libQt5Svg_arm64-v8a.so \
|
||||
${QT_PATH}lib/libQt5AndroidExtras_arm64-v8a.so \
|
||||
-fPIC -DQT_WIDGETS_LIB -I${QT_PATH}include/QtWidgets -I${QT_PATH}include/ -I${QT_PATH}include/QtCore -DQT_GUI_LIB -I${QT_PATH}include/QtGui -DQT_CORE_LIB \
|
||||
$tmpdir/miqtstub.cpp \
|
||||
"-Wl,-soname,$(basename "$ARG_DEST_SOFILE")" \
|
||||
-o "$ARG_DEST_SOFILE"
|
||||
|
||||
|
||||
echo "Done."
|
||||
}
|
||||
|
||||
main "$@"
|
BIN
doc/android-architecture.png
Normal file
BIN
doc/android-architecture.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 45 KiB |
17
docker/android-armv8a-go1.23-qt5.15-dynamic.Dockerfile
Normal file
17
docker/android-armv8a-go1.23-qt5.15-dynamic.Dockerfile
Normal file
@ -0,0 +1,17 @@
|
||||
FROM raymii/qt:5.15-android-source
|
||||
|
||||
RUN wget 'https://go.dev/dl/go1.23.1.linux-amd64.tar.gz' && \
|
||||
tar x -C /usr/local/ -f go1.23.1.linux-amd64.tar.gz && \
|
||||
rm go1.23.1.linux-amd64.tar.gz
|
||||
|
||||
COPY ../cmd/android-stub-gen/android-stub-gen.sh /usr/local/bin/android-stub-gen.sh
|
||||
COPY ../cmd/android-stub-gen/android-mktemplate.sh /usr/local/bin/android-mktemplate.sh
|
||||
|
||||
ENV PATH=/usr/local/go/bin:/opt/cmake/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/Qt-5.15.13/bin:/opt/android-sdk/cmdline-tools/tools/bin:/opt/android-sdk/tools:/opt/android-sdk/tools/bin:/opt/android-sdk/platform-tools
|
||||
|
||||
ENV CC=/opt/android-sdk/ndk/22.1.7171670/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android30-clang
|
||||
ENV CXX=/opt/android-sdk/ndk/22.1.7171670/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android30-clang++
|
||||
ENV CGO_ENABLED=1
|
||||
ENV GOOS=android
|
||||
ENV GOARCH=arm64
|
||||
ENV GOFLAGS=-buildvcs=false
|
29
examples/android/main.go
Normal file
29
examples/android/main.go
Normal file
@ -0,0 +1,29 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/mappu/miqt/qt"
|
||||
)
|
||||
|
||||
func myRealMainFunc() {
|
||||
|
||||
qt.NewQApplication(os.Args)
|
||||
|
||||
btn := qt.NewQPushButton2("Hello world!")
|
||||
btn.SetFixedWidth(320)
|
||||
|
||||
var counter int = 0
|
||||
|
||||
btn.OnPressed(func() {
|
||||
counter++
|
||||
btn.SetText(fmt.Sprintf("You have clicked the button %d time(s)", counter))
|
||||
})
|
||||
|
||||
btn.Show()
|
||||
|
||||
qt.QApplication_Exec()
|
||||
|
||||
fmt.Println("OK!")
|
||||
}
|
BIN
examples/android/screenshot.png
Executable file
BIN
examples/android/screenshot.png
Executable file
Binary file not shown.
After Width: | Height: | Size: 33 KiB |
14
examples/android/startup_android.go
Normal file
14
examples/android/startup_android.go
Normal file
@ -0,0 +1,14 @@
|
||||
// +build android
|
||||
|
||||
package main
|
||||
|
||||
import "C" // Required for export support
|
||||
|
||||
//export AndroidMain
|
||||
func AndroidMain() {
|
||||
myRealMainFunc()
|
||||
}
|
||||
|
||||
func main() {
|
||||
// Must be empty
|
||||
}
|
7
examples/android/startup_other.go
Normal file
7
examples/android/startup_other.go
Normal file
@ -0,0 +1,7 @@
|
||||
// +build !android
|
||||
|
||||
package main
|
||||
|
||||
func main() {
|
||||
myRealMainFunc()
|
||||
}
|
9
qt/cflags_android.go
Normal file
9
qt/cflags_android.go
Normal file
@ -0,0 +1,9 @@
|
||||
package qt
|
||||
|
||||
/*
|
||||
|
||||
#cgo CXXFLAGS: -fPIC -DQT_WIDGETS_LIB -I/usr/local/Qt-5.15.13/include/QtWidgets -I/usr/local/Qt-5.15.13/include/ -I/usr/local/Qt-5.15.13/include/QtCore -DQT_GUI_LIB -I/usr/local/Qt-5.15.13/include/QtGui -DQT_CORE_LIB
|
||||
#cgo LDFLAGS: /usr/local/Qt-5.15.13/lib/libQt5Widgets_arm64-v8a.so /usr/local/Qt-5.15.13/lib/libQt5Gui_arm64-v8a.so /usr/local/Qt-5.15.13/lib/libQt5Core_arm64-v8a.so /usr/local/Qt-5.15.13/lib/libQt5Svg_arm64-v8a.so /usr/local/Qt-5.15.13/lib/libQt5AndroidExtras_arm64-v8a.so
|
||||
|
||||
*/
|
||||
import "C"
|
@ -1,3 +1,5 @@
|
||||
// +build linux,!android
|
||||
|
||||
package qt
|
||||
|
||||
/*
|
||||
|
Loading…
Reference in New Issue
Block a user