Delete the old IDL compiler (#801)

It is moving to a new location: https://github.com/pulumi/pidlc
This commit is contained in:
Joe Duffy 2018-01-13 15:11:52 -08:00 committed by GitHub
parent aa8f8eadcd
commit 34984ba1cb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 2 additions and 2142 deletions

8
Gopkg.lock generated
View file

@ -210,12 +210,6 @@
packages = ["collate","collate/build","internal/colltab","internal/gen","internal/tag","internal/triegen","internal/ucd","language","secure/bidirule","transform","unicode/bidi","unicode/cldr","unicode/norm","unicode/rangetable"]
revision = "88f656faf3f37f690df1a32515b479415e1a6769"
[[projects]]
branch = "master"
name = "golang.org/x/tools"
packages = ["go/ast/astutil","go/buildutil","go/loader"]
revision = "05e91d06384e3a4eb0aba75560d254b8d5a7b431"
[[projects]]
branch = "master"
name = "google.golang.org/genproto"
@ -249,6 +243,6 @@
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
inputs-digest = "70b9f724d4635dd578c12a951ddcfe2315a569443822eeb162fbba2cbcf7627e"
inputs-digest = "d03e73559b3e1af69207eeae805c237c8bef6f836a2322e883d428e0abef1810"
solver-name = "gps-cdcl"
solver-version = 1

View file

@ -15,7 +15,6 @@ TEST_FAST_TIMEOUT := 2m
build::
go install -ldflags "-X github.com/pulumi/pulumi/pkg/version.Version=${VERSION}" ${PROJECT}
go install -ldflags "-X github.com/pulumi/pulumi/pkg/version.Version=${VERSION}" ${PROJECT}/cmd/lumidl
install::
GOBIN=$(PULUMI_BIN) go install -ldflags "-X github.com/pulumi/pulumi/pkg/version.Version=${VERSION}" ${PROJECT}

View file

@ -62,7 +62,7 @@
<ItemGroup>
<NodeSdkProtosForBinplace Include="$(NodeSdkDirectory)\proto\nodejs\**\*" />
</ItemGroup>
<Copy SourceFiles="@(NodeSdkProtos)"
DestinationFolder="$(NodeJSSdkDirectory)\bin\proto" />
</Target>
@ -105,7 +105,6 @@
<Target Name="BuildGoCmds">
<ItemGroup>
<GoCmdsToBuild Include="github.com/pulumi/pulumi" />
<GoCmdsToBuild Include="github.com/pulumi/pulumi/cmd/lumidl" />
</ItemGroup>
<Exec Command="git describe --tags 2>nul" ConsoleToMSBuild="true" Condition="'$(Version)' == ''">

View file

@ -1,52 +0,0 @@
# Lumi IDL
The Lumi IDL compiler (LumIDL) compiles an Go-based IDL into Lumi metadata and packages.
## IDL
The compiler, `lumidl`, accepts a subset of Go. Please refer to [the IDL design document](/docs/idl.md) for details.
## Providers
The primary use case for Lumi IDL is to author resource packages and providers. A resource package is a low level
Lumi package with metadata associated with a set of resource type definitions. Its associated provider is a dynamic
plugin that implements the behavior associated with those resources, their CRUD functions, and operational semantics.
The LumIDL toolset cuts down on boilerplate and makes it easy to author new resource packages and providers.
## Building
To build the Lumi IDL compiler, run
$ go install github.com/pulumi/pulumi/cmd/lumidl
from an enlistment with a proper `GOPATH` set.
## Running
To generate code, run:
$ lumidl pkg-name idl-path [flags]
where the `pkg-name` is the name of the target Lumi package, `idl-path` is a path to a directory containing IDL, and
`flags` are an optional set of flags. All `*.go` files in the target directory are parsed and processed as IDL. To
recursively fetch sub-packages, pass the `--recursive` (or `-r`) flag.
The output includes the following:
* A Lumi package in LumiJS, containing resource definitions.
* An RPC package in Go to aid in the creation of a resource provider, containing:
- A base resource provider that handles marshaling goo at the edges.
- A marshalable type for each resource type (used for dynamic plugin serialization).
The two flags, `--out-pack` and `--out-rpc` control if and where the output will be generated, respectively. It is
possible to specify just one or the other, or you can generate both simultaneously.
By default, the IDL Go package is inferred from a combination of `idl-path` and `GOPATH`. This is used to generate
inter- and intra-package references. If you are generating RPC code, that too is inferred, based on `--out-rpc` and
`GOPATH`. In the event you are running outside of a Go workspace (where `GOPATH` is not set), or need to customize
these, the packages can be set by hand using the flags `--pkg-base-idl` and `--pkg-base-rpc`, respectively.
After generating the code and implementing behavior associated with the resource in the provider, the Lumi package
may then be distributed to consumers using LumiPy, LumiRu, LumiJS, and other LumiLangs.

View file

@ -1,76 +0,0 @@
// Copyright 2016-2017, Pulumi Corporation. All rights reserved.
package main
import (
"github.com/golang/glog"
"github.com/spf13/cobra"
"github.com/pulumi/pulumi/pkg/tokens"
"github.com/pulumi/pulumi/pkg/tools/lumidl"
"github.com/pulumi/pulumi/pkg/util/cmdutil"
)
func NewIDLCCmd() *cobra.Command {
var logToStderr bool
var outPack string
var outRPC string
var pkgBaseIDL string
var pkgBaseRPC string
var quiet bool
var recursive bool
var verbose int
cmd := &cobra.Command{
Use: "lumidl pkg-name idl-path",
Short: "The Lumi IDL compiler generates Lumi metadata and RPC stubs from IDL written in Go",
Long: "The Lumi IDL compiler generates Lumi 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 Lumi 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 source code.\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, true)
},
Args: cmdutil.ExactArgs(2),
Run: cmdutil.RunFunc(func(cmd *cobra.Command, args []string) error {
// Now pass the arguments and compile the package.
name := args[0] // the name of the Lumi package.
path := args[1] // the path to the IDL directory that is compiled recursively.
return lumidl.Compile(lumidl.CompileOptions{
Name: tokens.PackageName(name),
PkgBaseIDL: pkgBaseIDL,
PkgBaseRPC: pkgBaseRPC,
OutPack: outPack,
OutRPC: outRPC,
Quiet: quiet,
Recursive: recursive,
}, path)
}),
PersistentPostRun: func(cmd *cobra.Command, args []string) {
glog.Flush()
},
}
cmd.PersistentFlags().BoolVar(
&logToStderr, "logtostderr", false, "Log to stderr instead of to files")
cmd.PersistentFlags().BoolVarP(
&recursive, "recursive", "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().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")
return cmd
}

View file

@ -1,15 +0,0 @@
// Copyright 2016-2017, Pulumi Corporation. All rights reserved.
package main
import (
"fmt"
"os"
)
func main() {
if err := NewIDLCCmd().Execute(); err != nil {
fmt.Fprintf(os.Stderr, "An error occurred: %v\n", err)
os.Exit(-1)
}
}

View file

