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:
Elias Naur
2019-08-15 10:15:03 +02:00
parent c578043970
commit 50599bc65d
5 changed files with 162 additions and 13 deletions
+16 -2
View File
@@ -242,6 +242,20 @@ func exeAndroid(tmpDir string, tools *androidTools, bi *buildInfo) (err error) {
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"?>
<resources>
<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-permission android:name="android.permission.INTERNET" />
<uses-feature android:glEsVersion="0x00030000"/>
<application android:label="Gio">
<application %s android:label="%s">
<activity android:name="org.gioui.GioActivity"
android:label="%s"
android:theme="@style/Theme.GioApp"
@@ -296,7 +310,7 @@ func exeAndroid(tmpDir string, tools *androidTools, bi *buildInfo) (err error) {
</intent-filter>
</activity>
</application>
</manifest>`, *appID, bi.version, bi.version, appName)
</manifest>`, *appID, bi.version, bi.version, iconSnip, appName, appName)
manifest := filepath.Join(tmpDir, "AndroidManifest.xml")
if err := ioutil.WriteFile(manifest, []byte(manifestSrc), 0660); err != nil {
return err
+51
View File
@@ -6,12 +6,17 @@ import (
"bytes"
"flag"
"fmt"
"image"
"image/png"
"io"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"strings"
"golang.org/x/image/draw"
"golang.org/x/sync/errgroup"
)
var (
@@ -32,6 +37,7 @@ type buildInfo struct {
target string
appID string
version int
dir string
archs []string
}
@@ -68,11 +74,16 @@ func main() {
errorf("gio: %v", err)
}
name = filepath.Base(name)
dir, err := runCmd(exec.Command("go", "list", "-f", "{{.Dir}}", pkg))
if err != nil {
errorf("gio: %v", err)
}
bi := &buildInfo{
name: name,
pkg: pkg,
target: *target,
appID: *appID,
dir: dir,
version: *version,
}
switch *target {
@@ -189,3 +200,43 @@ var allArchs = map[string]arch{
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
View File
@@ -18,6 +18,8 @@ import (
"golang.org/x/sync/errgroup"
)
const minIOSVersion = "9.0"
func buildIOS(tmpDir, target string, bi *buildInfo) error {
appName := bi.name
switch *buildMode {
@@ -199,21 +201,86 @@ int main(int argc, char * argv[]) {
if err := ioutil.WriteFile(plistFile, []byte(infoPlist), 0660); err != nil {
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 {
return err
}
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 {
appName := strings.Title(bi.name)
var platform string
switch bi.target {
case "ios":
platform = "iphoneos"
case "tvos":
platform = "appletvos"
}
platform := iosPlatformFor(bi.target)
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">
<plist version="1.0">
@@ -243,7 +310,7 @@ func buildInfoPlist(bi *buildInfo) string {
<key>DTPlatformVersion</key>
<string>12.4</string>
<key>MinimumOSVersion</key>
<string>9.0</string>
<string>%s</string>
<key>UIDeviceFamily</key>
<array>
<integer>1</integer>
@@ -255,7 +322,18 @@ func buildInfoPlist(bi *buildInfo) string {
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
</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 {
@@ -379,7 +457,7 @@ func iosCompilerFor(target, arch string) (string, []string, error) {
"-Werror",
"-arch", allArchs[arch].iosArch,
"-isysroot", sdkPath,
"-m" + platformOS + "-version-min=9.0",
"-m" + platformOS + "-version-min=" + minIOSVersion,
}
return clang, cflags, nil
}
+4 -1
View File
@@ -2,4 +2,7 @@ module gioui.org/cmd
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
)
+3
View File
@@ -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/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=