From e8dea067bee80745e3c437bd56589aae9e7ea2a2 Mon Sep 17 00:00:00 2001 From: David Crawshaw Date: Fri, 1 Aug 2014 10:45:18 -0400 Subject: [PATCH] go.mobile/cmd/gobind: language binding command line tool LGTM=adonovan R=adonovan CC=golang-codereviews https://golang.org/cl/120140043 --- cmd/gobind/doc.go | 172 +++++++++++++++++++++++++++++++++++++++++++++ cmd/gobind/gen.go | 87 +++++++++++++++++++++++ cmd/gobind/main.go | 44 ++++++++++++ 3 files changed, 303 insertions(+) create mode 100644 cmd/gobind/doc.go create mode 100644 cmd/gobind/gen.go create mode 100644 cmd/gobind/main.go diff --git a/cmd/gobind/doc.go b/cmd/gobind/doc.go new file mode 100644 index 0000000..7ebe9ea --- /dev/null +++ b/cmd/gobind/doc.go @@ -0,0 +1,172 @@ +// 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 code +and pass objects from Java. + +Using gobind + +Gobind takes a Go package and generates bindings for all of the exported +symbols. The exported symbols define the cross-language interface. + +The gobind tool generates both an API stub in Java, and binding code in +Go. Start with a Go package: + + package hi + + import "fmt" + + func Hello(name string) { + fmt.Println("Hello, %s!\n", name) + } + +Generate a Go binding package and Java stubs: + + go install code.google.com/p/go.mobile/cmd/gobind + gobind -lang=go github.com/crawshaw/hi > hi/go_hi/go_hi.go + gobind -lang=java github.com/crawshaw/hi > hi/Hi.java + +The generated Go package, go_hi, must be linked into your Go program: + + import _ "github.com/crawshaw/hi/go_hi" + +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. + + - 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 all 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. + +Passing Go objects to foreign languages + +Consider a type for counting: + + package mypkg + + type Counter struct { + Value int + } + + func (c *Counter) Inc() { c.Value++ } + + func New() *Counter { return &Counter{ 5 } } + +The generated bindings enable Java programs to create and use a Counter. + + public abstract class Mypkg { + private Mypkg() {} + public static final class Counter { + public void Inc() { ... } + public long GetValue() { ... } + public void SetValue(long value) { ... } + } + public static Counter New() { ... } + } + +The package-level function New can be called like so: + + Counter c = Mypkg.New() + +returns a Java Counter, which is a proxy for a Go *Counter. Calling the Inc +and Get methods will call the Go implementations of these methods. + +Passing foreign 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 stub that can be used to implement a Printer: + + public abstract class Myfmt { + private Myfmt() {} + public interface Printer { + public void Print(String s); + + public static abstract class Stub implements Printer { + ... + } + + ... + } + + public static void PrintHello(Printer p) { ... } + } + +You can extend Myfmt.Printer.Stub to implement the Printer interface, and +pass it to Go using the PrintHello package function: + + public class SysPrint extends Myfmt.Printer.Stub { + public void Print(String s) { + System.out.println(s); + } + } + +The Java implementation can be used like so: + + Myfmt.Printer printer = new SysPrint(); + Myfmt.PrintHello(printer); + +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 +Java objects, via proxies, meaning objects cannot be collected. This +causes a memory leak. + +For example, 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 extend a Stub in +Java, do not store an instance of Seq.Object inside it. + +Further reading + +Examples can be found in http://code.google.com/p/go.mobile/example. + +Design doc: http://golang.org/s/gobind +*/ +package main diff --git a/cmd/gobind/gen.go b/cmd/gobind/gen.go new file mode 100644 index 0000000..28ffa63 --- /dev/null +++ b/cmd/gobind/gen.go @@ -0,0 +1,87 @@ +// 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 ( + "os" + "path/filepath" + + "go/ast" + "go/build" + "go/parser" + "go/scanner" + "go/token" + + "code.google.com/p/go.mobile/bind" + _ "code.google.com/p/go.tools/go/gcimporter" + "code.google.com/p/go.tools/go/types" +) + +func genPkg(pkg *build.Package) { + if len(pkg.CgoFiles) > 0 { + errorf("gobind: cannot use cgo-dependent package as service definition: %s", pkg.CgoFiles[0]) + return + } + + files := parseFiles(pkg.Dir, pkg.GoFiles) + if len(files) == 0 { + return // some error has been reported + } + + conf := types.Config{ + Error: func(err error) { + errorf("%v", err) + }, + } + p, err := conf.Check(pkg.ImportPath, fset, files, nil) + if err != nil { + return // printed above + } + + switch *lang { + case "java": + err = bind.GenJava(os.Stdout, fset, p) + case "go": + err = bind.GenGo(os.Stdout, fset, p) + default: + errorf("unknown target language: %q", *lang) + } + + 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 parseFiles(dir string, filenames []string) []*ast.File { + var files []*ast.File + hasErr := false + for _, filename := range filenames { + path := filepath.Join(dir, filename) + file, err := parser.ParseFile(fset, path, nil, parser.AllErrors) + if err != nil { + hasErr = true + if list, _ := err.(scanner.ErrorList); len(list) > 0 { + for _, err := range list { + errorf("%v", err) + } + } else { + errorf("%v", err) + } + } + files = append(files, file) + } + if hasErr { + return nil + } + return files +} diff --git a/cmd/gobind/main.go b/cmd/gobind/main.go new file mode 100644 index 0000000..6866c12 --- /dev/null +++ b/cmd/gobind/main.go @@ -0,0 +1,44 @@ +// 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 ( + "flag" + "fmt" + "go/build" + "log" + "os" +) + +var lang = flag.String("lang", "java", "target language for bindings, either java or go") + +var usage = `The Gobind tool generates Java language bindings for Go. + +For usage details, see doc.go.` + +func main() { + flag.Parse() + + cwd, err := os.Getwd() + if err != nil { + log.Fatal(err) + } + for _, arg := range flag.Args() { + pkg, err := build.Import(arg, cwd, 0) + if err != nil { + fmt.Fprintf(os.Stderr, "%s: %v", arg, err) + os.Exit(1) + } + genPkg(pkg) + } + os.Exit(exitStatus) +} + +var exitStatus = 0 + +func errorf(format string, args ...interface{}) { + fmt.Fprintf(os.Stderr, format, args...) + exitStatus = 1 +}