@ -1,389 +0,0 @@
// Copyright 2016-2017, Pulumi Corporation. All rights reserved.
package lumidl
import (
"go/ast"
"go/types"
"path/filepath"
"reflect"
"github.com/pkg/errors"
"golang.org/x/tools/go/loader"
"github.com/pulumi/pulumi/pkg/diag"
"github.com/pulumi/pulumi/pkg/tokens"
"github.com/pulumi/pulumi/pkg/util/cmdutil"
"github.com/pulumi/pulumi/pkg/util/contract"
)
type Checker struct {
Root string
Program *loader.Program
EnumValues map[types.Type][]string
}
func NewChecker(root string, prog *loader.Program) *Checker {
return &Checker{
Root: root,
Program: prog,
}
}
// diag produces a nice diagnostic location (document+position) from a Go element. It should be used for all output
// messages to enable easy correlation with the source IDL artifact that triggered an error.
func (chk *Checker) diag(elem goPos) diag.Diagable {
return goDiag(chk.Program, elem, chk.Root)
}
// Check analyzes a Go program, ensures that it is valid as an IDL, and classifies all of the types that it
// encounters. These classifications are returned. If problems are encountered, diagnostic messages will be output
// and the returned error will be non-nil.
func (chk *Checker) Check(name tokens.PackageName, pkginfo *loader.PackageInfo) (*Package, error) {
ok := true
// First just create a list of the constants and types so we can visit them in the right order. Also maintain a
// file map so that we can recover the AST information later on (required for import processing, etc).
var goconsts []*types.Const
var gotypes []*types.TypeName
// Enumerate the scope and classify all objects.
scope := pkginfo.Pkg.Scope()
for _, objname := range scope.Names() {
obj := scope.Lookup(objname)
switch o := obj.(type) {
case *types.Const:
goconsts = append(goconsts, o)
case *types.TypeName:
gotypes = append(gotypes, o)
default:
ok = false
cmdutil.Diag().Errorf(
diag.Message("%v is an unrecognized Go declaration type: %v").At(chk.diag(obj)),
objname, reflect.TypeOf(obj))
}
}
// Start building a package to return.
pkg := NewPackage(name, chk.Program, pkginfo)
oldenums := chk.EnumValues
chk.EnumValues = make(map[types.Type][]string)
defer (func() { chk.EnumValues = oldenums })()
getfile := func(path string) *File {
// If the file exists, fetch it.
if file, has := pkg.Files[path]; has {
return file
}
// Otherwise, find the AST node, and create a new object.
for _, fileast := range pkginfo.Files {
if rel := RelFilename(chk.Root, chk.Program, fileast); rel == path {
mod := string(name) + ":"
if ext := filepath.Ext(rel); ext != "" {
mod += rel[:len(rel)-len(ext)]
} else {
mod += rel
}
file := NewFile(path, tokens.Module(mod), fileast)
pkg.Files[path] = file
return file
}
}
contract.Failf("Missing file AST for path %v", path)
return nil
}
getdecl := func(file *File, obj types.Object) ast.Decl {
for _, decl := range file.Node.Decls {
if gdecl, isgdecl := decl.(*ast.GenDecl); isgdecl {
for _, spec := range gdecl.Specs {
switch sp := spec.(type) {
case *ast.ImportSpec:
// ignore
case *ast.TypeSpec:
if sp.Name.Name == obj.Name() {
return decl
}
case *ast.ValueSpec:
for _, name := range sp.Names {
if name.Name == obj.Name() {
return decl
}
}
default:
contract.Failf("Unrecognized GenDecl Spec type: %v", reflect.TypeOf(sp))
}
}
}
}
contract.Failf("Missing object AST decl for %v in %v", obj.Name(), file)
return nil
}
// Now visit all constants so that we can have them handy as we visit enum types.
for _, goconst := range goconsts {
path := RelFilename(chk.Root, chk.Program, goconst)
file := getfile(path)
decl := getdecl(file, goconst)
if c, cok := chk.CheckConst(goconst, file, decl); cok {
nm := tokens.Name(goconst.Name())
pkg.AddMember(file, nm, c)
} else {
contract.Assert(!cmdutil.Diag().Success())
ok = false
}
}
// Next, visit all the types.
for _, gotype := range gotypes {
path := RelFilename(chk.Root, chk.Program, gotype)
file := getfile(path)
decl := getdecl(file, gotype)
if t, tok := chk.CheckType(gotype, file, decl); tok {
nm := tokens.Name(gotype.Name())
pkg.AddMember(file, nm, t)
} else {
contract.Assert(!cmdutil.Diag().Success())
ok = false
}
}
if !ok {
contract.Assert(!cmdutil.Diag().Success())
return nil, errors.New("one or more problems with the input IDL were found; skipping code-generation")
}
return pkg, nil
}
func (chk *Checker) CheckConst(c *types.Const, file *File, decl ast.Decl) (*Const, bool) {
pt := c.Type()
var t types.Type
if IsPrimitive(pt) {
// A primitive, just use it as-is.
t = pt
} else if named, isnamed := pt.(*types.Named); isnamed {
// A constant of a type alias. This is how IDL enums are defined, so interpret it as such.
if basic, isbasic := named.Underlying().(*types.Basic); isbasic && basic.Kind() == types.String {
// Use this type and remember the enum value.
t = pt
chk.EnumValues[t] = append(chk.EnumValues[t], c.Val().String())
} else {
cmdutil.Diag().Errorf(
diag.Message("enums must be string-backed; %v has type %v").At(chk.diag(decl)),
c, named,
)
}
} else {
cmdutil.Diag().Errorf(
diag.Message("only constants of valid primitive types (bool, float64, number, or aliases) supported").At(
chk.diag(decl)))
}
if t != nil {
return &Const{
member: member{
tok: tokens.ModuleMember(string(file.Module) + ":" + c.Name()),
exported: c.Exported(),
pos: c.Pos(),
},
Type: pt,
Value: c.Val(),
}, true
}
return nil, false
}
func (chk *Checker) CheckType(t *types.TypeName, file *File, decl ast.Decl) (Member, bool) {
memb := member{
tok: tokens.ModuleMember(string(file.Module) + ":" + t.Name()),
exported: t.Exported(),
pos: t.Pos(),
}
switch typ := t.Type().(type) {
case *types.Named:
switch s := typ.Underlying().(type) {
case *types.Basic:
// A type alias, possibly interpreted as an enum if there are constants.
if IsPrimitive(s) {
if vals, isenum := chk.EnumValues[typ]; isenum {
// There are enum values defined, use them to create an enum type.
return &Enum{
member: memb,
Values: vals,
}, true
}
// Otherwise, this is a simple type alias.
return &Alias{
member: memb,
target: s,
}, true
}
cmdutil.Diag().Errorf(diag.Message(
"type alias %v is not a valid IDL alias type (must be bool, float64, or string)").At(
chk.diag(decl)))
case *types.Map, *types.Slice:
return &Alias{
member: memb,
target: s,
}, true
case *types.Struct:
// A struct definition, possibly a resource. First, check that all the fields are supported types.
isres := IsResource(t, s)
if ok, props, opts := chk.CheckStructFields(typ.Obj(), s, isres); ok {
// If a resource, return additional information.
if isres {
return &Resource{
member: memb,
s: s,
props: props,
popts: opts,
}, true
}
// Otherwise, it's a plain old ordinary struct.
return &Struct{
member: memb,
s: s,
props: props,
popts: opts,
}, true
}
contract.Assert(!cmdutil.Diag().Success())
default:
cmdutil.Diag().Errorf(
diag.Message("%v is an illegal underlying type: %v").At(chk.diag(decl)), s, reflect.TypeOf(s))
}
default:
cmdutil.Diag().Errorf(
diag.Message("%v is an illegal Go type kind: %v").At(chk.diag(decl)), t.Name(), reflect.TypeOf(typ))
}
return nil, false
}
// CheckStructFields ensures that a struct only contains valid "JSON-like" fields
func (chk *Checker) CheckStructFields(t *types.TypeName, s *types.Struct,
isres bool) (bool, []*types.Var, []PropertyOptions) {
ok := true
var allprops []*types.Var
var allopts []PropertyOptions
for i := 0; i < s.NumFields(); i++ {
fld := s.Field(i)
if fld.Anonymous() {
// If an embedded structure, validate its fields deeply.
anon := fld.Type().(*types.Named)
embedded := anon.Underlying().(*types.Struct)
isembres := IsResource(anon.Obj(), embedded)
isok, props, opts := chk.CheckStructFields(anon.Obj(), embedded, isembres)
if !isok {
ok = false
}
allprops = append(allprops, props...)
allopts = append(allopts, opts...)
} else {
allprops = append(allprops, fld)
opts := ParsePropertyOptions(s.Tag(i))
allopts = append(allopts, opts)
if opts.Name == "" {
ok = false
cmdutil.Diag().Errorf(
diag.Message("field %v.%v is missing a `pulumi:\"<name>\"` tag directive").At(chk.diag(fld)),
t.Name(), fld.Name())
}
if opts.Out && !isres {
ok = false
cmdutil.Diag().Errorf(
diag.Message("field %v.%v is marked `out` but is not a resource property").At(chk.diag(fld)),
t.Name(), fld.Name())
}
if opts.Replaces && !isres {
ok = false
cmdutil.Diag().Errorf(
diag.Message("field %v.%v is marked `replaces` but is not a resource property").At(chk.diag(fld)),
t.Name(), fld.Name())
}
if _, isptr := fld.Type().(*types.Pointer); !isptr && opts.Optional {
ok = false
cmdutil.Diag().Errorf(
diag.Message("field %v.%v is marked `optional` but is not a pointer in the IDL").At(chk.diag(fld)),
t.Name(), fld.Name())
}
if err := chk.CheckIDLType(fld.Type(), opts); err != nil {
ok = false
cmdutil.Diag().Errorf(
diag.Message("field %v.%v is an not a legal IDL type: %v").At(chk.diag(fld)),
t.Name(), fld.Name(), err)
}
}
}
return ok, allprops, allopts
}
func (chk *Checker) CheckIDLType(t types.Type, opts PropertyOptions) error {
// Only these types are legal:
// - Primitives: bool, float64, string
// - Other structs
// - Pointers to any of the above (if-and-only-if an optional property)
// - Pointers to other resource types (capabilities)
// - Arrays of the above things
// - Maps with string keys and any of the above as values
switch ft := t.(type) {
case *types.Basic:
if !IsPrimitive(ft) {
return errors.Errorf("bad primitive type %v; must be bool, float64, or string", ft)
}
case *types.Interface:
// interface{} is fine and is interpreted as a weakly typed map.
return nil
case *types.Named:
switch ut := ft.Underlying().(type) {
case *types.Basic:
// A named type alias of a primitive type. Ensure it is legal.
if !IsPrimitive(ut) {
return errors.Errorf(
"typedef %v backed by bad primitive type %v; must be bool, float64, or string", ft, ut)
}
case *types.Struct:
// Struct types are okay so long as they aren't entities (these are required to be pointers).
if isent := IsEntity(ft.Obj(), ut); isent {
return errors.Errorf("type %v cannot be referenced by-value; must be a pointer", ft)
}
default:
return errors.Errorf("bad named field type: %v", reflect.TypeOf(ut))
}
case *types.Pointer:
// A pointer is OK so long as the field is either optional or an entity type (asset, resource, etc).
if !opts.Optional && !opts.In && !opts.Out {
elem := ft.Elem()
var ok bool
if named, isnamed := elem.(*types.Named); isnamed {
ok = IsEntity(named.Obj(), named)
}
if !ok {
return errors.New("bad pointer; must be optional or a resource type")
}
}
case *types.Map:
// A map is OK so long as its key is a string (or string-backed type) and its element type is okay.
isstr := false
switch kt := ft.Key().(type) {
case *types.Basic:
isstr = (kt.Kind() == types.String)
case *types.Named:
if bt, isbt := kt.Underlying().(*types.Basic); isbt {
isstr = (bt.Kind() == types.String)
}
}
if !isstr {
return errors.Errorf("map index type %v must be a string (or string-backed typedef)", ft.Key())
}
return chk.CheckIDLType(ft.Elem(), PropertyOptions{})
case *types.Slice:
// A slice is OK so long as its element type is also OK.
return chk.CheckIDLType(ft.Elem(), PropertyOptions{})
default:
contract.Failf("Unrecognized field type %v: %v", t, reflect.TypeOf(t))
}
return nil
}

