mirror of
https://github.com/mappu/miqt.git
synced 2025-05-03 18:40:21 +00:00
Merge pull request #208 from mappu/miqt-docker-cmd
Add miqt-docker helper command, add one-command Android builds
This commit is contained in:
commit
9d4cabc099
@ -1,33 +0,0 @@
|
||||
cmd/genbindings/cachedir
|
||||
|
||||
*.exe
|
||||
|
||||
cmd/handbindings/handbindings
|
||||
cmd/handbindings/bindings_test/direct
|
||||
cmd/handbindings/bindings_test/testapp
|
||||
cmd/genbindings/genbindings
|
||||
cmd/miqt-uic/miqt-uic
|
||||
cmd/miqt-rcc/miqt-rcc
|
||||
|
||||
examples/goroutine6/goroutine6
|
||||
examples/helloworld/helloworld
|
||||
examples/helloworld6/helloworld6
|
||||
examples/mdoutliner/mdoutliner
|
||||
examples/mdoutliner6/mdoutliner6
|
||||
examples/windowsmanifest/windowsmanifest
|
||||
examples/uidesigner/uidesigner
|
||||
examples/trivialwizard6/trivialwizard6
|
||||
examples/subclass/subclass
|
||||
examples/modelview/modelview
|
||||
examples/modelview_color6/modelview_color6
|
||||
examples/libraries/extras-scintillaedit/extras-scintillaedit
|
||||
examples/libraries/qt-multimedia/qt-multimedia
|
||||
examples/libraries/qt-network/qt-network
|
||||
examples/libraries/qt-printsupport/qt-printsupport
|
||||
examples/libraries/qt-script/qt-script
|
||||
examples/libraries/qt-svg/qt-svg
|
||||
examples/libraries/qt-webengine/qt-webengine
|
||||
examples/libraries/qt-webkit/qt-webkit
|
||||
examples/libraries/qt6-multimedia/qt6-multimedia
|
||||
examples/libraries/qt6-webengine/qt6-webengine
|
||||
examples/libraries/restricted-extras-qscintilla/restricted-extras-qscintilla
|
167
.github/workflows/miqt.yml
vendored
167
.github/workflows/miqt.yml
vendored
@ -14,6 +14,9 @@ jobs:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
# WARNING: This loads the cache but does not commit back any new changes to
|
||||
# it unless the cache is invalidated in GitHub
|
||||
# It will help to do that every time a new Qt library is added
|
||||
- name: Cache clang ASTs
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
@ -33,14 +36,12 @@ jobs:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Cache GOCACHE
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.cache/go-build
|
||||
key: linux64-buildall-gocache
|
||||
|
||||
# This uses the `genbindings` container in miqt-docker
|
||||
- name: Rebuild all libraries and examples
|
||||
run: make build-all
|
||||
|
||||
- name: Run marshalling test suite
|
||||
run: cmd/miqt-docker/miqt-docker genbindings /bin/bash -c 'cd examples/marshalling && env QT_QPA_PLATFORM=offscreen go test -v'
|
||||
|
||||
miqt_linux64_qt5:
|
||||
runs-on: ubuntu-24.04
|
||||
@ -49,18 +50,11 @@ jobs:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Linux64 docker build
|
||||
run: docker build -t miqt/linux64:qt5 -f docker/linux64-go1.19-qt5.15-dynamic.Dockerfile .
|
||||
|
||||
- name: Cache GOCACHE
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.cache/go-build
|
||||
key: linux64-qt5-gocache
|
||||
|
||||
- name: Linux64 bindings compile and test
|
||||
run: docker run -v ~/.cache/go-build:/root/.cache/go-build -v $PWD:/src -w /src miqt/linux64:qt5 /bin/bash -c 'cd qt && go build && cd ../examples/marshalling && env QT_QPA_PLATFORM=offscreen go test -v'
|
||||
|
||||
- name: Linux64 bindings compile
|
||||
run: |
|
||||
make cmd/miqt-docker/miqt-docker
|
||||
cd qt && ../cmd/miqt-docker/miqt-docker linux64-go1.19-qt5.15-dynamic go build
|
||||
|
||||
miqt_linux64_qt6_4:
|
||||
runs-on: ubuntu-24.04
|
||||
|
||||
@ -68,17 +62,10 @@ jobs:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Linux64 docker build
|
||||
run: docker build -t miqt/linux64:qt64 -f docker/linux64-go1.19-qt6.4-dynamic.Dockerfile .
|
||||
|
||||
- name: Cache GOCACHE
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.cache/go-build
|
||||
key: linux64-qt64-gocache
|
||||
|
||||
- name: Linux64 bindings compile
|
||||
run: docker run -v ~/.cache/go-build:/root/.cache/go-build -v $PWD:/src -w /src miqt/linux64:qt64 /bin/bash -c 'cd qt6 && go build'
|
||||
run: |
|
||||
make cmd/miqt-docker/miqt-docker
|
||||
cd qt6 && ../cmd/miqt-docker/miqt-docker linux64-go1.19-qt6.4-dynamic go build
|
||||
|
||||
miqt_linux64_qt6_8:
|
||||
runs-on: ubuntu-24.04
|
||||
@ -86,18 +73,11 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Linux64 docker build
|
||||
run: docker build -t miqt/linux64:qt68 -f docker/linux64-go1.23-qt6.8-dynamic.Dockerfile .
|
||||
|
||||
- name: Cache GOCACHE
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.cache/go-build
|
||||
key: linux64-qt68-gocache
|
||||
|
||||
|
||||
- name: Linux64 bindings compile
|
||||
run: docker run -v ~/.cache/go-build:/root/.cache/go-build -v $PWD:/src -w /src miqt/linux64:qt68 /bin/bash -c 'cd qt6 && go build'
|
||||
run: |
|
||||
make cmd/miqt-docker/miqt-docker
|
||||
cd qt6 && ../cmd/miqt-docker/miqt-docker linux64-go1.23-qt6.8-dynamic go build
|
||||
|
||||
miqt_win32_qt5:
|
||||
runs-on: ubuntu-24.04
|
||||
@ -105,18 +85,11 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Cache GOCACHE
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.cache/go-build
|
||||
key: win32-qt5-gocache
|
||||
|
||||
- name: Win32 docker build
|
||||
run: docker build -t miqt/win32:qt5 -f docker/win32-cross-go1.23-qt5.15-static.Dockerfile .
|
||||
|
||||
- name: Win32 bindings compile
|
||||
run: docker run -v ~/.cache/go-build:/root/.cache/go-build -v $PWD:/src -w /src miqt/win32:qt5 /bin/bash -c 'cd qt && go build && cd ../examples/helloworld && go build'
|
||||
run: |
|
||||
make cmd/miqt-docker/miqt-docker
|
||||
cmd/miqt-docker/miqt-docker win32-cross-go1.23-qt5.15-static /bin/bash -c 'cd qt && go build && cd ../examples/helloworld && go build'
|
||||
|
||||
miqt_win64_qt5:
|
||||
runs-on: ubuntu-24.04
|
||||
@ -124,18 +97,11 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Cache GOCACHE
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.cache/go-build
|
||||
key: win64-qt5-gocache
|
||||
|
||||
- name: Win64 docker build
|
||||
run: docker build -t miqt/win64:qt5 -f docker/win64-cross-go1.23-qt5.15-static.Dockerfile .
|
||||
|
||||
- name: Win64 bindings compile
|
||||
run: docker run -v ~/.cache/go-build:/root/.cache/go-build -v $PWD:/src -w /src miqt/win64:qt5 /bin/bash -c 'cd qt && go build && cd ../examples/helloworld && go build'
|
||||
run: |
|
||||
make cmd/miqt-docker/miqt-docker
|
||||
cmd/miqt-docker/miqt-docker win64-cross-go1.23-qt5.15-static /bin/bash -c 'cd qt && go build && cd ../examples/helloworld && go build'
|
||||
|
||||
miqt_win64_qt68:
|
||||
runs-on: ubuntu-24.04
|
||||
@ -144,17 +110,10 @@ jobs:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Cache GOCACHE
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.cache/go-build
|
||||
key: win64-qt68-gocache
|
||||
|
||||
- name: Win64 docker build
|
||||
run: docker build -t miqt/win64:qt68 -f docker/win64-cross-go1.23-qt6.8-dynamic.Dockerfile .
|
||||
|
||||
- name: Win64 bindings compile
|
||||
run: docker run -v ~/.cache/go-build:/root/.cache/go-build -v $PWD:/src -w /src miqt/win64:qt68 /bin/bash -c 'cd qt6 && go build && cd ../examples/helloworld6 && go build'
|
||||
run: |
|
||||
make cmd/miqt-docker/miqt-docker
|
||||
cmd/miqt-docker/miqt-docker win64-cross-go1.23-qt6.8-dynamic /bin/bash -c 'cd qt6 && go build && cd ../examples/helloworld6 && go build'
|
||||
|
||||
miqt_android_qt5:
|
||||
runs-on: ubuntu-24.04
|
||||
@ -162,58 +121,24 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Android compile
|
||||
run: |
|
||||
make cmd/miqt-docker/miqt-docker
|
||||
cd examples/helloworld
|
||||
../../cmd/miqt-docker/miqt-docker android-armv8a-go1.23-qt5.15-dynamic -android-build
|
||||
test -f helloworld.apk
|
||||
|
||||
- name: Android armv8a docker build
|
||||
run: docker build -t miqt/android:qt5 -f docker/android-armv8a-go1.23-qt5.15-dynamic.Dockerfile .
|
||||
miqt_android_qt6:
|
||||
runs-on: ubuntu-24.04
|
||||
|
||||
- name: Cache GOCACHE
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.cache/go-build
|
||||
key: android-qt5-armv8a-gocache
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Android compile app as c-shared my_go_app.so
|
||||
run: docker run -v ~/.cache/go-build:/root/.cache/go-build -v $PWD:/src -w /src/examples/android miqt/android:qt5 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
|
||||
|
||||
- name: Android generate libRealAppName.so linking stub
|
||||
run: docker run -v ~/.cache/go-build:/root/.cache/go-build -v $PWD:/src -w /src/examples/android miqt/android:qt5 android-stub-gen.sh my_go_app.so AndroidMain android-build/libs/arm64-v8a/libRealAppName_arm64-v8a.so
|
||||
|
||||
- name: Android generate json packaging metadata
|
||||
run: docker run --rm -v $(pwd):/src -w /src/examples/android miqt/android:qt5 android-mktemplate.sh RealAppName deployment-settings.json
|
||||
|
||||
- name: Android build APK package
|
||||
run: docker run --rm -v $(pwd):/src -w /src/examples/android miqt/android:qt5 androiddeployqt --input ./deployment-settings.json --output ./android-build/
|
||||
|
||||
- name: Verify that package exists
|
||||
run: test -f examples/android/android-build/build/outputs/apk/debug/android-build-debug.apk
|
||||
|
||||
# miqt_android_qt6:
|
||||
# runs-on: ubuntu-24.04
|
||||
#
|
||||
# steps:
|
||||
# - name: Checkout
|
||||
# uses: actions/checkout@v4
|
||||
#
|
||||
# - name: Android armv8a docker build
|
||||
# run: docker build -t miqt/android:qt6 -f docker/android-armv8a-go1.23-qt6.6-dynamic.Dockerfile .
|
||||
#
|
||||
# - name: Cache GOCACHE
|
||||
# uses: actions/cache@v4
|
||||
# with:
|
||||
# path: ~/.cache/go-build
|
||||
# key: android-qt6-armv8a-gocache
|
||||
#
|
||||
# - name: Android compile app as c-shared my_go_app.so
|
||||
# run: docker run -v ~/.cache/go-build:/root/.cache/go-build -v $PWD:/src -w /src/examples/android6 miqt/android:qt6 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
|
||||
#
|
||||
# - name: Android generate libRealAppName.so linking stub
|
||||
# run: docker run -v ~/.cache/go-build:/root/.cache/go-build -v $PWD:/src -w /src/examples/android6 miqt/android:qt6 android-stub-gen.sh my_go_app.so AndroidMain android-build/libs/arm64-v8a/libRealAppName_arm64-v8a.so --qt6
|
||||
#
|
||||
# - name: Android generate json packaging metadata
|
||||
# run: docker run --rm -v $(pwd):/src -w /src/examples/android6 miqt/android:qt6 android-mktemplate.sh RealAppName deployment-settings.json
|
||||
#
|
||||
# - name: Android build APK package
|
||||
# run: docker run --rm -v $(pwd):/src -w /src/examples/android6 miqt/android:qt6 androiddeployqt --input ./deployment-settings.json --output ./android-build/
|
||||
#
|
||||
# - name: Verify that package exists
|
||||
# run: test -f examples/android6/android-build/build/outputs/apk/debug/android-build-debug.apk
|
||||
- name: Android compile
|
||||
run: |
|
||||
make cmd/miqt-docker/miqt-docker
|
||||
cd examples/helloworld6
|
||||
../../cmd/miqt-docker/miqt-docker android-armv8a-go1.23-qt6.6-dynamic -android-build
|
||||
test -f helloworld6.apk
|
||||
|
10
.gitignore
vendored
10
.gitignore
vendored
@ -1,15 +1,6 @@
|
||||
# cache files for genbindings
|
||||
cmd/genbindings/cachedir/
|
||||
|
||||
# docker files
|
||||
container-build-cache/
|
||||
|
||||
# local genbindings configuration
|
||||
cmd/genbindings/genbindings.local*
|
||||
|
||||
# local pkg-config configuration
|
||||
pkg-config/*.pc
|
||||
|
||||
# binaries
|
||||
*.exe
|
||||
|
||||
@ -17,6 +8,7 @@ cmd/handbindings/handbindings
|
||||
cmd/handbindings/bindings_test/direct
|
||||
cmd/handbindings/bindings_test/testapp
|
||||
cmd/genbindings/genbindings
|
||||
cmd/miqt-docker/miqt-docker
|
||||
cmd/miqt-uic/miqt-uic
|
||||
cmd/miqt-rcc/miqt-rcc
|
||||
|
||||
|
37
Makefile
37
Makefile
@ -1,40 +1,23 @@
|
||||
BUILDSTAMPS := docker/genbindings.docker-buildstamp
|
||||
DOCKER := docker
|
||||
SHELL := /bin/bash
|
||||
|
||||
# DOCKEREXEC runs the target command in the `genbindings` docker container.
|
||||
# It mounts in the current GOCACHE and GOMODCACHE.
|
||||
DOCKEREXEC = mkdir -p "$$(go env GOCACHE)" && \
|
||||
mkdir -p "$$(go env GOMODCACHE)" && \
|
||||
$(DOCKER) run \
|
||||
--user "$$(id -u):$$(id -g)" \
|
||||
-v "$$(go env GOCACHE):/.cache/go-build" \
|
||||
-v "$$(go env GOMODCACHE):/go/pkg/mod" \
|
||||
-v "$$PWD:/src" \
|
||||
-w /src \
|
||||
miqt/genbindings:latest \
|
||||
/bin/bash -c
|
||||
GO := go
|
||||
|
||||
.PHONY: all
|
||||
all: genbindings
|
||||
|
||||
docker/genbindings.docker-buildstamp: docker/genbindings.Dockerfile
|
||||
$(DOCKER) build -t miqt/genbindings:latest -f docker/genbindings.Dockerfile .
|
||||
touch $@
|
||||
|
||||
.PHONY: clean
|
||||
clean:
|
||||
$(DOCKER) image rm -f miqt/genbindings:latest
|
||||
rm -f $(BUILDSTAMPS)
|
||||
cmd/miqt-docker/miqt-docker: go.mod cmd/miqt-docker/*.go docker/*.Dockerfile
|
||||
$(GO) build -o cmd/miqt-docker/miqt-docker ./cmd/miqt-docker
|
||||
|
||||
.PHONY: clean-cache
|
||||
clean-cache:
|
||||
rm -f cmd/genbindings/cachedir/*.json
|
||||
|
||||
cmd/genbindings/genbindings: go.mod cmd/genbindings/*.go
|
||||
$(GO) build -o cmd/genbindings/genbindings ./cmd/genbindings
|
||||
|
||||
.PHONY: genbindings
|
||||
genbindings: $(BUILDSTAMPS)
|
||||
$(DOCKEREXEC) 'cd cmd/genbindings && go build && ./genbindings'
|
||||
genbindings: cmd/miqt-docker/miqt-docker cmd/genbindings/genbindings
|
||||
cd cmd/genbindings && ../miqt-docker/miqt-docker genbindings ./genbindings
|
||||
|
||||
.PHONY: build-all
|
||||
build-all: $(BUILDSTAMPS)
|
||||
$(DOCKEREXEC) 'go build ./...'
|
||||
build-all: cmd/miqt-docker/miqt-docker
|
||||
./cmd/miqt-docker/miqt-docker genbindings go build ./...
|
||||
|
87
README.md
87
README.md
@ -53,6 +53,8 @@ Make sure to compile with `go build -ldflags "-s -w"`. This reduces the `hellowo
|
||||
|
||||
Then, it's possible to reduce the size further with `upx --best` to 2MB or `upx --lzma` to 1.4MB.
|
||||
|
||||
You can also try `miqt-docker native -minify-build` to use aggressive `CFLAGS`.
|
||||
|
||||
### 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.
|
||||
@ -63,7 +65,7 @@ The first time MIQT is used, your `go build` would take [about 10 minutes](https
|
||||
|
||||
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`
|
||||
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`. The `miqt-docker` helper app does this automatically.
|
||||
|
||||
See also [issue #8](https://github.com/mappu/miqt/issues/8).
|
||||
|
||||
@ -84,27 +86,29 @@ MIQT is a clean-room binding that does not use any code from other Qt bindings.
|
||||
|
||||
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>`, `QVector<T>`, `QMap<K,V>`, `QHash<K,V>` types are projected as plain Go `[]byte`, `string`, `[]T`, and `map[K]V`. Therefore, you can't call any of the Qt type's methods, you must use some Go equivalent method instead.
|
||||
|
||||
Container types:
|
||||
- The `QByteArray`, `QString`, `QList<T>`, `QVector<T>`, `QMap<K,V>`, `QHash<K,V>` types are projected as plain Go `[]byte`, `string`, `[]T`, and `map[K]V`. Therefore, you can't call any of the Qt type's 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`.
|
||||
- The iteration order of a Qt `QMap`/`QHash` will differ from the Go map iteration order. `QMap` is iterated by key order, but Go maps and `QHash` iterate in an undefined internal order.
|
||||
|
||||
- The iteration order of a Qt QMap/QHash will differ from the Go map iteration order. QMap is iterated by key order, but Go maps and QHash iterate in an undefined internal order.
|
||||
|
||||
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()...)`.
|
||||
Memory management:
|
||||
- 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.
|
||||
|
||||
Events and signals:
|
||||
- The `connect(sourceObject, sourceSignal, targetObject, targetSlot)` is projected as `targetObject.onSourceSignal(func()...)`.
|
||||
- You can also override virtual methods like PaintEvent in the same way. Your callback `func()` receives `super()` as a first argument that can be used to call the base class implementation.
|
||||
|
||||
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`.
|
||||
|
||||
Class pointers:
|
||||
- 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*)`.
|
||||
|
||||
- A MIQT pointer points to a Go struct, not to the raw C++ Qt widget class. Therefore `QTabWidget.CurrentWidget() == MyTab` will never compare equal because `CurrentWidget()` created a new Go struct wrapping the same C++ pointer. You can compare `QTabWidget.CurrentIndex()`, or, you can use: `QTabWidget.CurrentWidget().UnsafePointer() == MyTab.UnsafePointer()`.
|
||||
|
||||
The Go runtime migrates goroutines between OS threads, but Qt expects fixed OS threads to be used for each QObject. When you first call `qt.NewQApplication` in MIQT, that will be considered the [Qt main thread](https://doc.qt.io/qt-6/thread-basics.html#gui-thread-and-worker-thread) and will automatically signal the Go runtime to bind to a fixed OS thread using `runtime.LockOSThread()`.
|
||||
Multithreading:
|
||||
- The Go runtime migrates goroutines between OS threads, but Qt expects fixed OS threads to be used for each QObject. When you first call `qt.NewQApplication` in MIQT, that will be considered the [Qt main thread](https://doc.qt.io/qt-6/thread-basics.html#gui-thread-and-worker-thread) and will automatically signal the Go runtime to bind to a fixed OS thread using `runtime.LockOSThread()`.
|
||||
- When accessing Qt objects from inside another goroutine, it's safest to use `(qt6/mainthread).Wait()` or `Start()` to access the Qt objects from Qt's main thread.
|
||||
|
||||
- When accessing Qt objects from inside another goroutine, it's safest to use `(qt6/mainthread).Wait()` to access the Qt objects from Qt's main thread.
|
||||
Android:
|
||||
- A `QFileDialog` may return a filepath of the form `content://...`. Such paths can be opened with `qt.QFile` but not with Go `os.Open()`; you can pass the handle to Go using `os.NewFile(QFile.Handle(), "name")`.
|
||||
|
||||
Some C++ idioms that were difficult to project were omitted from the binding. But, this can be improved in the future.
|
||||
|
||||
@ -118,20 +122,22 @@ MIQT has a custom implementation of Qt `uic` and `rcc` tools, to allow using [Qt
|
||||
|
||||
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)
|
||||
You can use the `PKG_CONFIG_PATH` environment variable to override where CGO looks for `.pc` files. [Read more »](doc/pkg-config.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"_' {} \;`
|
||||
You can update all imports by running `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?
|
||||
|
||||
Fork this repository and add your library to the `genbindings/config-libraries` file. [Read more »](cmd/genbindings/README.md)
|
||||
|
||||
### Q10. Is there an easy build tool?
|
||||
|
||||
You can use the ordinary `go get` and `go build` commands. To help with cross-compilation, you can use the optional `miqt-docker` tool. [Read more »](cmd/miqt-docker/README.md)
|
||||
|
||||
## Building
|
||||
|
||||
### Linux (native)
|
||||
@ -180,6 +186,13 @@ pacman -S pkg-config gcc go qt6-base qscintilla-qt6 qt6-charts qt6-multimedia qt
|
||||
go build -ldflags '-s -w'
|
||||
```
|
||||
|
||||
### Windows (Docker with miqt-docker)
|
||||
|
||||
```bash
|
||||
go install github.com/mappu/miqt/cmd/miqt-docker
|
||||
miqt-docker win64-qt6-static -windows-build # or -qt5- or -static
|
||||
```
|
||||
|
||||
### Windows (native)
|
||||
|
||||
*Tested with Fsu0413 Qt 5.15 / Clang 18.1 native compilation*
|
||||
@ -236,14 +249,14 @@ Static linking is also available by installing the `mingw-w64-ucrt-x86_64-qt5-st
|
||||
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 .`
|
||||
- In the `docker/` directory: docker build -t miqt/win64-cross:latest -f 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 --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 .`
|
||||
- In the `docker/` directory: `docker build -t miqt/win64-dynamic:latest -f 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 -ldflags '-s -w -H windowsgui'`
|
||||
3. Copy necessary Qt LGPL libraries and plugin files.
|
||||
@ -290,44 +303,26 @@ Installing `qt@5` from Homebrew may be very slow if Homebrew chooses to do a fro
|
||||
For dynamic linking:
|
||||
|
||||
1. 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 .`
|
||||
- In the `docker/` directory: `docker build -t miqt/osxcross:latest -f macos-cross-x86_64-sdk14.5-go1.19-qt5.15-dynamic.Dockerfile .`
|
||||
2. Build your application:
|
||||
- `docker run --rm -v $(pwd):/src -w /src miqt/osxcross:latest go build -ldflags '-s -w'`
|
||||
3. Copy necessary Qt LGPL libraries and plugin files.
|
||||
|
||||
See FAQ Q3 for advice about docker performance.
|
||||
|
||||
### Android (Docker)
|
||||
### Android (Docker with miqt-docker)
|
||||
|
||||
*Tested with Raymii Qt 5.15 / Android SDK 31 / Android NDK 22*
|
||||
|
||||
*Tested with Qt.io Qt 6.6 / Android SDK 33 / Android NDK 25*
|
||||
*Tested with Raymii Qt 5.15 / Android SDK 31 / Android NDK 22 and with Qt.io Qt 6.6 / Android SDK 33 / Android NDK 25*
|
||||
|
||||
MIQT supports compiling for Android. Some extra steps are required to bridge the Java, C++, Go worlds.
|
||||
|
||||

|
||||
|
||||
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:
|
||||
- (Qt 5) `docker build -t miqt/android:latest -f docker/android-armv8a-go1.23-qt5.15-dynamic.Dockerfile .`
|
||||
- (Qt 6) `docker build -t miqt/android:latest -f docker/android-armv8a-go1.23-qt6.6-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:
|
||||
- (Qt 5) `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`
|
||||
- (Qt 6) Add `--qt6` final argument
|
||||
- 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`
|
||||
```bash
|
||||
go install github.com/mappu/miqt/cmd/miqt-docker
|
||||
miqt-docker android-qt5 -android-build # or android-qt6
|
||||
```
|
||||
|
||||
See FAQ Q3 for advice about docker performance.
|
||||
This produces a `.apk` in the current directory. A default manifest, icon, and keystore will be created. If you customize the `AndroidManifest.xml` file or images, they will be used for the next build.
|
||||
|
||||
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.
|
||||
Advanced users may customize the build process or manually invoke `androiddeployqt`. You can invoke it inside the container environment via `miqt-docker android-qt5 androiddeployqt ...`. For more information, see the `android-build.sh` file that `miqt-docker` is running.
|
||||
|
@ -1,66 +0,0 @@
|
||||
#!/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/}
|
||||
QT_ANDROID=${QT_ANDROID:-$QT_PATH}
|
||||
|
||||
ndk_version() {
|
||||
ls /opt/android-sdk/ndk/ | tail -n1
|
||||
}
|
||||
|
||||
target_sdk_version() {
|
||||
ls /opt/android-sdk/platforms | tail -n1 | sed -re 's/android-//'
|
||||
}
|
||||
|
||||
build_tools_version() {
|
||||
ls /opt/android-sdk/build-tools | tail -n1
|
||||
}
|
||||
|
||||
extra_libs() {
|
||||
if [[ -d /opt/android_openssl ]] ; then
|
||||
# Our miqt Qt5 container includes these extra .so libraries
|
||||
# However, the aqtinstall-based Qt 6 container does not use them
|
||||
echo "/opt/android_openssl/ssl_1.1/arm64-v8a/libssl_1_1.so,/opt/android_openssl/ssl_1.1/arm64-v8a/libcrypto_1_1.so"
|
||||
fi
|
||||
}
|
||||
|
||||
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": "$(extra_libs)",
|
||||
"android-min-sdk-version": "23",
|
||||
"android-target-sdk-version": "$(target_sdk_version)",
|
||||
"ndk": "/opt/android-sdk/ndk/$(ndk_version)",
|
||||
"ndk-host": "linux-x86_64",
|
||||
"qt": "${QT_ANDROID}",
|
||||
"sdk": "/opt/android-sdk",
|
||||
"sdkBuildToolsRevision": "$(build_tools_version)",
|
||||
"stdcpp-path": "/opt/android-sdk/ndk/$(ndk_version)/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/lib/",
|
||||
"tool-prefix": "llvm",
|
||||
"toolchain-prefix": "llvm",
|
||||
"useLLVM": true
|
||||
}
|
||||
EOF
|
||||
}
|
||||
|
||||
main "$@"
|
@ -1,106 +0,0 @@
|
||||
#!/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
|
||||
|
||||
main() {
|
||||
|
||||
if [[ $# -ne 3 && $# -ne 4 ]] ; then
|
||||
echo "Usage: android-gen-stub.sh src.so function-name dest.so [--qt6|--qt5]" >&2
|
||||
exit 1
|
||||
fi
|
||||
local ARG_SOURCE_SOFILE="$1"
|
||||
local ARG_FUNCTIONNAME="$2"
|
||||
local ARG_DEST_SOFILE="$3"
|
||||
local ARG_QTVERSION="${4:---qt5}"
|
||||
|
||||
local tmpdir=$(mktemp -d)
|
||||
trap "rm -r ${tmpdir}" EXIT
|
||||
|
||||
echo "- Using temporary directory: ${tmpdir}"
|
||||
|
||||
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
|
||||
|
||||
if [[ $ARG_QTVERSION == '--qt5' ]] ; then
|
||||
|
||||
# QT_PATH is already pre-set in our docker container environment. Includes trailing slash.
|
||||
QT_PATH=${QT_PATH:-/usr/local/Qt-5.15.13/}
|
||||
echo "- Found Qt path: ${QT_PATH}"
|
||||
|
||||
$CXX -shared \
|
||||
-ldl \
|
||||
-llog \
|
||||
-L${QT_PATH}plugins/platforms -lplugins_platforms_qtforandroid_arm64-v8a \
|
||||
$(pkg-config --libs Qt5Widgets) \
|
||||
$(pkg-config --libs Qt5AndroidExtras) \
|
||||
$tmpdir/miqtstub.cpp \
|
||||
"-Wl,-soname,$(basename "$ARG_DEST_SOFILE")" \
|
||||
-o "$ARG_DEST_SOFILE"
|
||||
|
||||
elif [[ $ARG_QTVERSION == '--qt6' ]] ; then
|
||||
|
||||
# QT_ANDROID is already pre-set in our docker container environment. Does NOT include trailing slash
|
||||
QT_ANDROID=${QT_ANDROID:-/opt/Qt/6.6.1/android_arm64_v8a}
|
||||
echo "- Found Qt path: ${QT_ANDROID}"
|
||||
|
||||
# There is no AndroidExtras in Qt 6
|
||||
|
||||
$CXX -shared \
|
||||
-ldl \
|
||||
-llog \
|
||||
-L${QT_ANDROID}/plugins/platforms -lplugins_platforms_qtforandroid_arm64-v8a \
|
||||
$(pkg-config --libs Qt6Widgets) \
|
||||
$tmpdir/miqtstub.cpp \
|
||||
"-Wl,-soname,$(basename "$ARG_DEST_SOFILE")" \
|
||||
-o "$ARG_DEST_SOFILE"
|
||||
|
||||
else
|
||||
echo "Unknown Qt version argument "${ARG_QTVERSION}" (expected --qt5 or --qt6)" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Done."
|
||||
}
|
||||
|
||||
main "$@"
|
@ -38,7 +38,7 @@ You should check the following configuration:
|
||||
|
||||
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)
|
||||
- If your library does not include a pkg-config file, [you must create one.](../../doc/pkg-config.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
|
||||
|
63
cmd/miqt-docker/README.md
Normal file
63
cmd/miqt-docker/README.md
Normal file
@ -0,0 +1,63 @@
|
||||
# miqt-docker
|
||||
|
||||
This is an optional helper program to quickly run a dockerized MIQT cross-compiler
|
||||
environment. Many containers are available targeting different OSes and Qt versions.
|
||||
|
||||
It also has some built-in commands that can be run either dockerized or natively.
|
||||
|
||||
## Usage
|
||||
|
||||
Run `miqt-docker` with no arguments to see full usage instructions and all
|
||||
available embedded dockerfiles:
|
||||
|
||||
```bash
|
||||
Usage: miqt-docker ENVIRONMENT COMMAND...
|
||||
|
||||
COMMAND may be any shell command (e.g. go build); or /bin/bash to get an
|
||||
interactive terminal; or one of the following predefined tasks:
|
||||
|
||||
-build Run 'go build' with usual MIQT flags
|
||||
-minify-build Run 'go build' with special minification flags
|
||||
-windows-build Run 'go build' with special Windows support
|
||||
-android-build Build an Android APK (using the android-qt5 or android-qt6
|
||||
container environments)
|
||||
|
||||
Environment variables:
|
||||
DOCKER Override the path to docker
|
||||
|
||||
Available container environments: (use - as wildcard character)
|
||||
[...]
|
||||
```
|
||||
|
||||
You can specify the environment using a short form of the name. For example,
|
||||
`win64-static` will be expanded to the regex `/win64.+static/` and pick the
|
||||
best available match with the highest version number (`win64-cross-go1.24-qt6.8-static`
|
||||
at time of writing).
|
||||
This allows you to pin platforms and major versions in build commands while
|
||||
automatically upgrading to minor versions.
|
||||
|
||||
Example build commands:
|
||||
|
||||
```bash
|
||||
miqt-docker macos go build -ldflags '-s -w'
|
||||
miqt-docker native -minify-build
|
||||
miqt-docker win64-qt6-static -windows-build
|
||||
miqt-docker win64-qt6-static /bin/bash
|
||||
miqt-docker android-qt6 -android-build
|
||||
```
|
||||
|
||||
## Comparison to manual Docker commands
|
||||
|
||||
You can create a dockerized MIQT build environment yourself using the Dockerfiles
|
||||
in the `docker/` directory. The benefit of miqt-docker is:
|
||||
|
||||
- Embeds all available MIQT docker containers
|
||||
- Use glob matches to automatically pick the highest version container for target
|
||||
- Automatically build new docker containers or reuse existing, based on content hash of the Dockerfile
|
||||
- Consistently named docker images across multiple projects using MIQT
|
||||
- Automatically bind source code volume from current go.mod / go.work / git repository and preserve relative working directory
|
||||
- Handles bind-mounting the GOCACHE and GOMODCACHE directories for fast rebuilds
|
||||
- Handles using the proper uid+gid on Linux
|
||||
- Automatically detect sudo requirement on Linux
|
||||
- Convenient predefined tasks
|
||||
- Advanced build support for Android
|
415
cmd/miqt-docker/android-build.sh
Normal file
415
cmd/miqt-docker/android-build.sh
Normal file
@ -0,0 +1,415 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# android-build.sh allows building a MIQT Go application for Android.
|
||||
# For details, see the top-level README.md file.
|
||||
|
||||
set -Eeuo pipefail
|
||||
|
||||
# QT_PATH is pre-set in the Qt 5 docker container environment. Includes trailing slash
|
||||
# QT_ANDROID is pre-set in the Qt 6 docker container environment
|
||||
QT_PATH=${QT_PATH:-/usr/local/Qt-5.15.13/}
|
||||
QT_ANDROID=${QT_ANDROID:-$QT_PATH}
|
||||
|
||||
export LC_ALL=C.UTF-8
|
||||
|
||||
# get_app_name returns the android app's name. This affects the default name
|
||||
# in deployment-settings.json and the generated lib.so names.
|
||||
# You can still customise the package name and the package ID in the xml
|
||||
# files after generation.
|
||||
get_app_name() {
|
||||
basename "$(pwd)"
|
||||
}
|
||||
|
||||
get_stub_soname() {
|
||||
# libRealAppName_arm64-v8a.so
|
||||
echo "lib$(get_app_name)_arm64-v8a.so"
|
||||
}
|
||||
|
||||
get_go_soname() {
|
||||
echo "libMiqtGolangApp_arm64-v8a.so"
|
||||
}
|
||||
|
||||
ndk_version() {
|
||||
ls /opt/android-sdk/ndk/ | tail -n1
|
||||
}
|
||||
|
||||
target_sdk_version() {
|
||||
ls /opt/android-sdk/platforms | tail -n1 | sed -re 's/android-//'
|
||||
}
|
||||
|
||||
build_tools_version() {
|
||||
ls /opt/android-sdk/build-tools | tail -n1
|
||||
}
|
||||
|
||||
# extra_libs returns a comma-separated list of extra libraries to include in
|
||||
# the apk package
|
||||
extra_libs() {
|
||||
if [[ -d /opt/android_openssl ]] ; then
|
||||
# Our miqt Qt5 container includes these extra .so libraries
|
||||
# However, the aqtinstall-based Qt 6 container does not use them
|
||||
echo "/opt/android_openssl/ssl_1.1/arm64-v8a/libssl_1_1.so,/opt/android_openssl/ssl_1.1/arm64-v8a/libcrypto_1_1.so"
|
||||
fi
|
||||
}
|
||||
|
||||
# generate_template_contents produces a deployment settings JSON file that is
|
||||
# understood by the androiddeployqt program.
|
||||
# Available fields are documented in the template file at:
|
||||
# @ref /usr/local/Qt-5.15.13/mkspecs/features/android/android_deployment_settings.prf
|
||||
generate_template_contents() {
|
||||
|
||||
cat <<EOF
|
||||
{
|
||||
"_description": "Generated by miqt/android-mktemplate",
|
||||
"application-binary": "$(get_app_name)",
|
||||
"architectures": {
|
||||
"arm64-v8a" : "aarch64-linux-android"
|
||||
},
|
||||
"android-extra-libs": "$(extra_libs)",
|
||||
"android-min-sdk-version": "23",
|
||||
"android-target-sdk-version": "$(target_sdk_version)",
|
||||
"ndk": "/opt/android-sdk/ndk/$(ndk_version)",
|
||||
"ndk-host": "linux-x86_64",
|
||||
"qt": "${QT_ANDROID}",
|
||||
"sdk": "/opt/android-sdk",
|
||||
"sdkBuildToolsRevision": "$(build_tools_version)",
|
||||
"stdcpp-path": "/opt/android-sdk/ndk/$(ndk_version)/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/lib/",
|
||||
"tool-prefix": "llvm",
|
||||
"toolchain-prefix": "llvm",
|
||||
"useLLVM": true
|
||||
}
|
||||
EOF
|
||||
}
|
||||
|
||||
# pkg_config_dependencies echoes the pkg-config packages that are needed as
|
||||
# a baseline for the detected Qt version.
|
||||
# It's not smart enough to discover other Qt packages yet.
|
||||
pkg_config_dependencies() {
|
||||
local QT_VERSION=$(detect_miqt_qt_version)
|
||||
if [[ $QT_VERSION == 'qt5' ]] ; then
|
||||
echo Qt5Widgets
|
||||
echo Qt5AndroidExtras
|
||||
|
||||
elif [[ $QT_VERSION == 'qt6' ]] ; then
|
||||
# There is no AndroidExtras in Qt 6
|
||||
echo Qt6Widgets
|
||||
|
||||
fi
|
||||
}
|
||||
|
||||
# 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`.
|
||||
android_stub_gen() {
|
||||
|
||||
local STUBNAME="miqt-stub-$(date +%s).cpp"
|
||||
|
||||
cat > $STUBNAME <<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("$(get_go_soname)", 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, "AndroidMain");
|
||||
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 \
|
||||
-L${QT_ANDROID}/plugins/platforms -lplugins_platforms_qtforandroid_arm64-v8a \
|
||||
$(pkg-config --libs $(pkg_config_dependencies)) \
|
||||
${STUBNAME} \
|
||||
"-Wl,-soname,$(basename "$(get_stub_soname)")" \
|
||||
-o "android-build/libs/arm64-v8a/$(get_stub_soname)"
|
||||
|
||||
rm "${STUBNAME}"
|
||||
}
|
||||
|
||||
# require_is_main_package verifies that this is the main Go package.
|
||||
require_is_main_package() {
|
||||
if ! grep -Fq 'package main' *.go ; then
|
||||
echo "This doesn't seem to be the main package" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# patch_app_main sets up the startup go files so that the go program can be
|
||||
# built either as c-shared for Android or as a normal program for desktop OSes.
|
||||
patch_app_main() {
|
||||
|
||||
# Replace func main() with app_main()
|
||||
|
||||
for srcfile in *.go ; do
|
||||
sed -i -re 's/^func main\(\) \{/func app_main() {/' "$srcfile"
|
||||
done
|
||||
|
||||
# Add shim startup files
|
||||
|
||||
cat <<EOF > startup_android.go
|
||||
//go:build android
|
||||
// +build android
|
||||
|
||||
package main
|
||||
|
||||
import "C" // Required for export support
|
||||
|
||||
//export AndroidMain
|
||||
func AndroidMain() {
|
||||
app_main()
|
||||
}
|
||||
|
||||
func main() {
|
||||
// Must be empty
|
||||
}
|
||||
|
||||
EOF
|
||||
|
||||
cat <<EOF > startup_other.go
|
||||
//go:build !android
|
||||
// +build !android
|
||||
|
||||
package main
|
||||
|
||||
func main() {
|
||||
app_main()
|
||||
}
|
||||
|
||||
EOF
|
||||
|
||||
gofmt -w startup_android.go || true
|
||||
gofmt -w startup_other.go || true
|
||||
|
||||
# Done
|
||||
}
|
||||
|
||||
# unpatch_app_main undoes the transformation from patch_app_main.
|
||||
unpatch_app_main() {
|
||||
|
||||
# Replace func main() with app_main()
|
||||
|
||||
for srcfile in *.go ; do
|
||||
sed -i -re 's/^func app_main\(\) \{/func main() {/' "$srcfile"
|
||||
done
|
||||
|
||||
# Remove extra startup files
|
||||
|
||||
rm startup_android.go || true
|
||||
rm startup_other.go || true
|
||||
}
|
||||
|
||||
# build_app_so compiles the Go app as a c-shared .so.
|
||||
build_app_so() {
|
||||
go build \
|
||||
-buildmode c-shared \
|
||||
-ldflags "-s -w -extldflags -Wl,-soname,$(get_go_soname)" \
|
||||
-o "android-build/libs/arm64-v8a/$(get_go_soname)"
|
||||
}
|
||||
|
||||
sdkmanager() {
|
||||
echo /opt/android-sdk/cmdline-tools/*/bin/sdkmanager
|
||||
}
|
||||
|
||||
# build_apk calls androiddeployqt to package the android-build directory into
|
||||
# the final apk.
|
||||
build_apk() {
|
||||
|
||||
# Qt 6 androiddeployqt: Understands the QT_ANDROID_KEYSTORE_STORE_PASS in env
|
||||
# Qt 5 androiddeployqt: Doesn't - any use of `--sign keystore alias` here
|
||||
# requires stdin prompt but doesn't pass androiddeployqt's stdin through
|
||||
# to jarsigner subprocess
|
||||
# Either way, don't sign the app here, rely on separate jarsigner command
|
||||
|
||||
# Work around an issue with Qt 6 sporadically failing to detect that
|
||||
# we have a valid Qt platform plugin
|
||||
# TODO why does this happen? Is it related to file sort ordering?
|
||||
# It really does fix itself after multiple attempts (usually less than 5)
|
||||
# - When it fails: Error: qmlimportscanner not found at /qmlimportscanner
|
||||
# - When it works: Error: qmlimportscanner not found at libexec/qmlimportscanner
|
||||
while ! androiddeployqt \
|
||||
--input ./deployment-settings.json \
|
||||
--output ./android-build/ \
|
||||
--release \
|
||||
2> >(tee /tmp/androiddeployqt.stderr.log >&2) ; do
|
||||
|
||||
if grep -Fq 'Make sure the app links to Qt Gui library' /tmp/androiddeployqt.stderr.log ; then
|
||||
echo "Detected temporary problem with Qt plugin detection. Retrying..."
|
||||
sleep 1
|
||||
|
||||
else
|
||||
# real error
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
local OUTAPK=$(get_app_name).apk
|
||||
rm "$OUTAPK" || true
|
||||
|
||||
# Zipalign
|
||||
echo "Zipalign..."
|
||||
/opt/android-sdk/build-tools/*/zipalign \
|
||||
-p 4 \
|
||||
./android-build/build/outputs/apk/release/android-build-release-unsigned.apk \
|
||||
"$OUTAPK"
|
||||
|
||||
# Sign
|
||||
echo "Signing..."
|
||||
#jarsigner \
|
||||
# -verbose \
|
||||
# -sigalg SHA256withRSA -digestalg SHA256 \
|
||||
# -keystore ./android.keystore \
|
||||
# "$OUTAPK" \
|
||||
# "${QT_ANDROID_KEYSTORE_ALIAS}" \
|
||||
# -storepass:env QT_ANDROID_KEYSTORE_STORE_PASS \
|
||||
# -keypass:env QT_ANDROID_KEYSTORE_KEY_PASS
|
||||
|
||||
/opt/android-sdk/build-tools/*/apksigner \
|
||||
sign \
|
||||
--ks ./android.keystore \
|
||||
--ks-key-alias "${QT_ANDROID_KEYSTORE_ALIAS}" \
|
||||
--ks-pass env:QT_ANDROID_KEYSTORE_STORE_PASS \
|
||||
--key-pass env:QT_ANDROID_KEYSTORE_KEY_PASS \
|
||||
"$OUTAPK"
|
||||
}
|
||||
|
||||
# detect_env_qt_version detects the system's current Qt version.
|
||||
detect_env_qt_version() {
|
||||
if qmake --version | fgrep -q 'Qt version 5' ; then
|
||||
echo "qt5"
|
||||
return 0
|
||||
|
||||
elif qmake --version | fgrep -q 'Qt version 6' ; then
|
||||
echo "qt6"
|
||||
return 0
|
||||
|
||||
else
|
||||
echo "Missing Qt tools in PATH" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# detect_miqt_qt_version echoes either "qt5", "qt6", or exits bash.
|
||||
detect_miqt_qt_version() {
|
||||
local IS_QT5=false
|
||||
if grep -qF '"github.com/mappu/miqt/qt"' *.go ; then
|
||||
IS_QT5=true
|
||||
fi
|
||||
|
||||
local IS_QT6=false
|
||||
if grep -qF '"github.com/mappu/miqt/qt6"' *.go ; then
|
||||
IS_QT6=true
|
||||
fi
|
||||
|
||||
if [[ $IS_QT5 == true && $IS_QT6 == true ]] ; then
|
||||
echo "Found qt5 and qt6 imports, confused about what to do next" >&2
|
||||
exit 1
|
||||
|
||||
elif [[ $IS_QT5 == true ]] ; then
|
||||
echo "qt5"
|
||||
return 0
|
||||
|
||||
elif [[ $IS_QT6 == true ]] ; then
|
||||
echo "qt6"
|
||||
return 0
|
||||
|
||||
else
|
||||
echo "Found neither qt5 nor qt6 imports. Is this a MIQT Qt app?" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
generate_default_keystore() {
|
||||
|
||||
local GENPASS=storepass_$(cat /dev/urandom | head -c64 | md5sum | cut -d' ' -f1)
|
||||
|
||||
keytool \
|
||||
-genkeypair \
|
||||
-dname "cn=Miqt, ou=Miqt, o=Miqt, c=US" \
|
||||
-keyalg RSA \
|
||||
-alias miqt \
|
||||
-keypass "${GENPASS}" \
|
||||
-keystore ./android.keystore \
|
||||
-storepass "${GENPASS}" \
|
||||
-validity 20000
|
||||
|
||||
echo "QT_ANDROID_KEYSTORE_PATH=./android.keystore" > android.keystore.env
|
||||
echo "QT_ANDROID_KEYSTORE_ALIAS=miqt" >> android.keystore.env
|
||||
echo "QT_ANDROID_KEYSTORE_STORE_PASS=${GENPASS}" >> android.keystore.env
|
||||
echo "QT_ANDROID_KEYSTORE_KEY_PASS=${GENPASS}" >> android.keystore.env
|
||||
}
|
||||
|
||||
# main is the entrypoint for android-build.sh.
|
||||
main() {
|
||||
|
||||
if [[ $(detect_env_qt_version) != $(detect_miqt_qt_version) ]] ; then
|
||||
echo "The system is $(detect_env_qt_version) but the app uses $(detect_miqt_qt_version). Is this the right container?" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
require_is_main_package
|
||||
|
||||
# Rebuild deployment-settings.json
|
||||
if [[ ! -f deployment-settings.json ]] ; then
|
||||
echo "Generating deployment-settings.json..."
|
||||
generate_template_contents > deployment-settings.json
|
||||
fi
|
||||
|
||||
mkdir -p android-build/libs/arm64-v8a
|
||||
|
||||
if [[ ! -f android-build/libs/arm64-v8a/$(get_stub_soname) ]] ; then
|
||||
echo "Generating stub so..."
|
||||
android_stub_gen
|
||||
fi
|
||||
|
||||
# Rebuild miqt_golang_app.so
|
||||
echo "Compiling Go app..."
|
||||
patch_app_main
|
||||
build_app_so
|
||||
unpatch_app_main
|
||||
|
||||
# Keypair
|
||||
if [[ ! -f android.keystore || ! -f android.keystore.env ]] ; then
|
||||
echo "Signing keystore not found, generating a default one..."
|
||||
generate_default_keystore
|
||||
fi
|
||||
|
||||
# Load keypair credentials into exported env vars
|
||||
set -o allexport
|
||||
source android.keystore.env
|
||||
set +o allexport
|
||||
|
||||
# Generate .apk
|
||||
echo "Packaging APK..."
|
||||
build_apk
|
||||
|
||||
echo "Complete"
|
||||
}
|
||||
|
||||
main "$@"
|
105
cmd/miqt-docker/docker.go
Normal file
105
cmd/miqt-docker/docker.go
Normal file
@ -0,0 +1,105 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
var (
|
||||
needsSudo bool
|
||||
)
|
||||
|
||||
// dockerImage describes an image available in the docker daemon.
|
||||
type dockerImage struct {
|
||||
ID string
|
||||
Repository string
|
||||
Tag string
|
||||
}
|
||||
|
||||
// dockerCommand creates an *exec.Cmd for running docker. It respects the global
|
||||
// `needsSudo` state.
|
||||
func dockerCommand(args ...string) *exec.Cmd {
|
||||
docker := os.Getenv("DOCKER")
|
||||
if docker == "" {
|
||||
docker = "docker"
|
||||
}
|
||||
|
||||
if needsSudo {
|
||||
useArgs := make([]string, 0, len(args)+1)
|
||||
useArgs = append(useArgs, docker)
|
||||
useArgs = append(useArgs, args...)
|
||||
return exec.Command(`sudo`, useArgs...)
|
||||
}
|
||||
|
||||
return exec.Command(docker, args...)
|
||||
}
|
||||
|
||||
// dockerListImages lists all the current docker images.
|
||||
func dockerListImages() ([]dockerImage, error) {
|
||||
|
||||
cmd := dockerCommand(`image`, `ls`, `--format`, `{{json . }}`)
|
||||
cmd.Stderr = os.Stderr // passthrough
|
||||
|
||||
buff, err := cmd.Output()
|
||||
if err != nil {
|
||||
if !needsSudo {
|
||||
// Retry with sudo
|
||||
log.Println("Retrying with sudo...")
|
||||
needsSudo = true
|
||||
return dockerListImages()
|
||||
}
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var ret []dockerImage
|
||||
|
||||
dec := json.NewDecoder(bytes.NewReader(buff))
|
||||
for {
|
||||
var entry dockerImage
|
||||
err = dec.Decode(&entry)
|
||||
if err != nil {
|
||||
if errors.Is(err, io.EOF) {
|
||||
return ret, nil
|
||||
}
|
||||
return nil, err // real error
|
||||
}
|
||||
|
||||
ret = append(ret, entry)
|
||||
}
|
||||
}
|
||||
|
||||
// dockerFindImage searches all the current docker images to find one named as
|
||||
// the supplied `repository:tag`.
|
||||
func dockerFindImage(repository, tag string) (*dockerImage, error) {
|
||||
|
||||
images, err := dockerListImages()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, im := range images {
|
||||
if im.Repository == repository && im.Tag == tag {
|
||||
// found it
|
||||
return &im, nil
|
||||
}
|
||||
}
|
||||
|
||||
// No match
|
||||
return nil, os.ErrNotExist
|
||||
}
|
||||
|
||||
// dockerBuild builds the supplied dockerfile and tags it as repository:tag
|
||||
// as well as repository:latest.
|
||||
func dockerBuild(dockerfile []byte, repository, tag string) error {
|
||||
cmd := dockerCommand(`build`, `-t`, repository+`:`+tag, `-t`, repository+`:latest`, `-`)
|
||||
cmd.Stderr = os.Stderr
|
||||
cmd.Stdin = bytes.NewReader(dockerfile)
|
||||
|
||||
return cmd.Run()
|
||||
}
|
64
cmd/miqt-docker/filepath.go
Normal file
64
cmd/miqt-docker/filepath.go
Normal file
@ -0,0 +1,64 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// highestCommonParent finds the oldest ancestor of a set of paths.
|
||||
// If there is no common ancestor, returns / on Linux or an error on Windows.
|
||||
func highestCommonParent(paths []string) (string, error) {
|
||||
if len(paths) == 0 {
|
||||
return "", errors.New("no input")
|
||||
}
|
||||
|
||||
parts := strings.Split(paths[0], string(filepath.Separator))
|
||||
|
||||
caseSensitive := runtime.GOOS != "windows"
|
||||
|
||||
for _, check := range paths {
|
||||
checkn := strings.Split(check, string(filepath.Separator))
|
||||
|
||||
// If this check path is shorter, the common part must also shrink
|
||||
if len(checkn) < len(parts) {
|
||||
parts = parts[0:len(checkn)]
|
||||
}
|
||||
|
||||
for i, checkpart := range checkn[0:len(parts)] { // len(parts) is now <= len(checkn) so this is safe
|
||||
if caseSensitive {
|
||||
if parts[i] == checkpart {
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
// case insensitive comparison
|
||||
if strings.EqualFold(parts[i], checkpart) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// Divergence from i: onwards
|
||||
parts = parts[0:i]
|
||||
break
|
||||
}
|
||||
|
||||
// Early failure case
|
||||
if len(parts) == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
isEmpty := len(parts) == 0 || (len(parts) == 1 && parts[0] == "")
|
||||
|
||||
if isEmpty {
|
||||
if runtime.GOOS == "windows" {
|
||||
return "", fmt.Errorf("Selected paths have no common ancestor: %v", paths)
|
||||
}
|
||||
return `/`, nil
|
||||
}
|
||||
|
||||
return strings.Join(parts, string(filepath.Separator)), nil
|
||||
}
|
78
cmd/miqt-docker/filepath_test.go
Normal file
78
cmd/miqt-docker/filepath_test.go
Normal file
@ -0,0 +1,78 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestHighestCommonParent(t *testing.T) {
|
||||
if runtime.GOOS == "windows" {
|
||||
t.Skip("This test uses platform-specific paths")
|
||||
}
|
||||
|
||||
type testCase struct {
|
||||
input []string
|
||||
expect string
|
||||
}
|
||||
|
||||
cases := []testCase{
|
||||
|
||||
// Single input
|
||||
testCase{
|
||||
input: []string{`/foo/bar/baz`},
|
||||
expect: `/foo/bar/baz`,
|
||||
},
|
||||
|
||||
// Duplicated input
|
||||
testCase{
|
||||
input: []string{`/foo/bar/baz`, `/foo/bar/baz`},
|
||||
expect: `/foo/bar/baz`,
|
||||
},
|
||||
|
||||
// Trailing slashes are preserved if they all included trailing slashes
|
||||
testCase{
|
||||
input: []string{`/foo/bar/baz/`, `/foo/bar/baz/`},
|
||||
expect: `/foo/bar/baz/`,
|
||||
},
|
||||
|
||||
// Common directory
|
||||
testCase{
|
||||
input: []string{`/foo/bar/baz`, `/foo/quux`},
|
||||
expect: `/foo`,
|
||||
},
|
||||
|
||||
// Common directory, multiple inputs
|
||||
testCase{
|
||||
input: []string{`/foo/a`, `/foo/b`, `/foo/c`, `/foo/d`},
|
||||
expect: `/foo`,
|
||||
},
|
||||
|
||||
testCase{
|
||||
input: []string{`/foo/bar/baz`, `/unrelated`, `/foo/bar/baz`},
|
||||
expect: `/`,
|
||||
},
|
||||
|
||||
// No leading forwardslash (single input)
|
||||
testCase{
|
||||
input: []string{`foo/bar/baz`},
|
||||
expect: `foo/bar/baz`,
|
||||
},
|
||||
|
||||
// No leading forwardslash (empty output assumes /)
|
||||
testCase{
|
||||
input: []string{`foo/bar/baz`, `unrelated`, `foo/bar/baz`},
|
||||
expect: `/`,
|
||||
},
|
||||
}
|
||||
|
||||
for idx, tc := range cases {
|
||||
got, err := highestCommonParent(tc.input)
|
||||
if err != nil {
|
||||
t.Errorf("test %d: input(%v) got error=%v", idx, tc.input, err)
|
||||
continue
|
||||
}
|
||||
if got != tc.expect {
|
||||
t.Errorf("test %d: input(%v) got %q, want %q", idx, tc.input, got, tc.expect)
|
||||
}
|
||||
}
|
||||
}
|
24
cmd/miqt-docker/isatty_linux.go
Normal file
24
cmd/miqt-docker/isatty_linux.go
Normal file
@ -0,0 +1,24 @@
|
||||
//+build linux
|
||||
//go:build linux
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
func isatty() bool {
|
||||
fd := os.Stdout.Fd()
|
||||
req := syscall.TCGETS
|
||||
termios := syscall.Termios{}
|
||||
|
||||
_, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), uintptr(req), uintptr(unsafe.Pointer(&termios)))
|
||||
if errno != 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
// Successfully got Termios info = stdout is a tty
|
||||
return true
|
||||
}
|
8
cmd/miqt-docker/isatty_other.go
Normal file
8
cmd/miqt-docker/isatty_other.go
Normal file
@ -0,0 +1,8 @@
|
||||
//+build !linux
|
||||
//go:build !linux
|
||||
|
||||
package main
|
||||
|
||||
func isatty() bool {
|
||||
return true
|
||||
}
|
296
cmd/miqt-docker/main.go
Normal file
296
cmd/miqt-docker/main.go
Normal file
@ -0,0 +1,296 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/user"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/mappu/miqt/docker"
|
||||
)
|
||||
|
||||
// glob2regex converts the glob pattern into a regexp.
|
||||
// It only supports `-` as a special character meaning 'anything'.
|
||||
// The resulting regex is unanchored i.e. can match anywhere within a target string.
|
||||
func glob2regex(pattern string) *regexp.Regexp {
|
||||
parts := strings.Split(pattern, `-`)
|
||||
for i, p := range parts {
|
||||
parts[i] = regexp.QuoteMeta(p)
|
||||
}
|
||||
|
||||
return regexp.MustCompile(strings.Join(parts, `.*`))
|
||||
}
|
||||
|
||||
// shasum returns the hex sha256 of a byte slice.
|
||||
func shasum(data []byte) string {
|
||||
hashdata := sha256.Sum256(data)
|
||||
return hex.EncodeToString(hashdata[:])
|
||||
}
|
||||
|
||||
// usage displays how to use miqt-docker and then exits the process.
|
||||
func usage(dockerfiles []fs.DirEntry) {
|
||||
fmt.Fprint(os.Stderr, `Usage: `+filepath.Base(os.Args[0])+` ENVIRONMENT COMMAND...
|
||||
|
||||
COMMAND may be any shell command (e.g. go build); or /bin/bash to get an
|
||||
interactive terminal; or one of the following special tasks:
|
||||
|
||||
-build Run 'go build' with usual MIQT flags
|
||||
-minify-build Run 'go build' with special minification flags
|
||||
-windows-build Run 'go build' with special Windows support
|
||||
-android-build Build an Android APK (using the android-qt5 or android-qt6
|
||||
container environments)
|
||||
|
||||
Environment variables:
|
||||
DOCKER Override the path to docker
|
||||
|
||||
Available container environments: (use - as wildcard character)
|
||||
native (Run natively without docker)
|
||||
`)
|
||||
|
||||
for _, ff := range dockerfiles {
|
||||
fmt.Fprintf(os.Stderr, " %s\n", strings.TrimSuffix(ff.Name(), `.Dockerfile`))
|
||||
}
|
||||
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// getDockerRunArgsForGlob returns a []string array of all the {busywork} arguments
|
||||
// for a `docker {run -e -v ...} go build` command.
|
||||
// It does glob matching for the target container, and builds it if it does not yet exist.
|
||||
func getDockerRunArgsForGlob(dockerfiles []fs.DirEntry, containerNameGlob string, isatty bool) ([]string, error) {
|
||||
|
||||
requestEnvironment := glob2regex(containerNameGlob)
|
||||
var match string
|
||||
for _, ff := range dockerfiles {
|
||||
if !requestEnvironment.MatchString(ff.Name()) {
|
||||
continue
|
||||
}
|
||||
|
||||
match = ff.Name()
|
||||
// continue searching for a later match with higher version number
|
||||
}
|
||||
|
||||
if match == "" {
|
||||
return nil, fmt.Errorf("No available environment matches the request %q\n", containerNameGlob)
|
||||
}
|
||||
|
||||
if !(match == os.Args[1] || match == os.Args[1]+`.Dockerfile`) {
|
||||
// An inexact/glob match was involved. Show what it was
|
||||
log.Printf("Selecting dockerfile: %s", match)
|
||||
}
|
||||
|
||||
dockerFileContent, err := docker.ReadFile(match)
|
||||
if err != nil {
|
||||
return nil, err // shouldn't happen
|
||||
}
|
||||
|
||||
dockerfileHash := shasum(dockerFileContent)[:8] // First 8 characters of content hash
|
||||
|
||||
// Check to see if this dockerfile has already been turned into an image
|
||||
|
||||
containerName := `miqt-docker/` + strings.TrimSuffix(match, `.Dockerfile`)
|
||||
|
||||
_, err = dockerFindImage(containerName, dockerfileHash)
|
||||
if err != nil {
|
||||
if err != os.ErrNotExist {
|
||||
return nil, err // real error
|
||||
}
|
||||
|
||||
log.Printf("No matching docker image, creating...")
|
||||
err = dockerBuild(dockerFileContent, containerName, dockerfileHash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Search again
|
||||
_, err = dockerFindImage(containerName, dockerfileHash)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to build container for %s:%s: %w", containerName, dockerfileHash, err) // Any error now is a real error
|
||||
}
|
||||
}
|
||||
|
||||
// Container match found - clean up older containers for the same tag
|
||||
|
||||
allContainers, err := dockerListImages()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, ctr := range allContainers {
|
||||
if ctr.Repository == containerName &&
|
||||
!(ctr.Tag == dockerfileHash || ctr.Tag == "latest") {
|
||||
log.Printf("Removing previous version container %s:%s ...", containerName, ctr.Tag)
|
||||
rmCmd := dockerCommand(`image`, `rm`, containerName+`:`+ctr.Tag)
|
||||
rmCmd.Stdout = os.Stdout
|
||||
rmCmd.Stderr = os.Stderr
|
||||
err = rmCmd.Run()
|
||||
if err != nil {
|
||||
log.Printf("Warning: Failed to remove previous container: %v", err.Error())
|
||||
// log and continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Container match found - safe to run our command
|
||||
|
||||
fullCommand := []string{"run", "--rm", "-i"}
|
||||
|
||||
if isatty {
|
||||
fullCommand = append(fullCommand, "-t")
|
||||
}
|
||||
|
||||
if runtime.GOOS != "windows" {
|
||||
userinfo, err := user.Current()
|
||||
if err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
|
||||
fullCommand = append(fullCommand, `--user`, userinfo.Uid+`:`+userinfo.Gid)
|
||||
}
|
||||
|
||||
// Find the GOMODCACHE and GOCACHE to populate mapped volumes
|
||||
gomodcache, err := exec.Command(`go`, `env`, `GOMODCACHE`).Output()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Finding GOMODCACHE: %w", err)
|
||||
}
|
||||
if gomodcache_sz := strings.TrimSpace(string(gomodcache)); len(gomodcache_sz) > 0 {
|
||||
_ = os.MkdirAll(gomodcache_sz, 0755) // Might not exist if no Go modules have been used yet
|
||||
|
||||
fullCommand = append(fullCommand, `-v`, gomodcache_sz+`:/go/pkg/mod`, `-e`, `GOMODCACHE=/go/pkg/mod`)
|
||||
}
|
||||
|
||||
gocache, err := exec.Command(`go`, `env`, `GOCACHE`).Output()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Finding GOCACHE: %w", err)
|
||||
}
|
||||
if gocache_sz := strings.TrimSpace(string(gocache)); len(gocache_sz) > 0 {
|
||||
_ = os.MkdirAll(gocache_sz, 0755) // Might not exist if no Go packages have been built yet
|
||||
|
||||
fullCommand = append(fullCommand, `-v`, gocache_sz+`:/.cache/go-build`, `-e`, `GOCACHE=/.cache/go-build`)
|
||||
}
|
||||
|
||||
// We need to bind-mount probably not just the current working directory,
|
||||
// but upwards to the root git repo / go.mod file / go.work file (whichever
|
||||
// is highest)
|
||||
|
||||
var parentPaths []string
|
||||
gomod, err := exec.Command(`go`, `env`, `GOMOD`).Output()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Finding GOMOD: %w", err)
|
||||
}
|
||||
if gomod_sz := strings.TrimSpace(string(gomod)); len(gomod_sz) > 0 {
|
||||
parentPaths = append(parentPaths, gomod_sz)
|
||||
}
|
||||
|
||||
gowork, err := exec.Command(`go`, `env`, `GOWORK`).Output()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Finding GOWORK: %w", err)
|
||||
}
|
||||
if gowork_sz := strings.TrimSpace(string(gowork)); len(gowork_sz) > 0 {
|
||||
parentPaths = append(parentPaths, gowork_sz)
|
||||
}
|
||||
|
||||
gitroot, err := exec.Command(`git`, `rev-parse`, `--show-toplevel`).Output()
|
||||
if err != nil {
|
||||
// Maybe this isn't a git repository? Git is optional anyway, there are hg/bzr users
|
||||
// Don't panic
|
||||
} else {
|
||||
if gitroot_sz := strings.TrimSpace(string(gitroot)); len(gitroot_sz) > 0 {
|
||||
parentPaths = append(parentPaths, gitroot_sz)
|
||||
}
|
||||
}
|
||||
|
||||
cwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
parentPaths = append(parentPaths, cwd) // It's an option too
|
||||
|
||||
basedir, err := highestCommonParent(parentPaths)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
relCwd, err := filepath.Rel(basedir, cwd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Don't mount directly on /src , otherwise -android-build will not know
|
||||
// the package name for top-level builds. Use a subfolder within it
|
||||
mountDir := `/src/` + filepath.Base(cwd)
|
||||
|
||||
if runtime.GOOS == "windows" {
|
||||
// convert C:\foo\bar paths to /c/foo/bar that Docker understands
|
||||
// Otherwise, you experience "invalid mode" when the : is parsed
|
||||
basedir = `/` + strings.ToLower(string(basedir[0])) + `/` + strings.ReplaceAll(basedir[3:], `\`, `/`)
|
||||
|
||||
// Always forwardslashes for in-docker paths, even on Windows OS
|
||||
mountDir = strings.ReplaceAll(mountDir, `\`, `/`)
|
||||
}
|
||||
|
||||
fullCommand = append(fullCommand,
|
||||
`-v`, basedir+`:`+mountDir,
|
||||
`-w`, path.Join(mountDir, relCwd),
|
||||
|
||||
// Final standard docker commands
|
||||
`-e`, `HOME=/tmp`,
|
||||
containerName+`:`+dockerfileHash,
|
||||
)
|
||||
|
||||
return fullCommand, nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
|
||||
dockerfiles, err := docker.Dockerfiles.ReadDir(`.`)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if len(os.Args) < 3 {
|
||||
usage(dockerfiles)
|
||||
}
|
||||
|
||||
taskArgs, taskOp, taskAllowTty, err := evaluateTask(os.Args[2:])
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
var cmd *exec.Cmd
|
||||
if os.Args[1] == "native" {
|
||||
|
||||
if taskArgs[0] == `/bin/bash` && runtime.GOOS == "windows" {
|
||||
log.Fatal("This command can't be used in 'native' mode on Windows.")
|
||||
}
|
||||
|
||||
cmd = exec.Command(taskArgs[0], taskArgs[1:]...) // n.b. [1:] may be an empty slice
|
||||
|
||||
} else {
|
||||
dockerArgs, err := getDockerRunArgsForGlob(dockerfiles, os.Args[1], taskAllowTty && isatty())
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
dockerArgs = append(dockerArgs, taskArgs...)
|
||||
cmd = dockerCommand(dockerArgs...)
|
||||
}
|
||||
|
||||
cmd.Stdin = os.Stdin
|
||||
cmd.Stderr = os.Stderr
|
||||
cmd.Stdout = os.Stdout
|
||||
taskOp(cmd)
|
||||
err = cmd.Run()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
77
cmd/miqt-docker/tasks.go
Normal file
77
cmd/miqt-docker/tasks.go
Normal file
@ -0,0 +1,77 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"embed"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
//go:embed android-build.sh
|
||||
var embedAndroidBuildSh []byte
|
||||
|
||||
var _ embed.FS // Workaround to allow import of package `embed`
|
||||
|
||||
// evaluateTask turns the supplied process arguments into real arguments to
|
||||
// execute, handling quick command recipes as well as arbitrary execution
|
||||
func evaluateTask(taskArgs []string) (retArgs []string, fixup func(*exec.Cmd), allowTty bool, err error) {
|
||||
|
||||
if len(taskArgs) == 0 {
|
||||
return nil, nil, false, errors.New("No task specified")
|
||||
}
|
||||
|
||||
if len(taskArgs[0]) == 0 {
|
||||
return nil, nil, false, errors.New("Empty-string first command")
|
||||
}
|
||||
|
||||
// Set up defaults
|
||||
|
||||
retArgs = []string{}
|
||||
fixup = func(*exec.Cmd) {} // no-op
|
||||
allowTty = true
|
||||
|
||||
//
|
||||
|
||||
stdinFrom := func(stdinBytes []byte) func(*exec.Cmd) {
|
||||
return func(c *exec.Cmd) {
|
||||
c.Stdin = bytes.NewReader(stdinBytes)
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
if taskArgs[0][0] != '-' {
|
||||
// Task does not start with a hyphen = plain command
|
||||
retArgs = taskArgs
|
||||
return
|
||||
}
|
||||
|
||||
switch taskArgs[0] {
|
||||
case `-build`:
|
||||
retArgs = []string{"go", "build", "-ldflags", "-s -w"}
|
||||
retArgs = append(retArgs, taskArgs[1:]...)
|
||||
return
|
||||
|
||||
case `-windows-build`:
|
||||
retArgs = []string{"go", "build", "-ldflags", "-s -w -H windowsgui"}
|
||||
retArgs = append(retArgs, taskArgs[1:]...)
|
||||
return
|
||||
|
||||
case `-minify-build`:
|
||||
// @ref https://github.com/mappu/miqt/issues/147#issuecomment-2800331135
|
||||
retArgs = []string{`/bin/bash`, `-c`, "CGO_CFLAGS='-Os -ffunction-sections -fdata-sections -flto=auto' CGO_CXXFLAGS='-Os -ffunction-sections -fdata-sections -flto=auto' CGO_LDFLAGS='-Wl,--gc-sections -flto=auto -fwhole-program' go build -ldflags '-s -w'"}
|
||||
retArgs = append(retArgs, taskArgs[1:]...)
|
||||
return
|
||||
|
||||
case `-android-build`:
|
||||
retArgs = []string{"/bin/bash", "-s"}
|
||||
retArgs = append(retArgs, taskArgs[1:]...)
|
||||
fixup = stdinFrom(embedAndroidBuildSh)
|
||||
allowTty = false
|
||||
return
|
||||
|
||||
default:
|
||||
return nil, nil, false, fmt.Errorf("Unrecognized task %q", taskArgs[0])
|
||||
}
|
||||
}
|
@ -8,6 +8,7 @@ To specify the CFLAGS/CXXFLAGS and LDFLAGS for a specific library, make a `MyLib
|
||||
|
||||
```pkgconfig
|
||||
Name: My Library
|
||||
Requires: Qt6Widgets
|
||||
Libs: -lfoo
|
||||
Cflags: -I/path/
|
||||
```
|
||||
@ -16,6 +17,8 @@ Then run `PKG_CONFIG_PATH=/path/to/dir/ go build` so CGO will find your library.
|
||||
|
||||
The `PKG_CONFIG_PATH` environment variable is understood both by CGO and by genbindings.
|
||||
|
||||
When running genbindings in the docker/genbindings container, custom pkg-config files are created inline in the Dockerfile.
|
||||
|
||||
## Further reading
|
||||
|
||||
- [Guide to pkg-config](https://people.freedesktop.org/~dbn/pkg-config-guide.html)
|
||||
@ -25,3 +28,23 @@ The `PKG_CONFIG_PATH` environment variable is understood both by CGO and by genb
|
||||
$ pkg-config --variable pc_path pkg-config
|
||||
/usr/local/lib/x86_64-linux-gnu/pkgconfig:/usr/local/lib/pkgconfig:/usr/local/share/pkgconfig:/usr/lib/x86_64-linux-gnu/pkgconfig:/usr/lib/pkgconfig:/usr/share/pkgconfig
|
||||
```
|
||||
|
||||
List all available packages known to pkg-config:
|
||||
|
||||
```bash
|
||||
pkg-config --list-all
|
||||
```
|
||||
|
||||
A full example for QScintilla (Qt 5) from the [genbindings.Dockerfile](../docker/genbindings.Dockerfile):
|
||||
|
||||
```pkg-config
|
||||
includedir=/usr/include/x86_64-linux-gnu/qt5/Qsci/
|
||||
|
||||
Name: QScintilla
|
||||
Description: Qt5 port of the Scintilla source code editing widget
|
||||
URL: http://www.riverbankcomputing.co.uk/software/qscintilla
|
||||
Version: 2.13.3
|
||||
Requires: Qt5Widgets, Qt5PrintSupport
|
||||
Libs: -lqscintilla2_qt5
|
||||
Cflags: -I${includedir}
|
||||
```
|
@ -4,9 +4,6 @@ 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-mktemplate/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
|
||||
|
||||
# The pkg-config definitions were all installed with platform-specific suffixes
|
||||
@ -14,6 +11,10 @@ ENV PATH=/usr/local/go/bin:/opt/cmake/bin:/usr/local/sbin:/usr/local/bin:/usr/sb
|
||||
# This container is targeting armv8-a, so set up simple symlinks
|
||||
RUN /bin/bash -c 'cd /usr/local/Qt-5.15.13/lib/pkgconfig ; for f in *_arm64-v8a.pc ; do cp $f "$(basename -s _arm64-v8a.pc "$f").pc"; done'
|
||||
|
||||
# This is gross but (A) it's containerized and (B) allows --uid=1000 to perform builds
|
||||
# Only needed for certain problematic versions of the Android SDK; a readonly SDK works in both older+newer SDKs
|
||||
RUN /bin/bash -c 'find /opt/android-sdk/ -type d -exec chmod 777 {} \; && find /opt/android-sdk/ -perm 660 -exec chmod 666 {} \;'
|
||||
|
||||
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
|
||||
|
@ -9,9 +9,6 @@ RUN wget 'https://go.dev/dl/go1.23.6.linux-amd64.tar.gz' && \
|
||||
tar x -C /usr/local/ -f go1.23.6.linux-amd64.tar.gz && \
|
||||
rm go1.23.6.linux-amd64.tar.gz
|
||||
|
||||
COPY cmd/android-stub-gen/android-stub-gen.sh /usr/local/bin/android-stub-gen.sh
|
||||
COPY cmd/android-mktemplate/android-mktemplate.sh /usr/local/bin/android-mktemplate.sh
|
||||
|
||||
# Fix up pkg-config definitions:
|
||||
# 1. There are only pkg-config definitions included for gcc_64 (Linux native), not for the android_arm64_v8a target we actually want
|
||||
# 2. It looks for `Libs: -L${libdir} -lQt6Widgets` but the file is named libQt6Widgets_arm64-v8a.so
|
||||
|
16
docker/embed.go
Normal file
16
docker/embed.go
Normal file
@ -0,0 +1,16 @@
|
||||
// This Go file exports all the *.Dockerfile files for miqt-docker to use.
|
||||
package docker
|
||||
|
||||
import (
|
||||
"embed"
|
||||
)
|
||||
|
||||
//go:embed *.Dockerfile
|
||||
var Dockerfiles embed.FS
|
||||
|
||||
// ReadFile returns the content of one of the dockerfiles.
|
||||
// That's because an embed.FS appears out-of-package as a []fs.DirEntry, which
|
||||
// isn't directly readable.
|
||||
func ReadFile(name string) ([]byte, error) {
|
||||
return Dockerfiles.ReadFile(name)
|
||||
}
|
@ -39,10 +39,43 @@ RUN \
|
||||
qmake && \
|
||||
make
|
||||
|
||||
# Custom pkg-config definitions
|
||||
|
||||
RUN mkdir -p /usr/local/lib/pkgconfig
|
||||
|
||||
COPY pkg-config/QScintilla.pc.example /usr/local/lib/pkgconfig/QScintilla.pc
|
||||
COPY pkg-config/QScintilla6.pc.example /usr/local/lib/pkgconfig/QScintilla6.pc
|
||||
COPY pkg-config/ScintillaEdit.pc.example /usr/local/lib/pkgconfig/ScintillaEdit.pc
|
||||
RUN echo 'includedir=/usr/include/x86_64-linux-gnu/qt5/Qsci/' \
|
||||
'\n' \
|
||||
'\nName: QScintilla' \
|
||||
'\nDescription: Qt5 port of the Scintilla source code editing widget' \
|
||||
'\nURL: http://www.riverbankcomputing.co.uk/software/qscintilla' \
|
||||
'\nVersion: 2.13.3' \
|
||||
'\nRequires: Qt5Widgets, Qt5PrintSupport' \
|
||||
'\nLibs: -lqscintilla2_qt5' \
|
||||
'\nCflags: -I${includedir}' \
|
||||
> /usr/local/lib/pkgconfig/QScintilla.pc
|
||||
|
||||
RUN echo 'includedir=/usr/include/x86_64-linux-gnu/qt6/Qsci/' \
|
||||
'\n' \
|
||||
'\nName: QScintilla6' \
|
||||
'\nDescription: Qt6 port of the Scintilla source code editing widget' \
|
||||
'\nURL: http://www.riverbankcomputing.co.uk/software/qscintilla' \
|
||||
'\nVersion: 2.13.3' \
|
||||
'\nRequires: Qt6Widgets, Qt6PrintSupport' \
|
||||
'\nLibs: -lqscintilla2_qt6' \
|
||||
'\nCflags: -I${includedir}' \
|
||||
> /usr/local/lib/pkgconfig/QScintilla6.pc
|
||||
|
||||
RUN echo 'srcdir=/usr/local/src/scintilla/' \
|
||||
'\n' \
|
||||
'\nName: ScintillaEdit' \
|
||||
'\nDescription: Scintilla upstream Qt port' \
|
||||
'\nURL: https://www.scintilla.org/' \
|
||||
'\nVersion: 5.5.2' \
|
||||
'\nRequires: Qt5Widgets' \
|
||||
'\nLibs: -L${srcdir}/bin -lScintillaEdit' \
|
||||
'\nCflags: -include stdint.h -I${srcdir}/qt/ScintillaEdit -I${srcdir}/qt/ScintillaEditBase -I${srcdir}/include -I${srcdir}/src' \
|
||||
> /usr/local/lib/pkgconfig/ScintillaEdit.pc
|
||||
|
||||
#
|
||||
|
||||
ENV GOFLAGS=-buildvcs=false
|
||||
|
@ -1,29 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/mappu/miqt/qt"
|
||||
)
|
||||
|
||||
func myRealMainFunc() {
|
||||
|
||||
qt.NewQApplication(os.Args)
|
||||
|
||||
btn := qt.NewQPushButton3("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!")
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
// +build android
|
||||
|
||||
package main
|
||||
|
||||
import "C" // Required for export support
|
||||
|
||||
//export AndroidMain
|
||||
func AndroidMain() {
|
||||
myRealMainFunc()
|
||||
}
|
||||
|
||||
func main() {
|
||||
// Must be empty
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
// +build !android
|
||||
|
||||
package main
|
||||
|
||||
func main() {
|
||||
myRealMainFunc()
|
||||
}
|
@ -1,29 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
qt "github.com/mappu/miqt/qt6"
|
||||
)
|
||||
|
||||
func myRealMainFunc() {
|
||||
|
||||
qt.NewQApplication(os.Args)
|
||||
|
||||
btn := qt.NewQPushButton3("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!")
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
// +build android
|
||||
|
||||
package main
|
||||
|
||||
import "C" // Required for export support
|
||||
|
||||
//export AndroidMain
|
||||
func AndroidMain() {
|
||||
myRealMainFunc()
|
||||
}
|
||||
|
||||
func main() {
|
||||
// Must be empty
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
// +build !android
|
||||
|
||||
package main
|
||||
|
||||
func main() {
|
||||
myRealMainFunc()
|
||||
}
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 33 KiB |
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 27 KiB |
@ -1,9 +0,0 @@
|
||||
includedir=/usr/include/x86_64-linux-gnu/qt5/Qsci/
|
||||
|
||||
Name: QScintilla
|
||||
Description: Qt5 port of the Scintilla source code editing widget
|
||||
URL: http://www.riverbankcomputing.co.uk/software/qscintilla
|
||||
Version: 2.13.3
|
||||
Requires: Qt5Widgets, Qt5PrintSupport
|
||||
Libs: -lqscintilla2_qt5
|
||||
Cflags: -I${includedir}
|
@ -1,9 +0,0 @@
|
||||
includedir=/usr/include/x86_64-linux-gnu/qt6/Qsci/
|
||||
|
||||
Name: QScintilla6
|
||||
Description: Qt6 port of the Scintilla source code editing widget
|
||||
URL: http://www.riverbankcomputing.co.uk/software/qscintilla
|
||||
Version: 2.13.3
|
||||
Requires: Qt6Widgets, Qt6PrintSupport
|
||||
Libs: -lqscintilla2_qt6
|
||||
Cflags: -I${includedir}
|
@ -1,9 +0,0 @@
|
||||
srcdir=/usr/local/src/scintilla/
|
||||
|
||||
Name: ScintillaEdit
|
||||
Description: Scintilla's own upstream Qt port
|
||||
URL: https://www.scintilla.org/
|
||||
Version: 5.5.2
|
||||
Requires: Qt5Widgets
|
||||
Libs: -L${srcdir}/bin -lScintillaEdit
|
||||
Cflags: -include stdint.h -I${srcdir}/qt/ScintillaEdit -I${srcdir}/qt/ScintillaEditBase -I${srcdir}/include -I${srcdir}/src
|
Loading…
x
Reference in New Issue
Block a user