cmd/gomobile: improve support for macOS and Catalyst
This is is a follow-up from my previous PR (#65). It makes gomobile aware of GOOS=ios and adds support for specifying specific Apple platforms, instead of overloading the "ios" platform. Supported platforms: ios, iossimulator, macos, and maccatalyst These can now be specified the -target argument to gomobile, e.g.: gomobile build -target=ios,iossimulator,macos,maccatalyst It preserves the current behavior of -target=ios, which will build for ios and iossimulator on supported architectures (arm64 and amd64). It adds platform-specific build tags so Go code can discriminate between different Apple platforms like maccatalyst (UIKit on macOS). This PR also fixes a number of broken tests. TODO: cgo has a bug where c-archive builds targeting Catalyst will fail unless -tags=ios is supplied. See https://golang.org/issues/47228 Fixes https://golang.org/issues/47212 Updates https://golang.org/issues/47228 Change-Id: Ib1a2f5302c5edd0704c13ffbe8f4061211f50d4e GitHub-Last-Rev: 01ab28e63fe6890a9f9783e3fc41b1c895b0274d GitHub-Pull-Request: golang/mobile#70 Reviewed-on: https://go-review.googlesource.com/c/mobile/+/334689 Reviewed-by: Hyang-Ah Hana Kim <hyangah@gmail.com> Trust: Hyang-Ah Hana Kim <hyangah@gmail.com> Trust: Hajime Hoshi <hajimehoshi@gmail.com>
This commit is contained in:
parent
5d9a33257a
commit
6d8ad35e46
|
@ -3,6 +3,7 @@ last-change
|
|||
*.apk
|
||||
*.app
|
||||
*.framework
|
||||
*.xcframework
|
||||
*.aar
|
||||
*.iml
|
||||
.idea
|
||||
|
|
|
@ -23,14 +23,14 @@ import (
|
|||
var cmdBind = &command{
|
||||
run: runBind,
|
||||
Name: "bind",
|
||||
Usage: "[-target android|ios] [-bootclasspath <path>] [-classpath <path>] [-o output] [build flags] [package]",
|
||||
Usage: "[-target android|" + strings.Join(applePlatforms, "|") + "] [-bootclasspath <path>] [-classpath <path>] [-o output] [build flags] [package]",
|
||||
Short: "build a library for Android and iOS",
|
||||
Long: `
|
||||
Bind generates language bindings for the package named by the import
|
||||
path, and compiles a library for the named target system.
|
||||
|
||||
The -target flag takes a target system name, either android (the
|
||||
default) or ios.
|
||||
The -target flag takes either android (the default), or one or more
|
||||
comma-delimited Apple platforms (` + strings.Join(applePlatforms, ", ") + `).
|
||||
|
||||
For -target android, the bind command produces an AAR (Android ARchive)
|
||||
file that archives the precompiled Java API stub classes, the compiled
|
||||
|
@ -52,9 +52,9 @@ instruction sets (arm, arm64, 386, amd64). A subset of instruction sets
|
|||
can be selected by specifying target type with the architecture name. E.g.,
|
||||
-target=android/arm,android/386.
|
||||
|
||||
For -target ios, gomobile must be run on an OS X machine with Xcode
|
||||
installed. The generated Objective-C types can be prefixed with the -prefix
|
||||
flag.
|
||||
For Apple -target platforms, gomobile must be run on an OS X machine with
|
||||
Xcode installed. The generated Objective-C types can be prefixed with the
|
||||
-prefix flag.
|
||||
|
||||
For -target android, the -bootclasspath and -classpath flags are used to
|
||||
control the bootstrap classpath and the classpath for Go wrappers to Java
|
||||
|
@ -76,29 +76,29 @@ func runBind(cmd *command) error {
|
|||
|
||||
args := cmd.flag.Args()
|
||||
|
||||
targetOS, targetArchs, err := parseBuildTarget(buildTarget)
|
||||
targets, err := parseBuildTarget(buildTarget)
|
||||
if err != nil {
|
||||
return fmt.Errorf(`invalid -target=%q: %v`, buildTarget, err)
|
||||
}
|
||||
|
||||
if bindJavaPkg != "" && targetOS != "android" {
|
||||
return fmt.Errorf("-javapkg is supported only for android target")
|
||||
}
|
||||
if bindPrefix != "" && targetOS != "ios" {
|
||||
return fmt.Errorf("-prefix is supported only for ios target")
|
||||
}
|
||||
|
||||
if targetOS == "android" {
|
||||
if isAndroidPlatform(targets[0].platform) {
|
||||
if bindPrefix != "" {
|
||||
return fmt.Errorf("-prefix is supported only for Apple targets")
|
||||
}
|
||||
if _, err := ndkRoot(); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if bindJavaPkg != "" {
|
||||
return fmt.Errorf("-javapkg is supported only for android target")
|
||||
}
|
||||
}
|
||||
|
||||
var gobind string
|
||||
if !buildN {
|
||||
gobind, err = exec.LookPath("gobind")
|
||||
if err != nil {
|
||||
return errors.New("gobind was not found. Please run gomobile init before trying again.")
|
||||
return errors.New("gobind was not found. Please run gomobile init before trying again")
|
||||
}
|
||||
} else {
|
||||
gobind = "gobind"
|
||||
|
@ -107,7 +107,10 @@ func runBind(cmd *command) error {
|
|||
if len(args) == 0 {
|
||||
args = append(args, ".")
|
||||
}
|
||||
pkgs, err := importPackages(args, targetOS)
|
||||
|
||||
// TODO(ydnar): this should work, unless build tags affect loading a single package.
|
||||
// Should we try to import packages with different build tags per platform?
|
||||
pkgs, err := packages.Load(packagesConfig(targets[0]), args...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -115,28 +118,23 @@ func runBind(cmd *command) error {
|
|||
// check if any of the package is main
|
||||
for _, pkg := range pkgs {
|
||||
if pkg.Name == "main" {
|
||||
return fmt.Errorf("binding 'main' package (%s) is not supported", pkg.PkgPath)
|
||||
return fmt.Errorf(`binding "main" package (%s) is not supported`, pkg.PkgPath)
|
||||
}
|
||||
}
|
||||
|
||||
switch targetOS {
|
||||
case "android":
|
||||
return goAndroidBind(gobind, pkgs, targetArchs)
|
||||
case "ios":
|
||||
switch {
|
||||
case isAndroidPlatform(targets[0].platform):
|
||||
return goAndroidBind(gobind, pkgs, targets)
|
||||
case isApplePlatform(targets[0].platform):
|
||||
if !xcodeAvailable() {
|
||||
return fmt.Errorf("-target=ios requires XCode")
|
||||
return fmt.Errorf("-target=%q requires Xcode", buildTarget)
|
||||
}
|
||||
return goIOSBind(gobind, pkgs, targetArchs)
|
||||
return goAppleBind(gobind, pkgs, targets)
|
||||
default:
|
||||
return fmt.Errorf(`invalid -target=%q`, buildTarget)
|
||||
}
|
||||
}
|
||||
|
||||
func importPackages(args []string, targetOS string) ([]*packages.Package, error) {
|
||||
config := packagesConfig(targetOS)
|
||||
return packages.Load(config, args...)
|
||||
}
|
||||
|
||||
var (
|
||||
bindPrefix string // -prefix
|
||||
bindJavaPkg string // -javapkg
|
||||
|
@ -212,11 +210,12 @@ func writeFile(filename string, generate func(io.Writer) error) error {
|
|||
return generate(f)
|
||||
}
|
||||
|
||||
func packagesConfig(targetOS string) *packages.Config {
|
||||
func packagesConfig(t targetInfo) *packages.Config {
|
||||
config := &packages.Config{}
|
||||
// Add CGO_ENABLED=1 explicitly since Cgo is disabled when GOOS is different from host OS.
|
||||
config.Env = append(os.Environ(), "GOARCH=arm64", "GOOS="+targetOS, "CGO_ENABLED=1")
|
||||
tags := buildTags
|
||||
config.Env = append(os.Environ(), "GOARCH="+t.arch, "GOOS="+platformOS(t.platform), "CGO_ENABLED=1")
|
||||
tags := append(buildTags[:], platformTags(t.platform)...)
|
||||
|
||||
if len(tags) > 0 {
|
||||
config.BuildFlags = []string{"-tags=" + strings.Join(tags, ",")}
|
||||
}
|
||||
|
@ -224,11 +223,12 @@ func packagesConfig(targetOS string) *packages.Config {
|
|||
}
|
||||
|
||||
// getModuleVersions returns a module information at the directory src.
|
||||
func getModuleVersions(targetOS string, targetArch string, src string) (*modfile.File, error) {
|
||||
func getModuleVersions(targetPlatform string, targetArch string, src string) (*modfile.File, error) {
|
||||
cmd := exec.Command("go", "list")
|
||||
cmd.Env = append(os.Environ(), "GOOS="+targetOS, "GOARCH="+targetArch)
|
||||
cmd.Env = append(os.Environ(), "GOOS="+platformOS(targetPlatform), "GOARCH="+targetArch)
|
||||
|
||||
tags := append(buildTags[:], platformTags(targetPlatform)...)
|
||||
|
||||
tags := buildTags
|
||||
// TODO(hyangah): probably we don't need to add all the dependencies.
|
||||
cmd.Args = append(cmd.Args, "-m", "-json", "-tags="+strings.Join(tags, ","), "all")
|
||||
cmd.Dir = src
|
||||
|
@ -281,7 +281,7 @@ func getModuleVersions(targetOS string, targetArch string, src string) (*modfile
|
|||
}
|
||||
|
||||
// writeGoMod writes go.mod file at $WORK/src when Go modules are used.
|
||||
func writeGoMod(targetOS string, targetArch string) error {
|
||||
func writeGoMod(dir, targetPlatform, targetArch string) error {
|
||||
m, err := areGoModulesUsed()
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -291,8 +291,8 @@ func writeGoMod(targetOS string, targetArch string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
return writeFile(filepath.Join(tmpdir, "src", "go.mod"), func(w io.Writer) error {
|
||||
f, err := getModuleVersions(targetOS, targetArch, ".")
|
||||
return writeFile(filepath.Join(dir, "src", "go.mod"), func(w io.Writer) error {
|
||||
f, err := getModuleVersions(targetPlatform, targetArch, ".")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ import (
|
|||
"golang.org/x/tools/go/packages"
|
||||
)
|
||||
|
||||
func goAndroidBind(gobind string, pkgs []*packages.Package, androidArchs []string) error {
|
||||
func goAndroidBind(gobind string, pkgs []*packages.Package, targets []targetInfo) error {
|
||||
if sdkDir := os.Getenv("ANDROID_HOME"); sdkDir == "" {
|
||||
return fmt.Errorf("this command requires ANDROID_HOME environment variable (path to the Android SDK)")
|
||||
}
|
||||
|
@ -58,12 +58,12 @@ func goAndroidBind(gobind string, pkgs []*packages.Package, androidArchs []strin
|
|||
}
|
||||
|
||||
// Generate binding code and java source code only when processing the first package.
|
||||
for _, arch := range androidArchs {
|
||||
if err := writeGoMod("android", arch); err != nil {
|
||||
for _, t := range targets {
|
||||
if err := writeGoMod(tmpdir, "android", t.arch); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
env := androidEnv[arch]
|
||||
env := androidEnv[t.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)
|
||||
|
@ -76,7 +76,7 @@ func goAndroidBind(gobind string, pkgs []*packages.Package, androidArchs []strin
|
|||
}
|
||||
}
|
||||
|
||||
toolchain := ndk.Toolchain(arch)
|
||||
toolchain := ndk.Toolchain(t.arch)
|
||||
err := goBuildAt(
|
||||
filepath.Join(tmpdir, "src"),
|
||||
"./gobind",
|
||||
|
@ -90,7 +90,7 @@ func goAndroidBind(gobind string, pkgs []*packages.Package, androidArchs []strin
|
|||
}
|
||||
|
||||
jsrc := filepath.Join(tmpdir, "java")
|
||||
if err := buildAAR(jsrc, androidDir, pkgs, androidArchs); err != nil {
|
||||
if err := buildAAR(jsrc, androidDir, pkgs, targets); err != nil {
|
||||
return err
|
||||
}
|
||||
return buildSrcJar(jsrc)
|
||||
|
@ -133,7 +133,7 @@ func buildSrcJar(src string) error {
|
|||
// aidl (optional, not relevant)
|
||||
//
|
||||
// javac and jar commands are needed to build classes.jar.
|
||||
func buildAAR(srcDir, androidDir string, pkgs []*packages.Package, androidArchs []string) (err error) {
|
||||
func buildAAR(srcDir, androidDir string, pkgs []*packages.Package, targets []targetInfo) (err error) {
|
||||
var out io.Writer = ioutil.Discard
|
||||
if buildO == "" {
|
||||
buildO = pkgs[0].Name + ".aar"
|
||||
|
@ -235,8 +235,8 @@ func buildAAR(srcDir, androidDir string, pkgs []*packages.Package, androidArchs
|
|||
}
|
||||
}
|
||||
|
||||
for _, arch := range androidArchs {
|
||||
toolchain := ndk.Toolchain(arch)
|
||||
for _, t := range targets {
|
||||
toolchain := ndk.Toolchain(t.arch)
|
||||
lib := toolchain.abi + "/libgojni.so"
|
||||
w, err = aarwcreate("jni/" + lib)
|
||||
if err != nil {
|
||||
|
|
|
@ -5,184 +5,236 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"golang.org/x/tools/go/packages"
|
||||
)
|
||||
|
||||
func goIOSBind(gobind string, pkgs []*packages.Package, archs []string) error {
|
||||
// Run gobind to generate the bindings
|
||||
cmd := exec.Command(
|
||||
gobind,
|
||||
"-lang=go,objc",
|
||||
"-outdir="+tmpdir,
|
||||
)
|
||||
cmd.Env = append(cmd.Env, "GOOS=darwin")
|
||||
cmd.Env = append(cmd.Env, "CGO_ENABLED=1")
|
||||
tags := append(buildTags, "ios")
|
||||
cmd.Args = append(cmd.Args, "-tags="+strings.Join(tags, ","))
|
||||
if bindPrefix != "" {
|
||||
cmd.Args = append(cmd.Args, "-prefix="+bindPrefix)
|
||||
}
|
||||
for _, p := range pkgs {
|
||||
cmd.Args = append(cmd.Args, p.PkgPath)
|
||||
}
|
||||
if err := runCmd(cmd); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
srcDir := filepath.Join(tmpdir, "src", "gobind")
|
||||
|
||||
func goAppleBind(gobind string, pkgs []*packages.Package, targets []targetInfo) 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("ios", arch); err != nil {
|
||||
var frameworkDirs []string
|
||||
frameworkArchCount := map[string]int{}
|
||||
for _, t := range targets {
|
||||
// Catalyst support requires iOS 13+
|
||||
v, _ := strconv.ParseFloat(buildIOSVersion, 64)
|
||||
if t.platform == "maccatalyst" && v < 13.0 {
|
||||
return errors.New("catalyst requires -iosversion=13 or higher")
|
||||
}
|
||||
|
||||
outDir := filepath.Join(tmpdir, t.platform)
|
||||
outSrcDir := filepath.Join(outDir, "src")
|
||||
gobindDir := filepath.Join(outSrcDir, "gobind")
|
||||
|
||||
// Run gobind once per platform to generate the bindings
|
||||
cmd := exec.Command(
|
||||
gobind,
|
||||
"-lang=go,objc",
|
||||
"-outdir="+outDir,
|
||||
)
|
||||
cmd.Env = append(cmd.Env, "GOOS="+platformOS(t.platform))
|
||||
cmd.Env = append(cmd.Env, "CGO_ENABLED=1")
|
||||
tags := append(buildTags[:], platformTags(t.platform)...)
|
||||
cmd.Args = append(cmd.Args, "-tags="+strings.Join(tags, ","))
|
||||
if bindPrefix != "" {
|
||||
cmd.Args = append(cmd.Args, "-prefix="+bindPrefix)
|
||||
}
|
||||
for _, p := range pkgs {
|
||||
cmd.Args = append(cmd.Args, p.PkgPath)
|
||||
}
|
||||
if err := runCmd(cmd); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
env := iosEnv[arch]
|
||||
env := appleEnv[t.String()][:]
|
||||
sdk := getenv(env, "DARWIN_SDK")
|
||||
|
||||
frameworkDir := filepath.Join(tmpdir, t.platform, sdk, title+".framework")
|
||||
frameworkDirs = append(frameworkDirs, frameworkDir)
|
||||
frameworkArchCount[frameworkDir] = frameworkArchCount[frameworkDir] + 1
|
||||
|
||||
fileBases := make([]string, len(pkgs)+1)
|
||||
for i, pkg := range pkgs {
|
||||
fileBases[i] = bindPrefix + strings.Title(pkg.Name)
|
||||
}
|
||||
fileBases[len(fileBases)-1] = "Universe"
|
||||
|
||||
// Add the generated packages to GOPATH for reverse bindings.
|
||||
gopath := fmt.Sprintf("GOPATH=%s%c%s", tmpdir, filepath.ListSeparator, goEnv("GOPATH"))
|
||||
gopath := fmt.Sprintf("GOPATH=%s%c%s", outDir, filepath.ListSeparator, goEnv("GOPATH"))
|
||||
env = append(env, gopath)
|
||||
|
||||
if err := writeGoMod(outDir, t.platform, t.arch); 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 {
|
||||
if err := goModTidyAt(outSrcDir, env); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
path, err := goIOSBindArchive(name, env, filepath.Join(tmpdir, "src"))
|
||||
path, err := goAppleBindArchive(name+"-"+t.platform+"-"+t.arch, env, outSrcDir)
|
||||
if err != nil {
|
||||
return fmt.Errorf("ios-%s: %v", arch, err)
|
||||
return fmt.Errorf("%s/%s: %v", t.platform, t.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
|
||||
}
|
||||
versionsDir := filepath.Join(frameworkDir, "Versions")
|
||||
versionsADir := filepath.Join(versionsDir, "A")
|
||||
titlePath := filepath.Join(versionsADir, title)
|
||||
if frameworkArchCount[frameworkDir] > 1 {
|
||||
// Not the first static lib, attach to a fat library and skip create headers
|
||||
fatCmd := exec.Command(
|
||||
"xcrun",
|
||||
"lipo", path, titlePath, "-create", "-output", titlePath,
|
||||
)
|
||||
if err := runCmd(fatCmd); err != nil {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
cmd.Args = append(cmd.Args, "-o", buildO+"/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 {
|
||||
versionsAHeadersDir := filepath.Join(versionsADir, "Headers")
|
||||
if err := mkdir(versionsAHeadersDir); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
for i, fileBase := range fileBases {
|
||||
headerFiles[i] = fileBase + ".objc.h"
|
||||
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", path, "-create", "-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(
|
||||
headers+"/"+fileBase+".objc.h",
|
||||
srcDir+"/"+fileBase+".objc.h")
|
||||
filepath.Join(versionsAHeadersDir, title+".h"),
|
||||
filepath.Join(gobindDir, 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(gobindDir, fileBase+".objc.h"),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
err := copyFile(
|
||||
filepath.Join(versionsAHeadersDir, "ref.h"),
|
||||
filepath.Join(gobindDir, "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 appleBindHeaderTmpl.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 {
|
||||
|
||||
if err := mkdir(filepath.Join(versionsADir, "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", 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(appleBindInfoPlist))
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
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 appleModuleMapTmpl.Execute(w, mmVals)
|
||||
})
|
||||
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"}
|
||||
|
||||
for _, dir := range frameworkDirs {
|
||||
xcframeworkArgs = append(xcframeworkArgs, "-framework", dir)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
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"?>
|
||||
const appleBindInfoPlist = `<?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>
|
||||
|
@ -190,16 +242,15 @@ const iosBindInfoPlist = `<?xml version="1.0" encoding="UTF-8"?>
|
|||
</plist>
|
||||
`
|
||||
|
||||
var iosModuleMapTmpl = template.Must(template.New("iosmmap").Parse(`framework module "{{.Module}}" {
|
||||
var appleModuleMapTmpl = template.Must(template.New("iosmmap").Parse(`framework module "{{.Module}}" {
|
||||
header "ref.h"
|
||||
{{range .Headers}} header "{{.}}"
|
||||
{{end}}
|
||||
export *
|
||||
}`))
|
||||
|
||||
func goIOSBindArchive(name string, env []string, gosrc string) (string, error) {
|
||||
arch := getenv(env, "GOARCH")
|
||||
archive := filepath.Join(tmpdir, name+"-"+arch+".a")
|
||||
func goAppleBindArchive(name string, env []string, gosrc string) (string, error) {
|
||||
archive := filepath.Join(tmpdir, name+".a")
|
||||
err := goBuildAt(gosrc, "./gobind", env, "-buildmode=c-archive", "-o", archive)
|
||||
if err != nil {
|
||||
return "", err
|
||||
|
@ -207,7 +258,7 @@ func goIOSBindArchive(name string, env []string, gosrc string) (string, error) {
|
|||
return archive, nil
|
||||
}
|
||||
|
||||
var iosBindHeaderTmpl = template.Must(template.New("ios.h").Parse(`
|
||||
var appleBindHeaderTmpl = template.Must(template.New("apple.h").Parse(`
|
||||
// Objective-C API for talking to the following Go packages
|
||||
//
|
||||
{{range .pkgs}}// {{.PkgPath}}
|
||||
|
|
|
@ -98,7 +98,7 @@ func TestBindAndroid(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestBindIOS(t *testing.T) {
|
||||
func TestBindApple(t *testing.T) {
|
||||
if !xcodeAvailable() {
|
||||
t.Skip("Xcode is missing")
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -159,12 +159,12 @@ func TestBindIOS(t *testing.T) {
|
|||
Prefix string
|
||||
}{
|
||||
outputData: output,
|
||||
Output: buildO[:len(buildO)-len(".framework")],
|
||||
Output: buildO[:len(buildO)-len(".xcframework")],
|
||||
Prefix: tc.prefix,
|
||||
}
|
||||
|
||||
wantBuf := new(bytes.Buffer)
|
||||
if err := bindIOSTmpl.Execute(wantBuf, data); err != nil {
|
||||
if err := bindAppleTmpl.Execute(wantBuf, data); err != nil {
|
||||
t.Errorf("%+v: computing diff failed: %v", tc, err)
|
||||
continue
|
||||
}
|
||||
|
@ -190,33 +190,34 @@ PWD=$WORK/java javac -d $WORK/javac-output -source 1.7 -target 1.7 -bootclasspat
|
|||
jar c -C $WORK/javac-output .
|
||||
`))
|
||||
|
||||
var bindIOSTmpl = template.Must(template.New("output").Parse(`GOMOBILE={{.GOPATH}}/pkg/gomobile
|
||||
var bindAppleTmpl = 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
|
||||
mkdir -p $WORK/src
|
||||
PWD=$WORK/src GOOS=ios GOARCH=arm64 CC=iphoneos-clang CXX=iphoneos-clang++ CGO_CFLAGS=-isysroot=iphoneos -miphoneos-version-min=7.0 -fembed-bitcode -arch arm64 CGO_CXXFLAGS=-isysroot=iphoneos -miphoneos-version-min=7.0 -fembed-bitcode -arch arm64 CGO_LDFLAGS=-isysroot=iphoneos -miphoneos-version-min=7.0 -fembed-bitcode -arch arm64 CGO_ENABLED=1 GOPATH=$WORK:$GOPATH go mod tidy
|
||||
PWD=$WORK/src GOOS=ios GOARCH=arm64 CC=iphoneos-clang CXX=iphoneos-clang++ CGO_CFLAGS=-isysroot=iphoneos -miphoneos-version-min=7.0 -fembed-bitcode -arch arm64 CGO_CXXFLAGS=-isysroot=iphoneos -miphoneos-version-min=7.0 -fembed-bitcode -arch arm64 CGO_LDFLAGS=-isysroot=iphoneos -miphoneos-version-min=7.0 -fembed-bitcode -arch arm64 CGO_ENABLED=1 GOPATH=$WORK:$GOPATH go build -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
|
||||
rm -r -f "{{.Output}}.xcframework"
|
||||
GOOS=ios CGO_ENABLED=1 gobind -lang=go,objc -outdir=$WORK/ios -tags=ios{{if .Prefix}} -prefix={{.Prefix}}{{end}} golang.org/x/mobile/asset
|
||||
mkdir -p $WORK/ios/src
|
||||
PWD=$WORK/ios/src GOOS=ios GOARCH=arm64 GOFLAGS=-tags=ios CC=iphoneos-clang CXX=iphoneos-clang++ CGO_CFLAGS=-isysroot iphoneos -miphoneos-version-min=13.0 -fembed-bitcode -arch arm64 CGO_CXXFLAGS=-isysroot iphoneos -miphoneos-version-min=13.0 -fembed-bitcode -arch arm64 CGO_LDFLAGS=-isysroot iphoneos -miphoneos-version-min=13.0 -fembed-bitcode -arch arm64 CGO_ENABLED=1 DARWIN_SDK=iphoneos GOPATH=$WORK/ios:$GOPATH go mod tidy
|
||||
PWD=$WORK/ios/src GOOS=ios GOARCH=arm64 GOFLAGS=-tags=ios CC=iphoneos-clang CXX=iphoneos-clang++ CGO_CFLAGS=-isysroot iphoneos -miphoneos-version-min=13.0 -fembed-bitcode -arch arm64 CGO_CXXFLAGS=-isysroot iphoneos -miphoneos-version-min=13.0 -fembed-bitcode -arch arm64 CGO_LDFLAGS=-isysroot iphoneos -miphoneos-version-min=13.0 -fembed-bitcode -arch arm64 CGO_ENABLED=1 DARWIN_SDK=iphoneos GOPATH=$WORK/ios:$GOPATH go build -x -buildmode=c-archive -o $WORK/{{.Output}}-ios-arm64.a ./gobind
|
||||
mkdir -p $WORK/ios/iphoneos/{{.Output}}.framework/Versions/A/Headers
|
||||
ln -s A $WORK/ios/iphoneos/{{.Output}}.framework/Versions/Current
|
||||
ln -s Versions/Current/Headers $WORK/ios/iphoneos/{{.Output}}.framework/Headers
|
||||
ln -s Versions/Current/{{.Output}} $WORK/ios/iphoneos/{{.Output}}.framework/{{.Output}}
|
||||
xcrun lipo $WORK/{{.Output}}-ios-arm64.a -create -o $WORK/ios/iphoneos/{{.Output}}.framework/Versions/A/{{.Output}}
|
||||
cp $WORK/ios/src/gobind/{{.Prefix}}Asset.objc.h $WORK/ios/iphoneos/{{.Output}}.framework/Versions/A/Headers/{{.Prefix}}Asset.objc.h
|
||||
mkdir -p $WORK/ios/iphoneos/{{.Output}}.framework/Versions/A/Headers
|
||||
cp $WORK/ios/src/gobind/Universe.objc.h $WORK/ios/iphoneos/{{.Output}}.framework/Versions/A/Headers/Universe.objc.h
|
||||
mkdir -p $WORK/ios/iphoneos/{{.Output}}.framework/Versions/A/Headers
|
||||
cp $WORK/ios/src/gobind/ref.h $WORK/ios/iphoneos/{{.Output}}.framework/Versions/A/Headers/ref.h
|
||||
mkdir -p $WORK/ios/iphoneos/{{.Output}}.framework/Versions/A/Headers
|
||||
mkdir -p $WORK/ios/iphoneos/{{.Output}}.framework/Versions/A/Headers
|
||||
mkdir -p $WORK/ios/iphoneos/{{.Output}}.framework/Versions/A/Resources
|
||||
ln -s Versions/Current/Resources $WORK/ios/iphoneos/{{.Output}}.framework/Resources
|
||||
mkdir -p $WORK/ios/iphoneos/{{.Output}}.framework/Resources
|
||||
mkdir -p $WORK/ios/iphoneos/{{.Output}}.framework/Versions/A/Modules
|
||||
ln -s Versions/Current/Modules $WORK/ios/iphoneos/{{.Output}}.framework/Modules
|
||||
xcodebuild -create-xcframework -framework $WORK/ios/iphoneos/{{.Output}}.framework -output {{.Output}}.xcframework
|
||||
`))
|
||||
|
||||
func TestBindIOSAll(t *testing.T) {
|
||||
func TestBindAppleAll(t *testing.T) {
|
||||
if !xcodeAvailable() {
|
||||
t.Skip("Xcode is missing")
|
||||
}
|
||||
|
@ -230,7 +231,7 @@ func TestBindIOSAll(t *testing.T) {
|
|||
}()
|
||||
buildN = true
|
||||
buildX = true
|
||||
buildO = "Asset.framework"
|
||||
buildO = "Asset.xcframework"
|
||||
buildTarget = "ios"
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
|
@ -290,7 +291,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 {
|
||||
|
|
|
@ -8,11 +8,13 @@ package main
|
|||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/tools/go/packages"
|
||||
|
@ -23,15 +25,15 @@ var tmpdir string
|
|||
var cmdBuild = &command{
|
||||
run: runBuild,
|
||||
Name: "build",
|
||||
Usage: "[-target android|ios] [-o output] [-bundleid bundleID] [build flags] [package]",
|
||||
Usage: "[-target android|" + strings.Join(applePlatforms, "|") + "] [-o output] [-bundleid bundleID] [build flags] [package]",
|
||||
Short: "compile android APK and iOS app",
|
||||
Long: `
|
||||
Build compiles and encodes the app named by the import path.
|
||||
|
||||
The named package must define a main function.
|
||||
|
||||
The -target flag takes a target system name, either android (the
|
||||
default) or ios.
|
||||
The -target flag takes either android (the default), or one or more
|
||||
comma-delimited Apple platforms (` + strings.Join(applePlatforms, ", ") + `).
|
||||
|
||||
For -target android, if an AndroidManifest.xml is defined in the
|
||||
package directory, it is added to the APK output. Otherwise, a default
|
||||
|
@ -40,14 +42,22 @@ instruction sets (arm, 386, amd64, arm64). A subset of instruction sets can
|
|||
be selected by specifying target type with the architecture name. E.g.
|
||||
-target=android/arm,android/386.
|
||||
|
||||
For -target ios, gomobile must be run on an OS X machine with Xcode
|
||||
installed.
|
||||
For Apple -target platforms, gomobile must be run on an OS X machine with
|
||||
Xcode installed.
|
||||
|
||||
By default, -target ios will generate an XCFramework for both ios
|
||||
and iossimulator. Multiple Apple targets can be specified, creating a "fat"
|
||||
XCFramework with each slice. To generate a fat XCFramework that supports
|
||||
iOS, macOS, and macCatalyst for all supportec architectures (amd64 and arm64),
|
||||
specify -target ios,macos,maccatalyst. A subset of instruction sets can be
|
||||
selectged by specifying the platform with an architecture name. E.g.
|
||||
-target=ios/arm64,maccatalyst/arm64.
|
||||
|
||||
If the package directory contains an assets subdirectory, its contents
|
||||
are copied into the output.
|
||||
|
||||
Flag -iosversion sets the minimal version of the iOS SDK to compile against.
|
||||
The default version is 7.0.
|
||||
The default version is 13.0.
|
||||
|
||||
Flag -androidapi sets the Android API version to compile against.
|
||||
The default and minimum is 15.
|
||||
|
@ -81,7 +91,7 @@ func runBuildImpl(cmd *command) (*packages.Package, error) {
|
|||
|
||||
args := cmd.flag.Args()
|
||||
|
||||
targetOS, targetArchs, err := parseBuildTarget(buildTarget)
|
||||
targets, err := parseBuildTarget(buildTarget)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(`invalid -target=%q: %v`, buildTarget, err)
|
||||
}
|
||||
|
@ -96,10 +106,14 @@ func runBuildImpl(cmd *command) (*packages.Package, error) {
|
|||
cmd.usage()
|
||||
os.Exit(1)
|
||||
}
|
||||
pkgs, err := packages.Load(packagesConfig(targetOS), buildPath)
|
||||
|
||||
// TODO(ydnar): this should work, unless build tags affect loading a single package.
|
||||
// Should we try to import packages with different build tags per platform?
|
||||
pkgs, err := packages.Load(packagesConfig(targets[0]), buildPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// len(pkgs) can be more than 1 e.g., when the specified path includes `...`.
|
||||
if len(pkgs) != 1 {
|
||||
cmd.usage()
|
||||
|
@ -113,27 +127,32 @@ func runBuildImpl(cmd *command) (*packages.Package, error) {
|
|||
}
|
||||
|
||||
var nmpkgs map[string]bool
|
||||
switch targetOS {
|
||||
case "android":
|
||||
switch {
|
||||
case isAndroidPlatform(targets[0].platform):
|
||||
if pkg.Name != "main" {
|
||||
for _, arch := range targetArchs {
|
||||
if err := goBuild(pkg.PkgPath, androidEnv[arch]); err != nil {
|
||||
for _, t := range targets {
|
||||
if err := goBuild(pkg.PkgPath, androidEnv[t.arch]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return pkg, nil
|
||||
}
|
||||
nmpkgs, err = goAndroidBuild(pkg, targetArchs)
|
||||
nmpkgs, err = goAndroidBuild(pkg, targets)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case "ios":
|
||||
case isApplePlatform(targets[0].platform):
|
||||
if !xcodeAvailable() {
|
||||
return nil, fmt.Errorf("-target=ios requires XCode")
|
||||
return nil, fmt.Errorf("-target=%s requires XCode", buildTarget)
|
||||
}
|
||||
if pkg.Name != "main" {
|
||||
for _, arch := range targetArchs {
|
||||
if err := goBuild(pkg.PkgPath, iosEnv[arch]); err != nil {
|
||||
for _, t := range targets {
|
||||
// Catalyst support requires iOS 13+
|
||||
v, _ := strconv.ParseFloat(buildIOSVersion, 64)
|
||||
if t.platform == "maccatalyst" && v < 13.0 {
|
||||
return nil, errors.New("catalyst requires -iosversion=13 or higher")
|
||||
}
|
||||
if err := goBuild(pkg.PkgPath, appleEnv[t.String()]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
@ -142,7 +161,7 @@ func runBuildImpl(cmd *command) (*packages.Package, error) {
|
|||
if buildBundleID == "" {
|
||||
return nil, fmt.Errorf("-target=ios requires -bundleid set")
|
||||
}
|
||||
nmpkgs, err = goIOSBuild(pkg, buildBundleID, targetArchs)
|
||||
nmpkgs, err = goAppleBuild(pkg, buildBundleID, targets)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -236,7 +255,7 @@ func addBuildFlags(cmd *command) {
|
|||
cmd.flag.StringVar(&buildLdflags, "ldflags", "", "")
|
||||
cmd.flag.StringVar(&buildTarget, "target", "android", "")
|
||||
cmd.flag.StringVar(&buildBundleID, "bundleid", "", "")
|
||||
cmd.flag.StringVar(&buildIOSVersion, "iosversion", "7.0", "")
|
||||
cmd.flag.StringVar(&buildIOSVersion, "iosversion", "13.0", "")
|
||||
cmd.flag.IntVar(&buildAndroidAPI, "androidapi", minAndroidAPI, "")
|
||||
|
||||
cmd.flag.BoolVar(&buildA, "a", false, "")
|
||||
|
@ -292,7 +311,7 @@ func goCmdAt(at string, subcmd string, srcs []string, env []string, args ...stri
|
|||
cmd := exec.Command("go", subcmd)
|
||||
tags := buildTags
|
||||
if len(tags) > 0 {
|
||||
cmd.Args = append(cmd.Args, "-tags", strings.Join(tags, " "))
|
||||
cmd.Args = append(cmd.Args, "-tags", strings.Join(tags, ","))
|
||||
}
|
||||
if buildV {
|
||||
cmd.Args = append(cmd.Args, "-v")
|
||||
|
@ -332,60 +351,77 @@ func goModTidyAt(at string, env []string) error {
|
|||
return runCmd(cmd)
|
||||
}
|
||||
|
||||
func parseBuildTarget(buildTarget string) (os string, archs []string, _ error) {
|
||||
// parseBuildTarget parses buildTarget into 1 or more platforms and architectures.
|
||||
// Returns an error if buildTarget contains invalid input.
|
||||
// Example valid target strings:
|
||||
// android
|
||||
// android/arm64,android/386,android/amd64
|
||||
// ios,iossimulator,maccatalyst
|
||||
// macos/amd64
|
||||
func parseBuildTarget(buildTarget string) ([]targetInfo, error) {
|
||||
if buildTarget == "" {
|
||||
return "", nil, fmt.Errorf(`invalid target ""`)
|
||||
return nil, fmt.Errorf(`invalid target ""`)
|
||||
}
|
||||
|
||||
all := false
|
||||
archNames := []string{}
|
||||
for i, p := range strings.Split(buildTarget, ",") {
|
||||
osarch := strings.SplitN(p, "/", 2) // len(osarch) > 0
|
||||
if osarch[0] != "android" && osarch[0] != "ios" {
|
||||
return "", nil, fmt.Errorf(`unsupported os`)
|
||||
}
|
||||
targets := []targetInfo{}
|
||||
targetsAdded := make(map[targetInfo]bool)
|
||||
|
||||
if i == 0 {
|
||||
os = osarch[0]
|
||||
addTarget := func(platform, arch string) {
|
||||
t := targetInfo{platform, arch}
|
||||
if targetsAdded[t] {
|
||||
return
|
||||
}
|
||||
targets = append(targets, t)
|
||||
targetsAdded[t] = true
|
||||
}
|
||||
|
||||
if os != osarch[0] {
|
||||
return "", nil, fmt.Errorf(`cannot target different OSes`)
|
||||
addPlatform := func(platform string) {
|
||||
for _, arch := range platformArchs(platform) {
|
||||
addTarget(platform, arch)
|
||||
}
|
||||
}
|
||||
|
||||
if len(osarch) == 1 {
|
||||
all = true
|
||||
var isAndroid, isApple bool
|
||||
for _, target := range strings.Split(buildTarget, ",") {
|
||||
tuple := strings.SplitN(target, "/", 2)
|
||||
platform := tuple[0]
|
||||
hasArch := len(tuple) == 2
|
||||
|
||||
if isAndroidPlatform(platform) {
|
||||
isAndroid = true
|
||||
} else if isApplePlatform(platform) {
|
||||
isApple = true
|
||||
} else {
|
||||
archNames = append(archNames, osarch[1])
|
||||
return nil, fmt.Errorf("unsupported platform: %q", platform)
|
||||
}
|
||||
if isAndroid && isApple {
|
||||
return nil, fmt.Errorf(`cannot mix android and Apple platforms`)
|
||||
}
|
||||
}
|
||||
|
||||
// verify all archs are supported one while deduping.
|
||||
isSupported := func(os, arch string) bool {
|
||||
for _, a := range allArchs(os) {
|
||||
if a == arch {
|
||||
return true
|
||||
if hasArch {
|
||||
arch := tuple[1]
|
||||
if !isSupportedArch(platform, arch) {
|
||||
return nil, fmt.Errorf(`unsupported platform/arch: %q`, target)
|
||||
}
|
||||
addTarget(platform, arch)
|
||||
} else {
|
||||
addPlatform(platform)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
targetOS := os
|
||||
seen := map[string]bool{}
|
||||
for _, arch := range archNames {
|
||||
if _, ok := seen[arch]; ok {
|
||||
continue
|
||||
}
|
||||
if !isSupported(os, arch) {
|
||||
return "", nil, fmt.Errorf(`unsupported arch: %q`, arch)
|
||||
}
|
||||
|
||||
seen[arch] = true
|
||||
archs = append(archs, arch)
|
||||
// Special case to build iossimulator if -target=ios
|
||||
if buildTarget == "ios" {
|
||||
addPlatform("iossimulator")
|
||||
}
|
||||
|
||||
if all {
|
||||
return targetOS, allArchs(os), nil
|
||||
}
|
||||
return targetOS, archs, nil
|
||||
return targets, nil
|
||||
}
|
||||
|
||||
type targetInfo struct {
|
||||
platform string
|
||||
arch string
|
||||
}
|
||||
|
||||
func (t targetInfo) String() string {
|
||||
return t.platform + "/" + t.arch
|
||||
}
|
||||
|
|
|
@ -24,7 +24,7 @@ import (
|
|||
"golang.org/x/tools/go/packages"
|
||||
)
|
||||
|
||||
func goAndroidBuild(pkg *packages.Package, androidArchs []string) (map[string]bool, error) {
|
||||
func goAndroidBuild(pkg *packages.Package, targets []targetInfo) (map[string]bool, error) {
|
||||
ndkRoot, err := ndkRoot()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -68,8 +68,8 @@ func goAndroidBuild(pkg *packages.Package, androidArchs []string) (map[string]bo
|
|||
libFiles := []string{}
|
||||
nmpkgs := make(map[string]map[string]bool) // map: arch -> extractPkgs' output
|
||||
|
||||
for _, arch := range androidArchs {
|
||||
toolchain := ndk.Toolchain(arch)
|
||||
for _, t := range targets {
|
||||
toolchain := ndk.Toolchain(t.arch)
|
||||
libPath := "lib/" + toolchain.abi + "/lib" + libName + ".so"
|
||||
libAbsPath := filepath.Join(tmpdir, libPath)
|
||||
if err := mkdir(filepath.Dir(libAbsPath)); err != nil {
|
||||
|
@ -77,14 +77,14 @@ func goAndroidBuild(pkg *packages.Package, androidArchs []string) (map[string]bo
|
|||
}
|
||||
err = goBuild(
|
||||
pkg.PkgPath,
|
||||
androidEnv[arch],
|
||||
androidEnv[t.arch],
|
||||
"-buildmode=c-shared",
|
||||
"-o", libAbsPath,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
nmpkgs[arch], err = extractPkgs(toolchain.Path(ndkRoot, "nm"), libAbsPath)
|
||||
nmpkgs[t.arch], err = extractPkgs(toolchain.Path(ndkRoot, "nm"), libAbsPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -169,9 +169,9 @@ func goAndroidBuild(pkg *packages.Package, androidArchs []string) (map[string]bo
|
|||
}
|
||||
}
|
||||
|
||||
for _, arch := range androidArchs {
|
||||
toolchain := ndk.Toolchain(arch)
|
||||
if nmpkgs[arch]["golang.org/x/mobile/exp/audio/al"] {
|
||||
for _, t := range targets {
|
||||
toolchain := ndk.Toolchain(t.arch)
|
||||
if nmpkgs[t.arch]["golang.org/x/mobile/exp/audio/al"] {
|
||||
dst := "lib/" + toolchain.abi + "/libopenal.so"
|
||||
src := filepath.Join(gomobilepath, dst)
|
||||
if _, err := os.Stat(src); err != nil {
|
||||
|
@ -282,7 +282,7 @@ func goAndroidBuild(pkg *packages.Package, androidArchs []string) (map[string]bo
|
|||
}
|
||||
|
||||
// TODO: return nmpkgs
|
||||
return nmpkgs[androidArchs[0]], nil
|
||||
return nmpkgs[targets[0].arch], nil
|
||||
}
|
||||
|
||||
// androidPkgName sanitizes the go package name to be acceptable as a android
|
||||
|
|
|
@ -20,7 +20,7 @@ import (
|
|||
"golang.org/x/tools/go/packages"
|
||||
)
|
||||
|
||||
func goIOSBuild(pkg *packages.Package, bundleID string, archs []string) (map[string]bool, error) {
|
||||
func goAppleBuild(pkg *packages.Package, bundleID string, targets []targetInfo) (map[string]bool, error) {
|
||||
src := pkg.PkgPath
|
||||
if buildO != "" && !strings.HasSuffix(buildO, ".app") {
|
||||
return nil, fmt.Errorf("-o must have an .app for -target=ios")
|
||||
|
@ -69,21 +69,32 @@ func goIOSBuild(pkg *packages.Package, bundleID string, archs []string) (map[str
|
|||
"-o", filepath.Join(tmpdir, "main/main"),
|
||||
"-create",
|
||||
)
|
||||
|
||||
var nmpkgs map[string]bool
|
||||
for _, arch := range archs {
|
||||
path := filepath.Join(tmpdir, arch)
|
||||
builtArch := map[string]bool{}
|
||||
for _, t := range targets {
|
||||
// Only one binary per arch allowed
|
||||
// e.g. ios/arm64 + iossimulator/amd64
|
||||
if builtArch[t.arch] {
|
||||
continue
|
||||
}
|
||||
builtArch[t.arch] = true
|
||||
|
||||
path := filepath.Join(tmpdir, t.platform, t.arch)
|
||||
|
||||
// Disable DWARF; see golang.org/issues/25148.
|
||||
if err := goBuild(src, iosEnv[arch], "-ldflags=-w", "-o="+path); err != nil {
|
||||
if err := goBuild(src, appleEnv[t.String()], "-ldflags=-w", "-o="+path); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if nmpkgs == nil {
|
||||
var err error
|
||||
nmpkgs, err = extractPkgs(iosArmNM, path)
|
||||
nmpkgs, err = extractPkgs(appleNM, path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
cmd.Args = append(cmd.Args, path)
|
||||
|
||||
}
|
||||
|
||||
if err := runCmd(cmd); err != nil {
|
||||
|
@ -91,7 +102,7 @@ func goIOSBuild(pkg *packages.Package, bundleID string, archs []string) (map[str
|
|||
}
|
||||
|
||||
// TODO(jbd): Set the launcher icon.
|
||||
if err := iosCopyAssets(pkg, tmpdir); err != nil {
|
||||
if err := appleCopyAssets(pkg, tmpdir); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
@ -145,7 +156,7 @@ func goIOSBuild(pkg *packages.Package, bundleID string, archs []string) (map[str
|
|||
}
|
||||
|
||||
func detectTeamID() (string, error) {
|
||||
// Grabs the certificate for "Apple Development"; will not work if there
|
||||
// Grabs the first certificate for "Apple Development"; will not work if there
|
||||
// are multiple certificates and the first is not desired.
|
||||
cmd := exec.Command(
|
||||
"security", "find-certificate",
|
||||
|
@ -170,14 +181,14 @@ func detectTeamID() (string, error) {
|
|||
}
|
||||
|
||||
if len(cert.Subject.OrganizationalUnit) == 0 {
|
||||
err = fmt.Errorf("the signing certificate has no organizational unit (team ID).")
|
||||
err = fmt.Errorf("the signing certificate has no organizational unit (team ID)")
|
||||
return "", err
|
||||
}
|
||||
|
||||
return cert.Subject.OrganizationalUnit[0], nil
|
||||
}
|
||||
|
||||
func iosCopyAssets(pkg *packages.Package, xcodeProjDir string) error {
|
||||
func appleCopyAssets(pkg *packages.Package, xcodeProjDir string) error {
|
||||
dstAssets := xcodeProjDir + "/main/assets"
|
||||
if err := mkdir(dstAssets); err != nil {
|
||||
return err
|
||||
|
@ -424,7 +435,6 @@ const projPbxproj = `// !$*UTF8*$!
|
|||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
SDKROOT = iphoneos;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
|
@ -12,7 +12,7 @@ import (
|
|||
"text/template"
|
||||
)
|
||||
|
||||
func TestIOSBuild(t *testing.T) {
|
||||
func TestAppleBuild(t *testing.T) {
|
||||
if !xcodeAvailable() {
|
||||
t.Skip("Xcode is missing")
|
||||
}
|
||||
|
@ -41,10 +41,13 @@ func TestIOSBuild(t *testing.T) {
|
|||
for _, test := range tests {
|
||||
buf := new(bytes.Buffer)
|
||||
xout = buf
|
||||
var tmpl *template.Template
|
||||
if test.main {
|
||||
buildO = "basic.app"
|
||||
tmpl = appleMainBuildTmpl
|
||||
} else {
|
||||
buildO = ""
|
||||
tmpl = appleOtherBuildTmpl
|
||||
}
|
||||
cmdBuild.flag.Parse([]string{test.pkg})
|
||||
err := runBuild(cmdBuild)
|
||||
|
@ -68,18 +71,20 @@ func TestIOSBuild(t *testing.T) {
|
|||
TeamID string
|
||||
Pkg string
|
||||
Main bool
|
||||
BuildO string
|
||||
}{
|
||||
outputData: output,
|
||||
TeamID: teamID,
|
||||
Pkg: test.pkg,
|
||||
Main: test.main,
|
||||
BuildO: buildO,
|
||||
}
|
||||
|
||||
got := filepath.ToSlash(buf.String())
|
||||
|
||||
wantBuf := new(bytes.Buffer)
|
||||
|
||||
if err := iosBuildTmpl.Execute(wantBuf, data); err != nil {
|
||||
if err := tmpl.Execute(wantBuf, data); err != nil {
|
||||
t.Fatalf("computing diff failed: %v", err)
|
||||
}
|
||||
|
||||
|
@ -94,18 +99,25 @@ func TestIOSBuild(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
var iosBuildTmpl = template.Must(infoplistTmpl.New("output").Parse(`GOMOBILE={{.GOPATH}}/pkg/gomobile
|
||||
WORK=$WORK{{if .Main}}
|
||||
var appleMainBuildTmpl = template.Must(infoplistTmpl.New("output").Parse(`GOMOBILE={{.GOPATH}}/pkg/gomobile
|
||||
WORK=$WORK
|
||||
mkdir -p $WORK/main.xcodeproj
|
||||
echo "{{.Xproj}}" > $WORK/main.xcodeproj/project.pbxproj
|
||||
mkdir -p $WORK/main
|
||||
echo "{{template "infoplist" .Xinfo}}" > $WORK/main/Info.plist
|
||||
mkdir -p $WORK/main/Images.xcassets/AppIcon.appiconset
|
||||
echo "{{.Xcontents}}" > $WORK/main/Images.xcassets/AppIcon.appiconset/Contents.json{{end}}
|
||||
GOOS=ios GOARCH=arm64 CC=iphoneos-clang CXX=iphoneos-clang++ CGO_CFLAGS=-isysroot=iphoneos -miphoneos-version-min=7.0 -fembed-bitcode -arch arm64 CGO_CXXFLAGS=-isysroot=iphoneos -miphoneos-version-min=7.0 -fembed-bitcode -arch arm64 CGO_LDFLAGS=-isysroot=iphoneos -miphoneos-version-min=7.0 -fembed-bitcode -arch arm64 CGO_ENABLED=1 go build -tags tag1 -x {{if .Main}}-ldflags=-w -o=$WORK/arm64 {{end}}{{.Pkg}}
|
||||
GOOS=ios GOARCH=amd64 CC=iphonesimulator-clang CXX=iphonesimulator-clang++ CGO_CFLAGS=-isysroot=iphonesimulator -mios-simulator-version-min=7.0 -fembed-bitcode -arch x86_64 CGO_CXXFLAGS=-isysroot=iphonesimulator -mios-simulator-version-min=7.0 -fembed-bitcode -arch x86_64 CGO_LDFLAGS=-isysroot=iphonesimulator -mios-simulator-version-min=7.0 -fembed-bitcode -arch x86_64 CGO_ENABLED=1 go build -tags tag1 -x {{if .Main}}-ldflags=-w -o=$WORK/amd64 {{end}}{{.Pkg}}{{if .Main}}
|
||||
xcrun lipo -o $WORK/main/main -create $WORK/arm64 $WORK/amd64
|
||||
echo "{{.Xcontents}}" > $WORK/main/Images.xcassets/AppIcon.appiconset/Contents.json
|
||||
GOOS=ios GOARCH=arm64 GOFLAGS=-tags=ios CC=iphoneos-clang CXX=iphoneos-clang++ CGO_CFLAGS=-isysroot iphoneos -miphoneos-version-min=13.0 -fembed-bitcode -arch arm64 CGO_CXXFLAGS=-isysroot iphoneos -miphoneos-version-min=13.0 -fembed-bitcode -arch arm64 CGO_LDFLAGS=-isysroot iphoneos -miphoneos-version-min=13.0 -fembed-bitcode -arch arm64 CGO_ENABLED=1 DARWIN_SDK=iphoneos go build -tags tag1 -x -ldflags=-w -o=$WORK/ios/arm64 {{.Pkg}}
|
||||
GOOS=ios GOARCH=amd64 GOFLAGS=-tags=ios CC=iphonesimulator-clang CXX=iphonesimulator-clang++ CGO_CFLAGS=-isysroot iphonesimulator -mios-simulator-version-min=13.0 -fembed-bitcode -arch x86_64 CGO_CXXFLAGS=-isysroot iphonesimulator -mios-simulator-version-min=13.0 -fembed-bitcode -arch x86_64 CGO_LDFLAGS=-isysroot iphonesimulator -mios-simulator-version-min=13.0 -fembed-bitcode -arch x86_64 CGO_ENABLED=1 DARWIN_SDK=iphonesimulator go build -tags tag1 -x -ldflags=-w -o=$WORK/iossimulator/amd64 {{.Pkg}}
|
||||
xcrun lipo -o $WORK/main/main -create $WORK/ios/arm64 $WORK/iossimulator/amd64
|
||||
mkdir -p $WORK/main/assets
|
||||
xcrun xcodebuild -configuration Release -project $WORK/main.xcodeproj -allowProvisioningUpdates DEVELOPMENT_TEAM={{.TeamID}}
|
||||
mv $WORK/build/Release-iphoneos/main.app basic.app{{end}}
|
||||
mv $WORK/build/Release-iphoneos/main.app {{.BuildO}}
|
||||
`))
|
||||
|
||||
var appleOtherBuildTmpl = template.Must(infoplistTmpl.New("output").Parse(`GOMOBILE={{.GOPATH}}/pkg/gomobile
|
||||
WORK=$WORK
|
||||
GOOS=ios GOARCH=arm64 GOFLAGS=-tags=ios CC=iphoneos-clang CXX=iphoneos-clang++ CGO_CFLAGS=-isysroot iphoneos -miphoneos-version-min=13.0 -fembed-bitcode -arch arm64 CGO_CXXFLAGS=-isysroot iphoneos -miphoneos-version-min=13.0 -fembed-bitcode -arch arm64 CGO_LDFLAGS=-isysroot iphoneos -miphoneos-version-min=13.0 -fembed-bitcode -arch arm64 CGO_ENABLED=1 DARWIN_SDK=iphoneos go build -tags tag1 -x {{.Pkg}}
|
||||
GOOS=ios GOARCH=arm64 GOFLAGS=-tags=ios CC=iphonesimulator-clang CXX=iphonesimulator-clang++ CGO_CFLAGS=-isysroot iphonesimulator -mios-simulator-version-min=13.0 -fembed-bitcode -arch arm64 CGO_CXXFLAGS=-isysroot iphonesimulator -mios-simulator-version-min=13.0 -fembed-bitcode -arch arm64 CGO_LDFLAGS=-isysroot iphonesimulator -mios-simulator-version-min=13.0 -fembed-bitcode -arch arm64 CGO_ENABLED=1 DARWIN_SDK=iphonesimulator go build -tags tag1 -x {{.Pkg}}
|
||||
GOOS=ios GOARCH=amd64 GOFLAGS=-tags=ios CC=iphonesimulator-clang CXX=iphonesimulator-clang++ CGO_CFLAGS=-isysroot iphonesimulator -mios-simulator-version-min=13.0 -fembed-bitcode -arch x86_64 CGO_CXXFLAGS=-isysroot iphonesimulator -mios-simulator-version-min=13.0 -fembed-bitcode -arch x86_64 CGO_LDFLAGS=-isysroot iphonesimulator -mios-simulator-version-min=13.0 -fembed-bitcode -arch x86_64 CGO_ENABLED=1 DARWIN_SDK=iphonesimulator go build -tags tag1 -x {{.Pkg}}
|
||||
`))
|
||||
|
|
|
@ -114,46 +114,63 @@ mkdir -p $WORK/lib/armeabi-v7a
|
|||
GOOS=android GOARCH=arm CC=$NDK_PATH/toolchains/llvm/prebuilt/{{.NDKARCH}}/bin/armv7a-linux-androideabi16-clang CXX=$NDK_PATH/toolchains/llvm/prebuilt/{{.NDKARCH}}/bin/armv7a-linux-androideabi16-clang++ CGO_ENABLED=1 GOARM=7 go build -tags tag1 -x -buildmode=c-shared -o $WORK/lib/armeabi-v7a/libbasic.so golang.org/x/mobile/example/basic
|
||||
`))
|
||||
|
||||
func TestParseBuildTargetFlag(t *testing.T) {
|
||||
androidArchs := strings.Join(allArchs("android"), ",")
|
||||
iosArchs := strings.Join(allArchs("ios"), ",")
|
||||
func TestParseBuildTarget(t *testing.T) {
|
||||
wantAndroid := "android/" + strings.Join(platformArchs("android"), ",android/")
|
||||
|
||||
tests := []struct {
|
||||
in string
|
||||
wantErr bool
|
||||
wantOS string
|
||||
wantArchs string
|
||||
in string
|
||||
wantErr bool
|
||||
want string
|
||||
}{
|
||||
{"android", false, "android", androidArchs},
|
||||
{"android,android/arm", false, "android", androidArchs},
|
||||
{"android/arm", false, "android", "arm"},
|
||||
{"android", false, wantAndroid},
|
||||
{"android,android/arm", false, wantAndroid},
|
||||
{"android/arm", false, "android/arm"},
|
||||
|
||||
{"ios", false, "ios", iosArchs},
|
||||
{"ios,ios/arm64", false, "ios", iosArchs},
|
||||
{"ios/arm64", false, "ios", "arm64"},
|
||||
{"ios/amd64", false, "ios", "amd64"},
|
||||
{"ios", false, "ios/arm64,iossimulator/arm64,iossimulator/amd64"},
|
||||
{"ios,ios/arm64", false, "ios/arm64"},
|
||||
{"ios/arm64", false, "ios/arm64"},
|
||||
|
||||
{"", true, "", ""},
|
||||
{"linux", true, "", ""},
|
||||
{"android/x86", true, "", ""},
|
||||
{"android/arm5", true, "", ""},
|
||||
{"ios/mips", true, "", ""},
|
||||
{"android,ios", true, "", ""},
|
||||
{"ios,android", true, "", ""},
|
||||
{"iossimulator", false, "iossimulator/arm64,iossimulator/amd64"},
|
||||
{"iossimulator/amd64", false, "iossimulator/amd64"},
|
||||
|
||||
{"macos", false, "macos/arm64,macos/amd64"},
|
||||
{"macos,ios/arm64", false, "macos/arm64,macos/amd64,ios/arm64"},
|
||||
{"macos/arm64", false, "macos/arm64"},
|
||||
{"macos/amd64", false, "macos/amd64"},
|
||||
|
||||
{"maccatalyst", false, "maccatalyst/arm64,maccatalyst/amd64"},
|
||||
{"maccatalyst,ios/arm64", false, "maccatalyst/arm64,maccatalyst/amd64,ios/arm64"},
|
||||
{"maccatalyst/arm64", false, "maccatalyst/arm64"},
|
||||
{"maccatalyst/amd64", false, "maccatalyst/amd64"},
|
||||
|
||||
{"", true, ""},
|
||||
{"linux", true, ""},
|
||||
{"android/x86", true, ""},
|
||||
{"android/arm5", true, ""},
|
||||
{"ios/mips", true, ""},
|
||||
{"android,ios", true, ""},
|
||||
{"ios,android", true, ""},
|
||||
{"ios/amd64", true, ""},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
gotOS, gotArchs, err := parseBuildTarget(tc.in)
|
||||
if tc.wantErr {
|
||||
if err == nil {
|
||||
t.Errorf("-target=%q; want error, got (%q, %q, nil)", tc.in, gotOS, gotArchs)
|
||||
t.Run(tc.in, func(t *testing.T) {
|
||||
targets, err := parseBuildTarget(tc.in)
|
||||
var s []string
|
||||
for _, t := range targets {
|
||||
s = append(s, t.String())
|
||||
}
|
||||
continue
|
||||
}
|
||||
if err != nil || gotOS != tc.wantOS || strings.Join(gotArchs, ",") != tc.wantArchs {
|
||||
t.Errorf("-target=%q; want (%v, [%v], nil), got (%q, %q, %v)",
|
||||
tc.in, tc.wantOS, tc.wantArchs, gotOS, gotArchs, err)
|
||||
}
|
||||
got := strings.Join(s, ",")
|
||||
if tc.wantErr {
|
||||
if err == nil {
|
||||
t.Errorf("-target=%q; want error, got (%q, nil)", tc.in, got)
|
||||
}
|
||||
return
|
||||
}
|
||||
if err != nil || got != tc.want {
|
||||
t.Errorf("-target=%q; want (%q, nil), got (%q, %v)", tc.in, tc.want, got, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -35,13 +35,13 @@ Build a library for Android and iOS
|
|||
|
||||
Usage:
|
||||
|
||||
gomobile bind [-target android|ios] [-bootclasspath <path>] [-classpath <path>] [-o output] [build flags] [package]
|
||||
gomobile bind [-target android|ios|iossimulator|macos|maccatalyst] [-bootclasspath <path>] [-classpath <path>] [-o output] [build flags] [package]
|
||||
|
||||
Bind generates language bindings for the package named by the import
|
||||
path, and compiles a library for the named target system.
|
||||
|
||||
The -target flag takes a target system name, either android (the
|
||||
default) or ios.
|
||||
The -target flag takes either android (the default), or one or more
|
||||
comma-delimited Apple platforms (ios, iossimulator, macos, maccatalyst).
|
||||
|
||||
For -target android, the bind command produces an AAR (Android ARchive)
|
||||
file that archives the precompiled Java API stub classes, the compiled
|
||||
|
@ -63,9 +63,9 @@ instruction sets (arm, arm64, 386, amd64). A subset of instruction sets
|
|||
can be selected by specifying target type with the architecture name. E.g.,
|
||||
-target=android/arm,android/386.
|
||||
|
||||
For -target ios, gomobile must be run on an OS X machine with Xcode
|
||||
installed. The generated Objective-C types can be prefixed with the -prefix
|
||||
flag.
|
||||
For Apple -target platforms, gomobile must be run on an OS X machine with
|
||||
Xcode installed. The generated Objective-C types can be prefixed with the
|
||||
-prefix flag.
|
||||
|
||||
For -target android, the -bootclasspath and -classpath flags are used to
|
||||
control the bootstrap classpath and the classpath for Go wrappers to Java
|
||||
|
@ -81,14 +81,14 @@ Compile android APK and iOS app
|
|||
|
||||
Usage:
|
||||
|
||||
gomobile build [-target android|ios] [-o output] [-bundleid bundleID] [build flags] [package]
|
||||
gomobile build [-target android|ios|iossimulator|macos|maccatalyst] [-o output] [-bundleid bundleID] [build flags] [package]
|
||||
|
||||
Build compiles and encodes the app named by the import path.
|
||||
|
||||
The named package must define a main function.
|
||||
|
||||
The -target flag takes a target system name, either android (the
|
||||
default) or ios.
|
||||
The -target flag takes either android (the default), or one or more
|
||||
comma-delimited Apple platforms (ios, iossimulator, macos, maccatalyst).
|
||||
|
||||
For -target android, if an AndroidManifest.xml is defined in the
|
||||
package directory, it is added to the APK output. Otherwise, a default
|
||||
|
@ -97,14 +97,22 @@ instruction sets (arm, 386, amd64, arm64). A subset of instruction sets can
|
|||
be selected by specifying target type with the architecture name. E.g.
|
||||
-target=android/arm,android/386.
|
||||
|
||||
For -target ios, gomobile must be run on an OS X machine with Xcode
|
||||
installed.
|
||||
For Apple -target platforms, gomobile must be run on an OS X machine with
|
||||
Xcode installed.
|
||||
|
||||
By default, -target ios will generate an XCFramework for both ios
|
||||
and iossimulator. Multiple Apple targets can be specified, creating a "fat"
|
||||
XCFramework with each slice. To generate a fat XCFramework that supports
|
||||
iOS, macOS, and macCatalyst for all supportec architectures (amd64 and arm64),
|
||||
specify -target ios,macos,maccatalyst. A subset of instruction sets can be
|
||||
selectged by specifying the platform with an architecture name. E.g.
|
||||
-target=ios/arm64,maccatalyst/arm64.
|
||||
|
||||
If the package directory contains an assets subdirectory, its contents
|
||||
are copied into the output.
|
||||
|
||||
Flag -iosversion sets the minimal version of the iOS SDK to compile against.
|
||||
The default version is 7.0.
|
||||
The default version is 13.0.
|
||||
|
||||
Flag -androidapi sets the Android API version to compile against.
|
||||
The default and minimum is 15.
|
||||
|
|
|
@ -17,23 +17,95 @@ var (
|
|||
|
||||
androidEnv map[string][]string // android arch -> []string
|
||||
|
||||
iosEnv map[string][]string
|
||||
appleEnv map[string][]string
|
||||
|
||||
androidArmNM string
|
||||
iosArmNM string
|
||||
appleNM string
|
||||
)
|
||||
|
||||
func allArchs(targetOS string) []string {
|
||||
switch targetOS {
|
||||
func isAndroidPlatform(platform string) bool {
|
||||
return platform == "android"
|
||||
}
|
||||
|
||||
func isApplePlatform(platform string) bool {
|
||||
return contains(applePlatforms, platform)
|
||||
}
|
||||
|
||||
var applePlatforms = []string{"ios", "iossimulator", "macos", "maccatalyst"}
|
||||
|
||||
func platformArchs(platform string) []string {
|
||||
switch platform {
|
||||
case "ios":
|
||||
return []string{"arm64"}
|
||||
case "iossimulator":
|
||||
return []string{"arm64", "amd64"}
|
||||
case "macos", "maccatalyst":
|
||||
return []string{"arm64", "amd64"}
|
||||
case "android":
|
||||
return []string{"arm", "arm64", "386", "amd64"}
|
||||
default:
|
||||
panic(fmt.Sprintf("unexpected target OS: %s", targetOS))
|
||||
panic(fmt.Sprintf("unexpected platform: %s", platform))
|
||||
}
|
||||
}
|
||||
|
||||
func isSupportedArch(platform, arch string) bool {
|
||||
return contains(platformArchs(platform), arch)
|
||||
}
|
||||
|
||||
// platformOS returns the correct GOOS value for platform.
|
||||
func platformOS(platform string) string {
|
||||
switch platform {
|
||||
case "android":
|
||||
return "android"
|
||||
case "ios", "iossimulator":
|
||||
return "ios"
|
||||
case "macos", "maccatalyst":
|
||||
// For "maccatalyst", Go packages should be built with GOOS=darwin,
|
||||
// not GOOS=ios, since the underlying OS (and kernel, runtime) is macOS.
|
||||
// We also apply a "macos" or "maccatalyst" build tag, respectively.
|
||||
// See below for additional context.
|
||||
return "darwin"
|
||||
default:
|
||||
panic(fmt.Sprintf("unexpected platform: %s", platform))
|
||||
}
|
||||
}
|
||||
|
||||
func platformTags(platform string) []string {
|
||||
switch platform {
|
||||
case "android":
|
||||
return []string{"android"}
|
||||
case "ios", "iossimulator":
|
||||
return []string{"ios"}
|
||||
case "macos":
|
||||
return []string{"macos"}
|
||||
case "maccatalyst":
|
||||
// Mac Catalyst is a subset of iOS APIs made available on macOS
|
||||
// designed to ease porting apps developed for iPad to macOS.
|
||||
// See https://developer.apple.com/mac-catalyst/.
|
||||
// Because of this, when building a Go package targeting maccatalyst,
|
||||
// GOOS=darwin (not ios). To bridge the gap and enable maccatalyst
|
||||
// packages to be compiled, we also specify the "ios" build tag.
|
||||
// To help discriminate between darwin, ios, macos, and maccatalyst
|
||||
// targets, there is also a "maccatalyst" tag.
|
||||
// Some additional context on this can be found here:
|
||||
// https://stackoverflow.com/questions/12132933/preprocessor-macro-for-os-x-targets/49560690#49560690
|
||||
// TODO(ydnar): remove tag "ios" when cgo supports Catalyst
|
||||
// See golang.org/issues/47228
|
||||
return []string{"ios", "macos", "maccatalyst"}
|
||||
default:
|
||||
panic(fmt.Sprintf("unexpected platform: %s", platform))
|
||||
}
|
||||
}
|
||||
|
||||
func contains(haystack []string, needle string) bool {
|
||||
for _, v := range haystack {
|
||||
if v == needle {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func buildEnvInit() (cleanup func(), err error) {
|
||||
// Find gomobilepath.
|
||||
gopath := goEnv("GOPATH")
|
||||
|
@ -123,37 +195,85 @@ func envInit() (err error) {
|
|||
return nil
|
||||
}
|
||||
|
||||
iosArmNM = "nm"
|
||||
iosEnv = make(map[string][]string)
|
||||
for _, arch := range allArchs("ios") {
|
||||
var env []string
|
||||
var err error
|
||||
var clang, cflags string
|
||||
switch arch {
|
||||
case "arm64":
|
||||
clang, cflags, err = envClang("iphoneos")
|
||||
cflags += " -miphoneos-version-min=" + buildIOSVersion
|
||||
case "amd64":
|
||||
clang, cflags, err = envClang("iphonesimulator")
|
||||
cflags += " -mios-simulator-version-min=" + buildIOSVersion
|
||||
default:
|
||||
panic(fmt.Errorf("unknown GOARCH: %q", arch))
|
||||
appleNM = "nm"
|
||||
appleEnv = make(map[string][]string)
|
||||
for _, platform := range applePlatforms {
|
||||
for _, arch := range platformArchs(platform) {
|
||||
var env []string
|
||||
var goos, sdk, clang, cflags string
|
||||
var err error
|
||||
switch platform {
|
||||
case "ios":
|
||||
goos = "ios"
|
||||
sdk = "iphoneos"
|
||||
clang, cflags, err = envClang(sdk)
|
||||
cflags += " -miphoneos-version-min=" + buildIOSVersion
|
||||
cflags += " -fembed-bitcode"
|
||||
case "iossimulator":
|
||||
goos = "ios"
|
||||
sdk = "iphonesimulator"
|
||||
clang, cflags, err = envClang(sdk)
|
||||
cflags += " -mios-simulator-version-min=" + buildIOSVersion
|
||||
cflags += " -fembed-bitcode"
|
||||
case "maccatalyst":
|
||||
// Mac Catalyst is a subset of iOS APIs made available on macOS
|
||||
// designed to ease porting apps developed for iPad to macOS.
|
||||
// See https://developer.apple.com/mac-catalyst/.
|
||||
// Because of this, when building a Go package targeting maccatalyst,
|
||||
// GOOS=darwin (not ios). To bridge the gap and enable maccatalyst
|
||||
// packages to be compiled, we also specify the "ios" build tag.
|
||||
// To help discriminate between darwin, ios, macos, and maccatalyst
|
||||
// targets, there is also a "maccatalyst" tag.
|
||||
// Some additional context on this can be found here:
|
||||
// https://stackoverflow.com/questions/12132933/preprocessor-macro-for-os-x-targets/49560690#49560690
|
||||
goos = "darwin"
|
||||
sdk = "macosx"
|
||||
clang, cflags, err = envClang(sdk)
|
||||
// TODO(ydnar): the following 3 lines MAY be needed to compile
|
||||
// packages or apps for maccatalyst. Commenting them out now in case
|
||||
// it turns out they are necessary. Currently none of the example
|
||||
// apps will build for macos or maccatalyst because they have a
|
||||
// GLKit dependency, which is deprecated on all Apple platforms, and
|
||||
// broken on maccatalyst (GLKView isn’t available).
|
||||
// sysroot := strings.SplitN(cflags, " ", 2)[1]
|
||||
// cflags += " -isystem " + sysroot + "/System/iOSSupport/usr/include"
|
||||
// cflags += " -iframework " + sysroot + "/System/iOSSupport/System/Library/Frameworks"
|
||||
switch arch {
|
||||
case "amd64":
|
||||
cflags += " -target x86_64-apple-ios" + buildIOSVersion + "-macabi"
|
||||
case "arm64":
|
||||
cflags += " -target arm64-apple-ios" + buildIOSVersion + "-macabi"
|
||||
cflags += " -fembed-bitcode"
|
||||
}
|
||||
case "macos":
|
||||
goos = "darwin"
|
||||
sdk = "macosx" // Note: the SDK is called "macosx", not "macos"
|
||||
clang, cflags, err = envClang(sdk)
|
||||
if arch == "arm64" {
|
||||
cflags += " -fembed-bitcode"
|
||||
}
|
||||
default:
|
||||
panic(fmt.Errorf("unknown Apple target: %s/%s", platform, arch))
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
env = append(env,
|
||||
"GOOS="+goos,
|
||||
"GOARCH="+arch,
|
||||
"GOFLAGS="+"-tags="+strings.Join(platformTags(platform), ","),
|
||||
"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",
|
||||
"DARWIN_SDK="+sdk,
|
||||
)
|
||||
appleEnv[platform+"/"+arch] = env
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cflags += " -fembed-bitcode"
|
||||
env = append(env,
|
||||
"GOOS=ios",
|
||||
"GOARCH="+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",
|
||||
)
|
||||
iosEnv[arch] = env
|
||||
}
|
||||
|
||||
return nil
|
||||
|
@ -186,7 +306,7 @@ func ndkRoot() (string, error) {
|
|||
|
||||
func envClang(sdkName string) (clang, cflags string, err error) {
|
||||
if buildN {
|
||||
return sdkName + "-clang", "-isysroot=" + sdkName, nil
|
||||
return sdkName + "-clang", "-isysroot " + sdkName, nil
|
||||
}
|
||||
cmd := exec.Command("xcrun", "--sdk", sdkName, "--find", "clang")
|
||||
out, err := cmd.CombinedOutput()
|
||||
|
|
|
@ -167,7 +167,7 @@ func installOpenAL(gomobilepath string) error {
|
|||
}
|
||||
}
|
||||
|
||||
for _, arch := range allArchs("android") {
|
||||
for _, arch := range platformArchs("android") {
|
||||
t := ndk[arch]
|
||||
abi := t.arch
|
||||
if abi == "arm" {
|
||||
|
|
|
@ -53,7 +53,7 @@ func runVersion(cmd *command) (err error) {
|
|||
// Supported platforms
|
||||
platforms := "android"
|
||||
if xcodeAvailable() {
|
||||
platforms = "android,ios"
|
||||
platforms += "," + strings.Join(applePlatforms, ",")
|
||||
}
|
||||
|
||||
// ANDROID_HOME, sdk build tool version
|
||||
|
|
Loading…
Reference in New Issue