blob: f1baa2dc16318825f5bfe321fa826a442f293fe9 [file] [log] [blame]
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
import (
"fmt"
"go/build"
"io"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"strings"
"text/template"
)
func goIOSBind(pkgs []*build.Package) error {
srcDir := filepath.Join(tmpdir, "src", "gomobile_bind")
genDir := filepath.Join(tmpdir, "gen")
wrappers, err := GenObjcWrappers(pkgs, srcDir, genDir)
if err != nil {
return err
}
env := darwinArmEnv
gopath := fmt.Sprintf("GOPATH=%s%c%s", genDir, filepath.ListSeparator, os.Getenv("GOPATH"))
env = append(env, gopath)
typesPkgs, err := loadExportData(pkgs, env)
if err != nil {
return err
}
binder, err := newBinder(typesPkgs)
if err != nil {
return err
}
name := binder.pkgs[0].Name()
title := strings.Title(name)
if buildO != "" && !strings.HasSuffix(buildO, ".framework") {
return fmt.Errorf("static framework name %q missing .framework suffix", buildO)
}
if buildO == "" {
buildO = title + ".framework"
}
for _, pkg := range binder.pkgs {
if err := binder.GenGo(pkg, binder.pkgs, srcDir); err != nil {
return err
}
}
// Generate the error type.
if err := binder.GenGo(nil, binder.pkgs, srcDir); err != nil {
return err
}
mainFile := filepath.Join(tmpdir, "src/iosbin/main.go")
err = writeFile(mainFile, func(w io.Writer) error {
_, err := w.Write(iosBindFile)
return err
})
if err != nil {
return fmt.Errorf("failed to create the binding package for iOS: %v", err)
}
fileBases := make([]string, len(typesPkgs)+1)
for i, pkg := range binder.pkgs {
if fileBases[i], err = binder.GenObjc(pkg, binder.pkgs, srcDir, wrappers); err != nil {
return err
}
}
if fileBases[len(fileBases)-1], err = binder.GenObjc(nil, binder.pkgs, srcDir, wrappers); err != nil {
return err
}
if err := binder.GenObjcSupport(srcDir); err != nil {
return err
}
if err := binder.GenGoSupport(srcDir); err != nil {
return err
}
cmd := exec.Command("xcrun", "lipo", "-create")
for _, env := range [][]string{darwinArmEnv, darwinArm64Env, darwinAmd64Env} {
env = append(env, gopath)
arch := archClang(getenv(env, "GOARCH"))
path, err := goIOSBindArchive(name, mainFile, env, fileBases)
if err != nil {
return fmt.Errorf("darwin-%s: %v", arch, err)
}
cmd.Args = append(cmd.Args, "-arch", 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
}
// Copy header file next to output archive.
headerFiles := make([]string, len(fileBases))
if len(fileBases) == 1 {
headerFiles[0] = title + ".h"
err = copyFile(
headers+"/"+title+".h",
srcDir+"/"+bindPrefix+title+".objc.h",
)
if err != nil {
return err
}
} else {
for i, fileBase := range fileBases {
headerFiles[i] = fileBase + ".objc.h"
err = copyFile(
headers+"/"+fileBase+".objc.h",
srcDir+"/"+fileBase+".objc.h")
if err != nil {
return err
}
}
err = copyFile(
headers+"/ref.h",
srcDir+"/ref.h")
if err != nil {
return err
}
headerFiles = append(headerFiles, title+".h")
err = writeFile(headers+"/"+title+".h", func(w io.Writer) error {
return iosBindHeaderTmpl.Execute(w, map[string]interface{}{
"pkgs": pkgs, "title": title, "bases": fileBases,
})
})
if err != nil {
return err
}
}
resources := 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 := ioutil.WriteFile(buildO+"/Resources/Info.plist", []byte(iosBindInfoPlist), 0666); 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
}
return symlink("Versions/Current/Modules", buildO+"/Modules")
}
const iosBindInfoPlist = `<?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>
</dict>
</plist>
`
var iosModuleMapTmpl = template.Must(template.New("iosmmap").Parse(`framework module "{{.Module}}" {
header "ref.h"
{{range .Headers}} header "{{.}}"
{{end}}
export *
}`))
func goIOSBindArchive(name, path string, env, fileBases []string) (string, error) {
arch := getenv(env, "GOARCH")
archive := filepath.Join(tmpdir, name+"-"+arch+".a")
err := goBuild(path, env, "-buildmode=c-archive", "-o", archive)
if err != nil {
return "", err
}
return archive, nil
}
var iosBindFile = []byte(`
package main
import (
_ "../gomobile_bind"
)
import "C"
func main() {}
`)
var iosBindHeaderTmpl = template.Must(template.New("ios.h").Parse(`
// Objective-C API for talking to the following Go packages
//
{{range .pkgs}}// {{.ImportPath}}
{{end}}//
// File is generated by gomobile bind. Do not edit.
#ifndef __{{.title}}_FRAMEWORK_H__
#define __{{.title}}_FRAMEWORK_H__
{{range .bases}}#include "{{.}}.objc.h"
{{end}}
#endif
`))