forked from joejulian/gio-cmd
Compare commits
24 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1762d36dde | |||
| 7b5a6b418c | |||
| f71579e799 | |||
| 192acd9d09 | |||
| 2e72e8f0b2 | |||
| f4d6788248 | |||
| e5b1a4e6cd | |||
| f587d2f097 | |||
| 8de547d61d | |||
| 048614c60e | |||
| ed8d0aa9a6 | |||
| e1f06eb7b0 | |||
| ae8a780af9 | |||
| 74551d3253 | |||
| aecb4723c9 | |||
| 3f0ad89ca9 | |||
| c108ce0a29 | |||
| 1b42337ac0 | |||
| ae8dd5433d | |||
| ab2d621e47 | |||
| 01ffdf788d | |||
| 51c6d8037b | |||
| d1bccae359 | |||
| 37612f9112 |
+19
-10
@@ -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
@@ -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
@@ -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
@@ -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: |
|
||||
|
||||
@@ -1,28 +1,28 @@
|
||||
module gioui.org/cmd
|
||||
|
||||
go 1.21
|
||||
go 1.24.1
|
||||
|
||||
require (
|
||||
gioui.org v0.8.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.18.0
|
||||
golang.org/x/sync v0.7.0
|
||||
golang.org/x/text v0.16.0
|
||||
golang.org/x/tools v0.23.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/shader v1.0.8 // indirect
|
||||
github.com/go-text/typesetting v0.2.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-20240707233637-46b078467d37 // indirect
|
||||
golang.org/x/exp/shiny v0.0.0-20240707233637-46b078467d37 // indirect
|
||||
golang.org/x/mod v0.19.0 // indirect
|
||||
golang.org/x/sys v0.22.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
|
||||
)
|
||||
|
||||
@@ -1,44 +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.8.0 h1:QV5p5JvsmSmGiIXVYOKn6d9YDliTfjtLlVf5J+BZ9Pg=
|
||||
gioui.org v0.8.0/go.mod h1:vEMmpxMOd/iwJhXvGVIzWEbxMWhnMQ9aByOGQdlQ8rc=
|
||||
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/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.2.1 h1:x0jMOGyO3d1qFAPI0j4GSsh7M0Q3Ypjzr4+CEVg82V8=
|
||||
github.com/go-text/typesetting v0.2.1/go.mod h1:mTOxEwasOFpAMBjEQDhdWRckoLLeI/+qrQeBCTGEt6M=
|
||||
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.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=
|
||||
golang.org/x/exp v0.0.0-20240707233637-46b078467d37 h1:uLDX+AfeFCct3a2C7uIWBKMJIR3CJMhcgfrUAqjRK6w=
|
||||
golang.org/x/exp v0.0.0-20240707233637-46b078467d37/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
|
||||
golang.org/x/exp/shiny v0.0.0-20240707233637-46b078467d37 h1:SOSg7+sueresE4IbmmGM60GmlIys+zNX63d6/J4CMtU=
|
||||
golang.org/x/exp/shiny v0.0.0-20240707233637-46b078467d37/go.mod h1:3F+MieQB7dRYLTmnncoFbb1crS5lfQoTfDgQy6K4N0o=
|
||||
golang.org/x/image v0.18.0 h1:jGzIakQa/ZXI1I0Fxvaa9W7yP25TqT6cHIHn+6CqvSQ=
|
||||
golang.org/x/image v0.18.0/go.mod h1:4yyo5vMFQjVjUcVk4jEQcU9MGy/rulF5WvUILseCM2E=
|
||||
golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8=
|
||||
golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
|
||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20191113165036-4c7a9d0fe056/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
|
||||
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
|
||||
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
|
||||
golang.org/x/tools v0.23.0 h1:SGsXPZ+2l4JsgaCKkx+FQ9YZ5XEtA1GZYuoDjenLjvg=
|
||||
golang.org/x/tools v0.23.0/go.mod h1:pnu6ufv6vQkll6szChhK3C3L/ruaIv5eBeztNG8wtsI=
|
||||
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=
|
||||
|
||||
@@ -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
@@ -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) {
|
||||
|
||||
@@ -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
@@ -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]
|
||||
}
|
||||
|
||||
@@ -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
@@ -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.
|
||||
`
|
||||
|
||||
+160
-57
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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)
|
||||
|
||||
@@ -22,6 +22,9 @@ var AndroidPermissions = map[string][]string{
|
||||
"wakelock": {
|
||||
"android.permission.WAKE_LOCK",
|
||||
},
|
||||
"microphone": {
|
||||
"android.permission.RECORD_AUDIO",
|
||||
},
|
||||
}
|
||||
|
||||
var AndroidFeatures = map[string][]string{
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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"
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user