Skip to content

cmd/gomobile: support macOS and Catalyst #65

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 24 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
3235347
nominally working example for catalyst
dpwiese Jan 18, 2021
e581a49
remove mistaken hardcoded framework filenames
dpwiese Jan 18, 2021
cb8e364
update test and minor go fmt fix
dpwiese Jan 18, 2021
eb6b77b
Support Apple Silicon and Catalyst
waylybaye Jan 29, 2021
259d861
remove catalyst in allArchs
waylybaye Feb 3, 2021
b678ba0
cmd/gomobile: macOS support
ydnar Mar 1, 2021
df9f79b
cmd/gomobile: use correct MacCatalyst ABI for arm64
ydnar Jul 8, 2021
9d627d1
cmd/gomobile: remove commented-out go1.16rc1 exec
ydnar Jul 8, 2021
9a25750
cmd/gomobile: address CL feedback; remove commented-out line
ydnar Jul 8, 2021
7db0e88
cmd/gomobile: remove debugging line per CL feedback
ydnar Jul 8, 2021
abb78bd
cmd/gomobile: remove single-use 'targets' variable
ydnar Jul 8, 2021
e891281
cmd/gomobile: rename allTargetArchs to iOSTargetArchs
ydnar Jul 9, 2021
9d00df1
cmd/gomobile: replace allTargets with iOSTargets slice
ydnar Jul 9, 2021
cdc07c0
cmd/gomobile: macosx → macos
ydnar Jul 9, 2021
ab263e4
cmd/gomobile: add comment describing iOSTargets var
ydnar Jul 9, 2021
50f941d
cmd/gomobile: update iOSTargets comment per CL feedback
ydnar Jul 9, 2021
8a0a1e6
cmd/gomobile: remove unnecessary ARCH env var
ydnar Jul 9, 2021
221210c
cmd/gomobile: remove ARCH=arm64 from test
ydnar Jul 9, 2021
45defed
cmd/gomobile: remove comment per CL feedback
ydnar Jul 9, 2021
cb1feed
cmd/gomobile: add space after // (CL feedback)
ydnar Jul 9, 2021
6fd5aa7
cmd/gomobile: reorganize variables; use filepath.Join
ydnar Jul 9, 2021
964653a
cmd/gomobile: remove a few single-use variables
ydnar Jul 9, 2021
b39ae24
cmd/gomobile: remove slice assignment optimization
ydnar Jul 9, 2021
623f8f3
go.sum: go mod tidy; CL feedback
ydnar Jul 9, 2021
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
256 changes: 149 additions & 107 deletions cmd/gomobile/bind_iosapp.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,146 +40,188 @@ func goIOSBind(gobind string, pkgs []*packages.Package, archs []string) error {

var name string
var title 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)
if err := removeAll(buildO); err != nil {
return err
}
fileBases[len(fileBases)-1] = "Universe"

cmd = exec.Command("xcrun", "lipo", "-create")

modulesUsed, err := areGoModulesUsed()
if err != nil {
return err
}

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)
var frameworkDirs []string
for _, target := range iOSTargets {
frameworkDir := filepath.Join(tmpdir, target, title+".framework")
frameworkDirs = append(frameworkDirs, frameworkDir)

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)
for index, arch := range iOSTargetArchs(target) {
fileBases := make([]string, len(pkgs)+1)
for i, pkg := range pkgs {
fileBases[i] = bindPrefix + strings.Title(pkg.Name)
}
fileBases[len(fileBases)-1] = "Universe"

env := darwinEnv[target+"_"+arch]

// Run `go mod tidy` to force to create go.sum.
// Without go.sum, `go build` fails as of Go 1.16.
if modulesUsed {
if err := goModTidyAt(filepath.Join(tmpdir, "src"), env); err != nil {
if err := writeGoMod("darwin", getenv(env, "GOARCH")); err != nil {
return err
}
}

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)
}
// 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)

// 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
}
// Run `go mod tidy` to force to create go.sum.
// Without go.sum, `go build` fails as of Go 1.16.
if modulesUsed {
if err := goModTidyAt(filepath.Join(tmpdir, "src"), env); err != nil {
return err
}
}

