@@ -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+
16791725func (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-
17641798func (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
18691896func (c * cli ) friendlyFmtDir (dir string ) (string , bool ) {
18701897 return prj .FriendlyFmtDir (c .rootdir (), c .wd (), dir )
0 commit comments