Skip to content

cmd/gomobile: support Catalyst and Apple Silicon #63

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

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
250 changes: 147 additions & 103 deletions cmd/gomobile/bind_iosapp.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,133 +40,177 @@ func goIOSBind(gobind string, pkgs []*packages.Package, archs []string) error {

var name string
var title string
var buildTemp string

if buildO == "" {
name = pkgs[0].Name
title = strings.Title(name)
buildO = title + ".framework"
buildO = title + ".xcframework"
} else {
if !strings.HasSuffix(buildO, ".framework") {
return fmt.Errorf("static framework name %q missing .framework suffix", buildO)
if !strings.HasSuffix(buildO, ".xcframework") {
return fmt.Errorf("static framework name %q missing .xcframework suffix", buildO)
}
base := filepath.Base(buildO)
name = base[:len(base)-len(".framework")]
name = base[:len(base)-len(".xcframework")]
title = strings.Title(name)
}

fileBases := make([]string, len(pkgs)+1)
for i, pkg := range pkgs {
fileBases[i] = bindPrefix + strings.Title(pkg.Name)
// Build static xcframework output directory.
if err := removeAll(buildO); err != nil {
return err
}
fileBases[len(fileBases)-1] = "Universe"

cmd = exec.Command("xcrun", "lipo", "-create")
targets := allTargets("ios")

for _, arch := range archs {
if err := writeGoMod("darwin", arch); err != nil {
return err
}
// create separate framework for ios,simulator and catalyst
// every target has at least one arch (arm64 and x86_64)
for _, target := range targets {
archs := allTargetArchs("ios", target)

env := darwinEnv[arch]
// Add the generated packages to GOPATH for reverse bindings.
gopath := fmt.Sprintf("GOPATH=%s%c%s", tmpdir, filepath.ListSeparator, goEnv("GOPATH"))
env = append(env, gopath)
path, err := goIOSBindArchive(name, env, filepath.Join(tmpdir, "src"))
if err != nil {
return fmt.Errorf("darwin-%s: %v", arch, err)
}
cmd.Args = append(cmd.Args, "-arch", archClang(arch), path)
}
for index, arch := range archs {
buildTemp = tmpdir + "/" + target + "/" + title + ".framework"

// Build static framework output directory.
if err := removeAll(buildO); err != nil {
return err
}
headers := buildO + "/Versions/A/Headers"
if err := mkdir(headers); err != nil {
return err
}
if err := symlink("A", buildO+"/Versions/Current"); err != nil {
return err
}
if err := symlink("Versions/Current/Headers", buildO+"/Headers"); err != nil {
return err
}
if err := symlink("Versions/Current/"+title, buildO+"/"+title); err != nil {
return err
}
fileBases := make([]string, len(pkgs)+1)
for i, pkg := range pkgs {
fileBases[i] = bindPrefix + strings.Title(pkg.Name)
}
fileBases[len(fileBases)-1] = "Universe"

cmd.Args = append(cmd.Args, "-o", buildO+"/Versions/A/"+title)
if err := runCmd(cmd); err != nil {
return err
}
cmd = exec.Command("xcrun", "lipo", "-create")

// Copy header file next to output archive.
headerFiles := make([]string, len(fileBases))
if len(fileBases) == 1 {
headerFiles[0] = title + ".h"
err := copyFile(
headers+"/"+title+".h",
srcDir+"/"+bindPrefix+title+".objc.h",
)
if err != nil {
return err
}
} else {
for i, fileBase := range fileBases {
headerFiles[i] = fileBase + ".objc.h"
err := copyFile(
headers+"/"+fileBase+".objc.h",
srcDir+"/"+fileBase+".objc.h")
env := darwinEnv[target + "_" + arch]

if err := writeGoMod("darwin", getenv(env, "GOARCH")); err != nil {
return err
}

// Add the generated packages to GOPATH for reverse bindings.
gopath := fmt.Sprintf("GOPATH=%s%c%s", tmpdir, filepath.ListSeparator, goEnv("GOPATH"))
env = append(env, gopath)
fmt.Printf("[debug] goenv:\n%s\n", env)

path, err := goIOSBindArchive(name, env, filepath.Join(tmpdir, "src"))
if err != nil {
return fmt.Errorf("darwin-%s: %v", arch, err)
}

if index > 0 {
// not the first static lib, attach to a fat library and skip create headers
fatCmd := exec.Command("xcrun", "lipo", "-create",
"-output", buildTemp+"/Versions/A/"+title,
buildTemp+"/Versions/A/"+title, path)

if err := runCmd(fatCmd); err != nil {
return err
}

continue
}

cmd.Args = append(cmd.Args, "-arch", archClang(arch), path)

headers := buildTemp + "/Versions/A/Headers"
if err := mkdir(headers); err != nil {
return err
}
}
err := copyFile(
headers+"/ref.h",
srcDir+"/ref.h")
if err != nil {
return err
}
headerFiles = append(headerFiles, title+".h")
err = writeFile(headers+"/"+title+".h", func(w io.Writer) error {
return iosBindHeaderTmpl.Execute(w, map[string]interface{}{
"pkgs": pkgs, "title": title, "bases": fileBases,
if err := symlink("A", buildTemp+"/Versions/Current"); err != nil {
return err
}
if err := symlink("Versions/Current/Headers", buildTemp+"/Headers"); err != nil {
return err
}
if err := symlink("Versions/Current/"+title, buildTemp+"/"+title); err != nil {
return err
}

cmd.Args = append(cmd.Args, "-o", buildTemp+"/Versions/A/"+title)
if err := runCmd(cmd); err != nil {
return err
}

//Copy header file next to output archive.
headerFiles := make([]string, len(fileBases))
if len(fileBases) == 1 {
headerFiles[0] = title + ".h"
err := copyFile(
headers+"/"+title+".h",
srcDir+"/"+bindPrefix+title+".objc.h",
)
if err != nil {
return err
}
} else {
for i, fileBase := range fileBases {
headerFiles[i] = fileBase + ".objc.h"
err := copyFile(
headers+"/"+fileBase+".objc.h",
srcDir+"/"+fileBase+".objc.h")
if err != nil {
return err
}
}
err := copyFile(
headers+"/ref.h",
srcDir+"/ref.h")
if err != nil {
return err
}
headerFiles = append(headerFiles, title+".h")
err = writeFile(headers+"/"+title+".h", func(w io.Writer) error {
return iosBindHeaderTmpl.Execute(w, map[string]interface{}{
"pkgs": pkgs, "title": title, "bases": fileBases,
})
})
if err != nil {
return err
}
}

resources := buildTemp + "/Versions/A/Resources"
if err := mkdir(resources); err != nil {
return err
}
if err := symlink("Versions/Current/Resources", buildTemp+"/Resources"); err != nil {
return err
}
err = writeFile(buildTemp+"/Resources/Info.plist", func(w io.Writer) error {
_, err := w.Write([]byte(iosBindInfoPlist))
return err
})
})
if err != nil {
return err
if err != nil {
return err
}

var mmVals = struct {
Module string
Headers []string
}{
Module: title,
Headers: headerFiles,
}
err = writeFile(buildTemp+"/Versions/A/Modules/module.modulemap", func(w io.Writer) error {
return iosModuleMapTmpl.Execute(w, mmVals)
})
if err != nil {
return err
}
err = symlink("Versions/Current/Modules", buildTemp+"/Modules")
if err != nil {
return err
}
}
}

resources := buildO + "/Versions/A/Resources"
if err := mkdir(resources); err != nil {
return err
}
if err := symlink("Versions/Current/Resources", buildO+"/Resources"); err != nil {
return err
}
err := writeFile(buildO+"/Resources/Info.plist", func(w io.Writer) error {
_, err := w.Write([]byte(iosBindInfoPlist))
return err
})
if err != nil {
return err
}
// Finally combine ios/simulator/catalyst framework to xcframework
xcframeworkArgs := []string{"-create-xcframework"}

var mmVals = struct {
Module string
Headers []string
}{
Module: title,
Headers: headerFiles,
for _, target := range allTargets("ios") {
xcframeworkArgs = append(xcframeworkArgs, "-framework", tmpdir+"/"+target+"/"+title+".framework")
}
err = writeFile(buildO+"/Versions/A/Modules/module.modulemap", func(w io.Writer) error {
return iosModuleMapTmpl.Execute(w, mmVals)
})
if err != nil {
return err
}
return symlink("Versions/Current/Modules", buildO+"/Modules")

xcframeworkArgs = append(xcframeworkArgs, "-output", buildO)
cmd = exec.Command("xcodebuild", xcframeworkArgs...)
err := runCmd(cmd)
return err
}

const iosBindInfoPlist = `<?xml version="1.0" encoding="UTF-8"?>
Expand Down
50 changes: 26 additions & 24 deletions cmd/gomobile/bind_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ func TestBindIOS(t *testing.T) {
}()
buildN = true
buildX = true
buildO = "Asset.framework"
buildO = "Asset.xcframework"
buildTarget = "ios/arm64"

tests := []struct {
Expand All @@ -126,7 +126,7 @@ func TestBindIOS(t *testing.T) {
prefix: "Foo",
},
{
out: "Abcde.framework",
out: "Abcde.xcframework",
},
}
for _, tc := range tests {
Expand Down Expand Up @@ -160,7 +160,7 @@ func TestBindIOS(t *testing.T) {
BitcodeEnabled bool
}{
outputData: output,
Output: buildO[:len(buildO)-len(".framework")],
Output: buildO[:len(buildO)-len(".xcframework")],
Prefix: tc.prefix,
BitcodeEnabled: bitcodeEnabled,
}
Expand Down Expand Up @@ -194,26 +194,28 @@ jar c -C $WORK/javac-output .
var bindIOSTmpl = template.Must(template.New("output").Parse(`GOMOBILE={{.GOPATH}}/pkg/gomobile
WORK=$WORK
GOOS=darwin CGO_ENABLED=1 gobind -lang=go,objc -outdir=$WORK -tags=ios{{if .Prefix}} -prefix={{.Prefix}}{{end}} golang.org/x/mobile/asset
rm -r -f "{{.Output}}.xcframework"
mkdir -p $WORK/src
PWD=$WORK/src GOOS=darwin GOARCH=arm64 CC=iphoneos-clang CXX=iphoneos-clang++ CGO_CFLAGS=-isysroot=iphoneos -miphoneos-version-min=7.0 {{if .BitcodeEnabled}}-fembed-bitcode {{end}}-arch arm64 CGO_CXXFLAGS=-isysroot=iphoneos -miphoneos-version-min=7.0 {{if .BitcodeEnabled}}-fembed-bitcode {{end}}-arch arm64 CGO_LDFLAGS=-isysroot=iphoneos -miphoneos-version-min=7.0 {{if .BitcodeEnabled}}-fembed-bitcode {{end}}-arch arm64 CGO_ENABLED=1 GOPATH=$WORK:$GOPATH go build -tags ios -x -buildmode=c-archive -o $WORK/{{.Output}}-arm64.a ./gobind
rm -r -f "{{.Output}}.framework"
mkdir -p {{.Output}}.framework/Versions/A/Headers
ln -s A {{.Output}}.framework/Versions/Current
ln -s Versions/Current/Headers {{.Output}}.framework/Headers
ln -s Versions/Current/{{.Output}} {{.Output}}.framework/{{.Output}}
xcrun lipo -create -arch arm64 $WORK/{{.Output}}-arm64.a -o {{.Output}}.framework/Versions/A/{{.Output}}
cp $WORK/src/gobind/{{.Prefix}}Asset.objc.h {{.Output}}.framework/Versions/A/Headers/{{.Prefix}}Asset.objc.h
mkdir -p {{.Output}}.framework/Versions/A/Headers
cp $WORK/src/gobind/Universe.objc.h {{.Output}}.framework/Versions/A/Headers/Universe.objc.h
mkdir -p {{.Output}}.framework/Versions/A/Headers
cp $WORK/src/gobind/ref.h {{.Output}}.framework/Versions/A/Headers/ref.h
mkdir -p {{.Output}}.framework/Versions/A/Headers
mkdir -p {{.Output}}.framework/Versions/A/Headers
mkdir -p {{.Output}}.framework/Versions/A/Resources
ln -s Versions/Current/Resources {{.Output}}.framework/Resources
mkdir -p {{.Output}}.framework/Resources
mkdir -p {{.Output}}.framework/Versions/A/Modules
ln -s Versions/Current/Modules {{.Output}}.framework/Modules
PWD=$WORK/src GOOS=darwin GOARCH=arm64 CC=iphoneos-clang CXX=iphoneos-clang++ CGO_CFLAGS=-isysroot=iphoneos -miphoneos-version-min=7.0 {{if .BitcodeEnabled}}-fembed-bitcode {{end}}-arch arm64 CGO_CXXFLAGS=-isysroot=iphoneos -miphoneos-version-min=7.0 {{if .BitcodeEnabled}}-fembed-bitcode {{end}}-arch arm64 CGO_LDFLAGS=-isysroot=iphoneos -miphoneos-version-min=7.0 {{if .BitcodeEnabled}}-fembed-bitcode {{end}}-arch arm64 CGO_ENABLED=1 ARCH=arm64 GOPATH=$WORK:$GOPATH go build -tags ios -x -buildmode=c-archive -o $WORK/{{.Output}}-arm64.a ./gobind
mkdir -p $WORK/arm64/{{.Output}}.framework/Versions/A/Headers
ln -s A $WORK/arm64/{{.Output}}.framework/Versions/Current
ln -s Versions/Current/Headers $WORK/arm64/{{.Output}}.framework/Headers
ln -s Versions/Current/{{.Output}} $WORK/arm64/{{.Output}}.framework/{{.Output}}
xcrun lipo -create -arch arm64 $WORK/{{.Output}}-arm64.a -o $WORK/arm64/{{.Output}}.framework/Versions/A/{{.Output}}
cp $WORK/src/gobind/{{.Prefix}}Asset.objc.h $WORK/arm64/{{.Output}}.framework/Versions/A/Headers/{{.Prefix}}Asset.objc.h
mkdir -p $WORK/arm64/{{.Output}}.framework/Versions/A/Headers
cp $WORK/src/gobind/Universe.objc.h $WORK/arm64/{{.Output}}.framework/Versions/A/Headers/Universe.objc.h
mkdir -p $WORK/arm64/{{.Output}}.framework/Versions/A/Headers
cp $WORK/src/gobind/ref.h $WORK/arm64/{{.Output}}.framework/Versions/A/Headers/ref.h
mkdir -p $WORK/arm64/{{.Output}}.framework/Versions/A/Headers
mkdir -p $WORK/arm64/{{.Output}}.framework/Versions/A/Headers
mkdir -p $WORK/arm64/{{.Output}}.framework/Versions/A/Resources
ln -s Versions/Current/Resources $WORK/arm64/{{.Output}}.framework/Resources
mkdir -p $WORK/arm64/{{.Output}}.framework/Resources
mkdir -p $WORK/arm64/{{.Output}}.framework/Versions/A/Modules
ln -s Versions/Current/Modules $WORK/arm64/{{.Output}}.framework/Modules
xcrun lipo $WORK/arm64/{{.Output}}.framework/Versions/A/{{.Output}} -thin arm64 -output $WORK/arm64/{{.Output}}.framework/Versions/A/{{.Output}}
xcodebuild -create-xcframework -framework $WORK/arm64/{{.Output}}.framework -framework $WORK/amd64/{{.Output}}.framework -framework $WORK/catalyst/{{.Output}}.framework -output {{.Output}}.xcframework
`))

func TestBindIOSAll(t *testing.T) {
Expand All @@ -230,7 +232,7 @@ func TestBindIOSAll(t *testing.T) {
}()
buildN = true
buildX = true
buildO = "Asset.framework"
buildO = "Asset.xcframework"
buildTarget = "ios"

buf := new(bytes.Buffer)
Expand Down Expand Up @@ -290,7 +292,7 @@ func TestBindWithGoModules(t *testing.T) {
case "android":
out = filepath.Join(dir, "cgopkg.aar")
case "ios":
out = filepath.Join(dir, "Cgopkg.framework")
out = filepath.Join(dir, "Cgopkg.xcframework")
}

tests := []struct {
Expand Down
1 change: 1 addition & 0 deletions cmd/gomobile/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,7 @@ func goCmd(subcmd string, srcs []string, env []string, args ...string) error {

func goCmdAt(at string, subcmd string, srcs []string, env []string, args ...string) error {
cmd := exec.Command("go", subcmd)
//cmd := exec.Command("go1.16rc1", subcmd)
tags := buildTags
targetOS, _, err := parseBuildTarget(buildTarget)
if err != nil {
Expand Down
Loading