View file

@ -1,211 +0,0 @@
// Copyright 2016-2017, Pulumi Corporation. All rights reserved.
package lumidl
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/pulumi/pkg/tokens"
)
type CompileOptions struct {
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.
Recursive 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, 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 == "" {
if wd, err := os.Getwd(); err == nil {
path = wd
}
} else {
if absPath, err := filepath.Abs(path); err == nil {
path = absPath
}
}
if opts.PkgBaseIDL == "" {
// The default IDL package base is just the GOPATH package path for the target IDL path.
pkgpath, err := goPackagePath(path)
if err != nil {
return err
}
opts.PkgBaseIDL = pkgpath
}
if opts.OutPack != "" {
if outpack, err := filepath.Abs(opts.OutPack); err == nil {
opts.OutPack = outpack
}
}
if opts.OutRPC != "" {
if outrpc, err := filepath.Abs(opts.OutRPC); err == nil {
opts.OutRPC = 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.
pkgpath, err := goPackagePath(opts.OutRPC)
if err != nil {
return err
}
opts.PkgBaseRPC = pkgpath
}
}
var inputs []string
if opts.Recursive {
inp, err := gatherGoPackages(path)
if err != nil {
return err
}
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(inputs, false); err != nil {
return err
}
conf.ParserMode |= parser.ParseComments // ensure doc comments are retained.
prog, err := conf.Load()
if err != nil {
return err
}
// 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(path, prog)
var packgen *PackGenerator
if out := opts.OutPack; out != "" {
packgen = NewPackGenerator(prog, path, opts.PkgBaseIDL, out)
}
var rpcgen *RPCGenerator
if out := opts.OutRPC; out != "" {
rpcgen = NewRPCGenerator(path, opts.PkgBaseIDL, opts.PkgBaseRPC, out)
}
// 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(outpkg); err != nil {
return err
}
}
// Next generate the RPC stubs output.
if rpcgen != nil {
if err = rpcgen.Generate(outpkg); err != nil {
return err
}
}
}
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 !strings.HasPrefix(path, gopath+string(os.PathSeparator)) {
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
files, err := ioutil.ReadDir(path)
if err != nil {
return nil, err
}
for _, file := range files {
if file.IsDir() {
dirs = append(dirs, file.Name())
} else if filepath.Ext(file.Name()) == ".go" {
hasGoFiles = true
}
}
if hasGoFiles {
pkg, err := goPackagePath(path)
if err != nil {
return nil, err
}
pkgs = append(pkgs, pkg)
}
// Next, enumerate all directories recursively, to find all Go sub-packages.
for _, dir := range dirs {
subpkgs, err := gatherGoPackages(filepath.Join(path, dir))
if err != nil {
return nil, err
}
pkgs = append(pkgs, subpkgs...)
}
return pkgs, nil
}

View file

@ -1,46 +0,0 @@
// Copyright 2016-2017, Pulumi Corporation. All rights reserved.
package lumidl
import (
"go/token"
"path/filepath"
"golang.org/x/tools/go/loader"
"github.com/pulumi/pulumi/pkg/diag"
"github.com/pulumi/pulumi/pkg/util/contract"
)
type goPos interface {
Pos() token.Pos
}
// goDiag produces a diagnostics object out of a Go type artifact.
func goDiag(prog *loader.Program, elem goPos, relto string) diag.Diagable {
pos := prog.Fset.Position(elem.Pos())
file := pos.Filename
if relto != "" {
var err error
file, err = filepath.Rel(relto, file)
contract.Assertf(err == nil, "error: %v", err)
}
return &goDiagable{
doc: diag.NewDocument(file),
loc: &diag.Location{
Start: diag.Pos{
Line: pos.Line,
Column: pos.Column,
},
},
}
}
type goDiagable struct {
doc *diag.Document
loc *diag.Location
}
func (d *goDiagable) Where() (*diag.Document, *diag.Location) {
return d.doc, d.loc
}

View file

