From d06c25ead6a4b5cba5214716890ad0933685cc7b Mon Sep 17 00:00:00 2001 From: Daniel Wiese Date: Mon, 18 Jan 2021 16:11:06 -0500 Subject: [PATCH 1/3] nominally working example for catalyst --- cmd/gomobile/bind_iosapp.go | 192 ++++++++++++++++++++---------------- cmd/gomobile/env.go | 27 ++++- 2 files changed, 131 insertions(+), 88 deletions(-) diff --git a/cmd/gomobile/bind_iosapp.go b/cmd/gomobile/bind_iosapp.go index 91d0de3ae..dc9b66121 100644 --- a/cmd/gomobile/bind_iosapp.go +++ b/cmd/gomobile/bind_iosapp.go @@ -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 } @@ -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/Sample.framework", "-framework", tmpdir+"/amd64/Sample.framework", "-framework", tmpdir+"/catalyst/Sample.framework", "-output", buildO) + err := runCmd(cmd) + return err } const iosBindInfoPlist = ` diff --git a/cmd/gomobile/env.go b/cmd/gomobile/env.go index 6dacc630c..1cf981524 100644 --- a/cmd/gomobile/env.go +++ b/cmd/gomobile/env.go @@ -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: @@ -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)) } @@ -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 } @@ -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": @@ -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)) } From ffb4b4a32cfc3ea2fdf08bf6ebfaf4ef4b340549 Mon Sep 17 00:00:00 2001 From: Daniel Wiese Date: Mon, 18 Jan 2021 16:56:38 -0500 Subject: [PATCH 2/3] remove mistaken hardcoded framework filenames --- cmd/gomobile/bind_iosapp.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/gomobile/bind_iosapp.go b/cmd/gomobile/bind_iosapp.go index dc9b66121..eadaa45f5 100644 --- a/cmd/gomobile/bind_iosapp.go +++ b/cmd/gomobile/bind_iosapp.go @@ -184,7 +184,7 @@ func goIOSBind(gobind string, pkgs []*packages.Package, archs []string) error { } } // Combine frameworks into xcframework - cmd = exec.Command("xcodebuild", "-create-xcframework", "-framework", tmpdir+"/arm64/Sample.framework", "-framework", tmpdir+"/amd64/Sample.framework", "-framework", tmpdir+"/catalyst/Sample.framework", "-output", buildO) + 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 } From 6cad7ffbdf1a53f62b5dcafa91ac9cd3be61b225 Mon Sep 17 00:00:00 2001 From: Daniel Wiese Date: Mon, 18 Jan 2021 18:24:44 -0500 Subject: [PATCH 3/3] update test and minor go fmt fix --- cmd/gomobile/bind_iosapp.go | 4 +-- cmd/gomobile/bind_test.go | 50 +++++++++++++++++++------------------ 2 files changed, 28 insertions(+), 26 deletions(-) diff --git a/cmd/gomobile/bind_iosapp.go b/cmd/gomobile/bind_iosapp.go index eadaa45f5..89b325250 100644 --- a/cmd/gomobile/bind_iosapp.go +++ b/cmd/gomobile/bind_iosapp.go @@ -177,14 +177,14 @@ func goIOSBind(gobind string, pkgs []*packages.Package, archs []string) error { } // 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) + cmd = exec.Command("xcrun", "lipo", buildTemp+"/Versions/A/"+title, "-thin", "arm64", "-output", buildTemp+"/Versions/A/"+title) if err := runCmd(cmd); err != nil { return err } } } // 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) + 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 } diff --git a/cmd/gomobile/bind_test.go b/cmd/gomobile/bind_test.go index 4a4c14788..724fb5577 100644 --- a/cmd/gomobile/bind_test.go +++ b/cmd/gomobile/bind_test.go @@ -112,7 +112,7 @@ func TestBindIOS(t *testing.T) { }() buildN = true buildX = true - buildO = "Asset.framework" + buildO = "Asset.xcframework" buildTarget = "ios/arm64" tests := []struct { @@ -126,7 +126,7 @@ func TestBindIOS(t *testing.T) { prefix: "Foo", }, { - out: "Abcde.framework", + out: "Abcde.xcframework", }, } for _, tc := range tests { @@ -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, } @@ -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) { @@ -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) @@ -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 {