forked from joejulian/gio-cmd
Compare commits
51 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 | |||
| 03a1ada8ac | |||
| eb9fbc797e | |||
| d0d7f84d0e | |||
| 6465f30f98 | |||
| ddde16a09e | |||
| c9121dcb8c | |||
| b9c6c3edf0 | |||
| aa41850405 | |||
| a24a395e5f | |||
| 7a117566ca | |||
| cb72b91a92 | |||
| 607a9e37c5 | |||
| 36ef219a61 | |||
| abc34cf117 | |||
| 7cb98d0557 | |||
| 4128f253e8 | |||
| 42b1cd0f6c | |||
| 7b925a6c25 | |||
| 940364d3e9 | |||
| 0a86898b41 | |||
| 78da7b9864 | |||
| cf291ca3bd | |||
| 5c14d1ba64 | |||
| 02068d6340 | |||
| 2edf599beb | |||
| 9768b95616 | |||
| ecebd405a7 |
+19
-10
@@ -8,8 +8,10 @@ packages:
|
|||||||
- libxml2-dev
|
- libxml2-dev
|
||||||
- libssl-dev
|
- libssl-dev
|
||||||
- libz-dev
|
- libz-dev
|
||||||
|
- ninja-build # cctools
|
||||||
- llvm-dev # for cctools
|
- llvm-dev # for cctools
|
||||||
- uuid-dev ## for cctools
|
- uuid-dev # for cctools
|
||||||
|
- libblocksruntime-dev # for cctools
|
||||||
- libplist-utils # for gogio
|
- libplist-utils # for gogio
|
||||||
sources:
|
sources:
|
||||||
- https://git.sr.ht/~eliasnaur/gio-cmd
|
- https://git.sr.ht/~eliasnaur/gio-cmd
|
||||||
@@ -17,6 +19,7 @@ sources:
|
|||||||
- https://git.sr.ht/~eliasnaur/giouiorg
|
- https://git.sr.ht/~eliasnaur/giouiorg
|
||||||
- https://github.com/tpoechtrager/cctools-port.git
|
- https://github.com/tpoechtrager/cctools-port.git
|
||||||
- https://github.com/tpoechtrager/apple-libtapi.git
|
- https://github.com/tpoechtrager/apple-libtapi.git
|
||||||
|
- https://github.com/tpoechtrager/apple-libdispatch
|
||||||
- https://github.com/mackyle/xar.git
|
- https://github.com/mackyle/xar.git
|
||||||
environment:
|
environment:
|
||||||
APPLE_TOOLCHAIN_ROOT: /home/build/appletools
|
APPLE_TOOLCHAIN_ROOT: /home/build/appletools
|
||||||
@@ -24,7 +27,7 @@ environment:
|
|||||||
tasks:
|
tasks:
|
||||||
- install_go: |
|
- install_go: |
|
||||||
mkdir -p /home/build/sdk
|
mkdir -p /home/build/sdk
|
||||||
curl -s https://dl.google.com/go/go1.17.7.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: |
|
- prepare_toolchain: |
|
||||||
mkdir -p $APPLE_TOOLCHAIN_ROOT
|
mkdir -p $APPLE_TOOLCHAIN_ROOT
|
||||||
cd $APPLE_TOOLCHAIN_ROOT
|
cd $APPLE_TOOLCHAIN_ROOT
|
||||||
@@ -42,6 +45,11 @@ tasks:
|
|||||||
- install_appletoolchain: |
|
- install_appletoolchain: |
|
||||||
cd giouiorg
|
cd giouiorg
|
||||||
go build -o $APPLE_TOOLCHAIN_ROOT/tools ./cmd/appletoolchain
|
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: |
|
- build_xar: |
|
||||||
cd xar/xar
|
cd xar/xar
|
||||||
ac_cv_lib_crypto_OpenSSL_add_all_ciphers=yes CC=clang ./autogen.sh --prefix=/usr
|
ac_cv_lib_crypto_OpenSSL_add_all_ciphers=yes CC=clang ./autogen.sh --prefix=/usr
|
||||||
@@ -53,15 +61,16 @@ tasks:
|
|||||||
./install.sh
|
./install.sh
|
||||||
- build_cctools: |
|
- build_cctools: |
|
||||||
cd cctools-port/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
|
make install
|
||||||
- install_gogio: |
|
- install_gogio: |
|
||||||
cd gio-cmd
|
cd gio-cmd
|
||||||
go install ./gogio
|
go install ./gogio
|
||||||
- test_ios_gogio: |
|
# Broken test.
|
||||||
mkdir tmp
|
# - test_ios_gogio: |
|
||||||
cd tmp
|
# mkdir tmp
|
||||||
go mod init example.com
|
# cd tmp
|
||||||
go get -d gioui.org/example/kitchen
|
# go mod init example.com
|
||||||
export PATH=/home/build/appletools/bin:$PATH
|
# go get -d gioui.org/example/kitchen
|
||||||
gogio -target ios -o app.app 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:
|
tasks:
|
||||||
- install_go: |
|
- install_go: |
|
||||||
mkdir -p /home/build/sdk
|
mkdir -p /home/build/sdk
|
||||||
curl https://dl.google.com/go/go1.17.7.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: |
|
- test_cmd: |
|
||||||
cd gio-cmd
|
cd gio-cmd
|
||||||
go test ./...
|
go test ./...
|
||||||
|
|||||||
+3
-4
@@ -24,7 +24,6 @@ packages:
|
|||||||
- scrot
|
- scrot
|
||||||
- sway
|
- sway
|
||||||
- grim
|
- grim
|
||||||
- wine
|
|
||||||
- unzip
|
- unzip
|
||||||
sources:
|
sources:
|
||||||
- https://git.sr.ht/~eliasnaur/gio-cmd
|
- https://git.sr.ht/~eliasnaur/gio-cmd
|
||||||
@@ -39,7 +38,7 @@ secrets:
|
|||||||
tasks:
|
tasks:
|
||||||
- install_go: |
|
- install_go: |
|
||||||
mkdir -p /home/build/sdk
|
mkdir -p /home/build/sdk
|
||||||
curl -s https://dl.google.com/go/go1.17.7.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: |
|
- check_gofmt: |
|
||||||
cd gio-cmd
|
cd gio-cmd
|
||||||
test -z "$(gofmt -s -l .)"
|
test -z "$(gofmt -s -l .)"
|
||||||
@@ -57,8 +56,8 @@ tasks:
|
|||||||
# mirror to github
|
# mirror to github
|
||||||
ssh-keyscan github.com > "$HOME"/.ssh/known_hosts && cd gio-cmd && git push --mirror "$github_mirror" || echo "failed mirroring"
|
ssh-keyscan github.com > "$HOME"/.ssh/known_hosts && cd gio-cmd && git push --mirror "$github_mirror" || echo "failed mirroring"
|
||||||
- install_chrome: |
|
- install_chrome: |
|
||||||
curl -s https://dl.google.com/linux/linux_signing_key.pub | sudo apt-key add -
|
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] https://dl-ssl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list'
|
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 update
|
||||||
sudo apt-get -qq install -y google-chrome-stable
|
sudo apt-get -qq install -y google-chrome-stable
|
||||||
- test: |
|
- test: |
|
||||||
|
|||||||
+1
-1
@@ -10,7 +10,7 @@ environment:
|
|||||||
tasks:
|
tasks:
|
||||||
- install_go: |
|
- install_go: |
|
||||||
mkdir -p /home/build/sdk
|
mkdir -p /home/build/sdk
|
||||||
curl https://dl.google.com/go/go1.17.7.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
|
cd /home/build/sdk/go/src
|
||||||
./make.bash
|
./make.bash
|
||||||
- test_cmd: |
|
- test_cmd: |
|
||||||
|
|||||||
@@ -1,31 +1,28 @@
|
|||||||
module gioui.org/cmd
|
module gioui.org/cmd
|
||||||
|
|
||||||
go 1.17
|
go 1.24.1
|
||||||
|
|
||||||
require (
|
require (
|
||||||
gioui.org v0.0.0-20220628163331-e21c665e70ae
|
gioui.org v0.9.0
|
||||||
github.com/akavel/rsrc v0.10.1
|
github.com/akavel/rsrc v0.10.1
|
||||||
github.com/chromedp/cdproto v0.0.0-20191114225735-6626966fbae4
|
github.com/chromedp/cdproto v0.0.0-20250429231605-6ed5b53462d4
|
||||||
github.com/chromedp/chromedp v0.5.2
|
github.com/chromedp/chromedp v0.13.6
|
||||||
golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d
|
golang.org/x/image v0.26.0
|
||||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9
|
golang.org/x/sync v0.13.0
|
||||||
golang.org/x/text v0.3.7
|
golang.org/x/text v0.24.0
|
||||||
golang.org/x/tools v0.1.0
|
golang.org/x/tools v0.32.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
gioui.org/cpu v0.0.0-20210817075930-8d6a761490d2 // indirect
|
gioui.org/shader v1.0.8 // indirect
|
||||||
gioui.org/shader v1.0.6 // indirect
|
github.com/chromedp/sysutil v1.1.0 // indirect
|
||||||
github.com/benoitkugler/textlayout v0.1.1 // indirect
|
github.com/go-json-experiment/json v0.0.0-20250417205406-170dfdcf87d1 // indirect
|
||||||
github.com/gioui/uax v0.2.1-0.20220325163150-e3d987515a12 // indirect
|
github.com/go-text/typesetting v0.3.0 // indirect
|
||||||
github.com/go-text/typesetting v0.0.0-20220411150340-35994bc27a7b // indirect
|
github.com/gobwas/httphead v0.1.0 // indirect
|
||||||
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee // indirect
|
github.com/gobwas/pool v0.2.1 // indirect
|
||||||
github.com/gobwas/pool v0.2.0 // indirect
|
github.com/gobwas/ws v1.4.0 // indirect
|
||||||
github.com/gobwas/ws v1.0.2 // indirect
|
github.com/google/go-cmp v0.7.0 // indirect
|
||||||
github.com/knq/sysutil v0.0.0-20191005231841-15668db23d08 // indirect
|
golang.org/x/exp/shiny v0.0.0-20250408133849-7e4ce0ab07d0 // indirect
|
||||||
github.com/mailru/easyjson v0.7.0 // indirect
|
golang.org/x/mod v0.24.0 // indirect
|
||||||
golang.org/x/exp v0.0.0-20210722180016-6781d3edade3 // indirect
|
golang.org/x/sys v0.33.0 // indirect
|
||||||
golang.org/x/mod v0.4.2 // indirect
|
|
||||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c // indirect
|
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
|
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,120 +1,50 @@
|
|||||||
eliasnaur.com/font v0.0.0-20220124212145-832bb8fc08c3 h1:djFprmHZgrSepsHAIRMp5UJn3PzsoTg9drI+BDmif5Q=
|
eliasnaur.com/font v0.0.0-20230308162249-dd43949cb42d h1:ARo7NCVvN2NdhLlJE9xAbKweuI9L6UgfTbYb0YwPacY=
|
||||||
gioui.org v0.0.0-20220628163331-e21c665e70ae h1:s8Erm0/zVvi3Fbq0ijjPkRT04XxcGZWTxkxDwUBsxuQ=
|
eliasnaur.com/font v0.0.0-20230308162249-dd43949cb42d/go.mod h1:OYVuxibdk9OSLX8vAqydtRPP87PyTFcT9uH3MlEGBQA=
|
||||||
gioui.org v0.0.0-20220628163331-e21c665e70ae/go.mod h1:WHoHbUjH91BJS2xkfps2AhKxji+9o3xwfsphGsCBfnM=
|
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-20210808092351-bfe733dd3334/go.mod h1:A8M0Cn5o+vY5LTMlnRoK3O5kG+rH0kWfJjeKd9QpBmQ=
|
||||||
gioui.org/cpu v0.0.0-20210817075930-8d6a761490d2 h1:AGDDxsJE1RpcXTAxPG2B4jrwVUJGFDjINIPi1jtO6pc=
|
gioui.org/shader v1.0.8 h1:6ks0o/A+b0ne7RzEqRZK5f4Gboz2CfG+mVliciy6+qA=
|
||||||
gioui.org/cpu v0.0.0-20210817075930-8d6a761490d2/go.mod h1:A8M0Cn5o+vY5LTMlnRoK3O5kG+rH0kWfJjeKd9QpBmQ=
|
gioui.org/shader v1.0.8/go.mod h1:mWdiME581d/kV7/iEhLmUgUK5iZ09XR5XpduXzbePVM=
|
||||||
gioui.org/shader v1.0.6 h1:cvZmU+eODFR2545X+/8XucgZdTtEjR3QWW6W65b0q5Y=
|
|
||||||
gioui.org/shader v1.0.6/go.mod h1:mWdiME581d/kV7/iEhLmUgUK5iZ09XR5XpduXzbePVM=
|
|
||||||
github.com/akavel/rsrc v0.10.1 h1:hCCPImjmFKVNGpeLZyTDRHEFC283DzyTXTo0cO0Rq9o=
|
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/akavel/rsrc v0.10.1/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c=
|
||||||
github.com/benoitkugler/pstokenizer v1.0.0/go.mod h1:l1G2Voirz0q/jj0TQfabNxVsa8HZXh/VMxFSRALWTiE=
|
github.com/chromedp/cdproto v0.0.0-20250429231605-6ed5b53462d4 h1:UZdrvid2JFwnvPlUSEFlE794XZL4Jmrj8fuxfcLECJE=
|
||||||
github.com/benoitkugler/textlayout v0.0.5/go.mod h1:puH4v13Uz7uIhIH0XMk5jgc8U3MXcn5r3VlV9K8n0D8=
|
github.com/chromedp/cdproto v0.0.0-20250429231605-6ed5b53462d4/go.mod h1:NItd7aLkcfOA/dcMXvl8p1u+lQqioRMq/SqDp71Pb/k=
|
||||||
github.com/benoitkugler/textlayout v0.1.1 h1:hizE/085xAeY8q7gwV00uHR2Q27KYB2g1HW+UacXl68=
|
github.com/chromedp/chromedp v0.13.6 h1:xlNunMyzS5bu3r/QKrb3fzX6ow3WBQ6oao+J65PGZxk=
|
||||||
github.com/benoitkugler/textlayout v0.1.1/go.mod h1:o+1hFV+JSHBC9qNLIuwVoLedERU7sBPgEFcuSgfvi/w=
|
github.com/chromedp/chromedp v0.13.6/go.mod h1:h8GPP6ZtLMLsU8zFbTcb7ZDGCvCy8j/vRoFmRltQx9A=
|
||||||
github.com/benoitkugler/textlayout-testdata v0.1.1 h1:AvFxBxpfrQd8v55qH59mZOJOQjtD6K2SFe9/HvnIbJk=
|
github.com/chromedp/sysutil v1.1.0 h1:PUFNv5EcprjqXZD9nJb9b/c9ibAbxiYo4exNWZyipwM=
|
||||||
github.com/chromedp/cdproto v0.0.0-20191114225735-6626966fbae4 h1:QD3KxSJ59L2lxG6MXBjNHxiQO2RmxTQ3XcK+wO44WOg=
|
github.com/chromedp/sysutil v1.1.0/go.mod h1:WiThHUdltqCNKGc4gaU50XgYjwjYIhKWoHGPTUfWTJ8=
|
||||||
github.com/chromedp/cdproto v0.0.0-20191114225735-6626966fbae4/go.mod h1:PfAWWKJqjlGFYJEidUM6aVIWPr0EpobeyVWEEmplX7g=
|
github.com/go-json-experiment/json v0.0.0-20250417205406-170dfdcf87d1 h1:+VexzzkMLb1tnvpuQdGT/DicIRW7MN8ozsXqBMgp0Hk=
|
||||||
github.com/chromedp/chromedp v0.5.2 h1:W8xBXQuUnd2dZK0SN/lyVwsQM7KgW+kY5HGnntms194=
|
github.com/go-json-experiment/json v0.0.0-20250417205406-170dfdcf87d1/go.mod h1:TiCD2a1pcmjd7YnhGH0f/zKNcCD06B029pHhzV23c2M=
|
||||||
github.com/chromedp/chromedp v0.5.2/go.mod h1:rsTo/xRo23KZZwFmWk2Ui79rBaVRRATCjLzNQlOFSiA=
|
github.com/go-text/typesetting v0.3.0 h1:OWCgYpp8njoxSRpwrdd1bQOxdjOXDj9Rqart9ML4iF4=
|
||||||
github.com/cloudfoundry/jibber_jabber v0.0.0-20151120183258-bcc4c8345a21/go.mod h1:po7NpZ/QiTKzBKyrsEAxwnTamCoh8uDk/egRpQ7siIc=
|
github.com/go-text/typesetting v0.3.0/go.mod h1:qjZLkhRgOEYMhU9eHBr3AR4sfnGJvOXNLt8yRAySFuY=
|
||||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
github.com/go-text/typesetting-utils v0.0.0-20241103174707-87a29e9e6066 h1:qCuYC+94v2xrb1PoS4NIDe7DGYtLnU2wWiQe9a1B1c0=
|
||||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
github.com/go-text/typesetting-utils v0.0.0-20241103174707-87a29e9e6066/go.mod h1:DDxDdQEnB70R8owOx3LVpEFvpMK9eeH1o2r0yZhFI9o=
|
||||||
github.com/gioui/uax v0.2.1-0.20220325163150-e3d987515a12 h1:1bjaB/5IIicfKpP4k0s30T2WEw//Kh00zULa8DQ0cxA=
|
github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=
|
||||||
github.com/gioui/uax v0.2.1-0.20220325163150-e3d987515a12/go.mod h1:kDhBRTA/i3H46PVdhqcw26TdGSIj42TOKNWKY+Kipnw=
|
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
|
||||||
github.com/go-text/typesetting v0.0.0-20220411150340-35994bc27a7b h1:WINlj3ANt+CVrO2B4NGDHRlPvEWZPxjhb7z+JKypwXI=
|
github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
|
||||||
github.com/go-text/typesetting v0.0.0-20220411150340-35994bc27a7b/go.mod h1:ZNYu5saGoMOqtkVH5T8onTwhzenDUVszI+5WFHJRaxQ=
|
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
|
||||||
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee h1:s+21KNqlpePfkah2I+gwHF8xmJWRjooY+5248k6m4A0=
|
github.com/gobwas/ws v1.4.0 h1:CTaoG1tojrh4ucGPcoJFiAQUAsEWekEWvLy7GsVNqGs=
|
||||||
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo=
|
github.com/gobwas/ws v1.4.0/go.mod h1:G3gNqMNtPppf5XUz7O4shetPpcZ1VJ7zt18dlUeakrc=
|
||||||
github.com/gobwas/pool v0.2.0 h1:QEmUOlnSjWtnpRGHF3SauEiOsy82Cup83Vf2LcMlnc8=
|
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||||
github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
|
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||||
github.com/gobwas/ws v1.0.2 h1:CoAavW/wd/kulfZmSIBt6p24n4j7tHgNVCjsfHVNUbo=
|
github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80 h1:6Yzfa6GP0rIo/kULo2bwGEkFvCePZ3qHDDTC3/J9Swo=
|
||||||
github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
|
github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80/go.mod h1:imJHygn/1yfhB7XSJJKlFZKl/J+dCPAknuiaGOshXAs=
|
||||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde h1:x0TT0RDC7UhAVbbWWBzr41ElhJx5tXPWkIHA2HWPRuw=
|
||||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0=
|
||||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 h1:R84qjqJb5nVJMxqWYb3np9L5ZsaDtB+a39EqjV0JSUM=
|
||||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0/go.mod h1:S9Xr4PYopiDyqSyp5NjCrhFrqg6A5zA2E/iPHPhqnS8=
|
||||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
golang.org/x/exp/shiny v0.0.0-20250408133849-7e4ce0ab07d0 h1:tMSqXTK+AQdW3LpCbfatHSRPHeW6+2WuxaVQuHftn80=
|
||||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
golang.org/x/exp/shiny v0.0.0-20250408133849-7e4ce0ab07d0/go.mod h1:ygj7T6vSGhhm/9yTpOQQNvuAUFziTH7RUiH74EoE2C8=
|
||||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
golang.org/x/image v0.26.0 h1:4XjIFEZWQmCZi6Wv8BoxsDhRU3RVnLX04dToTDAEPlY=
|
||||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
golang.org/x/image v0.26.0/go.mod h1:lcxbMFAovzpnJxzXS3nyL83K27tmqtKzIJpctK8YO5c=
|
||||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU=
|
||||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
|
||||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610=
|
||||||
github.com/knq/sysutil v0.0.0-20191005231841-15668db23d08 h1:V0an7KRw92wmJysvFvtqtKMAPmvS5O0jtB0nYo6t+gs=
|
golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||||
github.com/knq/sysutil v0.0.0-20191005231841-15668db23d08/go.mod h1:dFWs1zEqDjFtnBXsd1vPOZaLsESovai349994nHx3e0=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
github.com/mailru/easyjson v0.7.0 h1:aizVhC/NAAcKWb+5QsU1iNOZb4Yws5UO2I+aIprQITM=
|
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
|
||||||
github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs=
|
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
|
||||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
|
||||||
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
|
golang.org/x/tools v0.32.0 h1:Q7N1vhpkQv7ybVzLFtTjvQya2ewbwNDZzUgfXGqtMWU=
|
||||||
github.com/onsi/ginkgo v1.15.0/go.mod h1:hF8qUzuuC8DJGygJH3726JnCZX4MYbRB8yFfISqnKUg=
|
golang.org/x/tools v0.32.0/go.mod h1:ZxrU41P/wAbZD8EDa6dDCa6XfpkhJ7HFMjHJXfBDu8s=
|
||||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
|
||||||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
|
||||||
github.com/onsi/gomega v1.10.5/go.mod h1:gza4q3jKQJijlu05nKWRCW/GavJumGt8aNRxWg7mt48=
|
|
||||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
|
||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
|
||||||
golang.org/x/exp v0.0.0-20210722180016-6781d3edade3 h1:IlrJD2AM5p8JhN/wVny9jt6gJ9hut2VALhSeZ3SYluk=
|
|
||||||
golang.org/x/exp v0.0.0-20210722180016-6781d3edade3/go.mod h1:DVyR6MI7P4kEQgvZJSj1fQGrWIi2RzIrfYWycwheUAc=
|
|
||||||
golang.org/x/image v0.0.0-20210504121937-7319ad40d33e/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
|
||||||
golang.org/x/image v0.0.0-20210607152325-775e3b0c77b9/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=
|
|
||||||
golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d h1:RNPAfi2nHY7C2srAV8A49jpsYr0ADedCk1wq6fTMTvs=
|
|
||||||
golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=
|
|
||||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
|
||||||
golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo=
|
|
||||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
|
||||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
|
||||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
|
||||||
golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
|
||||||
golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
|
||||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 h1:SQFwaSi55rU7vdNs9Yr0Z324VNlrF+0wMqRXT4St8ck=
|
|
||||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20191113165036-4c7a9d0fe056/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I=
|
|
||||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
|
||||||
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.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
|
||||||
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
|
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
|
||||||
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.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
|
||||||
golang.org/x/tools v0.1.0 h1:po9/4sTYwZU9lPhi1tOrb4hCv3qrhiQ77LZfGa2OjwY=
|
|
||||||
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
|
||||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
|
||||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
|
||||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
|
||||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
|
||||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
|
||||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
|
||||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
|
||||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
|
|||||||
@@ -24,9 +24,9 @@ type AndroidTestDriver struct {
|
|||||||
var rxAdbDevice = regexp.MustCompile(`(.*)\s+device$`)
|
var rxAdbDevice = regexp.MustCompile(`(.*)\s+device$`)
|
||||||
|
|
||||||
func (d *AndroidTestDriver) Start(path string) {
|
func (d *AndroidTestDriver) Start(path string) {
|
||||||
d.sdkDir = os.Getenv("ANDROID_SDK_ROOT")
|
d.sdkDir = os.Getenv("ANDROID_HOME")
|
||||||
if d.sdkDir == "" {
|
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")
|
d.adbPath = filepath.Join(d.sdkDir, "platform-tools", "adb")
|
||||||
if _, err := os.Stat(d.adbPath); os.IsNotExist(err) {
|
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{}
|
strs := []string{}
|
||||||
for _, arg := range args {
|
for _, arg := range args {
|
||||||
strs = append(strs, fmt.Sprint(arg))
|
strs = append(strs, fmt.Sprint(arg))
|
||||||
|
|||||||
+207
-76
@@ -8,7 +8,6 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@@ -41,14 +40,18 @@ type errWriter struct {
|
|||||||
var exeSuffix string
|
var exeSuffix string
|
||||||
|
|
||||||
type manifestData struct {
|
type manifestData struct {
|
||||||
AppID string
|
AppID string
|
||||||
Version int
|
Version Semver
|
||||||
MinSDK int
|
MinSDK int
|
||||||
TargetSDK int
|
TargetSDK int
|
||||||
Permissions []string
|
Permissions []string
|
||||||
Features []string
|
Features []string
|
||||||
IconSnip string
|
IconSnip string
|
||||||
AppName string
|
AppName string
|
||||||
|
Schemes []string
|
||||||
|
PackageQueries []string
|
||||||
|
ManifestSnip string
|
||||||
|
AppSnip string
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -77,9 +80,9 @@ func init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func buildAndroid(tmpDir string, bi *buildInfo) error {
|
func buildAndroid(tmpDir string, bi *buildInfo) error {
|
||||||
sdk := os.Getenv("ANDROID_SDK_ROOT")
|
sdk := os.Getenv("ANDROID_HOME")
|
||||||
if sdk == "" {
|
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 {
|
if _, err := os.Stat(sdk); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -114,7 +117,9 @@ func buildAndroid(tmpDir string, bi *buildInfo) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
moduleRoot := moduleRootForDir(bi.pkgDir)
|
||||||
var extraJars []string
|
var extraJars []string
|
||||||
|
seenJars := make(map[string]bool)
|
||||||
visitedPkgs := make(map[string]bool)
|
visitedPkgs := make(map[string]bool)
|
||||||
var visitPkg func(*packages.Package) error
|
var visitPkg func(*packages.Package) error
|
||||||
visitPkg = func(p *packages.Package) error {
|
visitPkg = func(p *packages.Package) error {
|
||||||
@@ -122,11 +127,17 @@ func buildAndroid(tmpDir string, bi *buildInfo) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
dir := filepath.Dir(p.GoFiles[0])
|
dir := filepath.Dir(p.GoFiles[0])
|
||||||
jars, err := filepath.Glob(filepath.Join(dir, "*.jar"))
|
jars, err := androidExtraJars(dir, moduleRoot)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
extraJars = append(extraJars, jars...)
|
for _, jar := range jars {
|
||||||
|
if seenJars[jar] {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
seenJars[jar] = true
|
||||||
|
extraJars = append(extraJars, jar)
|
||||||
|
}
|
||||||
switch {
|
switch {
|
||||||
case p.PkgPath == "net":
|
case p.PkgPath == "net":
|
||||||
perms = append(perms, "network")
|
perms = append(perms, "network")
|
||||||
@@ -180,9 +191,9 @@ func buildAndroid(tmpDir string, bi *buildInfo) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func compileAndroid(tmpDir string, tools *androidTools, bi *buildInfo) (err error) {
|
func compileAndroid(tmpDir string, tools *androidTools, bi *buildInfo) (err error) {
|
||||||
androidHome := os.Getenv("ANDROID_SDK_ROOT")
|
androidHome := os.Getenv("ANDROID_HOME")
|
||||||
if androidHome == "" {
|
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()
|
javac, err := findJavaC()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -192,10 +203,7 @@ func compileAndroid(tmpDir string, tools *androidTools, bi *buildInfo) (err erro
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
minSDK := 17
|
minSDK := max(bi.minsdk, 17)
|
||||||
if bi.minsdk > minSDK {
|
|
||||||
minSDK = bi.minsdk
|
|
||||||
}
|
|
||||||
tcRoot := filepath.Join(ndkRoot, "toolchains", "llvm", "prebuilt", archNDK())
|
tcRoot := filepath.Join(ndkRoot, "toolchains", "llvm", "prebuilt", archNDK())
|
||||||
var builds errgroup.Group
|
var builds errgroup.Group
|
||||||
for _, a := range bi.archs {
|
for _, a := range bi.archs {
|
||||||
@@ -214,14 +222,14 @@ func compileAndroid(tmpDir string, tools *androidTools, bi *buildInfo) (err erro
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
archDir := filepath.Join(tmpDir, "jni", arch.jniArch)
|
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)
|
return fmt.Errorf("failed to create %q: %v", archDir, err)
|
||||||
}
|
}
|
||||||
libFile := filepath.Join(archDir, "libgio.so")
|
libFile := filepath.Join(archDir, "libgio.so")
|
||||||
cmd := exec.Command(
|
cmd := exec.Command(
|
||||||
"go",
|
"go",
|
||||||
"build",
|
"build",
|
||||||
"-ldflags=-w -s "+bi.ldflags,
|
"-ldflags=-w -s -extldflags \"-Wl,-z,max-page-size=65536\" "+bi.ldflags,
|
||||||
"-buildmode=c-shared",
|
"-buildmode=c-shared",
|
||||||
"-tags", bi.tags,
|
"-tags", bi.tags,
|
||||||
"-o", libFile,
|
"-o", libFile,
|
||||||
@@ -234,13 +242,14 @@ func compileAndroid(tmpDir string, tools *androidTools, bi *buildInfo) (err erro
|
|||||||
"GOARM=7", // Avoid softfloat.
|
"GOARM=7", // Avoid softfloat.
|
||||||
"CGO_ENABLED=1",
|
"CGO_ENABLED=1",
|
||||||
"CC="+clang,
|
"CC="+clang,
|
||||||
|
"CXX="+clang+"++",
|
||||||
)
|
)
|
||||||
builds.Go(func() error {
|
builds.Go(func() error {
|
||||||
_, err := runCmd(cmd)
|
_, err := runCmd(cmd)
|
||||||
return err
|
return err
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
appDir, err := runCmd(exec.Command("go", "list", "-f", "{{.Dir}}", "gioui.org/app/"))
|
appDir, err := runCmd(exec.Command("go", "list", "-tags", bi.tags, "-f", "{{.Dir}}", "gioui.org/app/"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -253,7 +262,7 @@ func compileAndroid(tmpDir string, tools *androidTools, bi *buildInfo) (err erro
|
|||||||
}
|
}
|
||||||
if len(javaFiles) > 0 {
|
if len(javaFiles) > 0 {
|
||||||
classes := filepath.Join(tmpDir, "classes")
|
classes := filepath.Join(tmpDir, "classes")
|
||||||
if err := os.MkdirAll(classes, 0755); err != nil {
|
if err := os.MkdirAll(classes, 0o755); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
javac := exec.Command(
|
javac := exec.Command(
|
||||||
@@ -350,17 +359,17 @@ func exeAndroid(tmpDir string, tools *androidTools, bi *buildInfo, extraJars, pe
|
|||||||
})
|
})
|
||||||
classFiles = append(classFiles, extraJars...)
|
classFiles = append(classFiles, extraJars...)
|
||||||
dexDir := filepath.Join(tmpDir, "apk")
|
dexDir := filepath.Join(tmpDir, "apk")
|
||||||
if err := os.MkdirAll(dexDir, 0755); err != nil {
|
if err := os.MkdirAll(dexDir, 0o755); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
minSDK := max(bi.minsdk, 16)
|
||||||
// https://developer.android.com/distribute/best-practices/develop/target-sdk
|
// https://developer.android.com/distribute/best-practices/develop/target-sdk
|
||||||
targetSDK := 31
|
targetSDK := 35
|
||||||
if bi.minsdk > targetSDK {
|
if bi.targetsdk > 0 {
|
||||||
targetSDK = bi.minsdk
|
targetSDK = bi.targetsdk
|
||||||
}
|
}
|
||||||
minSDK := 16
|
if minSDK > targetSDK {
|
||||||
if bi.minsdk > minSDK {
|
targetSDK = minSDK
|
||||||
minSDK = bi.minsdk
|
|
||||||
}
|
}
|
||||||
if len(classFiles) > 0 {
|
if len(classFiles) > 0 {
|
||||||
d8 := exec.Command(
|
d8 := exec.Command(
|
||||||
@@ -385,7 +394,7 @@ func exeAndroid(tmpDir string, tools *androidTools, bi *buildInfo, extraJars, pe
|
|||||||
v21Dir := filepath.Join(resDir, "values-v21")
|
v21Dir := filepath.Join(resDir, "values-v21")
|
||||||
v26mipmapDir := filepath.Join(resDir, `mipmap-anydpi-v26`)
|
v26mipmapDir := filepath.Join(resDir, `mipmap-anydpi-v26`)
|
||||||
for _, dir := range []string{valDir, v21Dir, v26mipmapDir} {
|
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
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -405,24 +414,28 @@ func exeAndroid(tmpDir string, tools *androidTools, bi *buildInfo, extraJars, pe
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = ioutil.WriteFile(filepath.Join(v26mipmapDir, `ic_launcher.xml`), []byte(`<?xml version="1.0" encoding="utf-8"?>
|
err = os.WriteFile(filepath.Join(v26mipmapDir, `ic_launcher.xml`), []byte(`<?xml version="1.0" encoding="utf-8"?>
|
||||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<background android:drawable="@mipmap/ic_launcher_adaptive" />
|
<background android:drawable="@mipmap/ic_launcher_adaptive" />
|
||||||
<foreground android:drawable="@mipmap/ic_launcher_adaptive" />
|
<foreground android:drawable="@mipmap/ic_launcher_adaptive" />
|
||||||
</adaptive-icon>`), 0660)
|
</adaptive-icon>`), 0o660)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
iconSnip = `android:icon="@mipmap/ic_launcher"`
|
iconSnip = `android:icon="@mipmap/ic_launcher"`
|
||||||
}
|
}
|
||||||
err = ioutil.WriteFile(filepath.Join(valDir, "themes.xml"), []byte(themes), 0660)
|
err = os.WriteFile(filepath.Join(valDir, "themes.xml"), []byte(themes), 0o660)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = ioutil.WriteFile(filepath.Join(v21Dir, "themes.xml"), []byte(themesV21), 0660)
|
err = os.WriteFile(filepath.Join(v21Dir, "themes.xml"), []byte(themesV21), 0o660)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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")
|
resZip := filepath.Join(tmpDir, "resources.zip")
|
||||||
aapt2 := filepath.Join(tools.buildtools, "aapt2")
|
aapt2 := filepath.Join(tools.buildtools, "aapt2")
|
||||||
_, err = runCmd(exec.Command(
|
_, err = runCmd(exec.Command(
|
||||||
@@ -436,46 +449,27 @@ func exeAndroid(tmpDir string, tools *androidTools, bi *buildInfo, extraJars, pe
|
|||||||
|
|
||||||
// Link APK.
|
// Link APK.
|
||||||
permissions, features := getPermissions(perms)
|
permissions, features := getPermissions(perms)
|
||||||
appName := strings.Title(bi.name)
|
appName := UppercaseName(bi.name)
|
||||||
manifestSrc := manifestData{
|
manifestSrc := manifestData{
|
||||||
AppID: bi.appID,
|
AppID: bi.appID,
|
||||||
Version: bi.version,
|
Version: bi.version,
|
||||||
MinSDK: minSDK,
|
MinSDK: minSDK,
|
||||||
TargetSDK: targetSDK,
|
TargetSDK: targetSDK,
|
||||||
Permissions: permissions,
|
Permissions: permissions,
|
||||||
Features: features,
|
Features: features,
|
||||||
IconSnip: iconSnip,
|
IconSnip: iconSnip,
|
||||||
AppName: appName,
|
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(
|
manifestBuffer, err := renderAndroidManifest(manifestSrc)
|
||||||
`<?xml version="1.0" encoding="utf-8"?>
|
if err != nil {
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
package="{{.AppID}}"
|
|
||||||
android:versionCode="{{.Version}}"
|
|
||||||
android:versionName="1.0.{{.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 {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
manifest := filepath.Join(tmpDir, "AndroidManifest.xml")
|
manifest := filepath.Join(tmpDir, "AndroidManifest.xml")
|
||||||
if err := ioutil.WriteFile(manifest, manifestBuffer.Bytes(), 0660); err != nil {
|
if err := os.WriteFile(manifest, manifestBuffer, 0o660); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -677,7 +671,7 @@ func signAAB(tmpDir string, aabFile string, tools *androidTools, bi *buildInfo)
|
|||||||
}
|
}
|
||||||
|
|
||||||
var alias string
|
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 {
|
if i, _ := fmt.Sscanf(t, "Alias name: %s", &alias); i > 0 {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -740,6 +734,141 @@ func defaultAndroidKeystore(tmpDir string, bi *buildInfo) error {
|
|||||||
return err
|
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) {
|
func findNDK(androidHome string) (string, error) {
|
||||||
ndks, err := filepath.Glob(filepath.Join(androidHome, "ndk", "*"))
|
ndks, err := filepath.Glob(filepath.Join(androidHome, "ndk", "*"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -748,7 +877,7 @@ func findNDK(androidHome string) (string, error) {
|
|||||||
if bestNDK, found := latestVersionPath(ndks); found {
|
if bestNDK, found := latestVersionPath(ndks); found {
|
||||||
return bestNDK, nil
|
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")
|
ndkBundle := filepath.Join(androidHome, "ndk-bundle")
|
||||||
if _, err := os.Stat(ndkBundle); err == nil {
|
if _, err := os.Stat(ndkBundle); err == nil {
|
||||||
return ndkBundle, nil
|
return ndkBundle, nil
|
||||||
@@ -761,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) {
|
func findKeytool() (string, error) {
|
||||||
@@ -769,7 +898,9 @@ func findKeytool() (string, error) {
|
|||||||
if javaHome == "" {
|
if javaHome == "" {
|
||||||
return exec.LookPath("keytool")
|
return exec.LookPath("keytool")
|
||||||
}
|
}
|
||||||
keytool := filepath.Join(javaHome, "jre", "bin", "keytool"+exeSuffix)
|
|
||||||
|
// bin, instead of "jre". "jre" was for older JVM it seems.
|
||||||
|
keytool := filepath.Join(javaHome, "bin", "keytool"+exeSuffix)
|
||||||
if _, err := os.Stat(keytool); err != nil {
|
if _, err := os.Stat(keytool); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
@@ -989,12 +1120,12 @@ func (z *zipWriter) Close() error {
|
|||||||
|
|
||||||
func (z *zipWriter) Create(name string) io.Writer {
|
func (z *zipWriter) Create(name string) io.Writer {
|
||||||
if z.err != nil {
|
if z.err != nil {
|
||||||
return ioutil.Discard
|
return io.Discard
|
||||||
}
|
}
|
||||||
w, err := z.w.Create(name)
|
w, err := z.w.Create(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
z.err = err
|
z.err = err
|
||||||
return ioutil.Discard
|
return io.Discard
|
||||||
}
|
}
|
||||||
return &errWriter{w: w, err: &z.err}
|
return &errWriter{w: w, err: &z.err}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
+98
-29
@@ -9,22 +9,35 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
|
"unicode"
|
||||||
|
"unicode/utf8"
|
||||||
)
|
)
|
||||||
|
|
||||||
type buildInfo struct {
|
type buildInfo struct {
|
||||||
appID string
|
appID string
|
||||||
archs []string
|
archs []string
|
||||||
ldflags string
|
ldflags string
|
||||||
minsdk int
|
minsdk int
|
||||||
name string
|
targetsdk int
|
||||||
pkgDir string
|
name string
|
||||||
pkgPath string
|
pkgDir string
|
||||||
iconPath string
|
pkgPath string
|
||||||
tags string
|
iconPath string
|
||||||
target string
|
tags string
|
||||||
version int
|
target string
|
||||||
key string
|
version Semver
|
||||||
password string
|
key string
|
||||||
|
password string
|
||||||
|
notaryAppleID string
|
||||||
|
notaryPassword string
|
||||||
|
notaryTeamID string
|
||||||
|
schemes []string
|
||||||
|
packageQueries []string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Semver struct {
|
||||||
|
Major, Minor, Patch int
|
||||||
|
VersionCode uint32
|
||||||
}
|
}
|
||||||
|
|
||||||
func newBuildInfo(pkgPath string) (*buildInfo, error) {
|
func newBuildInfo(pkgPath string) (*buildInfo, error) {
|
||||||
@@ -37,24 +50,66 @@ func newBuildInfo(pkgPath string) (*buildInfo, error) {
|
|||||||
if *iconPath != "" {
|
if *iconPath != "" {
|
||||||
appIcon = *iconPath
|
appIcon = *iconPath
|
||||||
}
|
}
|
||||||
|
appName := getPkgName(pkgMetadata)
|
||||||
|
if *name != "" {
|
||||||
|
appName = *name
|
||||||
|
}
|
||||||
|
ver, err := parseSemver(*version)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
sp := *signPass
|
||||||
|
if sp == "" {
|
||||||
|
sp = os.Getenv("GOGIO_SIGNPASS")
|
||||||
|
}
|
||||||
bi := &buildInfo{
|
bi := &buildInfo{
|
||||||
appID: appID,
|
appID: appID,
|
||||||
archs: getArchs(),
|
archs: getArchs(),
|
||||||
ldflags: getLdFlags(appID),
|
ldflags: getLdFlags(appID),
|
||||||
minsdk: *minsdk,
|
minsdk: *minsdk,
|
||||||
name: getPkgName(pkgMetadata),
|
targetsdk: *targetsdk,
|
||||||
pkgDir: pkgMetadata.Dir,
|
name: appName,
|
||||||
pkgPath: pkgPath,
|
pkgDir: pkgMetadata.Dir,
|
||||||
iconPath: appIcon,
|
pkgPath: pkgPath,
|
||||||
tags: *extraTags,
|
iconPath: appIcon,
|
||||||
target: *target,
|
tags: *extraTags,
|
||||||
version: *version,
|
target: *target,
|
||||||
key: *signKey,
|
version: ver,
|
||||||
password: *signPass,
|
key: *signKey,
|
||||||
|
password: sp,
|
||||||
|
notaryAppleID: *notaryID,
|
||||||
|
notaryPassword: *notaryPass,
|
||||||
|
notaryTeamID: *notaryTeamID,
|
||||||
|
schemes: getCommaList(*schemes),
|
||||||
|
packageQueries: getCommaList(*pkgQueries),
|
||||||
}
|
}
|
||||||
return bi, nil
|
return bi, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UppercaseName returns a string with its first rune in uppercase.
|
||||||
|
func UppercaseName(name string) string {
|
||||||
|
ch, w := utf8.DecodeRuneInString(name)
|
||||||
|
return string(unicode.ToUpper(ch)) + name[w:]
|
||||||
|
}
|
||||||
|
|
||||||
|
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 || sv.String() != v {
|
||||||
|
return Semver{}, fmt.Errorf("invalid semver: %q (must match major.minor.patch.versioncode)", v)
|
||||||
|
}
|
||||||
|
return sv, nil
|
||||||
|
}
|
||||||
|
|
||||||
func getArchs() []string {
|
func getArchs() []string {
|
||||||
if *archNames != "" {
|
if *archNames != "" {
|
||||||
return strings.Split(*archNames, ",")
|
return strings.Split(*archNames, ",")
|
||||||
@@ -73,6 +128,8 @@ func getArchs() []string {
|
|||||||
goarch = runtime.GOARCH
|
goarch = runtime.GOARCH
|
||||||
}
|
}
|
||||||
return []string{goarch}
|
return []string{goarch}
|
||||||
|
case "macos":
|
||||||
|
return []string{"arm64", "amd64"}
|
||||||
default:
|
default:
|
||||||
// TODO: Add flag tests.
|
// TODO: Add flag tests.
|
||||||
panic("The target value has already been validated, this will never execute.")
|
panic("The target value has already been validated, this will never execute.")
|
||||||
@@ -85,6 +142,9 @@ func getLdFlags(appID string) string {
|
|||||||
ldflags = append(ldflags, strings.Split(extra, " ")...)
|
ldflags = append(ldflags, strings.Split(extra, " ")...)
|
||||||
}
|
}
|
||||||
// Pass appID along, to be used for logging on platforms like Android.
|
// Pass appID along, to be used for logging on platforms like Android.
|
||||||
|
ldflags = append(ldflags, fmt.Sprintf("-X gioui.org/app.ID=%s", appID))
|
||||||
|
// Support earlier Gio versions that had a separate app id recorded.
|
||||||
|
// TODO: delete this in the future.
|
||||||
ldflags = append(ldflags, fmt.Sprintf("-X gioui.org/app/internal/log.appID=%s", appID))
|
ldflags = append(ldflags, fmt.Sprintf("-X gioui.org/app/internal/log.appID=%s", appID))
|
||||||
// Pass along all remaining arguments to the app.
|
// Pass along all remaining arguments to the app.
|
||||||
if appArgs := flag.Args()[1:]; len(appArgs) > 0 {
|
if appArgs := flag.Args()[1:]; len(appArgs) > 0 {
|
||||||
@@ -96,17 +156,26 @@ func getLdFlags(appID string) string {
|
|||||||
return strings.Join(ldflags, " ")
|
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 {
|
type packageMetadata struct {
|
||||||
PkgPath string
|
PkgPath string
|
||||||
Dir string
|
Dir string
|
||||||
}
|
}
|
||||||
|
|
||||||
func getPkgMetadata(pkgPath string) (*packageMetadata, error) {
|
func getPkgMetadata(pkgPath string) (*packageMetadata, error) {
|
||||||
pkgImportPath, err := runCmd(exec.Command("go", "list", "-f", "{{.ImportPath}}", pkgPath))
|
pkgImportPath, err := runCmd(exec.Command("go", "list", "-tags", *extraTags, "-f", "{{.ImportPath}}", pkgPath))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
pkgDir, err := runCmd(exec.Command("go", "list", "-f", "{{.Dir}}", pkgPath))
|
pkgDir, err := runCmd(exec.Command("go", "list", "-tags", *extraTags, "-f", "{{.Dir}}", pkgPath))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -130,7 +199,7 @@ func getAppID(pkgMetadata *packageMetadata) string {
|
|||||||
name = "." + domain[0]
|
name = "." + domain[0]
|
||||||
domain[0] = "localhost"
|
domain[0] = "localhost"
|
||||||
} else {
|
} else {
|
||||||
for i := 0; i < len(domain)/2; i++ {
|
for i := range len(domain) / 2 {
|
||||||
opp := len(domain) - 1 - i
|
opp := len(domain) - 1 - i
|
||||||
domain[i], domain[opp] = domain[opp], domain[i]
|
domain[i], domain[opp] = domain[opp], domain[i]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,14 +2,12 @@ package main
|
|||||||
|
|
||||||
import "testing"
|
import "testing"
|
||||||
|
|
||||||
type expval struct {
|
|
||||||
in, out string
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAppID(t *testing.T) {
|
func TestAppID(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
tests := []expval{
|
tests := []struct {
|
||||||
|
in, out string
|
||||||
|
}{
|
||||||
{"example", "localhost.example"},
|
{"example", "localhost.example"},
|
||||||
{"example.com", "com.example"},
|
{"example.com", "com.example"},
|
||||||
{"www.example.com", "com.example.www"},
|
{"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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+2
-3
@@ -10,7 +10,6 @@ import (
|
|||||||
"image"
|
"image"
|
||||||
"image/color"
|
"image/color"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"runtime"
|
"runtime"
|
||||||
@@ -83,7 +82,7 @@ func TestEndToEnd(t *testing.T) {
|
|||||||
}{
|
}{
|
||||||
{"X11 using go import path", &X11TestDriver{}, testdataWithGoImportPkgPath, ""},
|
{"X11 using go import path", &X11TestDriver{}, testdataWithGoImportPkgPath, ""},
|
||||||
{"X11", &X11TestDriver{}, testdataWithRelativePkgPath, ""},
|
{"X11", &X11TestDriver{}, testdataWithRelativePkgPath, ""},
|
||||||
{"X11 with custom rendering", &X11TestDriver{}, customRenderTestdataWithRelativePkgPath, "openbsd"},
|
{"X11 with custom rendering", &X11TestDriver{}, customRenderTestdataWithRelativePkgPath, "openbsd,darwin,windows,netbsd"},
|
||||||
// Doesn't work on the builders.
|
// Doesn't work on the builders.
|
||||||
//{"Wayland", &WaylandTestDriver{}, testdataWithRelativePkgPath},
|
//{"Wayland", &WaylandTestDriver{}, testdataWithRelativePkgPath},
|
||||||
{"JS", &JSTestDriver{}, testdataWithRelativePkgPath, ""},
|
{"JS", &JSTestDriver{}, testdataWithRelativePkgPath, ""},
|
||||||
@@ -316,7 +315,7 @@ func (d *driverBase) needPrograms(names ...string) {
|
|||||||
|
|
||||||
func (d *driverBase) tempDir(name string) string {
|
func (d *driverBase) tempDir(name string) string {
|
||||||
d.Helper()
|
d.Helper()
|
||||||
dir, err := ioutil.TempDir("", name)
|
dir, err := os.MkdirTemp("", name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
d.Fatal(err)
|
d.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|||||||
+30
-4
@@ -18,7 +18,8 @@ Compiled Java class files from jar files in the package directory are
|
|||||||
included in Android builds.
|
included in Android builds.
|
||||||
|
|
||||||
The mandatory -target flag selects the target platform: ios or android for the
|
The mandatory -target flag selects the target platform: ios or android for the
|
||||||
mobile platforms, tvos for Apple's tvOS, js for WebAssembly/WebGL.
|
mobile platforms, tvos for Apple's tvOS, js for WebAssembly/WebGL, macos for
|
||||||
|
MacOS and windows for Windows.
|
||||||
|
|
||||||
The -arch flag specifies a comma separated list of GOARCHs to include. The
|
The -arch flag specifies a comma separated list of GOARCHs to include. The
|
||||||
default is all supported architectures.
|
default is all supported architectures.
|
||||||
@@ -46,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
|
iOS and tvOS. A bundle id must be provisioned through Xcode before the gogio
|
||||||
tool can use it.
|
tool can use it.
|
||||||
|
|
||||||
The -version flag specifies the integer version code for Android and the last
|
The -version flag specifies the semantic version for -buildmode exe. It must
|
||||||
component of the 1.0.X version for iOS and tvOS.
|
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,
|
For Android builds the -minsdk flag specify the minimum SDK level. For example,
|
||||||
use -minsdk 22 to target Android 5.1 (Lollipop) and later.
|
use -minsdk 22 to target Android 5.1 (Lollipop) and later.
|
||||||
@@ -58,12 +60,36 @@ use -mindk 10 to target Windows 10 and later, -minsdk 6 for Windows Vista and la
|
|||||||
For iOS builds the -minsdk flag specify the minimum iOS version. For example,
|
For iOS builds the -minsdk flag specify the minimum iOS version. For example,
|
||||||
use -mindk 15 to target iOS 15.0 and later.
|
use -mindk 15 to target iOS 15.0 and later.
|
||||||
|
|
||||||
|
For Android builds the -targetsdk flag specify the target SDK level. For example,
|
||||||
|
use -targetsdk 33 to target Android 13 (Tiramisu) and later.
|
||||||
|
|
||||||
The -work flag prints the path to the working directory and suppress
|
The -work flag prints the path to the working directory and suppress
|
||||||
its deletion.
|
its deletion.
|
||||||
|
|
||||||
The -x flag will print all the external commands executed by the gogio tool.
|
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.
|
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 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.
|
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.
|
||||||
|
|
||||||
|
The -notarypass flag specifies the password of the Apple ID, ignored if -notaryid is not
|
||||||
|
provided. That must be an app-specific password, see https://support.apple.com/en-us/HT204397
|
||||||
|
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.
|
||||||
`
|
`
|
||||||
|
|||||||
@@ -20,8 +20,8 @@ import (
|
|||||||
|
|
||||||
"gioui.org/app"
|
"gioui.org/app"
|
||||||
"gioui.org/gpu"
|
"gioui.org/gpu"
|
||||||
|
"gioui.org/io/event"
|
||||||
"gioui.org/io/pointer"
|
"gioui.org/io/pointer"
|
||||||
"gioui.org/io/system"
|
|
||||||
"gioui.org/layout"
|
"gioui.org/layout"
|
||||||
"gioui.org/op"
|
"gioui.org/op"
|
||||||
"gioui.org/op/clip"
|
"gioui.org/op/clip"
|
||||||
@@ -98,7 +98,8 @@ type eglContext struct {
|
|||||||
func main() {
|
func main() {
|
||||||
go func() {
|
go func() {
|
||||||
// Set CustomRenderer so we can provide our own rendering context.
|
// 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 {
|
if err := loop(w); err != nil {
|
||||||
log.Fatal(err)
|
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.
|
// eglMakeCurrent binds a context to an operating system thread. Prevent Go from switching thread.
|
||||||
runtime.LockOSThread()
|
runtime.LockOSThread()
|
||||||
for e := range w.Events() {
|
for {
|
||||||
switch e := e.(type) {
|
switch e := w.Event().(type) {
|
||||||
case app.ViewEvent:
|
case app.ViewEvent:
|
||||||
ve = e
|
ve = e
|
||||||
init = true
|
init = true
|
||||||
if size != (image.Point{}) {
|
if size != (image.Point{}) {
|
||||||
recreateContext()
|
recreateContext()
|
||||||
}
|
}
|
||||||
case system.DestroyEvent:
|
case app.DestroyEvent:
|
||||||
return e.Err
|
return e.Err
|
||||||
case system.FrameEvent:
|
case app.FrameEvent:
|
||||||
if init && size != e.Size {
|
if init && size != e.Size {
|
||||||
size = e.Size
|
size = e.Size
|
||||||
recreateContext()
|
recreateContext()
|
||||||
@@ -183,7 +184,7 @@ func loop(w *app.Window) error {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
// Build ops.
|
// Build ops.
|
||||||
gtx := layout.NewContext(&ops, e)
|
gtx := app.NewContext(&ops, e)
|
||||||
|
|
||||||
// Clear background to white, even on embedded platforms such as webassembly.
|
// 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})
|
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")
|
log.Println("frame")
|
||||||
|
|
||||||
// Trigger window resize detection in ANGLE.
|
// Trigger window resize detection in ANGLE.
|
||||||
@@ -351,13 +352,16 @@ func (w *quarterWidget) Layout(gtx layout.Context) layout.Dimensions {
|
|||||||
defer clip.Rect(image.Rectangle{
|
defer clip.Rect(image.Rectangle{
|
||||||
Max: image.Pt(gtx.Constraints.Max.X, gtx.Constraints.Max.Y),
|
Max: image.Pt(gtx.Constraints.Max.X, gtx.Constraints.Max.Y),
|
||||||
}).Push(gtx.Ops).Pop()
|
}).Push(gtx.Ops).Pop()
|
||||||
pointer.InputOp{
|
event.Op(gtx.Ops, w)
|
||||||
Tag: w,
|
for {
|
||||||
Types: pointer.Press,
|
e, ok := gtx.Event(pointer.Filter{
|
||||||
}.Add(gtx.Ops)
|
Target: w,
|
||||||
|
Kinds: pointer.Press,
|
||||||
for _, e := range gtx.Events(w) {
|
})
|
||||||
if e, ok := e.(pointer.Event); ok && e.Type == pointer.Press {
|
if !ok {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if e, ok := e.(pointer.Event); ok && e.Kind == pointer.Press {
|
||||||
w.clicked = !w.clicked
|
w.clicked = !w.clicked
|
||||||
// notify when we're done updating the frame.
|
// notify when we're done updating the frame.
|
||||||
notify = notifyInvalidate
|
notify = notifyInvalidate
|
||||||
|
|||||||
@@ -10,8 +10,8 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
|
|
||||||
"gioui.org/app"
|
"gioui.org/app"
|
||||||
|
"gioui.org/io/event"
|
||||||
"gioui.org/io/pointer"
|
"gioui.org/io/pointer"
|
||||||
"gioui.org/io/system"
|
|
||||||
"gioui.org/layout"
|
"gioui.org/layout"
|
||||||
"gioui.org/op"
|
"gioui.org/op"
|
||||||
"gioui.org/op/clip"
|
"gioui.org/op/clip"
|
||||||
@@ -20,7 +20,7 @@ import (
|
|||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
go func() {
|
go func() {
|
||||||
w := app.NewWindow()
|
w := new(app.Window)
|
||||||
if err := loop(w); err != nil {
|
if err := loop(w); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -61,12 +61,12 @@ func loop(w *app.Window) error {
|
|||||||
|
|
||||||
var ops op.Ops
|
var ops op.Ops
|
||||||
for {
|
for {
|
||||||
e := <-w.Events()
|
e := w.Event()
|
||||||
switch e := e.(type) {
|
switch e := e.(type) {
|
||||||
case system.DestroyEvent:
|
case app.DestroyEvent:
|
||||||
return e.Err
|
return e.Err
|
||||||
case system.FrameEvent:
|
case app.FrameEvent:
|
||||||
gtx := layout.NewContext(&ops, e)
|
gtx := app.NewContext(&ops, e)
|
||||||
// Clear background to white, even on embedded platforms such as webassembly.
|
// 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})
|
paint.Fill(gtx.Ops, color.NRGBA{A: 0xff, R: 0xff, G: 0xff, B: 0xff})
|
||||||
layout.Flex{Axis: layout.Vertical}.Layout(gtx,
|
layout.Flex{Axis: layout.Vertical}.Layout(gtx,
|
||||||
@@ -126,13 +126,18 @@ func (w *quarterWidget) Layout(gtx layout.Context) layout.Dimensions {
|
|||||||
defer clip.Rect(image.Rectangle{
|
defer clip.Rect(image.Rectangle{
|
||||||
Max: image.Pt(gtx.Constraints.Max.X, gtx.Constraints.Max.Y),
|
Max: image.Pt(gtx.Constraints.Max.X, gtx.Constraints.Max.Y),
|
||||||
}).Push(gtx.Ops).Pop()
|
}).Push(gtx.Ops).Pop()
|
||||||
pointer.InputOp{
|
event.Op(gtx.Ops, w)
|
||||||
Tag: w,
|
filter := pointer.Filter{
|
||||||
Types: pointer.Press,
|
Target: w,
|
||||||
}.Add(gtx.Ops)
|
Kinds: pointer.Press,
|
||||||
|
}
|
||||||
|
|
||||||
for _, e := range gtx.Events(w) {
|
for {
|
||||||
if e, ok := e.(pointer.Event); ok && e.Type == pointer.Press {
|
e, ok := gtx.Event(filter)
|
||||||
|
if !ok {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if e, ok := e.(pointer.Event); ok && e.Kind == pointer.Press {
|
||||||
w.clicked = !w.clicked
|
w.clicked = !w.clicked
|
||||||
// notify when we're done updating the frame.
|
// notify when we're done updating the frame.
|
||||||
notify = notifyInvalidate
|
notify = notifyInvalidate
|
||||||
|
|||||||
+189
-129
@@ -4,17 +4,19 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"archive/zip"
|
"archive/zip"
|
||||||
|
"bytes"
|
||||||
"crypto/sha1"
|
"crypto/sha1"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"slices"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"text/template"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"golang.org/x/sync/errgroup"
|
"golang.org/x/sync/errgroup"
|
||||||
@@ -22,6 +24,8 @@ import (
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
minIOSVersion = 10
|
minIOSVersion = 10
|
||||||
|
// Some Metal features require tvOS 11
|
||||||
|
minTVOSVersion = 11
|
||||||
// Metal is available from iOS 8 on devices, yet from version 13 on the
|
// Metal is available from iOS 8 on devices, yet from version 13 on the
|
||||||
// simulator.
|
// simulator.
|
||||||
minSimulatorVersion = 13
|
minSimulatorVersion = 13
|
||||||
@@ -33,7 +37,7 @@ func buildIOS(tmpDir, target string, bi *buildInfo) error {
|
|||||||
case "archive":
|
case "archive":
|
||||||
framework := *destPath
|
framework := *destPath
|
||||||
if framework == "" {
|
if framework == "" {
|
||||||
framework = fmt.Sprintf("%s.framework", strings.Title(appName))
|
framework = fmt.Sprintf("%s.framework", UppercaseName(appName))
|
||||||
}
|
}
|
||||||
return archiveIOS(tmpDir, target, framework, bi)
|
return archiveIOS(tmpDir, target, framework, bi)
|
||||||
case "exe":
|
case "exe":
|
||||||
@@ -55,11 +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)
|
||||||
}
|
|
||||||
tmpFramework := filepath.Join(tmpDir, "Gio.framework")
|
|
||||||
if err := archiveIOS(tmpDir, target, tmpFramework, bi); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
if !forDevice && !strings.HasSuffix(out, ".app") {
|
if !forDevice && !strings.HasSuffix(out, ".app") {
|
||||||
return fmt.Errorf("the specified output directory %q does not end in .app or .ipa", out)
|
return fmt.Errorf("the specified output directory %q does not end in .app or .ipa", out)
|
||||||
@@ -69,13 +69,35 @@ func buildIOS(tmpDir, target string, bi *buildInfo) error {
|
|||||||
}
|
}
|
||||||
payload := filepath.Join(tmpDir, "Payload")
|
payload := filepath.Join(tmpDir, "Payload")
|
||||||
appDir := filepath.Join(payload, appName+".app")
|
appDir := filepath.Join(payload, appName+".app")
|
||||||
if err := os.MkdirAll(appDir, 0755); err != nil {
|
if err := os.MkdirAll(appDir, 0o755); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := exeIOS(tmpDir, target, appDir, bi); err != nil {
|
if err := exeIOS(tmpDir, target, appDir, bi); err != nil {
|
||||||
return err
|
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 err
|
||||||
}
|
}
|
||||||
return zipDir(out, tmpDir, "Payload")
|
return zipDir(out, tmpDir, "Payload")
|
||||||
@@ -84,16 +106,8 @@ func buildIOS(tmpDir, target string, bi *buildInfo) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func signIOS(bi *buildInfo, tmpDir, app string) error {
|
// signApple is shared between iOS and macOS.
|
||||||
home, err := os.UserHomeDir()
|
func signApple(appID, tmpDir, embedded, app string, provisions []string) error {
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
provPattern := filepath.Join(home, "Library", "MobileDevice", "Provisioning Profiles", "*.mobileprovision")
|
|
||||||
provisions, err := filepath.Glob(provPattern)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
provInfo := filepath.Join(tmpDir, "provision.plist")
|
provInfo := filepath.Join(tmpDir, "provision.plist")
|
||||||
var avail []string
|
var avail []string
|
||||||
for _, prov := range provisions {
|
for _, prov := range provisions {
|
||||||
@@ -117,17 +131,23 @@ func signIOS(bi *buildInfo, tmpDir, app string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
expAppID := fmt.Sprintf("%s.%s", appIDPrefix, bi.appID)
|
expAppID := fmt.Sprintf("%s.%s", appIDPrefix, appID)
|
||||||
avail = append(avail, provAppID)
|
avail = append(avail, provAppID)
|
||||||
if expAppID != provAppID {
|
if expAppID != provAppID {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// Copy provisioning file.
|
// Copy provisioning file.
|
||||||
embedded := filepath.Join(app, "embedded.mobileprovision")
|
|
||||||
if err := copyFile(embedded, prov); err != nil {
|
if err := copyFile(embedded, prov); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -142,15 +162,23 @@ func signIOS(bi *buildInfo, tmpDir, app string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
entFile := filepath.Join(tmpDir, "entitlements.plist")
|
entFile := filepath.Join(tmpDir, "entitlements.plist")
|
||||||
if err := ioutil.WriteFile(entFile, []byte(entitlements), 0660); err != nil {
|
if err := os.WriteFile(entFile, []byte(entitlements), 0o660); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
identity := sha1.Sum(certDER)
|
identity := sha1.Sum(certDER)
|
||||||
idHex := hex.EncodeToString(identity[:])
|
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 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 {
|
func exeIOS(tmpDir, target, app string, bi *buildInfo) error {
|
||||||
@@ -160,36 +188,10 @@ func exeIOS(tmpDir, target, app string, bi *buildInfo) error {
|
|||||||
if err := os.RemoveAll(app); err != nil {
|
if err := os.RemoveAll(app); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := os.Mkdir(app, 0755); err != nil {
|
if err := os.Mkdir(app, 0o755); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
mainm := filepath.Join(tmpDir, "main.m")
|
appName := UppercaseName(bi.name)
|
||||||
const mainmSrc = `@import UIKit;
|
|
||||||
@import Gio;
|
|
||||||
|
|
||||||
@interface GioAppDelegate : UIResponder <UIApplicationDelegate>
|
|
||||||
@property (strong, nonatomic) UIWindow *window;
|
|
||||||
@end
|
|
||||||
|
|
||||||
@implementation GioAppDelegate
|
|
||||||
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
|
|
||||||
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
|
|
||||||
GioViewController *controller = [[GioViewController alloc] initWithNibName:nil bundle:nil];
|
|
||||||
self.window.rootViewController = controller;
|
|
||||||
[self.window makeKeyAndVisible];
|
|
||||||
return YES;
|
|
||||||
}
|
|
||||||
@end
|
|
||||||
|
|
||||||
int main(int argc, char * argv[]) {
|
|
||||||
@autoreleasepool {
|
|
||||||
return UIApplicationMain(argc, argv, nil, NSStringFromClass([GioAppDelegate class]));
|
|
||||||
}
|
|
||||||
}`
|
|
||||||
if err := ioutil.WriteFile(mainm, []byte(mainmSrc), 0660); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
appName := strings.Title(bi.name)
|
|
||||||
exe := filepath.Join(app, appName)
|
exe := filepath.Join(app, appName)
|
||||||
lipo := exec.Command("xcrun", "lipo", "-o", exe, "-create")
|
lipo := exec.Command("xcrun", "lipo", "-o", exe, "-create")
|
||||||
var builds errgroup.Group
|
var builds errgroup.Group
|
||||||
@@ -198,17 +200,31 @@ int main(int argc, char * argv[]) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
cflags = append(cflags,
|
||||||
|
"-fobjc-arc",
|
||||||
|
fmt.Sprintf("-miphoneos-version-min=%d.0", bi.minsdk),
|
||||||
|
)
|
||||||
|
cflagsLine := strings.Join(cflags, " ")
|
||||||
exeSlice := filepath.Join(tmpDir, "app-"+a)
|
exeSlice := filepath.Join(tmpDir, "app-"+a)
|
||||||
lipo.Args = append(lipo.Args, exeSlice)
|
lipo.Args = append(lipo.Args, exeSlice)
|
||||||
compile := exec.Command(clang, cflags...)
|
compile := exec.Command(
|
||||||
compile.Args = append(compile.Args,
|
"go",
|
||||||
"-Werror",
|
"build",
|
||||||
"-fmodules",
|
"-ldflags=-s -w "+bi.ldflags,
|
||||||
"-fobjc-arc",
|
|
||||||
"-x", "objective-c",
|
|
||||||
"-F", tmpDir,
|
|
||||||
"-o", exeSlice,
|
"-o", exeSlice,
|
||||||
mainm,
|
"-tags", bi.tags,
|
||||||
|
bi.pkgPath,
|
||||||
|
)
|
||||||
|
compile.Env = append(
|
||||||
|
os.Environ(),
|
||||||
|
"GOOS=ios",
|
||||||
|
"GOARCH="+a,
|
||||||
|
"CGO_ENABLED=1",
|
||||||
|
"CC="+clang,
|
||||||
|
"CXX="+clang+"++",
|
||||||
|
"CGO_CFLAGS="+cflagsLine,
|
||||||
|
"CGO_CXXFLAGS="+cflagsLine,
|
||||||
|
"CGO_LDFLAGS=-lresolv "+cflagsLine,
|
||||||
)
|
)
|
||||||
builds.Go(func() error {
|
builds.Go(func() error {
|
||||||
_, err := runCmd(compile)
|
_, err := runCmd(compile)
|
||||||
@@ -223,7 +239,7 @@ int main(int argc, char * argv[]) {
|
|||||||
}
|
}
|
||||||
infoPlist := buildInfoPlist(bi)
|
infoPlist := buildInfoPlist(bi)
|
||||||
plistFile := filepath.Join(app, "Info.plist")
|
plistFile := filepath.Join(app, "Info.plist")
|
||||||
if err := ioutil.WriteFile(plistFile, []byte(infoPlist), 0660); err != nil {
|
if err := os.WriteFile(plistFile, []byte(infoPlist), 0o660); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if _, err := os.Stat(bi.iconPath); err == nil {
|
if _, err := os.Stat(bi.iconPath); err == nil {
|
||||||
@@ -251,13 +267,16 @@ int main(int argc, char * argv[]) {
|
|||||||
// iosIcons returns the asset plist file to be merged into Info.plist.
|
// iosIcons returns the asset plist file to be merged into Info.plist.
|
||||||
func iosIcons(bi *buildInfo, tmpDir, appDir, icon string) (string, error) {
|
func iosIcons(bi *buildInfo, tmpDir, appDir, icon string) (string, error) {
|
||||||
assets := filepath.Join(tmpDir, "Assets.xcassets")
|
assets := filepath.Join(tmpDir, "Assets.xcassets")
|
||||||
if err := os.Mkdir(assets, 0700); err != nil {
|
if err := os.Mkdir(assets, 0o700); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
appIcon := filepath.Join(assets, "AppIcon.appiconset")
|
appIcon := filepath.Join(assets, "AppIcon.appiconset")
|
||||||
err := buildIcons(appIcon, icon, []iconVariant{
|
err := buildIcons(appIcon, icon, []iconVariant{
|
||||||
{path: "ios_2x.png", size: 120},
|
{path: "ios_2x.png", size: 120},
|
||||||
{path: "ios_3x.png", size: 180},
|
{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
|
// The App Store icon is not allowed to contain
|
||||||
// transparent pixels.
|
// transparent pixels.
|
||||||
{path: "ios_store.png", size: 1024, fill: true},
|
{path: "ios_store.png", size: 1024, fill: true},
|
||||||
@@ -266,29 +285,47 @@ func iosIcons(bi *buildInfo, tmpDir, appDir, icon string) (string, error) {
|
|||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
contentJson := `{
|
contentJson := `{
|
||||||
"images" : [
|
"images": [
|
||||||
{
|
{
|
||||||
"size" : "60x60",
|
"size": "60x60",
|
||||||
"idiom" : "iphone",
|
"idiom": "iphone",
|
||||||
"filename" : "ios_2x.png",
|
"filename": "ios_2x.png",
|
||||||
"scale" : "2x"
|
"scale": "2x"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"size" : "60x60",
|
"size": "60x60",
|
||||||
"idiom" : "iphone",
|
"idiom": "iphone",
|
||||||
"filename" : "ios_3x.png",
|
"filename": "ios_3x.png",
|
||||||
"scale" : "3x"
|
"scale": "3x"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"size" : "1024x1024",
|
"size": "76x76",
|
||||||
"idiom" : "ios-marketing",
|
"idiom": "ipad",
|
||||||
"filename" : "ios_store.png",
|
"filename": "ipad_1x.png",
|
||||||
"scale" : "1x"
|
"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")
|
contentFile := filepath.Join(appIcon, "Contents.json")
|
||||||
if err := ioutil.WriteFile(contentFile, []byte(contentJson), 0600); err != nil {
|
if err := os.WriteFile(contentFile, []byte(contentJson), 0o600); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
assetPlist := filepath.Join(tmpDir, "assets.plist")
|
assetPlist := filepath.Join(tmpDir, "assets.plist")
|
||||||
@@ -310,7 +347,7 @@ func iosIcons(bi *buildInfo, tmpDir, appDir, icon string) (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func buildInfoPlist(bi *buildInfo) string {
|
func buildInfoPlist(bi *buildInfo) string {
|
||||||
appName := strings.Title(bi.name)
|
appName := UppercaseName(bi.name)
|
||||||
platform := iosPlatformFor(bi.target)
|
platform := iosPlatformFor(bi.target)
|
||||||
var supportPlatform string
|
var supportPlatform string
|
||||||
switch bi.target {
|
switch bi.target {
|
||||||
@@ -319,36 +356,57 @@ func buildInfoPlist(bi *buildInfo) string {
|
|||||||
case "tvos":
|
case "tvos":
|
||||||
supportPlatform = "AppleTVOS"
|
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">
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
<key>CFBundleDevelopmentRegion</key>
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
<string>en</string>
|
<string>en</string>
|
||||||
<key>CFBundleExecutable</key>
|
<key>CFBundleExecutable</key>
|
||||||
<string>%s</string>
|
<string>{{.AppName}}</string>
|
||||||
<key>CFBundleIdentifier</key>
|
<key>CFBundleIdentifier</key>
|
||||||
<string>%s</string>
|
<string>{{.AppID}}</string>
|
||||||
<key>CFBundleInfoDictionaryVersion</key>
|
<key>CFBundleInfoDictionaryVersion</key>
|
||||||
<string>6.0</string>
|
<string>6.0</string>
|
||||||
<key>CFBundleName</key>
|
<key>CFBundleName</key>
|
||||||
<string>%s</string>
|
<string>{{.AppName}}</string>
|
||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>APPL</string>
|
<string>APPL</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>1.0.%d</string>
|
<string>{{.Version}}</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>%d</string>
|
<string>{{.VersionCode}}</string>
|
||||||
<key>UILaunchStoryboardName</key>
|
<key>UILaunchStoryboardName</key>
|
||||||
<string>LaunchScreen</string>
|
<string>LaunchScreen</string>
|
||||||
<key>UIRequiredDeviceCapabilities</key>
|
<key>UIRequiredDeviceCapabilities</key>
|
||||||
<array><string>arm64</string></array>
|
<array><string>arm64</string></array>
|
||||||
<key>DTPlatformName</key>
|
<key>DTPlatformName</key>
|
||||||
<string>%s</string>
|
<string>{{.Platform}}</string>
|
||||||
<key>DTPlatformVersion</key>
|
<key>DTPlatformVersion</key>
|
||||||
<string>12.4</string>
|
<string>12.4</string>
|
||||||
<key>MinimumOSVersion</key>
|
<key>MinimumOSVersion</key>
|
||||||
<string>%d</string>
|
<string>{{.MinVersion}}.0</string>
|
||||||
<key>UIDeviceFamily</key>
|
<key>UIDeviceFamily</key>
|
||||||
<array>
|
<array>
|
||||||
<integer>1</integer>
|
<integer>1</integer>
|
||||||
@@ -356,14 +414,17 @@ func buildInfoPlist(bi *buildInfo) string {
|
|||||||
</array>
|
</array>
|
||||||
<key>CFBundleSupportedPlatforms</key>
|
<key>CFBundleSupportedPlatforms</key>
|
||||||
<array>
|
<array>
|
||||||
<string>%s</string>
|
<string>{{.SupportPlatform}}</string>
|
||||||
</array>
|
</array>
|
||||||
<key>UISupportedInterfaceOrientations</key>
|
<key>UISupportedInterfaceOrientations</key>
|
||||||
<array>
|
<array>
|
||||||
<string>UIInterfaceOrientationPortrait</string>
|
<string>UIInterfaceOrientationPortrait</string>
|
||||||
|
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||||
</array>
|
</array>
|
||||||
|
<key>UIRequiresFullScreen</key>
|
||||||
|
<true/>
|
||||||
<key>DTCompiler</key>
|
<key>DTCompiler</key>
|
||||||
<string>com.apple.compilers.llvm.clang.1_0</string>
|
<string>com.apple.compilers.llvm.clang.1_0</string>
|
||||||
<key>DTPlatformBuild</key>
|
<key>DTPlatformBuild</key>
|
||||||
@@ -371,13 +432,38 @@ func buildInfoPlist(bi *buildInfo) string {
|
|||||||
<key>DTSDKBuild</key>
|
<key>DTSDKBuild</key>
|
||||||
<string>16G73</string>
|
<string>16G73</string>
|
||||||
<key>DTSDKName</key>
|
<key>DTSDKName</key>
|
||||||
<string>%s12.4</string>
|
<string>{{.Platform}}12.4</string>
|
||||||
<key>DTXcode</key>
|
<key>DTXcode</key>
|
||||||
<string>1030</string>
|
<string>1030</string>
|
||||||
<key>DTXcodeBuild</key>
|
<key>DTXcodeBuild</key>
|
||||||
<string>10G8</string>
|
<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>
|
</dict>
|
||||||
</plist>`, appName, bi.appID, appName, bi.version, bi.version, 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 {
|
func iosPlatformFor(target string) string {
|
||||||
@@ -404,7 +490,7 @@ func archiveIOS(tmpDir, target, frameworkRoot string, bi *buildInfo) error {
|
|||||||
frameworkDir := filepath.Join(frameworkRoot, "Versions", "A")
|
frameworkDir := filepath.Join(frameworkRoot, "Versions", "A")
|
||||||
for _, dir := range []string{"Headers", "Modules"} {
|
for _, dir := range []string{"Headers", "Modules"} {
|
||||||
p := filepath.Join(frameworkDir, dir)
|
p := filepath.Join(frameworkDir, dir)
|
||||||
if err := os.MkdirAll(p, 0755); err != nil {
|
if err := os.MkdirAll(p, 0o755); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -423,16 +509,6 @@ func archiveIOS(tmpDir, target, frameworkRoot string, bi *buildInfo) error {
|
|||||||
lipo := exec.Command("xcrun", "lipo", "-o", exe, "-create")
|
lipo := exec.Command("xcrun", "lipo", "-o", exe, "-create")
|
||||||
var builds errgroup.Group
|
var builds errgroup.Group
|
||||||
tags := bi.tags
|
tags := bi.tags
|
||||||
goos := "ios"
|
|
||||||
supportsIOS, err := supportsGOOS("ios")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if !supportsIOS {
|
|
||||||
// Go 1.15 and earlier target iOS with GOOS=darwin, tags=ios.
|
|
||||||
goos = "darwin"
|
|
||||||
tags = "ios " + tags
|
|
||||||
}
|
|
||||||
for _, a := range bi.archs {
|
for _, a := range bi.archs {
|
||||||
clang, cflags, err := iosCompilerFor(target, a, bi.minsdk)
|
clang, cflags, err := iosCompilerFor(target, a, bi.minsdk)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -452,7 +528,7 @@ func archiveIOS(tmpDir, target, frameworkRoot string, bi *buildInfo) error {
|
|||||||
cflagsLine := strings.Join(cflags, " ")
|
cflagsLine := strings.Join(cflags, " ")
|
||||||
cmd.Env = append(
|
cmd.Env = append(
|
||||||
os.Environ(),
|
os.Environ(),
|
||||||
"GOOS="+goos,
|
"GOOS=ios",
|
||||||
"GOARCH="+a,
|
"GOARCH="+a,
|
||||||
"CGO_ENABLED=1",
|
"CGO_ENABLED=1",
|
||||||
"CC="+clang,
|
"CC="+clang,
|
||||||
@@ -470,7 +546,7 @@ func archiveIOS(tmpDir, target, frameworkRoot string, bi *buildInfo) error {
|
|||||||
if _, err := runCmd(lipo); err != nil {
|
if _, err := runCmd(lipo); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
appDir, err := runCmd(exec.Command("go", "list", "-f", "{{.Dir}}", "gioui.org/app/"))
|
appDir, err := runCmd(exec.Command("go", "list", "-tags", tags, "-f", "{{.Dir}}", "gioui.org/app/"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -485,25 +561,7 @@ func archiveIOS(tmpDir, target, frameworkRoot string, bi *buildInfo) error {
|
|||||||
export *
|
export *
|
||||||
}`, framework)
|
}`, framework)
|
||||||
moduleFile := filepath.Join(frameworkDir, "Modules", "module.modulemap")
|
moduleFile := filepath.Join(frameworkDir, "Modules", "module.modulemap")
|
||||||
return ioutil.WriteFile(moduleFile, []byte(module), 0644)
|
return os.WriteFile(moduleFile, []byte(module), 0o644)
|
||||||
}
|
|
||||||
|
|
||||||
func supportsGOOS(wantGoos string) (bool, error) {
|
|
||||||
geese, err := runCmd(exec.Command("go", "tool", "dist", "list"))
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
for _, pair := range strings.Split(geese, "\n") {
|
|
||||||
s := strings.SplitN(pair, "/", 2)
|
|
||||||
if len(s) != 2 {
|
|
||||||
return false, fmt.Errorf("go tool dist list: invalid GOOS/GOARCH pair: %s", pair)
|
|
||||||
}
|
|
||||||
goos := s[0]
|
|
||||||
if goos == wantGoos {
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func iosCompilerFor(target, arch string, minsdk int) (string, []string, error) {
|
func iosCompilerFor(target, arch string, minsdk int) (string, []string, error) {
|
||||||
@@ -524,6 +582,9 @@ func iosCompilerFor(target, arch string, minsdk int) (string, []string, error) {
|
|||||||
platformSDK += "os"
|
platformSDK += "os"
|
||||||
if minsdk == 0 {
|
if minsdk == 0 {
|
||||||
minsdk = minIOSVersion
|
minsdk = minIOSVersion
|
||||||
|
if target == "tvos" {
|
||||||
|
minsdk = minTVOSVersion
|
||||||
|
}
|
||||||
}
|
}
|
||||||
case "386", "amd64":
|
case "386", "amd64":
|
||||||
platformOS += "-simulator"
|
platformOS += "-simulator"
|
||||||
@@ -543,7 +604,6 @@ func iosCompilerFor(target, arch string, minsdk int) (string, []string, error) {
|
|||||||
return "", nil, err
|
return "", nil, err
|
||||||
}
|
}
|
||||||
cflags := []string{
|
cflags := []string{
|
||||||
"-fembed-bitcode",
|
|
||||||
"-arch", allArchs[arch].iosArch,
|
"-arch", allArchs[arch].iosArch,
|
||||||
"-isysroot", sdkPath,
|
"-isysroot", sdkPath,
|
||||||
"-m" + platformOS + "-version-min=" + strconv.Itoa(minsdk),
|
"-m" + platformOS + "-version-min=" + strconv.Itoa(minsdk),
|
||||||
|
|||||||
+2
-1
@@ -30,6 +30,7 @@ func (d *JSTestDriver) Start(path string) {
|
|||||||
if raceEnabled {
|
if raceEnabled {
|
||||||
d.Skipf("js/wasm doesn't support -race; skipping")
|
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.
|
// First, build the app.
|
||||||
dir := d.tempDir("gio-endtoend-js")
|
dir := d.tempDir("gio-endtoend-js")
|
||||||
@@ -60,7 +61,7 @@ func (d *JSTestDriver) Start(path string) {
|
|||||||
pr, pw := io.Pipe()
|
pr, pw := io.Pipe()
|
||||||
d.Cleanup(func() { pw.Close() })
|
d.Cleanup(func() { pw.Close() })
|
||||||
d.output = pr
|
d.output = pr
|
||||||
chromedp.ListenTarget(ctx, func(ev interface{}) {
|
chromedp.ListenTarget(ctx, func(ev any) {
|
||||||
switch ev := ev.(type) {
|
switch ev := ev.(type) {
|
||||||
case *runtime.EventConsoleAPICalled:
|
case *runtime.EventConsoleAPICalled:
|
||||||
switch ev.Type {
|
switch ev.Type {
|
||||||
|
|||||||
+12
-8
@@ -6,7 +6,6 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@@ -21,7 +20,7 @@ func buildJS(bi *buildInfo) error {
|
|||||||
if out == "" {
|
if out == "" {
|
||||||
out = bi.name
|
out = bi.name
|
||||||
}
|
}
|
||||||
if err := os.MkdirAll(out, 0700); err != nil {
|
if err := os.MkdirAll(out, 0o700); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
cmd := exec.Command(
|
cmd := exec.Command(
|
||||||
@@ -45,11 +44,11 @@ func buildJS(bi *buildInfo) error {
|
|||||||
var faviconPath string
|
var faviconPath string
|
||||||
if _, err := os.Stat(bi.iconPath); err == nil {
|
if _, err := os.Stat(bi.iconPath); err == nil {
|
||||||
// Copy icon to the output folder
|
// Copy icon to the output folder
|
||||||
icon, err := ioutil.ReadFile(bi.iconPath)
|
icon, err := os.ReadFile(bi.iconPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := ioutil.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
|
return err
|
||||||
}
|
}
|
||||||
faviconPath = filepath.Base(bi.iconPath)
|
faviconPath = filepath.Base(bi.iconPath)
|
||||||
@@ -71,7 +70,7 @@ func buildJS(bi *buildInfo) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := ioutil.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
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,9 +78,14 @@ func buildJS(bi *buildInfo) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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 {
|
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{
|
pkgs, err := packages.Load(&packages.Config{
|
||||||
Mode: packages.NeedName | packages.NeedFiles | packages.NeedImports | packages.NeedDeps,
|
Mode: packages.NeedName | packages.NeedFiles | packages.NeedImports | packages.NeedDeps,
|
||||||
@@ -170,7 +174,7 @@ const (
|
|||||||
</html>`
|
</html>`
|
||||||
// jsSetGo sets the `window.go` variable.
|
// jsSetGo sets the `window.go` variable.
|
||||||
jsSetGo = `(() => {
|
jsSetGo = `(() => {
|
||||||
window.go = {argv: [], env: {}, importObject: {go: {}}};
|
window.go = {argv: [], env: {}, importObject: {go: {}, gojs: {}}};
|
||||||
const argv = new URLSearchParams(location.search).get("argv");
|
const argv = new URLSearchParams(location.search).get("argv");
|
||||||
if (argv) {
|
if (argv) {
|
||||||
window.go["argv"] = argv.split(" ");
|
window.go["argv"] = argv.split(" ");
|
||||||
|
|||||||
@@ -0,0 +1,285 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"text/template"
|
||||||
|
)
|
||||||
|
|
||||||
|
func buildMac(tmpDir string, bi *buildInfo) error {
|
||||||
|
builder := &macBuilder{TempDir: tmpDir}
|
||||||
|
builder.DestDir = *destPath
|
||||||
|
if builder.DestDir == "" {
|
||||||
|
builder.DestDir = bi.pkgPath
|
||||||
|
}
|
||||||
|
|
||||||
|
name := bi.name
|
||||||
|
if *destPath != "" {
|
||||||
|
if filepath.Ext(*destPath) != ".app" {
|
||||||
|
return fmt.Errorf("invalid output name %q, it must end with `.app`", *destPath)
|
||||||
|
}
|
||||||
|
name = filepath.Base(*destPath)
|
||||||
|
}
|
||||||
|
name = strings.TrimSuffix(name, ".app")
|
||||||
|
|
||||||
|
if bi.appID == "" {
|
||||||
|
return errors.New("app id is empty; use -appid to set it")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := builder.setIcon(bi.iconPath); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.setInfo(bi, name)
|
||||||
|
|
||||||
|
for _, arch := range bi.archs {
|
||||||
|
tmpDest := filepath.Join(builder.TempDir, filepath.Base(builder.DestDir))
|
||||||
|
finalDest := builder.DestDir
|
||||||
|
if len(bi.archs) > 1 {
|
||||||
|
tmpDest = filepath.Join(builder.TempDir, name+"_"+arch+".app")
|
||||||
|
finalDest = filepath.Join(builder.DestDir, name+"_"+arch+".app")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := builder.buildProgram(bi, tmpDest, name, arch); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if bi.key != "" {
|
||||||
|
if err := builder.signProgram(bi, tmpDest, name, arch); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := dittozip(tmpDest, tmpDest+".zip"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if bi.notaryAppleID != "" {
|
||||||
|
if err := builder.notarize(bi, tmpDest+".zip"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := dittounzip(tmpDest+".zip", finalDest); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type macBuilder struct {
|
||||||
|
TempDir string
|
||||||
|
DestDir string
|
||||||
|
|
||||||
|
Icons []byte
|
||||||
|
Manifest []byte
|
||||||
|
Entitlements []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *macBuilder) setIcon(path string) (err error) {
|
||||||
|
if _, err := os.Stat(path); err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
out := filepath.Join(b.TempDir, "iconset.iconset")
|
||||||
|
if err := os.MkdirAll(out, 0o777); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = buildIcons(out, path, []iconVariant{
|
||||||
|
{path: "icon_512x512@2x.png", size: 1024},
|
||||||
|
{path: "icon_512x512.png", size: 512},
|
||||||
|
{path: "icon_256x256@2x.png", size: 512},
|
||||||
|
{path: "icon_256x256.png", size: 256},
|
||||||
|
{path: "icon_128x128@2x.png", size: 256},
|
||||||
|
{path: "icon_128x128.png", size: 128},
|
||||||
|
{path: "icon_64x64@2x.png", size: 128},
|
||||||
|
{path: "icon_64x64.png", size: 64},
|
||||||
|
{path: "icon_32x32@2x.png", size: 64},
|
||||||
|
{path: "icon_32x32.png", size: 32},
|
||||||
|
{path: "icon_16x16@2x.png", size: 32},
|
||||||
|
{path: "icon_16x16.png", size: 16},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := exec.Command("iconutil",
|
||||||
|
"-c", "icns", out,
|
||||||
|
"-o", filepath.Join(b.TempDir, "icon.icns"))
|
||||||
|
if _, err := runCmd(cmd); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
b.Icons, err = os.ReadFile(filepath.Join(b.TempDir, "icon.icns"))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
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">
|
||||||
|
<dict>
|
||||||
|
<key>CFBundleExecutable</key>
|
||||||
|
<string>{{.Name}}</string>
|
||||||
|
<key>CFBundleIconFile</key>
|
||||||
|
<string>icon.icns</string>
|
||||||
|
<key>CFBundleIdentifier</key>
|
||||||
|
<string>{{.Bundle}}</string>
|
||||||
|
<key>NSHighResolutionCapable</key>
|
||||||
|
<true/>
|
||||||
|
<key>CFBundlePackageType</key>
|
||||||
|
<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 {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var manifest bytes.Buffer
|
||||||
|
if err := t.Execute(&manifest, manifestSrc); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
b.Manifest = manifest.Bytes()
|
||||||
|
|
||||||
|
b.Entitlements = []byte(`<?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>com.apple.security.cs.allow-unsigned-executable-memory</key>
|
||||||
|
<true/>
|
||||||
|
<key>com.apple.security.cs.allow-jit</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
</plist>`)
|
||||||
|
}
|
||||||
|
|
||||||
|
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), 0o755); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(b.Icons) > 0 {
|
||||||
|
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, 0o755); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := exec.Command(
|
||||||
|
"go",
|
||||||
|
"build",
|
||||||
|
"-ldflags="+buildInfo.ldflags,
|
||||||
|
"-tags="+buildInfo.tags,
|
||||||
|
"-o", filepath.Join(binDest, "/Contents/MacOS/"+name),
|
||||||
|
buildInfo.pkgPath,
|
||||||
|
)
|
||||||
|
cmd.Env = append(
|
||||||
|
os.Environ(),
|
||||||
|
"GOOS=darwin",
|
||||||
|
"GOARCH="+arch,
|
||||||
|
"CGO_ENABLED=1", // Required to cross-compile between AMD/ARM
|
||||||
|
)
|
||||||
|
_, err := runCmd(cmd)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
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, 0o777); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
xattr := exec.Command("xattr", "-rc", binDest)
|
||||||
|
if _, err := runCmd(xattr); err != nil {
|
||||||
|
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",
|
||||||
|
"--force",
|
||||||
|
"--options", "runtime",
|
||||||
|
"--entitlements", options,
|
||||||
|
"--sign", buildInfo.key,
|
||||||
|
binDest,
|
||||||
|
)
|
||||||
|
_, err := runCmd(cmd)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *macBuilder) notarize(buildInfo *buildInfo, binDest string) error {
|
||||||
|
cmd := exec.Command(
|
||||||
|
"xcrun",
|
||||||
|
"notarytool",
|
||||||
|
"submit",
|
||||||
|
binDest,
|
||||||
|
"--apple-id", buildInfo.notaryAppleID,
|
||||||
|
"--team-id", buildInfo.notaryTeamID,
|
||||||
|
"--wait",
|
||||||
|
)
|
||||||
|
|
||||||
|
if buildInfo.notaryPassword != "" {
|
||||||
|
cmd.Args = append(cmd.Args, "--password", buildInfo.notaryPassword)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := runCmd(cmd)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func dittozip(input, output string) error {
|
||||||
|
cmd := exec.Command("ditto", "-c", "-k", "-X", "--rsrc", input, output)
|
||||||
|
|
||||||
|
_, err := runCmd(cmd)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func dittounzip(input, output string) error {
|
||||||
|
cmd := exec.Command("ditto", "-x", "-k", "-X", "--rsrc", input, output)
|
||||||
|
|
||||||
|
_, err := runCmd(cmd)
|
||||||
|
return err
|
||||||
|
}
|
||||||
+14
-9
@@ -11,7 +11,6 @@ import (
|
|||||||
"image/color"
|
"image/color"
|
||||||
"image/png"
|
"image/png"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@@ -25,18 +24,25 @@ var (
|
|||||||
target = flag.String("target", "", "specify target (ios, tvos, android, js).\n")
|
target = flag.String("target", "", "specify target (ios, tvos, android, js).\n")
|
||||||
archNames = flag.String("arch", "", "specify architecture(s) to include (arm, arm64, amd64).")
|
archNames = flag.String("arch", "", "specify architecture(s) to include (arm, arm64, amd64).")
|
||||||
minsdk = flag.Int("minsdk", 0, "specify the minimum supported operating system level")
|
minsdk = flag.Int("minsdk", 0, "specify the minimum supported operating system level")
|
||||||
|
targetsdk = flag.Int("targetsdk", 0, "specify the target supported operating system level for Android")
|
||||||
buildMode = flag.String("buildmode", "exe", "specify buildmode (archive, exe)")
|
buildMode = flag.String("buildmode", "exe", "specify buildmode (archive, exe)")
|
||||||
destPath = flag.String("o", "", "output file or directory.\nFor -target ios or tvos, use the .app suffix to target simulators.")
|
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)")
|
appID = flag.String("appid", "", "app identifier (for -buildmode=exe)")
|
||||||
version = flag.Int("version", 1, "app version (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. The versioncode is not used for iOS and macOS.")
|
||||||
printCommands = flag.Bool("x", false, "print the commands")
|
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.")
|
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")
|
linkMode = flag.String("linkmode", "", "set the -linkmode flag of the go tool")
|
||||||
extraLdflags = flag.String("ldflags", "", "extra flags to the Go linker")
|
extraLdflags = flag.String("ldflags", "", "extra flags to the Go linker")
|
||||||
extraTags = flag.String("tags", "", "extra tags to the Go tool")
|
extraTags = flag.String("tags", "", "extra tags to the Go tool")
|
||||||
iconPath = flag.String("icon", "", "specify an icon for iOS and Android")
|
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.")
|
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() {
|
func main() {
|
||||||
@@ -65,14 +71,11 @@ func flagValidate() error {
|
|||||||
if pkgPathArg == "" {
|
if pkgPathArg == "" {
|
||||||
return errors.New("specify a package")
|
return errors.New("specify a package")
|
||||||
}
|
}
|
||||||
if len(flag.Args()) > 1 {
|
|
||||||
return fmt.Errorf("build arguments must be specified before the package (%q)", pkgPathArg)
|
|
||||||
}
|
|
||||||
if *target == "" {
|
if *target == "" {
|
||||||
return errors.New("please specify -target")
|
return errors.New("please specify -target")
|
||||||
}
|
}
|
||||||
switch *target {
|
switch *target {
|
||||||
case "ios", "tvos", "android", "js", "windows":
|
case "ios", "tvos", "android", "js", "windows", "macos":
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("invalid -target %s", *target)
|
return fmt.Errorf("invalid -target %s", *target)
|
||||||
}
|
}
|
||||||
@@ -85,7 +88,7 @@ func flagValidate() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func build(bi *buildInfo) error {
|
func build(bi *buildInfo) error {
|
||||||
tmpDir, err := ioutil.TempDir("", "gogio-")
|
tmpDir, err := os.MkdirTemp("", "gogio-")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -103,6 +106,8 @@ func build(bi *buildInfo) error {
|
|||||||
return buildAndroid(tmpDir, bi)
|
return buildAndroid(tmpDir, bi)
|
||||||
case "windows":
|
case "windows":
|
||||||
return buildWindows(tmpDir, bi)
|
return buildWindows(tmpDir, bi)
|
||||||
|
case "macos":
|
||||||
|
return buildMac(tmpDir, bi)
|
||||||
default:
|
default:
|
||||||
panic("unreachable")
|
panic("unreachable")
|
||||||
}
|
}
|
||||||
@@ -196,7 +201,7 @@ func buildIcons(baseDir, icon string, variants []iconVariant) error {
|
|||||||
v := v
|
v := v
|
||||||
resizes.Go(func() (err error) {
|
resizes.Go(func() (err error) {
|
||||||
path := filepath.Join(baseDir, v.path)
|
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
|
return err
|
||||||
}
|
}
|
||||||
f, err := os.Create(path)
|
f, err := os.Create(path)
|
||||||
|
|||||||
@@ -19,6 +19,12 @@ var AndroidPermissions = map[string][]string{
|
|||||||
"android.permission.READ_EXTERNAL_STORAGE",
|
"android.permission.READ_EXTERNAL_STORAGE",
|
||||||
"android.permission.WRITE_EXTERNAL_STORAGE",
|
"android.permission.WRITE_EXTERNAL_STORAGE",
|
||||||
},
|
},
|
||||||
|
"wakelock": {
|
||||||
|
"android.permission.WAKE_LOCK",
|
||||||
|
},
|
||||||
|
"microphone": {
|
||||||
|
"android.permission.RECORD_AUDIO",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
var AndroidFeatures = map[string][]string{
|
var AndroidFeatures = map[string][]string{
|
||||||
|
|||||||
@@ -174,7 +174,7 @@ func (d *WaylandTestDriver) Screenshot() image.Image {
|
|||||||
return img
|
return img
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *WaylandTestDriver) swaymsg(args ...interface{}) {
|
func (d *WaylandTestDriver) swaymsg(args ...any) {
|
||||||
strs := []string{"--socket", d.socket}
|
strs := []string{"--socket", d.socket}
|
||||||
for _, arg := range args {
|
for _, arg := range args {
|
||||||
strs = append(strs, fmt.Sprint(arg))
|
strs = append(strs, fmt.Sprint(arg))
|
||||||
|
|||||||
+13
-11
@@ -7,12 +7,10 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"image/png"
|
"image/png"
|
||||||
"io"
|
"io"
|
||||||
"math"
|
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
"text/template"
|
"text/template"
|
||||||
|
|
||||||
@@ -40,10 +38,6 @@ func buildWindows(tmpDir string, bi *buildInfo) error {
|
|||||||
if sdk > 10 {
|
if sdk > 10 {
|
||||||
return fmt.Errorf("invalid minsdk (%d) it's higher than Windows 10", sdk)
|
return fmt.Errorf("invalid minsdk (%d) it's higher than Windows 10", sdk)
|
||||||
}
|
}
|
||||||
version := strconv.Itoa(bi.version)
|
|
||||||
if bi.version > math.MaxUint16 {
|
|
||||||
return fmt.Errorf("version (%d) is larger than the maximum (%d)", bi.version, math.MaxUint16)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, arch := range bi.archs {
|
for _, arch := range bi.archs {
|
||||||
builder.Coff = coff.NewRSRC()
|
builder.Coff = coff.NewRSRC()
|
||||||
@@ -54,7 +48,7 @@ func buildWindows(tmpDir string, bi *buildInfo) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := builder.embedManifest(windowsManifest{
|
if err := builder.embedManifest(windowsManifest{
|
||||||
Version: "1.0.0." + version,
|
Version: bi.version.String(),
|
||||||
WindowsVersion: sdk,
|
WindowsVersion: sdk,
|
||||||
Name: name,
|
Name: name,
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
@@ -62,8 +56,8 @@ func buildWindows(tmpDir string, bi *buildInfo) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := builder.embedInfo(windowsResources{
|
if err := builder.embedInfo(windowsResources{
|
||||||
Version: [2]uint32{uint32(1) << 16, uint32(bi.version)},
|
Version: [2]uint32{uint32(bi.version.Major), uint32(bi.version.Minor)<<16 | uint32(bi.version.Patch)},
|
||||||
VersionHuman: "1.0.0." + version,
|
VersionHuman: bi.version.String(),
|
||||||
Name: name,
|
Name: name,
|
||||||
Language: 0x0400, // Process Default Language: https://docs.microsoft.com/en-us/previous-versions/ms957130(v=msdn.10)
|
Language: 0x0400, // Process Default Language: https://docs.microsoft.com/en-us/previous-versions/ms957130(v=msdn.10)
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
@@ -208,10 +202,18 @@ func (b *windowsBuilder) buildProgram(buildInfo *buildInfo, name string, arch st
|
|||||||
dest = filepath.Join(filepath.Dir(b.DestDir), name+"_"+arch+".exe")
|
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(
|
cmd := exec.Command(
|
||||||
"go",
|
"go",
|
||||||
"build",
|
"build",
|
||||||
"-ldflags=-H=windowsgui "+buildInfo.ldflags,
|
"-ldflags=-H=windowsgui "+ldflags,
|
||||||
"-tags="+buildInfo.tags,
|
"-tags="+buildInfo.tags,
|
||||||
"-o", dest,
|
"-o", dest,
|
||||||
buildInfo.pkgPath,
|
buildInfo.pkgPath,
|
||||||
@@ -364,7 +366,7 @@ const (
|
|||||||
valueText uint16 = 1
|
valueText uint16 = 1
|
||||||
)
|
)
|
||||||
|
|
||||||
func newValue(valueType uint16, key string, input interface{}) windowsInfoValue {
|
func newValue(valueType uint16, key string, input any) windowsInfoValue {
|
||||||
v := windowsInfoValue{
|
v := windowsInfoValue{
|
||||||
Type: valueType,
|
Type: valueType,
|
||||||
Length: 6,
|
Length: 6,
|
||||||
|
|||||||
+1
-1
@@ -145,7 +145,7 @@ func (d *X11TestDriver) Screenshot() image.Image {
|
|||||||
return img
|
return img
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *X11TestDriver) xdotool(args ...interface{}) string {
|
func (d *X11TestDriver) xdotool(args ...any) string {
|
||||||
d.Helper()
|
d.Helper()
|
||||||
strs := make([]string, len(args))
|
strs := make([]string, len(args))
|
||||||
for i, arg := range args {
|
for i, arg := range args {
|
||||||
|
|||||||
+581
@@ -0,0 +1,581 @@
|
|||||||
|
// SPDX-License-Identifier: Unlicense OR MIT
|
||||||
|
|
||||||
|
// Command svg2gio converts SVG files to Gio functions. Only a limited subset of
|
||||||
|
// SVG files are supported.
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/xml"
|
||||||
|
"errors"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"go/format"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"unicode"
|
||||||
|
|
||||||
|
"gioui.org/f32"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
pkg = flag.String("pkg", "", "Go package")
|
||||||
|
output = flag.String("o", "svg.go", "Output Go file")
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
flag.Parse()
|
||||||
|
if *pkg == "" {
|
||||||
|
fmt.Fprintf(os.Stderr, "specify a package name (-pkg)\n")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
args := flag.Args()
|
||||||
|
if err := convertAll(args); err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "%v\n", err)
|
||||||
|
os.Exit(2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Points []float32
|
||||||
|
|
||||||
|
func (p *Points) UnmarshalText(text []byte) error {
|
||||||
|
for {
|
||||||
|
text = bytes.TrimLeft(text, "\t\n")
|
||||||
|
if len(text) == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
var num []byte
|
||||||
|
end := bytes.IndexAny(text, " ,")
|
||||||
|
if end != -1 {
|
||||||
|
num = text[:end]
|
||||||
|
text = text[end+1:]
|
||||||
|
} else {
|
||||||
|
num = text
|
||||||
|
text = nil
|
||||||
|
}
|
||||||
|
f, err := strconv.ParseFloat(string(num), 32)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*p = append(*p, float32(f))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type Transform f32.Affine2D
|
||||||
|
|
||||||
|
func (t *Transform) UnmarshalText(text []byte) error {
|
||||||
|
switch {
|
||||||
|
case bytes.HasPrefix(text, []byte("matrix(")) && bytes.HasSuffix(text, []byte(")")):
|
||||||
|
trans := text[7 : len(text)-1]
|
||||||
|
var p Points
|
||||||
|
if err := p.UnmarshalText(trans); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(p) != 6 {
|
||||||
|
return fmt.Errorf("malformed transform matrix: %q", text)
|
||||||
|
}
|
||||||
|
*t = Transform(f32.NewAffine2D(p[0], p[2], p[4], p[1], p[3], p[5]))
|
||||||
|
return nil
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unsupported transform: %q", text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Fill struct {
|
||||||
|
Transform Transform `xml:"transform,attr"`
|
||||||
|
Fill Color `xml:"fill,attr"`
|
||||||
|
Stroke Color `xml:"stroke,attr"`
|
||||||
|
StrokeLinejoin string `xml:"stroke-linejoin,attr"`
|
||||||
|
StrokeLinecap string `xml:"stroke-linecap,attr"`
|
||||||
|
StrokeWidth float32 `xml:"stroke-width,attr"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Color struct {
|
||||||
|
Set bool
|
||||||
|
Value int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Color) UnmarshalText(text []byte) error {
|
||||||
|
if string(text) == "none" {
|
||||||
|
*c = Color{}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if !bytes.HasPrefix(text, []byte("#")) {
|
||||||
|
return fmt.Errorf("invalid color: %q", text)
|
||||||
|
}
|
||||||
|
text = text[1:]
|
||||||
|
i, err := strconv.ParseInt(string(text), 16, 32)
|
||||||
|
// Implied alpha.
|
||||||
|
if len(text) == 6 {
|
||||||
|
i |= 0xff000000
|
||||||
|
}
|
||||||
|
*c = Color{
|
||||||
|
Set: true,
|
||||||
|
Value: int(i),
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func convertAll(files []string) error {
|
||||||
|
w := new(bytes.Buffer)
|
||||||
|
fmt.Fprintf(w, "// Code generated by gioui.org/cmd/svg2gio; DO NOT EDIT.\n\n")
|
||||||
|
fmt.Fprintf(w, "package %s\n\n", *pkg)
|
||||||
|
fmt.Fprintf(w, "import \"image/color\"\n")
|
||||||
|
fmt.Fprintf(w, "import \"math\"\n")
|
||||||
|
fmt.Fprintf(w, "import \"gioui.org/op\"\n")
|
||||||
|
fmt.Fprintf(w, "import \"gioui.org/op/clip\"\n")
|
||||||
|
fmt.Fprintf(w, "import \"gioui.org/op/paint\"\n")
|
||||||
|
fmt.Fprintf(w, "import \"gioui.org/f32\"\n\n")
|
||||||
|
fmt.Fprintf(w, "var ops op.Ops\n\n")
|
||||||
|
fmt.Fprintf(w, funcs)
|
||||||
|
for _, filename := range files {
|
||||||
|
if err := convert(w, filename); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
src, err := format.Source(w.Bytes())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return os.WriteFile(*output, src, 0o660)
|
||||||
|
}
|
||||||
|
|
||||||
|
func convert(w io.Writer, filename string) error {
|
||||||
|
base := filepath.Base(filename)
|
||||||
|
ext := filepath.Ext(base)
|
||||||
|
name := "Image_" + base[:len(base)-len(ext)]
|
||||||
|
|
||||||
|
fmt.Fprintf(w, "var %s struct {\n", name)
|
||||||
|
fmt.Fprintf(w, "ViewBox struct { Min, Max f32.Point }\n")
|
||||||
|
fmt.Fprintf(w, "Call op.CallOp\n\n")
|
||||||
|
fmt.Fprintf(w, "}\n")
|
||||||
|
fmt.Fprintf(w, "func init() {\n")
|
||||||
|
defer fmt.Fprintf(w, "}\n")
|
||||||
|
f, err := os.Open(filename)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
d := xml.NewDecoder(f)
|
||||||
|
if err := parse(w, d, name); err != nil {
|
||||||
|
line, col := d.InputPos()
|
||||||
|
return fmt.Errorf("%s:%d:%d: %w", filename, line, col, err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parse(w io.Writer, d *xml.Decoder, name string) error {
|
||||||
|
for {
|
||||||
|
tok, err := d.Token()
|
||||||
|
if err != nil {
|
||||||
|
if err == io.EOF {
|
||||||
|
return errors.New("unexpected end of file")
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
switch tok := tok.(type) {
|
||||||
|
case xml.StartElement:
|
||||||
|
if n := tok.Name.Local; n != "svg" {
|
||||||
|
return fmt.Errorf("invalid SVG root: <%s>", n)
|
||||||
|
}
|
||||||
|
if n := tok.Name.Space; n != "http://www.w3.org/2000/svg" {
|
||||||
|
return fmt.Errorf("unsupported SVG namespace: %s", n)
|
||||||
|
}
|
||||||
|
fmt.Fprintf(w, "m := op.Record(&ops)\n")
|
||||||
|
defer fmt.Fprintf(w, "%s.Call = m.Stop()\n", name)
|
||||||
|
for _, a := range tok.Attr {
|
||||||
|
if a.Name.Local == "viewBox" {
|
||||||
|
var p Points
|
||||||
|
if err := p.UnmarshalText([]byte(a.Value)); err != nil {
|
||||||
|
return fmt.Errorf("invalid viewBox attribute: %s", a.Value)
|
||||||
|
}
|
||||||
|
if len(p) != 4 {
|
||||||
|
return fmt.Errorf("invalid viewBox attribute: %s", a.Value)
|
||||||
|
}
|
||||||
|
fmt.Fprintf(w, "%s.ViewBox.Min = %s\n", name, point(f32.Pt(p[0], p[1])))
|
||||||
|
fmt.Fprintf(w, "%s.ViewBox.Max = %s\n", name, point(f32.Pt(p[2], p[3])))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return parseSVG(w, d)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func point(p f32.Point) string {
|
||||||
|
return fmt.Sprintf("f32.Pt(%g, %g)", p.X, p.Y)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Poly struct {
|
||||||
|
XMLName xml.Name
|
||||||
|
Points Points `xml:"points,attr"`
|
||||||
|
Fill
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Poly) Path(w io.Writer) error {
|
||||||
|
if len(p.Points) <= 1 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
pen := f32.Pt(p.Points[0], p.Points[1])
|
||||||
|
fmt.Fprintf(w, "p.MoveTo(%s)\n", point(pen))
|
||||||
|
last := pen
|
||||||
|
for i := 2; i < len(p.Points); i += 2 {
|
||||||
|
last = f32.Pt(p.Points[i], p.Points[i+1])
|
||||||
|
fmt.Fprintf(w, "p.LineTo(%s)\n", point(last))
|
||||||
|
}
|
||||||
|
if p.XMLName.Local == "polygon" && last != pen {
|
||||||
|
fmt.Fprintf(w, "p.LineTo(%s)\n", point(pen))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type Path struct {
|
||||||
|
D string `xml:"d,attr"`
|
||||||
|
Fill
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Path) Path(w io.Writer) error {
|
||||||
|
return printPathCommands(w, p.D)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Line struct {
|
||||||
|
X1 float32 `xml:"x1,attr"`
|
||||||
|
Y1 float32 `xml:"y1,attr"`
|
||||||
|
X2 float32 `xml:"x2,attr"`
|
||||||
|
Y2 float32 `xml:"y2,attr"`
|
||||||
|
Fill
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Line) Path(w io.Writer) error {
|
||||||
|
fmt.Fprintf(w, "p.MoveTo(%s)\n", point(f32.Pt(l.X1, l.Y1)))
|
||||||
|
fmt.Fprintf(w, "p.LineTo(%s)\n", point(f32.Pt(l.X2, l.Y2)))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type Ellipse struct {
|
||||||
|
Cx float32 `xml:"cx,attr"`
|
||||||
|
Cy float32 `xml:"cy,attr"`
|
||||||
|
Rx float32 `xml:"rx,attr"`
|
||||||
|
Ry float32 `xml:"ry,attr"`
|
||||||
|
Fill
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Ellipse) Path(w io.Writer) error {
|
||||||
|
c := f32.Pt(e.Cx, e.Cy)
|
||||||
|
r := f32.Pt(e.Rx, e.Ry)
|
||||||
|
fmt.Fprintf(w, "ellipse(&p, %s, %s)\n", point(c), point(r))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type Rect struct {
|
||||||
|
X float32 `xml:"x,attr"`
|
||||||
|
Y float32 `xml:"y,attr"`
|
||||||
|
Width float32 `xml:"width,attr"`
|
||||||
|
Height float32 `xml:"height,attr"`
|
||||||
|
Fill
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Rect) Path(w io.Writer) error {
|
||||||
|
o := f32.Pt(r.X, r.Y)
|
||||||
|
sz := f32.Pt(r.Width, r.Height)
|
||||||
|
fmt.Fprintf(w, "rect(&p, %s, %s)\n", point(o), point(sz))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type Circle struct {
|
||||||
|
Cx float32 `xml:"cx,attr"`
|
||||||
|
Cy float32 `xml:"cy,attr"`
|
||||||
|
R float32 `xml:"r,attr"`
|
||||||
|
Fill
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Circle) Path(w io.Writer) error {
|
||||||
|
center := f32.Pt(c.Cx, c.Cy)
|
||||||
|
r := f32.Pt(c.R, c.R)
|
||||||
|
fmt.Fprintf(w, "ellipse(&p, %s, %s)\n", point(center), point(r))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseSVG(w io.Writer, d *xml.Decoder) error {
|
||||||
|
for {
|
||||||
|
tok, err := d.Token()
|
||||||
|
if err != nil {
|
||||||
|
if err == io.EOF {
|
||||||
|
return errors.New("unexpected end of <svg> element")
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var start xml.StartElement
|
||||||
|
switch tok := tok.(type) {
|
||||||
|
case xml.EndElement:
|
||||||
|
return nil
|
||||||
|
case xml.StartElement:
|
||||||
|
start = tok
|
||||||
|
default:
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
var elem interface {
|
||||||
|
Path(w io.Writer) error
|
||||||
|
}
|
||||||
|
var fill *Fill
|
||||||
|
switch n := start.Name.Local; n {
|
||||||
|
case "g":
|
||||||
|
// Flatten groups.
|
||||||
|
if err := parseSVG(w, d); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
case "title":
|
||||||
|
d.Skip()
|
||||||
|
continue
|
||||||
|
case "polygon", "polyline":
|
||||||
|
p := new(Poly)
|
||||||
|
elem = p
|
||||||
|
fill = &p.Fill
|
||||||
|
case "path":
|
||||||
|
p := new(Path)
|
||||||
|
elem = p
|
||||||
|
fill = &p.Fill
|
||||||
|
case "line":
|
||||||
|
l := new(Line)
|
||||||
|
elem = l
|
||||||
|
fill = &l.Fill
|
||||||
|
case "ellipse":
|
||||||
|
e := new(Ellipse)
|
||||||
|
elem = e
|
||||||
|
fill = &e.Fill
|
||||||
|
case "rect":
|
||||||
|
r := new(Rect)
|
||||||
|
elem = r
|
||||||
|
fill = &r.Fill
|
||||||
|
case "circle":
|
||||||
|
c := new(Circle)
|
||||||
|
elem = c
|
||||||
|
fill = &c.Fill
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unsupported tag: <%s>", n)
|
||||||
|
}
|
||||||
|
if err := d.DecodeElement(elem, &start); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !fill.Fill.Set && !fill.Stroke.Set {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
fmt.Fprintf(w, "{\n")
|
||||||
|
trans := f32.Affine2D(fill.Transform)
|
||||||
|
if trans != (f32.Affine2D{}) {
|
||||||
|
sx, hx, ox, sy, hy, oy := trans.Elems()
|
||||||
|
fmt.Fprintf(w, "t := op.Affine(f32.NewAffine2D(%g, %g, %g, %g, %g, %g)).Push(&ops)\n", sx, hx, ox, sy, hy, oy)
|
||||||
|
}
|
||||||
|
fmt.Fprintf(w, "var p clip.Path\n")
|
||||||
|
fmt.Fprintf(w, "p.Begin(&ops)\n")
|
||||||
|
if err := elem.Path(w); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fmt.Fprintf(w, "spec := p.End()\n")
|
||||||
|
if fill.Fill.Set {
|
||||||
|
fmt.Fprintf(w, "paint.FillShape(&ops, argb(%#.8x), clip.Outline{Path: spec}.Op())\n", fill.Fill.Value)
|
||||||
|
}
|
||||||
|
if fill.Stroke.Set {
|
||||||
|
fmt.Fprintf(w, "paint.FillShape(&ops, argb(%#.8x), clip.Stroke{Width: %g, Path: spec}.Op())\n", fill.Stroke.Value, fill.StrokeWidth)
|
||||||
|
}
|
||||||
|
if trans != (f32.Affine2D{}) {
|
||||||
|
fmt.Fprintf(w, "t.Pop()\n")
|
||||||
|
}
|
||||||
|
fmt.Fprintf(w, "}\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func printPathCommands(w io.Writer, cmds string) error {
|
||||||
|
moveTo := func(p f32.Point) {
|
||||||
|
fmt.Fprintf(w, "p.MoveTo(%s)\n", point(p))
|
||||||
|
}
|
||||||
|
lineTo := func(p f32.Point) {
|
||||||
|
fmt.Fprintf(w, "p.LineTo(%s)\n", point(p))
|
||||||
|
}
|
||||||
|
cubeTo := func(p0, p1, p2 f32.Point) {
|
||||||
|
fmt.Fprintf(w, "p.CubeTo(%s, %s, %s)\n", point(p0), point(p1), point(p2))
|
||||||
|
}
|
||||||
|
cmds = strings.TrimSpace(cmds)
|
||||||
|
var pen f32.Point
|
||||||
|
initPoint := pen
|
||||||
|
ctrl2 := pen
|
||||||
|
for {
|
||||||
|
cmds = strings.TrimLeft(cmds, " ,\t\n")
|
||||||
|
if len(cmds) == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
orig := cmds
|
||||||
|
op := rune(cmds[0])
|
||||||
|
cmds = cmds[1:]
|
||||||
|
switch op {
|
||||||
|
case 'M', 'm', 'V', 'v', 'L', 'l', 'H', 'h', 'C', 'c', 'S', 's':
|
||||||
|
case 'Z', 'z':
|
||||||
|
if pen != initPoint {
|
||||||
|
lineTo(initPoint)
|
||||||
|
pen = initPoint
|
||||||
|
}
|
||||||
|
ctrl2 = initPoint
|
||||||
|
continue
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unknown <path> command %s in %q", string(op), orig)
|
||||||
|
}
|
||||||
|
var coords []float64
|
||||||
|
for {
|
||||||
|
cmds = strings.TrimLeft(cmds, " ,\t\n")
|
||||||
|
if len(cmds) == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
n, x, ok := parseFloat(cmds)
|
||||||
|
if !ok {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
cmds = cmds[n:]
|
||||||
|
coords = append(coords, x)
|
||||||
|
}
|
||||||
|
rel := unicode.IsLower(op)
|
||||||
|
newPen := pen
|
||||||
|
switch unicode.ToLower(op) {
|
||||||
|
case 'h':
|
||||||
|
for _, x := range coords {
|
||||||
|
p := f32.Pt(float32(x), pen.Y)
|
||||||
|
if rel {
|
||||||
|
p.X += pen.X
|
||||||
|
}
|
||||||
|
lineTo(p)
|
||||||
|
newPen = p
|
||||||
|
}
|
||||||
|
pen = newPen
|
||||||
|
ctrl2 = newPen
|
||||||
|
continue
|
||||||
|
case 'v':
|
||||||
|
for _, y := range coords {
|
||||||
|
p := f32.Pt(pen.X, float32(y))
|
||||||
|
if rel {
|
||||||
|
p.Y += pen.Y
|
||||||
|
}
|
||||||
|
lineTo(p)
|
||||||
|
newPen = p
|
||||||
|
}
|
||||||
|
pen = newPen
|
||||||
|
ctrl2 = newPen
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if len(coords)%2 != 0 {
|
||||||
|
return fmt.Errorf("odd number of coordinates in <path> data: %q", orig)
|
||||||
|
}
|
||||||
|
var off f32.Point
|
||||||
|
if rel {
|
||||||
|
// Relative command.
|
||||||
|
off = pen
|
||||||
|
} else {
|
||||||
|
off = f32.Pt(0, 0)
|
||||||
|
}
|
||||||
|
var points []f32.Point
|
||||||
|
for i := 0; i < len(coords); i += 2 {
|
||||||
|
p := f32.Pt(float32(coords[i]), float32(coords[i+1]))
|
||||||
|
p = p.Add(off)
|
||||||
|
points = append(points, p)
|
||||||
|
}
|
||||||
|
newCtrl2 := ctrl2
|
||||||
|
switch op := unicode.ToLower(op); op {
|
||||||
|
case 'm', 'l':
|
||||||
|
sop := moveTo
|
||||||
|
if op == 'l' {
|
||||||
|
sop = lineTo
|
||||||
|
}
|
||||||
|
for _, p := range points {
|
||||||
|
sop(p)
|
||||||
|
newPen = p
|
||||||
|
}
|
||||||
|
if op == 'm' {
|
||||||
|
initPoint = newPen
|
||||||
|
}
|
||||||
|
case 'c':
|
||||||
|
for i := 0; i < len(points); i += 3 {
|
||||||
|
p1, p2, p3 := points[i], points[i+1], points[i+2]
|
||||||
|
cubeTo(p1, p2, p3)
|
||||||
|
newPen = p3
|
||||||
|
newCtrl2 = p2
|
||||||
|
}
|
||||||
|
case 's':
|
||||||
|
for i := 0; i < len(points); i += 2 {
|
||||||
|
p2, p3 := points[i], points[i+1]
|
||||||
|
// Compute p1 by reflecting p2 on to the line that contains pen and p2.
|
||||||
|
p1 := pen.Mul(2).Sub(ctrl2)
|
||||||
|
cubeTo(p1, p2, p3)
|
||||||
|
newPen = p3
|
||||||
|
newCtrl2 = p2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pen = newPen
|
||||||
|
ctrl2 = newCtrl2
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseFloat(s string) (int, float64, bool) {
|
||||||
|
n := 0
|
||||||
|
if len(s) > 0 && s[0] == '-' {
|
||||||
|
n++
|
||||||
|
}
|
||||||
|
for ; n < len(s); n++ {
|
||||||
|
if !(unicode.IsDigit(rune(s[n])) || s[n] == '.') {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
f, err := strconv.ParseFloat(s[:n], 64)
|
||||||
|
return n, f, err == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const funcs = `
|
||||||
|
func argb(c uint32) color.NRGBA {
|
||||||
|
return color.NRGBA{A: uint8(c >> 24), R: uint8(c >> 16), G: uint8(c >> 8), B: uint8(c)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func rect(p *clip.Path, origin, size f32.Point) {
|
||||||
|
p.MoveTo(origin)
|
||||||
|
p.LineTo(origin.Add(f32.Pt(size.X, 0)))
|
||||||
|
p.LineTo(origin.Add(size))
|
||||||
|
p.LineTo(origin.Add(f32.Pt(0, size.Y)))
|
||||||
|
p.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func ellipse(p *clip.Path, center, radius f32.Point) {
|
||||||
|
r := radius.X
|
||||||
|
// We'll model the ellipse as a circle scaled in the Y
|
||||||
|
// direction.
|
||||||
|
scale := radius.Y / r
|
||||||
|
|
||||||
|
// https://pomax.github.io/bezierinfo/#circles_cubic.
|
||||||
|
const q = 4 * (math.Sqrt2 - 1) / 3
|
||||||
|
|
||||||
|
curve := r * q
|
||||||
|
top := f32.Point{X: center.X, Y: center.Y - r*scale}
|
||||||
|
|
||||||
|
p.MoveTo(top)
|
||||||
|
p.CubeTo(
|
||||||
|
f32.Point{X: center.X + curve, Y: center.Y - r*scale},
|
||||||
|
f32.Point{X: center.X + r, Y: center.Y - curve*scale},
|
||||||
|
f32.Point{X: center.X + r, Y: center.Y},
|
||||||
|
)
|
||||||
|
p.CubeTo(
|
||||||
|
f32.Point{X: center.X + r, Y: center.Y + curve*scale},
|
||||||
|
f32.Point{X: center.X + curve, Y: center.Y + r*scale},
|
||||||
|
f32.Point{X: center.X, Y: center.Y + r*scale},
|
||||||
|
)
|
||||||
|
p.CubeTo(
|
||||||
|
f32.Point{X: center.X - curve, Y: center.Y + r*scale},
|
||||||
|
f32.Point{X: center.X - r, Y: center.Y + curve*scale},
|
||||||
|
f32.Point{X: center.X - r, Y: center.Y},
|
||||||
|
)
|
||||||
|
p.CubeTo(
|
||||||
|
f32.Point{X: center.X - r, Y: center.Y - curve*scale},
|
||||||
|
f32.Point{X: center.X - curve, Y: center.Y - r*scale},
|
||||||
|
top,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
`
|
||||||
Reference in New Issue
Block a user