@ -1,51 +0,0 @@
// Copyright 2016-2017, Pulumi Corporation. All rights reserved.
package lumidl
import (
"go/types"
"path/filepath"
"github.com/pulumi/pulumi/pkg/tools"
)
const lumidl = "the Lumi IDL Compiler (LUMIDL)" // used in generated files.
// mirrorDirLayout ensures a target output directory contains the same layout as the input package.
func mirrorDirLayout(pkg *Package, out string) error {
for relpath := range pkg.Files {
// Make the target file by concatening the output with the relative path, and ensure the directory exists.
path := filepath.Join(out, relpath)
if err := tools.EnsureFileDir(path); err != nil {
return err
}
}
return nil
}
func forEachField(t TypeMember, action func(*types.Var, PropertyOptions)) int {
return forEachStructField(t.Struct(), t.PropertyOptions(), action)
}
func forEachStructField(s *types.Struct, opts []PropertyOptions, action func(*types.Var, PropertyOptions)) int {
n := 0
for i, j := 0, 0; i < s.NumFields(); i++ {
fld := s.Field(i)
if fld.Anonymous() {
// For anonymous types, recurse.
named := fld.Type().(*types.Named)
embedded := named.Underlying().(*types.Struct)
k := forEachStructField(embedded, opts[j:], action)
j += k
n += k
} else {
// For actual fields, invoke the action, and bump the counters.
if action != nil {
action(s.Field(i), opts[j])
}
j++
n++
}
}
return n
}

View file

@ -1,445 +0,0 @@
// Copyright 2016-2017, Pulumi Corporation. All rights reserved.
package lumidl
import (
"fmt"
"go/types"
"path/filepath"
"reflect"
"sort"
"strings"
"golang.org/x/tools/go/loader"
"github.com/pulumi/pulumi/pkg/tokens"
"github.com/pulumi/pulumi/pkg/tools"
"github.com/pulumi/pulumi/pkg/util/contract"
)
// TODO[pulumi/pulumi#139]: preserve GoDocs in the generated code.
type PackGenerator struct {
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.
}
type MemberImports map[tokens.Name]string
func NewPackGenerator(prog *loader.Program, root string, pkgBase string, out string) *PackGenerator {
return &PackGenerator{
Program: prog,
IDLRoot: root,
IDLPkgBase: pkgBase,
Out: out,
}
}
// 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 Lumi package's source code from a given compiled IDL program.
func (g *PackGenerator) Generate(pkg *Package) error {
// Ensure the directory structure exists in the target.
if err := mirrorDirLayout(pkg, g.Out); err != nil {
return err
}
// Install context about the current entity being visited.
oldpkg, oldfile := g.CurrPkg, g.CurrFile
g.CurrPkg = pkg
defer (func() {
g.CurrPkg, g.CurrFile = oldpkg, oldfile
})()
// Now walk through the package, file by file, and generate the contents.
for relpath, file := range pkg.Files {
var members []Member
for _, nm := range file.MemberNames {
members = append(members, file.Members[nm])
}
g.CurrFile = relpath
path := filepath.Join(g.Out, relpath)
if err := g.EmitFile(path, members); err != nil {
return err
}
}
return nil
}
func (g *PackGenerator) EmitFile(file string, members []Member) error {
// Set up context.
oldHadRes, oldImports := g.FileHadRes, g.FileImports
g.FileHadRes, g.FileImports = false, make(map[string]MemberImports)
defer (func() {
g.FileHadRes = oldHadRes
g.FileImports = oldImports
})()
// First, generate the body. This is required first so we know which imports to emit.
body := g.genFileBody(members)
// Next actually open up the file and emit the header, imports, and the body of the module.
return g.emitFileContents(file, body)
}
func (g *PackGenerator) emitFileContents(file string, body string) error {
// The output is TypeScript, so alter the extension.
if dotindex := strings.LastIndex(file, "."); dotindex != -1 {
file = file[:dotindex]
}
file += ".ts"
// Open up a writer that overwrites whatever file contents already exist.
w, err := tools.NewGenWriter(lumidl, file)
if err != nil {
return err
}
defer contract.IgnoreClose(w)
// Emit a header into the file.
w.EmitHeaderWarning()
w.Writefmtln("/* tslint:disable:ordered-imports variable-name */")
// If there are any resources, import the Lumi package.
if g.FileHadRes {
w.Writefmtln("import * as fabric from \"@pulumi/pulumi\";")
w.Writefmtln("")
}
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)
}
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.
w.Writefmt("import {")
for i, member := range members {
if i > 0 {
w.Writefmt(", ")
}
w.Writefmt(string(member))
}
w.Writefmtln("} from \"%v\";", impname)
}
}
w.Writefmtln("")
}
w.Writefmt("%v", body)
return nil
}
func (g *PackGenerator) genFileBody(members []Member) string {
// Accumulate the buffer in a string.
w, err := tools.NewGenWriter(lumidl, "")
contract.IgnoreError(err)
// Now go ahead and emit the code for all members of this package.
for i, m := range members {
if i > 0 {
// Allow aliases and consts to pile up without line breaks.
_, isalias := m.(*Alias)
_, isconst := m.(*Const)
if (!isalias && !isconst) || reflect.TypeOf(m) != reflect.TypeOf(members[i-1]) {
w.Writefmtln("")
}
}
switch t := m.(type) {
case *Alias:
g.EmitAlias(w, t)
case *Const:
g.EmitConst(w, t)
case *Enum:
g.EmitEnum(w, t)
case *Resource:
g.EmitResource(w, t)
case *Struct:
g.EmitStruct(w, t)
default:
contract.Failf("Unrecognized package member type: %v", reflect.TypeOf(m))
}
}
w.Writefmtln("")
err = w.Flush()
contract.IgnoreError(err)
return w.Buffer()
}
func (g *PackGenerator) EmitAlias(w *tools.GenWriter, alias *Alias) {
w.Writefmtln("export type %v = %v;",
alias.Name(), g.GenTypeName(alias.Target(), NormalField))
}
func (g *PackGenerator) EmitConst(w *tools.GenWriter, konst *Const) {
w.Writefmtln("export let %v: %v = %v;",
konst.Name(), g.GenTypeName(konst.Type, NormalField), konst.Value.String())
}
func (g *PackGenerator) EmitEnum(w *tools.GenWriter, enum *Enum) {
w.Writefmtln("export type %v =", enum.Name())
contract.Assert(len(enum.Values) > 0)
for i, value := range enum.Values {
if i > 0 {
w.Writefmtln(" |")
}
w.Writefmt(" %v", value)
}
w.Writefmtln(";")
}
func (g *PackGenerator) EmitResource(w *tools.GenWriter, res *Resource) {
// Emit the full resource class definition, including constructor, etc.
g.emitResourceClass(w, res)
w.Writefmtln("")
// Finally, emit an entire struct type for the args interface.
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.FileHadRes = true
}
func (g *PackGenerator) emitResourceClass(w *tools.GenWriter, res *Resource) {
// Emit the class definition itself.
name := res.Name()
w.Writefmtln("export class %s extends fabric.Resource {", name)
// First, emit all fields definitions.
hasArgs := false
hasRequiredArgs := false
fn := forEachField(res, func(fld *types.Var, opt PropertyOptions) {
g.emitField(w, fld, ComputedField, opt, " public ")
if !opt.Out {
hasArgs = true
if !opt.Optional {
hasRequiredArgs = true
}
}
})
if fn > 0 {
w.Writefmtln("")
}
// Add the standard "factory" functions: get and query. These are static, so they go before the constructor.
w.Writefmtln(" public static get(id: fabric.ID): %s {", name)
w.Writefmtln(" return <any>undefined; // functionality provided by the runtime")
w.Writefmtln(" }")
w.Writefmtln("")
w.Writefmtln(" public static query(q: any): %s[] {", name)
w.Writefmtln(" return <any>undefined; // functionality provided by the runtime")
w.Writefmtln(" }")
w.Writefmtln("")
// Next, a constructor that validates arguments and chains to the base constructor.
var opt string
if !hasRequiredArgs {
opt = "?"
}
w.Writefmtln(" constructor(name: string, args%s: %sArgs) {", opt, name)
// First, validate that required parameters exist, and store all arguments on the object.
argLinePrefix := " "
needsArgsCheck := hasArgs && !hasRequiredArgs
if needsArgsCheck {
w.Writefmtln(" if (args !== undefined) {")
argLinePrefix += " "
}
forEachField(res, func(fld *types.Var, opt PropertyOptions) {
if !opt.Out && !opt.Optional {
w.Writefmtln("%sif (args.%s === undefined) {", argLinePrefix, opt.Name)
w.Writefmtln("%s throw new Error(\"Missing required property '%s'\");", argLinePrefix, opt.Name)
w.Writefmtln("%s}", argLinePrefix)
}
})
if needsArgsCheck {
w.Writefmtln(" }")
}
// Now invoke the base.
w.Writefmtln(" super(\"%s\", name, {", res.Tok())
forEachField(res, func(fld *types.Var, opt PropertyOptions) {
w.Writefmt(" \"%s\": ", opt.Name)
if opt.Out {
w.Writefmtln("undefined,")
} else {
w.Writefmtln("args.%s,", opt.Name)
}
})
w.Writefmtln(" });")
w.Writefmtln(" }")
w.Writefmtln("}")
}
func (g *PackGenerator) EmitStruct(w *tools.GenWriter, s *Struct) {
g.emitStructType(w, s, s.Name())
}
func (g *PackGenerator) emitStructType(w *tools.GenWriter, t TypeMember, name tokens.Name) {
w.Writefmtln(fmt.Sprintf("export interface %s {", name))
forEachField(t, func(fld *types.Var, opt PropertyOptions) {
if opt.Out {
return // skip output properties, since those exist solely on the resource class.
}
g.emitField(w, fld, MaybeComputedField, opt, " ")
})
w.Writefmtln("}")
}
type FieldKind int
const (
NormalField FieldKind = iota
ComputedField
MaybeComputedField
)
func (g *PackGenerator) emitField(w *tools.GenWriter, fld *types.Var,
kind FieldKind, opt PropertyOptions, prefix string) {
var mods string
if opt.Replaces {
mods += "readonly "
}
if opt.Out {
mods += "/*out*/ "
}
var optional string
if opt.Optional {
optional = "?"
}
typ := g.GenTypeName(fld.Type(), kind)
if kind == ComputedField {
typ = fmt.Sprintf("fabric.Computed<%v>", typ)
}
w.Writefmtln("%v%v%v%v: %v;", prefix, mods, opt.Name, optional, typ)
}
func (g *PackGenerator) GenTypeName(t types.Type, kind FieldKind) string {
var ret string
var skipwrap bool
switch u := t.(type) {
case *types.Basic:
switch k := u.Kind(); k {
case types.Bool:
ret = "boolean"
case types.String:
ret = "string"
case types.Float64:
ret = "number"
default:
contract.Failf("Unrecognized GenTypeName basic type: %v", k)
}
case *types.Interface:
ret = "any"
case *types.Named:
obj := u.Obj()
if spec, kind := IsSpecial(obj); spec {
switch kind {
case SpecialArchiveType:
ret = "fabric.asset.Archive"
case SpecialAssetType:
ret = "fabric.asset.Asset"
case SpecialResourceType:
ret = "fabric.Resource"
default:
contract.Failf("Unrecognized special type: %v", kind)
}
} else {
// Our import logic will have arranged for the type name to be available.
// IDEA: consider auto-generated import names to avoid conflicts between imported and local names.
g.trackNameReference(obj)
ret = obj.Name()
}
case *types.Map:
ret = fmt.Sprintf("{[key: %s]: %s}", g.GenTypeName(u.Key(), kind), g.GenTypeName(u.Elem(), kind))
case *types.Pointer:
ret = g.GenTypeName(u.Elem(), kind) // no pointers in TypeScript, just emit the underlying type.
skipwrap = true // don't doubly emit wrapper types
case *types.Slice:
ret = fmt.Sprintf("%s[]", g.GenTypeName(u.Elem(), kind)) // postfix syntax for arrays.
default:
contract.Failf("Unrecognized GenTypeName type: %s", reflect.TypeOf(u))
}
if !skipwrap && kind == MaybeComputedField {
ret = fmt.Sprintf("fabric.MaybeComputed<%s>", ret)
}
return ret
}
// 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

