diff --git a/cmd/create.go b/cmd/create.go index fbc8b5856..330f9e540 100644 --- a/cmd/create.go +++ b/cmd/create.go @@ -11,19 +11,20 @@ func newCreateCmd() *cobra.Command { var summary bool var output string var cmd = &cobra.Command{ - Use: "create [blueprint] [-- [args]]", - Short: "Create a new environment and its resources", - Long: "Create a new environment and its resources.\n" + + Use: "create husk-name [nut-file] [-- [args]]", + Short: "Create a new husk (target) with a given name and fresh resources", + Long: "Create a new husk (target) with a given name and fresh resources.\n" + "\n" + - "This command creates a new environment and its resources. These resources are\n" + - "the result of compiling and evaluating a Nut blueprint, and then extracting all\n" + - "resource allocations from its CocoGL graph. This command results in a full snapshot\n" + - "of the environment's resource state, so that it may be updated incrementally later on.\n" + + "This command creates a new husk (target) and its resources, with the given name. These\n" + + "resources are computed by compiling and evaluating an executable Nut, and then extracting\n" + + "resource allocations from its resulting object graph. This command saves full snapshot\n" + + "of the husk's final resource state, so that it may be updated incrementally later on.\n" + "\n" + - "By default, the Nut blueprint is loaded from the current directory. Optionally,\n" + - "a path to a Nut elsewhere can be provided as the [blueprint] argument.", + "By default, the Nut to execute is loaded from the current directory. Optionally, an\n" + + "explicit path can be provided using the [nut-file] argument.", Run: func(cmd *cobra.Command, args []string) { - apply(cmd, args, "", applyOptions{ + apply(cmd, args, applyOptions{ + Create: true, Delete: false, DryRun: dryRun, Summary: summary, @@ -40,7 +41,7 @@ func newCreateCmd() *cobra.Command { "Only display summarization of resources and plan operations") cmd.PersistentFlags().StringVarP( &output, "output", "o", "", - "Serialize the resulting snapshot to a specific file, instead of the standard location") + "Serialize the resulting husk snapshot to a specific file, instead of the standard location") return cmd } diff --git a/cmd/delete.go b/cmd/delete.go index fcb2e2800..cb94e611b 100644 --- a/cmd/delete.go +++ b/cmd/delete.go @@ -10,14 +10,19 @@ func newDeleteCmd() *cobra.Command { var dryRun bool var summary bool var cmd = &cobra.Command{ - Use: "delete [snapshot]", - Short: "Delete an existing environment and its resources", - Long: "Delete an existing environment and its resources.\n" + + Use: "delete husk-name", + Short: "Delete an existing husk (target) and its resources", + Long: "Delete an existing husk (target) and its resources.\n" + "\n" + - "This command deletes an entire existing environment whose state is represented by the\n" + - "existing snapshot file. After running to completion, this environment will be gone.", + "This command deletes an entire existing husk by name. The current state is loaded\n" + + "from the associated snapshot file in the workspace. After running to completion,\n" + + "this environment and all of its associated state will be gone.\n" + + "\n" + + "Warning: although old snapshots can be used to recreate an environment, this command\n" + + "is generally irreversable and should be used with great care.", Run: func(cmd *cobra.Command, args []string) { - applyExisting(cmd, args, applyOptions{ + apply(cmd, args, applyOptions{ + Create: false, Delete: true, DryRun: dryRun, Summary: summary, diff --git a/cmd/describe.go b/cmd/describe.go index b7b660a21..115f8096e 100644 --- a/cmd/describe.go +++ b/cmd/describe.go @@ -4,6 +4,7 @@ package cmd import ( "fmt" + "os" "strings" "unicode" @@ -14,6 +15,7 @@ import ( "github.com/pulumi/coconut/pkg/tokens" "github.com/pulumi/coconut/pkg/util/cmdutil" "github.com/pulumi/coconut/pkg/util/contract" + "github.com/pulumi/coconut/pkg/workspace" ) func newDescribeCmd() *cobra.Command { @@ -22,8 +24,8 @@ func newDescribeCmd() *cobra.Command { var printSymbols bool var printExportedSymbols bool var cmd = &cobra.Command{ - Use: "describe [packages...]", - Short: "Describe a Nut", + Use: "describe [nuts...]", + Short: "Describe one or more Nuts", Long: "Describe prints package, symbol, and IL information from one or more Nuts.", Run: func(cmd *cobra.Command, args []string) { // If printAll is true, flip all the flags. @@ -33,13 +35,27 @@ func newDescribeCmd() *cobra.Command { printExportedSymbols = true } - // Enumerate the list of packages, deserialize them, and print information. - for _, arg := range args { - pkg := cmdutil.ReadPackageFromArg(arg) - if pkg == nil { - break + if len(args) == 0 { + // No package specified, just load from the current directory. + pwd, _ := os.Getwd() + pkgpath, err := workspace.DetectPackage(pwd, sink()) + if err != nil { + fmt.Fprintf(os.Stderr, "fatal: could not find a nut: %v", err) + os.Exit(-1) + } + + if pkg := cmdutil.ReadPackage(pkgpath); pkg != nil { + printPackage(pkg, printSymbols, printExportedSymbols, printIL) + } + } else { + // Enumerate the list of packages, deserialize them, and print information. + for _, arg := range args { + pkg := cmdutil.ReadPackageFromArg(arg) + if pkg == nil { + break + } + printPackage(pkg, printSymbols, printExportedSymbols, printIL) } - printPackage(pkg, printSymbols, printExportedSymbols, printIL) } }, } diff --git a/cmd/shared.go b/cmd/shared.go index 93146ac2a..39289d678 100644 --- a/cmd/shared.go +++ b/cmd/shared.go @@ -95,14 +95,14 @@ type compileResult struct { } // plan just uses the standard logic to parse arguments, options, and to create a snapshot and plan. -func plan(cmd *cobra.Command, args []string, existfn string, delete bool) *planResult { +func plan(cmd *cobra.Command, args []string, husk tokens.QName, create bool, delete bool) *planResult { // Create a new context for the plan operations. ctx := resource.NewContext(sink()) // If we are using an existing snapshot, read in that file (bailing if an IO error occurs). - var existing resource.Snapshot - if existfn != "" { - if existing = readSnapshot(ctx, existfn); existing == nil { + var old resource.Snapshot + if !create { + if old = readSnapshot(ctx, husk); old == nil { return nil } } @@ -112,15 +112,14 @@ func plan(cmd *cobra.Command, args []string, existfn string, delete bool) *planR return &planResult{ compileResult: nil, Ctx: ctx, - Nutpoint: existfn, - Existing: existing, - Snap: nil, - Plan: resource.NewDeletePlan(ctx, existing), + Husk: husk, + Old: old, + New: nil, + Plan: resource.NewDeletePlan(ctx, old), } } else if result := compile(cmd, args); result != nil && result.Heap != nil { // Create a resource snapshot from the compiled/evaluated object graph. - ns := resource.Namespace("no_namespace") // TODO[pulumi/coconut#94]: support for targets/namespaces. - snap, err := resource.NewGraphSnapshot(ctx, ns, result.Pkg.Name, result.C.Ctx().Opts.Args, result.Heap) + snap, err := resource.NewGraphSnapshot(ctx, husk, result.Pkg.Name, result.C.Ctx().Opts.Args, result.Heap) if err != nil { result.C.Diag().Errorf(errors.ErrorCantCreateSnapshot, err) return nil @@ -129,19 +128,20 @@ func plan(cmd *cobra.Command, args []string, existfn string, delete bool) *planR } var plan resource.Plan - if existing == nil { + if create { // Generate a plan for creating the resources from scratch. plan = resource.NewCreatePlan(ctx, snap) } else { // Generate a plan for updating existing resources to the new snapshot. - plan = resource.NewUpdatePlan(ctx, existing, snap) + contract.Assert(old != nil) + plan = resource.NewUpdatePlan(ctx, old, snap) } return &planResult{ compileResult: result, Ctx: ctx, - Nutpoint: existfn, - Existing: existing, - Snap: snap, + Husk: husk, + Old: old, + New: snap, Plan: plan, } } @@ -151,15 +151,25 @@ func plan(cmd *cobra.Command, args []string, existfn string, delete bool) *planR type planResult struct { *compileResult - Ctx *resource.Context - Nutpoint string // the file from which the existing snapshot was loaded (if any). - Existing resource.Snapshot // the existing snapshot (if any). - Snap resource.Snapshot // the new snapshot for this plan (if any). - Plan resource.Plan + Ctx *resource.Context + Husk tokens.QName // the husk name. + Old resource.Snapshot // the existing snapshot (if any). + New resource.Snapshot // the new snapshot for this plan (if any). + Plan resource.Plan } -func apply(cmd *cobra.Command, args []string, existing string, opts applyOptions) { - if result := plan(cmd, args, existing, opts.Delete); result != nil { +func apply(cmd *cobra.Command, args []string, opts applyOptions) { + // Read in the name of the husk to use. + var husk tokens.QName + if len(args) == 0 { + fmt.Fprintf(os.Stderr, "fatal: missing required husk name\n") + os.Exit(-1) + } else { + husk = tokens.QName(args[0]) + args = args[1:] + } + + if result := plan(cmd, args, husk, opts.Create, opts.Delete); result != nil { if result.Plan.Empty() { sink().Infof(diag.Message("nothing to do -- resources are up to date")) } else if opts.DryRun { @@ -167,7 +177,7 @@ func apply(cmd *cobra.Command, args []string, existing string, opts applyOptions if opts.Output == "" || opts.Output == "-" { printPlan(result.Plan, opts.Summary) } else { - saveSnapshot(result.Snap, opts.Output) + saveSnapshot(husk, result.New, opts.Output) } } else { // Create an object to track progress and perform the actual operations. @@ -206,53 +216,38 @@ func apply(cmd *cobra.Command, args []string, existing string, opts applyOptions fmt.Printf(colors.Colorize(s)) // Now save the updated snapshot to the specified output file, if any, or the standard location otherwise. - // TODO: perform partial updates if we weren't able to perform the entire planned set of operations. + // TODO: save partial updates if we weren't able to perform the entire planned set of operations. if opts.Delete { - contract.Assert(result.Nutpoint != "") - deleteSnapshot(result.Nutpoint) + deleteSnapshot(result.Husk) } else { - out := opts.Output - if out == "" { - out = result.Nutpoint // try overwriting the existing file. - } - if out == "" { - out = workspace.Nutpoint // use the default file name. - } - contract.Assert(result.Snap != nil) - saveSnapshot(result.Snap, out) + contract.Assert(result.New != nil) + saveSnapshot(result.Husk, result.New, opts.Output) } } } } -func applyExisting(cmd *cobra.Command, args []string, opts applyOptions) { - // Read in the snapshot argument. - // TODO: if not supplied, auto-detect the current one. - if len(args) == 0 { - fmt.Fprintf(os.Stderr, "fatal: missing required snapshot argument\n") - os.Exit(-1) - } - - apply(cmd, args[1:], args[0], opts) -} - // backupSnapshot makes a backup of an existing file, in preparation for writing a new one. Instead of a copy, it // simply renames the file, which is simpler, more efficient, etc. func backupSnapshot(file string) { contract.Require(file != "", "file") - // TODO: consider multiple backups (.bak.bak.bak...etc). os.Rename(file, file+".bak") // ignore errors. + // TODO: consider multiple backups (.bak.bak.bak...etc). } // deleteSnapshot removes an existing snapshot file, leaving behind a backup. -func deleteSnapshot(file string) { - contract.Require(file != "", "file") +func deleteSnapshot(husk tokens.QName) { + contract.Require(husk != "", "husk") // Just make a backup of the file and don't write out anything new. + file := workspace.HuskPath(husk) backupSnapshot(file) } // readSnapshot reads in an existing snapshot file, issuing an error and returning nil if something goes awry. -func readSnapshot(ctx *resource.Context, file string) resource.Snapshot { +func readSnapshot(ctx *resource.Context, husk tokens.QName) resource.Snapshot { + contract.Require(husk != "", "husk") + file := workspace.HuskPath(husk) + // Detect the encoding of the file so we can do our initial unmarshaling. m, ext := encoding.Detect(file) if m == nil { @@ -291,10 +286,13 @@ func readSnapshot(ctx *resource.Context, file string) resource.Snapshot { return resource.DeserializeSnapshot(ctx, &snap) } -// saveSnapshot saves a new CocoGL snapshot at the given location, backing up any existing ones. -func saveSnapshot(snap resource.Snapshot, file string) { +// saveSnapshot saves a new snapshot at the given location, backing up any existing ones. +func saveSnapshot(husk tokens.QName, snap resource.Snapshot, file string) { contract.Require(snap != nil, "snap") - contract.Require(file != "", "file") + contract.Require(husk != "", "husk") + if file == "" { + file = workspace.HuskPath(husk) + } // Make a serializable CocoGL data structure and then use the encoder to encode it. m, ext := encoding.Detect(file) @@ -313,15 +311,21 @@ func saveSnapshot(snap resource.Snapshot, file string) { // Back up the existing file if it already exists. backupSnapshot(file) - // And now write out the new snapshot file, overwriting that location. - if err = ioutil.WriteFile(file, b, 0644); err != nil { + // Ensure the directory exists. + if err = os.MkdirAll(filepath.Dir(file), 0744); err != nil { sink().Errorf(errors.ErrorIO, err) + } else { + // And now write out the new snapshot file, overwriting that location. + if err = ioutil.WriteFile(file, b, 0644); err != nil { + sink().Errorf(errors.ErrorIO, err) + } } } } } type applyOptions struct { + Create bool // true if we are creating resources. Delete bool // true if we are deleting resources. DryRun bool // true if we should just print the plan without performing it. Summary bool // true if we should only summarize resources and operations. diff --git a/cmd/update.go b/cmd/update.go index 330a617a7..821e869a3 100644 --- a/cmd/update.go +++ b/cmd/update.go @@ -11,21 +11,22 @@ func newUpdateCmd() *cobra.Command { var summary bool var output string var cmd = &cobra.Command{ - Use: "update [snapshot] [blueprint] [-- [args]]", - Short: "Update an existing environment and its resources", - Long: "Update an existing environment and its resources.\n" + + Use: "update husk-name [nut-file] [-- [args]]", + Short: "Update an existing husk (target) and its resources", + Long: "Update an existing husk (target) and its resources.\n" + "\n" + - "This command updates an existing environment whose state is represented by the\n" + + "This command updates an existing husk environment whose state is represented by the\n" + "existing snapshot file. The new desired state is computed by compiling and evaluating\n" + - "a Nut blueprint, and extracting all resource allocations from its CocoGL graph.\n" + - "This is then compared against the existing state to determine what operations must take\n" + + "an executable Nut, and extracting all resource allocations from its resulting object graph.\n" + + "This graph is compared against the existing state to determine what operations must take\n" + "place to achieve the desired state. This command results in a full snapshot of the\n" + "environment's new resource state, so that it may be updated incrementally again later.\n" + "\n" + - "By default, the Nut blueprint is loaded from the current directory. Optionally,\n" + - "a path to a Nut elsewhere can be provided as the [blueprint] argument.", + "By default, the Nut to execute is loaded from the current directory. Optionally, an\n" + + "explicit path can be provided using the [nut-file] argument.", Run: func(cmd *cobra.Command, args []string) { - applyExisting(cmd, args, applyOptions{ + apply(cmd, args, applyOptions{ + Create: false, Delete: false, DryRun: dryRun, Summary: summary, @@ -42,7 +43,7 @@ func newUpdateCmd() *cobra.Command { "Only display summarization of resources and plan operations") cmd.PersistentFlags().StringVarP( &output, "output", "o", "", - "Serialize the resulting snapshot to a specific file, instead of overwriting the existing one") + "Serialize the resulting husk snapshot to a specific file, instead of overwriting the existing one") return cmd } diff --git a/examples/scenarios/aws/ec2instance/.gitignore b/examples/scenarios/aws/ec2instance/.gitignore index 6dbf1d900..37953e513 100644 --- a/examples/scenarios/aws/ec2instance/.gitignore +++ b/examples/scenarios/aws/ec2instance/.gitignore @@ -1,3 +1,4 @@ bin/ node_modules/ +nutpack/ diff --git a/examples/scenarios/aws/ec2instance/mu_package/.gitignore b/examples/scenarios/aws/ec2instance/mu_package/.gitignore deleted file mode 100644 index 81325b3d8..000000000 --- a/examples/scenarios/aws/ec2instance/mu_package/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -/bin/ - diff --git a/examples/scenarios/aws/ec2instance/mu_package/README.md b/examples/scenarios/aws/ec2instance/mu_package/README.md deleted file mode 100644 index 591a5ccce..000000000 --- a/examples/scenarios/aws/ec2instance/mu_package/README.md +++ /dev/null @@ -1,6 +0,0 @@ -# ec2instance Mu Package - -This is the auto-generated directory structure for the "ec2instance" Mu package. - -The compiled package can be found underneath `bin/`, while all known deployment targets are in `targets/`. - diff --git a/examples/scenarios/aws/ec2instance/mu_package/targets/.gitignore b/examples/scenarios/aws/ec2instance/mu_package/targets/.gitignore deleted file mode 100644 index 07c43328d..000000000 --- a/examples/scenarios/aws/ec2instance/mu_package/targets/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -/*.bak - diff --git a/examples/scenarios/aws/ec2instance/tsconfig.json b/examples/scenarios/aws/ec2instance/tsconfig.json index 3086b63ab..7955f6e26 100644 --- a/examples/scenarios/aws/ec2instance/tsconfig.json +++ b/examples/scenarios/aws/ec2instance/tsconfig.json @@ -1,6 +1,6 @@ { "compilerOptions": { - "outDir": "bin", + "outDir": "nutpack/bin", "target": "es6", "module": "commonjs", "moduleResolution": "node", diff --git a/pkg/resource/moniker.go b/pkg/resource/moniker.go index 9a0053456..b074792e1 100644 --- a/pkg/resource/moniker.go +++ b/pkg/resource/moniker.go @@ -30,7 +30,7 @@ type Moniker string const MonikerDelimiter = "::" // the delimiter between elements of the moniker. // NewMoniker creates a unique moniker for the given object. -func NewMoniker(ns Namespace, alloc tokens.Module, t tokens.Type, name tokens.QName) Moniker { +func NewMoniker(ns tokens.QName, alloc tokens.Module, t tokens.Type, name tokens.QName) Moniker { return Moniker( string(ns) + MonikerDelimiter + string(alloc) + diff --git a/pkg/resource/serialize.go b/pkg/resource/serialize.go index 65b8d7e38..5bc9f1fe9 100644 --- a/pkg/resource/serialize.go +++ b/pkg/resource/serialize.go @@ -15,7 +15,7 @@ import ( // SerializedSnapshot is a serializable, flattened CocoGL graph structure, specifically for snapshots. It is similar // to the actual Snapshot interface, except that it flattens and rearranges a few data structures for serializability. type SerializedSnapshot struct { - Target Namespace `json:"target"` // the target environment name. + Husk tokens.QName `json:"husk"` // the target environment name. Package tokens.PackageName `json:"package"` // the package which created this graph. Args *core.Args `json:"args,omitempty"` // the blueprint args for graph creation. Refs *string `json:"refs,omitempty"` // the ref alias, if any (`#ref` by default). @@ -66,7 +66,7 @@ func SerializeSnapshot(snap Snapshot, reftag string) *SerializedSnapshot { } return &SerializedSnapshot{ - Target: snap.Ns(), + Husk: snap.Husk(), Package: snap.Pkg(), // TODO: eventually, this should carry version metadata too. Args: argsp, Refs: refp, @@ -149,20 +149,20 @@ func SerializeProperty(prop PropertyValue, reftag string) (interface{}, bool) { } // DeserializeSnapshot takes a serialized CocoGL snapshot data structure and returns its associated snapshot. -func DeserializeSnapshot(ctx *Context, mugl *SerializedSnapshot) Snapshot { +func DeserializeSnapshot(ctx *Context, ser *SerializedSnapshot) Snapshot { // Determine the reftag to use. var reftag string - if mugl.Refs == nil { + if ser.Refs == nil { reftag = DefaultSnapshotReftag } else { - reftag = *mugl.Refs + reftag = *ser.Refs } // For every serialized resource vertex, create a SerializedResource out of it. var resources []Resource - if mugl.Resources != nil { + if ser.Resources != nil { // TODO: we need to enumerate resources in the specific order in which they were emitted. - for _, kvp := range mugl.Resources.Iter() { + for _, kvp := range ser.Resources.Iter() { // Deserialize the resources, if they exist. res := kvp.Value var props PropertyMap @@ -183,11 +183,11 @@ func DeserializeSnapshot(ctx *Context, mugl *SerializedSnapshot) Snapshot { } var args core.Args - if mugl.Args != nil { - args = *mugl.Args + if ser.Args != nil { + args = *ser.Args } - return NewSnapshot(ctx, mugl.Target, mugl.Package, args, resources) + return NewSnapshot(ctx, ser.Husk, ser.Package, args, resources) } func DeserializeProperties(props SerializedPropertyMap, reftag string) PropertyMap { diff --git a/pkg/resource/snapshot.go b/pkg/resource/snapshot.go index e60aa6612..3c1fdeb5d 100644 --- a/pkg/resource/snapshot.go +++ b/pkg/resource/snapshot.go @@ -14,14 +14,12 @@ import ( "github.com/pulumi/coconut/pkg/util/contract" ) -type Namespace tokens.QName // a namespace is the target for a deployment. - // Snapshot is a view of a collection of resources in an environment at a point in time. It describes resources; their // IDs, names, and properties; their dependencies; and more. A snapshot is a diffable entity and can be used to create // or apply an infrastructure deployment plan in order to make reality match the snapshot state. type Snapshot interface { Ctx() *Context // fetches the context for this snapshot. - Ns() Namespace // the namespace being deployed into. + Husk() tokens.QName // the husk/namespace target being deployed into. Pkg() tokens.PackageName // the package from which this snapshot came. Args() core.Args // the arguments used to compile this package. Resources() []Resource // a topologically sorted list of resources (based on dependencies). @@ -32,14 +30,15 @@ type Snapshot interface { // NewSnapshot creates a snapshot from the given arguments. Note that resources must be in topologically-sorted // dependency order, otherwise undefined behavior will result from using the resulting snapshot object. -func NewSnapshot(ctx *Context, ns Namespace, pkg tokens.PackageName, args core.Args, resources []Resource) Snapshot { - return &snapshot{ctx, ns, pkg, args, resources} +func NewSnapshot(ctx *Context, husk tokens.QName, pkg tokens.PackageName, + args core.Args, resources []Resource) Snapshot { + return &snapshot{ctx, husk, pkg, args, resources} } // NewGraphSnapshot takes an object graph and produces a resource snapshot from it. It understands how to name // resources based on their position within the graph and how to identify and record dependencies. This function can // fail dynamically if the input graph did not satisfy the preconditions for resource graphs (like that it is a DAG). -func NewGraphSnapshot(ctx *Context, ns Namespace, pkg tokens.PackageName, args core.Args, +func NewGraphSnapshot(ctx *Context, husk tokens.QName, pkg tokens.PackageName, args core.Args, heap *heapstate.Heap) (Snapshot, error) { // Topologically sort the entire heapstate (in dependency order) and extract just the resource objects. @@ -50,24 +49,24 @@ func NewGraphSnapshot(ctx *Context, ns Namespace, pkg tokens.PackageName, args c // Next, name all resources, create their monikers and objects, and maps that we will use. Note that we must do // this in DAG order (guaranteed by our topological sort above), so that referenced monikers are available. - resources, err := createResources(ctx, ns, heap, resobjs) + resources, err := createResources(ctx, husk, heap, resobjs) if err != nil { return nil, err } - return NewSnapshot(ctx, ns, pkg, args, resources), nil + return NewSnapshot(ctx, husk, pkg, args, resources), nil } type snapshot struct { ctx *Context // the context shared by all operations in this snapshot. - ns Namespace // the namespace being deployed into. + husk tokens.QName // the husk/namespace target being deployed into. pkg tokens.PackageName // the package from which this snapshot came. args core.Args // the arguments used to compile this package. resources []Resource // the topologically sorted linearized list of resources. } func (s *snapshot) Ctx() *Context { return s.ctx } -func (s *snapshot) Ns() Namespace { return s.ns } +func (s *snapshot) Husk() tokens.QName { return s.husk } func (s *snapshot) Pkg() tokens.PackageName { return s.pkg } func (s *snapshot) Args() core.Args { return s.args } func (s *snapshot) Resources() []Resource { return s.resources } @@ -82,7 +81,7 @@ func (s *snapshot) ResourceByObject(obj *rt.Object) Resource { return s.ctx.ObjR // createResources uses a graph to create monikers and resource objects for every resource within. It // returns two maps for further use: a map of vertex to its new resource object, and a map of vertex to its moniker. -func createResources(ctx *Context, ns Namespace, heap *heapstate.Heap, resobjs []*rt.Object) ([]Resource, error) { +func createResources(ctx *Context, husk tokens.QName, heap *heapstate.Heap, resobjs []*rt.Object) ([]Resource, error) { var resources []Resource for _, resobj := range resobjs { // Create an object resource without a moniker. @@ -101,7 +100,7 @@ func createResources(ctx *Context, ns Namespace, heap *heapstate.Heap, resobjs [ // Now compute a unique moniker for this object and ensure we haven't had any collisions. alloc := heap.Alloc(resobj) - moniker := NewMoniker(ns, alloc.Mod.Tok, t, name) + moniker := NewMoniker(husk, alloc.Mod.Tok, t, name) glog.V(7).Infof("Resource moniker computed: %v", moniker) if _, exists := ctx.MksRes[moniker]; exists { // If this moniker is already in use, issue an error, ignore this one, and break. The break is necessary diff --git a/pkg/workspace/paths.go b/pkg/workspace/paths.go index 260063f6b..088d3f1e2 100644 --- a/pkg/workspace/paths.go +++ b/pkg/workspace/paths.go @@ -11,35 +11,24 @@ import ( "github.com/pulumi/coconut/pkg/compiler/errors" "github.com/pulumi/coconut/pkg/diag" "github.com/pulumi/coconut/pkg/encoding" + "github.com/pulumi/coconut/pkg/tokens" ) -// Nutfile is the base name of a Nutfile. -const Nutfile = "Nut" +const Nutfile = "Nut" // the base name of a Nutfile. +const Nutpack = "Nutpack" // the base name of a compiled NutPack. +const NutpackOutDir = "nutpack" // the default name of the NutPack output directory. +const NutpackBinDir = "bin" // the default name of the NutPack binary output directory. +const NutpackHusksDir = "husks" // the default name of the NutPack husks directory. +const Nutspace = "Coconut" // the base name of a markup file for shared settings in a workspace. +const Nutdeps = ".Nuts" // the directory in which dependencies exist, either local or global. -// Nutpack is the base name of a compiled Nut package. -const Nutpack = "Nutpack" - -// Nutpoint is the base name of a Nut's CocoGL graph file (checkpoint). -const Nutpoint = "Nutpoint" - -// Nutspace is the base name of a markup file containing settings shared amongst a workspace. -const Nutspace = "Nutspace" - -// Nutdeps is the directory in which dependency modules exist, either local to a workspace, or globally. -const Nutdeps = ".Nuts" - -// InstallRootEnvvar is the envvar describing where Coconut has been installed. -const InstallRootEnvvar = "COCOROOT" - -// InstallRootLibdir is the directory in which the Coconut standard library exists. -const InstallRootLibdir = "lib" - -// DefaultInstallRoot is where Coconut is installed by default, if the envvar is missing. -// TODO: support Windows. -const DefaultInstallRoot = "/usr/local/coconut" +const InstallRootEnvvar = "COCOROOT" // the envvar describing where Coconut has been installed. +const InstallRootLibdir = "lib" // the directory in which the Coconut standard library exists. +const DefaultInstallRoot = "/usr/local/coconut" // where Coconut is installed by default. // InstallRoot returns Coconut's installation location. This is controlled my the COCOROOT envvar. func InstallRoot() string { + // TODO: support Windows. root := os.Getenv(InstallRootEnvvar) if root == "" { return DefaultInstallRoot @@ -47,6 +36,11 @@ func InstallRoot() string { return root } +// HuskPath returns a path to the given husk's default location. +func HuskPath(husk tokens.QName) string { + return filepath.Join(NutpackOutDir, NutpackHusksDir, qnamePath(husk)+encoding.Exts[0]) +} + // isTop returns true if the path represents the top of the filesystem. func isTop(path string) bool { return os.IsPathSeparator(path[len(path)-1]) @@ -79,6 +73,17 @@ func DetectPackage(path string, d diag.Sink) (string, error) { if err != nil { return "", err } + + // See if there's a compiled Nutpack in the expected location. + pack := filepath.Join(NutpackOutDir, NutpackBinDir, Nutpack) + for _, ext := range encoding.Exts { + packfile := pack + ext + if IsNutpack(packfile, d) { + return packfile, nil + } + } + + // Now look for individual Nutfiles. for _, file := range files { name := file.Name() path := filepath.Join(curr, name) @@ -117,12 +122,6 @@ func IsNutpack(path string, d diag.Sink) bool { return isMarkupFile(path, Nutpack, d) } -// IsNutpoint returns true if the path references what appears to be a valid CocoGL file. If problems are detected -- -// like an incorrect extension -- they are logged to the provided diag.Sink (if non-nil). -func IsNutpoint(path string, d diag.Sink) bool { - return isMarkupFile(path, Nutpoint, d) -} - // IsNutspace returns true if the path references what appears to be a valid Nutspace file. If problems are detected -- // like an incorrect extension -- they are logged to the provided diag.Sink (if non-nil). func IsNutspace(path string, d diag.Sink) bool { diff --git a/pkg/workspace/workspace.go b/pkg/workspace/workspace.go index d9ad947e8..3651fecef 100644 --- a/pkg/workspace/workspace.go +++ b/pkg/workspace/workspace.go @@ -183,6 +183,11 @@ func namePath(nm tokens.Name) string { return stringNamePath(string(nm)) } +// qnamePath just cleans a name and makes sure it's appropriate to use as a path. +func qnamePath(nm tokens.QName) string { + return stringNamePath(string(nm)) +} + // packageNamePath just cleans a package name and makes sure it's appropriate to use as a path. func packageNamePath(nm tokens.PackageName) string { return stringNamePath(string(nm))