Skip to content

Support building for target catalyst #45

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 3 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
192 changes: 106 additions & 86 deletions cmd/gomobile/bind_iosapp.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,28 +40,38 @@ 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")

for _, arch := range archs {

// Keep the .framework for each "architecture" to combine into .xcframework
buildTemp = tmpdir + "/" + arch + "/" + title + ".framework"

fileBases := make([]string, len(pkgs)+1)
for i, pkg := range pkgs {
fileBases[i] = bindPrefix + strings.Title(pkg.Name)
}
fileBases[len(fileBases)-1] = "Universe"

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

if err := writeGoMod("darwin", arch); err != nil {
return err
}
Expand All @@ -75,98 +85,108 @@ func goIOSBind(gobind string, pkgs []*packages.Package, archs []string) error {
return fmt.Errorf("darwin-%s: %v", arch, err)
}
cmd.Args = append(cmd.Args, "-arch", archClang(arch), path)
}

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

cmd.Args = append(cmd.Args, "-o", buildO+"/Versions/A/"+title)
if err := runCmd(cmd); err != nil {
return err
}
headers := buildTemp + "/Versions/A/Headers"
if err := mkdir(headers); err != nil {
return err
}
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
}

// 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 {
cmd.Args = append(cmd.Args, "-o", buildTemp+"/Versions/A/"+title)
if err := runCmd(cmd); err != nil {
return err
}
} else {
for i, fileBase := range fileBases {
headerFiles[i] = fileBase + ".objc.h"

// 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+"/"+fileBase+".objc.h",
srcDir+"/"+fileBase+".objc.h")
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
}
}
err := copyFile(
headers+"/ref.h",
srcDir+"/ref.h")
if err != nil {

resources := buildTemp + "/Versions/A/Resources"
if err := mkdir(resources); 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("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
}
}

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
}

var mmVals = struct {
Module string
Headers []string
}{
Module: title,
Headers: headerFiles,
}
err = writeFile(buildO+"/Versions/A/Modules/module.modulemap", func(w io.Writer) error {
return iosModuleMapTmpl.Execute(w, mmVals)
})
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
}
// Thin the arm64 framework with lipo
if arch == "arm64" {
cmd = exec.Command("xcrun", "lipo", buildTemp+"/Versions/A/"+title, "-thin", "arm64", "-output", buildTemp+"/Versions/A/"+title)
if err := runCmd(cmd); err != nil {
return err
}
}
}
return symlink("Versions/Current/Modules", buildO+"/Modules")
// Combine frameworks into xcframework
cmd = exec.Command("xcodebuild", "-create-xcframework", "-framework", tmpdir+"/arm64/"+title+".framework", "-framework", tmpdir+"/amd64/"+title+".framework", "-framework", tmpdir+"/catalyst/"+title+".framework", "-output", buildO)
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
27 changes: 25 additions & 2 deletions cmd/gomobile/env.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ var (
func allArchs(targetOS string) []string {
switch targetOS {
case "ios":
return []string{"arm64", "amd64"}
return []string{"arm64", "amd64", "catalyst"}
case "android":
return []string{"arm", "arm64", "386", "amd64"}
default:
Expand Down Expand Up @@ -152,6 +152,9 @@ func envInit() (err error) {
case "amd64":
clang, cflags, err = envClang("iphonesimulator")
cflags += " -mios-simulator-version-min=" + buildIOSVersion
case "catalyst":
clang, cflags, err = envClang("macosx")
cflags += " -target x86_64-apple-ios13.0-macabi"
default:
panic(fmt.Errorf("unknown GOARCH: %q", arch))
}
Expand All @@ -164,13 +167,14 @@ func envInit() (err error) {
}
env = append(env,
"GOOS=darwin",
"GOARCH="+arch,
"GOARCH="+archGo(arch),
"CC="+clang,
"CXX="+clang+"++",
"CGO_CFLAGS="+cflags+" -arch "+archClang(arch),
"CGO_CXXFLAGS="+cflags+" -arch "+archClang(arch),
"CGO_LDFLAGS="+cflags+" -arch "+archClang(arch),
"CGO_ENABLED=1",
"ARCH="+arch,
)
darwinEnv[arch] = env
}
Expand Down Expand Up @@ -223,6 +227,23 @@ func envClang(sdkName string) (clang, cflags string, err error) {
return clang, "-isysroot " + sdk, nil
}

func archGo(goarch string) string {
switch goarch {
case "arm":
return "arm"
case "arm64":
return "arm64"
case "386":
return "386"
case "amd64":
return "amd64"
case "catalyst":
return "amd64"
default:
panic(fmt.Sprintf("unknown GOARCH: %q", goarch))
}
}

func archClang(goarch string) string {
switch goarch {
case "arm":
Expand All @@ -233,6 +254,8 @@ func archClang(goarch string) string {
return "i386"
case "amd64":
return "x86_64"
case "catalyst":
return "x86_64"
default:
panic(fmt.Sprintf("unknown GOARCH: %q", goarch))
}
Expand Down