bind,cmd: generate ObjC and Java documentation

Convert Go documentation to JavaDoc tags (/** ... */).

Since the .aar file format doesn't support source files, gomobile
will create a package-sources.jar along with the main package.aar.

For Objective-C, JavaDoc-style comments seems to work as well,
judging by manual inspection of Xcode quick help.

Change-Id: I47fe5b6804681d459a873be37a44610d392166ef
Reviewed-on: https://go-review.googlesource.com/52330
Reviewed-by: Hyang-Ah Hana Kim <hyangah@gmail.com>
diff --git a/bind/bind_test.go b/bind/bind_test.go
index ed95611..777588e 100644
--- a/bind/bind_test.go
+++ b/bind/bind_test.go
@@ -43,6 +43,7 @@
 	"testdata/try.go",
 	"testdata/vars.go",
 	"testdata/ignore.go",
+	"testdata/doc.go",
 }
 
 var javaTests = []string{
@@ -73,8 +74,8 @@
 	return refs
 }
 
-func typeCheck(t *testing.T, filename string, gopath string) *types.Package {
-	f, err := parser.ParseFile(fset, filename, nil, parser.AllErrors)
+func typeCheck(t *testing.T, filename string, gopath string) (*types.Package, *ast.Package) {
+	f, err := parser.ParseFile(fset, filename, nil, parser.AllErrors|parser.ParseComments)
 	if err != nil {
 		t.Fatalf("%s: %v", filename, err)
 	}
@@ -97,7 +98,9 @@
 	if err != nil {
 		t.Fatal(err)
 	}
-	return pkg
+	// Ignore errors (probably from unknown imports): we only need the package documentation.
+	astPkg, _ := ast.NewPackage(fset, map[string]*ast.File{filename: f}, nil, nil)
+	return pkg, astPkg
 }
 
 // diff runs the command "diff a b" and returns its output
@@ -133,8 +136,9 @@
 func TestGenObjc(t *testing.T) {
 	for _, filename := range tests {
 		var pkg *types.Package
+		var astPkg *ast.Package
 		if filename != "" {
-			pkg = typeCheck(t, filename, "")
+			pkg, astPkg = typeCheck(t, filename, "")
 		}
 
 		var buf bytes.Buffer
@@ -142,6 +146,7 @@
 			Generator: &Generator{
 				Printer: &Printer{Buf: &buf, IndentEach: []byte("\t")},
 				Fset:    fset,
+				AST:     astPkg,
 				Pkg:     pkg,
 			},
 		}
@@ -280,6 +285,7 @@
 	}
 	for _, filename := range allTests {
 		var pkg *types.Package
+		var astPkg *ast.Package
 		var buf bytes.Buffer
 		var cg *ClassGen
 		var classes []*java.Class
@@ -308,12 +314,13 @@
 				genJavaPackages(t, tmpGopath, cg)
 				cg.Buf = &buf
 			}
-			pkg = typeCheck(t, filename, tmpGopath)
+			pkg, astPkg = typeCheck(t, filename, tmpGopath)
 		}
 		g := &JavaGen{
 			Generator: &Generator{
 				Printer: &Printer{Buf: &buf, IndentEach: []byte("    ")},
 				Fset:    fset,
+				AST:     astPkg,
 				Pkg:     pkg,
 			},
 		}
@@ -391,7 +398,7 @@
 		var buf bytes.Buffer
 		var pkg *types.Package
 		if filename != "" {
-			pkg = typeCheck(t, filename, "")
+			pkg, _ = typeCheck(t, filename, "")
 		}
 		testGenGo(t, filename, &buf, pkg)
 	}
@@ -422,7 +429,7 @@
 		}
 		cg.Init(classes, refs.Embedders)
 		genJavaPackages(t, tmpGopath, cg)
-		pkg := typeCheck(t, filename, tmpGopath)
+		pkg, _ := typeCheck(t, filename, tmpGopath)
 		cg.GenGo()
 		testGenGo(t, filename, &buf, pkg)
 	}
@@ -456,7 +463,7 @@
 		}
 		cg.Init(types, genNames)
 		genObjcPackages(t, tmpGopath, cg)
-		pkg := typeCheck(t, filename, tmpGopath)
+		pkg, _ := typeCheck(t, filename, tmpGopath)
 		cg.GenGo()
 		testGenGo(t, filename, &buf, pkg)
 	}
@@ -496,7 +503,7 @@
 
 func TestCustomPrefix(t *testing.T) {
 	const datafile = "testdata/customprefix.go"
-	pkg := typeCheck(t, datafile, "")
+	pkg, astPkg := typeCheck(t, datafile, "")
 
 	type testCase struct {
 		golden string
@@ -509,6 +516,7 @@
 			Printer: &Printer{Buf: &buf, IndentEach: []byte("    ")},
 			Fset:    fset,
 			AllPkg:  []*types.Package{pkg},
+			AST:     astPkg,
 			Pkg:     pkg,
 		},
 	}
diff --git a/bind/gen.go b/bind/gen.go
index 546c5cf..0670f5a 100644
--- a/bind/gen.go
+++ b/bind/gen.go
@@ -7,6 +7,7 @@
 import (
 	"bytes"
 	"fmt"
+	"go/ast"
 	"go/token"
 	"go/types"
 	"io"
@@ -80,6 +81,7 @@
 	*Printer
 	Fset   *token.FileSet
 	AllPkg []*types.Package
+	AST    *ast.Package
 	Pkg    *types.Package
 	err    ErrorList
 
@@ -95,6 +97,17 @@
 	otherNames []*types.TypeName
 	// allIntf contains interfaces from all bound packages.
 	allIntf []interfaceInfo
+
+	docs pkgDocs
+}
+
+// A pkgDocs maps identifier names to its extracted documentation for a package.
+type pkgDocs map[string]*pkgDoc
+
+type pkgDoc struct {
+	doc string
+	// Struct or interface fields and methods.
+	members map[string]string
 }
 
 // pkgPrefix returns a prefix that disambiguates symbol names for binding
@@ -118,6 +131,7 @@
 	g.pkgPrefix = pkgPrefix(g.Pkg)
 
 	if g.Pkg != nil {
+		g.parseDocs()
 		scope := g.Pkg.Scope()
 		hasExported := false
 		for _, name := range scope.Names() {
@@ -175,6 +189,128 @@
 	}
 }
 
