26 Commits

Author SHA1 Message Date
Joe Julian 1762d36dde gogio: deduplicate Android jars 2026-04-16 21:04:56 -07:00
Joe Julian 7b5a6b418c gogio: load module-root Android jars 2026-04-16 21:03:09 -07:00
Joe Julian f71579e799 gogio: load Android assets from module root 2026-04-16 20:59:20 -07:00
Joe Julian 192acd9d09 Add Android packaging hooks for app resources 2026-04-16 08:42:03 -07:00
inkeliz 2e72e8f0b2 gogio: [android] bump target sdk version
Match the "Target SDK" with the mininum required
by Google Play.

Signed-off-by: inkeliz <inkeliz@inkeliz.com>
2026-02-28 01:10:27 +00:00
CoyAce f4d6788248 gogio: [ios] add CXX, CGO_CXXFLAGS environment variable for cgo cross-compilation and correct signKey
Signed-off-by: CoyAce <AkeyCoy@gmail.com>
2026-02-10 07:34:11 +01:00
CoyAce e5b1a4e6cd gogio: [android] add CXX environment variable for cgo cross-compilation
Adds the CXX environment variable when building Android shared libraries
to ensure proper C++ compiler selection for cgo-enabled packages.

Signed-off-by: CoyAce <AkeyCoy@gmail.com>
2026-01-26 16:24:59 +01:00
CoyAce f587d2f097 gogio: [Android] map the microphone permission to RECORD_AUDIO
Signed-off-by: CoyAce <AkeyCoy@gmail.com>
2026-01-07 12:34:13 +01:00
inkeliz 8de547d61d gogio: [Android] add support for querying apps
Previously, it was impossible to identify if a specific app
was installed on the user device. It was also impossible to
launch a external app from Gio using Intent.

Now, you can use `-queries` with a comma separed package names,
like `com.another.app`. That allows you to launch Intent to
`com.another.app`.

Signed-off-by: inkeliz <inkeliz@inkeliz.com>
2025-12-16 21:53:56 +01:00
inkeliz 048614c60e gogio: [iOS] fix compatibility with Apple Connect and iPad requirement
This pach fixes a total of 6 issues caused by gogio,
when uploading .ipa to Apple Connect/Apple Store.

1. Asset validation failed (90474), caused by not set
"UIInterfaceOrientationPortraitUpside" in plist.

2. Asset validation failed (90482), the executable contains
bitcode. Now, gogio will use "bitcode_strip" to remove such
bitcode.

3. Asset validation failed (90060), the version can only have
three non-negative numbers. Using values from semVer is
invalid (such as 1.2.3.4), it must be either 1.2.3 or
1.2.34. Now, gogio uses the later one.

4. Asset validation failed (90476), supporting multitask on
iPad requires UILaunchScreen. That is tricky to solve, instead
gogio will NOT support multitask on iPad.

5. Asset validation failed (90208), version mismatch between
plist and binary. Now, gogio will use compile flags to set
the version AND will use the proper minSdk on plist.

6. Asset validation failed (90023), missing 152x152 icon for
iPad. Now, gogio will create such icon.

Signed-off-by: inkeliz <inkeliz@inkeliz.com>
Signed-off-by: Elias Naur <mail@eliasnaur.com>
2025-12-16 18:40:23 +01:00
inkeliz ed8d0aa9a6 gogio: [macOS] support custom profile
This patch enables `-signkey` to load provisioning profiles.

Signed-off-by: inkeliz <inkeliz@inkeliz.com>
Signed-off-by: Elias Naur <mail@eliasnaur.com>
2025-12-16 18:04:09 +01:00
inkeliz e1f06eb7b0 gogio: [wasm] fix compatibility with Go 1.23+
Signed-off-by: inkeliz <inkeliz@inkeliz.com>
2025-12-15 22:06:47 +00:00
inkeliz ae8a780af9 gogio: add deeplink support
Add a new flag "-schemes" which links the URL schemes to the app.

Signed-off-by: inkeliz <inkeliz@inkeliz.com>
2025-12-15 22:25:08 +01:00
Chris Waldon 74551d3253 go.*: update to gio v0.9.0
Signed-off-by: Chris Waldon <christopher.waldon.dev@gmail.com>
2025-09-16 08:41:55 -04:00
inkeliz aecb4723c9 gogio: [Android] support 16kB page sizes, required for Android 15+
Previously, Gio crashes on 16KB page-size enable version of
Android 15. Also, Google Play will require 16KB compatible
apps by November 2025.

This patch changes the page-size of C/CGO to 64KB, which is
compatible with 4KB, 16KB and 64KB. That is also the same
value used by Golang Compiler itself.

Signed-off-by: inkeliz <inkeliz@inkeliz.com>
Signed-off-by: Elias Naur <mail@eliasnaur.com>
2025-05-27 10:26:34 +02:00
Elias Naur 3f0ad89ca9 gogio: use ANDROID_HOME instead of the deprecated ANDROID_SDK_ROOT
Signed-off-by: Elias Naur <mail@eliasnaur.com>
2025-05-24 11:20:00 +02:00
Sean E. Russell c108ce0a29 gogio: read -signpass from environment variable if it isn't provided as an argument
Signed-off-by: Elias Naur <mail@eliasnaur.com>
2025-05-22 17:36:52 +02:00
Elias Naur 1b42337ac0 gogio: improve -version parse error message
Add a test while here.

Signed-off-by: Elias Naur <mail@eliasnaur.com>
2025-05-22 17:23:53 +02:00
Admin ae8dd5433d all: cleanup code and upgrade to modern Go facilities
Signed-off-by: ddkwork
2025-05-05 19:53:28 +02:00
Admin ab2d621e47 go.*: bump Go and dependencies
Signed-off-by: ddkwork
2025-05-05 19:53:28 +02:00
Elias Naur 01ffdf788d gogio: disable failing JS test
Signed-off-by: Elias Naur <mail@eliasnaur.com>
2025-03-21 13:07:11 +01:00
Elias Naur 51c6d8037b .builds: upgrade debian image to fix a wine compatibility issue
See https://groups.google.com/g/golang-nuts/c/Msg1USaNaqM

Also disable the Apple builder, it no longer builds.

Signed-off-by: Elias Naur <mail@eliasnaur.com>
2025-03-21 13:07:11 +01:00
Elias Naur d1bccae359 go.mod,.builds: upgrade minimum Go version to 1.23
Remove a redundant package installation line while here.

