mirror of
https://git.sr.ht/~eliasnaur/gio
synced 2026-07-06 01:45:36 +00:00
cmd/gio: support app icons
If there is an appicon.png file in the main package the gio tool will use it for Android and iOS apps in buildmode exe. Signed-off-by: Elias Naur <mail@eliasnaur.com>
This commit is contained in:
+16
-2
@@ -242,6 +242,20 @@ func exeAndroid(tmpDir string, tools *androidTools, bi *buildInfo) (err error) {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
icon := filepath.Join(bi.dir, "appicon.png")
|
||||||
|
iconSnip := ""
|
||||||
|
if _, err := os.Stat(icon); err == nil {
|
||||||
|
err := buildIcons(resDir, icon, []iconVariant{
|
||||||
|
{filepath.Join("mipmap-hdpi", "ic_launcher.png"), 72},
|
||||||
|
{filepath.Join("mipmap-xhdpi", "ic_launcher.png"), 96},
|
||||||
|
{filepath.Join("mipmap-xxhdpi", "ic_launcher.png"), 144},
|
||||||
|
{filepath.Join("mipmap-xxxhdpi", "ic_launcher.png"), 192},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
iconSnip = `android:icon="@mipmap/ic_launcher"`
|
||||||
|
}
|
||||||
themes := `<?xml version="1.0" encoding="utf-8"?>
|
themes := `<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<style name="Theme.GioApp" parent="android:style/Theme.NoTitleBar">
|
<style name="Theme.GioApp" parent="android:style/Theme.NoTitleBar">
|
||||||
@@ -284,7 +298,7 @@ func exeAndroid(tmpDir string, tools *androidTools, bi *buildInfo) (err error) {
|
|||||||
<uses-sdk android:minSdkVersion="16" android:targetSdkVersion="28" />
|
<uses-sdk android:minSdkVersion="16" android:targetSdkVersion="28" />
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
<uses-feature android:glEsVersion="0x00030000"/>
|
<uses-feature android:glEsVersion="0x00030000"/>
|
||||||
<application android:label="Gio">
|
<application %s android:label="%s">
|
||||||
<activity android:name="org.gioui.GioActivity"
|
<activity android:name="org.gioui.GioActivity"
|
||||||
android:label="%s"
|
android:label="%s"
|
||||||
android:theme="@style/Theme.GioApp"
|
android:theme="@style/Theme.GioApp"
|
||||||
@@ -296,7 +310,7 @@ func exeAndroid(tmpDir string, tools *androidTools, bi *buildInfo) (err error) {
|
|||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
</application>
|
</application>
|
||||||
</manifest>`, *appID, bi.version, bi.version, appName)
|
</manifest>`, *appID, bi.version, bi.version, iconSnip, appName, appName)
|
||||||
manifest := filepath.Join(tmpDir, "AndroidManifest.xml")
|
manifest := filepath.Join(tmpDir, "AndroidManifest.xml")
|
||||||
if err := ioutil.WriteFile(manifest, []byte(manifestSrc), 0660); err != nil {
|
if err := ioutil.WriteFile(manifest, []byte(manifestSrc), 0660); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -6,12 +6,17 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"image"
|
||||||
|
"image/png"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"golang.org/x/image/draw"
|
||||||
|
"golang.org/x/sync/errgroup"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -32,6 +37,7 @@ type buildInfo struct {
|
|||||||
target string
|
target string
|
||||||
appID string
|
appID string
|
||||||
version int
|
version int
|
||||||
|
dir string
|
||||||
archs []string
|
archs []string
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,11 +74,16 @@ func main() {
|
|||||||
errorf("gio: %v", err)
|
errorf("gio: %v", err)
|
||||||
}
|
}
|
||||||
name = filepath.Base(name)
|
name = filepath.Base(name)
|
||||||
|
dir, err := runCmd(exec.Command("go", "list", "-f", "{{.Dir}}", pkg))
|
||||||
|
if err != nil {
|
||||||
|
errorf("gio: %v", err)
|
||||||
|
}
|
||||||
bi := &buildInfo{
|
bi := &buildInfo{
|
||||||
name: name,
|
name: name,
|
||||||
pkg: pkg,
|
pkg: pkg,
|
||||||
target: *target,
|
target: *target,
|
||||||
appID: *appID,
|
appID: *appID,
|
||||||
|
dir: dir,
|
||||||
version: *version,
|
version: *version,
|
||||||
}
|
}
|
||||||
switch *target {
|
switch *target {
|
||||||
@@ -189,3 +200,43 @@ var allArchs = map[string]arch{
|
|||||||
clang: "x86_64-linux-android21-clang",
|
clang: "x86_64-linux-android21-clang",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type iconVariant struct {
|
||||||
|
path string
|
||||||
|
size int
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildIcons(baseDir, icon string, variants []iconVariant) error {
|
||||||
|
f, err := os.Open(icon)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
img, _, err := image.Decode(f)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var resizes errgroup.Group
|
||||||
|
for _, v := range variants {
|
||||||
|
v := v
|
||||||
|
resizes.Go(func() (err error) {
|
||||||
|
scaled := image.NewNRGBA(image.Rectangle{Max: image.Point{X: v.size, Y: v.size}})
|
||||||
|
draw.CatmullRom.Scale(scaled, scaled.Bounds(), img, img.Bounds(), draw.Src, nil)
|
||||||
|
path := filepath.Join(baseDir, v.path)
|
||||||
|
if err := os.MkdirAll(filepath.Dir(path), 0700); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
f, err := os.Create(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if cerr := f.Close(); err == nil {
|
||||||
|
err = cerr
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
return png.Encode(f, scaled)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return resizes.Wait()
|
||||||
|
}
|
||||||
|
|||||||
+88
-10
@@ -18,6 +18,8 @@ import (
|
|||||||
"golang.org/x/sync/errgroup"
|
"golang.org/x/sync/errgroup"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const minIOSVersion = "9.0"
|
||||||
|
|
||||||
func buildIOS(tmpDir, target string, bi *buildInfo) error {
|
func buildIOS(tmpDir, target string, bi *buildInfo) error {
|
||||||
appName := bi.name
|
appName := bi.name
|
||||||
switch *buildMode {
|
switch *buildMode {
|
||||||
@@ -199,21 +201,86 @@ int main(int argc, char * argv[]) {
|
|||||||
if err := ioutil.WriteFile(plistFile, []byte(infoPlist), 0660); err != nil {
|
if err := ioutil.WriteFile(plistFile, []byte(infoPlist), 0660); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
icon := filepath.Join(bi.dir, "appicon.png")
|
||||||
|
if _, err := os.Stat(icon); err == nil {
|
||||||
|
assetPlist, err := iosIcons(bi, tmpDir, app, icon)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Merge assets plist with Info.plist
|
||||||
|
cmd := exec.Command(
|
||||||
|
"/usr/libexec/PlistBuddy",
|
||||||
|
"-c", "Merge "+assetPlist,
|
||||||
|
plistFile,
|
||||||
|
)
|
||||||
|
if _, err := runCmd(cmd); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
if _, err := runCmd(exec.Command("plutil", "-convert", "binary1", plistFile)); err != nil {
|
if _, err := runCmd(exec.Command("plutil", "-convert", "binary1", plistFile)); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// iosIcons builds an asset catalog and compile it with the Xcode command actool.
|
||||||
|
// iosIcons returns the asset plist file to be merged into Info.plist.
|
||||||
|
func iosIcons(bi *buildInfo, tmpDir, appDir, icon string) (string, error) {
|
||||||
|
assets := filepath.Join(tmpDir, "Assets.xcassets")
|
||||||
|
if err := os.Mkdir(assets, 0700); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
appIcon := filepath.Join(assets, "AppIcon.appiconset")
|
||||||
|
err := buildIcons(appIcon, icon, []iconVariant{
|
||||||
|
{"ios_2x.png", 120},
|
||||||
|
{"ios_3x.png", 180},
|
||||||
|
{"ios_store.png", 1024},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
contentJson := `{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"size" : "60x60",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"filename" : "ios_2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "60x60",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"filename" : "ios_3x.png",
|
||||||
|
"scale" : "3x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "1024x1024",
|
||||||
|
"idiom" : "ios-marketing",
|
||||||
|
"filename" : "ios_store.png",
|
||||||
|
"scale" : "1x"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}`
|
||||||
|
contentFile := filepath.Join(appIcon, "Contents.json")
|
||||||
|
if err := ioutil.WriteFile(contentFile, []byte(contentJson), 0600); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
assetPlist := filepath.Join(tmpDir, "assets.plist")
|
||||||
|
compile := exec.Command(
|
||||||
|
"actool",
|
||||||
|
"--compile", appDir,
|
||||||
|
"--platform", iosPlatformFor(bi.target),
|
||||||
|
"--minimum-deployment-target", minIOSVersion,
|
||||||
|
"--app-icon", "AppIcon",
|
||||||
|
"--output-partial-info-plist", assetPlist,
|
||||||
|
assets)
|
||||||
|
_, err = runCmd(compile)
|
||||||
|
return assetPlist, err
|
||||||
|
}
|
||||||
|
|
||||||
func buildInfoPlist(bi *buildInfo) string {
|
func buildInfoPlist(bi *buildInfo) string {
|
||||||
appName := strings.Title(bi.name)
|
appName := strings.Title(bi.name)
|
||||||
var platform string
|
platform := iosPlatformFor(bi.target)
|
||||||
switch bi.target {
|
|
||||||
case "ios":
|
|
||||||
platform = "iphoneos"
|
|
||||||
case "tvos":
|
|
||||||
platform = "appletvos"
|
|
||||||
}
|
|
||||||
return fmt.Sprintf(`<?xml version="1.0" encoding="UTF-8"?>
|
return fmt.Sprintf(`<?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">
|
||||||
@@ -243,7 +310,7 @@ func buildInfoPlist(bi *buildInfo) string {
|
|||||||
<key>DTPlatformVersion</key>
|
<key>DTPlatformVersion</key>
|
||||||
<string>12.4</string>
|
<string>12.4</string>
|
||||||
<key>MinimumOSVersion</key>
|
<key>MinimumOSVersion</key>
|
||||||
<string>9.0</string>
|
<string>%s</string>
|
||||||
<key>UIDeviceFamily</key>
|
<key>UIDeviceFamily</key>
|
||||||
<array>
|
<array>
|
||||||
<integer>1</integer>
|
<integer>1</integer>
|
||||||
@@ -255,7 +322,18 @@ func buildInfoPlist(bi *buildInfo) string {
|
|||||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||||
</array>
|
</array>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>`, bi.appID, appName, bi.version, bi.version, platform)
|
</plist>`, bi.appID, appName, bi.version, bi.version, platform, minIOSVersion)
|
||||||
|
}
|
||||||
|
|
||||||
|
func iosPlatformFor(target string) string {
|
||||||
|
switch target {
|
||||||
|
case "ios":
|
||||||
|
return "iphoneos"
|
||||||
|
case "tvos":
|
||||||
|
return "appletvos"
|
||||||
|
default:
|
||||||
|
panic("invalid platform " + target)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func archiveIOS(tmpDir, target, frameworkRoot string, bi *buildInfo) error {
|
func archiveIOS(tmpDir, target, frameworkRoot string, bi *buildInfo) error {
|
||||||
@@ -379,7 +457,7 @@ func iosCompilerFor(target, arch string) (string, []string, error) {
|
|||||||
"-Werror",
|
"-Werror",
|
||||||
"-arch", allArchs[arch].iosArch,
|
"-arch", allArchs[arch].iosArch,
|
||||||
"-isysroot", sdkPath,
|
"-isysroot", sdkPath,
|
||||||
"-m" + platformOS + "-version-min=9.0",
|
"-m" + platformOS + "-version-min=" + minIOSVersion,
|
||||||
}
|
}
|
||||||
return clang, cflags, nil
|
return clang, cflags, nil
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-1
@@ -2,4 +2,7 @@ module gioui.org/cmd
|
|||||||
|
|
||||||
go 1.12
|
go 1.12
|
||||||
|
|
||||||
require golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4
|
require (
|
||||||
|
golang.org/x/image v0.0.0-20190802002840-cff245a6509b
|
||||||
|
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4
|
||||||
|
)
|
||||||
|
|||||||
@@ -1,2 +1,5 @@
|
|||||||
|
golang.org/x/image v0.0.0-20190802002840-cff245a6509b h1:+qEpEAPhDZ1o0x3tHzZTQDArnOixOzGD9HUJfcg0mb4=
|
||||||
|
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 h1:YUO/7uOKsKeq9UokNS62b8FYywz3ker1l1vDZRCRefw=
|
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 h1:YUO/7uOKsKeq9UokNS62b8FYywz3ker1l1vDZRCRefw=
|
||||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
|||||||
Reference in New Issue
Block a user