+func (d pkgDocs) Visit(n ast.Node) ast.Visitor {
+	switch n := n.(type) {
+	case *ast.GenDecl:
+		outerDoc := n.Doc
+		for _, spec := range n.Specs {
+			switch t := spec.(type) {
+			case *ast.TypeSpec:
+				d.addType(t, outerDoc)
+			case *ast.ValueSpec:
+				d.addValue(t, outerDoc)
+			}
+		}
+		return nil
+	case *ast.FuncDecl:
+		d.addFunc(n)
+		return nil
+	default:
+		return d
+	}
+}
+
+func (d pkgDocs) addValue(t *ast.ValueSpec, outerDoc *ast.CommentGroup) {
+	for _, n := range t.Names {
+		doc := t.Doc
+		if doc == nil {
+			doc = outerDoc
+		}
+		if doc != nil {
+			d[n.Name] = &pkgDoc{doc: doc.Text()}
+		}
+	}
+}
+
+func (d pkgDocs) addFunc(f *ast.FuncDecl) {
+	doc := f.Doc
+	if doc == nil {
+		return
+	}
+	fn := f.Name.Name
+	if r := f.Recv; r != nil {
+		// f is a method.
+		switch t := r.List[0].Type.(type) {
+		case *ast.Ident:
+			d.addMethod(t.Name, fn, doc.Text())
+		case *ast.StarExpr:
+			sname := t.X.(*ast.Ident).Name
+			d.addMethod(sname, fn, doc.Text())
+		}
+	} else {
+		// f is a function.
+		d[fn] = &pkgDoc{doc: doc.Text()}
+	}
+}
+
+func (d pkgDocs) addType(t *ast.TypeSpec, outerDoc *ast.CommentGroup) {
+	doc := t.Doc
+	if doc == nil {
+		doc = outerDoc
+	}
+	pd, exists := d[t.Name.Name]
+	if !exists {
+		pd = &pkgDoc{members: make(map[string]string)}
+		d[t.Name.Name] = pd
+	}
+	if doc != nil {
+		pd.doc = doc.Text()
+	}
+	var fields *ast.FieldList
+	switch t := t.Type.(type) {
+	case *ast.StructType:
+		fields = t.Fields
+	case *ast.InterfaceType:
+		fields = t.Methods
+	}
+	if fields != nil {
+		for _, field := range fields.List {
+			if field.Doc != nil {
+				pd.members[field.Names[0].Name] = field.Doc.Text()
+			}
+		}
+	}
+}
+
+func (d pkgDocs) addMethod(sname, fname, doc string) {
+	pd, exists := d[sname]
+	if !exists {
+		pd = &pkgDoc{members: make(map[string]string)}
+		d[sname] = pd
+	}
+	pd.members[fname] = doc
+}
+
+// parseDocs extracts documentation from a package in a form useful for lookups.
+func (g *Generator) parseDocs() {
+	g.docs = make(pkgDocs)
+	if g.AST != nil {
+		ast.Walk(g.docs, g.AST)
+	}
+}
+
+func (d *pkgDoc) Visit(n ast.Node) ast.Visitor {
+	switch n := n.(type) {
+	case *ast.Field:
+		d.members[n.Names[0].Name] = n.Doc.Text()
+	}
+	return d
+}
+
+func (d *pkgDoc) Doc() string {
+	if d == nil {
+		return ""
+	}
+	return d.doc
+}
+
+func (d *pkgDoc) Member(n string) string {
+	if d == nil {
+		return ""
+	}
+	return d.members[n]
+}
+
 // constructorType returns the type T for a function of the forms:
 //
 // func NewT...(...) *T
diff --git a/bind/genjava.go b/bind/genjava.go
index ee45c20..dd3cf18 100644
--- a/bind/genjava.go
+++ b/bind/genjava.go
@@ -8,6 +8,7 @@
 	"fmt"
 	"go/constant"
 	"go/types"
+	"html"
 	"math"
 	"reflect"
 	"regexp"
@@ -271,6 +272,8 @@
 		}
 	}
 
+	doc := g.docs[n]
+	g.javadoc(doc.Doc())
 	g.Printf("public final class %s", n)
 	if jinf != nil {
 		if jinf.extends != nil {
@@ -308,7 +311,11 @@
 			g.Printf("// skipped field %s.%s with unsupported type: %T\n\n", n, f.Name(), t)
 			continue
 		}
+
+		fdoc := doc.Member(f.Name())
+		g.javadoc(fdoc)
 		g.Printf("public final native %s get%s();\n", g.javaType(f.Type()), f.Name())
+		g.javadoc(fdoc)
 		g.Printf("public final native void set%s(%s v);\n\n", f.Name(), g.javaType(f.Type()))
 	}
 
@@ -318,6 +325,7 @@
 			g.Printf("// skipped method %s.%s with unsupported parameter or return types\n\n", n, m.Name())
 			continue
 		}
+		g.javadoc(doc.Member(m.Name()))
 		var jm *java.Func
 		hasThis := false
 		if jinf != nil {
@@ -342,6 +350,14 @@
 	g.Printf("}\n\n")
 }
 
+func (g *JavaGen) javadoc(doc string) {
+	if doc == "" {
+		return
+	}
+	// JavaDoc expects HTML-escaped documentation.
+	g.Printf("/**\n * %s */\n", html.EscapeString(doc))
+}
+
 // hasThis returns whether a method has an implicit "this" parameter.
 func (g *JavaGen) hasThis(sName string, m *types.Func) bool {
 	sig := m.Type().(*types.Signature)
@@ -372,6 +388,7 @@
 }
 
 func (g *JavaGen) genConstructor(f *types.Func, n string, jcls bool) {
+	g.javadoc(g.docs[f.Name()].Doc())
 	g.Printf("public %s(", n)
 	g.genFuncArgs(f, nil, false)
 	g.Printf(") {\n")
@@ -511,6 +528,8 @@
 			exts = append(exts, n)
 		}
 	}
+	doc := g.docs[iface.obj.Name()]
+	g.javadoc(doc.Doc())
 	g.Printf("public interface %s", iface.obj.Name())
 	if len(exts) > 0 {
 		g.Printf(" extends %s", strings.Join(exts, ", "))
@@ -523,6 +542,7 @@
 			g.Printf("// skipped method %s.%s with unsupported parameter or return types\n\n", iface.obj.Name(), m.Name())
 			continue
 		}
+		g.javadoc(doc.Member(m.Name()))
 		g.Printf("public ")
 		g.genFuncSignature(m, nil, false)
 	}
@@ -804,10 +824,13 @@
 	}
 	jType := g.javaType(o.Type())
 
+	doc := g.docs[o.Name()].Doc()
 	// setter
+	g.javadoc(doc)
 	g.Printf("public static native void set%s(%s v);\n", o.Name(), jType)
 
 	// getter
+	g.javadoc(doc)
 	g.Printf("public static native %s get%s();\n\n", jType, o.Name())
 }
 
@@ -1005,6 +1028,7 @@
 		}
 		val = fmt.Sprintf("%g", f)
 	}
+	g.javadoc(g.docs[o.Name()].Doc())
 	g.Printf("public static final %s %s = %s;\n", g.javaType(o.Type()), o.Name(), val)
 }
 
diff --git a/bind/genobjc.go b/bind/genobjc.go
index 6c87827..44e178b 100644
--- a/bind/genobjc.go
+++ b/bind/genobjc.go
@@ -194,6 +194,7 @@
 			g.Printf("// skipped const %s with unsupported type: %T\n\n", obj.Name(), obj)
 			continue
 		}
