diff --git a/Gopkg.lock b/Gopkg.lock index 9c57a9085..ea157222b 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -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 diff --git a/Makefile b/Makefile index 1d202b931..96c2eec6e 100644 --- a/Makefile +++ b/Makefile @@ -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} diff --git a/build.proj b/build.proj index 5529b4c8d..3fc87c588 100644 --- a/build.proj +++ b/build.proj @@ -62,7 +62,7 @@ - + @@ -105,7 +105,6 @@ - diff --git a/cmd/lumidl/README.md b/cmd/lumidl/README.md deleted file mode 100644 index 86c27954a..000000000 --- a/cmd/lumidl/README.md +++ /dev/null @@ -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. - diff --git a/cmd/lumidl/lumidl.go b/cmd/lumidl/lumidl.go deleted file mode 100644 index e053d0fab..000000000 --- a/cmd/lumidl/lumidl.go +++ /dev/null @@ -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 -} diff --git a/cmd/lumidl/main.go b/cmd/lumidl/main.go deleted file mode 100644 index a13ab7606..000000000 --- a/cmd/lumidl/main.go +++ /dev/null @@ -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) - } -} diff --git a/pkg/tools/lumidl/check.go b/pkg/tools/lumidl/check.go deleted file mode 100644 index 338ff4d4f..000000000 --- a/pkg/tools/lumidl/check.go +++ /dev/null @@ -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:\"\"` 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 -} diff --git a/pkg/tools/lumidl/compile.go b/pkg/tools/lumidl/compile.go deleted file mode 100644 index 4cafe790f..000000000 --- a/pkg/tools/lumidl/compile.go +++ /dev/null @@ -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 -} diff --git a/pkg/tools/lumidl/diag.go b/pkg/tools/lumidl/diag.go deleted file mode 100644 index 0a085ee6e..000000000 --- a/pkg/tools/lumidl/diag.go +++ /dev/null @@ -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 -} diff --git a/pkg/tools/lumidl/gen.go b/pkg/tools/lumidl/gen.go deleted file mode 100644 index 88b3ddab1..000000000 --- a/pkg/tools/lumidl/gen.go +++ /dev/null @@ -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 -} diff --git a/pkg/tools/lumidl/gen_pack.go b/pkg/tools/lumidl/gen_pack.go deleted file mode 100644 index 50def0796..000000000 --- a/pkg/tools/lumidl/gen_pack.go +++ /dev/null @@ -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 undefined; // functionality provided by the runtime") - w.Writefmtln(" }") - w.Writefmtln("") - w.Writefmtln(" public static query(q: any): %s[] {", name) - w.Writefmtln(" return 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) -} diff --git a/pkg/tools/lumidl/gen_rpc.go b/pkg/tools/lumidl/gen_rpc.go deleted file mode 100644 index 6ecd2db12..000000000 --- a/pkg/tools/lumidl/gen_rpc.go +++ /dev/null @@ -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("") -} diff --git a/pkg/tools/lumidl/options.go b/pkg/tools/lumidl/options.go deleted file mode 100644 index 8a07047f8..000000000 --- a/pkg/tools/lumidl/options.go +++ /dev/null @@ -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 -} diff --git a/pkg/tools/lumidl/package.go b/pkg/tools/lumidl/package.go deleted file mode 100644 index c5fa6f4cc..000000000 --- a/pkg/tools/lumidl/package.go +++ /dev/null @@ -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 -} diff --git a/pkg/tools/lumidl/paths.go b/pkg/tools/lumidl/paths.go deleted file mode 100644 index cd0ff99f9..000000000 --- a/pkg/tools/lumidl/paths.go +++ /dev/null @@ -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 -} diff --git a/pkg/tools/lumidl/types.go b/pkg/tools/lumidl/types.go deleted file mode 100644 index c3557ae1e..000000000 --- a/pkg/tools/lumidl/types.go +++ /dev/null @@ -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) -}