Add a mu verify command

This adds a `mu verify` command that simply runs the verification
pass against a MuPackage and its MuIL.  This is handy for compiler
authors to verify that the right stuff is getting emitted.
This commit is contained in:
joeduffy 2017-02-03 19:27:37 -08:00
parent a1c94275e0
commit 15ba2949dc
3 changed files with 123 additions and 17 deletions

View file

@ -41,6 +41,7 @@ func NewMuCmd() *cobra.Command {
cmd.AddCommand(newEvalCmd())
cmd.AddCommand(newGetCmd())
cmd.AddCommand(newPlanCmd())
cmd.AddCommand(newVerifyCmd())
cmd.AddCommand(newVersionCmd())
return cmd

63
cmd/verify.go Normal file
View file

@ -0,0 +1,63 @@
// Copyright 2016 Marapongo, Inc. All rights reserved.
package cmd
import (
"fmt"
"path/filepath"
"github.com/spf13/cobra"
"github.com/marapongo/mu/pkg/compiler"
"github.com/marapongo/mu/pkg/util/cmdutil"
"github.com/marapongo/mu/pkg/util/contract"
)
func newVerifyCmd() *cobra.Command {
var cmd = &cobra.Command{
Use: "verify [package]",
Short: "Check that a MuPackage and its MuIL are correct",
Long: "Check that a MuPackage and its MuIL are correct, reporting any errors.\n" +
"\n" +
"A MuPackage contains intermediate language (MuIL) that encodes symbols,\n" +
"definitions, and executable code. This MuIL must obey a set of specific rules\n" +
"for it to be valid. Otherwise, evaluating it will fail.\n" +
"\n" +
"The verify command thoroughly checks the MuIL against these rules, and issues\n" +
"errors anywhere it doesn't obey them. This is generally useful for tools developers\n" +
"and can ensure that MuPackages do not fail at runtime, when such invariants are checked.",
Run: func(cmd *cobra.Command, args []string) {
// In the case of an argument, load that specific package and new up a compiler based on its base path.
// Otherwise, use the default workspace and package logic (which consults the current working directory).
var success bool
if len(args) == 0 {
comp, err := compiler.Newwd(nil)
if err != nil {
contract.Failf("fatal: %v", err)
}
success = comp.Verify()
} else {
fn := args[0]
if pkg := cmdutil.ReadPackageFromArg(fn); pkg != nil {
var comp compiler.Compiler
var err error
if fn == "-" {
comp, err = compiler.Newwd(nil)
} else {
comp, err = compiler.New(filepath.Dir(fn), nil)
}
if err != nil {
contract.Failf("fatal: %v", err)
}
success = comp.VerifyPackage(pkg)
}
}
if !success {
fmt.Printf("fatal: MuPackage verification failed\n")
}
},
}
return cmd
}

View file

@ -34,6 +34,12 @@ type Compiler interface {
CompilePath(path string) graph.Graph
// CompilePackage compiles a given package object into its associated graph form.
CompilePackage(pkg *pack.Package) graph.Graph
// Verify detects a package from the workspace and validates its MuIL contents.
Verify() bool
// VerifyPath verifies a package given its path, validating that its MuIL contents are correct.
VerifyPath(path string) bool
// VerifyPackage verifies a given package object, validating that its MuIL contents are correct.
VerifyPackage(pkg *pack.Package) bool
}
// compiler is the canonical implementation of the Mu compiler.
@ -100,30 +106,18 @@ 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() graph.Graph {
path, err := c.w.DetectPackage()
if err != nil {
c.Diag().Errorf(errors.ErrorIO, err)
return nil
} else if path == "" {
c.Diag().Errorf(errors.ErrorMissingMufile, c.ctx.Path)
return nil
} else {
if path := c.detectPackage(); path != "" {
return c.CompilePath(path)
}
return nil
}
// CompilePath loads a package at the given path and compiles it into a graph.
func (c *compiler) CompilePath(path string) graph.Graph {
doc, err := diag.ReadDocument(path)
if err != nil {
c.Diag().Errorf(errors.ErrorCouldNotReadMufile.AtFile(path), err)
return nil
if pkg := c.readPackage(path); pkg != nil {
return c.CompilePackage(pkg)
}
pkg := c.reader.ReadPackage(doc)
if pkg == nil {
return nil
}
return c.CompilePackage(pkg)
return nil
}
// CompilePackage compiles the given package into a graph.
@ -170,3 +164,51 @@ func (c *compiler) CompilePackage(pkg *pack.Package) graph.Graph {
// Finally ask the graph generator to return what it has seen in graph form.
return gg.Graph()
}
// Verify detects a package from the workspace and validates its MuIL 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 MuIL 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 MuIL 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.ErrorMissingMufile, 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.ErrorCouldNotReadMufile.AtFile(path), err)
return nil
}
return c.reader.ReadPackage(doc)
}