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..5d866c5d7 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,45 @@ 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 + Label BuildLabel + Statement BuildStatement +} + +type BuildStatement struct { + Filename string + 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 @@ -1971,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(other.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/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..f7424d05e 100644 --- a/src/export/export.go +++ b/src/export/export.go @@ -4,14 +4,16 @@ 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" "github.com/thought-machine/please/src/fs" - "github.com/thought-machine/please/src/gc" "github.com/thought-machine/please/src/parse" ) @@ -22,19 +24,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 +51,13 @@ func ToDir(state *core.BuildState, dir string, noTrim bool, targets []core.Build e.export(state.Graph.TargetOrDie(includeLabel)) } } + + log.Warningf("Exporting selected targets: %v", targets) 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 +65,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 +75,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) @@ -116,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) } } } @@ -129,8 +106,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,31 +146,113 @@ 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.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.Statement.Filename][frame.Statement] = true + } + if frame.Statement.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 { + 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 - 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)) } - 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.Warningf("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 a.Start - b.Start + }) + + e.writeBuildFile(filename, stmts) + } +} + +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) + } + 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 := 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 93e4ca0a3..7bc91f406 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() @@ -186,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 { @@ -310,6 +316,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 +522,44 @@ func (s *scope) LoadSingletons(state *core.BuildState) { } } +func (s *scope) PushCall(f *pyFunc) bool { + if s.cursor == nil { + return false // skip builtin method calls (e.g. format) + } + + stmt := core.BuildStatement{ + Filename: s.filename, + Start: int(s.cursor.Pos), + End: int(s.cursor.EndPos), + } + var label core.BuildLabel + if s.parsingFor != nil { + label = s.parsingFor.label + } + + log.Debug("PushCall ", f.name, s.filename,label, stmt, s.cursor) + + s.interpreter.callStack.Push(core.CallFrame{MethodName: f.name, Label: label, Statement: stmt}) + return true +} + +func (s *scope) PopCall() { + s.interpreter.callStack.Pop() +} + +func (s *scope) CallStackSnapshot(name string) 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.Warningf("CallStack Snapshot for %s: %s", name, 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 +571,8 @@ 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 { @@ -762,7 +810,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 +921,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 @@ -1044,6 +1093,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 a783b55b5..7adea0b7c 100644 --- a/src/parse/asp/objects.go +++ b/src/parse/asp/objects.go @@ -698,6 +698,7 @@ func (f *pyFunc) Call(s *scope, c *Call) pyObject { } return f.callNative(s, c) } + 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..8fa23c818 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(name) target.Subrepo = s.pkg.Subrepo target.IsBinary = isTruthy(binaryBuildRuleArgIdx) target.IsSubrepo = isTruthy(subrepoArgIdx) diff --git a/test/build_defs/test.build_defs b/test/build_defs/test.build_defs index 5830e3f62..c7b5df9bb 100644 --- a/test/build_defs/test.build_defs +++ b/test/build_defs/test.build_defs @@ -5,7 +5,9 @@ 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 = {}, @@ -31,13 +33,17 @@ 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] 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..b47719a48 100644 --- a/test/export/BUILD +++ b/test/export/BUILD @@ -1,6 +1,59 @@ subinclude("//test/build_defs") +sh_binary( + name = "build_statement_checker", + main = "build_statement_checker.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, + 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 {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" + }, + ) + +# 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 and trim unused build statements. +please_export_e2e_test( + name = "export_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_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_builtins/file.txt b/test/export/repo/test_builtins/file.txt new file mode 100644 index 000000000..9768ee14c --- /dev/null +++ b/test/export/repo/test_builtins/file.txt @@ -0,0 +1 @@ +Test source file 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