cmd.Args = append(cmd.Args, "-o", buildO+"/Versions/A/"+title)
if err := runCmd(cmd); err != nil {
return err
}
path, err := goIOSBindArchive(name, env, filepath.Join(tmpdir, "src"))
if err != nil {
return fmt.Errorf("darwin-%s: %v", arch, 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")
versionsDir := filepath.Join(frameworkDir, "Versions")
versionsADir := filepath.Join(versionsDir, "A")
titlePath := filepath.Join(versionsADir, title)
if index > 0 {
// not the first static lib, attach to a fat library and skip create headers
fatCmd := exec.Command(
"xcrun",
"lipo", "-create", "-output", titlePath, titlePath, path,
)
if err := runCmd(fatCmd); err != nil {
return err
}
continue
}

versionsAHeadersDir := filepath.Join(versionsADir, "Headers")
if err := mkdir(versionsAHeadersDir); err != nil {
return err
}
if err := symlink("A", filepath.Join(versionsDir, "Current")); err != nil {
return err
}
if err := symlink("Versions/Current/Headers", filepath.Join(frameworkDir, "Headers")); err != nil {
return err
}
if err := symlink(filepath.Join("Versions/Current", title), filepath.Join(frameworkDir, title)); err != nil {
return err
}

lipoCmd := exec.Command(
"xcrun",
"lipo", "-create", "-arch", archClang(arch), path, "-o", titlePath,
)
if err := runCmd(lipoCmd); err != nil {
return err
}

// Copy header file next to output archive.
var headerFiles []string
if len(fileBases) == 1 {
headerFiles = append(headerFiles, title+".h")
err := copyFile(
filepath.Join(versionsAHeadersDir, title+".h"),
filepath.Join(srcDir, bindPrefix+title+".objc.h"),
)
if err != nil {
return err
}
} else {
for _, fileBase := range fileBases {
headerFiles = append(headerFiles, fileBase+".objc.h")
err := copyFile(
filepath.Join(versionsAHeadersDir, fileBase+".objc.h"),
filepath.Join(srcDir, fileBase+".objc.h"),
)
if err != nil {
return err
}
}
err := copyFile(
filepath.Join(versionsAHeadersDir, "ref.h"),
filepath.Join(srcDir, "ref.h"),
)
if err != nil {
return err
}
headerFiles = append(headerFiles, title+".h")
err = writeFile(filepath.Join(versionsAHeadersDir, 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
}
}

if err := mkdir(filepath.Join(versionsADir, "Resources")); err != nil {
return err
}
if err := symlink("Versions/Current/Resources", filepath.Join(frameworkDir, "Resources")); err != nil {
return err
}
err = writeFile(filepath.Join(frameworkDir, "Resources", "Info.plist"), func(w io.Writer) error {
_, err := w.Write([]byte(iosBindInfoPlist))
return err
})
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,

var mmVals = struct {
Module string
Headers []string
}{
Module: title,
Headers: headerFiles,
}
err = writeFile(filepath.Join(versionsADir, "Modules", "module.modulemap"), func(w io.Writer) error {
return iosModuleMapTmpl.Execute(w, mmVals)
})
})
if err != nil {
return err
if err != nil {
return err
}
err = symlink(filepath.Join("Versions/Current/Modules"), filepath.Join(frameworkDir, "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
}
if err := writeFile(buildO+"/Resources/Info.plist", func(w io.Writer) error {
_, err := w.Write([]byte(iosBindInfoPlist))
return err
}); err != nil {
return err
}
// Finally combine all frameworks to an XCFramework
xcframeworkArgs := []string{"-create-xcframework"}

var mmVals = struct {
Module string
Headers []string
}{
Module: title,
Headers: headerFiles,
for _, dir := range frameworkDirs {
xcframeworkArgs = append(xcframeworkArgs, "-framework", dir)
}
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
48 changes: 25 additions & 23 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 @@ -195,26 +195,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
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 @@ -231,7 +233,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 @@ -291,7 +293,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
Loading