a68d97f9476ecfb6ec0c4041b3c8681d1136d5bf — Greg Pomerantz 3 months ago 86b231c
cmd/gio: generate appID if not specified

Use the Go import path to create an appID based on the domain name
plus the last directory location in the import path.

Signed-off-by: Greg Pomerantz <gmp.gio@wow.st>
3 files changed, 74 insertions(+), 9 deletions(-)

M cmd/gio/androidbuild.go
M cmd/gio/gio.go
A cmd/gio/gio_test.go
M cmd/gio/androidbuild.go => cmd/gio/androidbuild.go +2 -5
@@ 176,10 176,10 @@ func archiveAndroid(tmpDir string, bi *buildInfo) (err error) {
 	aarw.Create("R.txt")
 	aarw.Create("res/")
 	manifest := aarw.Create("AndroidManifest.xml")
-	manifest.Write([]byte(`<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="org.gioui.app">
+	manifest.Write([]byte(fmt.Sprintf(`<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="%s">
 	<uses-sdk android:minSdkVersion="16"/>
 	<uses-feature android:glEsVersion="0x00030000" android:required="true" />
-</manifest>`))
+</manifest>`, bi.appID)))
 	proguard := aarw.Create("proguard.txt")
 	proguard.Write([]byte(`-keep class org.gioui.** { *; }`))
 


@@ 200,9 200,6 @@ func archiveAndroid(tmpDir string, bi *buildInfo) (err error) {
 }
 
 func exeAndroid(tmpDir string, tools *androidTools, bi *buildInfo) (err error) {
-	if bi.appID == "" {
-		return errors.New("app id is empty; use -appid to set it")
-	}
 	classes := filepath.Join(tmpDir, "classes")
 	var classFiles []string
 	err = filepath.Walk(classes, func(path string, f os.FileInfo, err error) error {

M cmd/gio/gio.go => cmd/gio/gio.go +40 -4
@@ 14,7 14,6 @@ import (
 	"io/ioutil"
 	"os"
 	"os/exec"
-	"path"
 	"path/filepath"
 	"strings"
 


@@ 27,7 26,7 @@ var (
 	archNames     = flag.String("arch", "", "specify architecture(s) to include (arm, arm64, amd64).")
 	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.")
-	appID         = flag.String("appid", "org.gioui.app", "app identifier (for -buildmode=exe)")
+	appID         = flag.String("appid", "", "app identifier (for -buildmode=exe)")
 	version       = flag.Int("version", 1, "app version (for -buildmode=exe)")
 	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.")


@@ 75,15 74,16 @@ func mainErr() error {
 		return fmt.Errorf("invalid -buildmode %s\n", *buildMode)
 	}
 	// Find package name.
-	name, err := runCmd(exec.Command("go", "list", "-f", "{{.ImportPath}}", pkg))
+	pkgPath, err := runCmd(exec.Command("go", "list", "-f", "{{.ImportPath}}", pkg))
 	if err != nil {
 		return fmt.Errorf("gio: %v", err)
 	}
-	name = path.Base(name)
 	dir, err := runCmd(exec.Command("go", "list", "-f", "{{.Dir}}", pkg))
 	if err != nil {
 		return fmt.Errorf("gio: %v", err)
 	}
+	elems := strings.Split(pkgPath, "/")
+	name := elems[len(elems)-1]
 	bi := &buildInfo{
 		name:    name,
 		pkg:     pkg,


@@ 92,6 92,10 @@ func mainErr() error {
 		dir:     dir,
 		version: *version,
 	}
+	if bi.appID == "" {
+		bi.appID = appIDFromPackage(pkgPath)
+	}
+
 	switch *target {
 	case "js":
 		bi.archs = []string{"wasm"}


@@ 114,6 118,38 @@ func mainErr() error {
 	return nil
 }
 
+func appIDFromPackage(pkgPath string) string {
+	elems := strings.Split(pkgPath, "/")
+	domain := strings.Split(elems[0], ".")
+	name := ""
+	if len(elems) > 1 {
+		name = "." + elems[len(elems)-1]
+	}
+	if len(elems) < 2 && len(domain) < 2 {
+		name = "." + domain[0]
+		domain[0] = "localhost"
+	} else {
+		for i := 0; i < len(domain)/2; i++ {
+			opp := len(domain) - 1 - i
+			domain[i], domain[opp] = domain[opp], domain[i]
+		}
+	}
+
+	pkgDomain := strings.Join(domain, ".")
+	appid := []rune(pkgDomain + name)
+
+	// a Java-language-style package name may contain upper- and lower-case
+	// letters and underscores with individual parts separated by '.'.
+	// https://developer.android.com/guide/topics/manifest/manifest-element
+	for i, c := range appid {
+		if !('a' <= c && c <= 'z' || 'A' <= c && c <= 'Z' ||
+			c == '_' || c == '.') {
+			appid[i] = '_'
+		}
+	}
+	return string(appid)
+}
+
 func build(bi *buildInfo) error {
 	tmpDir, err := ioutil.TempDir("", "gio-")
 	if err != nil {

A cmd/gio/gio_test.go => cmd/gio/gio_test.go +32 -0
@@ 0,0 1,32 @@
+package main
+
+import (
+	"testing"
+)
+
+type expval struct {
+	in, out string
+}
+
+func TestAppID(t *testing.T) {
+	tests := []expval{
+		{"example", "localhost.example"},
+		{"example.com", "com.example"},
+		{"www.example.com", "com.example.www"},
+		{"examplecom/app", "examplecom.app"},
+		{"example.com/app", "com.example.app"},
+		{"www.example.com/app", "com.example.www.app"},
+		{"www.en.example.com/app", "com.example.en.www.app"},
+		{"example.com/dir/app", "com.example.app"},
+		{"example.com/dir.ext/app", "com.example.app"},
+		{"example.com/dir/app.ext", "com.example.app.ext"},
+		{"example-com.net/dir/app", "net.example_com.app"},
+	}
+
+	for i, test := range tests {
+		got := appIDFromPackage(test.in)
+		if exp := test.out; got != exp {
+			t.Errorf("(%d): expected '%s', got '%s'", i, exp, got)
+		}
+	}
+}