Implement CIDLC support for package imports

This change correctly implements package/module resolution in CIDLC.
For now, this only works for intra-package imports, which is sufficient
for now.  Eventually we will need to support this (see pulumi/coconut#138).
This commit is contained in:
joeduffy 2017-04-28 10:31:18 -07:00
parent 46227870e4
commit af3949509a
5 changed files with 389 additions and 152 deletions

View file

@ -3,9 +3,6 @@
package main
import (
"os"
"path/filepath"
"github.com/golang/glog"
"github.com/pkg/errors"
"github.com/spf13/cobra"
@ -17,42 +14,47 @@ import (
func NewCIDLCCmd() *cobra.Command {
var logToStderr bool
var name string
var outPack string
var outRPC string
var pkgBase string
var root string
var pkgBaseIDL string
var pkgBaseRPC string
var quiet bool
var recurse bool
var verbose int
cmd := &cobra.Command{
Use: "cidlc --name <name> [paths...]",
Use: "cidlc [pkg-name] [idl-path]",
Short: "CIDLC generates Coconut metadata and RPC stubs from IDL written in Go",
Long: "CIDLC generates Coconut metadata and RPC stubs from IDL written in Go.\n" +
"\n" +
"The tool accepts a subset of Go types and produces packages that can be consumed by\n" +
"ordinary Coconut programs and libraries in any language. The [pkg-name] argument\n" +
"controls the output package name and [idl-path] is the path to the IDL sourcecode.\n" +
"\n" +
"The --out-pack and --out-rpc flags indicate where generated code is to be saved,\n" +
"and pkg-base-idl and --pkg-base-rpc may be used to override the default inferred Go\n" +
"package names (which, by default, are based on your GOPATH).",
PersistentPreRun: func(cmd *cobra.Command, args []string) {
cmdutil.InitLogging(logToStderr, verbose)
},
Run: cmdutil.RunFunc(func(cmd *cobra.Command, args []string) error {
if name == "" {
return errors.New("missing required package name (--name or -n)")
} else if pkgBase == "" {
return errors.New("missing required package base (--pkg-base or -p)")
}
if root == "" {
root, _ = os.Getwd() // default to the current working directory.
} else {
root, _ = filepath.Abs(root)
}
if outPack != "" {
outPack, _ = filepath.Abs(outPack)
}
if outRPC != "" {
outRPC, _ = filepath.Abs(outRPC)
if len(args) == 0 {
return cmd.Usage()
} else if len(args) == 1 {
return errors.New("missing required [idl-path] argument")
}
// Now pass the arguments and compile the package.
name := args[0] // the name of the Coconut package.
path := args[1] // the path to the IDL directory that is compiled recursively.
return cidlc.Compile(cidlc.CompileOptions{
Name: tokens.PackageName(name),
Root: root,
OutPack: outPack,
OutRPC: outRPC,
}, args...)
Name: tokens.PackageName(name),
PkgBaseIDL: pkgBaseIDL,
PkgBaseRPC: pkgBaseRPC,
OutPack: outPack,
OutRPC: outRPC,
Quiet: quiet,
Recurse: recurse,
}, path)
}),
PersistentPostRun: func(cmd *cobra.Command, args []string) {
glog.Flush()
@ -61,16 +63,18 @@ func NewCIDLCCmd() *cobra.Command {
cmd.PersistentFlags().BoolVar(
&logToStderr, "logtostderr", false, "Log to stderr instead of to files")
cmd.PersistentFlags().StringVarP(
&name, "name", "n", "", "The Coconut package name")
cmd.PersistentFlags().BoolVarP(
&recurse, "recurse", "r", false, "Recursively generate code for all sub-packages in the target")
cmd.PersistentFlags().StringVar(
&outPack, "out-pack", "", "Save generated package metadata to this directory")
cmd.PersistentFlags().StringVar(
&outRPC, "out-rpc", "", "Save generated RPC provider stubs to this directory")
cmd.PersistentFlags().StringVarP(
&pkgBase, "pkg-base", "p", "", "Specify the package base where this will be published")
cmd.PersistentFlags().StringVarP(
&root, "root", "r", "", "Pick a different root directory than `pwd` (the default)")
cmd.PersistentFlags().StringVar(
&pkgBaseIDL, "pkg-base-idl", "", "Override the base URL where the IDL package is published")
cmd.PersistentFlags().StringVar(
&pkgBaseRPC, "pkg-base-rpc", "", "Override the base URL where the RPC package is published")
cmd.PersistentFlags().BoolVarP(
&quiet, "quiet", "q", false, "Suppress non-error output progress messages")
cmd.PersistentFlags().IntVarP(
&verbose, "verbose", "v", 0, "Enable verbose logging (e.g., v=3); anything >3 is very verbose")

View file

@ -3,27 +3,84 @@
package cidlc
import (
"fmt"
"go/parser"
"go/types"
"io/ioutil"
"os"
"path/filepath"
"sort"
"strings"
"github.com/pkg/errors"
"golang.org/x/tools/go/loader"
"github.com/pulumi/coconut/pkg/tokens"
)
type CompileOptions struct {
Name tokens.PackageName // the package name.
Root string // the package root on the filesystem.
PkgBase string // the base package name.
OutPack string // the package output location.
OutRPC string // the RPC output location.
Name tokens.PackageName // the package name.
PkgBaseIDL string // the base Go package URL for the IDL input.
PkgBaseRPC string // the base Go package URL for the RPC output.
OutPack string // the package output location.
OutRPC string // the RPC output location.
Quiet bool // true to suppress innocuous output messages.
Recurse bool // true to generate code for all sub-packages.
}
// Compile runs the Go compiler against an IDL project and then generates code for the resulting program.
func Compile(opts CompileOptions, paths ...string) error {
// First point the Go compiler at the target paths and compile them. Note that this runs both parsing and semantic
func Compile(opts CompileOptions, path string) error {
// Ensure we are generating *something*.
if opts.OutPack == "" && opts.OutRPC == "" {
return errors.New("Neither --out-pack nor --out-rpc specified; no code to generate...")
}
// Adjust settings to their defaults and adjust any paths to be absolute.
if path == "" {
path, _ = os.Getwd()
} else {
path, _ = filepath.Abs(path)
}
if opts.PkgBaseIDL == "" {
// The default IDL package base is just the GOPATH package path for the target IDL path.
if pkgpath, err := goPackagePath(path); err != nil {
return err
} else {
opts.PkgBaseIDL = pkgpath
}
}
if opts.OutPack != "" {
opts.OutPack, _ = filepath.Abs(opts.OutPack)
}
if opts.OutRPC != "" {
opts.OutRPC, _ = filepath.Abs(opts.OutRPC)
// If there is no package base, pick a default based on GOPATH.
if opts.PkgBaseRPC == "" {
// The default RPC package base, like the IDL package base, defaults to the GOPATH package path.
if pkgpath, err := goPackagePath(opts.OutRPC); err != nil {
return err
} else {
opts.PkgBaseRPC = pkgpath
}
}
}
var inputs []string
if opts.Recurse {
if inp, err := gatherGoPackages(path); err != nil {
return err
} else {
inputs = inp
}
} else {
inputs = []string{opts.PkgBaseIDL}
}
// First point the Go compiler at the target packages to compile. Note that this runs both parsing and semantic
// analysis, and will yield an error if anything with the Go program is wrong.
var conf loader.Config
if _, err := conf.FromArgs(paths, false); err != nil {
if _, err := conf.FromArgs(inputs, false); err != nil {
return err
}
conf.ParserMode |= parser.ParseComments // ensure doc comments are retained.
@ -34,31 +91,50 @@ func Compile(opts CompileOptions, paths ...string) error {
// Now create in-memory IDL packages, validating contents as we go. The result contains classified elements
// such as resources, structs, enum types, and anything required in order to perform subsequent code-generation.
chk := NewChecker(opts.Root, prog)
chk := NewChecker(path, prog)
var packgen *PackGenerator
if out := opts.OutPack; out != "" {
packgen = NewPackGenerator(opts.Root, out)
packgen = NewPackGenerator(prog, path, opts.PkgBaseIDL, out)
}
var rpcgen *RPCGenerator
if out := opts.OutRPC; out != "" {
rpcgen = NewRPCGenerator(opts.Root, opts.PkgBase, out)
rpcgen = NewRPCGenerator(path, opts.PkgBaseIDL, opts.PkgBaseRPC, out)
}
for _, pkginfo := range prog.Created {
pkg, err := chk.Check(opts.Name, pkginfo)
// Enumerate all packages (in a deterministic order).
var pkgs []*types.Package
for pkg := range prog.AllPackages {
pkgs = append(pkgs, pkg)
}
sort.Slice(pkgs, func(i, j int) bool {
return pkgs[i].Path() < pkgs[j].Path()
})
for _, pkg := range pkgs {
// Only emit packages that are underneath the base IDL package.
if !strings.HasPrefix(pkg.Path(), opts.PkgBaseIDL) {
continue
}
pkginfo := prog.AllPackages[pkg]
if !opts.Quiet {
fmt.Printf("Processing package %v\n", pkginfo.Pkg.Path())
}
outpkg, err := chk.Check(opts.Name, pkginfo)
if err != nil {
return err
}
// Now generate the package output.
if packgen != nil {
if err = packgen.Generate(pkg); err != nil {
if err = packgen.Generate(outpkg); err != nil {
return err
}
}
// Next generate the RPC stubs output.
if rpcgen != nil {
if err = rpcgen.Generate(pkg); err != nil {
if err = rpcgen.Generate(outpkg); err != nil {
return err
}
}
@ -66,3 +142,62 @@ func Compile(opts CompileOptions, paths ...string) error {
return nil
}
// goPackagePath takes a path to a filesystem location and returns its Go package path, based on GOPATH. Given a path
// referring to a source location of the form, `$GOPATH/src/...`, the function returns the `...` part.
func goPackagePath(path string) (string, error) {
// Fetch the GOPATH; it must be set, else we bail out.
gopath := os.Getenv("GOPATH")
if gopath == "" {
return "", errors.New("GOPATH is not set, so package paths cannot be inferred (see --pkg-base-x)")
}
gopath = filepath.Join(gopath, "src")
// Now ensure that the package path is a proper subset within it.
if !filepath.HasPrefix(path, gopath) {
return "", errors.Errorf(
"Package root '%v' is not underneath $GOPATH/src, so its package cannot be inferred", path)
}
// Finally, strip off the GOPATH/src prefix, and return the remainder.
return path[len(gopath)+1:], nil
}
// gatherGoPackages recurses into a given path and fetches all of its inferred Go packages. The algorithm considers
// any sub-directory containing a *.go file, recursively, to be a package. It could, of course, be wrong.
func gatherGoPackages(path string) ([]string, error) {
var pkgs []string
// First, if this path contains Go files, append it.
var dirs []string
hasGoFiles := false
if files, err := ioutil.ReadDir(path); err != nil {
return nil, err
} else {
for _, file := range files {
if file.IsDir() {
dirs = append(dirs, file.Name())
} else if filepath.Ext(file.Name()) == ".go" {
hasGoFiles = true
}
}
}
if hasGoFiles {
if pkg, err := goPackagePath(path); err != nil {
return nil, err
} else {
pkgs = append(pkgs, pkg)
}
}
// Next, enumerate all directories recursively, to find all Go sub-packages.
for _, dir := range dirs {
if subpkgs, err := gatherGoPackages(filepath.Join(path, dir)); err != nil {
return nil, err
} else {
pkgs = append(pkgs, subpkgs...)
}
}
return pkgs, nil
}

View file

@ -10,8 +10,11 @@ import (
"os"
"path/filepath"
"reflect"
"sort"
"strings"
"golang.org/x/tools/go/loader"
"github.com/pulumi/coconut/pkg/tokens"
"github.com/pulumi/coconut/pkg/util/contract"
)
@ -19,35 +22,32 @@ import (
// TODO: preserve GoDocs.
type PackGenerator struct {
Root string
Out string
Currpkg *Package // the package currently being visited.
Currfile string // the file currently being visited.
Fhadres bool // true if the file had at least one resource.
Ffimp map[string]string // a map of foreign packages used in a file.
Flimp map[tokens.Name]bool // a map of imported members from modules within this package.
Program *loader.Program // the compiled Go program.
IDLRoot string // the path to the IDL on disk.
IDLPkgBase string // the IDL's base package path.
Out string // where to store the output package.
CurrPkg *Package // the package currently being visited.
CurrFile string // the file currently being visited.
FileHadRes bool // true if the file had at least one resource.
FileImports map[string]MemberImports // a map of imported package paths and members.
}
func NewPackGenerator(root string, out string) *PackGenerator {
type MemberImports map[tokens.Name]string
func NewPackGenerator(prog *loader.Program, root string, pkgBase string, out string) *PackGenerator {
return &PackGenerator{
Root: root,
Out: out,
Program: prog,
IDLRoot: root,
IDLPkgBase: pkgBase,
Out: out,
}
}
func (g *PackGenerator) Relpath(s string) (string, error) {
return filepath.Rel(g.Root, s)
}
// Filename gets the target filename for any given member.
func (g *PackGenerator) Filename(pkg *Package, m Member) (string, error) {
prog := pkg.Program
source := prog.Fset.Position(m.Pos()).Filename // the source filename.`
rel, err := g.Relpath(source)
if err != nil {
return "", err
}
return filepath.Join(g.Out, rel), nil
// Filename gets the source filename for a given Go element.
func (g *PackGenerator) Filename(elem goPos) string {
pos := elem.Pos()
fset := g.Program.Fset
return fset.Position(pos).Filename
}
// Generate generates a Coconut package's source code from a given compiled IDL program.
@ -58,10 +58,10 @@ func (g *PackGenerator) Generate(pkg *Package) error {
}
// Install context about the current entity being visited.
oldpkg, oldfile := g.Currpkg, g.Currfile
g.Currpkg = pkg
oldpkg, oldfile := g.CurrPkg, g.CurrFile
g.CurrPkg = pkg
defer (func() {
g.Currpkg, g.Currfile = oldpkg, oldfile
g.CurrPkg, g.CurrFile = oldpkg, oldfile
})()
// Now walk through the package, file by file, and generate the contents.
@ -70,7 +70,7 @@ func (g *PackGenerator) Generate(pkg *Package) error {
for _, nm := range file.MemberNames {
members = append(members, file.Members[nm])
}
g.Currfile = relpath
g.CurrFile = relpath
path := filepath.Join(g.Out, relpath)
if err := g.EmitFile(path, members); err != nil {
return err
@ -82,12 +82,11 @@ func (g *PackGenerator) Generate(pkg *Package) error {
func (g *PackGenerator) EmitFile(file string, members []Member) error {
// Set up context.
oldhadres, oldffimp, oldflimp := g.Fhadres, g.Ffimp, g.Flimp
g.Fhadres, g.Ffimp, g.Flimp = false, make(map[string]string), make(map[tokens.Name]bool)
oldHadRes, oldImports := g.FileHadRes, g.FileImports
g.FileHadRes, g.FileImports = false, make(map[string]MemberImports)
defer (func() {
g.Fhadres = oldhadres
g.Ffimp = oldffimp
g.Flimp = oldflimp
g.FileHadRes = oldHadRes
g.FileImports = oldImports
})()
// First, generate the body. This is required first so we know which imports to emit.
@ -116,34 +115,68 @@ func (g *PackGenerator) emitFileContents(file string, body string) error {
emitHeaderWarning(w)
// If there are any resources, import the Coconut package.
if g.Fhadres {
if g.FileHadRes {
writefmtln(w, "import * as coconut from \"@coconut/coconut\";")
writefmtln(w, "")
}
if len(g.Flimp) > 0 {
for local := range g.Flimp {
// For a local import, make sure to manufacture a correct relative import of the members.
dir := filepath.Dir(file)
module := g.Currpkg.MemberFiles[local].Path
relimp, err := filepath.Rel(dir, filepath.Join(g.Out, module))
contract.Assert(err == nil)
var impname string
if strings.HasPrefix(relimp, ".") {
impname = relimp
} else {
impname = "./" + relimp
}
if filepath.Ext(impname) != "" {
lastdot := strings.LastIndex(impname, ".")
impname = impname[:lastdot]
}
writefmtln(w, "import {%v} from \"%v\";", local, impname)
if len(g.FileImports) > 0 {
// First, sort the imported package names, to ensure determinism.
var ipkgs []string
for ipkg := range g.FileImports {
ipkgs = append(ipkgs, ipkg)
}
writefmtln(w, "")
}
if len(g.Ffimp) > 0 {
for impname, pkg := range g.Ffimp {
contract.Failf("Foreign imports not yet supported: import=%v pkg=%v", impname, pkg)
sort.Strings(ipkgs)
for _, ipkg := range ipkgs {
// Produce a map of filename to the members in that file that have been imported.
importMap := make(map[string][]tokens.Name)
for member, file := range g.FileImports[ipkg] {
importMap[file] = append(importMap[file], member)
}
// Now sort them to ensure determinism.
var importFiles []string
for file := range importMap {
importFiles = append(importFiles, file)
}
sort.Strings(importFiles)
// Next, walk each imported file and import all members from within it.
for _, file := range importFiles {
// Make a relative import path from the current file.
contract.Assertf(strings.HasPrefix(file, g.IDLRoot),
"Inter-IDL package references not yet supported (%v is not part of %v)", file, g.IDLRoot)
dir := filepath.Dir(g.CurrFile)
relimp, err := filepath.Rel(dir, file[len(g.IDLRoot)+1:])
contract.Assertf(err == nil, "Unexpected filepath.Rel error: %v", err)
var impname string
if strings.HasPrefix(relimp, ".") {
impname = relimp
} else {
impname = "./" + relimp
}
if filepath.Ext(impname) != "" {
lastdot := strings.LastIndex(impname, ".")
impname = impname[:lastdot]
}
// Now produce a sorted list of imported members, again to ensure determinism.
members := importMap[file]
contract.Assert(len(members) > 0)
sort.Slice(members, func(i, j int) bool {
return string(members[i]) < string(members[j])
})
// Finally, go through and produce the import clause.
writefmt(w, "import {")
for i, member := range members {
if i > 0 {
writefmt(w, ", ")
}
writefmt(w, string(member))
}
writefmtln(w, "} from \"%v\";", impname)
}
}
writefmtln(w, "")
}
@ -217,7 +250,7 @@ func (g *PackGenerator) EmitResource(w *bufio.Writer, res *Resource) {
g.emitStructType(w, res, res.Name()+tokens.Name("Args"))
// Remember we had a resource in this file so we can import the right stuff.
g.Fhadres = true
g.FileHadRes = true
}
func (g *PackGenerator) emitResourceClass(w *bufio.Writer, res *Resource) {
@ -282,19 +315,6 @@ func (g *PackGenerator) emitField(w *bufio.Writer, fld *types.Var, opt PropertyO
writefmtln(w, "%v%v%v%v: %v;", prefix, readonly, opt.Name, optional, typ)
}
// registerForeign registers that we have seen a foreign package and requests that the imports be emitted for it.
func (g *PackGenerator) registerForeign(pkg *types.Package) string {
path := pkg.Path()
if impname, has := g.Ffimp[path]; has {
return impname
}
// If we haven't seen this yet, allocate an import name for it. For now, just use the package name.
name := pkg.Name()
g.Ffimp[path] = name
return name
}
func (g *PackGenerator) GenTypeName(t types.Type) string {
switch u := t.(type) {
case *types.Basic:
@ -311,22 +331,11 @@ func (g *PackGenerator) GenTypeName(t types.Type) string {
case *types.Interface:
return "any"
case *types.Named:
// If this came from the same package; the imports will have arranged for it to be available by name.
// Our import logic will have arranged for the type name to be available.
// TODO: consider auto-generated import names to avoid conflicts between imported and local names.
obj := u.Obj()
pkg := obj.Pkg()
name := obj.Name()
if pkg == g.Currpkg.Pkginfo.Pkg {
// If this wasn't in the same file, we still need a relative module import to get the name in scope.
nm := tokens.Name(name)
if g.Currpkg.MemberFiles[nm].Path != g.Currfile {
g.Flimp[nm] = true
}
return name
}
// Otherwise, we will need to refer to a qualified import name.
impname := g.registerForeign(pkg)
return fmt.Sprintf("%v.%v", impname, name)
g.trackNameReference(obj)
return obj.Name()
case *types.Map:
return fmt.Sprintf("{[key: %v]: %v}", g.GenTypeName(u.Key()), g.GenTypeName(u.Elem()))
case *types.Pointer:
@ -338,3 +347,24 @@ func (g *PackGenerator) GenTypeName(t types.Type) string {
}
return ""
}
// trackNameReference registers that we have seen a foreign package and requests that the imports be emitted for it.
func (g *PackGenerator) trackNameReference(obj *types.TypeName) {
// If a reference to a type within the same package and file, there is no need to register anything.
pkg := obj.Pkg()
member := tokens.Name(obj.Name())
if pkg == g.CurrPkg.Pkginfo.Pkg &&
g.CurrPkg.MemberFiles[member].Path == g.CurrFile {
return
}
// Otherwise, we need to track the member so that we can import it later on. Make sure not to add duplicates
// because we want to ensure we don't import the same thing twice.
path := pkg.Path()
members, has := g.FileImports[path]
if !has {
members = make(MemberImports)
g.FileImports[path] = members
}
members[member] = g.Filename(obj)
}

View file

@ -4,11 +4,13 @@ package cidlc
import (
"bufio"
"bytes"
"fmt"
"go/types"
"os"
"path/filepath"
"reflect"
"sort"
"strings"
"github.com/pulumi/coconut/pkg/tokens"
@ -16,17 +18,21 @@ import (
)
type RPCGenerator struct {
Root string
PkgBase string
Out string
Currpkg *Package // the package currently being visited.
IDLRoot string // the root where IDL is loaded from.
IDLPkgBase string // the IDL's base package path.
RPCPkgBase string // the RPC's base package path.
Out string // where RPC stub outputs will be saved.
CurrPkg *Package // the package currently being visited.
CurrFile string // the file currently being visited.
FileImports map[string]string // a map of foreign packages used in a file.
}
func NewRPCGenerator(root string, pkgbase string, out string) *RPCGenerator {
func NewRPCGenerator(root, idlPkgBase, rpcPkgBase, out string) *RPCGenerator {
return &RPCGenerator{
Root: root,
PkgBase: pkgbase,
Out: out,
IDLRoot: root,
IDLPkgBase: idlPkgBase,
RPCPkgBase: rpcPkgBase,
Out: out,
}
}
@ -37,14 +43,16 @@ func (g *RPCGenerator) Generate(pkg *Package) error {
}
// Install context about the current entity being visited.
oldpkg := g.Currpkg
g.Currpkg = pkg
oldpkg, oldfile := g.CurrPkg, g.CurrFile
g.CurrPkg = pkg
defer (func() {
g.Currpkg = oldpkg
g.CurrPkg = oldpkg
g.CurrFile = oldfile
})()
// Now walk through the package, file by file, and generate the contents.
for relpath, file := range pkg.Files {
g.CurrFile = relpath
var members []Member
for _, nm := range file.MemberNames {
members = append(members, file.Members[nm])
@ -59,6 +67,13 @@ func (g *RPCGenerator) Generate(pkg *Package) error {
}
func (g *RPCGenerator) EmitFile(file string, pkg *Package, members []Member) error {
oldimports := g.FileImports
g.FileImports = make(map[string]string)
defer (func() { g.FileImports = oldimports })()
// First, generate the body. This is required first so we know which imports to emit.
body := g.genFileBody(file, pkg, members)
// Open up a writer that overwrites whatever file contents already exist.
f, err := os.OpenFile(file, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644)
if err != nil {
@ -78,17 +93,55 @@ func (g *RPCGenerator) EmitFile(file string, pkg *Package, members []Member) err
// TODO: what about imports for cross-package references.
writefmtln(w, "import (")
writefmtln(w, ` "errors"`)
writefmtln(w, "")
writefmtln(w, ` pbempty "github.com/golang/protobuf/ptypes/empty"`)
writefmtln(w, ` pbstruct "github.com/golang/protobuf/ptypes/struct"`)
writefmtln(w, ` "golang.org/x/net/context"`)
writefmtln(w, "")
writefmtln(w, ` "github.com/pulumi/coconut/pkg/resource"`)
writefmtln(w, ` "github.com/pulumi/coconut/pkg/tokens"`)
writefmtln(w, ` "github.com/pulumi/coconut/pkg/util/contract"`)
writefmtln(w, ` "github.com/pulumi/coconut/pkg/util/mapper"`)
writefmtln(w, ` "github.com/pulumi/coconut/sdk/go/pkg/cocorpc"`)
writefmtln(w, ` "golang.org/x/net/context"`)
if len(g.FileImports) > 0 {
writefmtln(w, "")
// Sort the imports so they are in a correct, deterministic order.
var imports []string
for imp := range g.FileImports {
imports = append(imports, imp)
}
sort.Strings(imports)
// Now just emit a list of imports with their given names.
for _, imp := range imports {
name := g.FileImports[imp]
// If the import referenced one of the IDL packages, we must rewrite it to an RPC package.
contract.Assertf(strings.HasPrefix(imp, g.IDLPkgBase),
"Inter-IDL package references not yet supported (%v is not part of %v)", imp, g.IDLPkgBase)
var imppath string
if imp == g.IDLPkgBase {
imppath = g.RPCPkgBase
} else {
relimp := imp[len(g.IDLPkgBase)+1:]
imppath = g.RPCPkgBase + "/" + relimp
}
writefmtln(w, ` %v "%v"`, name, imppath)
}
}
writefmtln(w, ")")
writefmtln(w, "")
// Now finally emit the actual body and close out the file.
writefmtln(w, "%v", body)
return w.Flush()
}
func (g *RPCGenerator) genFileBody(file string, pkg *Package, members []Member) string {
var buffer bytes.Buffer
w := bufio.NewWriter(&buffer)
// First, for each RPC struct/resource member, emit its appropriate generated code.
var typedefs []Typedef
var consts []*Const
@ -121,7 +174,8 @@ func (g *RPCGenerator) EmitFile(file string, pkg *Package, members []Member) err
g.EmitConstants(w, consts)
}
return w.Flush()
w.Flush()
return buffer.String()
}
// getFileModule generates a module name from a filename. To do so, we simply find the path part after the root and
@ -413,13 +467,13 @@ func (g *RPCGenerator) GenTypeName(t types.Type) string {
name := obj.Name()
// If this came from the same package, Go can access it without qualification.
if pkg == g.Currpkg.Pkginfo.Pkg {
if pkg == g.CurrPkg.Pkginfo.Pkg {
return name
}
// Otherwise, we will need to refer to a qualified import name.
// TODO: we will need to generate the right imports before we can emit such names.
contract.Failf("Cross-package IDL references not yet supported: pkg=%v name=%v", pkg, name)
impname := g.registerImport(pkg)
return fmt.Sprintf("%v.%v", impname, name)
case *types.Map:
return fmt.Sprintf("map[%v]%v", g.GenTypeName(u.Key()), g.GenTypeName(u.Elem()))
case *types.Pointer:
@ -432,6 +486,20 @@ func (g *RPCGenerator) GenTypeName(t types.Type) string {
return ""
}
// registerImport registers that we have seen a foreign package and requests that the imports be emitted for it.
func (g *RPCGenerator) registerImport(pkg *types.Package) string {
path := pkg.Path()
if impname, has := g.FileImports[path]; has {
return impname
}
// If we haven't seen this yet, allocate an import name for it. For now, we just use the package name with two
// leading underscores, to avoid accidental collisions with other names in the file.
name := "__" + pkg.Name()
g.FileImports[path] = name
return name
}
func (g *RPCGenerator) EmitTypedefs(w *bufio.Writer, typedefs []Typedef) {
writefmtln(w, "/* Typedefs */")
writefmtln(w, "")

View file

@ -15,16 +15,16 @@ import (
)
type Package struct {
Name tokens.PackageName // the name of the package.
Name tokens.PackageName // the package name.
Program *loader.Program // the fully parsed/analyzed Go program.
Pkginfo *loader.PackageInfo // the Go package information.
Files map[string]*File // the files inside of this package.
MemberFiles map[tokens.Name]*File // a map from member to the file containing it.
}
func NewPackage(nm tokens.PackageName, prog *loader.Program, pkginfo *loader.PackageInfo) *Package {
func NewPackage(name tokens.PackageName, prog *loader.Program, pkginfo *loader.PackageInfo) *Package {
return &Package{
Name: nm,
Name: name,
Program: prog,
Pkginfo: pkginfo,
Files: make(map[string]*File),