From c100a1a3a5efdaefd738938a66b46853d9f0c2ea Mon Sep 17 00:00:00 2001 From: duarte Date: Tue, 10 Mar 2026 16:38:27 +0000 Subject: [PATCH 1/7] interpreter: add a BUILD methods callstack Add a callstack to the interpreter phase and enrich the build targets with the build statements that generated them. --- src/BUILD.plz | 1 + src/core/build_target.go | 42 ++++++++++++++++++++++++++++++++++++ src/parse/asp/interpreter.go | 42 +++++++++++++++++++++++++++++++++++- src/parse/asp/objects.go | 4 ++++ src/parse/asp/targets.go | 1 + 5 files changed, 89 insertions(+), 1 deletion(-) diff --git a/src/BUILD.plz b/src/BUILD.plz index c0eaec7e7..0416d9f0f 100644 --- a/src/BUILD.plz +++ b/src/BUILD.plz @@ -43,6 +43,7 @@ go_binary( "//src/version", "//src/watch", ], + strip = False, # Example change ) # This is handy for things like plz plz --repo_root diff --git a/src/core/build_target.go b/src/core/build_target.go index d15a827b5..eb9fe69a8 100644 --- a/src/core/build_target.go +++ b/src/core/build_target.go @@ -264,6 +264,8 @@ type BuildTarget struct { // If true, the interactive progress display will try to infer the target's progress // via some heuristics on its output. showProgress atomic.Bool `name:"progress"` + // Metadata collected during parsing and interpreter steps + ParseMetadata ParseMetadata } // ExpectedBuildMetadataVersionTag is the version tag that the current Please version expects. If this doesn't match @@ -296,6 +298,46 @@ type BuildMetadata struct { VersionTag int } +type ParseMetadata struct { + // A call stack from the interpret step that generated this target. + CallStack CallStack +} + +type CallStack []CallFrame + +func (cs *CallStack) Push(item CallFrame) { + *cs = append(*cs, item) +} + +func (cs *CallStack) Pop() CallFrame { + if len(*cs) == 0 { + var zero CallFrame + return zero + } + item := (*cs)[len(*cs)-1] + *cs = (*cs)[:len(*cs)-1] + return item +} + +type CallFrame struct { + MethodName string + Filename string + Position int + Label BuildLabel + Statement BuildStatement +} + +type BuildStatement struct { + Start int + End int +} + +func (s BuildStatement) Len() int { + return s.End - s.Start +} + + + // A PreBuildFunction is a type that allows hooking a pre-build callback. type PreBuildFunction interface { fmt.Stringer diff --git a/src/parse/asp/interpreter.go b/src/parse/asp/interpreter.go index 93e4ca0a3..9cd8c31ac 100644 --- a/src/parse/asp/interpreter.go +++ b/src/parse/asp/interpreter.go @@ -34,8 +34,11 @@ type interpreter struct { stringMethods, dictMethods, configMethods map[string]*pyFunc regexCache *cmap.Map[string, *regexp.Regexp] + // callStack stores a stack of previous call statements. + callStack core.CallStack } + // newInterpreter creates and returns a new interpreter instance. // It loads all the builtin rules at this point. func newInterpreter(state *core.BuildState, p *Parser) *interpreter { @@ -163,6 +166,7 @@ func (i *interpreter) preloadSubinclude(s *scope, label core.BuildLabel) (err er // interpretAll runs a series of statements in the scope of the given package. // The first return value is for testing only. func (i *interpreter) interpretAll(pkg *core.Package, forLabel, dependent *core.BuildLabel, mode core.ParseMode, statements []*Statement) (*scope, error) { + log.Warning("Interpreting Package: %s", pkg.Label()) s := i.scope.NewPackagedScope(pkg, mode, 1) s.config = i.getConfig(s.state).Copy() @@ -310,6 +314,8 @@ type scope struct { // True if this scope is for a pre- or post-build callback. Callback bool mode core.ParseMode + // points to the statement currently being interpreted + cursor *Statement } // parseAnnotatedLabelInPackage similarly to parseLabelInPackage, parses the label contextualising it to the provided @@ -514,6 +520,38 @@ func (s *scope) LoadSingletons(state *core.BuildState) { } } +func (s *scope) PushCall(name string) { + stmt := core.BuildStatement{ + Start: int(s.cursor.Pos), + End: int(s.cursor.EndPos), + } + var label core.BuildLabel + if s.parsingFor != nil { + label = s.parsingFor.label + } + + s.interpreter.callStack.Push(core.CallFrame{MethodName: name, Filename: s.filename, Label: label, Statement: stmt}) + + +} + +func (s *scope) PopCall() { + s.interpreter.callStack.Pop() +} + +func (s *scope) CallStackSnapshot() core.CallStack { + snapshot := make(core.CallStack, len(s.interpreter.callStack)) + copy(snapshot, s.interpreter.callStack) + + var stack string + for _,v := range s.interpreter.callStack { + stack += fmt.Sprintf("\n\t%v", v) + } + log.Info("CallStack Snapshot: %s", stack) + + return snapshot +} + // interpretStatements interprets a series of statements in a particular scope. // Note that the return value is only non-nil if a return statement is encountered; // it is not implicitly the result of the last statement or anything like that. @@ -525,6 +563,7 @@ func (s *scope) interpretStatements(statements []*Statement) pyObject { } }() for _, stmt = range statements { + s.cursor = stmt if stmt.FuncDef != nil { s.Set(stmt.FuncDef.Name, newPyFunc(s, stmt.FuncDef)) } else if stmt.If != nil { @@ -762,7 +801,7 @@ func (s *scope) interpretValueExpression(expr *ValueExpression) pyObject { if expr.Property != nil { obj = s.interpretIdent(s.property(obj, expr.Property.Name), expr.Property) } else if expr.Call != nil { - obj = s.callObject("", obj, expr.Call) + obj = s.callObject("", obj, expr.Call) } return obj } @@ -873,6 +912,7 @@ func (s *scope) interpretSliceExpression(obj pyObject, expr *Expression, def pyI func (s *scope) interpretIdent(obj pyObject, expr *IdentExpr) pyObject { name := expr.Name + // log.Info("Iden Expression name: %s", name) for _, action := range expr.Action { if action.Property != nil { name = action.Property.Name diff --git a/src/parse/asp/objects.go b/src/parse/asp/objects.go index a783b55b5..420cb3e21 100644 --- a/src/parse/asp/objects.go +++ b/src/parse/asp/objects.go @@ -698,6 +698,10 @@ func (f *pyFunc) Call(s *scope, c *Call) pyObject { } return f.callNative(s, c) } + + s.PushCall(f.name) + defer s.PopCall() + s2 := f.scope.newScope(s.pkg, s.mode, f.scope.filename, len(f.args)+1) s2.config = s.config s2.Set("CONFIG", s.config) // This needs to be copied across too :( diff --git a/src/parse/asp/targets.go b/src/parse/asp/targets.go index 48404b792..9fe057150 100644 --- a/src/parse/asp/targets.go +++ b/src/parse/asp/targets.go @@ -102,6 +102,7 @@ func createTarget(s *scope, args []pyObject) *core.BuildTarget { label.Subrepo = s.pkg.SubrepoName target := core.NewBuildTarget(label) + target.ParseMetadata.CallStack = s.CallStackSnapshot() target.Subrepo = s.pkg.Subrepo target.IsBinary = isTruthy(binaryBuildRuleArgIdx) target.IsSubrepo = isTruthy(subrepoArgIdx) From 4bb7be1daaaf88207ff7cff613017201193c7f22 Mon Sep 17 00:00:00 2001 From: duarte Date: Tue, 10 Mar 2026 16:40:48 +0000 Subject: [PATCH 2/7] export: utilize target build statements callstack to generate exported BUILD files Rework export logic to export directly the build statements that generate a target. --- src/export/BUILD | 1 - src/export/export.go | 145 +++++++++++++++++++++++++++++-------------- 2 files changed, 100 insertions(+), 46 deletions(-) diff --git a/src/export/BUILD b/src/export/BUILD index 6b44d63d9..76217a23e 100644 --- a/src/export/BUILD +++ b/src/export/BUILD @@ -7,7 +7,6 @@ go_library( "//src/cli/logging", "//src/core", "//src/fs", - "//src/gc", "//src/parse", ], ) diff --git a/src/export/export.go b/src/export/export.go index 2863dfef0..77984ddca 100644 --- a/src/export/export.go +++ b/src/export/export.go @@ -7,11 +7,11 @@ import ( iofs "io/fs" "os" "path/filepath" + "slices" "github.com/thought-machine/please/src/cli/logging" "github.com/thought-machine/please/src/core" "github.com/thought-machine/please/src/fs" - "github.com/thought-machine/please/src/gc" "github.com/thought-machine/please/src/parse" ) @@ -22,19 +22,21 @@ type export struct { targetDir string noTrim bool - exportedTargets map[core.BuildLabel]bool - exportedPackages map[string]bool + exportedTargets map[core.BuildLabel]bool + exportedPackages map[string]bool + selectedStatements map[string]map[core.BuildStatement]bool } // ToDir exports a set of targets to the given directory. // It dies on any errors. func ToDir(state *core.BuildState, dir string, noTrim bool, targets []core.BuildLabel) { e := &export{ - state: state, - noTrim: noTrim, - targetDir: dir, - exportedPackages: map[string]bool{}, - exportedTargets: map[core.BuildLabel]bool{}, + state: state, + noTrim: noTrim, + targetDir: dir, + exportedPackages: map[string]bool{}, + exportedTargets: map[core.BuildLabel]bool{}, + selectedStatements: map[string]map[core.BuildStatement]bool{}, } if err := os.MkdirAll(dir, fs.DirPermissions); err != nil { @@ -47,14 +49,12 @@ func ToDir(state *core.BuildState, dir string, noTrim bool, targets []core.Build e.export(state.Graph.TargetOrDie(includeLabel)) } } + for _, target := range targets { e.export(state.Graph.TargetOrDie(target)) } - // Now write all the build files - packages := map[*core.Package]bool{} - for target := range e.exportedTargets { - packages[state.Graph.PackageOrDie(target)] = true - } + + e.writeBuildStatements() // Write any preloaded build defs as well; preloaded subincludes should be fine though. for _, preload := range state.Config.Parse.PreloadBuildDefs { @@ -62,33 +62,6 @@ func ToDir(state *core.BuildState, dir string, noTrim bool, targets []core.Build log.Fatalf("Failed to copy preloaded build def %s: %s", preload, err) } } - - if noTrim { - return // We have already exported the whole directory - } - - for pkg := range packages { - if pkg.Name == parse.InternalPackageName { - continue // This isn't a real package to be copied - } - if pkg.Subrepo != nil { - continue // Don't copy subrepo BUILD files... they don't exist in our source tree - } - dest := filepath.Join(dir, pkg.Filename) - if err := fs.CopyFile(pkg.Filename, dest, 0); err != nil { - log.Fatalf("Failed to copy BUILD file %s: %s\n", pkg.Filename, err) - } - // Now rewrite the unused targets out of it - var victims []string - for _, target := range pkg.AllTargets() { - if !e.exportedTargets[target.Label] && !target.HasParent() { - victims = append(victims, target.Label.Name) - } - } - if err := gc.RewriteFile(state, dest, victims); err != nil { - log.Fatalf("Failed to rewrite BUILD file: %s\n", err) - } - } } func (e *export) exportPlzConf() { @@ -99,7 +72,7 @@ func (e *export) exportPlzConf() { for _, file := range profiles { path := filepath.Join(e.targetDir, file) if err := os.RemoveAll(path); err != nil { - log.Fatalf("failed to copy .plzconfig file %s: %v", file, err) + log.Fatalf("failed to remove .plzconfig file %s: %v", file, err) } if err := fs.CopyFile(file, path, 0); err != nil { log.Fatalf("failed to copy .plzconfig file %s: %v", file, err) @@ -129,8 +102,8 @@ var ignoreDirectories = map[string]bool{ ".hg": true, } -// exportPackage exports the package BUILD file containing the given target and all sources -func (e *export) exportPackage(target *core.BuildTarget) { +// exportEntirePackage exports the package BUILD file containing the given target and all sources +func (e *export) exportEntirePackage(target *core.BuildTarget) { pkgName := target.Label.PackageName if pkgName == parse.InternalPackageName { return @@ -169,34 +142,116 @@ func (e *export) exportPackage(target *core.BuildTarget) { } } +// selectBuildStatements exports BUILD statements that generate the build target. +func (e *export) selectBuildStatements(target *core.BuildTarget) { + if target.Label.PackageName == parse.InternalPackageName { + return + } + + log.Infof("Selecting Build stmts of %s with callstack:\n", target.Label.String()) + for _, frame := range target.ParseMetadata.CallStack { + log.Infof("\t%v", frame) + if frame.Statement.Start == 0 && frame.Statement.End == 0 { + continue + } + + if frame.Filename != "" { + if _, ok := e.selectedStatements[frame.Filename]; !ok { + e.selectedStatements[frame.Filename] = map[core.BuildStatement]bool{} + } + e.selectedStatements[frame.Filename][frame.Statement] = true + } + if frame.Filename == "" { + log.Warning("Package without filename %v", frame) + } + } +} + // export implements the logic of ToDir, but prevents repeating targets. func (e *export) export(target *core.BuildTarget) { if e.exportedTargets[target.Label] { return } + log.Warningf("Exporting %v.\n", target.Label) + e.exportedTargets[target.Label] = true + // We want to export the package that made this subrepo available, but we still need to walk the target deps // as it may depend on other subrepos or first party targets if target.Subrepo != nil { e.export(target.Subrepo.Target) } else if e.noTrim { // Export the whole package, rather than trying to trim the package down to only the targets we need - e.exportPackage(target) + e.exportEntirePackage(target) } else { + e.selectBuildStatements(target) e.exportSources(target) } - e.exportedTargets[target.Label] = true for _, dep := range target.Dependencies() { e.export(dep) } for _, subinclude := range e.state.Graph.PackageOrDie(target.Label).AllSubincludes(e.state.Graph) { e.export(e.state.Graph.TargetOrDie(subinclude)) } + // TODO handle parents from Callstack. How? Using the current label? if parent := target.Parent(e.state.Graph); parent != nil && parent != target { e.export(parent) } } +// writeBuildStatements writes the BUILD file statements to the export directory. +func (e *export) writeBuildStatements() { + log.Infof("Selected Statements: %v", e.selectedStatements) + + for filename, stmtMap := range e.selectedStatements { + stmts := make([]core.BuildStatement, 0, len(stmtMap)) + for stmt := range stmtMap { + stmts = append(stmts, stmt) + } + // Sort statements by position to keep them in order + slices.SortFunc(stmts, func(a, b core.BuildStatement) int { + return int(a.Start) + }) + + e.writeBuildStatement(filename, stmts) + } +} + +func (e *export) writeBuildStatement(filename string, stmts []core.BuildStatement) { + log.Infof("Writing file: %s", filename) + if err := fs.EnsureDir(filepath.Join(e.targetDir, filename)); err != nil { + log.Fatalf("failed to create directory for %s: %v", filename, err) + } + fw, err := os.Create(filepath.Join(e.targetDir, filename)) + if err != nil { + log.Fatalf("failed to create BUILD file %s: %v", filename, err) + } + defer fw.Close() + + fr, err := os.Open(filename) + if err != nil { + // TODO ensure only visiting correct files and move Warn to Fatal + log.Warningf("failed to open file %s: %v", filename, err) + return + } + defer fr.Close() + + for _, s := range stmts { + buff := make([]byte, s.Len()) + _, err := fr.ReadAt(buff, int64(s.Start)) + if err != nil { + log.Fatalf("failed to read BUILD file %s: %v", filename, err) + } + + if _, err := fw.Write(buff); err != nil { + log.Fatalf("failed to write statement to %s: %v", filename, err) + } + if _, err := fw.WriteString("\n\n"); err != nil { + log.Fatalf("failed to write newline to %s: %v", filename, err) + } + } +} + // Outputs exports the outputs of a target. func Outputs(state *core.BuildState, dir string, targets []core.BuildLabel) { for _, label := range targets { From f750c3960879da8e5ccfc3d18750cfc004d10a92 Mon Sep 17 00:00:00 2001 From: duarte Date: Wed, 18 Mar 2026 18:07:58 +0000 Subject: [PATCH 3/7] crude implementation of related targets and push call for native code --- src/core/build_target.go | 20 +++++++++++++++++++- src/export/export.go | 30 +++++++++++++++++------------- src/parse/asp/interpreter.go | 23 ++++++++++++++++++----- src/parse/asp/objects.go | 3 --- src/parse/asp/targets.go | 2 +- 5 files changed, 55 insertions(+), 23 deletions(-) diff --git a/src/core/build_target.go b/src/core/build_target.go index eb9fe69a8..14f844ce8 100644 --- a/src/core/build_target.go +++ b/src/core/build_target.go @@ -322,7 +322,6 @@ func (cs *CallStack) Pop() CallFrame { type CallFrame struct { MethodName string Filename string - Position int Label BuildLabel Statement BuildStatement } @@ -2013,6 +2012,25 @@ func (target *BuildTarget) NeedCoverage(state *BuildState) bool { return state.NeedCoverage && !target.Test.NoOutput && !target.Test.NoCoverage && !target.HasAnyLabel(state.Config.Test.DisableCoverage) } +// RelatedTargets returns all the targets in the package that originate from the same Build Statement. +func (target *BuildTarget) RelatedTargets(graph *BuildGraph) BuildTargets { + relatedTargets := make(BuildTargets, 0) + pkg := graph.PackageOrDie(target.Label) + for _, otherTarget := range pkg.AllTargets() { + if otherTarget.isRelated(target) { + relatedTargets = append(relatedTargets, otherTarget) + } + } + return relatedTargets +} + +func (target *BuildTarget) isRelated(other *BuildTarget) bool { + if len(target.ParseMetadata.CallStack) == 0 || len(target.ParseMetadata.CallStack) == 0 { + return false + } + return target.ParseMetadata.CallStack[0].Statement == other.ParseMetadata.CallStack[0].Statement +} + // Parent finds the parent of a build target, or nil if the target is parentless. // Note that this is a fairly informal relationship; we identify it by labels with the convention of // a leading _ and trailing hashtag on child rules, rather than storing pointers between them in the graph. diff --git a/src/export/export.go b/src/export/export.go index 77984ddca..9e8880ba2 100644 --- a/src/export/export.go +++ b/src/export/export.go @@ -4,10 +4,12 @@ package export import ( + "fmt" iofs "io/fs" "os" "path/filepath" "slices" + "strings" "github.com/thought-machine/please/src/cli/logging" "github.com/thought-machine/please/src/core" @@ -50,6 +52,7 @@ func ToDir(state *core.BuildState, dir string, noTrim bool, targets []core.Build } } + log.Warningf("Exporting selected targets: %v", targets) for _, target := range targets { e.export(state.Graph.TargetOrDie(target)) } @@ -89,6 +92,7 @@ func (e *export) exportSources(target *core.BuildTarget) { if err := fs.RecursiveCopy(p, filepath.Join(e.targetDir, p), 0); err != nil { log.Fatalf("Error copying file: %s\n", err) } + log.Warning("Writing source file: %s", p) } } } @@ -151,11 +155,8 @@ func (e *export) selectBuildStatements(target *core.BuildTarget) { log.Infof("Selecting Build stmts of %s with callstack:\n", target.Label.String()) for _, frame := range target.ParseMetadata.CallStack { log.Infof("\t%v", frame) - if frame.Statement.Start == 0 && frame.Statement.End == 0 { - continue - } - if frame.Filename != "" { + if frame.Filename != "" && !strings.HasPrefix(frame.Filename, core.OutDir) { if _, ok := e.selectedStatements[frame.Filename]; !ok { e.selectedStatements[frame.Filename] = map[core.BuildStatement]bool{} } @@ -178,6 +179,7 @@ func (e *export) export(target *core.BuildTarget) { // We want to export the package that made this subrepo available, but we still need to walk the target deps // as it may depend on other subrepos or first party targets if target.Subrepo != nil { + log.Warningf("Subrepo: %v", target.Subrepo.Target) e.export(target.Subrepo.Target) } else if e.noTrim { // Export the whole package, rather than trying to trim the package down to only the targets we need @@ -187,21 +189,23 @@ func (e *export) export(target *core.BuildTarget) { e.exportSources(target) } + for _, dep := range target.Dependencies() { e.export(dep) } for _, subinclude := range e.state.Graph.PackageOrDie(target.Label).AllSubincludes(e.state.Graph) { e.export(e.state.Graph.TargetOrDie(subinclude)) } - // TODO handle parents from Callstack. How? Using the current label? - if parent := target.Parent(e.state.Graph); parent != nil && parent != target { - e.export(parent) + + for _, otherTarget := range target.RelatedTargets(e.state.Graph) { + log.Warningf("Exporting Other %s", otherTarget) + e.export(otherTarget) } } // writeBuildStatements writes the BUILD file statements to the export directory. func (e *export) writeBuildStatements() { - log.Infof("Selected Statements: %v", e.selectedStatements) + log.Warningf("Selected Statements: %v", e.selectedStatements) for filename, stmtMap := range e.selectedStatements { stmts := make([]core.BuildStatement, 0, len(stmtMap)) @@ -210,15 +214,15 @@ func (e *export) writeBuildStatements() { } // Sort statements by position to keep them in order slices.SortFunc(stmts, func(a, b core.BuildStatement) int { - return int(a.Start) + return a.Start - b.Start }) - e.writeBuildStatement(filename, stmts) + e.writeBuildFile(filename, stmts) } } -func (e *export) writeBuildStatement(filename string, stmts []core.BuildStatement) { - log.Infof("Writing file: %s", filename) +func (e *export) writeBuildFile(filename string, stmts []core.BuildStatement) { + log.Warningf("Writing file: %s", filename) if err := fs.EnsureDir(filepath.Join(e.targetDir, filename)); err != nil { log.Fatalf("failed to create directory for %s: %v", filename, err) } @@ -246,7 +250,7 @@ func (e *export) writeBuildStatement(filename string, stmts []core.BuildStatemen if _, err := fw.Write(buff); err != nil { log.Fatalf("failed to write statement to %s: %v", filename, err) } - if _, err := fw.WriteString("\n\n"); err != nil { + if _, err := fmt.Fprintf(fw, "\n#%+v\n\n", s); err != nil { log.Fatalf("failed to write newline to %s: %v", filename, err) } } diff --git a/src/parse/asp/interpreter.go b/src/parse/asp/interpreter.go index 9cd8c31ac..9da4a414e 100644 --- a/src/parse/asp/interpreter.go +++ b/src/parse/asp/interpreter.go @@ -190,6 +190,8 @@ func (i *interpreter) interpretAll(pkg *core.Package, forLabel, dependent *core. } } + log.Warning("Preload done for Package: %s", pkg.Label()) + s.Set("CONFIG", s.config) _, err := i.interpretStatements(s, statements) if err == nil { @@ -520,7 +522,11 @@ func (s *scope) LoadSingletons(state *core.BuildState) { } } -func (s *scope) PushCall(name string) { +func (s *scope) PushCall(f *pyFunc) bool { + if s.cursor == nil { + return false // skip builtin method calls (e.g. format) + } + stmt := core.BuildStatement{ Start: int(s.cursor.Pos), End: int(s.cursor.EndPos), @@ -530,16 +536,17 @@ func (s *scope) PushCall(name string) { label = s.parsingFor.label } - s.interpreter.callStack.Push(core.CallFrame{MethodName: name, Filename: s.filename, Label: label, Statement: stmt}) - + log.Debug("PushCall ", f.name, s.filename,label, stmt, s.cursor) + s.interpreter.callStack.Push(core.CallFrame{MethodName: f.name, Filename: s.filename, Label: label, Statement: stmt}) + return true } func (s *scope) PopCall() { s.interpreter.callStack.Pop() } -func (s *scope) CallStackSnapshot() core.CallStack { +func (s *scope) CallStackSnapshot(name string) core.CallStack { snapshot := make(core.CallStack, len(s.interpreter.callStack)) copy(snapshot, s.interpreter.callStack) @@ -547,7 +554,7 @@ func (s *scope) CallStackSnapshot() core.CallStack { for _,v := range s.interpreter.callStack { stack += fmt.Sprintf("\n\t%v", v) } - log.Info("CallStack Snapshot: %s", stack) + log.Warningf("CallStack Snapshot for %s: %s", name, stack) return snapshot } @@ -564,6 +571,7 @@ func (s *scope) interpretStatements(statements []*Statement) pyObject { }() for _, stmt = range statements { s.cursor = stmt + // log.Warningf("Cursor val (%s/%s): %v", s.filename, s.parsingFor, s.cursor) if stmt.FuncDef != nil { s.Set(stmt.FuncDef.Name, newPyFunc(s, stmt.FuncDef)) } else if stmt.If != nil { @@ -1084,6 +1092,11 @@ func (s *scope) callObject(name string, obj pyObject, c *Call) pyObject { if !ok { s.Error("Non-callable object '%s' (is a %s)", name, obj.Type()) } + + if ok := s.PushCall(f); ok { + defer s.PopCall() + } + return f.Call(s, c) } diff --git a/src/parse/asp/objects.go b/src/parse/asp/objects.go index 420cb3e21..7adea0b7c 100644 --- a/src/parse/asp/objects.go +++ b/src/parse/asp/objects.go @@ -699,9 +699,6 @@ func (f *pyFunc) Call(s *scope, c *Call) pyObject { return f.callNative(s, c) } - s.PushCall(f.name) - defer s.PopCall() - s2 := f.scope.newScope(s.pkg, s.mode, f.scope.filename, len(f.args)+1) s2.config = s.config s2.Set("CONFIG", s.config) // This needs to be copied across too :( diff --git a/src/parse/asp/targets.go b/src/parse/asp/targets.go index 9fe057150..8fa23c818 100644 --- a/src/parse/asp/targets.go +++ b/src/parse/asp/targets.go @@ -102,7 +102,7 @@ func createTarget(s *scope, args []pyObject) *core.BuildTarget { label.Subrepo = s.pkg.SubrepoName target := core.NewBuildTarget(label) - target.ParseMetadata.CallStack = s.CallStackSnapshot() + target.ParseMetadata.CallStack = s.CallStackSnapshot(name) target.Subrepo = s.pkg.Subrepo target.IsBinary = isTruthy(binaryBuildRuleArgIdx) target.IsSubrepo = isTruthy(subrepoArgIdx) From 048b19e070954ec3266b3d3b8f522be90c16b5e4 Mon Sep 17 00:00:00 2001 From: duarte Date: Wed, 18 Mar 2026 18:59:36 +0000 Subject: [PATCH 4/7] filename as part of build statement --- src/core/build_target.go | 4 ++-- src/export/export.go | 10 +++++----- src/parse/asp/interpreter.go | 7 ++++--- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/core/build_target.go b/src/core/build_target.go index 14f844ce8..5d866c5d7 100644 --- a/src/core/build_target.go +++ b/src/core/build_target.go @@ -321,12 +321,12 @@ func (cs *CallStack) Pop() CallFrame { type CallFrame struct { MethodName string - Filename string Label BuildLabel Statement BuildStatement } type BuildStatement struct { + Filename string Start int End int } @@ -2025,7 +2025,7 @@ func (target *BuildTarget) RelatedTargets(graph *BuildGraph) BuildTargets { } func (target *BuildTarget) isRelated(other *BuildTarget) bool { - if len(target.ParseMetadata.CallStack) == 0 || len(target.ParseMetadata.CallStack) == 0 { + if len(target.ParseMetadata.CallStack) == 0 || len(other.ParseMetadata.CallStack) == 0 { return false } return target.ParseMetadata.CallStack[0].Statement == other.ParseMetadata.CallStack[0].Statement diff --git a/src/export/export.go b/src/export/export.go index 9e8880ba2..f7424d05e 100644 --- a/src/export/export.go +++ b/src/export/export.go @@ -156,13 +156,13 @@ func (e *export) selectBuildStatements(target *core.BuildTarget) { for _, frame := range target.ParseMetadata.CallStack { log.Infof("\t%v", frame) - if frame.Filename != "" && !strings.HasPrefix(frame.Filename, core.OutDir) { - if _, ok := e.selectedStatements[frame.Filename]; !ok { - e.selectedStatements[frame.Filename] = map[core.BuildStatement]bool{} + if frame.Statement.Filename != "" && !strings.HasPrefix(frame.Statement.Filename, core.OutDir) { + if _, ok := e.selectedStatements[frame.Statement.Filename]; !ok { + e.selectedStatements[frame.Statement.Filename] = map[core.BuildStatement]bool{} } - e.selectedStatements[frame.Filename][frame.Statement] = true + e.selectedStatements[frame.Statement.Filename][frame.Statement] = true } - if frame.Filename == "" { + if frame.Statement.Filename == "" { log.Warning("Package without filename %v", frame) } } diff --git a/src/parse/asp/interpreter.go b/src/parse/asp/interpreter.go index 9da4a414e..7bc91f406 100644 --- a/src/parse/asp/interpreter.go +++ b/src/parse/asp/interpreter.go @@ -528,8 +528,9 @@ func (s *scope) PushCall(f *pyFunc) bool { } stmt := core.BuildStatement{ - Start: int(s.cursor.Pos), - End: int(s.cursor.EndPos), + Filename: s.filename, + Start: int(s.cursor.Pos), + End: int(s.cursor.EndPos), } var label core.BuildLabel if s.parsingFor != nil { @@ -538,7 +539,7 @@ func (s *scope) PushCall(f *pyFunc) bool { log.Debug("PushCall ", f.name, s.filename,label, stmt, s.cursor) - s.interpreter.callStack.Push(core.CallFrame{MethodName: f.name, Filename: s.filename, Label: label, Statement: stmt}) + s.interpreter.callStack.Push(core.CallFrame{MethodName: f.name, Label: label, Statement: stmt}) return true } From 0bb9ef5465408a985613aacded27535d6e37e13c Mon Sep 17 00:00:00 2001 From: duarte Date: Thu, 19 Mar 2026 12:20:22 +0000 Subject: [PATCH 5/7] test: initial build statement testing --- test/build_defs/test.build_defs | 3 +- test/export/BUILD | 16 ++++++++++ test/export/repo/test/BUILD_FILE | 8 +++++ test/export/repo/test/file.txt | 1 + test/export/test_build_statement_content.sh | 33 +++++++++++++++++++++ 5 files changed, 60 insertions(+), 1 deletion(-) create mode 100644 test/export/repo/test/BUILD_FILE create mode 100644 test/export/repo/test/file.txt create mode 100755 test/export/test_build_statement_content.sh diff --git a/test/build_defs/test.build_defs b/test/build_defs/test.build_defs index 5830e3f62..33ab98369 100644 --- a/test/build_defs/test.build_defs +++ b/test/build_defs/test.build_defs @@ -10,6 +10,7 @@ def please_repo_e2e_test( expected_output: dict = {}, expect_output_contains: dict = {}, expect_output_doesnt_contain: dict = {}, + tool_content_checker = ["//test/build_defs:content_checker"], labels: list = [], ): plz_command = plz_command.replace("plz ", "$TOOLS_PLEASE ") @@ -37,7 +38,7 @@ def please_repo_e2e_test( data["BASE_CONFIG"] = ["//test/build_defs:base_config"] tools["PLEASE"] = ["//package:installed_files|please"] - tools["CONTENT_CHECKER"] = ["//test/build_defs:content_checker"] + tools["CONTENT_CHECKER"] = tool_content_checker return gentest( name = name, diff --git a/test/export/BUILD b/test/export/BUILD index c0f7ed5a0..9ac48b353 100644 --- a/test/export/BUILD +++ b/test/export/BUILD @@ -4,3 +4,19 @@ plz_e2e_test( name = "export_src_please_test", cmd = "plz export --output plz-out/plzexport //src/core && plz --repo_root=$(plz query reporoot)/plz-out/plzexport build //src/core", ) + +sh_binary( + name = "build_statement_checker", + main = "test_build_statement_content.sh", +) + +# Export a target generated by a native genrule target. +please_repo_e2e_test( + name = "export_native_genrule_bstmt", + plz_command = "plz export --output plz-out/plzexport //test:native_genrule", + expected_output = { + "plz-out/plzexport/test/BUILD_FILE": "test/BUILD_FILE:native_genrule", + }, + tool_content_checker = [":build_statement_checker"], + repo = "repo", +) diff --git a/test/export/repo/test/BUILD_FILE b/test/export/repo/test/BUILD_FILE new file mode 100644 index 000000000..e57512d48 --- /dev/null +++ b/test/export/repo/test/BUILD_FILE @@ -0,0 +1,8 @@ +# Start BStmt native_genrule +genrule( + name = "native_genrule", + srcs = ["file.txt"], + outs = ["file.wordcount"], + cmd = "wc $SRCS > $OUT", +) +# End BStmt diff --git a/test/export/repo/test/file.txt b/test/export/repo/test/file.txt new file mode 100644 index 000000000..9768ee14c --- /dev/null +++ b/test/export/repo/test/file.txt @@ -0,0 +1 @@ +Test source file diff --git a/test/export/test_build_statement_content.sh b/test/export/test_build_statement_content.sh new file mode 100755 index 000000000..939638cab --- /dev/null +++ b/test/export/test_build_statement_content.sh @@ -0,0 +1,33 @@ +#!/usr/bin/env bash + + +gen_file=$1 +if ! test -f "$gen_file"; then + echo "$gen_file" doesnt exist + exit 1 +fi + +# Statement identifier of type path/file:name +stmt_identifier=$2 +# string BEFORE the last colon +file_path="${stmt_identifier%:*}" +# string AFTER the last colon +statement_name="${stmt_identifier##*:}" + +if ! test -f "$file_path"; then + echo "$file_path" doesnt exist + exit 1 +fi + +# Extract the block into a variable +ORIG_CONTENT=$(sed -n "/# Start BStmt ${statement_name}/,/# End BStmt/{ /# /d; p; }" "$file_path") + +if ! grep -Fq "$ORIG_CONTENT" "$gen_file"; then + printf '%s\n%s\n%s\n%s\n%s\n' \ + "${gen_file} doesnt contain" \ + "${ORIG_CONTENT}" \ + "---- it contains ----" \ + "$(cat "$gen_file")" \ + "---- EOF ----" + exit 1 +fi From c4a305a34f9f4c8c09eb3d91d084b0868b08477f Mon Sep 17 00:00:00 2001 From: duarte Date: Thu, 19 Mar 2026 17:11:51 +0000 Subject: [PATCH 6/7] test: move to export_e2e build rule --- test/build_defs/test.build_defs | 7 ++- test/export/BUILD | 48 +++++++++++++++------ test/export/repo/test/BUILD_FILE | 4 +- test/export/test_build_statement_content.sh | 20 ++++----- 4 files changed, 52 insertions(+), 27 deletions(-) diff --git a/test/build_defs/test.build_defs b/test/build_defs/test.build_defs index 33ab98369..c7b5df9bb 100644 --- a/test/build_defs/test.build_defs +++ b/test/build_defs/test.build_defs @@ -5,12 +5,13 @@ def please_repo_e2e_test( repo: str, data: dict={}, deps: list=[], + defer_cmd=[], tools: dict={}, + tool_content_checker = ["//test/build_defs:content_checker"], expected_failure: bool = False, expected_output: dict = {}, expect_output_contains: dict = {}, expect_output_doesnt_contain: dict = {}, - tool_content_checker = ["//test/build_defs:content_checker"], labels: list = [], ): plz_command = plz_command.replace("plz ", "$TOOLS_PLEASE ") @@ -32,6 +33,10 @@ def please_repo_e2e_test( if expect_output_doesnt_contain: test_cmd += [f'_STR="$(cat {o})" _SUBSTR="{c}" && if [ -z "${_STR##*$_SUBSTR*}" ]; then echo "$_STR"; exit 1; fi' for o, c in expect_output_doesnt_contain.items()] + # defer commands should be added last + if defer_cmd: + test_cmd += [cmd.replace("plz ", "$TOOLS_PLEASE ") for cmd in defer_cmd] + test_cmd = ' && '.join(test_cmd) data["REPO"] = [repo] diff --git a/test/export/BUILD b/test/export/BUILD index 9ac48b353..bf483516b 100644 --- a/test/export/BUILD +++ b/test/export/BUILD @@ -1,22 +1,46 @@ subinclude("//test/build_defs") -plz_e2e_test( - name = "export_src_please_test", - cmd = "plz export --output plz-out/plzexport //src/core && plz --repo_root=$(plz query reporoot)/plz-out/plzexport build //src/core", -) - sh_binary( name = "build_statement_checker", main = "test_build_statement_content.sh", ) +# Runs an export e2e test. It purposely tests for different aspect (e.g. BUILD files, sources) +# within the same test to avoid the overhead of exporting multiple times. +def please_export_e2e_test( + name:str, + export_target:str, + repo:str): + + EXPORT_DIR = "plz-out/plzexport" + please_target = "//" + export_target + file_path, target_name = export_target.split(":") + build_file = file_path + "/BUILD_FILE" + + return please_repo_e2e_test( + name = name, + repo = repo, + plz_command = f"plz export --output {EXPORT_DIR} {please_target}", + defer_cmd = [ + # Tests the contents of the exported BUILD file + f'$TOOLS_BUILD_STATEMENT_CHECKER "{EXPORT_DIR}/{build_file}" "{build_file}" "{target_name}"', + # Tests building the exported target + f'plz build --repo_root=$(plz query reporoot)/plz-out/plzexport "{please_target}"', + ], + tools = { + "BUILD_STATEMENT_CHECKER": "//test/export:build_statement_checker" + }, + ) + +# Generic catch-all test on internal repo. +plz_e2e_test( + name = "export_src_please_test", + cmd = "plz export --output plz-out/plzexport //src/core && plz --repo_root=$(plz query reporoot)/plz-out/plzexport build //src/core", +) + # Export a target generated by a native genrule target. -please_repo_e2e_test( - name = "export_native_genrule_bstmt", - plz_command = "plz export --output plz-out/plzexport //test:native_genrule", - expected_output = { - "plz-out/plzexport/test/BUILD_FILE": "test/BUILD_FILE:native_genrule", - }, - tool_content_checker = [":build_statement_checker"], +please_export_e2e_test( + name = "export_native_genrule", + export_target = "test:native_genrule", repo = "repo", ) diff --git a/test/export/repo/test/BUILD_FILE b/test/export/repo/test/BUILD_FILE index e57512d48..e198e11f9 100644 --- a/test/export/repo/test/BUILD_FILE +++ b/test/export/repo/test/BUILD_FILE @@ -1,8 +1,8 @@ -# Start BStmt native_genrule +# Start BUILDStmt native_genrule genrule( name = "native_genrule", srcs = ["file.txt"], outs = ["file.wordcount"], cmd = "wc $SRCS > $OUT", ) -# End BStmt +# End BUILDStmt diff --git a/test/export/test_build_statement_content.sh b/test/export/test_build_statement_content.sh index 939638cab..92d4a054b 100755 --- a/test/export/test_build_statement_content.sh +++ b/test/export/test_build_statement_content.sh @@ -1,29 +1,25 @@ #!/usr/bin/env bash - gen_file=$1 +orig_file=$2 +stmt_identifier=$3 + if ! test -f "$gen_file"; then echo "$gen_file" doesnt exist exit 1 fi -# Statement identifier of type path/file:name -stmt_identifier=$2 -# string BEFORE the last colon -file_path="${stmt_identifier%:*}" -# string AFTER the last colon -statement_name="${stmt_identifier##*:}" - -if ! test -f "$file_path"; then - echo "$file_path" doesnt exist +if ! test -f "$orig_file"; then + echo "$orig_file" doesnt exist exit 1 fi # Extract the block into a variable -ORIG_CONTENT=$(sed -n "/# Start BStmt ${statement_name}/,/# End BStmt/{ /# /d; p; }" "$file_path") +ORIG_CONTENT=$(sed -n "/# Start BStmt ${stmt_identifier}/,/# End BStmt/{ /# /d; p; }" "$orig_file") if ! grep -Fq "$ORIG_CONTENT" "$gen_file"; then - printf '%s\n%s\n%s\n%s\n%s\n' \ + printf '%s\n%s\n%s\n%s\n%s\n%s\n' \ + "BUILD statements mismatch" \ "${gen_file} doesnt contain" \ "${ORIG_CONTENT}" \ "---- it contains ----" \ From 5383e64b4cec824dc8d387e736578e915b072fc0 Mon Sep 17 00:00:00 2001 From: duarte Date: Fri, 20 Mar 2026 15:50:27 +0000 Subject: [PATCH 7/7] test: add test for custom build def and rework build stmt checked build stmt checker now uses awk, supports more than one stmt associated with with a target and has a strict matching mode --- test/export/BUILD | 27 ++-- test/export/build_statement_checker.sh | 119 ++++++++++++++++++ test/export/repo/.plzconfig | 0 test/export/repo/test/BUILD_FILE | 8 -- test/export/repo/test_builtins/BUILD_FILE | 17 +++ .../repo/{test => test_builtins}/file.txt | 0 test/export/repo/test_custom_defs/BUILD_FILE | 11 ++ .../test_custom_defs/build_defs/BUILD_FILE | 5 + .../build_defs/custom.build_defs | 10 ++ test/export/repo/test_custom_defs/file.txt | 1 + test/export/test_build_statement_content.sh | 29 ----- 11 files changed, 183 insertions(+), 44 deletions(-) create mode 100755 test/export/build_statement_checker.sh create mode 100644 test/export/repo/.plzconfig delete mode 100644 test/export/repo/test/BUILD_FILE create mode 100644 test/export/repo/test_builtins/BUILD_FILE rename test/export/repo/{test => test_builtins}/file.txt (100%) create mode 100644 test/export/repo/test_custom_defs/BUILD_FILE create mode 100644 test/export/repo/test_custom_defs/build_defs/BUILD_FILE create mode 100644 test/export/repo/test_custom_defs/build_defs/custom.build_defs create mode 100644 test/export/repo/test_custom_defs/file.txt delete mode 100755 test/export/test_build_statement_content.sh diff --git a/test/export/BUILD b/test/export/BUILD index bf483516b..b47719a48 100644 --- a/test/export/BUILD +++ b/test/export/BUILD @@ -2,7 +2,7 @@ subinclude("//test/build_defs") sh_binary( name = "build_statement_checker", - main = "test_build_statement_content.sh", + main = "build_statement_checker.sh", ) # Runs an export e2e test. It purposely tests for different aspect (e.g. BUILD files, sources) @@ -10,22 +10,27 @@ sh_binary( def please_export_e2e_test( name:str, export_target:str, - repo:str): + repo:str, + strict_check_build_stmt:bool = False, + ): EXPORT_DIR = "plz-out/plzexport" + please_target = "//" + export_target file_path, target_name = export_target.split(":") build_file = file_path + "/BUILD_FILE" + strict_opt = "--strict" if strict_check_build_stmt else "" + return please_repo_e2e_test( name = name, repo = repo, plz_command = f"plz export --output {EXPORT_DIR} {please_target}", defer_cmd = [ # Tests the contents of the exported BUILD file - f'$TOOLS_BUILD_STATEMENT_CHECKER "{EXPORT_DIR}/{build_file}" "{build_file}" "{target_name}"', - # Tests building the exported target - f'plz build --repo_root=$(plz query reporoot)/plz-out/plzexport "{please_target}"', + f'$TOOLS_BUILD_STATEMENT_CHECKER {strict_opt} --exported "{EXPORT_DIR}/{build_file}" --original "{build_file}" --target "{target_name}"', + # Tests building the exported target which in turn ensures the sources are included + f'plz build --repo_root=$(plz query reporoot)/{EXPORT_DIR} "{please_target}"', ], tools = { "BUILD_STATEMENT_CHECKER": "//test/export:build_statement_checker" @@ -38,9 +43,17 @@ plz_e2e_test( cmd = "plz export --output plz-out/plzexport //src/core && plz --repo_root=$(plz query reporoot)/plz-out/plzexport build //src/core", ) -# Export a target generated by a native genrule target. +# Export a target generated by a native genrule target and trim unused build statements. please_export_e2e_test( name = "export_native_genrule", - export_target = "test:native_genrule", + export_target = "test_builtins:native_genrule", + repo = "repo", + strict_check_build_stmt = True, +) + +# Export a target generated by a custom build def. +please_export_e2e_test( + name = "export_custom_target", + export_target = "test_custom_defs:custom_target", repo = "repo", ) diff --git a/test/export/build_statement_checker.sh b/test/export/build_statement_checker.sh new file mode 100755 index 000000000..f8bcb9c91 --- /dev/null +++ b/test/export/build_statement_checker.sh @@ -0,0 +1,119 @@ +#!/usr/bin/env bash +set -euo pipefail + +strict_match=false +exported_file="" +original_file="" +target_name="" + +while [[ "$#" -gt 0 ]]; do + case $1 in + --strict) + strict_match=true + shift + ;; + --exported) + exported_file=$2 + shift 2 + ;; + --original) + original_file=$2 + shift 2 + ;; + --target) + target_name=$2 + shift 2 + ;; + -*) + echo "Unknown option: $1" >&2 + exit 1 + ;; + *) + echo "Unknown argument: $1" >&2 + exit 1 + ;; + esac +done + +if [[ -z "$exported_file" ]] || [[ -z "$original_file" ]] || [[ -z "$target_name" ]]; then + echo "Usage: $0 [--strict] --exported --original --target " >&2 + exit 1 +fi + +if [[ ! -f "$exported_file" ]]; then + echo "$exported_file doesnt exist" >&2 + exit 1 +fi + +if [[ ! -f "$original_file" ]]; then + echo "$original_file doesnt exist" >&2 + exit 1 +fi + +readonly START_DELIM="# BUILD_STMT_START" +readonly END_DELIM="# BUILD_STMT_END" + +# Using awk to extract statement blocks directly into an array. +blocks=() +while IFS= read -r -d '' block; do + blocks+=("$block") +done < <(awk -v id="$target_name" -v start_delim="$START_DELIM" -v end_delim="$END_DELIM" ' + BEGIN { in_block = 0; block = "" } + $0 == start_delim " " id { + in_block = 1; + block = ""; + next; + } + $0 == end_delim { + if (in_block) { + in_block = 0; + printf "%s\0", block; + } + next; + } + in_block { + block = block ? block "\n" $0 : $0 + } +' "$original_file") + +if [[ ${#blocks[@]} -eq 0 ]]; then + echo "Failed to pull original content for $target_name" >&2 + exit 1 +fi + +# Ensure that ALL required blocks are present in the generated file +for block_content in "${blocks[@]}"; do + if ! grep -Fq "$block_content" "$exported_file"; then + printf '%s\n%s\n%s\n%s\n%s\n%s\n' \ + "BUILD statements mismatch" \ + "${exported_file} doesnt contain" \ + "${block_content}" \ + "---- it contains ----" \ + "$(cat "$exported_file")" \ + "---- EOF ----" >&2 + exit 1 + fi +done + +# If --strict is enabled, ensure ONLY these blocks are present +# (ignoring all whitespace, newlines and comments). +if [[ "$strict_match" == true ]]; then + concatenated_blocks="" + for block_content in "${blocks[@]}"; do + concatenated_blocks="${concatenated_blocks}${block_content}" + done + + stripped_blocks=$(echo -n "$concatenated_blocks" | sed 's/#.*//' | tr -d ' \t\n\r') + stripped_exported_file=$(sed 's/#.*//' "$exported_file" | tr -d ' \t\n\r') + + if [[ "$stripped_blocks" != "$stripped_exported_file" ]]; then + printf '%s\n' "Strict match failed: exported file contains extra or out-of-order content." >&2 + printf '%s\n' "---- Expected (stripped) ----" >&2 + printf '%s\n' "$stripped_blocks" >&2 + printf '%s\n' "---- Got (stripped) ----" >&2 + printf '%s\n' "$stripped_exported_file" >&2 + exit 1 + fi +fi + +exit 0 diff --git a/test/export/repo/.plzconfig b/test/export/repo/.plzconfig new file mode 100644 index 000000000..e69de29bb diff --git a/test/export/repo/test/BUILD_FILE b/test/export/repo/test/BUILD_FILE deleted file mode 100644 index e198e11f9..000000000 --- a/test/export/repo/test/BUILD_FILE +++ /dev/null @@ -1,8 +0,0 @@ -# Start BUILDStmt native_genrule -genrule( - name = "native_genrule", - srcs = ["file.txt"], - outs = ["file.wordcount"], - cmd = "wc $SRCS > $OUT", -) -# End BUILDStmt diff --git a/test/export/repo/test_builtins/BUILD_FILE b/test/export/repo/test_builtins/BUILD_FILE new file mode 100644 index 000000000..c533d280d --- /dev/null +++ b/test/export/repo/test_builtins/BUILD_FILE @@ -0,0 +1,17 @@ +# BUILD_STMT_START native_genrule +genrule( + name = "native_genrule", + srcs = ["file.txt"], + outs = ["file.wordcount"], + cmd = "wc $SRCS > $OUT", +) +# BUILD_STMT_END + + +# Du mmy target to be trimmed +genrule( + name = "dummy", + srcs = ["file.txt"], + outs = ["dummy"], + cmd = "cat $SRCS > $OUT", +) diff --git a/test/export/repo/test/file.txt b/test/export/repo/test_builtins/file.txt similarity index 100% rename from test/export/repo/test/file.txt rename to test/export/repo/test_builtins/file.txt diff --git a/test/export/repo/test_custom_defs/BUILD_FILE b/test/export/repo/test_custom_defs/BUILD_FILE new file mode 100644 index 000000000..df8819489 --- /dev/null +++ b/test/export/repo/test_custom_defs/BUILD_FILE @@ -0,0 +1,11 @@ +# BUILD_STMT_START custom_target +subinclude("//test_custom_defs/build_defs") +# BUILD_STMT_END + +# BUILD_STMT_START custom_target +simple_custom_target( + name = "custom_target", + srcs = ["file.txt"], + outs = ["file.out"], +) +# BUILD_STMT_END diff --git a/test/export/repo/test_custom_defs/build_defs/BUILD_FILE b/test/export/repo/test_custom_defs/build_defs/BUILD_FILE new file mode 100644 index 000000000..cf7d4a898 --- /dev/null +++ b/test/export/repo/test_custom_defs/build_defs/BUILD_FILE @@ -0,0 +1,5 @@ +filegroup( + name = "build_defs", + srcs = ["custom.build_defs"], + visibility = ["//test_custom_defs/..."], +) diff --git a/test/export/repo/test_custom_defs/build_defs/custom.build_defs b/test/export/repo/test_custom_defs/build_defs/custom.build_defs new file mode 100644 index 000000000..8fe3a6021 --- /dev/null +++ b/test/export/repo/test_custom_defs/build_defs/custom.build_defs @@ -0,0 +1,10 @@ +def simple_custom_target( + name:str, + srcs:list=[], + outs:list=[]): + return genrule( + name = name, + srcs = srcs, + outs = outs, + cmd = "cat $SRCS > $OUT", + ) diff --git a/test/export/repo/test_custom_defs/file.txt b/test/export/repo/test_custom_defs/file.txt new file mode 100644 index 000000000..9768ee14c --- /dev/null +++ b/test/export/repo/test_custom_defs/file.txt @@ -0,0 +1 @@ +Test source file diff --git a/test/export/test_build_statement_content.sh b/test/export/test_build_statement_content.sh deleted file mode 100755 index 92d4a054b..000000000 --- a/test/export/test_build_statement_content.sh +++ /dev/null @@ -1,29 +0,0 @@ -#!/usr/bin/env bash - -gen_file=$1 -orig_file=$2 -stmt_identifier=$3 - -if ! test -f "$gen_file"; then - echo "$gen_file" doesnt exist - exit 1 -fi - -if ! test -f "$orig_file"; then - echo "$orig_file" doesnt exist - exit 1 -fi - -# Extract the block into a variable -ORIG_CONTENT=$(sed -n "/# Start BStmt ${stmt_identifier}/,/# End BStmt/{ /# /d; p; }" "$orig_file") - -if ! grep -Fq "$ORIG_CONTENT" "$gen_file"; then - printf '%s\n%s\n%s\n%s\n%s\n%s\n' \ - "BUILD statements mismatch" \ - "${gen_file} doesnt contain" \ - "${ORIG_CONTENT}" \ - "---- it contains ----" \ - "$(cat "$gen_file")" \ - "---- EOF ----" - exit 1 -fi