@ -1,547 +0,0 @@
// Copyright 2016-2017, Pulumi Corporation. All rights reserved.
package lumidl
import (
"fmt"
"go/types"
"path/filepath"
"reflect"
"sort"
"strings"
"github.com/pulumi/pulumi/pkg/tokens"
"github.com/pulumi/pulumi/pkg/tools"
"github.com/pulumi/pulumi/pkg/util/contract"
)
type RPCGenerator struct {
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.
FileHadRes bool // true if the file had at least one resource.
FileImports map[string]string // a map of foreign packages used in a file.
}
func NewRPCGenerator(root, idlPkgBase, rpcPkgBase, out string) *RPCGenerator {
return &RPCGenerator{
IDLRoot: root,
IDLPkgBase: idlPkgBase,
RPCPkgBase: rpcPkgBase,
Out: out,
}
}
func (g *RPCGenerator) Generate(pkg *Package) error {
// Ensure the directory structure exists in the target.
if err := mirrorDirLayout(pkg, g.Out); err != nil {
return err
}
// Install context about the current entity being visited.
oldpkg, oldfile := g.CurrPkg, g.CurrFile
g.CurrPkg = pkg
defer (func() {
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])
}
path := filepath.Join(g.Out, relpath)
if err := g.EmitFile(path, pkg, members); err != nil {
return err
}
}
return nil
}
func (g *RPCGenerator) EmitFile(file string, pkg *Package, members []Member) error {
oldHadRes, oldImports := g.FileHadRes, g.FileImports
g.FileHadRes, g.FileImports = false, make(map[string]string)
defer (func() {
g.FileHadRes = oldHadRes
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.
w, err := tools.NewGenWriter(lumidl, file)
if err != nil {
return err
}
defer contract.IgnoreClose(w)
// Emit a header into the file.
w.EmitHeaderWarning()
// Now emit the package name at the top-level.
w.Writefmtln("package %v", pkg.Pkginfo.Pkg.Name())
w.Writefmtln("")
// And all of the imports that we're going to need.
if g.FileHadRes || len(g.FileImports) > 0 {
w.Writefmtln("import (")
if g.FileHadRes {
w.Writefmtln(` pbempty "github.com/golang/protobuf/ptypes/empty"`)
w.Writefmtln(` pbstruct "github.com/golang/protobuf/ptypes/struct"`)
w.Writefmtln(` "golang.org/x/net/context"`)
w.Writefmtln("")
w.Writefmtln(` "github.com/pulumi/pulumi/pkg/resource"`)
w.Writefmtln(` "github.com/pulumi/pulumi/pkg/resource/plugin"`)
w.Writefmtln(` "github.com/pulumi/pulumi/pkg/tokens"`)
w.Writefmtln(` "github.com/pulumi/pulumi/pkg/util/contract"`)
w.Writefmtln(` "github.com/pulumi/pulumi/pkg/util/mapper"`)
w.Writefmtln(` lumirpc "github.com/pulumi/pulumi/sdk/proto/go"`)
}
if len(g.FileImports) > 0 {
if g.FileHadRes {
w.Writefmtln("")
}
// 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
}
w.Writefmtln(` %v "%v"`, name, imppath)
}
}
w.Writefmtln(")")
w.Writefmtln("")
}
// Now finally emit the actual body and close out the file.
w.Writefmtln("%v", body)
return w.Flush()
}
func (g *RPCGenerator) genFileBody(file string, pkg *Package, members []Member) string {
w, err := tools.NewGenWriter(lumidl, "")
contract.IgnoreError(err)
// First, for each RPC struct/resource member, emit its appropriate generated code.
var typedefs []Typedef
var consts []*Const
module := g.getFileModule(file)
for _, m := range members {
switch t := m.(type) {
case *Alias:
typedefs = append(typedefs, t)
case *Const:
consts = append(consts, t)
case *Enum:
typedefs = append(typedefs, t)
case *Resource:
g.EmitResource(w, module, pkg, t)
g.EmitStructType(w, module, pkg, t)
case *Struct:
g.EmitStructType(w, module, pkg, t)
default:
contract.Failf("Unrecognized package member type: %v", reflect.TypeOf(t))
}
}
// Next emit all supporting types. First, aliases and enum types.
if len(typedefs) > 0 {
g.EmitTypedefs(w, typedefs)
}
// Finally, emit any consts at the very end.
if len(consts) > 0 {
g.EmitConstants(w, consts)
}
err = w.Flush()
contract.IgnoreError(err)
return w.Buffer()
}
// getFileModule generates a module name from a filename. To do so, we simply find the path part after the root and
// remove any file extensions, to get the underlying package's module token.
func (g *RPCGenerator) getFileModule(file string) tokens.Module {
module, _ := filepath.Rel(g.Out, file)
if ext := filepath.Ext(module); ext != "" {
extix := strings.LastIndex(module, ext)
module = module[:extix]
}
return tokens.Module(module)
}
func (g *RPCGenerator) EmitResource(w *tools.GenWriter, module tokens.Module, pkg *Package, res *Resource) {
name := res.Name()
w.Writefmtln("/* RPC stubs for %v resource provider */", name)
w.Writefmtln("")
// Remember when we encounter resources so we can import the right packages.
g.FileHadRes = true
propopts := res.PropertyOptions()
var hasinputs bool
for _, propopt := range propopts {
if !propopt.Out {
hasinputs = true
break
}
}
// Emit a type token.
token := fmt.Sprintf("%v:%v:%v", pkg.Name, module, name)
w.Writefmtln("// %[1]vToken is the type token corresponding to the %[1]v package type.", name)
w.Writefmtln(`const %vToken = tokens.Type("%v")`, name, token)
w.Writefmtln("")
// Now, generate an ops interface that the real provider will implement.
w.Writefmtln("// %[1]vProviderOps is a pluggable interface for %[1]v-related management functionality.", name)
w.Writefmtln("type %vProviderOps interface {", name)
w.Writefmtln(" Configure(ctx context.Context, vars map[tokens.ModuleMember]string) error")
w.Writefmtln(" Check(ctx context.Context, obj *%v, property string) error", name)
w.Writefmtln(" Diff(ctx context.Context, id resource.ID,")
w.Writefmtln(" old *%[1]v, new *%[1]v, diff *resource.ObjectDiff) ([]string, error)", name)
w.Writefmtln(" Create(ctx context.Context, obj *%v) (resource.ID, error)", name)
w.Writefmtln(" Update(ctx context.Context, id resource.ID,")
w.Writefmtln(" old *%[1]v, new *%[1]v, diff *resource.ObjectDiff) error", name)
w.Writefmtln(" Delete(ctx context.Context, id resource.ID, obj %v) error", name)
w.Writefmtln("}")
w.Writefmtln("")
// Next generate all the RPC scaffolding goo
w.Writefmtln("// %[1]vProvider is a dynamic gRPC-based plugin for managing %[1]v resources.", name)
w.Writefmtln("type %vProvider struct {", name)
w.Writefmtln(" ops %vProviderOps", name)
w.Writefmtln("}")
w.Writefmtln("")
w.Writefmtln("// New%vProvider allocates a resource provider that delegates to a ops instance.", name)
w.Writefmtln("func New%[1]vProvider(ops %[1]vProviderOps) lumirpc.ResourceProviderServer {", name)
w.Writefmtln(" contract.Assert(ops != nil)")
w.Writefmtln(" return &%vProvider{ops: ops}", name)
w.Writefmtln("}")
w.Writefmtln("")
w.Writefmtln("func (p *%vProvider) Configure(", name)
w.Writefmtln(" ctx context.Context, req *lumirpc.ConfigureRequest) (*pbempty.Empty, error) {")
w.Writefmtln(" vars := make(map[tokens.ModuleMember]string)")
w.Writefmtln(" for k, v := range req.GetVariables() {")
w.Writefmtln(" vars[tokens.ModuleMember(k)] = v")
w.Writefmtln(" }")
w.Writefmtln(" if err := p.ops.Configure(ctx, vars); err != nil {")
w.Writefmtln(" return nil, err")
w.Writefmtln(" }")
w.Writefmtln(" return &pbempty.Empty{}, nil")
w.Writefmtln("}")
w.Writefmtln("")
w.Writefmtln("func (p *%vProvider) Check(", name)
w.Writefmtln(" ctx context.Context, req *lumirpc.CheckRequest) (*lumirpc.CheckResponse, error) {")
w.Writefmtln(" contract.Assert(resource.URN(req.GetUrn()).Type() == %vToken)", name)
w.Writefmtln(" obj, _, err := p.Unmarshal(req.GetProperties(), true)")
w.Writefmtln(" if obj == nil || err != nil {")
w.Writefmtln(" return plugin.NewCheckResponse(err), nil")
w.Writefmtln(" }")
w.Writefmtln(" var failures []error")
// check global properties:
w.Writefmtln(" if failure := p.ops.Check(ctx, obj, \"\"); failure != nil {")
w.Writefmtln(" failures = append(failures, failure)")
w.Writefmtln(" }")
// check each input property:
if hasinputs {
for _, opts := range propopts {
if !opts.Out {
w.Writefmtln(" if failure := p.ops.Check(ctx, obj, \"%v\"); failure != nil {", opts.Name)
w.Writefmtln(" failures = append(failures,")
w.Writefmtln(" resource.NewPropertyError(\"%v\", \"%v\", failure))", name, opts.Name)
w.Writefmtln(" }")
}
}
w.Writefmtln(" if len(failures) > 0 {")
w.Writefmtln(" return plugin.NewCheckResponse(resource.NewErrors(failures)), nil")
w.Writefmtln(" }")
}
w.Writefmtln(" return plugin.NewCheckResponse(nil), nil")
w.Writefmtln("}")
w.Writefmtln("")
w.Writefmtln("func (p *%vProvider) Create(", name)
w.Writefmtln(" ctx context.Context, req *lumirpc.CreateRequest) (*lumirpc.CreateResponse, error) {")
w.Writefmtln(" contract.Assert(resource.URN(req.GetUrn()).Type() == %vToken)", name)
w.Writefmtln(" obj, _, err := p.Unmarshal(req.GetProperties(), false)")
w.Writefmtln(" if err != nil {")
w.Writefmtln(" return nil, err")
w.Writefmtln(" }")
w.Writefmtln(" id, err := p.ops.Create(ctx, obj)")
w.Writefmtln(" if err != nil {")
w.Writefmtln(" return nil, err")
w.Writefmtln(" }")
w.Writefmtln(" props, err := plugin.MarshalProperties(")
w.Writefmtln(" resource.NewPropertyMap(obj), plugin.MarshalOptions{})")
w.Writefmtln(" if err != nil {")
w.Writefmtln(" return nil, err")
w.Writefmtln(" }")
w.Writefmtln(" return &lumirpc.CreateResponse{Id: string(id), Properties: props}, nil")
w.Writefmtln("}")
w.Writefmtln("")
w.Writefmtln("func (p *%vProvider) Diff(", name)
w.Writefmtln(" ctx context.Context, req *lumirpc.DiffRequest) (*lumirpc.DiffResponse, error) {")
w.Writefmtln(" contract.Assert(resource.URN(req.GetUrn()).Type() == %vToken)", name)
w.Writefmtln(" id := resource.ID(req.GetId())")
w.Writefmtln(" old, oldprops, err := p.Unmarshal(req.GetOlds(), false)")
w.Writefmtln(" if err != nil {")
w.Writefmtln(" return nil, err")
w.Writefmtln(" }")
w.Writefmtln(" new, newprops, err := p.Unmarshal(req.GetNews(), true)")
w.Writefmtln(" if new == nil || err != nil {")
w.Writefmtln(" return nil, err")
w.Writefmtln(" }")
w.Writefmtln(" var replaces []string")
w.Writefmtln(" diff := oldprops.Diff(newprops)")
w.Writefmtln(" if diff != nil {")
for _, opts := range propopts {
if opts.Replaces {
w.Writefmtln(" if diff.Changed(\"%v\") {", opts.Name)
w.Writefmtln(" replaces = append(replaces, \"%v\")", opts.Name)
w.Writefmtln(" }")
}
}
w.Writefmtln(" }")
w.Writefmtln(" more, err := p.ops.Diff(ctx, id, old, new, diff)")
w.Writefmtln(" if err != nil {")
w.Writefmtln(" return nil, err")
w.Writefmtln(" }")
w.Writefmtln(" return &lumirpc.DiffResponse{")
w.Writefmtln(" Replaces: append(replaces, more...),")
w.Writefmtln(" }, err")
w.Writefmtln("}")
w.Writefmtln("")
w.Writefmtln("func (p *%vProvider) Update(", name)
w.Writefmtln(" ctx context.Context, req *lumirpc.UpdateRequest) (*lumirpc.UpdateResponse, error) {")
w.Writefmtln(" contract.Assert(resource.URN(req.GetUrn()).Type() == %vToken)", name)
w.Writefmtln(" id := resource.ID(req.GetId())")
w.Writefmtln(" old, oldprops, err := p.Unmarshal(req.GetOlds(), false)")
w.Writefmtln(" if err != nil {")
w.Writefmtln(" return nil, err")
w.Writefmtln(" }")
w.Writefmtln(" new, newprops, err := p.Unmarshal(req.GetNews(), false)")
w.Writefmtln(" if err != nil {")
w.Writefmtln(" return nil, err")
w.Writefmtln(" }")
w.Writefmtln(" diff := oldprops.Diff(newprops)")
w.Writefmtln(" if err := p.ops.Update(ctx, id, old, new, diff); err != nil {")
w.Writefmtln(" return nil, err")
w.Writefmtln(" }")
w.Writefmtln(" props, err := plugin.MarshalProperties(")
w.Writefmtln(" resource.NewPropertyMap(new), plugin.MarshalOptions{})")
w.Writefmtln(" if err != nil {")
w.Writefmtln(" return nil, err")
w.Writefmtln(" }")
w.Writefmtln(" return &lumirpc.UpdateResponse{Properties: props}, nil")
w.Writefmtln("}")
w.Writefmtln("")
w.Writefmtln("func (p *%vProvider) Delete(", name)
w.Writefmtln(" ctx context.Context, req *lumirpc.DeleteRequest) (*pbempty.Empty, error) {")
w.Writefmtln(" contract.Assert(resource.URN(req.GetUrn()).Type() == %vToken)", name)
w.Writefmtln(" id := resource.ID(req.GetId())")
w.Writefmtln(" obj, _, err := p.Unmarshal(req.GetProperties(), false)")
w.Writefmtln(" if err != nil {")
w.Writefmtln(" return nil, err")
w.Writefmtln(" }")
w.Writefmtln(" if err := p.ops.Delete(ctx, id, *obj); err != nil {")
w.Writefmtln(" return nil, err")
w.Writefmtln(" }")
w.Writefmtln(" return &pbempty.Empty{}, nil")
w.Writefmtln("}")
w.Writefmtln("")
w.Writefmtln("func (p *%vProvider) Unmarshal(", name)
w.Writefmtln(" v *pbstruct.Struct, allowUnknowns bool) (*%v, resource.PropertyMap, error) {", name)
w.Writefmtln(" opts := plugin.MarshalOptions{AllowUnknowns: allowUnknowns}")
w.Writefmtln(" props, err := plugin.UnmarshalProperties(v, opts)")
w.Writefmtln(" if err != nil {")
w.Writefmtln(" return nil, nil, err")
w.Writefmtln(" }")
w.Writefmtln(" if allowUnknowns && props.ContainsUnknowns() {")
w.Writefmtln(" return nil, props, nil")
w.Writefmtln(" }")
w.Writefmtln(" var obj %v", name)
w.Writefmtln(" return &obj, props, mapper.MapIU(props.Mappable(), &obj)")
w.Writefmtln("}")
w.Writefmtln("")
}
func (g *RPCGenerator) EmitStructType(w *tools.GenWriter, module tokens.Module, pkg *Package, t TypeMember) {
name := t.Name()
w.Writefmtln("/* Marshalable %v structure(s) */", name)
w.Writefmtln("")
props := t.Properties()
propopts := t.PropertyOptions()
w.Writefmtln("// %v is a marshalable representation of its corresponding IDL type.", name)
w.Writefmtln("type %v struct {", name)
for i, prop := range props {
opts := propopts[i]
// Make a JSON tag for this so we can serialize; note that outputs are always optional in this position.
jsontag := makeLumiTag(opts)
w.Writefmtln(" %v %v %v",
prop.Name(), g.GenTypeName(prop.Type(), opts.Optional || opts.In || opts.Out), jsontag)
}
w.Writefmtln("}")
w.Writefmtln("")
if len(props) > 0 {
w.Writefmtln("// %v's properties have constants to make dealing with diffs and property bags easier.", name)
w.Writefmtln("const (")
for i, prop := range props {
opts := propopts[i]
w.Writefmtln(" %v_%v = \"%v\"", name, prop.Name(), opts.Name)
}
w.Writefmtln(")")
w.Writefmtln("")
}
}
// makeLumiTag turns a set of property options into a serializable JSON tag.
func makeLumiTag(opts PropertyOptions) string {
var flags string
if opts.Optional || opts.In || opts.Out {
flags = ",optional"
}
return fmt.Sprintf("`pulumi:\"%v%v\"`", opts.Name, flags)
}
func (g *RPCGenerator) GenTypeName(t types.Type, opt bool) string {
switch u := t.(type) {
case *types.Basic:
switch k := u.Kind(); k {
case types.Bool:
return "bool"
case types.String:
return "string"
case types.Float64:
return "float64"
default:
contract.Failf("Unrecognized GenTypeName basic type: %v", k)
}
case *types.Interface:
return "interface{}"
case *types.Named:
obj := u.Obj()
// For resource types, simply emit an ID, since that is what will have been serialized.
if IsResource(obj, u) {
return "resource.ID"
}
// For references to the special predefined types, use the runtime provider representation.
if spec, kind := IsSpecial(obj); spec {
switch kind {
case SpecialArchiveType:
return "resource.Archive"
case SpecialAssetType:
return "resource.Asset"
default:
contract.Failf("Unexpected special kind: %v", kind)
}
}
// Otherwise, see how to reference the type, based on imports.
pkg := obj.Pkg()
name := obj.Name()
// If this came from the same package, Go can access it without qualification.
if pkg == g.CurrPkg.Pkginfo.Pkg {
return name
}
// Otherwise, we will need to refer to a qualified import 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(), false), g.GenTypeName(u.Elem(), false))
case *types.Pointer:
// If this isn't an optional property, and the underlying type is a resource or special type, unpointerize it.
elem := u.Elem()
unptr := false
if !opt {
if elnm, iselnm := elem.(*types.Named); iselnm {
if IsResource(elnm.Obj(), elnm) {
unptr = true
} else if spec, _ := IsSpecial(elnm.Obj()); spec {
unptr = true
}
}
}
if unptr {
return g.GenTypeName(elem, false)
}
return fmt.Sprintf("*%v", g.GenTypeName(u.Elem(), false))
case *types.Slice:
return fmt.Sprintf("[]%v", g.GenTypeName(u.Elem(), false)) // postfix syntax for arrays.
default:
contract.Failf("Unrecognized GenTypeName type: %v", reflect.TypeOf(u))
}
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 *tools.GenWriter, typedefs []Typedef) {
w.Writefmtln("/* Typedefs */")
w.Writefmtln("")
w.Writefmtln("type (")
for _, td := range typedefs {
w.Writefmtln(" %v %v", td.Name(), td.Target())
}
w.Writefmtln(")")
w.Writefmtln("")
}
func (g *RPCGenerator) EmitConstants(w *tools.GenWriter, consts []*Const) {
w.Writefmtln("/* Constants */")
w.Writefmtln("")
w.Writefmtln("const (")
for _, konst := range consts {
w.Writefmtln(" %v %v = %v", konst.Name(), g.GenTypeName(konst.Type, false), konst.Value)
}
w.Writefmtln(")")
w.Writefmtln("")
}