+		g.objcdoc(g.docs[obj.Name()].Doc())
 		switch b := obj.Type().(*types.Basic); b.Kind() {
 		case types.String, types.UntypedString:
 			g.Printf("FOUNDATION_EXPORT NSString* const %s%s;\n", g.namePrefix, obj.Name())
@@ -214,6 +215,7 @@
 				continue
 			}
 			objcType := g.objcType(obj.Type())
+			g.objcdoc(g.docs[obj.Name()].Doc())
 			g.Printf("+ (%s) %s;\n", objcType, objcNameReplacer(lowerFirst(obj.Name())))
 			g.Printf("+ (void) set%s:(%s)v;\n", obj.Name(), objcType)
 			g.Printf("\n")
@@ -628,6 +630,7 @@
 		return
 	}
 	if s := g.funcSummary(nil, obj); s != nil {
+		g.objcdoc(g.docs[obj.Name()].Doc())
 		g.Printf("FOUNDATION_EXPORT %s;\n", s.asFunc(g))
 	}
 }
@@ -856,6 +859,7 @@
 }
 
 func (g *ObjcGen) genInterfaceInterface(obj *types.TypeName, summary ifaceSummary, isProtocol bool) {
+	g.objcdoc(g.docs[obj.Name()].Doc())
 	g.Printf("@interface %[1]s%[2]s : ", g.namePrefix, obj.Name())
 	if isErrorType(obj.Type()) {
 		g.Printf("NSError")
@@ -1039,6 +1043,8 @@
 }
 
 func (g *ObjcGen) genStructH(obj *types.TypeName, t *types.Struct) {
+	doc := g.docs[obj.Name()]
+	g.objcdoc(doc.Doc())
 	g.Printf("@interface %s%s : ", g.namePrefix, obj.Name())
 	oinf := g.ostructs[obj]
 	var prots []string
@@ -1092,6 +1098,7 @@
 			continue
 		}
 		name, typ := f.Name(), g.objcFieldType(f.Type())
+		g.objcdoc(doc.Member(f.Name()))
 		g.Printf("- (%s)%s;\n", typ, objcNameReplacer(lowerFirst(name)))
 		g.Printf("- (void)set%s:(%s)v;\n", name, typ)
 	}
@@ -1103,11 +1110,19 @@
 			continue
 		}
 		s := g.funcSummary(obj, m)
+		g.objcdoc(doc.Member(m.Name()))
 		g.Printf("- %s;\n", s.asMethod(g))
 	}
 	g.Printf("@end\n")
 }
 
+func (g *ObjcGen) objcdoc(doc string) {
+	if doc == "" {
+		return
+	}
+	g.Printf("/**\n * %s */\n", doc)
+}
+
 func (g *ObjcGen) genStructM(obj *types.TypeName, t *types.Struct) {
 	fields := exportedFields(t)
 	methods := exportedMethodSet(types.NewPointer(obj.Type()))
@@ -1169,6 +1184,8 @@
 
 func (g *ObjcGen) genInitH(obj *types.TypeName, f *types.Func) {
 	s := g.funcSummary(obj, f)
+	doc := g.docs[f.Name()]
+	g.objcdoc(doc.Doc())
 	g.Printf("- (instancetype)%s%s;\n", s.initName, s.asInitSignature(g))
 }
 
diff --git a/bind/testdata/classes.java.golden b/bind/testdata/classes.java.golden
index a746c17..9cc75b1 100644
--- a/bind/testdata/classes.java.golden
+++ b/bind/testdata/classes.java.golden
@@ -27,6 +27,9 @@
     public final native void setFuture(java.util.concurrent.Future v);
     
     @Override public native java.lang.Object get() throws java.lang.InterruptedException, java.util.concurrent.ExecutionException;
+    /**
+     * Use a trailing underscore to override multiple overloaded methods.
+     */
     @Override public native java.lang.Object get(long p0, java.util.concurrent.TimeUnit p1) throws java.lang.InterruptedException, java.util.concurrent.ExecutionException, java.util.concurrent.TimeoutException;
 }
 
