diff --git a/gogio/androidbuild.go b/gogio/androidbuild.go
index 79943de..0428f3a 100644
--- a/gogio/androidbuild.go
+++ b/gogio/androidbuild.go
@@ -48,6 +48,7 @@ type manifestData struct {
Features []string
IconSnip string
AppName string
+ Schemes []string
}
const (
@@ -114,6 +115,7 @@ func buildAndroid(tmpDir string, bi *buildInfo) error {
return err
}
var extraJars []string
+ var extraAARs []string
visitedPkgs := make(map[string]bool)
var visitPkg func(*packages.Package) error
visitPkg = func(p *packages.Package) error {
@@ -126,6 +128,11 @@ func buildAndroid(tmpDir string, bi *buildInfo) error {
return err
}
extraJars = append(extraJars, jars...)
+ aars, err := filepath.Glob(filepath.Join(dir, "*.aar"))
+ if err != nil {
+ return err
+ }
+ extraAARs = append(extraAARs, aars...)
switch {
case p.PkgPath == "net":
perms = append(perms, "network")
@@ -166,7 +173,7 @@ func buildAndroid(tmpDir string, bi *buildInfo) error {
return fmt.Errorf("the specified output %q does not end in '.apk' or '.aab'", file)
}
- if err := exeAndroid(tmpDir, tools, bi, extraJars, perms, isBundle); err != nil {
+ if err := exeAndroid(tmpDir, tools, bi, extraJars, extraAARs, perms, isBundle); err != nil {
return err
}
if isBundle {
@@ -335,7 +342,7 @@ func archiveAndroid(tmpDir string, bi *buildInfo, perms []string) (err error) {
return aarw.Close()
}
-func exeAndroid(tmpDir string, tools *androidTools, bi *buildInfo, extraJars, perms []string, isBundle bool) (err error) {
+func exeAndroid(tmpDir string, tools *androidTools, bi *buildInfo, extraJars, extraAARs, perms []string, isBundle bool) (err error) {
classes := filepath.Join(tmpDir, "classes")
var classFiles []string
err = filepath.Walk(classes, func(path string, f os.FileInfo, err error) error {
@@ -347,7 +354,26 @@ func exeAndroid(tmpDir string, tools *androidTools, bi *buildInfo, extraJars, pe
}
return nil
})
+
+ // extract the jar files from the aars
+ aarOut := filepath.Join(tmpDir, "aars")
+ for _, aar := range extraAARs {
+ name := filepath.Base(aar)
+ name = strings.TrimSuffix(name, filepath.Ext(name))
+
+ if err := extractZip(filepath.Join(aarOut, name), aar); err != nil {
+ return err
+ }
+ }
+
+ // extract the jar files from the aars
+ jarsFromAAR, err := filepath.Glob(filepath.Join(aarOut, "*", "*.jar"))
+ if err != nil {
+ return err
+ }
+ extraJars = append(extraJars, jarsFromAAR...)
classFiles = append(classFiles, extraJars...)
+
dexDir := filepath.Join(tmpDir, "apk")
if err := os.MkdirAll(dexDir, 0755); err != nil {
return err
@@ -433,6 +459,24 @@ func exeAndroid(tmpDir string, tools *androidTools, bi *buildInfo, extraJars, pe
return err
}
+ resFromAAR, err := filepath.Glob(filepath.Join(aarOut, "*", "res"))
+ if err != nil {
+ return err
+ }
+
+ for i, res := range resFromAAR {
+ resZip := filepath.Join(tmpDir, fmt.Sprintf("aar-%d-resources.zip", i))
+
+ _, err = runCmd(exec.Command(
+ aapt2,
+ "compile",
+ "-o", resZip,
+ "--dir", res))
+ if err != nil {
+ return err
+ }
+ }
+
// Link APK.
permissions, features := getPermissions(perms)
appName := UppercaseName(bi.name)
@@ -445,6 +489,7 @@ func exeAndroid(tmpDir string, tools *androidTools, bi *buildInfo, extraJars, pe
Features: features,
IconSnip: iconSnip,
AppName: appName,
+ Schemes: bi.schemes,
}
tmpl, err := template.New("test").Parse(
`
@@ -461,11 +506,20 @@ func exeAndroid(tmpDir string, tools *androidTools, bi *buildInfo, extraJars, pe
android:theme="@style/Theme.GioApp"
android:configChanges="screenSize|screenLayout|smallestScreenSize|orientation|keyboardHidden"
android:windowSoftInputMode="adjustResize"
+ android:launchMode= "singleInstance"
android:exported="true">
+ {{range .Schemes}}
+
+
+
+
+
+
+ {{end}}
`)
@@ -478,6 +532,29 @@ func exeAndroid(tmpDir string, tools *androidTools, bi *buildInfo, extraJars, pe
return err
}
+ manifestsFromAAR, err := filepath.Glob(filepath.Join(aarOut, "*", "AndroidManifest.xml"))
+ if err != nil {
+ return err
+ }
+
+ // Merge manifests, if any.
+ if len(manifestsFromAAR) > 0 {
+ if _, err := os.Stat(filepath.Join(tools.buildtools, "manifest-merger.jar")); err != nil {
+ return fmt.Errorf("manifest-merger.jar not found in buildtools. Download it from https://github.com/distriqt/android-manifest-merger and place it in %s", tools.buildtools)
+ }
+
+ cmd := exec.Command("java",
+ "-jar",
+ filepath.Join(tools.buildtools, "manifest-merger.jar"),
+ "--main", manifest,
+ "--libs", strings.Join(manifestsFromAAR, ":"),
+ "--out", manifest,
+ )
+ if _, err := runCmd(cmd); err != nil {
+ return err
+ }
+ }
+
linkAPK := filepath.Join(tmpDir, "link.apk")
args := []string{
@@ -485,10 +562,19 @@ func exeAndroid(tmpDir string, tools *androidTools, bi *buildInfo, extraJars, pe
"--manifest", manifest,
"-I", tools.androidjar,
"-o", linkAPK,
+ "--auto-add-overlay",
}
if isBundle {
args = append(args, "--proto-format")
}
+
+ allResZip, err := filepath.Glob(filepath.Join(tmpDir, "*-resources.zip"))
+ if err != nil {
+ return err
+ }
+ for _, resZip := range allResZip {
+ args = append(args, "-R", resZip)
+ }
args = append(args, resZip)
if _, err := runCmd(exec.Command(aapt2, args...)); err != nil {
@@ -1043,3 +1129,33 @@ func (w *errWriter) Write(p []byte) (n int, err error) {
*w.err = err
return
}
+
+func extractZip(out string, zipFile string) error {
+ //extract the zip file
+ r, err := zip.OpenReader(zipFile)
+ if err != nil {
+ return err
+ }
+ defer r.Close()
+
+ for _, f := range r.File {
+ if err := os.MkdirAll(filepath.Dir(filepath.Join(out, f.Name)), 0777); err != nil {
+ return err
+ }
+ if f.FileInfo().IsDir() {
+ continue
+ }
+ out, err := os.Create(filepath.Join(out, f.Name))
+ rc, err := f.Open()
+ if err != nil {
+ return err
+ }
+ if _, err := io.Copy(out, rc); err != nil {
+ return err
+ }
+ rc.Close()
+ out.Close()
+ }
+
+ return nil
+}
diff --git a/gogio/build_info.go b/gogio/build_info.go
index 1797296..0dc8edc 100644
--- a/gogio/build_info.go
+++ b/gogio/build_info.go
@@ -30,6 +30,7 @@ type buildInfo struct {
notaryAppleID string
notaryPassword string
notaryTeamID string
+ schemes []string
}
type Semver struct {
@@ -51,6 +52,10 @@ func newBuildInfo(pkgPath string) (*buildInfo, error) {
if *name != "" {
appName = *name
}
+ schemes := strings.Split(*schemes, ",")
+ for i, scheme := range schemes {
+ schemes[i] = strings.TrimSpace(scheme)
+ }
ver, err := parseSemver(*version)
if err != nil {
return nil, err
@@ -72,6 +77,7 @@ func newBuildInfo(pkgPath string) (*buildInfo, error) {
notaryAppleID: *notaryID,
notaryPassword: *notaryPass,
notaryTeamID: *notaryTeamID,
+ schemes: schemes,
}
return bi, nil
}
diff --git a/gogio/help.go b/gogio/help.go
index 2561c2b..5541bb8 100644
--- a/gogio/help.go
+++ b/gogio/help.go
@@ -65,7 +65,8 @@ its deletion.
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
-or specifies the name of key on Keychain to sign MacOS app.
+or specifies the name of key on Keychain to sign MacOS app. On iOS/macOS it can be used to
+specify the path of provisioning profile (.mobileprovision/.provisionprofile).
The -signpass flag specifies the password of the keystore, ignored if -signkey is not provided.
@@ -77,4 +78,9 @@ 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 transfer.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.
`
diff --git a/gogio/iosbuild.go b/gogio/iosbuild.go
index 1126cd5..437af2b 100644
--- a/gogio/iosbuild.go
+++ b/gogio/iosbuild.go
@@ -4,6 +4,7 @@ package main
import (
"archive/zip"
+ "bytes"
"crypto/sha1"
"encoding/hex"
"errors"
@@ -14,6 +15,7 @@ import (
"path/filepath"
"strconv"
"strings"
+ "text/template"
"time"
"golang.org/x/sync/errgroup"
@@ -72,7 +74,8 @@ func buildIOS(tmpDir, target string, bi *buildInfo) error {
if err := exeIOS(tmpDir, target, appDir, bi); err != nil {
return err
}
- if err := signIOS(bi, tmpDir, appDir); err != nil {
+ embedded := filepath.Join(appDir, "embedded.mobileprovision")
+ if err := signApple(bi, tmpDir, embedded, appDir); err != nil {
return err
}
return zipDir(out, tmpDir, "Payload")
@@ -81,16 +84,27 @@ func buildIOS(tmpDir, target string, bi *buildInfo) error {
}
}
-func signIOS(bi *buildInfo, tmpDir, app string) error {
+// signApple is shared between iOS and macOS.
+func signApple(bi *buildInfo, tmpDir, embedded, app 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
+
+ var provisions []string
+ if bi.key != "" {
+ if filepath.Ext(bi.key) != ".mobileprovision" && filepath.Ext(bi.key) != ".provisionprofile" {
+ return fmt.Errorf("sign: on iOS/macOS -key is a provisioning profile, %q does not end in .mobileprovision/.provisionprofile", bi.key)
+ }
+ provisions = []string{bi.key}
+ } else {
+ 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")
var avail []string
for _, prov := range provisions {
@@ -114,7 +128,14 @@ func signIOS(bi *buildInfo, tmpDir, app string) error {
if err != nil {
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 {
return err
}
@@ -124,7 +145,6 @@ func signIOS(bi *buildInfo, tmpDir, app string) error {
continue
}
// Copy provisioning file.
- embedded := filepath.Join(app, "embedded.mobileprovision")
if err := copyFile(embedded, prov); err != nil {
return err
}
@@ -144,7 +164,15 @@ func signIOS(bi *buildInfo, tmpDir, app string) error {
}
identity := sha1.Sum(certDER)
idHex := hex.EncodeToString(identity[:])
- _, err = runCmd(exec.Command("codesign", "-s", idHex, "-v", "--entitlements", entFile, app))
+ _, err = runCmd(exec.Command(
+ "codesign",
+ "--sign", idHex,
+ "--deep",
+ "--force",
+ "--options", "runtime",
+ "--entitlements",
+ entFile,
+ app))
return err
}
return fmt.Errorf("sign: no valid provisioning profile found for bundle id %q among %v", bi.appID, avail)
@@ -203,7 +231,10 @@ func exeIOS(tmpDir, target, app string, bi *buildInfo) error {
if _, err := runCmd(lipo); err != nil {
return err
}
- infoPlist := buildInfoPlist(bi)
+ infoPlist, err := buildInfoPlist(bi)
+ if err != nil {
+ return err
+ }
plistFile := filepath.Join(app, "Info.plist")
if err := os.WriteFile(plistFile, []byte(infoPlist), 0660); err != nil {
return err
@@ -291,7 +322,7 @@ func iosIcons(bi *buildInfo, tmpDir, appDir, icon string) (string, error) {
return assetPlist, err
}
-func buildInfoPlist(bi *buildInfo) string {
+func buildInfoPlist(bi *buildInfo) (string, error) {
appName := UppercaseName(bi.name)
platform := iosPlatformFor(bi.target)
var supportPlatform string
@@ -301,36 +332,57 @@ func buildInfoPlist(bi *buildInfo) string {
case "tvos":
supportPlatform = "AppleTVOS"
}
- return fmt.Sprintf(`
+
+ 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.String(),
+ VersionCode: bi.version.VersionCode,
+ Platform: platform,
+ MinVersion: minIOSVersion,
+ SupportPlatform: supportPlatform,
+ Schemes: bi.schemes,
+ }
+
+ tmpl, err := template.New("manifest").Parse(`
CFBundleDevelopmentRegion
en
CFBundleExecutable
- %s
+ {{.AppName}}
CFBundleIdentifier
- %s
+ {{.AppID}}
CFBundleInfoDictionaryVersion
6.0
CFBundleName
- %s
+ {{.AppName}}
CFBundlePackageType
APPL
CFBundleShortVersionString
- %s
+ {{.Version}}
CFBundleVersion
- %d
+ {{.VersionCode}}
UILaunchStoryboardName
LaunchScreen
UIRequiredDeviceCapabilities
arm64
DTPlatformName
- %s
+ {{.Platform}}
DTPlatformVersion
12.4
MinimumOSVersion
- %d
+ {{.MinVersion}}
UIDeviceFamily
1
@@ -338,7 +390,7 @@ func buildInfoPlist(bi *buildInfo) string {
CFBundleSupportedPlatforms
- %s
+ {{.SupportPlatform}}
UISupportedInterfaceOrientations
@@ -353,13 +405,36 @@ func buildInfoPlist(bi *buildInfo) string {
DTSDKBuild
16G73
DTSDKName
- %s12.4
+ {{.Platform}}12.4
DTXcode
1030
DTXcodeBuild
10G8
+ {{if .Schemes}}
+ CFBundleURLTypes
+
+ {{range .Schemes}}
+
+ CFBundleURLSchemes
+
+ {{.}}
+
+
+ {{end}}
+
+ {{end}}
-`, appName, bi.appID, appName, bi.version, bi.version.VersionCode, 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(), nil
}
func iosPlatformFor(target string) string {
diff --git a/gogio/macosbuild.go b/gogio/macosbuild.go
index 88e9463..bfe2ece 100644
--- a/gogio/macosbuild.go
+++ b/gogio/macosbuild.go
@@ -1,6 +1,7 @@
package main
import (
+ "bytes"
"errors"
"fmt"
"os"
@@ -124,6 +125,19 @@ func (b *macBuilder) setIcon(path string) (err error) {
}
func (b *macBuilder) setInfo(buildInfo *buildInfo, name string) error {
+
+ 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(`
@@ -137,21 +151,29 @@ func (b *macBuilder) setInfo(buildInfo *buildInfo, name string) error {
NSHighResolutionCapable
CFBundlePackageType
- APPL
+ BNDL
+ {{if .Schemes}}
+ CFBundleURLTypes
+
+ {{range .Schemes}}
+
+ CFBundleURLSchemes
+
+ {{.}}
+
+
+ {{end}}
+
+ {{end}}
`)
if err != nil {
- return err
+ panic(err)
}
- var manifest bufferCoff
- if err := t.Execute(&manifest, struct {
- Name, Bundle string
- }{
- Name: name,
- Bundle: buildInfo.appID,
- }); err != nil {
- return err
+ var manifest bytes.Buffer
+ if err := t.Execute(&manifest, manifestSrc); err != nil {
+ panic(err)
}
b.Manifest = manifest.Bytes()
@@ -215,6 +237,12 @@ func (b *macBuilder) signProgram(buildInfo *buildInfo, binDest string, name stri
return err
}
+ // If the key is a provisioning profile use the same signing process as iOS
+ if strings.HasSuffix(buildInfo.key, ".provisionprofile") {
+ embedded := filepath.Join(binDest, "Contents", "embedded.provisionprofile")
+ return signApple(buildInfo, b.TempDir, embedded, binDest)
+ }
+
cmd := exec.Command(
"codesign",
"--deep",
diff --git a/gogio/main.go b/gogio/main.go
index 5fe373e..e29407d 100644
--- a/gogio/main.go
+++ b/gogio/main.go
@@ -35,11 +35,12 @@ var (
extraLdflags = flag.String("ldflags", "", "extra flags to the Go linker")
extraTags = flag.String("tags", "", "extra tags to the Go tool")
iconPath = flag.String("icon", "", "specify an icon for iOS and Android")
- signKey = flag.String("signkey", "", "specify the path of the keystore to be used to sign Android apk files.")
+ signKey = flag.String("signkey", "", "specify the path of the keystore to be used to sign Android apk files and macOS app. It can be used for iOS and macOS to specify Provisioning Profiles.")
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 deep-linking schemes that the program accepts")
)
func main() {
diff --git a/gogio/windowsbuild.go b/gogio/windowsbuild.go
index c867e03..5404893 100644
--- a/gogio/windowsbuild.go
+++ b/gogio/windowsbuild.go
@@ -202,10 +202,18 @@ func (b *windowsBuilder) buildProgram(buildInfo *buildInfo, name string, arch st
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(
"go",
"build",
- "-ldflags=-H=windowsgui "+buildInfo.ldflags,
+ "-ldflags=-H=windowsgui "+ldflags,
"-tags="+buildInfo.tags,
"-o", dest,
buildInfo.pkgPath,