View file

@ -1,49 +0,0 @@
// Copyright 2016-2017, Pulumi Corporation. All rights reserved.
package lumidl
import (
"reflect"
"strings"
"github.com/pulumi/pulumi/pkg/diag"
"github.com/pulumi/pulumi/pkg/util/cmdutil"
)
// PropertyOptionsTag is the field tag the IDL compiler uses to find property options.
const PropertyOptionsTag = "pulumi"
// PropertyOptions represents a parsed field tag, controlling how properties are treated.
type PropertyOptions struct {
Name string // the property name to emit into the package.
Optional bool // true if this is an optional property.
Replaces bool // true if changing this property triggers a replacement of this resource.
In bool // true if this is part of the resource's input, but not its output, properties.
Out bool // true if the property is part of the resource's output, rather than input, properties.
}
// ParsePropertyOptions parses a tag into a structured set of options.
func ParsePropertyOptions(tag string) PropertyOptions {
opts := PropertyOptions{}
if lumi, has := reflect.StructTag(tag).Lookup(PropertyOptionsTag); has {
// The first element is the name; all others are optional flags. All are delimited by commas.
if keys := strings.Split(lumi, ","); len(keys) > 0 {
opts.Name = keys[0]
for _, key := range keys[1:] {
switch key {
case "optional":
opts.Optional = true
case "replaces":
opts.Replaces = true
case "in":
opts.In = true
case "out":
opts.Out = true
default:
cmdutil.Diag().Errorf(diag.Message("unrecognized tag `pulumi:\"%v\"`"), key)
}
}
}
}
return opts
}

