pulumi/pkg/compiler/compiler.go
joeduffy 35aa6b7559 Rename pulumi/lumi to pulumi/pulumi-fabric
We are renaming Lumi to Pulumi Fabric.  This change simply renames the
pulumi/lumi repo to pulumi/pulumi-fabric, without the CLI tools and other
changes that will follow soon afterwards.
2017-08-02 09:25:22 -07:00

188 lines
7.1 KiB
Go

// Copyright 2016-2017, Pulumi Corporation. All rights reserved.
package compiler
import (
"os"
"github.com/golang/glog"
goerr "github.com/pkg/errors"
"github.com/pulumi/pulumi-fabric/pkg/compiler/binder"
"github.com/pulumi/pulumi-fabric/pkg/compiler/core"
"github.com/pulumi/pulumi-fabric/pkg/compiler/errors"
"github.com/pulumi/pulumi-fabric/pkg/compiler/metadata"
"github.com/pulumi/pulumi-fabric/pkg/compiler/symbols"
"github.com/pulumi/pulumi-fabric/pkg/diag"
"github.com/pulumi/pulumi-fabric/pkg/pack"
"github.com/pulumi/pulumi-fabric/pkg/util/contract"
"github.com/pulumi/pulumi-fabric/pkg/workspace"
)
// Compiler provides an interface into the many phases of the Lumi compilation process.
type Compiler interface {
core.Phase
Ctx() *core.Context // the shared context object.
Workspace() workspace.W // the workspace that this compielr is using.
// Compile detects a package from the workspace and compiles it.
Compile() (binder.Binder, *symbols.Package)
// CompilePath compiles a package in the given path.
CompilePath(path string) (binder.Binder, *symbols.Package)
// CompilePackage compiles a given pre-loaded package object.
CompilePackage(pkg *pack.Package) (binder.Binder, *symbols.Package)
// Verify detects a package from the workspace and validates its LumiIL contents.
Verify() bool
// VerifyPath verifies a package given its path, validating that its LumiIL contents are correct.
VerifyPath(path string) bool
// VerifyPackage verifies a given package object, validating that its LumiIL contents are correct.
VerifyPackage(pkg *pack.Package) bool
}
// compiler is the canonical implementation of the Lumi compiler.
type compiler struct {
w workspace.W
ctx *core.Context
reader metadata.Reader
}
// New creates a new instance of the Lumi compiler, along with a new workspace, from the given path. If options
// is nil, the default compiler options will be used instead. If any IO errors occur, they will be output in the usual
// diagnostics ways, and the compiler return value will be nil while the error will be non-nil.
func New(path string, opts *core.Options) (Compiler, error) {
// Ensure the options and diagnostics sink have been initialized.
if opts == nil {
opts = core.DefaultOptions()
}
d := opts.Diag
if d == nil {
d = core.DefaultSink(path)
}
// Now create a new context to share amongst the compiler and workspace.
ctx := core.NewContext(path, d, opts)
// Create a metadata reader for workspaces and packages (both the root one and dependencies).
reader := metadata.NewReader(ctx)
// Allocate the workspace object.
w, err := workspace.New(ctx)
if err != nil {
d.Errorf(errors.ErrorIO.AtFile(path), err)
return nil, goerr.Errorf("cannot proceed without a workspace")
}
// And finally return the freshly allocated compiler object.
return &compiler{
w: w,
ctx: ctx,
reader: reader,
}, nil
}
// Newwd creates a new instance of the Lumi compiler, along with a new workspace, from the current working directory.
// If options is nil, the default compiler options will be used instead. If any IO errors occur, they will be output in
// the usual diagnostics ways, and the compiler return value will be nil while the error will be non-nil.
func Newwd(opts *core.Options) (Compiler, error) {
pwd, err := os.Getwd()
contract.Assertf(err == nil, "Unexpected os.Getwd error: %v", err)
return New(pwd, opts)
}
func (c *compiler) Ctx() *core.Context { return c.ctx }
func (c *compiler) Diag() diag.Sink { return c.ctx.Diag }
func (c *compiler) Workspace() workspace.W { return c.w }
// Compile attempts to detect the package from the current working directory and, provided that succeeds, compiles it.
func (c *compiler) Compile() (binder.Binder, *symbols.Package) {
if path := c.detectPackage(); path != "" {
return c.CompilePath(path)
}
return nil, nil
}
// CompilePath loads a package at the given path and compiles it into a graph.
func (c *compiler) CompilePath(path string) (binder.Binder, *symbols.Package) {
if pkg := c.readPackage(path); pkg != nil {
return c.CompilePackage(pkg)
}
return nil, nil
}
// CompilePackage compiles the given package into a graph.
func (c *compiler) CompilePackage(pkg *pack.Package) (binder.Binder, *symbols.Package) {
contract.Requiref(pkg != nil, "pkg", "!= nil")
glog.Infof("Compiling package '%v' (w=%v)", pkg.Name, c.w.Root())
if glog.V(2) {
defer glog.V(2).Infof("Building package '%v' completed w/ %v warnings and %v errors",
pkg.Name, c.Diag().Warnings(), c.Diag().Errors())
}
// To compile a package, we require a decoded LumiPack object; this has already been done, and is presented to us
// an argument. Next, we must bind it's contents. To bind its contents, we must:
//
// * Resolve all dependency packages and inject them into a package map (just a map of names to symbols).
// * Bind each dependency package, in order, by recursing into the present algorithm.
// * Enumerate all modules, and for each:
// + Inject a module symbol into an export map associated with the package symbol.
// + Enumerate all module members, and for each:
// - Inject a symbol of the appropriate kind into the module's associated member map.
// - Enumerate any class symbols resulting from this process, and for each:
// . Inject a symbol of the appropriate kind into the class's associated member map.
//
// Essentially, all we are doing is mapping names to concrete symbols. This ensures that as we compile a package,
// we are able to find all tokens in these maps. If we ever cannot find a token in a map, it means the LumiPack
// file is invalid. We require that LumiLang compilers produce valid, verifiable LumiIL, and this is a requriement.
b := binder.New(c.w, c.ctx, c.reader)
return b, b.BindPackage(pkg)
}
// Verify detects a package from the workspace and validates its LumiIL contents.
func (c *compiler) Verify() bool {
if path := c.detectPackage(); path != "" {
return c.VerifyPath(path)
}
return false
}
// VerifyPath verifies a package given its path, validating that its LumiIL contents are correct.
func (c *compiler) VerifyPath(path string) bool {
if pkg := c.readPackage(path); pkg != nil {
return c.VerifyPackage(pkg)
}
return false
}
// VerifyPackage verifies a given package object, validating that its LumiIL contents are correct.
func (c *compiler) VerifyPackage(pkg *pack.Package) bool {
// To verify a package, simply run the binder aspects of it.
b := binder.New(c.w, c.ctx, c.reader)
b.BindPackage(pkg)
return c.Diag().Success()
}
// detectPackage detects a package in the current workspace.
func (c *compiler) detectPackage() string {
path, err := c.w.DetectPackage()
if err != nil {
c.Diag().Errorf(errors.ErrorIO, err)
return ""
}
if path == "" {
c.Diag().Errorf(errors.ErrorMissingProject, c.ctx.Path)
return ""
}
return path
}
// readPackage loads a package from the given path, issuing errors if anything bad happens.
func (c *compiler) readPackage(path string) *pack.Package {
doc, err := diag.ReadDocument(path)
if err != nil {
c.Diag().Errorf(errors.ErrorCouldNotReadProject.AtFile(path), err)
return nil
}
return c.reader.ReadPackage(doc)
}