2017-06-26 23:46:34 +02:00
|
|
|
// Copyright 2016-2017, Pulumi Corporation. All rights reserved.
|
2016-11-20 17:20:19 +01:00
|
|
|
|
|
|
|
package workspace
|
|
|
|
|
|
|
|
import (
|
|
|
|
"os"
|
|
|
|
"path/filepath"
|
|
|
|
"strings"
|
|
|
|
|
2017-10-25 19:20:08 +02:00
|
|
|
"github.com/pulumi/pulumi/pkg/util/fsutil"
|
|
|
|
|
2017-09-22 04:18:21 +02:00
|
|
|
"github.com/pulumi/pulumi/pkg/encoding"
|
2016-11-20 17:20:19 +01:00
|
|
|
)
|
|
|
|
|
2017-10-25 19:20:08 +02:00
|
|
|
const ProjectFile = "Pulumi" // the base name of a project file.
|
|
|
|
const GitDir = ".git" // the name of the folder git uses to store information
|
|
|
|
const BookkeepingDir = ".pulumi" // the name of our bookeeping folder, we store all state information here (like .git for git)
|
|
|
|
const StackDir = "stacks" // the name of the directory that holds stack information for projects.
|
|
|
|
const WorkspaceDir = "workspaces" // the name of the directory that holds workspace information for projects.
|
|
|
|
const RepoFile = "settings.json" // the name of the file that holds information specific to the entire repository.
|
|
|
|
const WorkspaceFile = "workspace.json" // the name of the file that holds workspace information.
|
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
|
Make major commands more pleasant
This change eliminates the need to constantly type in the environment
name when performing major commands like configuration, planning, and
deployment. It's probably due to my age, however, I keep fat-fingering
simple commands in front of investors and I am embarrassed!
In the new model, there is a notion of a "current environment", and
I have modeled it kinda sorta just like Git's notion of "current branch."
By default, the current environment is set when you `init` something.
Otherwise, there is the `coco env select <env>` command to change it.
(Running this command w/out a new <env> will show you the current one.)
The major commands `config`, `plan`, `deploy`, and `destroy` will prefer
to use the current environment, unless it is overridden by using the
--env flag. All of the `coco env <cmd> <env>` commands still require the
explicit passing of an environment which seems reasonable since they are,
after all, about manipulating environments.
As part of this, I've overhauled the aging workspace settings cruft,
which had fallen into disrepair since the initial prototype.
2017-03-22 03:23:32 +01:00
|
|
|
// Project is found, an empty path is returned. If problems are detected, they are logged to the diag.Sink.
|
2017-10-03 00:25:22 +02:00
|
|
|
func DetectPackage(path string) (string, error) {
|
2017-10-25 19:20:08 +02:00
|
|
|
return fsutil.WalkUp(path, isProject, func(s string) bool { return !isRepositoryFolder(filepath.Join(s, BookkeepingDir)) })
|
|
|
|
}
|
2016-11-20 17:20:19 +01:00
|
|
|
|
2017-10-25 19:20:08 +02:00
|
|
|
func isGitFolder(path string) bool {
|
|
|
|
info, err := os.Stat(path)
|
|
|
|
return err == nil && info.IsDir() && info.Name() == ".git"
|
2016-11-21 18:23:39 +01:00
|
|
|
}
|
|
|
|
|
2017-10-25 19:20:08 +02:00
|
|
|
func isRepositoryFolder(path string) bool {
|
Make major commands more pleasant
This change eliminates the need to constantly type in the environment
name when performing major commands like configuration, planning, and
deployment. It's probably due to my age, however, I keep fat-fingering
simple commands in front of investors and I am embarrassed!
In the new model, there is a notion of a "current environment", and
I have modeled it kinda sorta just like Git's notion of "current branch."
By default, the current environment is set when you `init` something.
Otherwise, there is the `coco env select <env>` command to change it.
(Running this command w/out a new <env> will show you the current one.)
The major commands `config`, `plan`, `deploy`, and `destroy` will prefer
to use the current environment, unless it is overridden by using the
--env flag. All of the `coco env <cmd> <env>` commands still require the
explicit passing of an environment which seems reasonable since they are,
after all, about manipulating environments.
As part of this, I've overhauled the aging workspace settings cruft,
which had fallen into disrepair since the initial prototype.
2017-03-22 03:23:32 +01:00
|
|
|
info, err := os.Stat(path)
|
2017-10-25 19:20:08 +02:00
|
|
|
return err == nil && info.IsDir() && info.Name() == BookkeepingDir
|
Make major commands more pleasant
This change eliminates the need to constantly type in the environment
name when performing major commands like configuration, planning, and
deployment. It's probably due to my age, however, I keep fat-fingering
simple commands in front of investors and I am embarrassed!
In the new model, there is a notion of a "current environment", and
I have modeled it kinda sorta just like Git's notion of "current branch."
By default, the current environment is set when you `init` something.
Otherwise, there is the `coco env select <env>` command to change it.
(Running this command w/out a new <env> will show you the current one.)
The major commands `config`, `plan`, `deploy`, and `destroy` will prefer
to use the current environment, unless it is overridden by using the
--env flag. All of the `coco env <cmd> <env>` commands still require the
explicit passing of an environment which seems reasonable since they are,
after all, about manipulating environments.
As part of this, I've overhauled the aging workspace settings cruft,
which had fallen into disrepair since the initial prototype.
2017-03-22 03:23:32 +01:00
|
|
|
}
|
|
|
|
|
2017-10-25 19:20:08 +02:00
|
|
|
// isProject returns true if the path references what appears to be a valid project. If problems are detected -- like
|
2016-11-20 17:20:19 +01:00
|
|
|
// an incorrect extension -- they are logged to the provided diag.Sink (if non-nil).
|
2017-10-25 19:20:08 +02:00
|
|
|
func isProject(path string) bool {
|
2017-10-03 00:25:22 +02:00
|
|
|
return isMarkupFile(path, ProjectFile)
|
2016-11-30 05:07:27 +01:00
|
|
|
}
|
|
|
|
|
2017-10-03 00:25:22 +02:00
|
|
|
func isMarkupFile(path string, expect string) 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 {
|
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
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false
|
|
|
|
}
|