2016-11-20 17:20:19 +01:00
|
|
|
// Copyright 2016 Marapongo, Inc. All rights reserved.
|
|
|
|
|
|
|
|
package workspace
|
|
|
|
|
|
|
|
import (
|
|
|
|
"io/ioutil"
|
|
|
|
"os"
|
|
|
|
"path/filepath"
|
|
|
|
"strings"
|
|
|
|
|
Begin overhauling semantic phases
This change further merges the new AST and MuPack/MuIL formats and
abstractions into the core of the compiler. A good amount of the old
code is gone now; I decided against ripping it all out in one fell
swoop so that I can methodically check that we are preserving all
relevant decisions and/or functionality we had in the old model.
The changes are too numerous to outline in this commit message,
however, here are the noteworthy ones:
* Split up the notion of symbols and tokens, resulting in:
- pkg/symbols for true compiler symbols (bound nodes)
- pkg/tokens for name-based tokens, identifiers, constants
* Several packages move underneath pkg/compiler:
- pkg/ast becomes pkg/compiler/ast
- pkg/errors becomes pkg/compiler/errors
- pkg/symbols becomes pkg/compiler/symbols
* pkg/ast/... becomes pkg/compiler/legacy/ast/...
* pkg/pack/ast becomes pkg/compiler/ast.
* pkg/options goes away, merged back into pkg/compiler.
* All binding functionality moves underneath a dedicated
package, pkg/compiler/binder. The legacy.go file contains
cruft that will eventually go away, while the other files
represent a halfway point between new and old, but are
expected to stay roughly in the current shape.
* All parsing functionality is moved underneath a new
pkg/compiler/metadata namespace, and we adopt new terminology
"metadata reading" since real parsing happens in the MetaMu
compilers. Hence, Parser has become metadata.Reader.
* In general phases of the compiler no longer share access to
the actual compiler.Compiler object. Instead, shared state is
moved to the core.Context object underneath pkg/compiler/core.
* Dependency resolution during binding has been rewritten to
the new model, including stashing bound package symbols in the
context object, and detecting import cycles.
* Compiler construction does not take a workspace object. Instead,
creation of a workspace is entirely hidden inside of the compiler's
constructor logic.
* There are three Compile* functions on the Compiler interface, to
support different styles of invoking compilation: Compile() auto-
detects a Mu package, based on the workspace; CompilePath(string)
loads the target as a Mu package and compiles it, regardless of
the workspace settings; and, CompilePackage(*pack.Package) will
compile a pre-loaded package AST, again regardless of workspace.
* Delete the _fe, _sema, and parsetree phases. They are no longer
relevant and the functionality is largely subsumed by the above.
...and so very much more. I'm surprised I ever got this to compile again!
2017-01-18 21:18:37 +01:00
|
|
|
"github.com/marapongo/mu/pkg/compiler/errors"
|
2016-11-20 17:20:19 +01:00
|
|
|
"github.com/marapongo/mu/pkg/diag"
|
Support Workspaces
This change adds support for Workspaces, a convenient way of sharing settings
among many Stacks, like default cluster targets, configuration settings, and the
like, which are not meant to be distributed as part of the Stack itself.
The following things are included in this checkin:
* At workspace initialization time, detect and parse the .mu/workspace.yaml
file. This is pretty rudimentary right now and contains just the default
cluster targets. The results are stored in a new ast.Workspace type.
* Rename "target" to "cluster". This impacts many things, including ast.Target
being changed to ast.Cluster, and all related fields, the command line --target
being changed to --cluster, various internal helper functions, and so on. This
helps to reinforce the desired mental model.
* Eliminate the ast.Metadata type. Instead, the metadata moves directly onto
the Stack. This reflects the decision to make Stacks "the thing" that is
distributed, versioned, and is the granularity of dependency.
* During cluster targeting, add the workspace settings into the probing logic.
We still search in the same order: CLI > Stack > Workspace.
2016-11-22 19:41:07 +01:00
|
|
|
"github.com/marapongo/mu/pkg/encoding"
|
2016-11-20 17:20:19 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
// Mufile is the base name of a Mufile.
|
|
|
|
const Mufile = "Mu"
|
|
|
|
|
2017-02-09 20:01:02 +01:00
|
|
|
// Mupack is the base name of a MuPackage.
|
|
|
|
const Mupack = "Mupack"
|
|
|
|
|
Repivot plan/apply commands; prepare for updates
This change repivots the plan/apply commands slightly. This is largely
in preparation for performing deletes and updates of existing environments.
The old way was slightly confusing and made things appear more "magical"
than they actually are. Namely, different things are needed for different
kinds of deployment operations, and trying to present them each underneath
a single pair of CLI commands just leads to weird modality and options.
The new way is to offer three commands: create, update, and delete. Each
does what it says on the tin: create provisions a new environment, update
makes resource updates to an existing one, and delete tears down an existing
one entirely. The arguments are what make this interesting: create demands
a MuPackage to evaluate (producing the new desired state snapshot), update
takes *both* an existing snapshot file plus a MuPackage to evaluate (producing
the new desired state snapshot to diff against the existing one), and delete
merely takes an existing snapshot file and no MuPackage, since all it must
do is tear down an existing known environment.
Replacing the plan functionality is the --dry-run (-n) flag that may be
passed to any of the above commands. This will print out the plan without
actually performing any opterations.
All commands produce serializable resource files in the MuGL file format,
and attempt to do smart things with respect to backups, etc., to support the
intended "Git-oriented" workflow of the pure CLI dev experience.
2017-02-22 20:21:26 +01:00
|
|
|
// Mugfile is the base name of a MuGL graph file.
|
|
|
|
const Mugfile = "Mug"
|
|
|
|
|
2016-11-30 05:07:27 +01:00
|
|
|
// Muspace is the base name of a markup file containing settings shared amongst a workspace.
|
|
|
|
const Muspace = "Muspace"
|
2016-11-20 17:20:19 +01:00
|
|
|
|
2016-11-30 05:07:27 +01:00
|
|
|
// Mudeps is the directory in which dependency modules exist, either local to a workspace, or globally.
|
|
|
|
const Mudeps = ".Mudeps"
|
2016-11-20 17:20:19 +01:00
|
|
|
|
2016-11-21 18:23:39 +01:00
|
|
|
// InstallRootEnvvar is the envvar describing where Mu has been installed.
|
|
|
|
const InstallRootEnvvar = "MUROOT"
|
2016-11-20 17:20:19 +01:00
|
|
|
|
Implement dependency resolution
This change includes logic to resolve dependencies declared by stacks. The design
is described in https://github.com/marapongo/mu/blob/master/docs/deps.md.
In summary, each stack may declare dependencies, which are name/semver pairs. A
new structure has been introduced, ast.Ref, to distinguish between ast.Names and
dependency names. An ast.Ref includes a protocol, base part, and a name part (the
latter being an ast.Name); for example, in "https://hub.mu.com/mu/container/",
"https://" is the protocol, "hub.mu.com/" is the base, and "mu/container" is the
name. This is used to resolve URL-like names to package manager-like artifacts.
The dependency resolution phase happens after parsing, but before semantic analysis.
This is because dependencies are "source-like" in that we must load and parse all
dependency metadata files. We stick the full transitive closure of dependencies
into a map attached to the compiler to avoid loading dependencies multiple times.
Note that, although dependencies prohibit cycles, this forms a DAG, meaning multiple
inbound edges to a single stack may come from multiple places.
From there, we rely on ordinary visitation to deal with dependencies further.
This includes inserting symbol entries into the symbol table, mapping names to the
loaded stacks, during the first phase of binding so that they may be found
subsequently when typechecking during the second phase and beyond.
2016-11-21 20:19:25 +01:00
|
|
|
// InstallRootLibdir is the directory in which the standard Mu library exists.
|
|
|
|
const InstallRootLibdir = "lib"
|
|
|
|
|
2016-11-21 18:23:39 +01:00
|
|
|
// DefaultInstallRoot is where Mu is installed by default, if the envvar is missing.
|
|
|
|
// TODO: support Windows.
|
2016-11-23 23:18:18 +01:00
|
|
|
const DefaultInstallRoot = "/usr/local/mu"
|
2016-11-21 18:23:39 +01:00
|
|
|
|
|
|
|
// InstallRoot returns Mu's installation location. This is controlled my the MUROOT envvar.
|
|
|
|
func InstallRoot() string {
|
|
|
|
root := os.Getenv(InstallRootEnvvar)
|
|
|
|
if root == "" {
|
|
|
|
return DefaultInstallRoot
|
|
|
|
}
|
|
|
|
return root
|
|
|
|
}
|
|
|
|
|
|
|
|
// isTop returns true if the path represents the top of the filesystem.
|
|
|
|
func isTop(path string) bool {
|
|
|
|
return os.IsPathSeparator(path[len(path)-1])
|
|
|
|
}
|
|
|
|
|
Implement dependency resolution
This change includes logic to resolve dependencies declared by stacks. The design
is described in https://github.com/marapongo/mu/blob/master/docs/deps.md.
In summary, each stack may declare dependencies, which are name/semver pairs. A
new structure has been introduced, ast.Ref, to distinguish between ast.Names and
dependency names. An ast.Ref includes a protocol, base part, and a name part (the
latter being an ast.Name); for example, in "https://hub.mu.com/mu/container/",
"https://" is the protocol, "hub.mu.com/" is the base, and "mu/container" is the
name. This is used to resolve URL-like names to package manager-like artifacts.
The dependency resolution phase happens after parsing, but before semantic analysis.
This is because dependencies are "source-like" in that we must load and parse all
dependency metadata files. We stick the full transitive closure of dependencies
into a map attached to the compiler to avoid loading dependencies multiple times.
Note that, although dependencies prohibit cycles, this forms a DAG, meaning multiple
inbound edges to a single stack may come from multiple places.
From there, we rely on ordinary visitation to deal with dependencies further.
This includes inserting symbol entries into the symbol table, mapping names to the
loaded stacks, during the first phase of binding so that they may be found
subsequently when typechecking during the second phase and beyond.
2016-11-21 20:19:25 +01:00
|
|
|
// pathDir returns the nearest directory to the given path (identity if a directory; parent otherwise).
|
|
|
|
func pathDir(path string) string {
|
2016-11-21 18:23:39 +01:00
|
|
|
// It's possible that the path is a file (e.g., a Mu.yaml file); if so, we want the directory.
|
Implement dependency resolution
This change includes logic to resolve dependencies declared by stacks. The design
is described in https://github.com/marapongo/mu/blob/master/docs/deps.md.
In summary, each stack may declare dependencies, which are name/semver pairs. A
new structure has been introduced, ast.Ref, to distinguish between ast.Names and
dependency names. An ast.Ref includes a protocol, base part, and a name part (the
latter being an ast.Name); for example, in "https://hub.mu.com/mu/container/",
"https://" is the protocol, "hub.mu.com/" is the base, and "mu/container" is the
name. This is used to resolve URL-like names to package manager-like artifacts.
The dependency resolution phase happens after parsing, but before semantic analysis.
This is because dependencies are "source-like" in that we must load and parse all
dependency metadata files. We stick the full transitive closure of dependencies
into a map attached to the compiler to avoid loading dependencies multiple times.
Note that, although dependencies prohibit cycles, this forms a DAG, meaning multiple
inbound edges to a single stack may come from multiple places.
From there, we rely on ordinary visitation to deal with dependencies further.
This includes inserting symbol entries into the symbol table, mapping names to the
loaded stacks, during the first phase of binding so that they may be found
subsequently when typechecking during the second phase and beyond.
2016-11-21 20:19:25 +01:00
|
|
|
info, err := os.Stat(path)
|
|
|
|
if err != nil || info.IsDir() {
|
|
|
|
return path
|
2016-11-21 18:23:39 +01:00
|
|
|
}
|
2017-01-28 00:42:39 +01:00
|
|
|
return filepath.Dir(path)
|
2016-11-21 18:23:39 +01:00
|
|
|
}
|
|
|
|
|
Begin overhauling semantic phases
This change further merges the new AST and MuPack/MuIL formats and
abstractions into the core of the compiler. A good amount of the old
code is gone now; I decided against ripping it all out in one fell
swoop so that I can methodically check that we are preserving all
relevant decisions and/or functionality we had in the old model.
The changes are too numerous to outline in this commit message,
however, here are the noteworthy ones:
* Split up the notion of symbols and tokens, resulting in:
- pkg/symbols for true compiler symbols (bound nodes)
- pkg/tokens for name-based tokens, identifiers, constants
* Several packages move underneath pkg/compiler:
- pkg/ast becomes pkg/compiler/ast
- pkg/errors becomes pkg/compiler/errors
- pkg/symbols becomes pkg/compiler/symbols
* pkg/ast/... becomes pkg/compiler/legacy/ast/...
* pkg/pack/ast becomes pkg/compiler/ast.
* pkg/options goes away, merged back into pkg/compiler.
* All binding functionality moves underneath a dedicated
package, pkg/compiler/binder. The legacy.go file contains
cruft that will eventually go away, while the other files
represent a halfway point between new and old, but are
expected to stay roughly in the current shape.
* All parsing functionality is moved underneath a new
pkg/compiler/metadata namespace, and we adopt new terminology
"metadata reading" since real parsing happens in the MetaMu
compilers. Hence, Parser has become metadata.Reader.
* In general phases of the compiler no longer share access to
the actual compiler.Compiler object. Instead, shared state is
moved to the core.Context object underneath pkg/compiler/core.
* Dependency resolution during binding has been rewritten to
the new model, including stashing bound package symbols in the
context object, and detecting import cycles.
* Compiler construction does not take a workspace object. Instead,
creation of a workspace is entirely hidden inside of the compiler's
constructor logic.
* There are three Compile* functions on the Compiler interface, to
support different styles of invoking compilation: Compile() auto-
detects a Mu package, based on the workspace; CompilePath(string)
loads the target as a Mu package and compiles it, regardless of
the workspace settings; and, CompilePackage(*pack.Package) will
compile a pre-loaded package AST, again regardless of workspace.
* Delete the _fe, _sema, and parsetree phases. They are no longer
relevant and the functionality is largely subsumed by the above.
...and so very much more. I'm surprised I ever got this to compile again!
2017-01-18 21:18:37 +01:00
|
|
|
// DetectPackage locates the closest package from the given path, searching "upwards" in the directory hierarchy. If no
|
Implement dependency resolution
This change includes logic to resolve dependencies declared by stacks. The design
is described in https://github.com/marapongo/mu/blob/master/docs/deps.md.
In summary, each stack may declare dependencies, which are name/semver pairs. A
new structure has been introduced, ast.Ref, to distinguish between ast.Names and
dependency names. An ast.Ref includes a protocol, base part, and a name part (the
latter being an ast.Name); for example, in "https://hub.mu.com/mu/container/",
"https://" is the protocol, "hub.mu.com/" is the base, and "mu/container" is the
name. This is used to resolve URL-like names to package manager-like artifacts.
The dependency resolution phase happens after parsing, but before semantic analysis.
This is because dependencies are "source-like" in that we must load and parse all
dependency metadata files. We stick the full transitive closure of dependencies
into a map attached to the compiler to avoid loading dependencies multiple times.
Note that, although dependencies prohibit cycles, this forms a DAG, meaning multiple
inbound edges to a single stack may come from multiple places.
From there, we rely on ordinary visitation to deal with dependencies further.
This includes inserting symbol entries into the symbol table, mapping names to the
loaded stacks, during the first phase of binding so that they may be found
subsequently when typechecking during the second phase and beyond.
2016-11-21 20:19:25 +01:00
|
|
|
// Mufile is found, an empty path is returned. If problems are detected, they are logged to the diag.Sink.
|
Begin overhauling semantic phases
This change further merges the new AST and MuPack/MuIL formats and
abstractions into the core of the compiler. A good amount of the old
code is gone now; I decided against ripping it all out in one fell
swoop so that I can methodically check that we are preserving all
relevant decisions and/or functionality we had in the old model.
The changes are too numerous to outline in this commit message,
however, here are the noteworthy ones:
* Split up the notion of symbols and tokens, resulting in:
- pkg/symbols for true compiler symbols (bound nodes)
- pkg/tokens for name-based tokens, identifiers, constants
* Several packages move underneath pkg/compiler:
- pkg/ast becomes pkg/compiler/ast
- pkg/errors becomes pkg/compiler/errors
- pkg/symbols becomes pkg/compiler/symbols
* pkg/ast/... becomes pkg/compiler/legacy/ast/...
* pkg/pack/ast becomes pkg/compiler/ast.
* pkg/options goes away, merged back into pkg/compiler.
* All binding functionality moves underneath a dedicated
package, pkg/compiler/binder. The legacy.go file contains
cruft that will eventually go away, while the other files
represent a halfway point between new and old, but are
expected to stay roughly in the current shape.
* All parsing functionality is moved underneath a new
pkg/compiler/metadata namespace, and we adopt new terminology
"metadata reading" since real parsing happens in the MetaMu
compilers. Hence, Parser has become metadata.Reader.
* In general phases of the compiler no longer share access to
the actual compiler.Compiler object. Instead, shared state is
moved to the core.Context object underneath pkg/compiler/core.
* Dependency resolution during binding has been rewritten to
the new model, including stashing bound package symbols in the
context object, and detecting import cycles.
* Compiler construction does not take a workspace object. Instead,
creation of a workspace is entirely hidden inside of the compiler's
constructor logic.
* There are three Compile* functions on the Compiler interface, to
support different styles of invoking compilation: Compile() auto-
detects a Mu package, based on the workspace; CompilePath(string)
loads the target as a Mu package and compiles it, regardless of
the workspace settings; and, CompilePackage(*pack.Package) will
compile a pre-loaded package AST, again regardless of workspace.
* Delete the _fe, _sema, and parsetree phases. They are no longer
relevant and the functionality is largely subsumed by the above.
...and so very much more. I'm surprised I ever got this to compile again!
2017-01-18 21:18:37 +01:00
|
|
|
func DetectPackage(path string, d diag.Sink) (string, error) {
|
2016-11-20 17:20:19 +01:00
|
|
|
// It's possible the target is already the file we seek; if so, return right away.
|
Implement dependency resolution
This change includes logic to resolve dependencies declared by stacks. The design
is described in https://github.com/marapongo/mu/blob/master/docs/deps.md.
In summary, each stack may declare dependencies, which are name/semver pairs. A
new structure has been introduced, ast.Ref, to distinguish between ast.Names and
dependency names. An ast.Ref includes a protocol, base part, and a name part (the
latter being an ast.Name); for example, in "https://hub.mu.com/mu/container/",
"https://" is the protocol, "hub.mu.com/" is the base, and "mu/container" is the
name. This is used to resolve URL-like names to package manager-like artifacts.
The dependency resolution phase happens after parsing, but before semantic analysis.
This is because dependencies are "source-like" in that we must load and parse all
dependency metadata files. We stick the full transitive closure of dependencies
into a map attached to the compiler to avoid loading dependencies multiple times.
Note that, although dependencies prohibit cycles, this forms a DAG, meaning multiple
inbound edges to a single stack may come from multiple places.
From there, we rely on ordinary visitation to deal with dependencies further.
This includes inserting symbol entries into the symbol table, mapping names to the
loaded stacks, during the first phase of binding so that they may be found
subsequently when typechecking during the second phase and beyond.
2016-11-21 20:19:25 +01:00
|
|
|
if IsMufile(path, d) {
|
|
|
|
return path, nil
|
2016-11-20 17:20:19 +01:00
|
|
|
}
|
|
|
|
|
Implement dependency resolution
This change includes logic to resolve dependencies declared by stacks. The design
is described in https://github.com/marapongo/mu/blob/master/docs/deps.md.
In summary, each stack may declare dependencies, which are name/semver pairs. A
new structure has been introduced, ast.Ref, to distinguish between ast.Names and
dependency names. An ast.Ref includes a protocol, base part, and a name part (the
latter being an ast.Name); for example, in "https://hub.mu.com/mu/container/",
"https://" is the protocol, "hub.mu.com/" is the base, and "mu/container" is the
name. This is used to resolve URL-like names to package manager-like artifacts.
The dependency resolution phase happens after parsing, but before semantic analysis.
This is because dependencies are "source-like" in that we must load and parse all
dependency metadata files. We stick the full transitive closure of dependencies
into a map attached to the compiler to avoid loading dependencies multiple times.
Note that, although dependencies prohibit cycles, this forms a DAG, meaning multiple
inbound edges to a single stack may come from multiple places.
From there, we rely on ordinary visitation to deal with dependencies further.
This includes inserting symbol entries into the symbol table, mapping names to the
loaded stacks, during the first phase of binding so that they may be found
subsequently when typechecking during the second phase and beyond.
2016-11-21 20:19:25 +01:00
|
|
|
curr := pathDir(path)
|
2016-11-20 17:20:19 +01:00
|
|
|
for {
|
|
|
|
stop := false
|
|
|
|
|
2016-11-21 18:23:39 +01:00
|
|
|
// Enumerate the current path's files, checking each to see if it's a Mufile.
|
2016-11-20 17:20:19 +01:00
|
|
|
files, err := ioutil.ReadDir(curr)
|
2016-11-21 18:23:39 +01:00
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
2016-11-20 17:20:19 +01:00
|
|
|
for _, file := range files {
|
|
|
|
name := file.Name()
|
|
|
|
path := filepath.Join(curr, name)
|
Implement dependency resolution
This change includes logic to resolve dependencies declared by stacks. The design
is described in https://github.com/marapongo/mu/blob/master/docs/deps.md.
In summary, each stack may declare dependencies, which are name/semver pairs. A
new structure has been introduced, ast.Ref, to distinguish between ast.Names and
dependency names. An ast.Ref includes a protocol, base part, and a name part (the
latter being an ast.Name); for example, in "https://hub.mu.com/mu/container/",
"https://" is the protocol, "hub.mu.com/" is the base, and "mu/container" is the
name. This is used to resolve URL-like names to package manager-like artifacts.
The dependency resolution phase happens after parsing, but before semantic analysis.
This is because dependencies are "source-like" in that we must load and parse all
dependency metadata files. We stick the full transitive closure of dependencies
into a map attached to the compiler to avoid loading dependencies multiple times.
Note that, although dependencies prohibit cycles, this forms a DAG, meaning multiple
inbound edges to a single stack may come from multiple places.
From there, we rely on ordinary visitation to deal with dependencies further.
This includes inserting symbol entries into the symbol table, mapping names to the
loaded stacks, during the first phase of binding so that they may be found
subsequently when typechecking during the second phase and beyond.
2016-11-21 20:19:25 +01:00
|
|
|
if IsMufile(path, d) {
|
2016-11-21 18:23:39 +01:00
|
|
|
return path, nil
|
2016-11-30 05:07:27 +01:00
|
|
|
} else if IsMuspace(path, d) {
|
|
|
|
// If we hit a Muspace file, stop looking.
|
2016-11-20 17:20:19 +01:00
|
|
|
stop = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// If we encountered a stop condition, break out of the loop.
|
|
|
|
if stop {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
// If neither succeeded, keep looking in our parent directory.
|
|
|
|
curr = filepath.Dir(curr)
|
2016-11-21 18:23:39 +01:00
|
|
|
if isTop(curr) {
|
2016-11-20 17:20:19 +01:00
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-11-21 18:23:39 +01:00
|
|
|
return "", nil
|
|
|
|
}
|
|
|
|
|
2016-11-20 17:20:19 +01:00
|
|
|
// IsMufile returns true if the path references what appears to be a valid Mufile. If problems are detected -- like
|
|
|
|
// an incorrect extension -- they are logged to the provided diag.Sink (if non-nil).
|
|
|
|
func IsMufile(path string, d diag.Sink) bool {
|
2016-11-30 05:07:27 +01:00
|
|
|
return isMarkupFile(path, Mufile, d)
|
|
|
|
}
|
|
|
|
|
2017-02-09 20:23:27 +01:00
|
|
|
// IsMupack returns true if the path references what appears to be a valid Mupack. If problems are detected -- like
|
|
|
|
// an incorrect extension -- they are logged to the provided diag.Sink (if non-nil).
|
|
|
|
func IsMupack(path string, d diag.Sink) bool {
|
|
|
|
return isMarkupFile(path, Mupack, d)
|
|
|
|
}
|
|
|
|
|
Repivot plan/apply commands; prepare for updates
This change repivots the plan/apply commands slightly. This is largely
in preparation for performing deletes and updates of existing environments.
The old way was slightly confusing and made things appear more "magical"
than they actually are. Namely, different things are needed for different
kinds of deployment operations, and trying to present them each underneath
a single pair of CLI commands just leads to weird modality and options.
The new way is to offer three commands: create, update, and delete. Each
does what it says on the tin: create provisions a new environment, update
makes resource updates to an existing one, and delete tears down an existing
one entirely. The arguments are what make this interesting: create demands
a MuPackage to evaluate (producing the new desired state snapshot), update
takes *both* an existing snapshot file plus a MuPackage to evaluate (producing
the new desired state snapshot to diff against the existing one), and delete
merely takes an existing snapshot file and no MuPackage, since all it must
do is tear down an existing known environment.
Replacing the plan functionality is the --dry-run (-n) flag that may be
passed to any of the above commands. This will print out the plan without
actually performing any opterations.
All commands produce serializable resource files in the MuGL file format,
and attempt to do smart things with respect to backups, etc., to support the
intended "Git-oriented" workflow of the pure CLI dev experience.
2017-02-22 20:21:26 +01:00
|
|
|
// IsMugfile returns true if the path references what appears to be a valid MuGL file. If problems are detected -- like
|
|
|
|
// an incorrect extension -- they are logged to the provided diag.Sink (if non-nil).
|
|
|
|
func IsMugfile(path string, d diag.Sink) bool {
|
|
|
|
return isMarkupFile(path, Mugfile, d)
|
|
|
|
}
|
|
|
|
|
2016-11-30 05:07:27 +01:00
|
|
|
// IsMuspace returns true if the path references what appears to be a valid Muspace file. If problems are detected --
|
|
|
|
// like an incorrect extension -- they are logged to the provided diag.Sink (if non-nil).
|
|
|
|
func IsMuspace(path string, d diag.Sink) bool {
|
|
|
|
return isMarkupFile(path, Muspace, d)
|
|
|
|
}
|
|
|
|
|
|
|
|
func isMarkupFile(path string, expect string, d diag.Sink) bool {
|
2016-11-20 17:20:19 +01:00
|
|
|
info, err := os.Stat(path)
|
Implement dependency resolution
This change includes logic to resolve dependencies declared by stacks. The design
is described in https://github.com/marapongo/mu/blob/master/docs/deps.md.
In summary, each stack may declare dependencies, which are name/semver pairs. A
new structure has been introduced, ast.Ref, to distinguish between ast.Names and
dependency names. An ast.Ref includes a protocol, base part, and a name part (the
latter being an ast.Name); for example, in "https://hub.mu.com/mu/container/",
"https://" is the protocol, "hub.mu.com/" is the base, and "mu/container" is the
name. This is used to resolve URL-like names to package manager-like artifacts.
The dependency resolution phase happens after parsing, but before semantic analysis.
This is because dependencies are "source-like" in that we must load and parse all
dependency metadata files. We stick the full transitive closure of dependencies
into a map attached to the compiler to avoid loading dependencies multiple times.
Note that, although dependencies prohibit cycles, this forms a DAG, meaning multiple
inbound edges to a single stack may come from multiple places.
From there, we rely on ordinary visitation to deal with dependencies further.
This includes inserting symbol entries into the symbol table, mapping names to the
loaded stacks, during the first phase of binding so that they may be found
subsequently when typechecking during the second phase and beyond.
2016-11-21 20:19:25 +01:00
|
|
|
if err != nil || info.IsDir() {
|
2016-11-30 05:07:27 +01:00
|
|
|
// Missing files and directories can't be markup files.
|
2016-11-20 17:20:19 +01:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
// Ensure the base name is expected.
|
|
|
|
name := info.Name()
|
|
|
|
ext := filepath.Ext(name)
|
|
|
|
base := strings.TrimSuffix(name, ext)
|
2016-11-30 05:07:27 +01:00
|
|
|
if base != expect {
|
|
|
|
if d != nil && strings.EqualFold(base, expect) {
|
2016-11-20 17:20:19 +01:00
|
|
|
// If the strings aren't equal, but case-insensitively match, issue a warning.
|
2016-11-30 05:07:27 +01:00
|
|
|
d.Warningf(errors.WarningIllegalMarkupFileCasing.AtFile(name), expect)
|
2016-11-20 17:20:19 +01:00
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check all supported extensions.
|
2016-11-30 05:07:27 +01:00
|
|
|
for _, mext := range encoding.Exts {
|
|
|
|
if name == expect+mext {
|
2016-11-20 17:20:19 +01:00
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// If we got here, it means the base name matched, but not the extension. Warn and return.
|
|
|
|
if d != nil {
|
2016-11-30 05:07:27 +01:00
|
|
|
d.Warningf(errors.WarningIllegalMarkupFileExt.AtFile(name), expect, ext)
|
2016-11-20 17:20:19 +01:00
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|