diff --git a/bind/testdata/doc.go b/bind/testdata/doc.go
new file mode 100644
index 0000000..73dfd32
--- /dev/null
+++ b/bind/testdata/doc.go
@@ -0,0 +1,53 @@
+// 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 doc tests that Go documentation is transferred
+// to the generated code.
+package doc
+
+// F is a function.
+func F() {}
+
+// C is a constant.
+const C = true
+
+// V is a var.
+var V string
+
+// A group of vars.
+var (
+	// A specific var.
+	Specific string
+	NoDocVar float64
+)
+
+// Before is a method.
+func (_ *S) Before() {}
+
+// S is a struct.
+type S struct {
+	// SF is a field.
+	SF string
+}
+
+// After is another method.
+func (_ *S) After() {}
+
+// A generic comment with <HTML>.
+type (
+	// S2 is a struct.
+	S2    struct{}
+	NoDoc struct{}
+)
+
+// NewS is a constructor.
+func NewS() *S {
+	return nil
+}
+
+// I is an interface.
+type I interface {
+	// IM is a method.
+	IM()
+}
diff --git a/bind/testdata/doc.go.golden b/bind/testdata/doc.go.golden
new file mode 100644
index 0000000..1c865ff
--- /dev/null
+++ b/bind/testdata/doc.go.golden
@@ -0,0 +1,135 @@
+// Package gomobile_bind is an autogenerated binder stub for package doc.
+//   gobind -lang=go doc
+//
+// File is generated by gobind. Do not edit.
+package gomobile_bind
+
+/*
+#include <stdlib.h>
+#include <stdint.h>
+#include "seq.h"
+#include "doc.h"
+
+*/
+import "C"
+
+import (
+	"doc"
+	_seq "golang.org/x/mobile/bind/seq"
+)
+
+// suppress the error if seq ends up unused
+var _ = _seq.FromRefNum
+
+//export new_doc_NoDoc
+func new_doc_NoDoc() C.int32_t {
+	return C.int32_t(_seq.ToRefNum(new(doc.NoDoc)))
+}
+
+//export proxydoc_S_SF_Set
+func proxydoc_S_SF_Set(refnum C.int32_t, v C.nstring) {
+	ref := _seq.FromRefNum(int32(refnum))
+	_v := decodeString(v)
+	ref.Get().(*doc.S).SF = _v
+}
+
+//export proxydoc_S_SF_Get
+func proxydoc_S_SF_Get(refnum C.int32_t) C.nstring {
+	ref := _seq.FromRefNum(int32(refnum))
+	v := ref.Get().(*doc.S).SF
+	_v := encodeString(v)
+	return _v
+}
+
+//export proxydoc_S_After
+func proxydoc_S_After(refnum C.int32_t) {
+	ref := _seq.FromRefNum(int32(refnum))
+	v := ref.Get().(*doc.S)
+	v.After()
+}
+
+//export proxydoc_S_Before
+func proxydoc_S_Before(refnum C.int32_t) {
+	ref := _seq.FromRefNum(int32(refnum))
+	v := ref.Get().(*doc.S)
+	v.Before()
+}
+
+//export new_doc_S
+func new_doc_S() C.int32_t {
+	return C.int32_t(_seq.ToRefNum(new(doc.S)))
+}
+
+//export new_doc_S2
+func new_doc_S2() C.int32_t {
+	return C.int32_t(_seq.ToRefNum(new(doc.S2)))
+}
+
+//export proxydoc_I_IM
+func proxydoc_I_IM(refnum C.int32_t) {
+	ref := _seq.FromRefNum(int32(refnum))
+	v := ref.Get().(doc.I)
+	v.IM()
+}
+
+type proxydoc_I _seq.Ref
+
+func (p *proxydoc_I) Bind_proxy_refnum__() int32 { return (*_seq.Ref)(p).Bind_IncNum() }
+
+func (p *proxydoc_I) IM() {
+	C.cproxydoc_I_IM(C.int32_t(p.Bind_proxy_refnum__()))
+}
+
+//export var_setdoc_NoDocVar
+func var_setdoc_NoDocVar(v C.double) {
+	_v := float64(v)
+	doc.NoDocVar = _v
+}
+
+//export var_getdoc_NoDocVar
+func var_getdoc_NoDocVar() C.double {
+	v := doc.NoDocVar
+	_v := C.double(v)
+	return _v
+}
+
+//export var_setdoc_Specific
+func var_setdoc_Specific(v C.nstring) {
+	_v := decodeString(v)
+	doc.Specific = _v
+}
+
+//export var_getdoc_Specific
+func var_getdoc_Specific() C.nstring {
+	v := doc.Specific
+	_v := encodeString(v)
+	return _v
+}
+
+//export var_setdoc_V
+func var_setdoc_V(v C.nstring) {
+	_v := decodeString(v)
+	doc.V = _v
+}
+
+//export var_getdoc_V
+func var_getdoc_V() C.nstring {
+	v := doc.V
+	_v := encodeString(v)
+	return _v
+}
+
+//export proxydoc__F
+func proxydoc__F() {
+	doc.F()
+}
+
+//export proxydoc__NewS
+func proxydoc__NewS() C.int32_t {
+	res_0 := doc.NewS()
+	var _res_0 C.int32_t = _seq.NullRefNum
+	if res_0 != nil {
+		_res_0 = C.int32_t(_seq.ToRefNum(res_0))
+	}
+	return _res_0
+}
diff --git a/bind/testdata/doc.java.c.golden b/bind/testdata/doc.java.c.golden
new file mode 100644
index 0000000..d76e37b
--- /dev/null
+++ b/bind/testdata/doc.java.c.golden
@@ -0,0 +1,150 @@
+// JNI functions for the Go <=> Java bridge.
+//   gobind -lang=java doc
+//
+// File is generated by gobind. Do not edit.
+
+#include <android/log.h>
+#include <stdint.h>
+#include "seq.h"
+#include "_cgo_export.h"
+#include "doc.h"
+
+jclass proxy_class_doc_I;
+jmethodID proxy_class_doc_I_cons;
+static jmethodID mid_I_IM;
+jclass proxy_class_doc_NoDoc;
+jmethodID proxy_class_doc_NoDoc_cons;
+jclass proxy_class_doc_S;
+jmethodID proxy_class_doc_S_cons;
+jclass proxy_class_doc_S2;
+jmethodID proxy_class_doc_S2_cons;
+
+JNIEXPORT void JNICALL
+Java_doc_Doc__1init(JNIEnv *env, jclass _unused) {
+    jclass clazz;
+    clazz = (*env)->FindClass(env, "doc/NoDoc");
+    proxy_class_doc_NoDoc = (*env)->NewGlobalRef(env, clazz);
+    proxy_class_doc_NoDoc_cons = (*env)->GetMethodID(env, clazz, "<init>", "(Lgo/Seq$Ref;)V");
+    clazz = (*env)->FindClass(env, "doc/S");
+    proxy_class_doc_S = (*env)->NewGlobalRef(env, clazz);
+    proxy_class_doc_S_cons = (*env)->GetMethodID(env, clazz, "<init>", "(Lgo/Seq$Ref;)V");
+    clazz = (*env)->FindClass(env, "doc/S2");
+    proxy_class_doc_S2 = (*env)->NewGlobalRef(env, clazz);
+    proxy_class_doc_S2_cons = (*env)->GetMethodID(env, clazz, "<init>", "(Lgo/Seq$Ref;)V");
+    clazz = (*env)->FindClass(env, "doc/Doc$proxyI");
+    proxy_class_doc_I = (*env)->NewGlobalRef(env, clazz);
+    proxy_class_doc_I_cons = (*env)->GetMethodID(env, clazz, "<init>", "(Lgo/Seq$Ref;)V");
+    clazz = (*env)->FindClass(env, "doc/I");
+    mid_I_IM = (*env)->GetMethodID(env, clazz, "im", "()V");
+    
+}
+
+JNIEXPORT void JNICALL
+Java_doc_Doc_f(JNIEnv* env, jclass _clazz) {
+    proxydoc__F();
+}
+
+JNIEXPORT jobject JNICALL
+Java_doc_Doc_newS(JNIEnv* env, jclass _clazz) {
+    int32_t r0 = proxydoc__NewS();
+    jobject _r0 = go_seq_from_refnum(env, r0, proxy_class_doc_S, proxy_class_doc_S_cons);
+    return _r0;
+}
+
+JNIEXPORT jobject JNICALL
+Java_doc_NoDoc__1_1New(JNIEnv *env, jclass clazz) {
+    int32_t refnum = new_doc_NoDoc();
+    return go_seq_from_refnum(env, refnum, NULL, NULL);
+}
+
+JNIEXPORT jobject JNICALL
+Java_doc_S__1_1NewS(JNIEnv *env, jclass clazz) {
+    int32_t refnum = proxydoc__NewS();
+    return go_seq_from_refnum(env, refnum, NULL, NULL);
+}
+
+JNIEXPORT void JNICALL
+Java_doc_S_after(JNIEnv* env, jobject __this__) {
+    int32_t o = go_seq_to_refnum_go(env, __this__);
+    proxydoc_S_After(o);
+}
+
+JNIEXPORT void JNICALL
+Java_doc_S_before(JNIEnv* env, jobject __this__) {
+    int32_t o = go_seq_to_refnum_go(env, __this__);
+    proxydoc_S_Before(o);
+}
+
+JNIEXPORT void JNICALL
+Java_doc_S_setSF(JNIEnv *env, jobject this, jstring v) {
+    int32_t o = go_seq_to_refnum_go(env, this);
+    nstring _v = go_seq_from_java_string(env, v);
+    proxydoc_S_SF_Set(o, _v);
+}
+
+JNIEXPORT jstring JNICALL
+Java_doc_S_getSF(JNIEnv *env, jobject this) {
+    int32_t o = go_seq_to_refnum_go(env, this);
+    nstring r0 = proxydoc_S_SF_Get(o);
+    jstring _r0 = go_seq_to_java_string(env, r0);
+    return _r0;
+}
+
+JNIEXPORT jobject JNICALL
+Java_doc_S2__1_1New(JNIEnv *env, jclass clazz) {
+    int32_t refnum = new_doc_S2();
+    return go_seq_from_refnum(env, refnum, NULL, NULL);
+}
+
+JNIEXPORT void JNICALL
+Java_doc_Doc_00024proxyI_im(JNIEnv* env, jobject __this__) {
+    int32_t o = go_seq_to_refnum_go(env, __this__);
+    proxydoc_I_IM(o);
+}
+
+void cproxydoc_I_IM(int32_t refnum) {
+    JNIEnv *env = go_seq_push_local_frame(0);
+    jobject o = go_seq_from_refnum(env, refnum, proxy_class_doc_I, proxy_class_doc_I_cons);
+    (*env)->CallVoidMethod(env, o, mid_I_IM);
+    go_seq_pop_local_frame(env);
+}
+
+JNIEXPORT void JNICALL
+Java_doc_Doc_setNoDocVar(JNIEnv *env, jclass clazz, jdouble v) {
+    double _v = (double)v;
+    var_setdoc_NoDocVar(_v);
+}
+
+JNIEXPORT jdouble JNICALL
+Java_doc_Doc_getNoDocVar(JNIEnv *env, jclass clazz) {
+    double r0 = var_getdoc_NoDocVar();
+    jdouble _r0 = (jdouble)r0;
+    return _r0;
+}
+
+JNIEXPORT void JNICALL
+Java_doc_Doc_setSpecific(JNIEnv *env, jclass clazz, jstring v) {
+    nstring _v = go_seq_from_java_string(env, v);
+    var_setdoc_Specific(_v);
+}
+
+JNIEXPORT jstring JNICALL
+Java_doc_Doc_getSpecific(JNIEnv *env, jclass clazz) {
+    nstring r0 = var_getdoc_Specific();
+    jstring _r0 = go_seq_to_java_string(env, r0);
+    return _r0;
+}
+
+JNIEXPORT void JNICALL
+Java_doc_Doc_setV(JNIEnv *env, jclass clazz, jstring v) {
+    nstring _v = go_seq_from_java_string(env, v);
+    var_setdoc_V(_v);
+}
+
+JNIEXPORT jstring JNICALL
+Java_doc_Doc_getV(JNIEnv *env, jclass clazz) {
+    nstring r0 = var_getdoc_V();
+    jstring _r0 = go_seq_to_java_string(env, r0);
+    return _r0;
+}
+
diff --git a/bind/testdata/doc.java.golden b/bind/testdata/doc.java.golden
new file mode 100644
index 0000000..a1c58bc
--- /dev/null
+++ b/bind/testdata/doc.java.golden
@@ -0,0 +1,262 @@
+// Java class doc.NoDoc is a proxy for talking to a Go program.
+//   gobind -lang=java doc
+//
+// File is generated by gobind. Do not edit.
+package doc;
+
+import go.Seq;
+
+/**
+ * A generic comment with &lt;HTML&gt;.
+ */
+public final class NoDoc implements Seq.Proxy {
+    static { Doc.touch(); }
+    
+    private final Seq.Ref ref;
+    
+    @Override public final int incRefnum() {
+          int refnum = ref.refnum;
+          Seq.incGoRef(refnum);
+          return refnum;
+    }
+    
+    NoDoc(Seq.Ref ref) { this.ref = ref; }
+    
+    public NoDoc() { this.ref = __New(); }
+    
+    private static native Seq.Ref __New();
+    
+    @Override public boolean equals(Object o) {
+        if (o == null || !(o instanceof NoDoc)) {
+            return false;
+        }
+        NoDoc that = (NoDoc)o;
+        return true;
+    }
+    
+    @Override public int hashCode() {
+        return java.util.Arrays.hashCode(new Object[] {});
+    }
+    
+    @Override public String toString() {
+        StringBuilder b = new StringBuilder();
+        b.append("NoDoc").append("{");
+        return b.append("}").toString();
+    }
+}
+
+// Java class doc.S is a proxy for talking to a Go program.
+//   gobind -lang=java doc
+//
+// File is generated by gobind. Do not edit.
+package doc;
+
+import go.Seq;
+
+/**
+ * S is a struct.
+ */
+public final class S implements Seq.Proxy {
+    static { Doc.touch(); }
+    
+    private final Seq.Ref ref;
+    
+    @Override public final int incRefnum() {
+          int refnum = ref.refnum;
+          Seq.incGoRef(refnum);
+          return refnum;
+    }
+    
+    /**
+     * NewS is a constructor.
+     */
+    public S() {
+        this.ref = __NewS();
+    }
+    
+    private static native Seq.Ref __NewS();
+    
+    S(Seq.Ref ref) { this.ref = ref; }
+    
+    /**
+     * SF is a field.
+     */
+    public final native String getSF();
+    /**
+     * SF is a field.
+     */
+    public final native void setSF(String v);
+    
+    /**
+     * After is another method.
+     */
+    public native void after();
+    /**
+     * Before is a method.
+     */
+    public native void before();
+    @Override public boolean equals(Object o) {
+        if (o == null || !(o instanceof S)) {
+            return false;
+        }
+        S that = (S)o;
+        String thisSF = getSF();
+        String thatSF = that.getSF();
+        if (thisSF == null) {
+            if (thatSF != null) {
+                return false;
+            }
+        } else if (!thisSF.equals(thatSF)) {
+            return false;
+        }
+        return true;
+    }
+    
+    @Override public int hashCode() {
+        return java.util.Arrays.hashCode(new Object[] {getSF()});
+    }
+    
+    @Override public String toString() {
+        StringBuilder b = new StringBuilder();
+        b.append("S").append("{");
+        b.append("SF:").append(getSF()).append(",");
+        return b.append("}").toString();
+    }
+}
+
+// Java class doc.S2 is a proxy for talking to a Go program.
+//   gobind -lang=java doc
+//
+// File is generated by gobind. Do not edit.
+package doc;
+
+import go.Seq;
+
+/**
+ * S2 is a struct.
+ */
+public final class S2 implements Seq.Proxy {
+    static { Doc.touch(); }
+    
+    private final Seq.Ref ref;
+    
+    @Override public final int incRefnum() {
+          int refnum = ref.refnum;
+          Seq.incGoRef(refnum);
+          return refnum;
+    }
+    
+    S2(Seq.Ref ref) { this.ref = ref; }
+    
+    public S2() { this.ref = __New(); }
+    
+    private static native Seq.Ref __New();
+    
+    @Override public boolean equals(Object o) {
+        if (o == null || !(o instanceof S2)) {
+            return false;
+        }
+        S2 that = (S2)o;
+        return true;
+    }
+    
+    @Override public int hashCode() {
+        return java.util.Arrays.hashCode(new Object[] {});
+    }
+    
+    @Override public String toString() {
+        StringBuilder b = new StringBuilder();
+        b.append("S2").append("{");
+        return b.append("}").toString();
+    }
+}
+
+// Java class doc.I is a proxy for talking to a Go program.
+//   gobind -lang=java doc
+//
+// File is generated by gobind. Do not edit.
+package doc;
+
+import go.Seq;
+
+/**
+ * I is an interface.
+ */
+public interface I {
+    /**
+     * IM is a method.
+     */
+    public void im();
+    
+}
+
+// Java class doc.Doc is a proxy for talking to a Go program.
+//   gobind -lang=java doc
+//
+// File is generated by gobind. Do not edit.
+package doc;
+
+import go.Seq;
+
+public abstract class Doc {
+    static {
+        Seq.touch(); // for loading the native library
+        _init();
+    }
+    
+    private Doc() {} // uninstantiable
+    
+    // touch is called from other bound packages to initialize this package
+    public static void touch() {}
+    
+    private static native void _init();
+    
+    private static final class proxyI implements Seq.Proxy, I {
+        private final Seq.Ref ref;
+        
+        @Override public final int incRefnum() {
+              int refnum = ref.refnum;
+              Seq.incGoRef(refnum);
+              return refnum;
+        }
+        
+        proxyI(Seq.Ref ref) { this.ref = ref; }
+        
+        public native void im();
+    }
+    
+    /**
+     * C is a constant.
+     */
+    public static final boolean C = true;
+    
+    /**
+     * A group of vars.
+     */
+    public static native void setNoDocVar(double v);
+    /**
+     * A group of vars.
+     */
+    public static native double getNoDocVar();
+    
+    /**
+     * A specific var.
+     */
+    public static native void setSpecific(String v);
+    /**
+     * A specific var.
+     */
+    public static native String getSpecific();
+    
+    /**
+     * V is a var.
+     */
+    public static native void setV(String v);
+    /**
+     * V is a var.
+     */
+    public static native String getV();
+    
+    public static native void f();
+    public static native S newS();
+}
diff --git a/bind/testdata/doc.java.h.golden b/bind/testdata/doc.java.h.golden
new file mode 100644
index 0000000..b4d2917
--- /dev/null
+++ b/bind/testdata/doc.java.h.golden
@@ -0,0 +1,22 @@
+// JNI function headers for the Go <=> Java bridge.
+//   gobind -lang=java doc
+//
+// File is generated by gobind. Do not edit.
+
+#ifndef __Doc_H__
+#define __Doc_H__
+
+#include <jni.h>
+
+extern jclass proxy_class_doc_I;
+extern jmethodID proxy_class_doc_I_cons;
+
+void cproxydoc_I_IM(int32_t refnum);
+
+extern jclass proxy_class_doc_NoDoc;
+extern jmethodID proxy_class_doc_NoDoc_cons;
+extern jclass proxy_class_doc_S;
+extern jmethodID proxy_class_doc_S_cons;
+extern jclass proxy_class_doc_S2;
+extern jmethodID proxy_class_doc_S2_cons;
+#endif
diff --git a/bind/testdata/doc.objc.go.h.golden b/bind/testdata/doc.objc.go.h.golden
new file mode 100644
index 0000000..2e4f436
--- /dev/null
+++ b/bind/testdata/doc.objc.go.h.golden
@@ -0,0 +1,13 @@
+// Objective-C API for talking to doc Go package.
+//   gobind -lang=objc doc
+//
+// File is generated by gobind. Do not edit.
+
+#ifndef __doc_H__
+#define __doc_H__
+
+#include <stdint.h>
+#include <objc/objc.h>
+void cproxydoc_I_IM(int32_t refnum);
+
+#endif
diff --git a/bind/testdata/doc.objc.h.golden b/bind/testdata/doc.objc.h.golden
new file mode 100644
index 0000000..a0b2d14
--- /dev/null
+++ b/bind/testdata/doc.objc.h.golden
@@ -0,0 +1,121 @@
+// Objective-C API for talking to doc Go package.
+//   gobind -lang=objc doc
+//
+// File is generated by gobind. Do not edit.
+
+#ifndef __Doc_H__
+#define __Doc_H__
+
+@import Foundation;
+#include "Universe.objc.h"
+
+
+@class DocNoDoc;
+@class DocS;
+@class DocS2;
+@protocol DocI;
+@class DocI;
+
+@protocol DocI <NSObject>
+- (void)im;
+@end
+
+/**
+ * A generic comment with <HTML>.
+ */
+@interface DocNoDoc : NSObject <goSeqRefInterface> {
+}
+@property(strong, readonly) id _ref;
+
+- (instancetype)initWithRef:(id)ref;
+- (instancetype)init;
+@end
+
+/**
+ * S is a struct.
+ */
+@interface DocS : NSObject <goSeqRefInterface> {
+}
+@property(strong, readonly) id _ref;
+
+- (instancetype)initWithRef:(id)ref;
+/**
+ * NewS is a constructor.
+ */
+- (instancetype)init;
+/**
+ * SF is a field.
+ */
+- (NSString*)sf;
+- (void)setSF:(NSString*)v;
+/**
+ * After is another method.
+ */
+- (void)after;
+/**
+ * Before is a method.
+ */
+- (void)before;
+@end
+
+/**
+ * S2 is a struct.
+ */
+@interface DocS2 : NSObject <goSeqRefInterface> {
+}
+@property(strong, readonly) id _ref;
+
+- (instancetype)initWithRef:(id)ref;
+- (instancetype)init;
+@end
+
+/**
+ * C is a constant.
+ */
+FOUNDATION_EXPORT const BOOL DocC;
+
+@interface Doc : NSObject
+/**
+ * A group of vars.
+ */
++ (double) noDocVar;
++ (void) setNoDocVar:(double)v;
+
+/**
+ * A specific var.
+ */
++ (NSString*) specific;
++ (void) setSpecific:(NSString*)v;
+
+/**
+ * V is a var.
+ */
++ (NSString*) v;
++ (void) setV:(NSString*)v;
+
+@end
+
+/**
+ * F is a function.
+ */
+FOUNDATION_EXPORT void DocF();
+
+/**
+ * NewS is a constructor.
+ */
+FOUNDATION_EXPORT DocS* DocNewS();
+
+@class DocI;
+
+/**
+ * I is an interface.
+ */
+@interface DocI : NSObject <goSeqRefInterface, DocI> {
+}
+@property(strong, readonly) id _ref;
+
+- (instancetype)initWithRef:(id)ref;
+- (void)im;
+@end
+
+#endif
diff --git a/bind/testdata/doc.objc.m.golden b/bind/testdata/doc.objc.m.golden
new file mode 100644
index 0000000..79b6f04
--- /dev/null
+++ b/bind/testdata/doc.objc.m.golden
@@ -0,0 +1,179 @@
+// Objective-C API for talking to doc Go package.
+//   gobind -lang=objc doc
+//
+// File is generated by gobind. Do not edit.
+
+#include <Foundation/Foundation.h>
+#include "seq.h"
+#include "_cgo_export.h"
+#include "Doc.objc.h"
+
+
+@implementation DocNoDoc {
+}
+
+- (instancetype)initWithRef:(id)ref {
+	self = [super init];
+	if (self) { __ref = ref; }
+	return self;
+}
+
+- (instancetype)init {
+	self = [super init];
+	if (self) {
+		__ref = go_seq_from_refnum(new_doc_NoDoc());
+	}
+	return self;
+}
+
+@end
+
+
+
+@implementation DocS {
+}
+
+- (instancetype)initWithRef:(id)ref {
+	self = [super init];
+	if (self) { __ref = ref; }
+	return self;
+}
+
+- (instancetype)init {
+	self = [super init];
+	if (!self) return nil;
+	int32_t refnum = proxydoc__NewS();
+	__ref = go_seq_from_refnum(refnum);
+	return self;
+}
+
+- (NSString*)sf {
+	int32_t refnum = go_seq_go_to_refnum(self._ref);
+	nstring r0 = proxydoc_S_SF_Get(refnum);
+	NSString *_r0 = go_seq_to_objc_string(r0);
+	return _r0;
+}
+
+- (void)setSF:(NSString*)v {
+	int32_t refnum = go_seq_go_to_refnum(self._ref);
+	nstring _v = go_seq_from_objc_string(v);
+	proxydoc_S_SF_Set(refnum, _v);
+}
+
+- (void)after {
+	int32_t refnum = go_seq_go_to_refnum(self._ref);
+	proxydoc_S_After(refnum);
+}
+
+- (void)before {
+	int32_t refnum = go_seq_go_to_refnum(self._ref);
+	proxydoc_S_Before(refnum);
+}
+
+@end
+
+
+
+@implementation DocS2 {
+}
+
+- (instancetype)initWithRef:(id)ref {
+	self = [super init];
+	if (self) { __ref = ref; }
+	return self;
+}
+
+- (instancetype)init {
+	self = [super init];
+	if (self) {
+		__ref = go_seq_from_refnum(new_doc_S2());
+	}
+	return self;
+}
+
+@end
+
+
+@implementation DocI {
+}
+
+- (instancetype)initWithRef:(id)ref {
+	self = [super init];
+	if (self) { __ref = ref; }
+	return self;
+}
+
+- (void)im {
+	int32_t refnum = go_seq_go_to_refnum(self._ref);
+	proxydoc_I_IM(refnum);
+}
+
+@end
+
+
+const BOOL DocC = YES;
+
+@implementation Doc
++ (void) setNoDocVar:(double)v {
+	double _v = (double)v;
+	var_setdoc_NoDocVar(_v);
+}
+
++ (double) noDocVar {
+	double r0 = var_getdoc_NoDocVar();
+	double _r0 = (double)r0;
+	return _r0;
+}
+
++ (void) setSpecific:(NSString*)v {
+	nstring _v = go_seq_from_objc_string(v);
+	var_setdoc_Specific(_v);
+}
+
++ (NSString*) specific {
+	nstring r0 = var_getdoc_Specific();
+	NSString *_r0 = go_seq_to_objc_string(r0);
+	return _r0;
+}
+
++ (void) setV:(NSString*)v {
+	nstring _v = go_seq_from_objc_string(v);
+	var_setdoc_V(_v);
+}
+
++ (NSString*) v {
+	nstring r0 = var_getdoc_V();
+	NSString *_r0 = go_seq_to_objc_string(r0);
+	return _r0;
+}
+
+@end
+
+
+void DocF() {
+	proxydoc__F();
+}
+
+DocS* DocNewS() {
+	int32_t r0 = proxydoc__NewS();
+	DocS* _ret0_ = nil;
+	GoSeqRef* _ret0__ref = go_seq_from_refnum(r0);
+	if (_ret0__ref != NULL) {
+		_ret0_ = _ret0__ref.obj;
+		if (_ret0_ == nil) {
+			_ret0_ = [[DocS alloc] initWithRef:_ret0__ref];
+		}
+	}
+	return _ret0_;
+}
+
+void cproxydoc_I_IM(int32_t refnum) {
+	@autoreleasepool {
+		DocI* o = go_seq_objc_from_refnum(refnum);
+		[o im];
+	}
+}
+
+__attribute__((constructor)) static void init() {
+	init_seq();
+}
diff --git a/bind/testdata/interfaces.java.golden b/bind/testdata/interfaces.java.golden
index 44d5413..6b95947 100644
--- a/bind/testdata/interfaces.java.golden
+++ b/bind/testdata/interfaces.java.golden
@@ -32,6 +32,9 @@
 
 import go.Seq;
 
