Adding upstream version 0.0~git20250520.a1d9079+dfsg.
Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
parent
590ac7ff5f
commit
20149b7f3a
456 changed files with 70406 additions and 0 deletions
252
cmd/gobind/doc.go
Normal file
252
cmd/gobind/doc.go
Normal file
|
@ -0,0 +1,252 @@
|
|||
// Copyright 2014 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.
|
||||
|
||||
/*
|
||||
Gobind generates language bindings that make it possible to call Go
|
||||
functions from Java and Objective-C.
|
||||
|
||||
Typically gobind is not used directly. Instead, a binding is
|
||||
generated and automatically packaged for Android or iOS by
|
||||
`gomobile bind`. For more details on installing and using the gomobile
|
||||
tool, see https://golang.org/x/mobile/cmd/gomobile.
|
||||
|
||||
# Binding Go
|
||||
|
||||
Gobind generates target language (Java or Objective-C) bindings for
|
||||
each exported symbol in a Go package. The Go package you choose to
|
||||
bind defines a cross-language interface.
|
||||
|
||||
Bindings require additional Go code be generated, so using gobind
|
||||
manually requires calling it twice, first with -lang=<target>, where
|
||||
target is either java or objc, and again with -lang=go. The generated
|
||||
package can then be _ imported into a Go program, typically built
|
||||
with -buildmode=c-archive for iOS or -buildmode=c-shared for Android.
|
||||
These details are handled by the `gomobile bind` command.
|
||||
|
||||
# Passing Go objects to target languages
|
||||
|
||||
Consider a type for counting:
|
||||
|
||||
package mypkg
|
||||
|
||||
type Counter struct {
|
||||
Value int
|
||||
}
|
||||
|
||||
func (c *Counter) Inc() { c.Value++ }
|
||||
|
||||
func NewCounter() *Counter { return &Counter{ 5 } }
|
||||
|
||||
In Java, the generated bindings are,
|
||||
|
||||
public abstract class Mypkg {
|
||||
public static native Counter newCounter();
|
||||
}
|
||||
|
||||
and
|
||||
|
||||
public final class Counter {
|
||||
public Counter() { ... }
|
||||
|
||||
public final long getValue();
|
||||
public final void setValue(long v);
|
||||
public void inc();
|
||||
|
||||
}
|
||||
|
||||
The package-level function newCounter can be called like so:
|
||||
|
||||
Counter c = Mypkg.newCounter()
|
||||
|
||||
For convenience, functions on the form NewT(...) *T are converted to constructors for T:
|
||||
|
||||
Counter c = new Counter()
|
||||
|
||||
Both forms returns a Java Counter, which is a proxy for a Go *Counter. Calling the inc, getValue and
|
||||
setValue methods will call the Go implementations of these methods.
|
||||
|
||||
Similarly, the same Go package will generate the Objective-C interface
|
||||
|
||||
@class GoMypkgCounter;
|
||||
|
||||
@interface GoMypkgCounter : NSObject {
|
||||
}
|
||||
|
||||
@property(strong, readonly) id ref;
|
||||
- (void)inc;
|
||||
- (int64_t)value;
|
||||
- (void)setValue:(int64_t)v;
|
||||
@end
|
||||
|
||||
FOUNDATION_EXPORT GoMypkgCounter* GoMypkgNewCounter(void);
|
||||
|
||||
The equivalent of calling newCounter in Go is GoMypkgNewCounter in Objective-C.
|
||||
The returned GoMypkgCounter* holds a reference to an underlying Go
|
||||
*Counter.
|
||||
|
||||
# Passing target language objects to Go
|
||||
|
||||
For a Go interface:
|
||||
|
||||
package myfmt
|
||||
|
||||
type Printer interface {
|
||||
Print(s string)
|
||||
}
|
||||
|
||||
func PrintHello(p Printer) {
|
||||
p.Print("Hello, World!")
|
||||
}
|
||||
|
||||
gobind generates a Java interface that can be used to implement a Printer:
|
||||
|
||||
public abstract class Myfmt {
|
||||
public static void printHello(Printer p0);
|
||||
}
|
||||
|
||||
and
|
||||
|
||||
public interface Printer {
|
||||
public void print(String s);
|
||||
}
|
||||
|
||||
You can implement Printer, and pass it to Go using the printHello
|
||||
package function:
|
||||
|
||||
public class SysPrint implements Printer {
|
||||
public void print(String s) {
|
||||
System.out.println(s);
|
||||
}
|
||||
}
|
||||
|
||||
The Java implementation can be used like so:
|
||||
|
||||
Printer printer = new SysPrint();
|
||||
Myfmt.printHello(printer);
|
||||
|
||||
For Objective-C binding, gobind generates a protocol that declares
|
||||
methods corresponding to Go interface's methods.
|
||||
|
||||
@protocol GoMyfmtPrinter
|
||||
- (void)Print:(NSString*)s;
|
||||
@end
|
||||
|
||||
FOUNDATION_EXPORT void GoMyfmtPrintHello(id<GoMyfmtPrinter> p0);
|
||||
|
||||
Any Objective-C classes conforming to the GoMyfmtPrinter protocol can be
|
||||
passed to Go using the GoMyfmtPrintHello function:
|
||||
|
||||
@interface SysPrint : NSObject<GoMyfmtPrinter> {
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation SysPrint {
|
||||
}
|
||||
- (void)Print:(NSString*)s {
|
||||
NSLog("%@", s);
|
||||
}
|
||||
@end
|
||||
|
||||
The Objective-C implementation can be used like so:
|
||||
|
||||
SysPrint* printer = [[SysPrint alloc] init];
|
||||
GoMyfmtPrintHello(printer);
|
||||
|
||||
# Type restrictions
|
||||
|
||||
At present, only a subset of Go types are supported.
|
||||
|
||||
All exported symbols in the package must have types that are supported.
|
||||
Supported types include:
|
||||
|
||||
- Signed integer and floating point types.
|
||||
|
||||
- String and boolean types.
|
||||
|
||||
- Byte slice types. Note that byte slices are passed by reference,
|
||||
and support mutation.
|
||||
|
||||
- Any function type all of whose parameters and results have
|
||||
supported types. Functions must return either no results,
|
||||
one result, or two results where the type of the second is
|
||||
the built-in 'error' type.
|
||||
|
||||
- Any interface type, all of whose exported methods have
|
||||
supported function types.
|
||||
|
||||
- Any struct type, all of whose exported methods have
|
||||
supported function types and all of whose exported fields
|
||||
have supported types.
|
||||
|
||||
Unexported symbols have no effect on the cross-language interface, and
|
||||
as such are not restricted.
|
||||
|
||||
The set of supported types will eventually be expanded to cover more
|
||||
Go types, but this is a work in progress.
|
||||
|
||||
Exceptions and panics are not yet supported. If either pass a language
|
||||
boundary, the program will exit.
|
||||
|
||||
# Reverse bindings
|
||||
|
||||
Gobind also supports accessing API from Java or Objective C from Go.
|
||||
Similar to how Cgo supports the magic "C" import, gobind recognizes
|
||||
import statements that start with "Java/" or "ObjC/". For example,
|
||||
to import java.lang.System and call the static method currentTimeMillis:
|
||||
|
||||
import "Java/java/lang/System"
|
||||
|
||||
t := System.CurrentTimeMillis()
|
||||
|
||||
Similarly, to import NSDate and call the static method [NSDate date]:
|
||||
|
||||
import "ObjC/Foundation/NSDate"
|
||||
|
||||
d := NSDate.Date()
|
||||
|
||||
Gobind also supports specifying particular classes, interfaces or
|
||||
protocols a particular Go struct should extend or implement. For example,
|
||||
to create an Android Activity subclass MainActivity:
|
||||
|
||||
import "Java/android/app/Activity"
|
||||
|
||||
type MainActivity struct {
|
||||
app.Activity
|
||||
}
|
||||
|
||||
Gobind also recognizes Java interfaces as well as Objective C classes and
|
||||
protocols the same way.
|
||||
|
||||
For more details on binding the native API, see the design proposals,
|
||||
https://golang.org/issues/16876 (Java) and https://golang.org/issues/17102
|
||||
(Objective C).
|
||||
|
||||
# Avoid reference cycles
|
||||
|
||||
The language bindings maintain a reference to each object that has been
|
||||
proxied. When a proxy object becomes unreachable, its finalizer reports
|
||||
this fact to the object's native side, so that the reference can be
|
||||
removed, potentially allowing the object to be reclaimed by its native
|
||||
garbage collector. The mechanism is symmetric.
|
||||
|
||||
However, it is possible to create a reference cycle between Go and
|
||||
objects in target languages, via proxies, meaning objects cannot be
|
||||
collected. This causes a memory leak.
|
||||
|
||||
For example, in Java: if a Go object G holds a reference to the Go
|
||||
proxy of a Java object J, and J holds a reference to the Java proxy
|
||||
of G, then the language bindings on each side must keep G and J live
|
||||
even if they are otherwise unreachable.
|
||||
|
||||
We recommend that implementations of foreign interfaces do not hold
|
||||
references to proxies of objects. That is: if you implement a Go
|
||||
interface in Java, do not store an instance of Seq.Object inside it.
|
||||
|
||||
# Further reading
|
||||
|
||||
Examples can be found in http://golang.org/x/mobile/example.
|
||||
|
||||
Design doc: http://golang.org/s/gobind
|
||||
*/
|
||||
package main
|
391
cmd/gobind/gen.go
Normal file
391
cmd/gobind/gen.go
Normal file
|
@ -0,0 +1,391 @@
|
|||
// Copyright 2014 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 (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
|
||||
"golang.org/x/mobile/bind"
|
||||
"golang.org/x/mobile/internal/importers"
|
||||
"golang.org/x/mobile/internal/importers/java"
|
||||
"golang.org/x/mobile/internal/importers/objc"
|
||||
"golang.org/x/tools/go/packages"
|
||||
)
|
||||
|
||||
func genPkg(lang string, p *types.Package, astFiles []*ast.File, allPkg []*types.Package, classes []*java.Class, otypes []*objc.Named) {
|
||||
fname := defaultFileName(lang, p)
|
||||
conf := &bind.GeneratorConfig{
|
||||
Fset: fset,
|
||||
Pkg: p,
|
||||
AllPkg: allPkg,
|
||||
}
|
||||
var pname string
|
||||
if p != nil {
|
||||
pname = p.Name()
|
||||
} else {
|
||||
pname = "universe"
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
generator := &bind.Generator{
|
||||
Printer: &bind.Printer{Buf: &buf, IndentEach: []byte("\t")},
|
||||
Fset: conf.Fset,
|
||||
AllPkg: conf.AllPkg,
|
||||
Pkg: conf.Pkg,
|
||||
Files: astFiles,
|
||||
}
|
||||
switch lang {
|
||||
case "java":
|
||||
g := &bind.JavaGen{
|
||||
JavaPkg: *javaPkg,
|
||||
Generator: generator,
|
||||
}
|
||||
g.Init(classes)
|
||||
|
||||
pkgname := bind.JavaPkgName(*javaPkg, p)
|
||||
pkgDir := strings.Replace(pkgname, ".", "/", -1)
|
||||
buf.Reset()
|
||||
w, closer := writer(filepath.Join("java", pkgDir, fname))
|
||||
processErr(g.GenJava())
|
||||
io.Copy(w, &buf)
|
||||
closer()
|
||||
for i, name := range g.ClassNames() {
|
||||
buf.Reset()
|
||||
w, closer := writer(filepath.Join("java", pkgDir, name+".java"))
|
||||
processErr(g.GenClass(i))
|
||||
io.Copy(w, &buf)
|
||||
closer()
|
||||
}
|
||||
buf.Reset()
|
||||
w, closer = writer(filepath.Join("src", "gobind", pname+"_android.c"))
|
||||
processErr(g.GenC())
|
||||
io.Copy(w, &buf)
|
||||
closer()
|
||||
buf.Reset()
|
||||
w, closer = writer(filepath.Join("src", "gobind", pname+"_android.h"))
|
||||
processErr(g.GenH())
|
||||
io.Copy(w, &buf)
|
||||
closer()
|
||||
// Generate support files along with the universe package
|
||||
if p == nil {
|
||||
dir, err := packageDir("golang.org/x/mobile/bind")
|
||||
if err != nil {
|
||||
errorf(`"golang.org/x/mobile/bind" is not found; run go get golang.org/x/mobile/bind: %v`, err)
|
||||
return
|
||||
}
|
||||
repo := filepath.Clean(filepath.Join(dir, "..")) // golang.org/x/mobile directory.
|
||||
for _, javaFile := range []string{"Seq.java"} {
|
||||
src := filepath.Join(repo, "bind/java/"+javaFile)
|
||||
in, err := os.Open(src)
|
||||
if err != nil {
|
||||
errorf("failed to open Java support file: %v", err)
|
||||
}
|
||||
defer in.Close()
|
||||
w, closer := writer(filepath.Join("java", "go", javaFile))
|
||||
defer closer()
|
||||
if _, err := io.Copy(w, in); err != nil {
|
||||
errorf("failed to copy Java support file: %v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
// Copy support files
|
||||
if err != nil {
|
||||
errorf("unable to import bind/java: %v", err)
|
||||
return
|
||||
}
|
||||
javaDir, err := packageDir("golang.org/x/mobile/bind/java")
|
||||
if err != nil {
|
||||
errorf("unable to import bind/java: %v", err)
|
||||
return
|
||||
}
|
||||
copyFile(filepath.Join("src", "gobind", "seq_android.c"), filepath.Join(javaDir, "seq_android.c.support"))
|
||||
copyFile(filepath.Join("src", "gobind", "seq_android.go"), filepath.Join(javaDir, "seq_android.go.support"))
|
||||
copyFile(filepath.Join("src", "gobind", "seq_android.h"), filepath.Join(javaDir, "seq_android.h"))
|
||||
}
|
||||
case "go":
|
||||
w, closer := writer(filepath.Join("src", "gobind", fname))
|
||||
conf.Writer = w
|
||||
processErr(bind.GenGo(conf))
|
||||
closer()
|
||||
w, closer = writer(filepath.Join("src", "gobind", pname+".h"))
|
||||
genPkgH(w, pname)
|
||||
io.Copy(w, &buf)
|
||||
closer()
|
||||
w, closer = writer(filepath.Join("src", "gobind", "seq.h"))
|
||||
genPkgH(w, "seq")
|
||||
io.Copy(w, &buf)
|
||||
closer()
|
||||
dir, err := packageDir("golang.org/x/mobile/bind")
|
||||
if err != nil {
|
||||
errorf("unable to import bind: %v", err)
|
||||
return
|
||||
}
|
||||
copyFile(filepath.Join("src", "gobind", "seq.go"), filepath.Join(dir, "seq.go.support"))
|
||||
case "objc":
|
||||
g := &bind.ObjcGen{
|
||||
Generator: generator,
|
||||
Prefix: *prefix,
|
||||
}
|
||||
g.Init(otypes)
|
||||
w, closer := writer(filepath.Join("src", "gobind", pname+"_darwin.h"))
|
||||
processErr(g.GenGoH())
|
||||
io.Copy(w, &buf)
|
||||
closer()
|
||||
hname := strings.Title(fname[:len(fname)-2]) + ".objc.h"
|
||||
w, closer = writer(filepath.Join("src", "gobind", hname))
|
||||
processErr(g.GenH())
|
||||
io.Copy(w, &buf)
|
||||
closer()
|
||||
mname := strings.Title(fname[:len(fname)-2]) + "_darwin.m"
|
||||
w, closer = writer(filepath.Join("src", "gobind", mname))
|
||||
conf.Writer = w
|
||||
processErr(g.GenM())
|
||||
io.Copy(w, &buf)
|
||||
closer()
|
||||
if p == nil {
|
||||
// Copy support files
|
||||
dir, err := packageDir("golang.org/x/mobile/bind/objc")
|
||||
if err != nil {
|
||||
errorf("unable to import bind/objc: %v", err)
|
||||
return
|
||||
}
|
||||
copyFile(filepath.Join("src", "gobind", "seq_darwin.m"), filepath.Join(dir, "seq_darwin.m.support"))
|
||||
copyFile(filepath.Join("src", "gobind", "seq_darwin.go"), filepath.Join(dir, "seq_darwin.go.support"))
|
||||
copyFile(filepath.Join("src", "gobind", "ref.h"), filepath.Join(dir, "ref.h"))
|
||||
copyFile(filepath.Join("src", "gobind", "seq_darwin.h"), filepath.Join(dir, "seq_darwin.h"))
|
||||
}
|
||||
default:
|
||||
errorf("unknown target language: %q", lang)
|
||||
}
|
||||
}
|
||||
|
||||
func genPkgH(w io.Writer, pname string) {
|
||||
fmt.Fprintf(w, `// Code generated by gobind. DO NOT EDIT.
|
||||
|
||||
#ifdef __GOBIND_ANDROID__
|
||||
#include "%[1]s_android.h"
|
||||
#endif
|
||||
#ifdef __GOBIND_DARWIN__
|
||||
#include "%[1]s_darwin.h"
|
||||
#endif`, pname)
|
||||
}
|
||||
|
||||
func genObjcPackages(dir string, types []*objc.Named, embedders []importers.Struct) error {
|
||||
var buf bytes.Buffer
|
||||
cg := &bind.ObjcWrapper{
|
||||
Printer: &bind.Printer{
|
||||
IndentEach: []byte("\t"),
|
||||
Buf: &buf,
|
||||
},
|
||||
}
|
||||
var genNames []string
|
||||
for _, emb := range embedders {
|
||||
genNames = append(genNames, emb.Name)
|
||||
}
|
||||
cg.Init(types, genNames)
|
||||
for i, opkg := range cg.Packages() {
|
||||
pkgDir := filepath.Join(dir, "src", "ObjC", opkg)
|
||||
if err := os.MkdirAll(pkgDir, 0700); err != nil {
|
||||
return err
|
||||
}
|
||||
pkgFile := filepath.Join(pkgDir, "package.go")
|
||||
buf.Reset()
|
||||
cg.GenPackage(i)
|
||||
if err := os.WriteFile(pkgFile, buf.Bytes(), 0600); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
buf.Reset()
|
||||
cg.GenInterfaces()
|
||||
objcBase := filepath.Join(dir, "src", "ObjC")
|
||||
if err := os.MkdirAll(objcBase, 0700); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := os.WriteFile(filepath.Join(objcBase, "interfaces.go"), buf.Bytes(), 0600); err != nil {
|
||||
return err
|
||||
}
|
||||
goBase := filepath.Join(dir, "src", "gobind")
|
||||
if err := os.MkdirAll(goBase, 0700); err != nil {
|
||||
return err
|
||||
}
|
||||
buf.Reset()
|
||||
cg.GenGo()
|
||||
if err := os.WriteFile(filepath.Join(goBase, "interfaces_darwin.go"), buf.Bytes(), 0600); err != nil {
|
||||
return err
|
||||
}
|
||||
buf.Reset()
|
||||
cg.GenH()
|
||||
if err := os.WriteFile(filepath.Join(goBase, "interfaces.h"), buf.Bytes(), 0600); err != nil {
|
||||
return err
|
||||
}
|
||||
buf.Reset()
|
||||
cg.GenM()
|
||||
if err := os.WriteFile(filepath.Join(goBase, "interfaces_darwin.m"), buf.Bytes(), 0600); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func genJavaPackages(dir string, classes []*java.Class, embedders []importers.Struct) error {
|
||||
var buf bytes.Buffer
|
||||
cg := &bind.ClassGen{
|
||||
JavaPkg: *javaPkg,
|
||||
Printer: &bind.Printer{
|
||||
IndentEach: []byte("\t"),
|
||||
Buf: &buf,
|
||||
},
|
||||
}
|
||||
cg.Init(classes, embedders)
|
||||
for i, jpkg := range cg.Packages() {
|
||||
pkgDir := filepath.Join(dir, "src", "Java", jpkg)
|
||||
if err := os.MkdirAll(pkgDir, 0700); err != nil {
|
||||
return err
|
||||
}
|
||||
pkgFile := filepath.Join(pkgDir, "package.go")
|
||||
buf.Reset()
|
||||
cg.GenPackage(i)
|
||||
if err := os.WriteFile(pkgFile, buf.Bytes(), 0600); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
buf.Reset()
|
||||
cg.GenInterfaces()
|
||||
javaBase := filepath.Join(dir, "src", "Java")
|
||||
if err := os.MkdirAll(javaBase, 0700); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := os.WriteFile(filepath.Join(javaBase, "interfaces.go"), buf.Bytes(), 0600); err != nil {
|
||||
return err
|
||||
}
|
||||
goBase := filepath.Join(dir, "src", "gobind")
|
||||
if err := os.MkdirAll(goBase, 0700); err != nil {
|
||||
return err
|
||||
}
|
||||
buf.Reset()
|
||||
cg.GenGo()
|
||||
if err := os.WriteFile(filepath.Join(goBase, "classes_android.go"), buf.Bytes(), 0600); err != nil {
|
||||
return err
|
||||
}
|
||||
buf.Reset()
|
||||
cg.GenH()
|
||||
if err := os.WriteFile(filepath.Join(goBase, "classes.h"), buf.Bytes(), 0600); err != nil {
|
||||
return err
|
||||
}
|
||||
buf.Reset()
|
||||
cg.GenC()
|
||||
if err := os.WriteFile(filepath.Join(goBase, "classes_android.c"), buf.Bytes(), 0600); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func processErr(err error) {
|
||||
if err != nil {
|
||||
if list, _ := err.(bind.ErrorList); len(list) > 0 {
|
||||
for _, err := range list {
|
||||
errorf("%v", err)
|
||||
}
|
||||
} else {
|
||||
errorf("%v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var fset = token.NewFileSet()
|
||||
|
||||
func writer(fname string) (w io.Writer, closer func()) {
|
||||
if *outdir == "" {
|
||||
return os.Stdout, func() { return }
|
||||
}
|
||||
|
||||
name := filepath.Join(*outdir, fname)
|
||||
dir := filepath.Dir(name)
|
||||
if err := os.MkdirAll(dir, 0755); err != nil {
|
||||
errorf("invalid output dir: %v", err)
|
||||
os.Exit(exitStatus)
|
||||
}
|
||||
|
||||
f, err := os.Create(name)
|
||||
if err != nil {
|
||||
errorf("invalid output dir: %v", err)
|
||||
os.Exit(exitStatus)
|
||||
}
|
||||
closer = func() {
|
||||
if err := f.Close(); err != nil {
|
||||
errorf("error in closing output file: %v", err)
|
||||
}
|
||||
}
|
||||
return f, closer
|
||||
}
|
||||
|
||||
func copyFile(dst, src string) {
|
||||
w, closer := writer(dst)
|
||||
f, err := os.Open(src)
|
||||
if err != nil {
|
||||
errorf("unable to open file: %v", err)
|
||||
closer()
|
||||
os.Exit(exitStatus)
|
||||
}
|
||||
if _, err := io.Copy(w, f); err != nil {
|
||||
errorf("unable to copy file: %v", err)
|
||||
f.Close()
|
||||
closer()
|
||||
os.Exit(exitStatus)
|
||||
}
|
||||
f.Close()
|
||||
closer()
|
||||
}
|
||||
|
||||
func defaultFileName(lang string, pkg *types.Package) string {
|
||||
switch lang {
|
||||
case "java":
|
||||
if pkg == nil {
|
||||
return "Universe.java"
|
||||
}
|
||||
firstRune, size := utf8.DecodeRuneInString(pkg.Name())
|
||||
className := string(unicode.ToUpper(firstRune)) + pkg.Name()[size:]
|
||||
return className + ".java"
|
||||
case "go":
|
||||
if pkg == nil {
|
||||
return "go_main.go"
|
||||
}
|
||||
return "go_" + pkg.Name() + "main.go"
|
||||
case "objc":
|
||||
if pkg == nil {
|
||||
return "Universe.m"
|
||||
}
|
||||
firstRune, size := utf8.DecodeRuneInString(pkg.Name())
|
||||
className := string(unicode.ToUpper(firstRune)) + pkg.Name()[size:]
|
||||
return *prefix + className + ".m"
|
||||
}
|
||||
errorf("unknown target language: %q", lang)
|
||||
os.Exit(exitStatus)
|
||||
return ""
|
||||
}
|
||||
|
||||
func packageDir(path string) (string, error) {
|
||||
mode := packages.NeedFiles
|
||||
pkgs, err := packages.Load(&packages.Config{Mode: mode}, path)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if len(pkgs) == 0 || len(pkgs[0].GoFiles) == 0 {
|
||||
return "", fmt.Errorf("no Go package in %v", path)
|
||||
}
|
||||
pkg := pkgs[0]
|
||||
if len(pkg.Errors) > 0 {
|
||||
return "", fmt.Errorf("%v", pkg.Errors)
|
||||
}
|
||||
return filepath.Dir(pkg.GoFiles[0]), nil
|
||||
}
|
218
cmd/gobind/gobind_test.go
Normal file
218
cmd/gobind/gobind_test.go
Normal file
|
@ -0,0 +1,218 @@
|
|||
// Copyright 2016 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 (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/tools/go/packages/packagestest"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
// To avoid recompiling the gobind command (and to support compiler options
|
||||
// like -race and -coverage), allow the test binary itself to re-exec itself
|
||||
// as the gobind command by setting an environment variable.
|
||||
if os.Getenv("GOBIND_TEST_IS_GOBIND") != "" {
|
||||
main()
|
||||
os.Exit(0)
|
||||
}
|
||||
os.Setenv("GOBIND_TEST_IS_GOBIND", "1")
|
||||
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
||||
var tests = []struct {
|
||||
name string
|
||||
lang string
|
||||
pkg string
|
||||
goos string
|
||||
// reverse is true if the test needs to generate reverse bindings using
|
||||
// external tools such as javap.
|
||||
reverse bool
|
||||
}{
|
||||
{
|
||||
name: "ObjC-Testpkg",
|
||||
lang: "objc",
|
||||
pkg: "golang.org/x/mobile/bind/testdata/testpkg",
|
||||
},
|
||||
{
|
||||
name: "Java-Testpkg",
|
||||
lang: "java",
|
||||
pkg: "golang.org/x/mobile/bind/testdata/testpkg",
|
||||
},
|
||||
{
|
||||
name: "Go-Testpkg",
|
||||
lang: "go",
|
||||
pkg: "golang.org/x/mobile/bind/testdata/testpkg",
|
||||
},
|
||||
{
|
||||
name: "Java-Javapkg",
|
||||
lang: "java",
|
||||
pkg: "golang.org/x/mobile/bind/testdata/testpkg/javapkg",
|
||||
goos: "android",
|
||||
reverse: true,
|
||||
},
|
||||
{
|
||||
name: "Go-Javapkg",
|
||||
lang: "go",
|
||||
pkg: "golang.org/x/mobile/bind/testdata/testpkg/javapkg",
|
||||
goos: "android",
|
||||
reverse: true,
|
||||
},
|
||||
{
|
||||
name: "Go-Cgopkg",
|
||||
lang: "go,java,objc",
|
||||
pkg: "golang.org/x/mobile/bind/testdata/cgopkg",
|
||||
goos: "android",
|
||||
},
|
||||
}
|
||||
|
||||
func mustHaveBindTestdata(t testing.TB) {
|
||||
switch runtime.GOOS {
|
||||
case "android", "ios":
|
||||
t.Skipf("skipping: test cannot access ../../bind/testdata on %s/%s", runtime.GOOS, runtime.GOARCH)
|
||||
}
|
||||
}
|
||||
|
||||
func gobindBin(t testing.TB) string {
|
||||
switch runtime.GOOS {
|
||||
case "js", "ios":
|
||||
t.Skipf("skipping: cannot exec subprocess on %s/%s", runtime.GOOS, runtime.GOARCH)
|
||||
}
|
||||
|
||||
p, err := os.Executable()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
func runGobind(t testing.TB, lang, pkg, goos string, exported *packagestest.Exported) error {
|
||||
cmd := exec.Command(gobindBin(t), "-lang", lang, pkg)
|
||||
cmd.Dir = exported.Config.Dir
|
||||
cmd.Env = exported.Config.Env
|
||||
if goos != "" {
|
||||
// Add CGO_ENABLED=1 explicitly since Cgo is disabled when GOOS is different from host OS.
|
||||
cmd.Env = append(cmd.Env, "GOOS="+goos, "CGO_ENABLED=1")
|
||||
}
|
||||
stderr := new(strings.Builder)
|
||||
cmd.Stderr = stderr
|
||||
stdout := new(strings.Builder)
|
||||
cmd.Stdout = stdout
|
||||
err := cmd.Run()
|
||||
if testing.Verbose() && stdout.Len() > 0 {
|
||||
t.Logf("stdout (%v):\n%s", cmd, stderr)
|
||||
}
|
||||
if stderr.Len() > 0 {
|
||||
t.Logf("stderr (%v):\n%s", cmd, stderr)
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("%v: %w", cmd, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestGobind(t *testing.T) { packagestest.TestAll(t, testGobind) }
|
||||
func testGobind(t *testing.T, exporter packagestest.Exporter) {
|
||||
mustHaveBindTestdata(t)
|
||||
|
||||
_, javapErr := exec.LookPath("javap")
|
||||
exported := packagestest.Export(t, exporter, []packagestest.Module{{
|
||||
Name: "golang.org/x/mobile",
|
||||
Files: packagestest.MustCopyFileTree("../.."),
|
||||
}})
|
||||
defer exported.Cleanup()
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
if exporter == packagestest.Modules && test.reverse {
|
||||
t.Skip("reverse binding does't work with Go modules")
|
||||
}
|
||||
if test.reverse && javapErr != nil {
|
||||
t.Skip("reverse bind test requires javap which is not available")
|
||||
}
|
||||
if err := runGobind(t, test.lang, test.pkg, test.goos, exported); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDocs(t *testing.T) { packagestest.TestAll(t, testDocs) }
|
||||
func testDocs(t *testing.T, exporter packagestest.Exporter) {
|
||||
mustHaveBindTestdata(t)
|
||||
|
||||
const docsrc = `
|
||||
package doctest
|
||||
|
||||
// This is a comment.
|
||||
type Struct struct{
|
||||
}`
|
||||
|
||||
exported := packagestest.Export(t, exporter, []packagestest.Module{
|
||||
{
|
||||
Name: "example.com/doctest",
|
||||
Files: map[string]interface{}{
|
||||
"doc.go": docsrc,
|
||||
},
|
||||
},
|
||||
{
|
||||
// gobind requires golang.org/x/mobile to generate code for reverse bindings.
|
||||
Name: "golang.org/x/mobile",
|
||||
Files: packagestest.MustCopyFileTree("../.."),
|
||||
},
|
||||
})
|
||||
defer exported.Cleanup()
|
||||
|
||||
const comment = "This is a comment."
|
||||
for _, lang := range []string{"java", "objc"} {
|
||||
cmd := exec.Command(gobindBin(t), "-lang", lang, "example.com/doctest")
|
||||
cmd.Dir = exported.Config.Dir
|
||||
cmd.Env = exported.Config.Env
|
||||
out, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
t.Errorf("gobind -lang %s failed: %v: %s", lang, err, out)
|
||||
continue
|
||||
}
|
||||
if bytes.Index(out, []byte(comment)) == -1 {
|
||||
t.Errorf("gobind output for language %s did not contain the comment %q", lang, comment)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkGobind(b *testing.B) {
|
||||
packagestest.BenchmarkAll(b, benchmarkGobind)
|
||||
}
|
||||
|
||||
func benchmarkGobind(b *testing.B, exporter packagestest.Exporter) {
|
||||
_, javapErr := exec.LookPath("javap")
|
||||
exported := packagestest.Export(b, exporter, []packagestest.Module{{
|
||||
Name: "golang.org/x/mobile",
|
||||
Files: packagestest.MustCopyFileTree("../.."),
|
||||
}})
|
||||
defer exported.Cleanup()
|
||||
|
||||
for _, test := range tests {
|
||||
b.Run(test.name, func(b *testing.B) {
|
||||
if exporter == packagestest.Modules && test.reverse {
|
||||
b.Skip("reverse binding does't work with Go modules")
|
||||
}
|
||||
if test.reverse && javapErr != nil {
|
||||
b.Skip("reverse bind test requires javap which is not available")
|
||||
}
|
||||
for i := 0; i < b.N; i++ {
|
||||
if err := runGobind(b, test.lang, test.pkg, test.goos, exported); err != nil {
|
||||
b.Error(err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
11
cmd/gobind/implicit.go
Normal file
11
cmd/gobind/implicit.go
Normal file
|
@ -0,0 +1,11 @@
|
|||
// This file imports implicit dependencies required by generated code.
|
||||
|
||||
//go:build mobile_implicit
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
_ "golang.org/x/mobile/bind"
|
||||
_ "golang.org/x/mobile/bind/java"
|
||||
_ "golang.org/x/mobile/bind/objc"
|
||||
)
|
168
cmd/gobind/main.go
Normal file
168
cmd/gobind/main.go
Normal file
|
@ -0,0 +1,168 @@
|
|||
// Copyright 2014 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 (
|
||||
"bytes"
|
||||
"flag"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/types"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/mobile/internal/importers"
|
||||
"golang.org/x/mobile/internal/importers/java"
|
||||
"golang.org/x/mobile/internal/importers/objc"
|
||||
"golang.org/x/tools/go/packages"
|
||||
)
|
||||
|
||||
var (
|
||||
lang = flag.String("lang", "", "target languages for bindings, either java, go, or objc. If empty, all languages are generated.")
|
||||
outdir = flag.String("outdir", "", "result will be written to the directory instead of stdout.")
|
||||
javaPkg = flag.String("javapkg", "", "custom Java package path prefix. Valid only with -lang=java.")
|
||||
prefix = flag.String("prefix", "", "custom Objective-C name prefix. Valid only with -lang=objc.")
|
||||
bootclasspath = flag.String("bootclasspath", "", "Java bootstrap classpath.")
|
||||
classpath = flag.String("classpath", "", "Java classpath.")
|
||||
tags = flag.String("tags", "", "build tags.")
|
||||
)
|
||||
|
||||
var usage = `The Gobind tool generates Java language bindings for Go.
|
||||
|
||||
For usage details, see doc.go.`
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
run()
|
||||
os.Exit(exitStatus)
|
||||
}
|
||||
|
||||
func run() {
|
||||
var langs []string
|
||||
if *lang != "" {
|
||||
langs = strings.Split(*lang, ",")
|
||||
} else {
|
||||
langs = []string{"go", "java", "objc"}
|
||||
}
|
||||
|
||||
// We need to give appropriate environment variables like CC or CXX so that the returned packages no longer have errors.
|
||||
// However, getting such environment variables is difficult or impossible so far.
|
||||
// Gomobile can obtain such environment variables in env.go, but this logic assumes some condiitons gobind doesn't assume.
|
||||
cfg := &packages.Config{
|
||||
Mode: packages.NeedName | packages.NeedFiles |
|
||||
packages.NeedImports | packages.NeedDeps |
|
||||
packages.NeedTypes | packages.NeedSyntax | packages.NeedTypesInfo,
|
||||
BuildFlags: []string{"-tags", strings.Join(strings.Split(*tags, ","), " ")},
|
||||
}
|
||||
|
||||
// Call Load twice to warm the cache. There is a known issue that the result of Load
|
||||
// depends on build cache state. See golang/go#33687.
|
||||
packages.Load(cfg, flag.Args()...)
|
||||
|
||||
allPkg, err := packages.Load(cfg, flag.Args()...)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
jrefs, err := importers.AnalyzePackages(allPkg, "Java/")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
orefs, err := importers.AnalyzePackages(allPkg, "ObjC/")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
var classes []*java.Class
|
||||
if len(jrefs.Refs) > 0 {
|
||||
jimp := &java.Importer{
|
||||
Bootclasspath: *bootclasspath,
|
||||
Classpath: *classpath,
|
||||
JavaPkg: *javaPkg,
|
||||
}
|
||||
classes, err = jimp.Import(jrefs)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
var otypes []*objc.Named
|
||||
if len(orefs.Refs) > 0 {
|
||||
otypes, err = objc.Import(orefs)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
if len(classes) > 0 || len(otypes) > 0 {
|
||||
srcDir := *outdir
|
||||
if srcDir == "" {
|
||||
srcDir, err = os.MkdirTemp(os.TempDir(), "gobind-")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(srcDir)
|
||||
} else {
|
||||
srcDir, err = filepath.Abs(srcDir)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
if len(classes) > 0 {
|
||||
if err := genJavaPackages(srcDir, classes, jrefs.Embedders); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
if len(otypes) > 0 {
|
||||
if err := genObjcPackages(srcDir, otypes, orefs.Embedders); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Add a new directory to GOPATH where the file for reverse bindings exist, and recreate allPkg.
|
||||
// It is because the current allPkg did not solve imports for reverse bindings.
|
||||
var gopath string
|
||||
if out, err := exec.Command("go", "env", "GOPATH").Output(); err != nil {
|
||||
log.Fatal(err)
|
||||
} else {
|
||||
gopath = string(bytes.TrimSpace(out))
|
||||
}
|
||||
if gopath != "" {
|
||||
gopath = string(filepath.ListSeparator) + gopath
|
||||
}
|
||||
gopath = srcDir + gopath
|
||||
cfg.Env = append(os.Environ(), "GOPATH="+gopath)
|
||||
allPkg, err = packages.Load(cfg, flag.Args()...)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
typePkgs := make([]*types.Package, len(allPkg))
|
||||
astPkgs := make([][]*ast.File, len(allPkg))
|
||||
for i, pkg := range allPkg {
|
||||
// Ignore pkg.Errors. pkg.Errors can exist when Cgo is used, but this should not affect the result.
|
||||
// See the discussion at golang/go#36547.
|
||||
typePkgs[i] = pkg.Types
|
||||
astPkgs[i] = pkg.Syntax
|
||||
}
|
||||
for _, l := range langs {
|
||||
for i, pkg := range typePkgs {
|
||||
genPkg(l, pkg, astPkgs[i], typePkgs, classes, otypes)
|
||||
}
|
||||
// Generate the error package and support files
|
||||
genPkg(l, nil, nil, typePkgs, classes, otypes)
|
||||
}
|
||||
}
|
||||
|
||||
var exitStatus = 0
|
||||
|
||||
func errorf(format string, args ...interface{}) {
|
||||
fmt.Fprintf(os.Stderr, format, args...)
|
||||
fmt.Fprintln(os.Stderr)
|
||||
exitStatus = 1
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue