Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Provisionkey macos #14

Draft
wants to merge 14 commits into
base: main
Choose a base branch
from
120 changes: 118 additions & 2 deletions gogio/androidbuild.go
Original file line number Diff line number Diff line change
@@ -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(
`<?xml version="1.0" encoding="utf-8"?>
@@ -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">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
{{range .Schemes}}
<intent-filter>
<action android:name="android.intent.action.VIEW"></action>
<category android:name="android.intent.category.DEFAULT"></category>
<category android:name="android.intent.category.BROWSABLE"></category>
<data android:scheme="{{.}}"></data>
</intent-filter>
{{end}}
</activity>
</application>
</manifest>`)
@@ -478,17 +532,49 @@ 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{
"link",
"--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
}
6 changes: 6 additions & 0 deletions gogio/build_info.go
Original file line number Diff line number Diff line change
@@ -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
}
8 changes: 7 additions & 1 deletion gogio/help.go
Original file line number Diff line number Diff line change
@@ -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.
`
119 changes: 97 additions & 22 deletions gogio/iosbuild.go
Original file line number Diff line number Diff line change
@@ -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,44 +332,65 @@ func buildInfoPlist(bi *buildInfo) string {
case "tvos":
supportPlatform = "AppleTVOS"
}
return fmt.Sprintf(`<?xml version="1.0" encoding="UTF-8"?>

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(`<?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">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>%s</string>
<string>{{.AppName}}</string>
<key>CFBundleIdentifier</key>
<string>%s</string>
<string>{{.AppID}}</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>%s</string>
<string>{{.AppName}}</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>%s</string>
<string>{{.Version}}</string>
<key>CFBundleVersion</key>
<string>%d</string>
<string>{{.VersionCode}}</string>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIRequiredDeviceCapabilities</key>
<array><string>arm64</string></array>
<key>DTPlatformName</key>
<string>%s</string>
<string>{{.Platform}}</string>
<key>DTPlatformVersion</key>
<string>12.4</string>
<key>MinimumOSVersion</key>
<string>%d</string>
<string>{{.MinVersion}}</string>
<key>UIDeviceFamily</key>
<array>
<integer>1</integer>
<integer>2</integer>
</array>
<key>CFBundleSupportedPlatforms</key>
<array>
<string>%s</string>
<string>{{.SupportPlatform}}</string>
</array>
<key>UISupportedInterfaceOrientations</key>
<array>
@@ -353,13 +405,36 @@ func buildInfoPlist(bi *buildInfo) string {
<key>DTSDKBuild</key>
<string>16G73</string>
<key>DTSDKName</key>
<string>%s12.4</string>
<string>{{.Platform}}12.4</string>
<key>DTXcode</key>
<string>1030</string>
<key>DTXcodeBuild</key>
<string>10G8</string>
{{if .Schemes}}
<key>CFBundleURLTypes</key>
<array>
{{range .Schemes}}
<dict>
<key>CFBundleURLSchemes</key>
<array>
<string>{{.}}</string>
</array>
</dict>
{{end}}
</array>
{{end}}
</dict>
</plist>`, appName, bi.appID, appName, bi.version, bi.version.VersionCode, platform, minIOSVersion, supportPlatform, platform)
</plist>`)
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 {
48 changes: 38 additions & 10 deletions gogio/macosbuild.go
Original file line number Diff line number Diff line change
@@ -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(`<?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">
@@ -137,21 +151,29 @@ func (b *macBuilder) setInfo(buildInfo *buildInfo, name string) error {
<key>NSHighResolutionCapable</key>
<true/>
<key>CFBundlePackageType</key>
<string>APPL</string>
<string>BNDL</string>
{{if .Schemes}}
<key>CFBundleURLTypes</key>
<array>
{{range .Schemes}}
<dict>
<key>CFBundleURLSchemes</key>
<array>
<string>{{.}}</string>
</array>
</dict>
{{end}}
</array>
{{end}}
</dict>
</plist>`)
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",
3 changes: 2 additions & 1 deletion gogio/main.go
Original file line number Diff line number Diff line change
@@ -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() {
10 changes: 9 additions & 1 deletion gogio/windowsbuild.go
Original file line number Diff line number Diff line change
@@ -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,