+/**
+ * not implementable
+ */
 public interface I1 {
     public void j();
     
@@ -45,6 +48,9 @@
 
 import go.Seq;
 
+/**
+ * not implementable
+ */
 public interface I2 {
     public void g();
     
@@ -58,6 +64,10 @@
 
 import go.Seq;
 
+/**
+ * implementable
+(the implementor has to find a source of I1s)
+ */
 public interface I3 {
     public I1 f();
     
diff --git a/bind/testdata/interfaces.objc.h.golden b/bind/testdata/interfaces.objc.h.golden
index ffbd03e..3962a89 100644
--- a/bind/testdata/interfaces.objc.h.golden
+++ b/bind/testdata/interfaces.objc.h.golden
@@ -33,6 +33,9 @@
 - (int32_t)rand;
 @end
 
+/**
+ * not implementable
+ */
 @interface InterfacesI1 : NSObject <goSeqRefInterface> {
 }
 @property(strong, readonly) id _ref;
@@ -41,6 +44,9 @@
 - (void)j;
 @end
 
+/**
+ * not implementable
+ */
 @interface InterfacesI2 : NSObject <goSeqRefInterface> {
 }
 @property(strong, readonly) id _ref;
@@ -100,6 +106,10 @@
 - (int32_t)rand;
 @end
 
+/**
+ * implementable
+(the implementor has to find a source of I1s)
+ */
 @interface InterfacesI3 : NSObject <goSeqRefInterface, InterfacesI3> {
 }
 @property(strong, readonly) id _ref;
diff --git a/cmd/gomobile/bind.go b/cmd/gomobile/bind.go
index 4b34f97..ea919cc 100644
--- a/cmd/gomobile/bind.go
+++ b/cmd/gomobile/bind.go
@@ -11,6 +11,7 @@
 	"go/ast"
 	"go/build"
 	"go/importer"
+	"go/parser"
 	"go/token"
 	"go/types"
 	"io"
@@ -197,7 +198,7 @@
 	return copyFile(filepath.Join(outdir, "seq.h"), filepath.Join(objcPkg.Dir, "seq.h"))
 }
 
-func (b *binder) GenObjc(pkg *types.Package, allPkg []*types.Package, outdir string, wrappers []*objc.Named) (string, error) {
+func (b *binder) GenObjc(pkg *types.Package, astPkg *ast.Package, allPkg []*types.Package, outdir string, wrappers []*objc.Named) (string, error) {
 	if pkg == nil {
 		bindPrefix = ""
 	}
@@ -226,6 +227,7 @@
 			Fset:    b.fset,
 			AllPkg:  allPkg,
 			Pkg:     pkg,
+			AST:     astPkg,
 		},
 		Prefix: bindPrefix,
 	}
@@ -493,7 +495,7 @@
 	return classes, nil
 }
 
-func (b *binder) GenJava(pkg *types.Package, allPkg []*types.Package, classes []*java.Class, outdir, androidDir string) error {
+func (b *binder) GenJava(pkg *types.Package, astPkg *ast.Package, allPkg []*types.Package, classes []*java.Class, outdir, androidDir string) error {
 	jpkgname := bind.JavaPkgName(bindJavaPkg, pkg)
 	javadir := filepath.Join(androidDir, strings.Replace(jpkgname, ".", "/", -1))
 	var className string
@@ -523,6 +525,7 @@
 			Fset:    b.fset,
 			AllPkg:  allPkg,
 			Pkg:     pkg,
+			AST:     astPkg,
 		},
 	}
 	g.Init(classes)
@@ -720,6 +723,26 @@
 	return typePkgs, nil
 }
 
