Skip to content

Commit 43932f0

Browse files
committed
feat: evaluator v2
Signed-off-by: Tiago Natel <[email protected]>
1 parent 917b5f7 commit 43932f0

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

66 files changed

+3453
-1929
lines changed

cmd/terramate/cli/cli.go

Lines changed: 80 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"os"
1212
"path"
1313
"path/filepath"
14+
"runtime/pprof"
1415
"strings"
1516
"time"
1617

@@ -34,6 +35,7 @@ import (
3435
"github.com/terramate-io/terramate/hcl/fmt"
3536
"github.com/terramate-io/terramate/hcl/info"
3637
"github.com/terramate-io/terramate/modvendor/download"
38+
"github.com/terramate-io/terramate/runtime"
3739
"github.com/terramate-io/terramate/versions"
3840

3941
"github.com/terramate-io/terramate/stack/trigger"
@@ -109,6 +111,7 @@ type cliSpec struct {
109111
LogDestination string `optional:"true" default:"stderr" enum:"stderr,stdout" help:"Destination of log messages"`
110112
Quiet bool `optional:"false" help:"Disable output"`
111113
Verbose int `short:"v" optional:"true" default:"0" type:"counter" help:"Increase verboseness of output"`
114+
CPUProfiling bool `optional:"true" default:"false" help:"Create a CPU profile file when running"`
112115

113116
DisableCheckGitUntracked bool `optional:"true" default:"false" help:"Disable git check for untracked files"`
114117
DisableCheckGitUncommitted bool `optional:"true" default:"false" help:"Disable git check for uncommitted files"`
@@ -324,6 +327,19 @@ func newCLI(version string, args []string, stdin io.Reader, stdout, stderr io.Wr
324327
fatal(err, "parsing cli args %v", args)
325328
}
326329

330+
if parsedArgs.CPUProfiling {
331+
stdfmt.Println("Creating CPU profile...")
332+
f, err := os.Create("terramate.prof")
333+
if err != nil {
334+
fatal(err, "can't create profile output file")
335+
}
336+
err = pprof.StartCPUProfile(f)
337+
if err != nil {
338+
fatal(err, "error when starting CPU profiling")
339+
}
340+
341+
}
342+
327343
configureLogging(parsedArgs.LogLevel, parsedArgs.LogFmt,
328344
parsedArgs.LogDestination, stdout, stderr)
329345
// If we don't re-create the logger after configuring we get some
@@ -474,6 +490,8 @@ func newCLI(version string, args []string, stdin io.Reader, stdout, stderr io.Wr
474490
log.Fatal().Msg("flag --changed provided but no git repository found")
475491
}
476492

493+
globalsResolver := globals.NewResolver(&prj.root)
494+
prj.globals = globalsResolver
477495
uimode := HumanMode
478496
if val := os.Getenv("CI"); envVarIsSet(val) {
479497
uimode = AutomationMode
@@ -516,6 +534,13 @@ func (c *cli) run() {
516534

517535
logger.Debug().Msg("Handle command.")
518536

537+
// We start the CPU Profiling during the flags parsing, but can't defer
538+
// the stop there, as the CLI parsing returns far before the program is
539+
// done running. Therefore we schedule it here.
540+
if c.parsedArgs.CPUProfiling {
541+
defer pprof.StopCPUProfile()
542+
}
543+
519544
switch c.ctx.Command() {
520545
case "fmt":
521546
c.format()
@@ -812,7 +837,7 @@ func (c *cli) gencodeWithVendor() (generate.Report, download.Report) {
812837

813838
log.Debug().Msg("generating code")
814839

815-
report := generate.Do(c.cfg(), c.vendorDir(), vendorRequestEvents)
840+
report := generate.Do(c.cfg(), c.globals(), c.vendorDir(), vendorRequestEvents)
816841

817842
log.Debug().Msg("code generation finished, waiting for vendor requests to be handled")
818843

@@ -1496,7 +1521,7 @@ func (c *cli) generateDebug() {
14961521
selectedStacks[stack.Dir()] = struct{}{}
14971522
}
14981523

1499-
results, err := generate.Load(c.cfg(), c.vendorDir())
1524+
results, err := generate.Load(c.cfg(), c.globals(), c.vendorDir())
15001525
if err != nil {
15011526
fatal(err, "generate debug: loading generated code")
15021527
}
@@ -1536,16 +1561,25 @@ func (c *cli) printStacksGlobals() {
15361561

15371562
for _, stackEntry := range c.filterStacks(report.Stacks) {
15381563
stack := stackEntry.Stack
1539-
report := globals.ForStack(c.cfg(), stack)
1540-
if err := report.AsError(); err != nil {
1564+
tree := stackEntry.Stack.Tree()
1565+
evalctx := eval.New(
1566+
stack.Dir,
1567+
runtime.NewResolver(c.cfg(), stack),
1568+
c.globals(),
1569+
)
1570+
evalctx.SetFunctions(stdlib.Functions(evalctx, tree.HostDir()))
1571+
1572+
expr, _ := ast.ParseExpression(`global`, `<print-globals>`)
1573+
globals, err := evalctx.Eval(expr)
1574+
if err != nil {
15411575
logger := log.With().
15421576
Stringer("stack", stack.Dir).
15431577
Logger()
15441578

15451579
errlog.Fatal(logger, err, "listing stacks globals: loading stack")
15461580
}
15471581

1548-
globalsStrRepr := report.Globals.String()
1582+
globalsStrRepr := fmt.FormatAttributes(globals.AsValueMap())
15491583
if globalsStrRepr == "" {
15501584
continue
15511585
}
@@ -1676,6 +1710,18 @@ func (c *cli) partialEval() {
16761710
}
16771711
}
16781712

1713+
func (c *cli) detectEvalContext(overrideGlobals map[string]string) *eval.Context {
1714+
var st *config.Stack
1715+
if config.IsStack(c.cfg(), c.wd()) {
1716+
var err error
1717+
st, err = config.LoadStack(c.cfg(), prj.PrjAbsPath(c.rootdir(), c.wd()))
1718+
if err != nil {
1719+
fatal(err, "setup eval context: loading stack config")
1720+
}
1721+
}
1722+
return c.setupEvalContext(st, overrideGlobals)
1723+
}
1724+
16791725
func (c *cli) evalRunArgs(st *config.Stack, cmd []string) []string {
16801726
ctx := c.setupEvalContext(st, map[string]string{})
16811727
var newargs []string
@@ -1749,62 +1795,42 @@ func (c *cli) outputEvalResult(val cty.Value, asJSON bool) {
17491795
c.output.MsgStdOut(string(data))
17501796
}
17511797

1752-
func (c *cli) detectEvalContext(overrideGlobals map[string]string) *eval.Context {
1753-
var st *config.Stack
1754-
if config.IsStack(c.cfg(), c.wd()) {
1755-
var err error
1756-
st, err = config.LoadStack(c.cfg(), prj.PrjAbsPath(c.rootdir(), c.wd()))
1757-
if err != nil {
1758-
fatal(err, "setup eval context: loading stack config")
1759-
}
1760-
}
1761-
return c.setupEvalContext(st, overrideGlobals)
1762-
}
1763-
17641798
func (c *cli) setupEvalContext(st *config.Stack, overrideGlobals map[string]string) *eval.Context {
1765-
runtime := c.cfg().Runtime()
1766-
1767-
var tdir string
1799+
var pdir prj.Path
17681800
if st != nil {
1769-
tdir = st.HostDir(c.cfg())
1770-
runtime.Merge(st.RuntimeValues(c.cfg()))
1801+
pdir = st.Dir
17711802
} else {
1772-
tdir = c.wd()
1773-
}
1774-
1775-
ctx := eval.NewContext(stdlib.NoFS(tdir))
1776-
ctx.SetNamespace("terramate", runtime)
1777-
1778-
wdPath := prj.PrjAbsPath(c.rootdir(), tdir)
1779-
tree, ok := c.cfg().Lookup(wdPath)
1780-
if !ok {
1781-
fatal(errors.E("configuration at %s not found", wdPath))
1782-
}
1783-
exprs, err := globals.LoadExprs(tree)
1784-
if err != nil {
1785-
fatal(err, "loading globals expressions")
1803+
pdir = prj.PrjAbsPath(c.rootdir(), c.wd())
17861804
}
17871805

1806+
var overrideStmts eval.Stmts
17881807
for name, exprStr := range overrideGlobals {
17891808
expr, err := ast.ParseExpression(exprStr, "<cmdline>")
17901809
if err != nil {
17911810
fatal(errors.E(err, "--global %s=%s is an invalid expresssion", name, exprStr))
17921811
}
17931812
parts := strings.Split(name, ".")
1794-
length := len(parts)
1795-
globalPath := globals.NewGlobalAttrPath(parts[0:length-1], parts[length-1])
1796-
exprs.SetOverride(
1797-
wdPath,
1798-
globalPath,
1799-
expr,
1800-
info.NewRange(c.rootdir(), hhcl.Range{
1801-
Filename: "<eval argument>",
1813+
ref := eval.NewRef("global", parts...)
1814+
overrideStmts = append(overrideStmts, eval.Stmt{
1815+
Origin: ref,
1816+
LHS: ref,
1817+
Info: eval.NewInfo(pdir, info.NewRange(c.rootdir(), hhcl.Range{
18021818
Start: hhcl.InitialPos,
18031819
End: hhcl.InitialPos,
1804-
}),
1805-
)
1820+
Filename: `<cmdline>`,
1821+
})),
1822+
RHS: eval.NewExprRHS(expr),
1823+
})
18061824
}
1807-
_ = exprs.Eval(ctx)
1825+
1826+
tree, _ := c.cfg().Lookup(pdir)
1827+
ctx := eval.New(
1828+
tree.Dir(),
1829+
runtime.NewResolver(c.cfg(), st),
1830+
globals.NewResolver(c.cfg(), overrideStmts...),
1831+
)
1832+
1833+
ctx.SetFunctions(stdlib.NoFS(ctx, c.wd()))
18081834
return ctx
18091835
}
18101836

@@ -1821,7 +1847,7 @@ func (c *cli) checkOutdatedGeneratedCode() {
18211847
return
18221848
}
18231849

1824-
outdatedFiles, err := generate.DetectOutdated(c.cfg(), c.vendorDir())
1850+
outdatedFiles, err := generate.DetectOutdated(c.cfg(), c.globals(), c.vendorDir())
18251851
if err != nil {
18261852
fatal(err, "failed to check outdated code on project")
18271853
}
@@ -1860,11 +1886,12 @@ func (c *cli) gitSafeguardRemoteEnabled() bool {
18601886
return true
18611887
}
18621888

1863-
func (c *cli) wd() string { return c.prj.wd }
1864-
func (c *cli) rootdir() string { return c.prj.rootdir }
1865-
func (c *cli) cfg() *config.Root { return &c.prj.root }
1866-
func (c *cli) rootNode() hcl.Config { return c.prj.root.Tree().Node }
1867-
func (c *cli) cred() credential { return c.cloud.client.Credential.(credential) }
1889+
func (c *cli) wd() string { return c.prj.wd }
1890+
func (c *cli) rootdir() string { return c.prj.rootdir }
1891+
func (c *cli) cfg() *config.Root { return &c.prj.root }
1892+
func (c *cli) globals() *globals.Resolver { return c.prj.globals }
1893+
func (c *cli) rootNode() hcl.Config { return c.prj.root.Tree().Node }
1894+
func (c *cli) cred() credential { return c.cloud.client.Credential.(credential) }
18681895

18691896
func (c *cli) friendlyFmtDir(dir string) (string, bool) {
18701897
return prj.FriendlyFmtDir(c.rootdir(), c.wd(), dir)

cmd/terramate/cli/project.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"github.com/terramate-io/terramate/config"
1212
"github.com/terramate-io/terramate/errors"
1313
"github.com/terramate-io/terramate/git"
14+
"github.com/terramate-io/terramate/globals"
1415
"github.com/terramate-io/terramate/hcl"
1516
"github.com/terramate-io/terramate/stack"
1617
)
@@ -21,6 +22,7 @@ type project struct {
2122
isRepo bool
2223
root config.Root
2324
baseRef string
25+
globals *globals.Resolver
2426
normalizedRepo string
2527

2628
git struct {

config/assert_test.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"github.com/terramate-io/terramate/errors"
1414
"github.com/terramate-io/terramate/hcl"
1515
"github.com/terramate-io/terramate/hcl/eval"
16+
"github.com/terramate-io/terramate/project"
1617
"github.com/terramate-io/terramate/stdlib"
1718
"github.com/terramate-io/terramate/test"
1819
"github.com/zclconf/go-cty/cty"
@@ -175,13 +176,15 @@ func TestAssertConfigEval(t *testing.T) {
175176
tcase := tcase
176177
t.Run(tcase.name, func(t *testing.T) {
177178
t.Parallel()
178-
hclctx := eval.NewContext(stdlib.Functions(test.TempDir(t)))
179+
evalctx := eval.New(project.RootPath)
180+
funcs := stdlib.Functions(evalctx, t.TempDir())
181+
evalctx.SetFunctions(funcs)
179182

180183
for k, v := range tcase.namespaces {
181-
hclctx.SetNamespace(k, v.asCtyMap())
184+
evalctx.SetNamespace(k, v.asCtyMap())
182185
}
183186

184-
got, err := config.EvalAssert(hclctx, tcase.assert)
187+
got, err := config.EvalAssert(evalctx, tcase.assert)
185188
assert.IsError(t, err, tcase.wantErr)
186189
if !equalAsserts(tcase.want, got) {
187190
t.Fatalf("got %#v != want %#v", got, tcase.want)

config/config.go

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ const (
3838
type Root struct {
3939
tree Tree
4040

41+
lookupCache map[string]*Tree
42+
4143
runtime project.Runtime
4244
}
4345

@@ -104,7 +106,8 @@ func TryLoadConfig(fromdir string) (tree *Root, configpath string, found bool, e
104106
// NewRoot creates a new [Root] tree for the cfg tree.
105107
func NewRoot(tree *Tree) *Root {
106108
r := &Root{
107-
tree: *tree,
109+
tree: *tree,
110+
lookupCache: make(map[string]*Tree),
108111
}
109112
r.initRuntime()
110113
return r
@@ -127,7 +130,13 @@ func (root *Root) HostDir() string { return root.tree.RootDir() }
127130

128131
// Lookup a node from the root using a filesystem query path.
129132
func (root *Root) Lookup(path project.Path) (*Tree, bool) {
130-
return root.tree.lookup(path)
133+
tree, ok := root.lookupCache[path.String()]
134+
if ok {
135+
return tree, tree != nil
136+
}
137+
tree, ok = root.tree.lookup(path)
138+
root.lookupCache[path.String()] = tree
139+
return tree, ok
131140
}
132141

133142
// StacksByPaths returns the stacks from the provided relative paths.

config/script_test.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616
"github.com/terramate-io/terramate/hcl/ast"
1717
"github.com/terramate-io/terramate/hcl/eval"
1818
"github.com/terramate-io/terramate/hcl/info"
19+
"github.com/terramate-io/terramate/project"
1920
"github.com/terramate-io/terramate/stdlib"
2021
"github.com/terramate-io/terramate/test"
2122

@@ -298,7 +299,9 @@ func TestScriptEval(t *testing.T) {
298299
tcase := tcase
299300
t.Run(tcase.name, func(t *testing.T) {
300301
t.Parallel()
301-
hclctx := eval.NewContext(stdlib.Functions(test.TempDir(t)))
302+
scopedir := test.TempDir(t)
303+
hclctx := eval.New(project.NewPath("/"))
304+
hclctx.SetFunctions(stdlib.Functions(hclctx, scopedir))
302305
hclctx.SetNamespace("global", tcase.globals)
303306

304307
got, err := config.EvalScript(hclctx, tcase.script)

0 commit comments

Comments
 (0)