diff --git a/cmd/gio/androidbuild.go b/cmd/gio/androidbuild.go index 8ff7b7e1..5802fa83 100644 --- a/cmd/gio/androidbuild.go +++ b/cmd/gio/androidbuild.go @@ -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.Write([]byte(fmt.Sprintf(` -`)) +`, 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 { diff --git a/cmd/gio/gio.go b/cmd/gio/gio.go index f23ff385..db23ae51 100644 --- a/cmd/gio/gio.go +++ b/cmd/gio/gio.go @@ -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 { diff --git a/cmd/gio/gio_test.go b/cmd/gio/gio_test.go new file mode 100644 index 00000000..4baa1128 --- /dev/null +++ b/cmd/gio/gio_test.go @@ -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) + } + } +}