mirror of https://github.com/pulumi/pulumi.git
165 lines
5.5 KiB
Go
165 lines
5.5 KiB
Go
// Copyright 2016-2020, Pulumi Corporation.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package pcl
|
|
|
|
import (
|
|
"sort"
|
|
"strings"
|
|
"unicode"
|
|
"unicode/utf8"
|
|
|
|
"github.com/hashicorp/hcl/v2"
|
|
"github.com/pulumi/pulumi/pkg/v3/codegen"
|
|
"github.com/pulumi/pulumi/pkg/v3/codegen/hcl2/model"
|
|
)
|
|
|
|
// titleCase replaces the first character in the given string with its upper-case equivalent.
|
|
func titleCase(s string) string {
|
|
c, sz := utf8.DecodeRuneInString(s)
|
|
if sz == 0 || unicode.IsUpper(c) {
|
|
return s
|
|
}
|
|
return string([]rune{unicode.ToUpper(c)}) + s[sz:]
|
|
}
|
|
|
|
func SourceOrderNodes(nodes []Node) []Node {
|
|
sort.Slice(nodes, func(i, j int) bool {
|
|
return model.SourceOrderLess(nodes[i].SyntaxNode().Range(), nodes[j].SyntaxNode().Range())
|
|
})
|
|
return nodes
|
|
}
|
|
|
|
func DecomposeToken(tok string, sourceRange hcl.Range) (string, string, string, hcl.Diagnostics) {
|
|
components := strings.Split(tok, ":")
|
|
if len(components) != 3 {
|
|
// If we don't have a valid type token, return the invalid token as the type name.
|
|
return "", "", tok, hcl.Diagnostics{malformedToken(tok, sourceRange)}
|
|
}
|
|
return components[0], components[1], components[2], nil
|
|
}
|
|
|
|
func linearizeNode(n Node, done codegen.Set, list *[]Node) {
|
|
if !done.Has(n) {
|
|
for _, d := range n.getDependencies() {
|
|
linearizeNode(d, done, list)
|
|
}
|
|
|
|
*list = append(*list, n)
|
|
done.Add(n)
|
|
}
|
|
}
|
|
|
|
// Linearize performs a topological sort of the nodes in the program so that they can be processed by tools that need
|
|
// to see all of a node's dependencies before the node itself (e.g. a code generator for a programming language that
|
|
// requires variables to be defined before they can be referenced). The sort is stable, and nodes are kept in source
|
|
// order as much as possible.
|
|
func Linearize(p *Program) []Node {
|
|
type file struct {
|
|
name string // The name of the HCL source file.
|
|
nodes []Node // The list of nodes defined by the source file.
|
|
}
|
|
|
|
// First, collect nodes into files. Ignore config and outputs, as these are sources and sinks, respectively.
|
|
files := map[string]*file{}
|
|
for _, n := range p.Nodes {
|
|
filename := n.SyntaxNode().Range().Filename
|
|
f, ok := files[filename]
|
|
if !ok {
|
|
f = &file{name: filename}
|
|
files[filename] = f
|
|
}
|
|
f.nodes = append(f.nodes, n)
|
|
}
|
|
|
|
// Now build a worklist out of the set of files, sorting the nodes in each file in source order as we go.
|
|
worklist := make([]*file, 0, len(files))
|
|
for _, f := range files {
|
|
SourceOrderNodes(f.nodes)
|
|
worklist = append(worklist, f)
|
|
}
|
|
|
|
// While the worklist is not empty, add the nodes in the file with the fewest unsatisfied dependencies on nodes in
|
|
// other files.
|
|
doneNodes, nodes := codegen.Set{}, make([]Node, 0, len(p.Nodes))
|
|
for len(worklist) > 0 {
|
|
// Recalculate file weights and find the file with the lowest weight.
|
|
var next *file
|
|
var nextIndex, nextWeight int
|
|
for i, f := range worklist {
|
|
weight, processed := 0, codegen.Set{}
|
|
for _, n := range f.nodes {
|
|
for _, d := range n.getDependencies() {
|
|
// We don't count nodes that we've already counted or nodes that have already been ordered.
|
|
if processed.Has(d) || doneNodes.Has(d) {
|
|
continue
|
|
}
|
|
|
|
// If this dependency resides in a different file, increment the current file's weight and mark the
|
|
// depdendency as processed.
|
|
depFilename := d.SyntaxNode().Range().Filename
|
|
if depFilename != f.name {
|
|
weight++
|
|
}
|
|
processed.Add(d)
|
|
}
|
|
}
|
|
|
|
// If we haven't yet chosen a file to generate or if this file has fewer unsatisfied dependencies than the
|
|
// current choice, choose this file. Ties are broken by the lexical order of the filenames.
|
|
if next == nil || weight < nextWeight || weight == nextWeight && f.name < next.name {
|
|
next, nextIndex, nextWeight = f, i, weight
|
|
}
|
|
}
|
|
|
|
// Swap the chosen file with the tail of the list, then trim the worklist by one.
|
|
worklist[len(worklist)-1], worklist[nextIndex] = worklist[nextIndex], worklist[len(worklist)-1]
|
|
worklist = worklist[:len(worklist)-1]
|
|
|
|
// Now generate the nodes in the chosen file and mark the file as done.
|
|
for _, n := range next.nodes {
|
|
linearizeNode(n, doneNodes, &nodes)
|
|
}
|
|
}
|
|
|
|
return nodes
|
|
}
|
|
|
|
// Remaps the "pulumi:providers:$Package" token style to "$Package:index:Provider", consistent with code generation.
|
|
// This mapping is consistent with how provider resources are projected into the schema and removes special casing logic
|
|
// to generate registering explicit providers.
|
|
//
|
|
// The resultant program should be a shallow copy of the source with only the modified resource nodes copied.
|
|
func MapProvidersAsResources(p *Program) {
|
|
nodes := make([]Node, 0, len(p.Nodes))
|
|
for _, n := range p.Nodes {
|
|
if r, ok := n.(*Resource); ok {
|
|
pkg, mod, name, _ := r.DecomposeToken()
|
|
if r.Schema.IsProvider && pkg == "pulumi" && mod == "providers" {
|
|
// the binder emits tokens like this when the module is "index"
|
|
r.Token = name + "::Provider"
|
|
}
|
|
}
|
|
|
|
nodes = append(nodes, n)
|
|
}
|
|
}
|
|
|
|
func FixupPulumiPackageTokens(r *Resource) {
|
|
pkg, mod, name, _ := r.DecomposeToken()
|
|
if pkg == "pulumi" && mod == "pulumi" {
|
|
r.Token = "pulumi::" + name
|
|
}
|
|
}
|