2 Commits

Author SHA1 Message Date
Elias Naur 4113870ba2 fixup
Signed-off-by: Elias Naur <mail@eliasnaur.com>
2022-06-28 21:47:01 +02:00
Chris Waldon 333b1df54a gogio: implement custom rendering test
This commit adds an end to end test for the custom rendering
use-case. I confirmed that the new test failed when custom
rendering frame lifecycle was broken, and succeeds now.

However, the old X11 tests started failing when the new
one started passing. I'm not sure how they interfere with
one another, but I'm out of time to investigate.

Signed-off-by: Chris Waldon <christopher.waldon.dev@gmail.com>
2022-06-28 20:48:58 +02:00
24 changed files with 437 additions and 1468 deletions
+10 -19
View File
@@ -8,10 +8,8 @@ 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
@@ -19,7 +17,6 @@ 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
@@ -27,7 +24,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.24.1.linux-amd64.tar.gz | tar -C /home/build/sdk -xzf - curl -s https://dl.google.com/go/go1.17.7.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
@@ -45,11 +42,6 @@ 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
@@ -61,16 +53,15 @@ 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 --with-libdispatch=$APPLE_TOOLCHAIN_ROOT/libdispatch --target=x86_64-apple-darwin19 ./configure --prefix $APPLE_TOOLCHAIN_ROOT/toolchain --with-libtapi=$APPLE_TOOLCHAIN_ROOT/libtapi --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
# Broken test. - test_ios_gogio: |
# - test_ios_gogio: | mkdir tmp
# mkdir tmp cd tmp
# cd tmp go mod init example.com
# go mod init example.com go get -d gioui.org/example/kitchen
# go get -d gioui.org/example/kitchen export PATH=/home/build/appletools/bin:$PATH
# export PATH=/home/build/appletools/bin:$PATH gogio -target ios -o app.app gioui.org/example/kitchen
# gogio -target ios -o app.app gioui.org/example/kitchen
+1 -1
View File
@@ -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.24.1.freebsd-amd64.tar.gz | tar -C /home/build/sdk -xzf - curl https://dl.google.com/go/go1.17.7.freebsd-amd64.tar.gz | tar -C /home/build/sdk -xzf -
- test_cmd: | - test_cmd: |
cd gio-cmd cd gio-cmd
go test ./... go test ./...
+4 -3
View File
@@ -24,6 +24,7 @@ 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
@@ -38,7 +39,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.24.1.linux-amd64.tar.gz | tar -C /home/build/sdk -xzf - curl -s https://dl.google.com/go/go1.17.7.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 .)"
@@ -56,8 +57,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: |
sudo curl -o /etc/apt/keyrings/google.pub -s https://dl.google.com/linux/linux_signing_key.pub curl -s https://dl.google.com/linux/linux_signing_key.pub | sudo apt-key add -
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 sh -c 'echo "deb [arch=amd64] 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
View File
@@ -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.24.1.src.tar.gz | tar -C /home/build/sdk -xzf - curl https://dl.google.com/go/go1.17.7.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: |
+22 -20
View File
@@ -1,29 +1,31 @@
module gioui.org/cmd module gioui.org/cmd
go 1.24.1 go 1.17
require ( require (
gioui.org v0.10.0 gioui.org v0.0.0-20220628163331-e21c665e70ae
github.com/akavel/rsrc v0.10.1 github.com/akavel/rsrc v0.10.1
github.com/chromedp/cdproto v0.0.0-20250429231605-6ed5b53462d4 github.com/chromedp/cdproto v0.0.0-20191114225735-6626966fbae4
github.com/chromedp/chromedp v0.13.6 github.com/chromedp/chromedp v0.5.2
golang.org/x/image v0.26.0 golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d
golang.org/x/sync v0.19.0 golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9
golang.org/x/text v0.32.0 golang.org/x/text v0.3.7
golang.org/x/tools v0.39.0 golang.org/x/tools v0.1.0
) )
require ( require (
gioui.org/shader v1.0.8 // indirect gioui.org/cpu v0.0.0-20210817075930-8d6a761490d2 // indirect
github.com/chromedp/sysutil v1.1.0 // indirect gioui.org/shader v1.0.6 // indirect
github.com/go-json-experiment/json v0.0.0-20250417205406-170dfdcf87d1 // indirect github.com/benoitkugler/textlayout v0.1.1 // indirect
github.com/go-text/typesetting v0.3.4 // indirect github.com/gioui/uax v0.2.1-0.20220325163150-e3d987515a12 // indirect
github.com/gobwas/httphead v0.1.0 // indirect github.com/go-text/typesetting v0.0.0-20220411150340-35994bc27a7b // indirect
github.com/gobwas/pool v0.2.1 // indirect github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee // indirect
github.com/gobwas/ws v1.4.0 // indirect github.com/gobwas/pool v0.2.0 // indirect
github.com/google/go-cmp v0.7.0 // indirect github.com/gobwas/ws v1.0.2 // indirect
golang.org/x/exp/shiny v0.0.0-20250408133849-7e4ce0ab07d0 // indirect github.com/knq/sysutil v0.0.0-20191005231841-15668db23d08 // indirect
golang.org/x/mod v0.30.0 // indirect github.com/mailru/easyjson v0.7.0 // indirect
golang.org/x/net v0.48.0 // indirect golang.org/x/exp v0.0.0-20210722180016-6781d3edade3 // indirect
golang.org/x/sys v0.39.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
) )
+117 -49
View File
@@ -1,52 +1,120 @@
eliasnaur.com/font v0.0.0-20230308162249-dd43949cb42d h1:ARo7NCVvN2NdhLlJE9xAbKweuI9L6UgfTbYb0YwPacY= eliasnaur.com/font v0.0.0-20220124212145-832bb8fc08c3 h1:djFprmHZgrSepsHAIRMp5UJn3PzsoTg9drI+BDmif5Q=
eliasnaur.com/font v0.0.0-20230308162249-dd43949cb42d/go.mod h1:OYVuxibdk9OSLX8vAqydtRPP87PyTFcT9uH3MlEGBQA= gioui.org v0.0.0-20220628163331-e21c665e70ae h1:s8Erm0/zVvi3Fbq0ijjPkRT04XxcGZWTxkxDwUBsxuQ=
gioui.org v0.10.0 h1:kV6NKGbEp0JSPLtgoT/xk5dMexVDzvEOErXA40gUxrU= gioui.org v0.0.0-20220628163331-e21c665e70ae/go.mod h1:WHoHbUjH91BJS2xkfps2AhKxji+9o3xwfsphGsCBfnM=
gioui.org v0.10.0/go.mod h1:MZJZsdEPkTBzChdqeE8CiiQhreUQBj43qusDxQNDf7k=
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/shader v1.0.8 h1:6ks0o/A+b0ne7RzEqRZK5f4Gboz2CfG+mVliciy6+qA= gioui.org/cpu v0.0.0-20210817075930-8d6a761490d2 h1:AGDDxsJE1RpcXTAxPG2B4jrwVUJGFDjINIPi1jtO6pc=
gioui.org/shader v1.0.8/go.mod h1:mWdiME581d/kV7/iEhLmUgUK5iZ09XR5XpduXzbePVM= gioui.org/cpu v0.0.0-20210817075930-8d6a761490d2/go.mod h1:A8M0Cn5o+vY5LTMlnRoK3O5kG+rH0kWfJjeKd9QpBmQ=
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/chromedp/cdproto v0.0.0-20250429231605-6ed5b53462d4 h1:UZdrvid2JFwnvPlUSEFlE794XZL4Jmrj8fuxfcLECJE= github.com/benoitkugler/pstokenizer v1.0.0/go.mod h1:l1G2Voirz0q/jj0TQfabNxVsa8HZXh/VMxFSRALWTiE=
github.com/chromedp/cdproto v0.0.0-20250429231605-6ed5b53462d4/go.mod h1:NItd7aLkcfOA/dcMXvl8p1u+lQqioRMq/SqDp71Pb/k= github.com/benoitkugler/textlayout v0.0.5/go.mod h1:puH4v13Uz7uIhIH0XMk5jgc8U3MXcn5r3VlV9K8n0D8=
github.com/chromedp/chromedp v0.13.6 h1:xlNunMyzS5bu3r/QKrb3fzX6ow3WBQ6oao+J65PGZxk= github.com/benoitkugler/textlayout v0.1.1 h1:hizE/085xAeY8q7gwV00uHR2Q27KYB2g1HW+UacXl68=
github.com/chromedp/chromedp v0.13.6/go.mod h1:h8GPP6ZtLMLsU8zFbTcb7ZDGCvCy8j/vRoFmRltQx9A= github.com/benoitkugler/textlayout v0.1.1/go.mod h1:o+1hFV+JSHBC9qNLIuwVoLedERU7sBPgEFcuSgfvi/w=
github.com/chromedp/sysutil v1.1.0 h1:PUFNv5EcprjqXZD9nJb9b/c9ibAbxiYo4exNWZyipwM= github.com/benoitkugler/textlayout-testdata v0.1.1 h1:AvFxBxpfrQd8v55qH59mZOJOQjtD6K2SFe9/HvnIbJk=
github.com/chromedp/sysutil v1.1.0/go.mod h1:WiThHUdltqCNKGc4gaU50XgYjwjYIhKWoHGPTUfWTJ8= github.com/chromedp/cdproto v0.0.0-20191114225735-6626966fbae4 h1:QD3KxSJ59L2lxG6MXBjNHxiQO2RmxTQ3XcK+wO44WOg=
github.com/go-json-experiment/json v0.0.0-20250417205406-170dfdcf87d1 h1:+VexzzkMLb1tnvpuQdGT/DicIRW7MN8ozsXqBMgp0Hk= github.com/chromedp/cdproto v0.0.0-20191114225735-6626966fbae4/go.mod h1:PfAWWKJqjlGFYJEidUM6aVIWPr0EpobeyVWEEmplX7g=
github.com/go-json-experiment/json v0.0.0-20250417205406-170dfdcf87d1/go.mod h1:TiCD2a1pcmjd7YnhGH0f/zKNcCD06B029pHhzV23c2M= github.com/chromedp/chromedp v0.5.2 h1:W8xBXQuUnd2dZK0SN/lyVwsQM7KgW+kY5HGnntms194=
github.com/go-text/typesetting v0.3.4 h1:YYurUOtEb9kGSOz4uE3k4OpBGsp1dDL8+fjCeaFamAU= github.com/chromedp/chromedp v0.5.2/go.mod h1:rsTo/xRo23KZZwFmWk2Ui79rBaVRRATCjLzNQlOFSiA=
github.com/go-text/typesetting v0.3.4/go.mod h1:4qZCQphq4KSgGTAeI0uMEkVbROgfah8BuyF5LRYr7XY= github.com/cloudfoundry/jibber_jabber v0.0.0-20151120183258-bcc4c8345a21/go.mod h1:po7NpZ/QiTKzBKyrsEAxwnTamCoh8uDk/egRpQ7siIc=
github.com/go-text/typesetting-utils v0.0.0-20260223113751-2d88ac90dae3 h1:drBZzMgdYPbmyXqOto4YhhJGrFIQCX94FpR4MzTCsos= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/go-text/typesetting-utils v0.0.0-20260223113751-2d88ac90dae3/go.mod h1:3/62I4La/HBRX9TcTpBj4eipLiwzf+vhI+7whTc9V7o= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU= github.com/gioui/uax v0.2.1-0.20220325163150-e3d987515a12 h1:1bjaB/5IIicfKpP4k0s30T2WEw//Kh00zULa8DQ0cxA=
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM= github.com/gioui/uax v0.2.1-0.20220325163150-e3d987515a12/go.mod h1:kDhBRTA/i3H46PVdhqcw26TdGSIj42TOKNWKY+Kipnw=
github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og= github.com/go-text/typesetting v0.0.0-20220411150340-35994bc27a7b h1:WINlj3ANt+CVrO2B4NGDHRlPvEWZPxjhb7z+JKypwXI=
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= github.com/go-text/typesetting v0.0.0-20220411150340-35994bc27a7b/go.mod h1:ZNYu5saGoMOqtkVH5T8onTwhzenDUVszI+5WFHJRaxQ=
github.com/gobwas/ws v1.4.0 h1:CTaoG1tojrh4ucGPcoJFiAQUAsEWekEWvLy7GsVNqGs= github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee h1:s+21KNqlpePfkah2I+gwHF8xmJWRjooY+5248k6m4A0=
github.com/gobwas/ws v1.4.0/go.mod h1:G3gNqMNtPppf5XUz7O4shetPpcZ1VJ7zt18dlUeakrc= github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/gobwas/pool v0.2.0 h1:QEmUOlnSjWtnpRGHF3SauEiOsy82Cup83Vf2LcMlnc8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80 h1:6Yzfa6GP0rIo/kULo2bwGEkFvCePZ3qHDDTC3/J9Swo= github.com/gobwas/ws v1.0.2 h1:CoAavW/wd/kulfZmSIBt6p24n4j7tHgNVCjsfHVNUbo=
github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80/go.mod h1:imJHygn/1yfhB7XSJJKlFZKl/J+dCPAknuiaGOshXAs= github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde h1:x0TT0RDC7UhAVbbWWBzr41ElhJx5tXPWkIHA2HWPRuw= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 h1:R84qjqJb5nVJMxqWYb3np9L5ZsaDtB+a39EqjV0JSUM= 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/go.mod h1:S9Xr4PYopiDyqSyp5NjCrhFrqg6A5zA2E/iPHPhqnS8= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
golang.org/x/exp/shiny v0.0.0-20250408133849-7e4ce0ab07d0 h1:tMSqXTK+AQdW3LpCbfatHSRPHeW6+2WuxaVQuHftn80= 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/go.mod h1:ygj7T6vSGhhm/9yTpOQQNvuAUFziTH7RUiH74EoE2C8= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
golang.org/x/image v0.26.0 h1:4XjIFEZWQmCZi6Wv8BoxsDhRU3RVnLX04dToTDAEPlY= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
golang.org/x/image v0.26.0/go.mod h1:lcxbMFAovzpnJxzXS3nyL83K27tmqtKzIJpctK8YO5c= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
golang.org/x/mod v0.30.0 h1:fDEXFVZ/fmCKProc/yAXXUijritrDzahmwwefnjoPFk= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY= github.com/knq/sysutil v0.0.0-20191005231841-15668db23d08 h1:V0an7KRw92wmJysvFvtqtKMAPmvS5O0jtB0nYo6t+gs=
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= github.com/knq/sysutil v0.0.0-20191005231841-15668db23d08/go.mod h1:dFWs1zEqDjFtnBXsd1vPOZaLsESovai349994nHx3e0=
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= github.com/mailru/easyjson v0.7.0 h1:aizVhC/NAAcKWb+5QsU1iNOZb4Yws5UO2I+aIprQITM=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs=
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= github.com/onsi/ginkgo v1.15.0/go.mod h1:hF8qUzuuC8DJGygJH3726JnCZX4MYbRB8yFfISqnKUg=
golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ= 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=
+3 -3
View File
@@ -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_HOME") d.sdkDir = os.Getenv("ANDROID_SDK_ROOT")
if d.sdkDir == "" { if d.sdkDir == "" {
d.Skipf("Android SDK is required; set $ANDROID_HOME") d.Skipf("Android SDK is required; set $ANDROID_SDK_ROOT")
} }
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 ...any) []byte { func (d *AndroidTestDriver) adb(args ...interface{}) []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))
+51 -70
View File
@@ -8,6 +8,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"io" "io"
"io/ioutil"
"os" "os"
"os/exec" "os/exec"
"path/filepath" "path/filepath"
@@ -40,16 +41,14 @@ type errWriter struct {
var exeSuffix string var exeSuffix string
type manifestData struct { type manifestData struct {
AppID string AppID string
Version Semver Version int
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
} }
const ( const (
@@ -78,9 +77,9 @@ func init() {
} }
func buildAndroid(tmpDir string, bi *buildInfo) error { func buildAndroid(tmpDir string, bi *buildInfo) error {
sdk := os.Getenv("ANDROID_HOME") sdk := os.Getenv("ANDROID_SDK_ROOT")
if sdk == "" { if sdk == "" {
return errors.New("please set ANDROID_HOME to the Android SDK path") return errors.New("please set ANDROID_SDK_ROOT to the Android SDK path")
} }
if _, err := os.Stat(sdk); err != nil { if _, err := os.Stat(sdk); err != nil {
return err return err
@@ -181,9 +180,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_HOME") androidHome := os.Getenv("ANDROID_SDK_ROOT")
if androidHome == "" { if androidHome == "" {
return errors.New("ANDROID_HOME is not set. Please point it to the root of the Android SDK") return errors.New("ANDROID_SDK_ROOT 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 {
@@ -193,7 +192,10 @@ func compileAndroid(tmpDir string, tools *androidTools, bi *buildInfo) (err erro
if err != nil { if err != nil {
return err return err
} }
minSDK := max(bi.minsdk, 17) 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 {
@@ -212,14 +214,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, 0o755); err != nil { if err := os.MkdirAll(archDir, 0755); 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 -extldflags \"-Wl,-z,max-page-size=65536\" "+bi.ldflags, "-ldflags=-w -s "+bi.ldflags,
"-buildmode=c-shared", "-buildmode=c-shared",
"-tags", bi.tags, "-tags", bi.tags,
"-o", libFile, "-o", libFile,
@@ -232,14 +234,13 @@ 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", "-tags", bi.tags, "-f", "{{.Dir}}", "gioui.org/app/")) appDir, err := runCmd(exec.Command("go", "list", "-f", "{{.Dir}}", "gioui.org/app/"))
if err != nil { if err != nil {
return err return err
} }
@@ -252,7 +253,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, 0o755); err != nil { if err := os.MkdirAll(classes, 0755); err != nil {
return err return err
} }
javac := exec.Command( javac := exec.Command(
@@ -349,17 +350,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, 0o755); err != nil { if err := os.MkdirAll(dexDir, 0755); 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 := 35 targetSDK := 31
if bi.targetsdk > 0 { if bi.minsdk > targetSDK {
targetSDK = bi.targetsdk targetSDK = bi.minsdk
} }
if minSDK > targetSDK { minSDK := 16
targetSDK = minSDK if bi.minsdk > minSDK {
minSDK = bi.minsdk
} }
if len(classFiles) > 0 { if len(classFiles) > 0 {
d8 := exec.Command( d8 := exec.Command(
@@ -384,7 +385,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, 0o755); err != nil { if err := os.MkdirAll(dir, 0755); err != nil {
return err return err
} }
} }
@@ -404,21 +405,21 @@ func exeAndroid(tmpDir string, tools *androidTools, bi *buildInfo, extraJars, pe
if err != nil { if err != nil {
return err return err
} }
err = os.WriteFile(filepath.Join(v26mipmapDir, `ic_launcher.xml`), []byte(`<?xml version="1.0" encoding="utf-8"?> err = ioutil.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>`), 0o660) </adaptive-icon>`), 0660)
if err != nil { if err != nil {
return err return err
} }
iconSnip = `android:icon="@mipmap/ic_launcher"` iconSnip = `android:icon="@mipmap/ic_launcher"`
} }
err = os.WriteFile(filepath.Join(valDir, "themes.xml"), []byte(themes), 0o660) err = ioutil.WriteFile(filepath.Join(valDir, "themes.xml"), []byte(themes), 0660)
if err != nil { if err != nil {
return err return err
} }
err = os.WriteFile(filepath.Join(v21Dir, "themes.xml"), []byte(themesV21), 0o660) err = ioutil.WriteFile(filepath.Join(v21Dir, "themes.xml"), []byte(themesV21), 0660)
if err != nil { if err != nil {
return err return err
} }
@@ -435,32 +436,23 @@ func exeAndroid(tmpDir string, tools *androidTools, bi *buildInfo, extraJars, pe
// Link APK. // Link APK.
permissions, features := getPermissions(perms) permissions, features := getPermissions(perms)
appName := UppercaseName(bi.name) appName := strings.Title(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,
} }
tmpl, err := template.New("test").Parse( tmpl, err := template.New("test").Parse(
`<?xml version="1.0" encoding="utf-8"?> `<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="{{.AppID}}" package="{{.AppID}}"
android:versionCode="{{.Version.VersionCode}}" android:versionCode="{{.Version}}"
android:versionName="{{.Version}}"> android:versionName="1.0.{{.Version}}">
{{if .PackageQueries}}
<queries>
{{range .PackageQueries}}
<package android:name="{{.}}" />
{{end}}
</queries>
{{end}}
<uses-sdk android:minSdkVersion="{{.MinSDK}}" android:targetSdkVersion="{{.TargetSDK}}" /> <uses-sdk android:minSdkVersion="{{.MinSDK}}" android:targetSdkVersion="{{.TargetSDK}}" />
{{range .Permissions}} <uses-permission android:name="{{.}}"/> {{range .Permissions}} <uses-permission android:name="{{.}}"/>
{{end}}{{range .Features}} <uses-feature android:{{.}} android:required="false"/> {{end}}{{range .Features}} <uses-feature android:{{.}} android:required="false"/>
@@ -470,20 +462,11 @@ func exeAndroid(tmpDir string, tools *androidTools, bi *buildInfo, extraJars, pe
android:theme="@style/Theme.GioApp" android:theme="@style/Theme.GioApp"
android:configChanges="screenSize|screenLayout|smallestScreenSize|orientation|keyboardHidden" android:configChanges="screenSize|screenLayout|smallestScreenSize|orientation|keyboardHidden"
android:windowSoftInputMode="adjustResize" android:windowSoftInputMode="adjustResize"
android:launchMode="singleInstance"
android:exported="true"> android:exported="true">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </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> </activity>
</application> </application>
</manifest>`) </manifest>`)
@@ -492,7 +475,7 @@ func exeAndroid(tmpDir string, tools *androidTools, bi *buildInfo, extraJars, pe
return err return err
} }
manifest := filepath.Join(tmpDir, "AndroidManifest.xml") manifest := filepath.Join(tmpDir, "AndroidManifest.xml")
if err := os.WriteFile(manifest, manifestBuffer.Bytes(), 0o660); err != nil { if err := ioutil.WriteFile(manifest, manifestBuffer.Bytes(), 0660); err != nil {
return err return err
} }
@@ -694,7 +677,7 @@ func signAAB(tmpDir string, aabFile string, tools *androidTools, bi *buildInfo)
} }
var alias string var alias string
for t := range strings.SplitSeq(keytoolList, "\n") { for _, t := range strings.Split(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
} }
@@ -765,7 +748,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_HOME/ndk-bundle. // The old NDK path was $ANDROID_SDK_ROOT/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
@@ -778,7 +761,7 @@ func findNDK(androidHome string) (string, error) {
} }
} }
return "", fmt.Errorf("no NDK found in $ANDROID_HOME (%s). Set $ANDROID_NDK_ROOT or use `sdkmanager ndk-bundle` to install the NDK", androidHome) 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)
} }
func findKeytool() (string, error) { func findKeytool() (string, error) {
@@ -786,9 +769,7 @@ 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
} }
@@ -1008,12 +989,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 io.Discard return ioutil.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 io.Discard return ioutil.Discard
} }
return &errWriter{w: w, err: &z.err} return &errWriter{w: w, err: &z.err}
} }
+29 -98
View File
@@ -9,35 +9,22 @@ 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
targetsdk int name string
name string pkgDir string
pkgDir string pkgPath string
pkgPath string iconPath string
iconPath string tags string
tags string target string
target string version int
version Semver key string
key string password 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) {
@@ -50,66 +37,24 @@ 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,
targetsdk: *targetsdk, name: getPkgName(pkgMetadata),
name: appName, pkgDir: pkgMetadata.Dir,
pkgDir: pkgMetadata.Dir, pkgPath: pkgPath,
pkgPath: pkgPath, iconPath: appIcon,
iconPath: appIcon, tags: *extraTags,
tags: *extraTags, target: *target,
target: *target, version: *version,
version: ver, key: *signKey,
key: *signKey, password: *signPass,
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, ",")
@@ -128,8 +73,6 @@ 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.")
@@ -142,9 +85,6 @@ 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 {
@@ -156,26 +96,17 @@ 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", "-tags", *extraTags, "-f", "{{.ImportPath}}", pkgPath)) pkgImportPath, err := runCmd(exec.Command("go", "list", "-f", "{{.ImportPath}}", pkgPath))
if err != nil { if err != nil {
return nil, err return nil, err
} }
pkgDir, err := runCmd(exec.Command("go", "list", "-tags", *extraTags, "-f", "{{.Dir}}", pkgPath)) pkgDir, err := runCmd(exec.Command("go", "list", "-f", "{{.Dir}}", pkgPath))
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -199,7 +130,7 @@ func getAppID(pkgMetadata *packageMetadata) string {
name = "." + domain[0] name = "." + domain[0]
domain[0] = "localhost" domain[0] = "localhost"
} else { } else {
for i := range len(domain) / 2 { for i := 0; i < len(domain)/2; i++ {
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]
} }
+5 -32
View File
@@ -2,12 +2,14 @@ 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 := []struct { tests := []expval{
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"},
@@ -28,32 +30,3 @@ 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)
}
}
}
+3 -2
View File
@@ -10,6 +10,7 @@ import (
"image" "image"
"image/color" "image/color"
"io" "io"
"io/ioutil"
"os" "os"
"os/exec" "os/exec"
"runtime" "runtime"
@@ -82,7 +83,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,darwin,windows,netbsd"}, {"X11 with custom rendering", &X11TestDriver{}, customRenderTestdataWithRelativePkgPath, "openbsd"},
// Doesn't work on the builders. // Doesn't work on the builders.
//{"Wayland", &WaylandTestDriver{}, testdataWithRelativePkgPath}, //{"Wayland", &WaylandTestDriver{}, testdataWithRelativePkgPath},
{"JS", &JSTestDriver{}, testdataWithRelativePkgPath, ""}, {"JS", &JSTestDriver{}, testdataWithRelativePkgPath, ""},
@@ -315,7 +316,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 := os.MkdirTemp("", name) dir, err := ioutil.TempDir("", name)
if err != nil { if err != nil {
d.Fatal(err) d.Fatal(err)
} }
+4 -30
View File
@@ -18,8 +18,7 @@ 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, macos for mobile platforms, tvos for Apple's tvOS, js for WebAssembly/WebGL.
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.
@@ -47,9 +46,8 @@ 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 semantic version for -buildmode exe. It must The -version flag specifies the integer version code for Android and the last
be on the form major.minor.patch.versioncode where the version code is used for component of the 1.0.X version for iOS and tvOS.
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.
@@ -60,36 +58,12 @@ 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.
` `
+15 -19
View File
@@ -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,8 +98,7 @@ 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 := new(app.Window) w := app.NewWindow(app.CustomRenderer(true))
w.Option(app.CustomRenderer(true))
if err := loop(w); err != nil { if err := loop(w); err != nil {
log.Fatal(err) log.Fatal(err)
} }
@@ -165,17 +164,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 { for e := range w.Events() {
switch e := w.Event().(type) { switch e := e.(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 app.DestroyEvent: case system.DestroyEvent:
return e.Err return e.Err
case app.FrameEvent: case system.FrameEvent:
if init && size != e.Size { if init && size != e.Size {
size = e.Size size = e.Size
recreateContext() recreateContext()
@@ -184,7 +183,7 @@ func loop(w *app.Window) error {
break break
} }
// Build ops. // Build ops.
gtx := app.NewContext(&ops, e) gtx := layout.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})
@@ -206,7 +205,7 @@ func loop(w *app.Window) error {
) )
}), }),
) )
gtx.Execute(op.InvalidateCmd{}) op.InvalidateOp{}.Add(gtx.Ops)
log.Println("frame") log.Println("frame")
// Trigger window resize detection in ANGLE. // Trigger window resize detection in ANGLE.
@@ -352,16 +351,13 @@ 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()
event.Op(gtx.Ops, w) pointer.InputOp{
for { Tag: w,
e, ok := gtx.Event(pointer.Filter{ Types: pointer.Press,
Target: w, }.Add(gtx.Ops)
Kinds: pointer.Press,
}) for _, e := range gtx.Events(w) {
if !ok { if e, ok := e.(pointer.Event); ok && e.Type == pointer.Press {
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
+12 -17
View File
@@ -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 := new(app.Window) w := app.NewWindow()
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.Event() e := <-w.Events()
switch e := e.(type) { switch e := e.(type) {
case app.DestroyEvent: case system.DestroyEvent:
return e.Err return e.Err
case app.FrameEvent: case system.FrameEvent:
gtx := app.NewContext(&ops, e) gtx := layout.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,18 +126,13 @@ 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()
event.Op(gtx.Ops, w) pointer.InputOp{
filter := pointer.Filter{ Tag: w,
Target: w, Types: pointer.Press,
Kinds: pointer.Press, }.Add(gtx.Ops)
}
for { for _, e := range gtx.Events(w) {
e, ok := gtx.Event(filter) 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
+129 -189
View File
@@ -4,19 +4,17 @@ 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"
@@ -24,8 +22,6 @@ 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
@@ -37,7 +33,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", UppercaseName(appName)) framework = fmt.Sprintf("%s.framework", strings.Title(appName))
} }
return archiveIOS(tmpDir, target, framework, bi) return archiveIOS(tmpDir, target, framework, bi)
case "exe": case "exe":
@@ -59,7 +55,11 @@ func buildIOS(tmpDir, target string, bi *buildInfo) error {
} }
} }
bi.archs = slices.Delete(bi.archs, i, i+1) bi.archs = append(bi.archs[:i], bi.archs[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,35 +69,13 @@ 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, 0o755); err != nil { if err := os.MkdirAll(appDir, 0755); 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")
@@ -106,8 +84,16 @@ func buildIOS(tmpDir, target string, bi *buildInfo) error {
} }
} }
// signApple is shared between iOS and macOS. func signIOS(bi *buildInfo, tmpDir, app string) error {
func signApple(appID, tmpDir, embedded, app string, provisions []string) error { home, err := os.UserHomeDir()
if err != nil {
return err
}
provPattern := filepath.Join(home, "Library", "MobileDevice", "Provisioning Profiles", "*.mobileprovision")
provisions, err := filepath.Glob(provPattern)
if err != nil {
return err
}
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 {
@@ -131,23 +117,17 @@ func signApple(appID, tmpDir, embedded, app string, provisions []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, appID) expAppID := fmt.Sprintf("%s.%s", appIDPrefix, bi.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
} }
@@ -162,23 +142,15 @@ func signApple(appID, tmpDir, embedded, app string, provisions []string) error {
return err return err
} }
entFile := filepath.Join(tmpDir, "entitlements.plist") entFile := filepath.Join(tmpDir, "entitlements.plist")
if err := os.WriteFile(entFile, []byte(entitlements), 0o660); err != nil { if err := ioutil.WriteFile(entFile, []byte(entitlements), 0660); 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( _, err = runCmd(exec.Command("codesign", "-s", idHex, "-v", "--entitlements", entFile, app))
"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", appID, avail) return fmt.Errorf("sign: no valid provisioning profile found for bundle id %q among %v", bi.appID, avail)
} }
func exeIOS(tmpDir, target, app string, bi *buildInfo) error { func exeIOS(tmpDir, target, app string, bi *buildInfo) error {
@@ -188,10 +160,36 @@ 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, 0o755); err != nil { if err := os.Mkdir(app, 0755); err != nil {
return err return err
} }
appName := UppercaseName(bi.name) mainm := filepath.Join(tmpDir, "main.m")
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
@@ -200,31 +198,17 @@ func exeIOS(tmpDir, target, app string, bi *buildInfo) error {
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( compile := exec.Command(clang, cflags...)
"go", compile.Args = append(compile.Args,
"build", "-Werror",
"-ldflags=-s -w "+bi.ldflags, "-fmodules",
"-fobjc-arc",
"-x", "objective-c",
"-F", tmpDir,
"-o", exeSlice, "-o", exeSlice,
"-tags", bi.tags, mainm,
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)
@@ -239,7 +223,7 @@ func exeIOS(tmpDir, target, app string, bi *buildInfo) error {
} }
infoPlist := buildInfoPlist(bi) infoPlist := buildInfoPlist(bi)
plistFile := filepath.Join(app, "Info.plist") plistFile := filepath.Join(app, "Info.plist")
if err := os.WriteFile(plistFile, []byte(infoPlist), 0o660); err != nil { if err := ioutil.WriteFile(plistFile, []byte(infoPlist), 0660); err != nil {
return err return err
} }
if _, err := os.Stat(bi.iconPath); err == nil { if _, err := os.Stat(bi.iconPath); err == nil {
@@ -267,16 +251,13 @@ func exeIOS(tmpDir, target, app string, bi *buildInfo) error {
// 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, 0o700); err != nil { if err := os.Mkdir(assets, 0700); 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},
@@ -285,47 +266,29 @@ 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": "76x76", "size" : "1024x1024",
"idiom": "ipad", "idiom" : "ios-marketing",
"filename": "ipad_1x.png", "filename" : "ios_store.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 := os.WriteFile(contentFile, []byte(contentJson), 0o600); err != nil { if err := ioutil.WriteFile(contentFile, []byte(contentJson), 0600); err != nil {
return "", err return "", err
} }
assetPlist := filepath.Join(tmpDir, "assets.plist") assetPlist := filepath.Join(tmpDir, "assets.plist")
@@ -347,7 +310,7 @@ func iosIcons(bi *buildInfo, tmpDir, appDir, icon string) (string, error) {
} }
func buildInfoPlist(bi *buildInfo) string { func buildInfoPlist(bi *buildInfo) string {
appName := UppercaseName(bi.name) appName := strings.Title(bi.name)
platform := iosPlatformFor(bi.target) platform := iosPlatformFor(bi.target)
var supportPlatform string var supportPlatform string
switch bi.target { switch bi.target {
@@ -356,57 +319,36 @@ 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>{{.AppName}}</string> <string>%s</string>
<key>CFBundleIdentifier</key> <key>CFBundleIdentifier</key>
<string>{{.AppID}}</string> <string>%s</string>
<key>CFBundleInfoDictionaryVersion</key> <key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string> <string>6.0</string>
<key>CFBundleName</key> <key>CFBundleName</key>
<string>{{.AppName}}</string> <string>%s</string>
<key>CFBundlePackageType</key> <key>CFBundlePackageType</key>
<string>APPL</string> <string>APPL</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>{{.Version}}</string> <string>1.0.%d</string>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>{{.VersionCode}}</string> <string>%d</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>{{.Platform}}</string> <string>%s</string>
<key>DTPlatformVersion</key> <key>DTPlatformVersion</key>
<string>12.4</string> <string>12.4</string>
<key>MinimumOSVersion</key> <key>MinimumOSVersion</key>
<string>{{.MinVersion}}.0</string> <string>%d</string>
<key>UIDeviceFamily</key> <key>UIDeviceFamily</key>
<array> <array>
<integer>1</integer> <integer>1</integer>
@@ -414,17 +356,14 @@ func buildInfoPlist(bi *buildInfo) string {
</array> </array>
<key>CFBundleSupportedPlatforms</key> <key>CFBundleSupportedPlatforms</key>
<array> <array>
<string>{{.SupportPlatform}}</string> <string>%s</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>
@@ -432,38 +371,13 @@ func buildInfoPlist(bi *buildInfo) string {
<key>DTSDKBuild</key> <key>DTSDKBuild</key>
<string>16G73</string> <string>16G73</string>
<key>DTSDKName</key> <key>DTSDKName</key>
<string>{{.Platform}}12.4</string> <string>%s12.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>`) </plist>`, appName, bi.appID, appName, bi.version, bi.version, platform, minIOSVersion, supportPlatform, platform)
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 {
@@ -490,7 +404,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, 0o755); err != nil { if err := os.MkdirAll(p, 0755); err != nil {
return err return err
} }
} }
@@ -509,6 +423,16 @@ 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 {
@@ -528,7 +452,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=ios", "GOOS="+goos,
"GOARCH="+a, "GOARCH="+a,
"CGO_ENABLED=1", "CGO_ENABLED=1",
"CC="+clang, "CC="+clang,
@@ -546,7 +470,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", "-tags", tags, "-f", "{{.Dir}}", "gioui.org/app/")) appDir, err := runCmd(exec.Command("go", "list", "-f", "{{.Dir}}", "gioui.org/app/"))
if err != nil { if err != nil {
return err return err
} }
@@ -561,7 +485,25 @@ 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 os.WriteFile(moduleFile, []byte(module), 0o644) return ioutil.WriteFile(moduleFile, []byte(module), 0644)
}
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) {
@@ -582,9 +524,6 @@ 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"
@@ -604,6 +543,7 @@ 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),
+1 -2
View File
@@ -30,7 +30,6 @@ 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")
@@ -61,7 +60,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 any) { chromedp.ListenTarget(ctx, func(ev interface{}) {
switch ev := ev.(type) { switch ev := ev.(type) {
case *runtime.EventConsoleAPICalled: case *runtime.EventConsoleAPICalled:
switch ev.Type { switch ev.Type {
+8 -12
View File
@@ -6,6 +6,7 @@ import (
"bytes" "bytes"
"fmt" "fmt"
"io" "io"
"io/ioutil"
"os" "os"
"os/exec" "os/exec"
"path/filepath" "path/filepath"
@@ -20,7 +21,7 @@ func buildJS(bi *buildInfo) error {
if out == "" { if out == "" {
out = bi.name out = bi.name
} }
if err := os.MkdirAll(out, 0o700); err != nil { if err := os.MkdirAll(out, 0700); err != nil {
return err return err
} }
cmd := exec.Command( cmd := exec.Command(
@@ -44,11 +45,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 := os.ReadFile(bi.iconPath) icon, err := ioutil.ReadFile(bi.iconPath)
if err != nil { if err != nil {
return err return err
} }
if err := os.WriteFile(filepath.Join(out, filepath.Base(bi.iconPath)), icon, 0o600); err != nil { if err := ioutil.WriteFile(filepath.Join(out, filepath.Base(bi.iconPath)), icon, 0600); err != nil {
return err return err
} }
faviconPath = filepath.Base(bi.iconPath) faviconPath = filepath.Base(bi.iconPath)
@@ -70,7 +71,7 @@ func buildJS(bi *buildInfo) error {
return err return err
} }
if err := os.WriteFile(filepath.Join(out, "index.html"), b.Bytes(), 0o600); err != nil { if err := ioutil.WriteFile(filepath.Join(out, "index.html"), b.Bytes(), 0600); err != nil {
return err return err
} }
@@ -78,14 +79,9 @@ func buildJS(bi *buildInfo) error {
if err != nil { if err != nil {
return err return err
} }
// Location of the wasm_exec.js for go>=1.24 wasmJS := filepath.Join(goroot, "misc", "wasm", "wasm_exec.js")
wasmJS := filepath.Join(goroot, "lib", "wasm", "wasm_exec.js")
if _, err := os.Stat(wasmJS); err != nil { if _, err := os.Stat(wasmJS); err != nil {
// Location of the wasm_exec.js for go<1.24 return fmt.Errorf("failed to find $GOROOT/misc/wasm/wasm_exec.js driver: %v", err)
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,
@@ -174,7 +170,7 @@ const (
</html>` </html>`
// jsSetGo sets the `window.go` variable. // jsSetGo sets the `window.go` variable.
jsSetGo = `(() => { jsSetGo = `(() => {
window.go = {argv: [], env: {}, importObject: {go: {}, gojs: {}}}; window.go = {argv: [], env: {}, importObject: {go: {}}};
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(" ");
-285
View File
@@ -1,285 +0,0 @@
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
}
+9 -14
View File
@@ -11,6 +11,7 @@ import (
"image/color" "image/color"
"image/png" "image/png"
"io" "io"
"io/ioutil"
"os" "os"
"os/exec" "os/exec"
"path/filepath" "path/filepath"
@@ -24,25 +25,18 @@ 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)")
name = flag.String("name", "", "app name (for -buildmode=exe)") version = flag.Int("version", 1, "app version (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 (Android) or provisioning profile (macOS or iOS) for signing") signKey = flag.String("signkey", "", "specify the path of the keystore to be used to sign Android apk files.")
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() {
@@ -71,11 +65,14 @@ 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", "macos": case "ios", "tvos", "android", "js", "windows":
default: default:
return fmt.Errorf("invalid -target %s", *target) return fmt.Errorf("invalid -target %s", *target)
} }
@@ -88,7 +85,7 @@ func flagValidate() error {
} }
func build(bi *buildInfo) error { func build(bi *buildInfo) error {
tmpDir, err := os.MkdirTemp("", "gogio-") tmpDir, err := ioutil.TempDir("", "gogio-")
if err != nil { if err != nil {
return err return err
} }
@@ -106,8 +103,6 @@ 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")
} }
@@ -201,7 +196,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), 0o700); err != nil { if err := os.MkdirAll(filepath.Dir(path), 0700); err != nil {
return err return err
} }
f, err := os.Create(path) f, err := os.Create(path)
-6
View File
@@ -19,12 +19,6 @@ 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{
+1 -1
View File
@@ -174,7 +174,7 @@ func (d *WaylandTestDriver) Screenshot() image.Image {
return img return img
} }
func (d *WaylandTestDriver) swaymsg(args ...any) { func (d *WaylandTestDriver) swaymsg(args ...interface{}) {
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))
+11 -13
View File
@@ -7,10 +7,12 @@ 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"
@@ -38,6 +40,10 @@ 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()
@@ -48,7 +54,7 @@ func buildWindows(tmpDir string, bi *buildInfo) error {
} }
if err := builder.embedManifest(windowsManifest{ if err := builder.embedManifest(windowsManifest{
Version: bi.version.String(), Version: "1.0.0." + version,
WindowsVersion: sdk, WindowsVersion: sdk,
Name: name, Name: name,
}); err != nil { }); err != nil {
@@ -56,8 +62,8 @@ func buildWindows(tmpDir string, bi *buildInfo) error {
} }
if err := builder.embedInfo(windowsResources{ if err := builder.embedInfo(windowsResources{
Version: [2]uint32{uint32(bi.version.Major), uint32(bi.version.Minor)<<16 | uint32(bi.version.Patch)}, Version: [2]uint32{uint32(1) << 16, uint32(bi.version)},
VersionHuman: bi.version.String(), VersionHuman: "1.0.0." + version,
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 {
@@ -202,18 +208,10 @@ 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 "+ldflags, "-ldflags=-H=windowsgui "+buildInfo.ldflags,
"-tags="+buildInfo.tags, "-tags="+buildInfo.tags,
"-o", dest, "-o", dest,
buildInfo.pkgPath, buildInfo.pkgPath,
@@ -366,7 +364,7 @@ const (
valueText uint16 = 1 valueText uint16 = 1
) )
func newValue(valueType uint16, key string, input any) windowsInfoValue { func newValue(valueType uint16, key string, input interface{}) windowsInfoValue {
v := windowsInfoValue{ v := windowsInfoValue{
Type: valueType, Type: valueType,
Length: 6, Length: 6,
+1 -1
View File
@@ -145,7 +145,7 @@ func (d *X11TestDriver) Screenshot() image.Image {
return img return img
} }
func (d *X11TestDriver) xdotool(args ...any) string { func (d *X11TestDriver) xdotool(args ...interface{}) 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
View File
@@ -1,581 +0,0 @@
// 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,
)
}
`