View file

@ -1,130 +0,0 @@
// Copyright 2016-2017, Pulumi Corporation. All rights reserved.
package lumidl
import (
"go/ast"
"go/constant"
"go/token"
"go/types"
"golang.org/x/tools/go/loader"
"github.com/pulumi/pulumi/pkg/tokens"
"github.com/pulumi/pulumi/pkg/util/contract"
)
type Package struct {
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(name tokens.PackageName, prog *loader.Program, pkginfo *loader.PackageInfo) *Package {
return &Package{
Name: name,
Program: prog,
Pkginfo: pkginfo,
Files: make(map[string]*File),
MemberFiles: make(map[tokens.Name]*File),
}
}
func (pkg *Package) AddMember(file *File, nm tokens.Name, m Member) {
_, has := file.Members[nm]
contract.Assertf(!has, "Unexpected duplicate member %v", nm)
file.Members[nm] = m
file.MemberNames = append(file.MemberNames, nm)
pkg.MemberFiles[nm] = file
}
type File struct {
Path string // a relative path to the file.
Module tokens.Module // the module token for this file.
Node *ast.File // the Go file object.
Members map[tokens.Name]Member // a map of all members, membered and internal.
MemberNames []tokens.Name // the list of member keys in the order in which they were encountered.
}
func NewFile(path string, mod tokens.Module, node *ast.File) *File {
return &File{
Path: path,
Module: mod,
Node: node,
Members: make(map[tokens.Name]Member),
}
}
type Member interface {
Tok() tokens.ModuleMember // the member's token.
Name() tokens.Name // the name of the member.
Exported() bool // true if this member is membered.
Pos() token.Pos // the file defining this member.
}
type member struct {
tok tokens.ModuleMember
exported bool
pos token.Pos
}
func (m *member) Tok() tokens.ModuleMember { return m.tok }
func (m *member) Name() tokens.Name { return tokens.Name(m.tok.Name()) }
func (m *member) Exported() bool { return m.exported }
func (m *member) Pos() token.Pos { return m.pos }
type TypeMember interface {
Member
Struct() *types.Struct // the raw underlying struct.
Properties() []*types.Var // a flattened list of all properties (including embedded ones).
PropertyOptions() []PropertyOptions // a flattened list of all property options.
}
type Resource struct {
member
s *types.Struct // the underlying Go struct node.
props []*types.Var
popts []PropertyOptions
}
func (r *Resource) Struct() *types.Struct { return r.s }
func (r *Resource) Properties() []*types.Var { return r.props }
func (r *Resource) PropertyOptions() []PropertyOptions { return r.popts }
type Struct struct {
member
s *types.Struct
props []*types.Var
popts []PropertyOptions
}
func (r *Struct) Struct() *types.Struct { return r.s }
func (r *Struct) Properties() []*types.Var { return r.props }
func (r *Struct) PropertyOptions() []PropertyOptions { return r.popts }
type Typedef interface {
Member
Target() types.Type
}
type Alias struct {
member
target types.Type
}
func (a *Alias) Target() types.Type { return a.target }
type Enum struct {
member
Values []string
}
func (a *Enum) Target() types.Type { return types.Typ[types.String] }
type Const struct {
member
Type types.Type
Value constant.Value
}

View file

@ -1,20 +0,0 @@
// Copyright 2016-2017, Pulumi Corporation. All rights reserved.
package lumidl
import (
"path/filepath"
"golang.org/x/tools/go/loader"
"github.com/pulumi/pulumi/pkg/util/contract"
)
// RelFilename gets the target filename for any given position relative to the root.
func RelFilename(root string, prog *loader.Program, p goPos) string {
pos := p.Pos()
source := prog.Fset.Position(pos).Filename // the source filename.`
rel, err := filepath.Rel(root, source) // make it relative to the root.
contract.AssertNoError(err)
return rel
}

View file

@ -1,101 +0,0 @@
// Copyright 2016-2017, Pulumi Corporation. All rights reserved.
package lumidl
import (
"go/types"
"reflect"
"strings"
"github.com/pulumi/pulumi/pkg/resource/idl"
"github.com/pulumi/pulumi/pkg/util/contract"
)
func IsPrimitive(t types.Type) bool {
if basic, isbasic := t.(*types.Basic); isbasic {
switch basic.Kind() {
case types.Bool, types.Float64, types.String:
return true
}
}
return false
}
// IsEntity checks whether a type is an entity that can be used by-reference (asset, resource, etc).
func IsEntity(obj *types.TypeName, t types.Type) bool {
if IsResource(obj, t) {
return true
}
spec, _ := IsSpecial(obj)
return spec
}
// IsResource returns true if a type is a special IDL resource.
func IsResource(obj *types.TypeName, t types.Type) bool {
contract.Assert(obj != nil)
// If this is a resource type itself, then we're done.
if IsSpecialResource(obj) {
return true
}
// If a named type, fetch the underlying.
if n, is := t.(*types.Named); is {
t = n.Underlying()
}
if s, is := t.(*types.Struct); is {
// Otherwise, it's a resource if it has an embedded resource field.
for i := 0; i < s.NumFields(); i++ {
fld := s.Field(i)
if fld.Anonymous() {
if named, ok := fld.Type().(*types.Named); ok {
if IsSpecialResource(named.Obj()) {
return true
}
}
}
}
}
return false
}
type SpecialType int
const (
NotSpecialType = iota
SpecialResourceType
SpecialAssetType
SpecialArchiveType
)
var (
idlArchiveType = reflect.TypeOf(idl.Archive{})
idlAssetType = reflect.TypeOf(idl.Asset{})
idlResourceType = reflect.TypeOf(idl.Resource{})
)
// pkgMatch compares two packages. If the first is a vendored version of match, it still returns true.
func pkgMatch(pkg string, match string) bool {
ix := strings.LastIndex(pkg, match)
return ix != -1 && ix+len(match) == len(pkg)
}
func IsSpecial(obj *types.TypeName) (bool, SpecialType) {
if obj != nil && pkgMatch(obj.Pkg().Path(), idlResourceType.PkgPath()) {
switch obj.Name() {
case idlArchiveType.Name():
return true, SpecialArchiveType
case idlAssetType.Name():
return true, SpecialAssetType
case idlResourceType.Name():
return true, SpecialResourceType
}
}
return false, NotSpecialType
}
func IsSpecialResource(obj *types.TypeName) bool {
spec, kind := IsSpecial(obj)
return (spec && kind == SpecialResourceType)
}