Signed-off-by: Elias Naur <mail@eliasnaur.com>
2025-03-21 11:00:04 +01:00
Thomas Bruyelle 37612f9112 gogio: fix #633 wasm_exec.js location for go1.24
Signed-off-by: Thomas Bruyelle <thomas.bruyelle@gmail.com>
2025-03-21 10:47:41 +01:00
Chris Waldon 03a1ada8ac go.*: update to gio v0.8.0
Signed-off-by: Chris Waldon <christopher.waldon.dev@gmail.com>
2025-01-14 10:57:56 -05:00
Chris Waldon eb9fbc797e go.*: update to Gio v0.7.1
Signed-off-by: Chris Waldon <christopher.waldon.dev@gmail.com>
2024-07-16 20:39:39 -04:00
23 changed files with 808 additions and 295 deletions
+19 -10
View File
@@ -8,8 +8,10 @@ packages:
- libxml2-dev
- libssl-dev
- libz-dev
- ninja-build # cctools
- llvm-dev # for cctools
- uuid-dev ## for cctools
- uuid-dev # for cctools
- libblocksruntime-dev # for cctools
- libplist-utils # for gogio
sources:
- https://git.sr.ht/~eliasnaur/gio-cmd
@@ -17,6 +19,7 @@ sources:
- https://git.sr.ht/~eliasnaur/giouiorg
- https://github.com/tpoechtrager/cctools-port.git
- https://github.com/tpoechtrager/apple-libtapi.git
- https://github.com/tpoechtrager/apple-libdispatch
- https://github.com/mackyle/xar.git
environment:
APPLE_TOOLCHAIN_ROOT: /home/build/appletools
@@ -24,7 +27,7 @@ environment:
tasks:
- install_go: |
mkdir -p /home/build/sdk
curl -s https://dl.google.com/go/go1.19.8.linux-amd64.tar.gz | tar -C /home/build/sdk -xzf -
curl -s https://dl.google.com/go/go1.24.1.linux-amd64.tar.gz | tar -C /home/build/sdk -xzf -
- prepare_toolchain: |
mkdir -p $APPLE_TOOLCHAIN_ROOT
cd $APPLE_TOOLCHAIN_ROOT
@@ -42,6 +45,11 @@ tasks:
- install_appletoolchain: |
cd giouiorg
go build -o $APPLE_TOOLCHAIN_ROOT/tools ./cmd/appletoolchain
- build_libdispatch: |
cd apple-libdispatch
cmake -G Ninja -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_INSTALL_PREFIX=$APPLE_TOOLCHAIN_ROOT/libdispatch .
ninja
ninja install
- build_xar: |
cd xar/xar
ac_cv_lib_crypto_OpenSSL_add_all_ciphers=yes CC=clang ./autogen.sh --prefix=/usr
@@ -53,15 +61,16 @@ tasks:
./install.sh
- build_cctools: |
cd cctools-port/cctools
./configure --prefix $APPLE_TOOLCHAIN_ROOT/toolchain --with-libtapi=$APPLE_TOOLCHAIN_ROOT/libtapi --target=x86_64-apple-darwin19
./configure --prefix $APPLE_TOOLCHAIN_ROOT/toolchain --with-libtapi=$APPLE_TOOLCHAIN_ROOT/libtapi --with-libdispatch=$APPLE_TOOLCHAIN_ROOT/libdispatch --target=x86_64-apple-darwin19
make install
- install_gogio: |
cd gio-cmd
go install ./gogio
- test_ios_gogio: |
mkdir tmp
cd tmp
go mod init example.com
go get -d gioui.org/example/kitchen
export PATH=/home/build/appletools/bin:$PATH
gogio -target ios -o app.app gioui.org/example/kitchen
# Broken test.
# - test_ios_gogio: |
# mkdir tmp
# cd tmp
# go mod init example.com
# go get -d gioui.org/example/kitchen
# export PATH=/home/build/appletools/bin:$PATH
# gogio -target ios -o app.app gioui.org/example/kitchen
+1 -1
View File
@@ -16,7 +16,7 @@ environment:
tasks:
- install_go: |
mkdir -p /home/build/sdk
curl https://dl.google.com/go/go1.19.8.freebsd-amd64.tar.gz | tar -C /home/build/sdk -xzf -
curl https://dl.google.com/go/go1.24.1.freebsd-amd64.tar.gz | tar -C /home/build/sdk -xzf -
- test_cmd: |
cd gio-cmd
go test ./...
+4 -5
View File
@@ -1,5 +1,5 @@
# SPDX-License-Identifier: Unlicense OR MIT
image: debian/bookworm
image: debian/testing
packages:
- curl
- pkg-config
@@ -24,7 +24,6 @@ packages:
- scrot
- sway
- grim
- wine
- unzip
sources:
- https://git.sr.ht/~eliasnaur/gio-cmd
@@ -39,7 +38,7 @@ secrets:
tasks:
- install_go: |
mkdir -p /home/build/sdk
curl -s https://dl.google.com/go/go1.19.8.linux-amd64.tar.gz | tar -C /home/build/sdk -xzf -
curl -s https://dl.google.com/go/go1.24.1.linux-amd64.tar.gz | tar -C /home/build/sdk -xzf -
- check_gofmt: |
cd gio-cmd
test -z "$(gofmt -s -l .)"
@@ -57,8 +56,8 @@ tasks:
# mirror to github
ssh-keyscan github.com > "$HOME"/.ssh/known_hosts && cd gio-cmd && git push --mirror "$github_mirror" || echo "failed mirroring"
- install_chrome: |
curl -s https://dl.google.com/linux/linux_signing_key.pub | sudo apt-key add -
sudo sh -c 'echo "deb [arch=amd64] https://dl-ssl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list'
sudo curl -o /etc/apt/keyrings/google.pub -s https://dl.google.com/linux/linux_signing_key.pub
sudo sh -c 'echo "deb [arch=amd64 signed-by=/etc/apt/keyrings/google.pub] https://dl-ssl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list'
sudo apt-get -qq update
sudo apt-get -qq install -y google-chrome-stable
- test: |
+1 -1
View File
@@ -10,7 +10,7 @@ environment:
tasks:
- install_go: |
mkdir -p /home/build/sdk
curl https://dl.google.com/go/go1.19.8.src.tar.gz | tar -C /home/build/sdk -xzf -
curl https://dl.google.com/go/go1.24.1.src.tar.gz | tar -C /home/build/sdk -xzf -
cd /home/build/sdk/go/src
./make.bash
- test_cmd: |
+18 -19
View File
@@ -1,29 +1,28 @@
module gioui.org/cmd
go 1.21
go 1.24.1
require (
gioui.org v0.7.0
gioui.org v0.9.0
github.com/akavel/rsrc v0.10.1
github.com/chromedp/cdproto v0.0.0-20191114225735-6626966fbae4
github.com/chromedp/chromedp v0.5.2
golang.org/x/image v0.5.0
golang.org/x/sync v0.1.0
golang.org/x/text v0.9.0
golang.org/x/tools v0.6.0
github.com/chromedp/cdproto v0.0.0-20250429231605-6ed5b53462d4
github.com/chromedp/chromedp v0.13.6
golang.org/x/image v0.26.0
golang.org/x/sync v0.13.0
golang.org/x/text v0.24.0
golang.org/x/tools v0.32.0
)
require (
gioui.org/cpu v0.0.0-20210817075930-8d6a761490d2 // indirect
gioui.org/shader v1.0.8 // indirect
github.com/go-text/typesetting v0.1.1 // indirect
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee // indirect
github.com/gobwas/pool v0.2.0 // indirect
github.com/gobwas/ws v1.0.2 // indirect
github.com/knq/sysutil v0.0.0-20191005231841-15668db23d08 // indirect
github.com/mailru/easyjson v0.7.0 // indirect
golang.org/x/exp v0.0.0-20221012211006-4de253d81b95 // indirect
golang.org/x/exp/shiny v0.0.0-20220827204233-334a2380cb91 // indirect
golang.org/x/mod v0.8.0 // indirect
golang.org/x/sys v0.5.0 // indirect
github.com/chromedp/sysutil v1.1.0 // indirect
github.com/go-json-experiment/json v0.0.0-20250417205406-170dfdcf87d1 // indirect
github.com/go-text/typesetting v0.3.0 // indirect
github.com/gobwas/httphead v0.1.0 // indirect
github.com/gobwas/pool v0.2.1 // indirect
github.com/gobwas/ws v1.4.0 // indirect
github.com/google/go-cmp v0.7.0 // indirect
golang.org/x/exp/shiny v0.0.0-20250408133849-7e4ce0ab07d0 // indirect
golang.org/x/mod v0.24.0 // indirect
golang.org/x/sys v0.33.0 // indirect
)
+43 -63
View File
@@ -1,70 +1,50 @@
eliasnaur.com/font v0.0.0-20230308162249-dd43949cb42d h1:ARo7NCVvN2NdhLlJE9xAbKweuI9L6UgfTbYb0YwPacY=
eliasnaur.com/font v0.0.0-20230308162249-dd43949cb42d/go.mod h1:OYVuxibdk9OSLX8vAqydtRPP87PyTFcT9uH3MlEGBQA=
gioui.org v0.7.0 h1:5I+7Uu2yjTu7W5p7HWQrgsDPO3vex+8T1DsvCLGBfuI=
gioui.org v0.7.0/go.mod h1:19wZxaNP+eHN4H2YdZwEfbkAAgoYB5rcIbDHo4BqUl4=
gioui.org v0.9.0 h1:4u7XZwnb5kzQW91Nz/vR0wKD6LdW9CaVF96r3rfy4kc=
gioui.org v0.9.0/go.mod h1:CjNig0wAhLt9WZxOPAusgFD8x8IRvqt26LdDBa3Jvao=
gioui.org/cpu v0.0.0-20210808092351-bfe733dd3334/go.mod h1:A8M0Cn5o+vY5LTMlnRoK3O5kG+rH0kWfJjeKd9QpBmQ=
gioui.org/cpu v0.0.0-20210817075930-8d6a761490d2 h1:AGDDxsJE1RpcXTAxPG2B4jrwVUJGFDjINIPi1jtO6pc=
gioui.org/cpu v0.0.0-20210817075930-8d6a761490d2/go.mod h1:A8M0Cn5o+vY5LTMlnRoK3O5kG+rH0kWfJjeKd9QpBmQ=
gioui.org/shader v1.0.8 h1:6ks0o/A+b0ne7RzEqRZK5f4Gboz2CfG+mVliciy6+qA=
gioui.org/shader v1.0.8/go.mod h1:mWdiME581d/kV7/iEhLmUgUK5iZ09XR5XpduXzbePVM=
github.com/akavel/rsrc v0.10.1 h1:hCCPImjmFKVNGpeLZyTDRHEFC283DzyTXTo0cO0Rq9o=
github.com/akavel/rsrc v0.10.1/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c=
github.com/chromedp/cdproto v0.0.0-20191114225735-6626966fbae4 h1:QD3KxSJ59L2lxG6MXBjNHxiQO2RmxTQ3XcK+wO44WOg=
github.com/chromedp/cdproto v0.0.0-20191114225735-6626966fbae4/go.mod h1:PfAWWKJqjlGFYJEidUM6aVIWPr0EpobeyVWEEmplX7g=
github.com/chromedp/chromedp v0.5.2 h1:W8xBXQuUnd2dZK0SN/lyVwsQM7KgW+kY5HGnntms194=
github.com/chromedp/chromedp v0.5.2/go.mod h1:rsTo/xRo23KZZwFmWk2Ui79rBaVRRATCjLzNQlOFSiA=
github.com/go-text/typesetting v0.1.1 h1:bGAesCuo85nXnEN5LmFMVGAGpGkCPtHrZLi//qD7EJo=
github.com/go-text/typesetting v0.1.1/go.mod h1:d22AnmeKq/on0HNv73UFriMKc4Ez6EqZAofLhAzpSzI=
github.com/go-text/typesetting-utils v0.0.0-20231211103740-d9332ae51f04 h1:zBx+p/W2aQYtNuyZNcTfinWvXBQwYtDfme051PR/lAY=
github.com/go-text/typesetting-utils v0.0.0-20231211103740-d9332ae51f04/go.mod h1:DDxDdQEnB70R8owOx3LVpEFvpMK9eeH1o2r0yZhFI9o=
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee h1:s+21KNqlpePfkah2I+gwHF8xmJWRjooY+5248k6m4A0=
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo=
github.com/gobwas/pool v0.2.0 h1:QEmUOlnSjWtnpRGHF3SauEiOsy82Cup83Vf2LcMlnc8=
github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
github.com/gobwas/ws v1.0.2 h1:CoAavW/wd/kulfZmSIBt6p24n4j7tHgNVCjsfHVNUbo=
github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
github.com/knq/sysutil v0.0.0-20191005231841-15668db23d08 h1:V0an7KRw92wmJysvFvtqtKMAPmvS5O0jtB0nYo6t+gs=
github.com/knq/sysutil v0.0.0-20191005231841-15668db23d08/go.mod h1:dFWs1zEqDjFtnBXsd1vPOZaLsESovai349994nHx3e0=
github.com/mailru/easyjson v0.7.0 h1:aizVhC/NAAcKWb+5QsU1iNOZb4Yws5UO2I+aIprQITM=
github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/exp v0.0.0-20221012211006-4de253d81b95 h1:sBdrWpxhGDdTAYNqbgBLAR+ULAPPhfgncLr1X0lyWtg=
golang.org/x/exp v0.0.0-20221012211006-4de253d81b95/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=
golang.org/x/exp/shiny v0.0.0-20220827204233-334a2380cb91 h1:ryT6Nf0R83ZgD8WnFFdfI8wCeyqgdXWN4+CkFVNPAT0=
golang.org/x/exp/shiny v0.0.0-20220827204233-334a2380cb91/go.mod h1:VjAR7z0ngyATZTELrBSkxOOHhhlnVUxDye4mcjx5h/8=
golang.org/x/image v0.5.0 h1:5JMiNunQeQw++mMOz48/ISeNu3Iweh/JaZU8ZLqHRrI=
golang.org/x/image v0.5.0/go.mod h1:FVC7BI/5Ym8R25iw5OLsgshdUBbT1h5jZTpA+mvAdZ4=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20191113165036-4c7a9d0fe056/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
github.com/chromedp/cdproto v0.0.0-20250429231605-6ed5b53462d4 h1:UZdrvid2JFwnvPlUSEFlE794XZL4Jmrj8fuxfcLECJE=
github.com/chromedp/cdproto v0.0.0-20250429231605-6ed5b53462d4/go.mod h1:NItd7aLkcfOA/dcMXvl8p1u+lQqioRMq/SqDp71Pb/k=
github.com/chromedp/chromedp v0.13.6 h1:xlNunMyzS5bu3r/QKrb3fzX6ow3WBQ6oao+J65PGZxk=
github.com/chromedp/chromedp v0.13.6/go.mod h1:h8GPP6ZtLMLsU8zFbTcb7ZDGCvCy8j/vRoFmRltQx9A=
github.com/chromedp/sysutil v1.1.0 h1:PUFNv5EcprjqXZD9nJb9b/c9ibAbxiYo4exNWZyipwM=
github.com/chromedp/sysutil v1.1.0/go.mod h1:WiThHUdltqCNKGc4gaU50XgYjwjYIhKWoHGPTUfWTJ8=
github.com/go-json-experiment/json v0.0.0-20250417205406-170dfdcf87d1 h1:+VexzzkMLb1tnvpuQdGT/DicIRW7MN8ozsXqBMgp0Hk=
github.com/go-json-experiment/json v0.0.0-20250417205406-170dfdcf87d1/go.mod h1:TiCD2a1pcmjd7YnhGH0f/zKNcCD06B029pHhzV23c2M=
github.com/go-text/typesetting v0.3.0 h1:OWCgYpp8njoxSRpwrdd1bQOxdjOXDj9Rqart9ML4iF4=
github.com/go-text/typesetting v0.3.0/go.mod h1:qjZLkhRgOEYMhU9eHBr3AR4sfnGJvOXNLt8yRAySFuY=
github.com/go-text/typesetting-utils v0.0.0-20241103174707-87a29e9e6066 h1:qCuYC+94v2xrb1PoS4NIDe7DGYtLnU2wWiQe9a1B1c0=
github.com/go-text/typesetting-utils v0.0.0-20241103174707-87a29e9e6066/go.mod h1:DDxDdQEnB70R8owOx3LVpEFvpMK9eeH1o2r0yZhFI9o=
github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
github.com/gobwas/ws v1.4.0 h1:CTaoG1tojrh4ucGPcoJFiAQUAsEWekEWvLy7GsVNqGs=
github.com/gobwas/ws v1.4.0/go.mod h1:G3gNqMNtPppf5XUz7O4shetPpcZ1VJ7zt18dlUeakrc=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80 h1:6Yzfa6GP0rIo/kULo2bwGEkFvCePZ3qHDDTC3/J9Swo=
github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80/go.mod h1:imJHygn/1yfhB7XSJJKlFZKl/J+dCPAknuiaGOshXAs=
github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde h1:x0TT0RDC7UhAVbbWWBzr41ElhJx5tXPWkIHA2HWPRuw=
github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0=
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 h1:R84qjqJb5nVJMxqWYb3np9L5ZsaDtB+a39EqjV0JSUM=
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0/go.mod h1:S9Xr4PYopiDyqSyp5NjCrhFrqg6A5zA2E/iPHPhqnS8=
golang.org/x/exp/shiny v0.0.0-20250408133849-7e4ce0ab07d0 h1:tMSqXTK+AQdW3LpCbfatHSRPHeW6+2WuxaVQuHftn80=
golang.org/x/exp/shiny v0.0.0-20250408133849-7e4ce0ab07d0/go.mod h1:ygj7T6vSGhhm/9yTpOQQNvuAUFziTH7RUiH74EoE2C8=
golang.org/x/image v0.26.0 h1:4XjIFEZWQmCZi6Wv8BoxsDhRU3RVnLX04dToTDAEPlY=
golang.org/x/image v0.26.0/go.mod h1:lcxbMFAovzpnJxzXS3nyL83K27tmqtKzIJpctK8YO5c=
golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU=
golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610=
golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
golang.org/x/tools v0.32.0 h1:Q7N1vhpkQv7ybVzLFtTjvQya2ewbwNDZzUgfXGqtMWU=
golang.org/x/tools v0.32.0/go.mod h1:ZxrU41P/wAbZD8EDa6dDCa6XfpkhJ7HFMjHJXfBDu8s=
+3 -3
View File
@@ -24,9 +24,9 @@ type AndroidTestDriver struct {
var rxAdbDevice = regexp.MustCompile(`(.*)\s+device$`)
func (d *AndroidTestDriver) Start(path string) {
d.sdkDir = os.Getenv("ANDROID_SDK_ROOT")
d.sdkDir = os.Getenv("ANDROID_HOME")
if d.sdkDir == "" {
d.Skipf("Android SDK is required; set $ANDROID_SDK_ROOT")
d.Skipf("Android SDK is required; set $ANDROID_HOME")
}
d.adbPath = filepath.Join(d.sdkDir, "platform-tools", "adb")
if _, err := os.Stat(d.adbPath); os.IsNotExist(err) {
@@ -121,7 +121,7 @@ func (d *AndroidTestDriver) tryUninstall() {
}
}
func (d *AndroidTestDriver) adb(args ...interface{}) []byte {
func (d *AndroidTestDriver) adb(args ...any) []byte {
strs := []string{}
for _, arg := range args {
strs = append(strs, fmt.Sprint(arg))
+195 -68
View File
@@ -40,14 +40,18 @@ type errWriter struct {
var exeSuffix string
type manifestData struct {
AppID string
Version Semver
MinSDK int
TargetSDK int
Permissions []string
Features []string
IconSnip string
AppName string
AppID string
Version Semver
MinSDK int
TargetSDK int
Permissions []string
Features []string
IconSnip string
AppName string
Schemes []string
PackageQueries []string
ManifestSnip string
AppSnip string
}
const (
@@ -76,9 +80,9 @@ func init() {
}
func buildAndroid(tmpDir string, bi *buildInfo) error {
sdk := os.Getenv("ANDROID_SDK_ROOT")
sdk := os.Getenv("ANDROID_HOME")
if sdk == "" {
return errors.New("please set ANDROID_SDK_ROOT to the Android SDK path")
return errors.New("please set ANDROID_HOME to the Android SDK path")
}
if _, err := os.Stat(sdk); err != nil {
return err
@@ -113,7 +117,9 @@ func buildAndroid(tmpDir string, bi *buildInfo) error {
if err != nil {
return err
}
moduleRoot := moduleRootForDir(bi.pkgDir)
var extraJars []string
seenJars := make(map[string]bool)
visitedPkgs := make(map[string]bool)
var visitPkg func(*packages.Package) error
visitPkg = func(p *packages.Package) error {
@@ -121,11 +127,17 @@ func buildAndroid(tmpDir string, bi *buildInfo) error {
return nil
}
dir := filepath.Dir(p.GoFiles[0])
jars, err := filepath.Glob(filepath.Join(dir, "*.jar"))
jars, err := androidExtraJars(dir, moduleRoot)
if err != nil {
return err
}
extraJars = append(extraJars, jars...)
for _, jar := range jars {
if seenJars[jar] {
continue
}
seenJars[jar] = true
extraJars = append(extraJars, jar)
}
switch {
case p.PkgPath == "net":
perms = append(perms, "network")
@@ -179,9 +191,9 @@ func buildAndroid(tmpDir string, bi *buildInfo) error {
}
func compileAndroid(tmpDir string, tools *androidTools, bi *buildInfo) (err error) {
androidHome := os.Getenv("ANDROID_SDK_ROOT")
androidHome := os.Getenv("ANDROID_HOME")
if androidHome == "" {
return errors.New("ANDROID_SDK_ROOT is not set. Please point it to the root of the Android SDK")
return errors.New("ANDROID_HOME is not set. Please point it to the root of the Android SDK")
}
javac, err := findJavaC()
if err != nil {
@@ -191,10 +203,7 @@ func compileAndroid(tmpDir string, tools *androidTools, bi *buildInfo) (err erro
if err != nil {
return err
}
minSDK := 17
if bi.minsdk > minSDK {
minSDK = bi.minsdk
}
minSDK := max(bi.minsdk, 17)
tcRoot := filepath.Join(ndkRoot, "toolchains", "llvm", "prebuilt", archNDK())
var builds errgroup.Group
for _, a := range bi.archs {
@@ -213,14 +222,14 @@ func compileAndroid(tmpDir string, tools *androidTools, bi *buildInfo) (err erro
}
}
archDir := filepath.Join(tmpDir, "jni", arch.jniArch)
if err := os.MkdirAll(archDir, 0755); err != nil {
if err := os.MkdirAll(archDir, 0o755); err != nil {
return fmt.Errorf("failed to create %q: %v", archDir, err)
}
libFile := filepath.Join(archDir, "libgio.so")
cmd := exec.Command(
"go",
"build",
"-ldflags=-w -s "+bi.ldflags,
"-ldflags=-w -s -extldflags \"-Wl,-z,max-page-size=65536\" "+bi.ldflags,
"-buildmode=c-shared",
"-tags", bi.tags,
"-o", libFile,
@@ -233,6 +242,7 @@ func compileAndroid(tmpDir string, tools *androidTools, bi *buildInfo) (err erro
"GOARM=7", // Avoid softfloat.
"CGO_ENABLED=1",
"CC="+clang,
"CXX="+clang+"++",
)
builds.Go(func() error {
_, err := runCmd(cmd)
@@ -252,7 +262,7 @@ func compileAndroid(tmpDir string, tools *androidTools, bi *buildInfo) (err erro
}
if len(javaFiles) > 0 {
classes := filepath.Join(tmpDir, "classes")
if err := os.MkdirAll(classes, 0755); err != nil {
if err := os.MkdirAll(classes, 0o755); err != nil {
return err
}
javac := exec.Command(
@@ -349,15 +359,12 @@ func exeAndroid(tmpDir string, tools *androidTools, bi *buildInfo, extraJars, pe
})
classFiles = append(classFiles, extraJars...)
dexDir := filepath.Join(tmpDir, "apk")
if err := os.MkdirAll(dexDir, 0755); err != nil {
if err := os.MkdirAll(dexDir, 0o755); err != nil {
return err
}
minSDK := 16
if bi.minsdk > minSDK {
minSDK = bi.minsdk
}
minSDK := max(bi.minsdk, 16)
// https://developer.android.com/distribute/best-practices/develop/target-sdk
targetSDK := 33
targetSDK := 35
if bi.targetsdk > 0 {
targetSDK = bi.targetsdk
}
@@ -387,7 +394,7 @@ func exeAndroid(tmpDir string, tools *androidTools, bi *buildInfo, extraJars, pe
v21Dir := filepath.Join(resDir, "values-v21")
v26mipmapDir := filepath.Join(resDir, `mipmap-anydpi-v26`)
for _, dir := range []string{valDir, v21Dir, v26mipmapDir} {
if err := os.MkdirAll(dir, 0755); err != nil {
if err := os.MkdirAll(dir, 0o755); err != nil {
return err
}
}
@@ -411,20 +418,24 @@ func exeAndroid(tmpDir string, tools *androidTools, bi *buildInfo, extraJars, pe
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@mipmap/ic_launcher_adaptive" />
<foreground android:drawable="@mipmap/ic_launcher_adaptive" />
</adaptive-icon>`), 0660)
</adaptive-icon>`), 0o660)
if err != nil {
return err
}
iconSnip = `android:icon="@mipmap/ic_launcher"`
}
err = os.WriteFile(filepath.Join(valDir, "themes.xml"), []byte(themes), 0660)
err = os.WriteFile(filepath.Join(valDir, "themes.xml"), []byte(themes), 0o660)
if err != nil {
return err
}
err = os.WriteFile(filepath.Join(v21Dir, "themes.xml"), []byte(themesV21), 0660)
err = os.WriteFile(filepath.Join(v21Dir, "themes.xml"), []byte(themesV21), 0o660)
if err != nil {
return err
}
moduleRoot := moduleRootForDir(bi.pkgDir)
if err := copyTree(filepath.Join(moduleRoot, "android", "res"), resDir); err != nil {
return err
}
resZip := filepath.Join(tmpDir, "resources.zip")
aapt2 := filepath.Join(tools.buildtools, "aapt2")
_, err = runCmd(exec.Command(
@@ -440,44 +451,25 @@ func exeAndroid(tmpDir string, tools *androidTools, bi *buildInfo, extraJars, pe
permissions, features := getPermissions(perms)
appName := UppercaseName(bi.name)
manifestSrc := manifestData{
AppID: bi.appID,
Version: bi.version,
MinSDK: minSDK,
TargetSDK: targetSDK,
Permissions: permissions,
Features: features,
IconSnip: iconSnip,
AppName: appName,
AppID: bi.appID,
Version: bi.version,
MinSDK: minSDK,
TargetSDK: targetSDK,
Permissions: permissions,
Features: features,
IconSnip: iconSnip,
AppName: appName,
Schemes: bi.schemes,
PackageQueries: bi.packageQueries,
ManifestSnip: readOptionalText(filepath.Join(moduleRoot, "android", "manifest_snippets.xml")),
AppSnip: readOptionalText(filepath.Join(moduleRoot, "android", "application_snippets.xml")),
}
tmpl, err := template.New("test").Parse(
`<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="{{.AppID}}"
android:versionCode="{{.Version.VersionCode}}"
android:versionName="{{.Version}}">
<uses-sdk android:minSdkVersion="{{.MinSDK}}" android:targetSdkVersion="{{.TargetSDK}}" />
{{range .Permissions}} <uses-permission android:name="{{.}}"/>
{{end}}{{range .Features}} <uses-feature android:{{.}} android:required="false"/>
{{end}} <application {{.IconSnip}} android:label="{{.AppName}}">
<activity android:name="org.gioui.GioActivity"
android:label="{{.AppName}}"
android:theme="@style/Theme.GioApp"
android:configChanges="screenSize|screenLayout|smallestScreenSize|orientation|keyboardHidden"
android:windowSoftInputMode="adjustResize"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>`)
var manifestBuffer bytes.Buffer
if err := tmpl.Execute(&manifestBuffer, manifestSrc); err != nil {
manifestBuffer, err := renderAndroidManifest(manifestSrc)
if err != nil {
return err
}
manifest := filepath.Join(tmpDir, "AndroidManifest.xml")
if err := os.WriteFile(manifest, manifestBuffer.Bytes(), 0660); err != nil {
if err := os.WriteFile(manifest, manifestBuffer, 0o660); err != nil {
return err
}
@@ -679,7 +671,7 @@ func signAAB(tmpDir string, aabFile string, tools *androidTools, bi *buildInfo)
}
var alias string
for _, t := range strings.Split(keytoolList, "\n") {
for t := range strings.SplitSeq(keytoolList, "\n") {
if i, _ := fmt.Sscanf(t, "Alias name: %s", &alias); i > 0 {
break
}
@@ -742,6 +734,141 @@ func defaultAndroidKeystore(tmpDir string, bi *buildInfo) error {
return err
}
func renderAndroidManifest(data manifestData) ([]byte, error) {
tmpl, err := template.New("test").Parse(
`<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="{{.AppID}}"
android:versionCode="{{.Version.VersionCode}}"
android:versionName="{{.Version}}">
{{if .PackageQueries}}
<queries>
{{range .PackageQueries}}
<package android:name="{{.}}" />
{{end}}
</queries>
{{end}}
<uses-sdk android:minSdkVersion="{{.MinSDK}}" android:targetSdkVersion="{{.TargetSDK}}" />
{{range .Permissions}} <uses-permission android:name="{{.}}"/>
{{end}}{{range .Features}} <uses-feature android:{{.}} android:required="false"/>
{{end}}{{.ManifestSnip}} <application {{.IconSnip}} android:label="{{.AppName}}">
{{.AppSnip}}
<activity android:name="org.gioui.GioActivity"
android:label="{{.AppName}}"
android:theme="@style/Theme.GioApp"
android:configChanges="screenSize|screenLayout|smallestScreenSize|orientation|keyboardHidden"
android:windowSoftInputMode="adjustResize"
android:launchMode="singleInstance"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
{{range .Schemes}}
<intent-filter>
<action android:name="android.intent.action.VIEW"></action>
<category android:name="android.intent.category.DEFAULT"></category>
<category android:name="android.intent.category.BROWSABLE"></category>
<data android:scheme="{{.}}"></data>
</intent-filter>
{{end}}
</activity>
</application>
</manifest>`)
if err != nil {
return nil, err
}
var manifestBuffer bytes.Buffer
if err := tmpl.Execute(&manifestBuffer, data); err != nil {
return nil, err
}
return manifestBuffer.Bytes(), nil
}
func readOptionalText(path string) string {
data, err := os.ReadFile(path)
if err != nil {
return ""
}
if len(data) == 0 {
return ""
}
return "\n" + string(data) + "\n"
}
func copyTree(src, dst string) error {
info, err := os.Stat(src)
if err != nil {
if os.IsNotExist(err) {
return nil
}
return err
}
if !info.IsDir() {
return fmt.Errorf("extra Android resources path is not a directory: %s", src)
}
return filepath.Walk(src, func(path string, entry os.FileInfo, walkErr error) error {
if walkErr != nil {
return walkErr
}
rel, err := filepath.Rel(src, path)
if err != nil {
return err
}
if rel == "." {
return nil
}
target := filepath.Join(dst, rel)
if entry.IsDir() {
return os.MkdirAll(target, 0o755)
}
if err := os.MkdirAll(filepath.Dir(target), 0o755); err != nil {
return err
}
data, err := os.ReadFile(path)
if err != nil {
return err
}
return os.WriteFile(target, data, 0o660)
})
}
func androidExtraJars(dir, moduleRoot string) ([]string, error) {
var jars []string
patterns := []string{
filepath.Join(dir, "*.jar"),
filepath.Join(dir, "android", "*.jar"),
}
if moduleRoot != "" && moduleRoot != dir {
patterns = append(patterns,
filepath.Join(moduleRoot, "*.jar"),
filepath.Join(moduleRoot, "android", "*.jar"),
)
}
for _, pattern := range patterns {
matches, err := filepath.Glob(pattern)
if err != nil {
return nil, err
}
jars = append(jars, matches...)
}
return jars, nil
}
func moduleRootForDir(dir string) string {
current := dir
for {
if _, err := os.Stat(filepath.Join(current, "go.mod")); err == nil {
return current
}
parent := filepath.Dir(current)
if parent == current {
return dir
}
current = parent
}
}
func findNDK(androidHome string) (string, error) {
ndks, err := filepath.Glob(filepath.Join(androidHome, "ndk", "*"))
if err != nil {
@@ -750,7 +877,7 @@ func findNDK(androidHome string) (string, error) {
if bestNDK, found := latestVersionPath(ndks); found {
return bestNDK, nil
}
// The old NDK path was $ANDROID_SDK_ROOT/ndk-bundle.
// The old NDK path was $ANDROID_HOME/ndk-bundle.
ndkBundle := filepath.Join(androidHome, "ndk-bundle")
if _, err := os.Stat(ndkBundle); err == nil {
return ndkBundle, nil
@@ -763,7 +890,7 @@ func findNDK(androidHome string) (string, error) {
}
}
return "", fmt.Errorf("no NDK found in $ANDROID_SDK_ROOT (%s). Set $ANDROID_NDK_ROOT or use `sdkmanager ndk-bundle` to install the NDK", androidHome)
return "", fmt.Errorf("no NDK found in $ANDROID_HOME (%s). Set $ANDROID_NDK_ROOT or use `sdkmanager ndk-bundle` to install the NDK", androidHome)
}
func findKeytool() (string, error) {
+193
View File
@@ -0,0 +1,193 @@
// SPDX-License-Identifier: Unlicense OR MIT
package main
import (
"os"
"path/filepath"
"strings"
"testing"
)
func TestAndroidExtraJarsIncludesAndroidSubdirectory(t *testing.T) {
t.Parallel()
dir := t.TempDir()
rootJar := filepath.Join(dir, "crew.jar")
androidJar := filepath.Join(dir, "android", "vault.jar")
writeTestFile(t, rootJar, "root")
writeTestFile(t, androidJar, "android")
got, err := androidExtraJars(dir, dir)
if err != nil {
t.Fatalf("androidExtraJars() error = %v", err)
}
want := []string{rootJar, androidJar}
for _, jar := range want {
if !containsString(got, jar) {
t.Fatalf("androidExtraJars() = %v, want %q included", got, jar)
}
}
}
func TestAndroidExtraJarsIncludesModuleRootAndroidSubdirectory(t *testing.T) {
t.Parallel()
root := t.TempDir()
dir := filepath.Join(root, "cmd", "keepassgo")
if err := os.MkdirAll(dir, 0o755); err != nil {
t.Fatalf("MkdirAll(%q) error = %v", dir, err)
}
moduleAndroidJar := filepath.Join(root, "android", "keepassgo-android.jar")
writeTestFile(t, moduleAndroidJar, "module-android")
got, err := androidExtraJars(dir, root)
if err != nil {
t.Fatalf("androidExtraJars() error = %v", err)
}
if !containsString(got, moduleAndroidJar) {
t.Fatalf("androidExtraJars() = %v, want %q included", got, moduleAndroidJar)
}
}
func TestAndroidExtraJarsDoesNotRepeatSharedModuleJarWhenCollectedAcrossPackages(t *testing.T) {
t.Parallel()
root := t.TempDir()
moduleAndroidJar := filepath.Join(root, "android", "keepassgo-android.jar")
writeTestFile(t, filepath.Join(root, "go.mod"), "module example.invalid/crew\n")
writeTestFile(t, moduleAndroidJar, "module-android")
dirs := []string{
filepath.Join(root, "cmd", "keepassgo"),
filepath.Join(root, "internal", "appui"),
}
seen := make(map[string]bool)
var collected []string
for _, dir := range dirs {
if err := os.MkdirAll(dir, 0o755); err != nil {
t.Fatalf("MkdirAll(%q) error = %v", dir, err)
}
jars, err := androidExtraJars(dir, root)
if err != nil {
t.Fatalf("androidExtraJars(%q) error = %v", dir, err)
}
for _, jar := range jars {
if seen[jar] {
continue
}
seen[jar] = true
collected = append(collected, jar)
}
}
count := 0
for _, jar := range collected {
if jar == moduleAndroidJar {
count++
}
}
if count != 1 {
t.Fatalf("collected module jar count = %d, want 1 in %v", count, collected)
}
}
func TestRenderAndroidManifestIncludesOptionalSnippets(t *testing.T) {
t.Parallel()
manifest, err := renderAndroidManifest(manifestData{
AppID: "org.example.heist",
Version: Semver{Major: 1, Minor: 2, Patch: 3, VersionCode: 4},
MinSDK: 28,
TargetSDK: 35,
Permissions: []string{"android.permission.INTERNET"},
Features: []string{`name="android.hardware.fingerprint"`},
IconSnip: `android:icon="@mipmap/ic_launcher"`,
AppName: "Bellagio Crew",
ManifestSnip: "\n\t<uses-permission android:name=\"android.permission.POST_NOTIFICATIONS\" />\n",
AppSnip: "\n\t\t<service android:name=\".CrewService\" />\n",
})
if err != nil {
t.Fatalf("renderAndroidManifest() error = %v", err)
}
got := string(manifest)
for _, want := range []string{
`android.permission.POST_NOTIFICATIONS`,
`<service android:name=".CrewService" />`,
`android.permission.INTERNET`,
`android.hardware.fingerprint`,
} {
if !strings.Contains(got, want) {
t.Fatalf("renderAndroidManifest() missing %q in %s", want, got)
}
}
}
func TestCopyTreeCopiesNestedResources(t *testing.T) {
t.Parallel()
src := filepath.Join(t.TempDir(), "android", "res")
dst := filepath.Join(t.TempDir(), "merged")
resourcePath := filepath.Join(src, "xml", "heist_service.xml")
writeTestFile(t, resourcePath, "<service />")
if err := copyTree(src, dst); err != nil {
t.Fatalf("copyTree() error = %v", err)
}
got, err := os.ReadFile(filepath.Join(dst, "xml", "heist_service.xml"))
if err != nil {
t.Fatalf("ReadFile() error = %v", err)
}
if string(got) != "<service />" {
t.Fatalf("copyTree() copied %q, want %q", string(got), "<service />")
}
}
func TestModuleRootForDirFindsOwningModule(t *testing.T) {
t.Parallel()
root := t.TempDir()
writeTestFile(t, filepath.Join(root, "go.mod"), "module example.invalid/crew\n")
dir := filepath.Join(root, "cmd", "keepassgo")
if err := os.MkdirAll(dir, 0o755); err != nil {
t.Fatalf("MkdirAll(%q) error = %v", dir, err)
}
if got := moduleRootForDir(dir); got != root {
t.Fatalf("moduleRootForDir(%q) = %q, want %q", dir, got, root)
}
}
func TestModuleRootForDirFallsBackToInputDir(t *testing.T) {
t.Parallel()
dir := filepath.Join(t.TempDir(), "cmd", "keepassgo")
if err := os.MkdirAll(dir, 0o755); err != nil {
t.Fatalf("MkdirAll(%q) error = %v", dir, err)
}
if got := moduleRootForDir(dir); got != dir {
t.Fatalf("moduleRootForDir(%q) = %q, want %q", dir, got, dir)
}
}
func writeTestFile(t *testing.T, path, contents string) {
t.Helper()
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
t.Fatalf("MkdirAll(%q) error = %v", path, err)
}
if err := os.WriteFile(path, []byte(contents), 0o644); err != nil {
t.Fatalf("WriteFile(%q) error = %v", path, err)
}
}
func containsString(values []string, want string) bool {
for _, value := range values {
if value == want {
return true
}
}
return false
}
+26 -7
View File
@@ -31,6 +31,8 @@ type buildInfo struct {
notaryAppleID string
notaryPassword string
notaryTeamID string
schemes []string
packageQueries []string
}
type Semver struct {
@@ -56,6 +58,10 @@ func newBuildInfo(pkgPath string) (*buildInfo, error) {
if err != nil {
return nil, err
}
sp := *signPass
if sp == "" {
sp = os.Getenv("GOGIO_SIGNPASS")
}
bi := &buildInfo{
appID: appID,
archs: getArchs(),
@@ -70,10 +76,12 @@ func newBuildInfo(pkgPath string) (*buildInfo, error) {
target: *target,
version: ver,
key: *signKey,
password: *signPass,
password: sp,
notaryAppleID: *notaryID,
notaryPassword: *notaryPass,
notaryTeamID: *notaryTeamID,
schemes: getCommaList(*schemes),
packageQueries: getCommaList(*pkgQueries),
}
return bi, nil
}
@@ -88,14 +96,16 @@ func (s Semver) String() string {
return fmt.Sprintf("%d.%d.%d.%d", s.Major, s.Minor, s.Patch, s.VersionCode)
}
func (s Semver) StringCompact() string {
// Used to meet CFBundleShortVersionString format.
return fmt.Sprintf("%d.%d.%d", s.Major, s.Minor, s.Patch)
}
func parseSemver(v string) (Semver, error) {
var sv Semver
_, err := fmt.Sscanf(v, "%d.%d.%d.%d", &sv.Major, &sv.Minor, &sv.Patch, &sv.VersionCode)
if err != nil {
return Semver{}, fmt.Errorf("invalid semver: %q", v)
}
if sv.String() != v {
return Semver{}, fmt.Errorf("invalid semver: %q", v)
if err != nil || sv.String() != v {
return Semver{}, fmt.Errorf("invalid semver: %q (must match major.minor.patch.versioncode)", v)
}
return sv, nil
}
@@ -146,6 +156,15 @@ func getLdFlags(appID string) string {
return strings.Join(ldflags, " ")
}
func getCommaList(s string) (list []string) {
for _, v := range strings.Split(s, ",") {
if v := strings.TrimSpace(v); v != "" {
list = append(list, v)
}
}
return list
}
type packageMetadata struct {
PkgPath string
Dir string
@@ -180,7 +199,7 @@ func getAppID(pkgMetadata *packageMetadata) string {
name = "." + domain[0]
domain[0] = "localhost"
} else {
for i := 0; i < len(domain)/2; i++ {
for i := range len(domain) / 2 {
opp := len(domain) - 1 - i
domain[i], domain[opp] = domain[opp], domain[i]
}
+32 -5
View File
@@ -2,14 +2,12 @@ package main
import "testing"
type expval struct {
in, out string
}
func TestAppID(t *testing.T) {
t.Parallel()
tests := []expval{
tests := []struct {
in, out string
}{
{"example", "localhost.example"},
{"example.com", "com.example"},
{"www.example.com", "com.example.www"},
@@ -30,3 +28,32 @@ func TestAppID(t *testing.T) {
}
}
}
func TestVersion(t *testing.T) {
t.Parallel()
tests := []struct {
version string
valid bool
}{
{"v1", false},
{"v10.21.333.12", false},
{"1.2.3", false},
{"1.2.3.4", true},
}
for i, test := range tests {
ver, err := parseSemver(test.version)
if err != nil {
if test.valid {
t.Errorf("(%d): %q failed to parse: %v", i, test.version, err)
}
continue
} else if !test.valid {
t.Errorf("(%d): %q was unexpectedly accepted", i, test.version)
}
if got := ver.String(); got != test.version {
t.Errorf("(%d): %q parsed to %q", i, test.version, got)
}
}
}
+15 -3
View File
@@ -47,8 +47,9 @@ The -appid flag specifies the package name for Android or the bundle id for
iOS and tvOS. A bundle id must be provisioned through Xcode before the gogio
tool can use it.
The -version flag specifies the integer version code for Android and the last
component of the 1.0.X version for iOS and tvOS.
The -version flag specifies the semantic version for -buildmode exe. It must
be on the form major.minor.patch.versioncode where the version code is used for
the integer version number for Android, iOS and tvOS.
For Android builds the -minsdk flag specify the minimum SDK level. For example,
use -minsdk 22 to target Android 5.1 (Lollipop) and later.
@@ -68,9 +69,11 @@ its deletion.
The -x flag will print all the external commands executed by the gogio tool.
The -signkey flag specifies the path of the keystore, used for signing Android apk/aab files
or specifies the name of key on Keychain to sign MacOS app.
or specifies the name of key on Keychain to sign MacOS apps. On iOS and macOS it can be used
to specify the path of a provisioning profile (.mobileprovision/.provisionprofile).
The -signpass flag specifies the password of the keystore, ignored if -signkey is not provided.
If -signpass is not sepecified it will be read from the environment variable GOGIO_SIGNPASS.
The -notaryid flag specifies the Apple ID to use for notarization of MacOS app.
@@ -80,4 +83,13 @@ for details. If not provided, the password will be prompted.
The -notaryteamid flag specifies the team ID to use for notarization of MacOS app, ignored if
-notaryid is not provided.
The -schemes flag specifies a list of comma separated URI schemes that the program can
handle. For example, use -schemes yourAppName to receive a app.URLEvent for URIs
starting with yourAppName://. It is only supported on Android, iOS, macOS and Windows.
On Windows, it will restrict the program to a single instance.
The -queries flag specifies a list of comma separated package names used to query other apps,
that is useful to launch other apps and verify their presence. For example, use -queries
com.example.otherapp to query the app with that package name. It is only necessary on Android.
`
+19 -15
View File
@@ -20,8 +20,8 @@ import (
"gioui.org/app"
"gioui.org/gpu"
"gioui.org/io/event"
"gioui.org/io/pointer"
"gioui.org/io/system"
"gioui.org/layout"
"gioui.org/op"
"gioui.org/op/clip"
@@ -98,7 +98,8 @@ type eglContext struct {
func main() {
go func() {
// Set CustomRenderer so we can provide our own rendering context.
w := app.NewWindow(app.CustomRenderer(true))
w := new(app.Window)
w.Option(app.CustomRenderer(true))
if err := loop(w); err != nil {
log.Fatal(err)
}
@@ -164,17 +165,17 @@ func loop(w *app.Window) error {
// eglMakeCurrent binds a context to an operating system thread. Prevent Go from switching thread.
runtime.LockOSThread()
for e := range w.Events() {
switch e := e.(type) {
for {
switch e := w.Event().(type) {
case app.ViewEvent:
ve = e
init = true
if size != (image.Point{}) {
recreateContext()
}
case system.DestroyEvent:
case app.DestroyEvent:
return e.Err
case system.FrameEvent:
case app.FrameEvent:
if init && size != e.Size {
size = e.Size
recreateContext()
@@ -183,7 +184,7 @@ func loop(w *app.Window) error {
break
}
// Build ops.
gtx := layout.NewContext(&ops, e)
gtx := app.NewContext(&ops, e)
// Clear background to white, even on embedded platforms such as webassembly.
paint.Fill(gtx.Ops, color.NRGBA{A: 0xff, R: 0xff, G: 0xff, B: 0xff})
@@ -205,7 +206,7 @@ func loop(w *app.Window) error {
)
}),
)
op.InvalidateOp{}.Add(gtx.Ops)
gtx.Execute(op.InvalidateCmd{})
log.Println("frame")
// Trigger window resize detection in ANGLE.
@@ -351,13 +352,16 @@ func (w *quarterWidget) Layout(gtx layout.Context) layout.Dimensions {
defer clip.Rect(image.Rectangle{
Max: image.Pt(gtx.Constraints.Max.X, gtx.Constraints.Max.Y),
}).Push(gtx.Ops).Pop()
pointer.InputOp{
Tag: w,
Types: pointer.Press,
}.Add(gtx.Ops)
for _, e := range gtx.Events(w) {
if e, ok := e.(pointer.Event); ok && e.Type == pointer.Press {
event.Op(gtx.Ops, w)
for {
e, ok := gtx.Event(pointer.Filter{
Target: w,
Kinds: pointer.Press,
})
if !ok {
break
}
if e, ok := e.(pointer.Event); ok && e.Kind == pointer.Press {
w.clicked = !w.clicked
// notify when we're done updating the frame.
notify = notifyInvalidate
+160 -57
View File
@@ -4,6 +4,7 @@ package main
import (
"archive/zip"
"bytes"
"crypto/sha1"
"encoding/hex"
"errors"
@@ -12,8 +13,10 @@ import (
"os"
"os/exec"
"path/filepath"
"slices"
"strconv"
"strings"
"text/template"
"time"
"golang.org/x/sync/errgroup"
@@ -56,7 +59,7 @@ func buildIOS(tmpDir, target string, bi *buildInfo) error {
}
}
bi.archs = append(bi.archs[:i], bi.archs[i+1:]...)
bi.archs = slices.Delete(bi.archs, i, i+1)
}
if !forDevice && !strings.HasSuffix(out, ".app") {
return fmt.Errorf("the specified output directory %q does not end in .app or .ipa", out)
@@ -66,13 +69,35 @@ func buildIOS(tmpDir, target string, bi *buildInfo) error {
}
payload := filepath.Join(tmpDir, "Payload")
appDir := filepath.Join(payload, appName+".app")
if err := os.MkdirAll(appDir, 0755); err != nil {
if err := os.MkdirAll(appDir, 0o755); err != nil {
return err
}
if err := exeIOS(tmpDir, target, appDir, bi); err != nil {
return err
}
if err := signIOS(bi, tmpDir, appDir); err != nil {
embedded := filepath.Join(appDir, "embedded.mobileprovision")
var provisions []string
if bi.key != "" {
if ext := filepath.Ext(bi.key); ext != ".mobileprovision" && ext != ".provisionprofile" {
return fmt.Errorf("sign: -signkey specifies an Apple provisioning profile, but %q does not end in .mobileprovision or .provisionprofile", bi.key)
}
provisions = []string{bi.key}
} else {
home, err := os.UserHomeDir()
if err != nil {
return err
}
p, err := filepath.Glob(filepath.Join(home, "Library", "MobileDevice", "Provisioning Profiles", "*.mobileprovision"))
if err != nil {
return err
}
provisions = p
}
if err := signApple(bi.appID, tmpDir, embedded, appDir, provisions); err != nil {
return err
}
return zipDir(out, tmpDir, "Payload")
@@ -81,16 +106,8 @@ func buildIOS(tmpDir, target string, bi *buildInfo) error {
}
}
func signIOS(bi *buildInfo, tmpDir, app string) error {
home, err := os.UserHomeDir()
if err != nil {
return err
}
provPattern := filepath.Join(home, "Library", "MobileDevice", "Provisioning Profiles", "*.mobileprovision")
provisions, err := filepath.Glob(provPattern)
if err != nil {
return err
}
// signApple is shared between iOS and macOS.
func signApple(appID, tmpDir, embedded, app string, provisions []string) error {
provInfo := filepath.Join(tmpDir, "provision.plist")
var avail []string
for _, prov := range provisions {
@@ -114,17 +131,23 @@ func signIOS(bi *buildInfo, tmpDir, app string) error {
if err != nil {
return err
}
provAppID, err := runCmd(exec.Command("/usr/libexec/PlistBuddy", "-c", "Print:Entitlements:application-identifier", provInfo))
// iOS/macOS Catalyst
provAppIDSearchKey := "Print:Entitlements:application-identifier"
if filepath.Ext(prov) == ".provisionprofile" {
// macOS
provAppIDSearchKey = "Print:Entitlements:com.apple.application-identifier"
}
provAppID, err := runCmd(exec.Command("/usr/libexec/PlistBuddy", "-c", provAppIDSearchKey, provInfo))
if err != nil {
return err
}
expAppID := fmt.Sprintf("%s.%s", appIDPrefix, bi.appID)
expAppID := fmt.Sprintf("%s.%s", appIDPrefix, appID)
avail = append(avail, provAppID)
if expAppID != provAppID {
continue
}
// Copy provisioning file.
embedded := filepath.Join(app, "embedded.mobileprovision")
if err := copyFile(embedded, prov); err != nil {
return err
}
@@ -139,15 +162,23 @@ func signIOS(bi *buildInfo, tmpDir, app string) error {
return err
}
entFile := filepath.Join(tmpDir, "entitlements.plist")
if err := os.WriteFile(entFile, []byte(entitlements), 0660); err != nil {
if err := os.WriteFile(entFile, []byte(entitlements), 0o660); err != nil {
return err
}
identity := sha1.Sum(certDER)
idHex := hex.EncodeToString(identity[:])
_, err = runCmd(exec.Command("codesign", "-s", idHex, "-v", "--entitlements", entFile, app))
_, err = runCmd(exec.Command(
"codesign",
"--sign", idHex,
"--deep",
"--force",
"--options", "runtime",
"--entitlements",
entFile,
app))
return err
}
return fmt.Errorf("sign: no valid provisioning profile found for bundle id %q among %v", bi.appID, avail)
return fmt.Errorf("sign: no valid provisioning profile found for bundle id %q among %v", appID, avail)
}
func exeIOS(tmpDir, target, app string, bi *buildInfo) error {
@@ -157,7 +188,7 @@ func exeIOS(tmpDir, target, app string, bi *buildInfo) error {
if err := os.RemoveAll(app); err != nil {
return err
}
if err := os.Mkdir(app, 0755); err != nil {
if err := os.Mkdir(app, 0o755); err != nil {
return err
}
appName := UppercaseName(bi.name)
@@ -171,6 +202,7 @@ func exeIOS(tmpDir, target, app string, bi *buildInfo) error {
}
cflags = append(cflags,
"-fobjc-arc",
fmt.Sprintf("-miphoneos-version-min=%d.0", bi.minsdk),
)
cflagsLine := strings.Join(cflags, " ")
exeSlice := filepath.Join(tmpDir, "app-"+a)
@@ -189,7 +221,9 @@ func exeIOS(tmpDir, target, app string, bi *buildInfo) error {
"GOARCH="+a,
"CGO_ENABLED=1",
"CC="+clang,
"CXX="+clang+"++",
"CGO_CFLAGS="+cflagsLine,
"CGO_CXXFLAGS="+cflagsLine,
"CGO_LDFLAGS=-lresolv "+cflagsLine,
)
builds.Go(func() error {
@@ -205,7 +239,7 @@ func exeIOS(tmpDir, target, app string, bi *buildInfo) error {
}
infoPlist := buildInfoPlist(bi)
plistFile := filepath.Join(app, "Info.plist")
if err := os.WriteFile(plistFile, []byte(infoPlist), 0660); err != nil {
if err := os.WriteFile(plistFile, []byte(infoPlist), 0o660); err != nil {
return err
}
if _, err := os.Stat(bi.iconPath); err == nil {
@@ -233,13 +267,16 @@ func exeIOS(tmpDir, target, app string, bi *buildInfo) error {
// iosIcons returns the asset plist file to be merged into Info.plist.
func iosIcons(bi *buildInfo, tmpDir, appDir, icon string) (string, error) {
assets := filepath.Join(tmpDir, "Assets.xcassets")
if err := os.Mkdir(assets, 0700); err != nil {
if err := os.Mkdir(assets, 0o700); err != nil {
return "", err
}
appIcon := filepath.Join(assets, "AppIcon.appiconset")
err := buildIcons(appIcon, icon, []iconVariant{
{path: "ios_2x.png", size: 120},
{path: "ios_3x.png", size: 180},
{path: "ipad_1x.png", size: 76},
{path: "ipad_2x.png", size: 152},
{path: "ipad_4x.png", size: 228},
// The App Store icon is not allowed to contain
// transparent pixels.
{path: "ios_store.png", size: 1024, fill: true},
@@ -248,29 +285,47 @@ func iosIcons(bi *buildInfo, tmpDir, appDir, icon string) (string, error) {
return "", err
}
contentJson := `{
"images" : [
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "ios_2x.png",
"scale" : "2x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "ios_3x.png",
"scale" : "3x"
},
{
"size" : "1024x1024",
"idiom" : "ios-marketing",
"filename" : "ios_store.png",
"scale" : "1x"
}
]
"images": [
{
"size": "60x60",
"idiom": "iphone",
"filename": "ios_2x.png",
"scale": "2x"
},
{
"size": "60x60",
"idiom": "iphone",
"filename": "ios_3x.png",
"scale": "3x"
},
{
"size": "76x76",
"idiom": "ipad",
"filename": "ipad_1x.png",
"scale": "1x"
},
{
"size": "76x76",
"idiom": "ipad",
"filename": "ipad_2x.png",
"scale": "2x"
},
{
"size": "152x152",
"idiom": "ipad",
"filename": "ipad_4x.png",
"scale": "2x"
},
{
"size": "1024x1024",
"idiom": "ios-marketing",
"filename": "ios_store.png",
"scale": "1x"
}
]
}`
contentFile := filepath.Join(appIcon, "Contents.json")
if err := os.WriteFile(contentFile, []byte(contentJson), 0600); err != nil {
if err := os.WriteFile(contentFile, []byte(contentJson), 0o600); err != nil {
return "", err
}
assetPlist := filepath.Join(tmpDir, "assets.plist")
@@ -301,36 +356,57 @@ func buildInfoPlist(bi *buildInfo) string {
case "tvos":
supportPlatform = "AppleTVOS"
}
return fmt.Sprintf(`<?xml version="1.0" encoding="UTF-8"?>
manifestSrc := struct {
AppName string
AppID string
Version string
VersionCode uint32
Platform string
MinVersion int
SupportPlatform string
Schemes []string
}{
AppName: appName,
AppID: bi.appID,
Version: bi.version.StringCompact(),
VersionCode: bi.version.VersionCode,
Platform: platform,
MinVersion: bi.minsdk,
SupportPlatform: supportPlatform,
Schemes: bi.schemes,
}
tmpl, err := template.New("manifest").Parse(`<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>%s</string>
<string>{{.AppName}}</string>
<key>CFBundleIdentifier</key>
<string>%s</string>
<string>{{.AppID}}</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>%s</string>
<string>{{.AppName}}</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>%s</string>
<string>{{.Version}}</string>
<key>CFBundleVersion</key>
<string>%d</string>
<string>{{.VersionCode}}</string>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIRequiredDeviceCapabilities</key>
<array><string>arm64</string></array>
<key>DTPlatformName</key>
<string>%s</string>
<string>{{.Platform}}</string>
<key>DTPlatformVersion</key>
<string>12.4</string>
<key>MinimumOSVersion</key>
<string>%d</string>
<string>{{.MinVersion}}.0</string>
<key>UIDeviceFamily</key>
<array>
<integer>1</integer>
@@ -338,14 +414,17 @@ func buildInfoPlist(bi *buildInfo) string {
</array>
<key>CFBundleSupportedPlatforms</key>
<array>
<string>%s</string>
<string>{{.SupportPlatform}}</string>
</array>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UIRequiresFullScreen</key>
<true/>
<key>DTCompiler</key>
<string>com.apple.compilers.llvm.clang.1_0</string>
<key>DTPlatformBuild</key>
@@ -353,13 +432,38 @@ func buildInfoPlist(bi *buildInfo) string {
<key>DTSDKBuild</key>
<string>16G73</string>
<key>DTSDKName</key>
<string>%s12.4</string>
<string>{{.Platform}}12.4</string>
<key>DTXcode</key>
<string>1030</string>
<key>DTXcodeBuild</key>
<string>10G8</string>
<key>UILaunchScreen</key>
<true/>
{{if .Schemes}}
<key>CFBundleURLTypes</key>
<array>
{{range .Schemes}}
<dict>
<key>CFBundleURLSchemes</key>
<array>
<string>{{.}}</string>
</array>
</dict>
{{end}}
</array>
{{end}}
</dict>
</plist>`, appName, bi.appID, appName, bi.version, bi.version.VersionCode, platform, minIOSVersion, supportPlatform, platform)
</plist>`)
if err != nil {
panic(err)
}
var manifestBuffer bytes.Buffer
if err := tmpl.Execute(&manifestBuffer, manifestSrc); err != nil {
panic(err)
}
return manifestBuffer.String()
}
func iosPlatformFor(target string) string {
@@ -386,7 +490,7 @@ func archiveIOS(tmpDir, target, frameworkRoot string, bi *buildInfo) error {
frameworkDir := filepath.Join(frameworkRoot, "Versions", "A")
for _, dir := range []string{"Headers", "Modules"} {
p := filepath.Join(frameworkDir, dir)
if err := os.MkdirAll(p, 0755); err != nil {
if err := os.MkdirAll(p, 0o755); err != nil {
return err
}
}
@@ -457,7 +561,7 @@ func archiveIOS(tmpDir, target, frameworkRoot string, bi *buildInfo) error {
export *
}`, framework)
moduleFile := filepath.Join(frameworkDir, "Modules", "module.modulemap")
return os.WriteFile(moduleFile, []byte(module), 0644)
return os.WriteFile(moduleFile, []byte(module), 0o644)
}
func iosCompilerFor(target, arch string, minsdk int) (string, []string, error) {
@@ -500,7 +604,6 @@ func iosCompilerFor(target, arch string, minsdk int) (string, []string, error) {
return "", nil, err
}
cflags := []string{
"-fembed-bitcode",
"-arch", allArchs[arch].iosArch,
"-isysroot", sdkPath,
"-m" + platformOS + "-version-min=" + strconv.Itoa(minsdk),
+2 -1
View File
@@ -30,6 +30,7 @@ func (d *JSTestDriver) Start(path string) {
if raceEnabled {
d.Skipf("js/wasm doesn't support -race; skipping")
}
d.Skipf("test fails with \"timed out waiting for a frame to be ready\"")
// First, build the app.
dir := d.tempDir("gio-endtoend-js")
@@ -60,7 +61,7 @@ func (d *JSTestDriver) Start(path string) {
pr, pw := io.Pipe()
d.Cleanup(func() { pw.Close() })
d.output = pr
chromedp.ListenTarget(ctx, func(ev interface{}) {
chromedp.ListenTarget(ctx, func(ev any) {
switch ev := ev.(type) {
case *runtime.EventConsoleAPICalled:
switch ev.Type {
+11 -6
View File
@@ -20,7 +20,7 @@ func buildJS(bi *buildInfo) error {
if out == "" {
out = bi.name
}
if err := os.MkdirAll(out, 0700); err != nil {
if err := os.MkdirAll(out, 0o700); err != nil {
return err
}
cmd := exec.Command(
@@ -48,7 +48,7 @@ func buildJS(bi *buildInfo) error {
if err != nil {
return err
}
if err := os.WriteFile(filepath.Join(out, filepath.Base(bi.iconPath)), icon, 0600); err != nil {
if err := os.WriteFile(filepath.Join(out, filepath.Base(bi.iconPath)), icon, 0o600); err != nil {
return err
}
faviconPath = filepath.Base(bi.iconPath)
@@ -70,7 +70,7 @@ func buildJS(bi *buildInfo) error {
return err
}
if err := os.WriteFile(filepath.Join(out, "index.html"), b.Bytes(), 0600); err != nil {
if err := os.WriteFile(filepath.Join(out, "index.html"), b.Bytes(), 0o600); err != nil {
return err
}
@@ -78,9 +78,14 @@ func buildJS(bi *buildInfo) error {
if err != nil {
return err
}
wasmJS := filepath.Join(goroot, "misc", "wasm", "wasm_exec.js")
// Location of the wasm_exec.js for go>=1.24
wasmJS := filepath.Join(goroot, "lib", "wasm", "wasm_exec.js")
if _, err := os.Stat(wasmJS); err != nil {
return fmt.Errorf("failed to find $GOROOT/misc/wasm/wasm_exec.js driver: %v", err)
// Location of the wasm_exec.js for go<1.24
wasmJS = filepath.Join(goroot, "misc", "wasm", "wasm_exec.js")
if _, err := os.Stat(wasmJS); err != nil {
return fmt.Errorf("failed to find $GOROOT/misc/wasm/wasm_exec.js driver: %v", err)
}
}
pkgs, err := packages.Load(&packages.Config{
Mode: packages.NeedName | packages.NeedFiles | packages.NeedImports | packages.NeedDeps,
@@ -169,7 +174,7 @@ const (
</html>`
// jsSetGo sets the `window.go` variable.
jsSetGo = `(() => {
window.go = {argv: [], env: {}, importObject: {go: {}}};
window.go = {argv: [], env: {}, importObject: {go: {}, gojs: {}}};
const argv = new URLSearchParams(location.search).get("argv");
if (argv) {
window.go["argv"] = argv.split(" ");
+45 -22
View File
@@ -1,6 +1,7 @@
package main
import (
"bytes"
"errors"
"fmt"
"os"
@@ -34,9 +35,7 @@ func buildMac(tmpDir string, bi *buildInfo) error {
return err
}
if err := builder.setInfo(bi, name); err != nil {
return fmt.Errorf("can't build the resources: %v", err)
}
builder.setInfo(bi, name)
for _, arch := range bi.archs {
tmpDest := filepath.Join(builder.TempDir, filepath.Base(builder.DestDir))
@@ -89,7 +88,7 @@ func (b *macBuilder) setIcon(path string) (err error) {
}
out := filepath.Join(b.TempDir, "iconset.iconset")
if err := os.MkdirAll(out, 0777); err != nil {
if err := os.MkdirAll(out, 0o777); err != nil {
return err
}
@@ -107,7 +106,6 @@ func (b *macBuilder) setIcon(path string) (err error) {
{path: "icon_16x16@2x.png", size: 32},
{path: "icon_16x16.png", size: 16},
})
if err != nil {
return err
}
@@ -123,7 +121,20 @@ func (b *macBuilder) setIcon(path string) (err error) {
return err
}
func (b *macBuilder) setInfo(buildInfo *buildInfo, name string) error {
func (b *macBuilder) setInfo(buildInfo *buildInfo, name string) {
manifestSrc := struct {
Name string
Bundle string
Version Semver
Schemes []string
}{
Name: name,
Bundle: buildInfo.appID,
Version: buildInfo.version,
Schemes: buildInfo.schemes,
}
t, err := template.New("manifest").Parse(`<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
@@ -137,21 +148,29 @@ func (b *macBuilder) setInfo(buildInfo *buildInfo, name string) error {
<key>NSHighResolutionCapable</key>
<true/>
<key>CFBundlePackageType</key>
<string>APPL</string>
<string>BNDL</string>
{{if .Schemes}}
<key>CFBundleURLTypes</key>
<array>
{{range .Schemes}}
<dict>
<key>CFBundleURLSchemes</key>
<array>
<string>{{.}}</string>
</array>
</dict>
{{end}}
</array>
{{end}}
</dict>
</plist>`)
if err != nil {
return err
panic(err)
}
var manifest bufferCoff
if err := t.Execute(&manifest, struct {
Name, Bundle string
}{
Name: name,
Bundle: buildInfo.appID,
}); err != nil {
return err
var manifest bytes.Buffer
if err := t.Execute(&manifest, manifestSrc); err != nil {
panic(err)
}
b.Manifest = manifest.Bytes()
@@ -165,24 +184,22 @@ func (b *macBuilder) setInfo(buildInfo *buildInfo, name string) error {
<true/>
</dict>
</plist>`)
return nil
}
func (b *macBuilder) buildProgram(buildInfo *buildInfo, binDest string, name string, arch string) error {
for _, path := range []string{"/Contents/MacOS", "/Contents/Resources"} {
if err := os.MkdirAll(filepath.Join(binDest, path), 0755); err != nil {
if err := os.MkdirAll(filepath.Join(binDest, path), 0o755); err != nil {
return err
}
}
if len(b.Icons) > 0 {
if err := os.WriteFile(filepath.Join(binDest, "/Contents/Resources/icon.icns"), b.Icons, 0755); err != nil {
if err := os.WriteFile(filepath.Join(binDest, "/Contents/Resources/icon.icns"), b.Icons, 0o755); err != nil {
return err
}
}
if err := os.WriteFile(filepath.Join(binDest, "/Contents/Info.plist"), b.Manifest, 0755); err != nil {
if err := os.WriteFile(filepath.Join(binDest, "/Contents/Info.plist"), b.Manifest, 0o755); err != nil {
return err
}
@@ -206,7 +223,7 @@ func (b *macBuilder) buildProgram(buildInfo *buildInfo, binDest string, name str
func (b *macBuilder) signProgram(buildInfo *buildInfo, binDest string, name string, arch string) error {
options := filepath.Join(b.TempDir, "ent.ent")
if err := os.WriteFile(options, b.Entitlements, 0777); err != nil {
if err := os.WriteFile(options, b.Entitlements, 0o777); err != nil {
return err
}
@@ -215,6 +232,12 @@ func (b *macBuilder) signProgram(buildInfo *buildInfo, binDest string, name stri
return err
}
// If the key is a provisioning profile use the same signing process as iOS
if filepath.Ext(buildInfo.key) == ".provisionprofile" {
embedded := filepath.Join(binDest, "Contents", "embedded.provisionprofile")
return signApple(buildInfo.appID, b.TempDir, embedded, binDest, []string{buildInfo.key})
}
cmd := exec.Command(
"codesign",
"--deep",
+5 -3
View File
@@ -29,18 +29,20 @@ var (
destPath = flag.String("o", "", "output file or directory.\nFor -target ios or tvos, use the .app suffix to target simulators.")
appID = flag.String("appid", "", "app identifier (for -buildmode=exe)")
name = flag.String("name", "", "app name (for -buildmode=exe)")
version = flag.String("version", "1.0.0.1", "semver app version (for -buildmode=exe) on the form major.minor.patch.versioncode")
version = flag.String("version", "1.0.0.1", "semver app version (for -buildmode=exe) on the form major.minor.patch.versioncode. The versioncode is not used for iOS and macOS.")
printCommands = flag.Bool("x", false, "print the commands")
keepWorkdir = flag.Bool("work", false, "print the name of the temporary work directory and do not delete it when exiting.")
linkMode = flag.String("linkmode", "", "set the -linkmode flag of the go tool")
extraLdflags = flag.String("ldflags", "", "extra flags to the Go linker")
extraTags = flag.String("tags", "", "extra tags to the Go tool")
iconPath = flag.String("icon", "", "specify an icon for iOS and Android")
signKey = flag.String("signkey", "", "specify the path of the keystore to be used to sign Android apk files.")
signKey = flag.String("signkey", "", "specify the path of the keystore (Android) or provisioning profile (macOS or iOS) for signing")
signPass = flag.String("signpass", "", "specify the password to decrypt the signkey.")
notaryID = flag.String("notaryid", "", "specify the apple id to use for notarization.")
notaryPass = flag.String("notarypass", "", "specify app-specific password of the Apple ID to be used for notarization.")
notaryTeamID = flag.String("notaryteamid", "", "specify the team id to use for notarization.")
schemes = flag.String("schemes", "", "specify a list of comma separated URL schemes that the program accepts")
pkgQueries = flag.String("queries", "", "specify a list of comma separated package names used to query other apps on Android.")
)
func main() {
@@ -199,7 +201,7 @@ func buildIcons(baseDir, icon string, variants []iconVariant) error {
v := v
resizes.Go(func() (err error) {
path := filepath.Join(baseDir, v.path)
if err := os.MkdirAll(filepath.Dir(path), 0700); err != nil {
if err := os.MkdirAll(filepath.Dir(path), 0o700); err != nil {
return err
}
f, err := os.Create(path)
+3
View File
@@ -22,6 +22,9 @@ var AndroidPermissions = map[string][]string{
"wakelock": {
"android.permission.WAKE_LOCK",
},
"microphone": {
"android.permission.RECORD_AUDIO",
},
}
var AndroidFeatures = map[string][]string{
+1 -1
View File
@@ -174,7 +174,7 @@ func (d *WaylandTestDriver) Screenshot() image.Image {
return img
}
func (d *WaylandTestDriver) swaymsg(args ...interface{}) {
func (d *WaylandTestDriver) swaymsg(args ...any) {
strs := []string{"--socket", d.socket}
for _, arg := range args {
strs = append(strs, fmt.Sprint(arg))
+10 -2
View File
@@ -202,10 +202,18 @@ func (b *windowsBuilder) buildProgram(buildInfo *buildInfo, name string, arch st
dest = filepath.Join(filepath.Dir(b.DestDir), name+"_"+arch+".exe")
}
ldflags := buildInfo.ldflags
if buildInfo.schemes != nil {
ldflags += ` -X "gioui.org/app.schemesURI=` + strings.Join(buildInfo.schemes, ",") + `" `
}
if buildInfo.appID != "" {
ldflags += ` -X "gioui.org/app.ID=` + buildInfo.appID + `" `
}
cmd := exec.Command(
"go",
"build",
"-ldflags=-H=windowsgui "+buildInfo.ldflags,
"-ldflags=-H=windowsgui "+ldflags,
"-tags="+buildInfo.tags,
"-o", dest,
buildInfo.pkgPath,
@@ -358,7 +366,7 @@ const (
valueText uint16 = 1
)
func newValue(valueType uint16, key string, input interface{}) windowsInfoValue {
func newValue(valueType uint16, key string, input any) windowsInfoValue {
v := windowsInfoValue{
Type: valueType,
Length: 6,
+1 -1
View File
@@ -145,7 +145,7 @@ func (d *X11TestDriver) Screenshot() image.Image {
return img
}
func (d *X11TestDriver) xdotool(args ...interface{}) string {
func (d *X11TestDriver) xdotool(args ...any) string {
d.Helper()
strs := make([]string, len(args))
for i, arg := range args {
+1 -2
View File
@@ -10,6 +10,7 @@ import (
"errors"
"flag"
"fmt"
"go/format"
"io"
"os"
"path/filepath"
@@ -17,8 +18,6 @@ import (
"strings"
"unicode"
"go/format"
"gioui.org/f32"
)