+func parseAST(pkgs []*build.Package) ([]*ast.Package, error) {
+	fset := token.NewFileSet()
+	var astPkgs []*ast.Package
+	for _, pkg := range pkgs {
+		fileNames := append(append([]string{}, pkg.GoFiles...), pkg.CgoFiles...)
+		files := make(map[string]*ast.File)
+		for _, name := range fileNames {
+			f, err := parser.ParseFile(fset, filepath.Join(pkg.Dir, name), nil, parser.ParseComments)
+			if err != nil {
+				return nil, err
+			}
+			files[name] = f
+		}
+		// Ignore errors (from unknown packages).
+		astPkg, _ := ast.NewPackage(fset, files, nil, nil)
+		astPkgs = append(astPkgs, astPkg)
+	}
+	return astPkgs, nil
+}
+
 func newBinder(pkgs []*types.Package) (*binder, error) {
 	for _, pkg := range pkgs {
 		if pkg.Name() == "main" {
diff --git a/cmd/gomobile/bind_androidapp.go b/cmd/gomobile/bind_androidapp.go
index 59ad764..8d304da 100644
--- a/cmd/gomobile/bind_androidapp.go
+++ b/cmd/gomobile/bind_androidapp.go
@@ -73,7 +73,12 @@
 
 		typesPkgs, err := loadExportData(pkgs, env, androidArgs...)
 		if err != nil {
-			return fmt.Errorf("loadExportData failed %v", err)
+			return fmt.Errorf("loadExportData failed: %v", err)
+		}
+
+		astPkgs, err := parseAST(pkgs)
+		if err != nil {
+			return fmt.Errorf("parseAST failed: %v", err)
 		}
 
 		binder, err := newBinder(typesPkgs)
@@ -106,12 +111,12 @@
 		repo := filepath.Clean(filepath.Join(p.Dir, "..")) // golang.org/x/mobile directory.
 
 		jclsDir := filepath.Join(androidDir, "src", "main", "java")
-		for _, pkg := range binder.pkgs {
-			if err := binder.GenJava(pkg, binder.pkgs, classes, srcDir, jclsDir); err != nil {
+		for i, pkg := range binder.pkgs {
+			if err := binder.GenJava(pkg, astPkgs[i], binder.pkgs, classes, srcDir, jclsDir); err != nil {
 				return err
 			}
 		}
-		if err := binder.GenJava(nil, binder.pkgs, classes, srcDir, jclsDir); err != nil {
+		if err := binder.GenJava(nil, nil, binder.pkgs, classes, srcDir, jclsDir); err != nil {
 			return err
 		}
 		if err := binder.GenJavaSupport(srcDir); err != nil {
@@ -145,7 +150,10 @@
 		}
 	}
 
-	return buildAAR(androidDir, pkgs, androidArchs)
+	if err := buildAAR(androidDir, pkgs, androidArchs); err != nil {
+		return err
+	}
+	return buildSrcJar(androidDir)
 }
 
 var androidMainFile = []byte(`
@@ -159,6 +167,26 @@
 func main() {}
 `)
 
+func buildSrcJar(androidDir string) error {
+	var out io.Writer = ioutil.Discard
+	if !buildN {
+		ext := filepath.Ext(buildO)
+		f, err := os.Create(buildO[:len(buildO)-len(ext)] + "-sources.jar")
+		if err != nil {
+			return err
+		}
+		defer func() {
+			if cerr := f.Close(); err == nil {
+				err = cerr
+			}
+		}()
+		out = f
+	}
+
+	src := filepath.Join(androidDir, "src/main/java")
+	return writeJar(out, src)
+}
+
 // AAR is the format for the binary distribution of an Android Library Project
 // and it is a ZIP archive with extension .aar.
 // http://tools.android.com/tech-docs/new-build-system/aar-format
@@ -362,6 +390,10 @@
 	if buildX {
 		printcmd("jar c -C %s .", dst)
 	}
+	return writeJar(w, dst)
+}
+
+func writeJar(w io.Writer, dir string) error {
 	if buildN {
 		return nil
 	}
@@ -378,14 +410,14 @@
 	}
 	fmt.Fprintf(f, manifestHeader)
 
-	err = filepath.Walk(dst, func(path string, info os.FileInfo, err error) error {
+	err = filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
 		if err != nil {
 			return err
 		}
 		if info.IsDir() {
 			return nil
 		}
-		out, err := jarwcreate(filepath.ToSlash(path[len(dst)+1:]))
+		out, err := jarwcreate(filepath.ToSlash(path[len(dir)+1:]))
 		if err != nil {
 			return err
 		}
diff --git a/cmd/gomobile/bind_iosapp.go b/cmd/gomobile/bind_iosapp.go
index f1baa2d..c13ae12 100644
--- a/cmd/gomobile/bind_iosapp.go
+++ b/cmd/gomobile/bind_iosapp.go
@@ -31,6 +31,11 @@
 		return err
 	}
 
+	astPkgs, err := parseAST(pkgs)
+	if err != nil {
+		return err
+	}
+
 	binder, err := newBinder(typesPkgs)
 	if err != nil {
 		return err
@@ -65,11 +70,11 @@
 
 	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 {
+		if fileBases[i], err = binder.GenObjc(pkg, astPkgs[i], binder.pkgs, srcDir, wrappers); err != nil {
 			return err
 		}
 	}
-	if fileBases[len(fileBases)-1], err = binder.GenObjc(nil, binder.pkgs, srcDir, wrappers); err != nil {
+	if fileBases[len(fileBases)-1], err = binder.GenObjc(nil, nil, binder.pkgs, srcDir, wrappers); err != nil {
 		return err
 	}
 	if err := binder.GenObjcSupport(srcDir); err != nil {