diff --git a/src/BUILD.plz b/src/BUILD.plz index c0eaec7e7b..4ef5ea6fd3 100644 --- a/src/BUILD.plz +++ b/src/BUILD.plz @@ -10,8 +10,8 @@ go_binary( deps = [ "///third_party/go/github.com_thought-machine_go-flags//:go-flags", "///third_party/go/go.uber.org_automaxprocs//maxprocs", - "//src/audit", "//src/assets", + "//src/audit", "//src/build", "//src/cache", "//src/clean", @@ -29,7 +29,6 @@ go_binary( "//src/help", "//src/metrics", "//src/output", - "//src/parse", "//src/plz", "//src/plzinit", "//src/process", diff --git a/src/core/package.go b/src/core/package.go index 377022a482..9a510750ed 100644 --- a/src/core/package.go +++ b/src/core/package.go @@ -34,23 +34,46 @@ type Package struct { targets map[string]*BuildTarget // Set of output files from rules. Outputs map[string]*BuildTarget + // Includes metadata from parsing the package BUILD file. + Metadata PackageMetadata // Protects access to above mutex sync.RWMutex } -// NewPackage constructs a new package with the given name. -func NewPackage(name string) *Package { - return NewPackageSubrepo(name, "") +// PackageOptions is a functional option type for configuring a new Package. +type PackageOptions func(*Package) + +// WithPackageSubrepo returns a PackageOptions that sets the subrepo name for a new Package. +func WithPackageSubrepo(name string) PackageOptions { + return func(p *Package) { + p.SubrepoName = name + } } -// NewPackageSubrepo constructs a new package with the given name and subrepo. -func NewPackageSubrepo(name, subrepo string) *Package { - return &Package{ - Name: name, - SubrepoName: subrepo, - targets: map[string]*BuildTarget{}, - Outputs: map[string]*BuildTarget{}, +// WithPackageMetadata returns a PackageOptions that enables tracking of +// metadata (like statement positions and subinclude mappings) for the Package. +// This is required for features like 'plz export'. +func WithPackageMetadata() PackageOptions { + return func(p *Package) { + p.Metadata = newPackageMetadata() + } +} + +// NewPackage constructs a new package with the given name, and enables additional features +// given the PackageOptions provided. +func NewPackage(name string, options ...PackageOptions) *Package { + pkg := &Package{ + Name: name, + targets: map[string]*BuildTarget{}, + Outputs: map[string]*BuildTarget{}, + // Defaults to noop to avoid storing metadata for most operations + Metadata: newNoopPackageMetadata(), + } + + for _, option := range options { + option(pkg) } + return pkg } // Target returns the target with the given name, or nil if this package doesn't have one. diff --git a/src/core/package_metadata.go b/src/core/package_metadata.go new file mode 100644 index 0000000000..03d7b314e6 --- /dev/null +++ b/src/core/package_metadata.go @@ -0,0 +1,183 @@ +package core + +import ( + "slices" + "sync" +) + +// BuildStatement represents the start and end byte positions of a parsed statement in a BUILD file. +type BuildStatement struct { + Start, End int +} + +// Len returns the byte length of the build statement. +func (bs *BuildStatement) Len() int64 { + return int64(bs.End - bs.Start) +} + +// StartPos returns the starting byte position of the build statement. +func (bs *BuildStatement) StartPos() int64 { + return int64(bs.Start) +} + +// BuildStatements is a slice of BuildStatement that implements sort.Interface. +type BuildStatements []BuildStatement + +func (s BuildStatements) Len() int { return len(s) } +func (s BuildStatements) Swap(i, j int) { s[i], s[j] = s[j], s[i] } +func (s BuildStatements) Less(i, j int) bool { return s[i].StartPos() < s[j].StartPos() } + +// BuildStatementProvider defines a closure that generates new build statements. +// It is used as an argument in PackageMetadata methods to defer evaluation, avoiding +// unnecessary computation when using the no-op implementation. +type BuildStatementProvider func() BuildStatement + +// SubincludesLabelProvider defines a closure that generates labels for a subinclude statement. +// It is used as an argument in PackageMetadata methods to defer evaluation, avoiding +// unnecessary computation when using the no-op implementation. +type SubincludesLabelProvider func() BuildLabels + +// PackageMetadata stores metadata about parsed BUILD files, mapping statements and subincludes +// to their respective targets. This supports additional logic for operations such as `plz export` +// but should be disabled for most operations by using the no-op implementation to avoid the overhead. +type PackageMetadata interface { + // RegisterStatementTarget records that the given build target was created as a result of the + // given statement being executed. This should only be called for statements in BUILD files. + RegisterStatementTarget(target *BuildTarget, stmtProvider BuildStatementProvider) + // RegisterRequiredSubinclude records that the given build target requires the given subinclude + // labels to be built. This is used to track which subinclude statements contributed to a target's + // definition. This should only be called for statements in BUILD files. + RegisterRequiredSubinclude(target *BuildTarget, labelProvider SubincludesLabelProvider) + // RegisterSubincludeStatement records that the given subinclude statement (provided by stmtProvider) + // includes the given build label. This should only be called for statements in BUILD files. + RegisterSubincludeStatement(label BuildLabel, stmtProvider BuildStatementProvider) + // FindStatement returns the build statement that was responsible for generating the given target. + // Returns nil if the target was not found in the recorded metadata. + FindStatement(target *BuildTarget) *BuildStatement + // FindTargets returns all build targets that were generated by the given build statement. + // Returns an empty slice if no targets were found for the given statement. + FindTargets(stmt *BuildStatement) []*BuildTarget + // FindRequiredSubincludes returns all subinclude labels that were required by the given target. + // The return value is empty if no subinclude information was found for the target. + FindRequiredSubincludes(target *BuildTarget) BuildLabels + // GetSubincludedLabels returns all build labels that were included by the given subinclude statement. + // Returns the labels or an empty slice if the statement wasn't found. + GetSubincludedLabels(stmt *BuildStatement) BuildLabels +} + +// packageMetadataImpl is the default implementation of the PackageMetadata interface. +// It uses in-memory maps to track the relationships between BUILD file statements, +// subincludes, and the build targets they define. +type packageMetadataImpl struct { + // StmtToTarget maps each build statement (identified by its byte range in a BUILD file) + // to the targets it produced. Since a single statement (like a custom target or loop) + // can produce multiple targets, this is a one-to-many mapping. + StmtToTarget map[BuildStatement][]*BuildTarget + // TargetToSubinclude tracks the subinclude labels that were required for a target's + // definition. This allows mapping a target back to the subincluded labels required for building + // the target. + TargetToSubinclude map[*BuildTarget]BuildLabels + // LabelsPerSubincludeStmt maps a subinclude statement (identified by its position + // in the BUILD file) to the labels it explicitly subincludes. + LabelsPerSubincludeStmt map[BuildStatement]BuildLabels + + // mutex protects concurrent access to the metadata maps during the parallel + // parsing of BUILD files. + mutex sync.RWMutex +} + +func newPackageMetadata() PackageMetadata { + return &packageMetadataImpl{ + StmtToTarget: map[BuildStatement][]*BuildTarget{}, + TargetToSubinclude: map[*BuildTarget]BuildLabels{}, + LabelsPerSubincludeStmt: map[BuildStatement]BuildLabels{}, + } +} + +func (m *packageMetadataImpl) RegisterStatementTarget(target *BuildTarget, stmtProvider BuildStatementProvider) { + stmt := stmtProvider() + + m.mutex.Lock() + defer m.mutex.Unlock() + m.StmtToTarget[stmt] = append(m.StmtToTarget[stmt], target) +} + +func (m *packageMetadataImpl) RegisterRequiredSubinclude(target *BuildTarget, labelProvider SubincludesLabelProvider) { + labels := labelProvider() + + m.mutex.Lock() + defer m.mutex.Unlock() + m.TargetToSubinclude[target] = append(m.TargetToSubinclude[target], labels...) +} + +func (m *packageMetadataImpl) RegisterSubincludeStatement(label BuildLabel, stmtProvider BuildStatementProvider) { + stmt := stmtProvider() + + m.mutex.Lock() + defer m.mutex.Unlock() + m.LabelsPerSubincludeStmt[stmt] = append(m.LabelsPerSubincludeStmt[stmt], label) +} + +func (m *packageMetadataImpl) FindStatement(target *BuildTarget) *BuildStatement { + m.mutex.RLock() + defer m.mutex.RUnlock() + + for stmt, targets := range m.StmtToTarget { + if slices.Contains(targets, target) { + return &stmt + } + } + return nil +} + +func (m *packageMetadataImpl) FindTargets(stmt *BuildStatement) []*BuildTarget { + m.mutex.RLock() + defer m.mutex.RUnlock() + + return m.StmtToTarget[*stmt] +} + +func (m *packageMetadataImpl) FindRequiredSubincludes(target *BuildTarget) BuildLabels { + m.mutex.RLock() + defer m.mutex.RUnlock() + + return m.TargetToSubinclude[target] +} + +func (m *packageMetadataImpl) GetSubincludedLabels(stmt *BuildStatement) BuildLabels { + m.mutex.RLock() + defer m.mutex.RUnlock() + + return m.LabelsPerSubincludeStmt[*stmt] +} + +// noopPackageMetadata implements the PackageMetadata interface with no-op methods. This is used to +// avoid the overhead of parsing metadata for operations that don't depend on it. +type noopPackageMetadata struct{} + +func newNoopPackageMetadata() PackageMetadata { + return &noopPackageMetadata{} +} + +func (n *noopPackageMetadata) RegisterStatementTarget(target *BuildTarget, stmtProvider BuildStatementProvider) { +} +func (n *noopPackageMetadata) RegisterRequiredSubinclude(target *BuildTarget, labelProvider SubincludesLabelProvider) { +} +func (n *noopPackageMetadata) RegisterSubincludeStatement(label BuildLabel, stmtProvider BuildStatementProvider) { +} +func (n *noopPackageMetadata) FindStatement(target *BuildTarget) *BuildStatement { + log.Fatalf("Metadata not tracked, using no-op implementation.") + return nil +} +func (n *noopPackageMetadata) FindTargets(stmt *BuildStatement) []*BuildTarget { + log.Fatalf("Metadata not tracked, using no-op implementation.") + return nil +} +func (n *noopPackageMetadata) FindRequiredSubincludes(target *BuildTarget) BuildLabels { + log.Fatalf("Metadata not tracked, using no-op implementation.") + return nil +} +func (n *noopPackageMetadata) GetSubincludedLabels(stmt *BuildStatement) BuildLabels { + log.Fatalf("Metadata not tracked, using no-op implementation.") + return nil +} diff --git a/src/core/state.go b/src/core/state.go index 18a03d70ab..013d5a59fe 100644 --- a/src/core/state.go +++ b/src/core/state.go @@ -240,6 +240,8 @@ type BuildState struct { // NeedDebugDeps is true if we're doing a `plz debug` and we need to build the debug tools and // data NeedDebugDeps bool + // ParseMetadata is true if we want to store build file metadata + ParseMetadata bool // initOnce is used to control loading the subrepo .plzconfig initOnce *sync.Once diff --git a/src/export/BUILD b/src/export/BUILD index 6b44d63d9b..bb625249a3 100644 --- a/src/export/BUILD +++ b/src/export/BUILD @@ -4,10 +4,23 @@ go_library( pgo_file = "//:pgo", visibility = ["PUBLIC"], deps = [ + "///third_party/go/github.com_please-build_buildtools//build", "//src/cli/logging", "//src/core", "//src/fs", - "//src/gc", "//src/parse", + "//src/parse/asp", + ], +) + +go_test( + name = "export_test", + srcs = ["export_test.go"], + data = ["test_data"], + deps = [ + ":export", + "///third_party/go/github.com_stretchr_testify//assert", + "//src/core", + "//src/parse/asp", ], ) diff --git a/src/export/export.go b/src/export/export.go index 2863dfef0c..78b5197962 100644 --- a/src/export/export.go +++ b/src/export/export.go @@ -4,212 +4,488 @@ package export import ( - iofs "io/fs" + "bytes" + "fmt" "os" "path/filepath" + "slices" + "sort" + + "github.com/please-build/buildtools/build" "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" + "github.com/thought-machine/please/src/parse/asp" ) var log = logging.Log -type export struct { - state *core.BuildState - targetDir string - noTrim bool - - exportedTargets map[core.BuildLabel]bool - exportedPackages map[string]bool +// Exporter defines the interface for exporting parts of a Please repository to a new directory. +// It handles the copying of configuration files, preloaded build definitions, and selected +// targets along with their necessary source files and dependencies. +type Exporter interface { + // ExportPlzConfig exports the repository's configuration files (e.g., .plzconfig and its + // platform-specific variants) to the target export directory. + ExportPlzConfig() + // ExportPreloaded exports all globally preloaded build definitions and subincluded targets. + // These are usually defined in the repository's configuration file. + ExportPreloaded() + // ExportTargets exports the set of targets identified by the given build labels. + // Each target recursively exports all their source files and required build statements, but also + // targets in their transitive dependencies. + ExportTargets(core.BuildLabels) + // ExportTarget exports an individual build target. + // Each target recursively exports all their source files and required build statements, but also + // targets in their transitive dependencies. + ExportTarget(target *core.BuildTarget) + // WritePackageFiles writes the processed BUILD files for all exported targets to the + // export directory. These BUILD files may be modified (e.g., trimmed) depending on + // the exporter's implementation. + WritePackageFiles() } -// 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{}, - } +// Repo export a new please repo including the targets and dependencies requested. Depending on the +// noTrim flag, the export will attempt to trim the resulting repository, exporting only the required +// targets and build statements in their packages. If noTrim is set, all targets of a package will be +// exported and not build statement trimming will be attempted, the BUILD file is copied in its entirety. +func Repo(state *core.BuildState, dir string, noTrim bool, targets []core.BuildLabel) { + e := newExporter(state, dir, noTrim) + // ensure output dir if err := os.MkdirAll(dir, fs.DirPermissions); err != nil { log.Fatalf("failed to create export directory %s: %v", dir, err) } - e.exportPlzConf() - for _, target := range state.Config.Parse.PreloadSubincludes { - for _, includeLabel := range append(state.Graph.TransitiveSubincludes(target), target) { - e.export(state.Graph.TargetOrDie(includeLabel)) + e.ExportPlzConfig() + e.ExportPreloaded() + e.ExportTargets(targets) + e.WritePackageFiles() +} + +// Outputs exports the build artifacts (output files) produced by building the specified +// targets to the given output directory. +func Outputs(state *core.BuildState, dir string, targets []core.BuildLabel) { + for _, label := range targets { + target := state.Graph.TargetOrDie(label) + for _, out := range target.Outputs() { + fullPath := filepath.Join(dir, out) + outDir := filepath.Dir(fullPath) + if err := os.MkdirAll(outDir, core.DirPermissions); err != nil { + log.Fatalf("Failed to create export dir %s: %s", outDir, err) + } + if err := fs.RecursiveCopy(filepath.Join(target.OutDir(), out), fullPath, target.OutMode()|0200); err != nil { + log.Fatalf("Failed to copy export file: %s", err) + } } } - 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 - } +} - // Write any preloaded build defs as well; preloaded subincludes should be fine though. - for _, preload := range state.Config.Parse.PreloadBuildDefs { - if err := fs.RecursiveCopy(preload, filepath.Join(dir, preload), 0); err != nil { - log.Fatalf("Failed to copy preloaded build def %s: %s", preload, err) - } +// newExporter creates a new exporter of a specific type based on the arguments. +func newExporter(state *core.BuildState, dir string, noTrim bool) Exporter { + base := baseExporter{ + state: state, + targetDir: dir, + exportedTargets: map[core.BuildLabel]bool{}, } if noTrim { - return // We have already exported the whole directory + exporter := &noTrimExporter{ + baseExporter: base, + exportedPackages: map[string]bool{}, + } + exporter.impl = exporter + return exporter } - 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) - } + exporter := &defaultExporter{ + baseExporter: base, + visitedPackages: map[core.BuildLabel]bool{}, + requiredSubincludes: map[core.BuildLabel]core.BuildLabels{}, + preloadedSubincludes: map[core.BuildLabel]bool{}, } + exporter.impl = exporter + return exporter } -func (e *export) exportPlzConf() { +// baseExporter provides common fields and methods of other exporters. +type baseExporter struct { + state *core.BuildState + targetDir string + + // exportedTargets maintains a record of the targets that have been exported so far. + exportedTargets map[core.BuildLabel]bool + // impl is a reference to the concrete exporter implementation. It's included for calling the + // specific exporter implementation from the common methods. + impl Exporter +} + +func (be *baseExporter) ExportPlzConfig() { profiles, err := filepath.Glob(".plzconfig*") if err != nil { log.Fatalf("failed to glob .plzconfig files: %v", err) } 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) + targetPath := filepath.Join(be.targetDir, file) + if err := os.RemoveAll(targetPath); err != nil { + log.Fatalf("failed to remove .plzconfig file %s: %v", file, err) } - if err := fs.CopyFile(file, path, 0); err != nil { + if err := fs.CopyFile(file, targetPath, 0); err != nil { log.Fatalf("failed to copy .plzconfig file %s: %v", file, err) } } } -// exportSources exports any source files (srcs and data) for the rule -func (e *export) exportSources(target *core.BuildTarget) { +func (be *baseExporter) ExportTargets(labels core.BuildLabels) { + for _, l := range labels { + target := be.state.Graph.Target(l) + if target == nil { + log.Errorf("Unable to lookup target %s", l) + continue + } + be.impl.ExportTarget(target) + } +} + +// exportDependencies exports exportDependencies of a target. +func (be *baseExporter) exportDependencies(target *core.BuildTarget) { + deps := target.DeclaredDependencies() + log.Debugf("Exporting dependencies of (%v): %v", target.Label, deps) + be.ExportTargets(deps) +} + +// exportSources exports all files required by the target. +func (be *baseExporter) exportSources(target *core.BuildTarget) { for _, src := range append(target.AllSources(), target.AllData()...) { - if _, ok := src.Label(); !ok { // We'll handle these dependencies later - for _, p := range src.FullPaths(e.state.Graph) { - if !filepath.IsAbs(p) { // Don't copy system file deps. - if err := fs.RecursiveCopy(p, filepath.Join(e.targetDir, p), 0); err != nil { - log.Fatalf("Error copying file: %s\n", err) - } - } + if _, ok := src.Label(); ok { + continue // These will be handled as dependencies later + } + for _, p := range src.Paths(be.state.Graph) { + if filepath.IsAbs(p) { // Don't copy system file deps. + log.Infof("System dependency detected, skipping...: %s", p) + continue + } + if err := fs.RecursiveCopy(p, filepath.Join(be.targetDir, p), 0); err != nil { + log.Warningf("Error copying file, skipping...: %s", err) } + log.Debugf("Writing exported source file: %s", p) } } } -var ignoreDirectories = map[string]bool{ - "plz-out": true, - ".git": true, - ".svn": true, - ".hg": true, +// checkAndSetVisited is a helper to ensure we only visit the same target once. +// It returns true if this is the first time the target is being exported. +func (be *baseExporter) checkAndSetVisited(target *core.BuildTarget) bool { + visited := be.exportedTargets[target.Label] + be.exportedTargets[target.Label] = true + return !visited } -// exportPackage exports the package BUILD file containing the given target and all sources -func (e *export) exportPackage(target *core.BuildTarget) { - pkgName := target.Label.PackageName - if pkgName == parse.InternalPackageName { +// defaultExporter implements an exporter that trims packages to reach a minimal exported repo. +type defaultExporter struct { + baseExporter + // visitedPackages maintains a record of the packages visited during the export process. + visitedPackages map[core.BuildLabel]bool + // requiredSubincludes maps packages to the subinclude labels they require. + requiredSubincludes map[core.BuildLabel]core.BuildLabels + // preloadedSubincludes tracks subincludes that are preloaded and don't need explicit export. + preloadedSubincludes map[core.BuildLabel]bool +} + +func (e *defaultExporter) ExportPreloaded() { + // Write any preloaded build defs + for _, preload := range e.state.Config.Parse.PreloadBuildDefs { + if err := fs.RecursiveCopy(preload, filepath.Join(e.targetDir, preload), 0); err != nil { + log.Fatalf("Failed to copy preloaded build def %s: %s", preload, err) + } + } + + for _, target := range e.state.Config.Parse.PreloadSubincludes { + targets := append(e.state.Graph.TransitiveSubincludes(target), target) + for _, t := range targets { + e.preloadedSubincludes[t] = true + } + e.ExportTargets(targets) + } +} + +func (e *defaultExporter) ExportTarget(target *core.BuildTarget) { + if !e.checkAndSetVisited(target) { + return + } + + log.Debugf("Exporting target: %v", target.Label) + + // Skip export for internal packages + if target.Label.PackageName == parse.InternalPackageName { return } - if e.exportedPackages[pkgName] { + // 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 && target.Subrepo.Target != nil { + e.ExportTarget(target.Subrepo.Target) + e.exportDependencies(target) return } - e.exportedPackages[pkgName] = true - pkgDir := filepath.Clean(pkgName) + e.exportSources(target) + e.exportDependencies(target) + + pkg := e.state.Graph.PackageOrDie(target.Label) + e.exportSubincludes(pkg, target) + e.exportRelatedTargets(pkg, target) + e.visitedPackages[pkg.Label()] = true +} + +func (e *defaultExporter) WritePackageFiles() { + for pkgLabel := range e.visitedPackages { + pkg := e.state.Graph.PackageOrDie(pkgLabel) + filteredBytes, err := e.filterPackageFile(pkg) + if err != nil { + log.Errorf("Failed to filter the build statements of package %s: %v", pkg.Label(), err) + continue + } - err := filepath.WalkDir(pkgDir, func(path string, d iofs.DirEntry, err error) error { + parsedBuild, err := build.ParseBuild(pkg.Filename, filteredBytes) if err != nil { - return err + log.Fatalf("Failed to parse bytes for formatting: %v", err) } - if d.IsDir() { - if path != pkgDir && fs.IsPackage(e.state.Config.Parse.BuildFileName, path) { - return filepath.SkipDir // We want to stop when we find another package in our dir tree + formattedBytes := build.Format(parsedBuild) + + e.WriteExportedPackageFile(pkg, formattedBytes) + } +} + +// exportSubincludes exports the subincluded targets required to generate the target and selects them to +// later be written to the package as statements. +func (e *defaultExporter) exportSubincludes(pkg *core.Package, target *core.BuildTarget) { + for _, subinclude := range pkg.Metadata.FindRequiredSubincludes(target) { + // skip for preloaded subincludes, these are handled separately at the start to ensure they are + // they are exported even if not directly used by an exported target. + if e.preloadedSubincludes[subinclude] { + continue + } + + required := e.requiredSubincludes[pkg.Label()] + if !slices.Contains(required, subinclude) { + required = append(required, subinclude) + } + e.requiredSubincludes[pkg.Label()] = required + + target := e.state.Graph.Target(subinclude) + if target == nil { + log.Errorf("Unable to lookup target %s", subinclude) + continue + } + e.ExportTarget(target) + } +} + +// exportRelatedTargets exports build targets that are related to the build statement that generated. +func (e *defaultExporter) exportRelatedTargets(pkg *core.Package, target *core.BuildTarget) { + stmt := pkg.Metadata.FindStatement(target) + if stmt == nil { + log.Errorf("Failed to find statement for target %s in %s", target, pkg.Name) + return + } + + relatedTargets := pkg.Metadata.FindTargets(stmt) + log.Debugf("Exporting targets related to %s: %v", target, relatedTargets) + for _, target := range relatedTargets { + e.ExportTarget(target) + } +} + +// WriteExportedPackageFile creates a new package (BUILD) file in the exported dir and writes to it. +func (e *defaultExporter) WriteExportedPackageFile(pkg *core.Package, content []byte) { + filename := pkg.Filename + exportedFilename := filepath.Join(e.targetDir, filename) + f, err := fs.OpenDirFile(exportedFilename, os.O_CREATE|os.O_WRONLY, 0664) + if err != nil { + log.Fatalf("Failed to create and open exported BUILD file for %s: %v", exportedFilename, err) + } + defer f.Close() + + if _, err := f.Write(content); err != nil { + log.Errorf("Failed to write to exported BUILD file %s: %v", f.Name(), err) + } +} + +// filterPackageFile filters the statements to be written to the exported BUILD file. +func (e *defaultExporter) filterPackageFile(pkg *core.Package) ([]byte, error) { + p := asp.NewParserOnly() + parsedStmts, err := p.ParseFileOnly(pkg.Filename) + if err != nil { + return nil, fmt.Errorf("Parsing original BUILD file: %v", err) + } + + original, err := os.ReadFile(pkg.Filename) + if err != nil { + return nil, fmt.Errorf("Opening original BUILD file: %v", err) + } + + cursor := 0 + var buffer bytes.Buffer + for _, stmt := range parsedStmts { + bStmt := asp.NewBuildStatement(stmt) + + log.Debugf("Evaluating statement %s", original[bStmt.Start:bStmt.End]) + // Write content that's between stmts (e.g. comments). We skip these while parsing so it won't + // be included in "parsedStmts" but we want the resulting BUILD file to include these. + if cursor < bStmt.Start { + if _, err := buffer.Write(original[cursor:bStmt.Start]); err != nil { + return nil, err } - if ignoreDirectories[d.Name()] { - return filepath.SkipDir + } + + if stmtLabels := pkg.Metadata.GetSubincludedLabels(&bStmt); len(stmtLabels) > 0 { + // Write filtered subincludes + subStmt := e.minimalSubincludeStatement(pkg, stmtLabels) + buffer.Write([]byte(subStmt)) + log.Debugf("Decision: %s", subStmt) + } else if e.shouldSkipStatement(pkg, &bStmt) { + // Don't write statements that generate targets we are not interested about + log.Debugf("Decision: ") + } else { + // Write every other statement + if _, err := buffer.Write(original[bStmt.Start:bStmt.End]); err != nil { + return nil, err } - return nil + log.Debugf("Decision: ") } - if !d.Type().IsRegular() { - return nil // Ignore symlinks, which are almost certainly generated sources. + + // Move the cursor to the end of the processed statement. The cursor will enable writing of lines + // that are not considered statements by the parser (e.g. comments, new lines). + cursor = bStmt.End + } + + // Write the rest of the original file (non build targets) + if _, err := buffer.Write(original[cursor:]); err != nil { + return nil, err + } + + return buffer.Bytes(), nil +} + +// shouldSkipStatement evaluates if the current build statement should be skipped during export. +// We skip statements that generated build targets, but none of those targets are required by the export. +func (e *defaultExporter) shouldSkipStatement(pkg *core.Package, stmt *core.BuildStatement) bool { + targets := pkg.Metadata.FindTargets(stmt) + if len(targets) == 0 { + // If the statement didn't generate any targets (e.g. variable assignments, package() calls), + // we keep it to ensure the BUILD file remains valid. + return false + } + + required := slices.ContainsFunc(targets, func(target *core.BuildTarget) bool { + return e.exportedTargets[target.Label] + }) + // Skip if it generated targets, but none of them are required. + return !required +} + +// minimalSubincludeStatement generates a subinclude statement containing only the required labels. +func (e *defaultExporter) minimalSubincludeStatement(pkg *core.Package, available core.BuildLabels) string { + var filteredLabels core.BuildLabels + for _, required := range e.requiredSubincludes[pkg.Label()] { + if slices.Contains(available, required) { + filteredLabels = append(filteredLabels, required) } - dest := filepath.Join(e.targetDir, path) - if err := fs.EnsureDir(dest); err != nil { - return err + } + + if len(filteredLabels) == 0 { + return "" + } + + sort.Sort(filteredLabels) + + call := &build.CallExpr{ + X: &build.Ident{Name: "subinclude"}, + } + for _, label := range filteredLabels { + call.List = append(call.List, &build.StringExpr{Value: label.ShortString(pkg.Label())}) + } + + return build.FormatString(call) +} + +// noTrimExporter implements an exporter that avoids trimming any packages by exporting all targets +// and statements in a package. +type noTrimExporter struct { + baseExporter + // exportedPackages tracks which packages have already had their BUILD files exported. + exportedPackages map[string]bool +} + +func (nte *noTrimExporter) ExportPreloaded() { + // Write any preloaded build defs + for _, preload := range nte.state.Config.Parse.PreloadBuildDefs { + if err := fs.RecursiveCopy(preload, filepath.Join(nte.targetDir, preload), 0); err != nil { + log.Errorf("Failed to copy preloaded build def %s: %s", preload, err) } - return fs.CopyFile(path, dest, 0) - }) - if err != nil { - log.Fatalf("failed to export package %s for %s: %v", pkgName, target.Label, err) + } + + for _, target := range nte.state.Config.Parse.PreloadSubincludes { + targets := append(nte.state.Graph.TransitiveSubincludes(target), target) + nte.ExportTargets(targets) } } -// export implements the logic of ToDir, but prevents repeating targets. -func (e *export) export(target *core.BuildTarget) { - if e.exportedTargets[target.Label] { +func (nte *noTrimExporter) ExportTarget(target *core.BuildTarget) { + pkg := nte.state.Graph.PackageOrDie(target.Label) + if !nte.checkAndSetVisited(target) { return } + // 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) - } else { - e.exportSources(target) + nte.ExportTarget(target.Subrepo.Target) + nte.exportDependencies(target) + return + } + + nte.exportPackage(pkg) + nte.exportSubincludes(pkg) + nte.exportAllTargets(pkg) + nte.exportSources(target) + nte.exportDependencies(target) +} + +func (nte *noTrimExporter) WritePackageFiles() { +} + +// exportPackage exports the package BUILD file. +func (nte *noTrimExporter) exportPackage(pkg *core.Package) { + // Skip subrepos and internal packages. These will be generated by build statements in the exported + // repo or included in please internally. + if pkg.Subrepo != nil || pkg.Name == parse.InternalPackageName { + return } - e.exportedTargets[target.Label] = true - for _, dep := range target.Dependencies() { - e.export(dep) + if nte.exportedPackages[pkg.Name] { + return } - for _, subinclude := range e.state.Graph.PackageOrDie(target.Label).AllSubincludes(e.state.Graph) { - e.export(e.state.Graph.TargetOrDie(subinclude)) + nte.exportedPackages[pkg.Name] = true + + exportedFilename := filepath.Join(nte.targetDir, pkg.Filename) + if err := fs.CopyFile(pkg.Filename, exportedFilename, 0); err != nil { + log.Errorf("failed to export package %s: %v", pkg.Name, err) } - if parent := target.Parent(e.state.Graph); parent != nil && parent != target { - e.export(parent) +} + +// exportSubincludes exports the subincluded targets. +func (nte *noTrimExporter) exportSubincludes(pkg *core.Package) { + subincludes := pkg.AllSubincludes(nte.state.Graph) + for _, subinclude := range subincludes { + nte.ExportTarget(nte.state.Graph.TargetOrDie(subinclude)) } } -// Outputs exports the outputs of a target. -func Outputs(state *core.BuildState, dir string, targets []core.BuildLabel) { - for _, label := range targets { - target := state.Graph.TargetOrDie(label) - for _, out := range target.Outputs() { - fullPath := filepath.Join(dir, out) - outDir := filepath.Dir(fullPath) - if err := os.MkdirAll(outDir, core.DirPermissions); err != nil { - log.Fatalf("Failed to create export dir %s: %s", outDir, err) - } - if err := fs.RecursiveCopy(filepath.Join(target.OutDir(), out), fullPath, target.OutMode()|0200); err != nil { - log.Fatalf("Failed to copy export file: %s", err) - } - } +// exportAllTargets will export all the targets in the provided package. +func (nte *noTrimExporter) exportAllTargets(pkg *core.Package) { + for _, target := range pkg.AllTargets() { + nte.ExportTarget(target) } } diff --git a/src/export/export_test.go b/src/export/export_test.go new file mode 100644 index 0000000000..35af3db9e5 --- /dev/null +++ b/src/export/export_test.go @@ -0,0 +1,130 @@ +package export + +import ( + "os" + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/thought-machine/please/src/core" + "github.com/thought-machine/please/src/parse/asp" +) + +func TestMinimalSubincludeStatement(t *testing.T) { + testCases := []struct { + name string + availableLabels []core.BuildLabel + requiredLabels []core.BuildLabel + out string + }{ + { + name: "Successful no pruning subinclude", + availableLabels: core.ParseBuildLabels([]string{"//build_defs:test"}), + requiredLabels: core.ParseBuildLabels([]string{"//build_defs:test"}), + out: `subinclude("//build_defs:test")`, + }, + { + name: "No subincludes", + availableLabels: nil, + requiredLabels: nil, + out: "", + }, + { + name: "Single subinclude (not required)", + availableLabels: core.ParseBuildLabels([]string{"//build_defs:other"}), + requiredLabels: nil, + out: "", + }, + { + name: "Multiple subincludes (sorted and filtered)", + availableLabels: core.ParseBuildLabels([]string{"//build_defs:test", "//build_defs:abc", "//build_defs:other"}), + requiredLabels: core.ParseBuildLabels([]string{"//build_defs:test", "//build_defs:abc"}), + out: "subinclude(\n" + + " \"//build_defs:abc\",\n" + + " \"//build_defs:test\",\n" + + ")", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + e := newExporter(nil, "", false).(*defaultExporter) + + pkg := &core.Package{Name: "test"} + e.requiredSubincludes[pkg.Label()] = tc.requiredLabels + + assert.Equal(t, tc.out, e.minimalSubincludeStatement(pkg, tc.availableLabels)) + }) + } +} + +func TestFilterPackageFile(t *testing.T) { + testCases := []struct { + name string + required []string + expected string + }{ + { + name: "Keep only A", + required: []string{"a"}, + expected: "src/export/test_data/filter_expected_a.build", + }, + { + name: "Keep only B", + required: []string{"b"}, + expected: "src/export/test_data/filter_expected_b.build", + }, + { + name: "Keep both", + required: []string{"a", "b"}, + expected: "src/export/test_data/filter.build", + }, + { + name: "Keep none", + required: []string{}, + expected: "src/export/test_data/filter_expected_none.build", + }, + } + + contentPath := "src/export/test_data/filter.build" + + p := asp.NewParserOnly() + statements, err := p.ParseFileOnly(contentPath) + assert.NoError(t, err) + + pkg := core.NewPackage("test", core.WithPackageMetadata()) + pkg.Filename = contentPath + + // stmtIndices maps target names to their statement index in filter.build + stmtIndices := map[string]int{ + "a": 2, + "b": 3, + } + + targetLabels := map[string]core.BuildLabel{} + for name, index := range stmtIndices { + label := core.NewBuildLabel("test", name) + targetLabels[name] = label + target := &core.BuildTarget{Label: label} + pkg.Metadata.RegisterStatementTarget(target, func() core.BuildStatement { + return asp.NewBuildStatement(statements[index]) + }) + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + e := newExporter(nil, "", false).(*defaultExporter) + for _, name := range tc.required { + e.exportedTargets[targetLabels[name]] = true + } + e.visitedPackages[pkg.Label()] = true + + got, err := e.filterPackageFile(pkg) + assert.NoError(t, err) + + expected, err := os.ReadFile(tc.expected) + assert.NoError(t, err) + assert.Equal(t, string(expected), string(got)) + }) + } +} diff --git a/src/export/test_data/filter.build b/src/export/test_data/filter.build new file mode 100644 index 0000000000..35f90c2265 --- /dev/null +++ b/src/export/test_data/filter.build @@ -0,0 +1,17 @@ +# This is a header comment +package(default_visibility = ["PUBLIC"]) + +VERSION = "1.2.3" + +target( + name = "a", + # This is a comment inside a target + srcs = ["a.py"], + version = VERSION, +) + +# This is a middle comment +target( + name = "b", + srcs = ["b.py"], +) diff --git a/src/export/test_data/filter_expected_a.build b/src/export/test_data/filter_expected_a.build new file mode 100644 index 0000000000..af682d5dfb --- /dev/null +++ b/src/export/test_data/filter_expected_a.build @@ -0,0 +1,14 @@ +# This is a header comment +package(default_visibility = ["PUBLIC"]) + +VERSION = "1.2.3" + +target( + name = "a", + # This is a comment inside a target + srcs = ["a.py"], + version = VERSION, +) + +# This is a middle comment + diff --git a/src/export/test_data/filter_expected_b.build b/src/export/test_data/filter_expected_b.build new file mode 100644 index 0000000000..7e6f18ca21 --- /dev/null +++ b/src/export/test_data/filter_expected_b.build @@ -0,0 +1,12 @@ +# This is a header comment +package(default_visibility = ["PUBLIC"]) + +VERSION = "1.2.3" + + + +# This is a middle comment +target( + name = "b", + srcs = ["b.py"], +) diff --git a/src/export/test_data/filter_expected_none.build b/src/export/test_data/filter_expected_none.build new file mode 100644 index 0000000000..9ee152a241 --- /dev/null +++ b/src/export/test_data/filter_expected_none.build @@ -0,0 +1,9 @@ +# This is a header comment +package(default_visibility = ["PUBLIC"]) + +VERSION = "1.2.3" + + + +# This is a middle comment + diff --git a/src/parse/asp/builtins.go b/src/parse/asp/builtins.go index 8be2cbc8e4..63f28b8a13 100644 --- a/src/parse/asp/builtins.go +++ b/src/parse/asp/builtins.go @@ -210,6 +210,9 @@ func buildRule(s *scope, args []pyObject) pyObject { s.Assert(s.pkg.Target(target.Label.Name) == nil, "Duplicate build target in %s: %s", s.pkg.Name, target.Label.Name) populateTarget(s, target, args) s.state.AddTarget(s.pkg, target) + s.pkg.Metadata.RegisterStatementTarget(target, s.CurrentBuildStatement()) + s.pkg.Metadata.RegisterRequiredSubinclude(target, s.ActiveSubincludes()) + if s.Callback { target.AddedPostBuild = true } @@ -416,6 +419,7 @@ func subincludeTarget(s *scope, l core.BuildLabel) *core.BuildTarget { t = s.WaitForSubincludedTarget(l, pkgLabel) if s.pkg != nil { s.pkg.RegisterSubinclude(l) + s.pkg.Metadata.RegisterSubincludeStatement(l, s.CurrentBuildStatement()) } else if s.subincludeLabel != nil { // If this is nil, that indicates a preloadedSubinclude s.state.Graph.RegisterTransitiveSubinclude(*s.subincludeLabel, l) } diff --git a/src/parse/asp/interpreter.go b/src/parse/asp/interpreter.go index 93e4ca0a32..1a613deabd 100644 --- a/src/parse/asp/interpreter.go +++ b/src/parse/asp/interpreter.go @@ -4,11 +4,13 @@ import ( "context" "fmt" "iter" + "maps" "path/filepath" "reflect" "regexp" "runtime/debug" "runtime/pprof" + "slices" "strings" "sync" @@ -303,13 +305,20 @@ type scope struct { pkg *core.Package subincludeLabel *core.BuildLabel // If set, label of the subinclude we're currently interpreting parsingFor *parseTarget - parent *scope - locals pyDict - config *pyConfig - globber *fs.Globber + // parent points to the lexical parent of this scope. It is used for variable resolution + // and is nil for the root scope. + parent *scope + // caller points to the scope that initiated the call which created this scope. + // It is used to trace the call stack and is nil if not in a call stack. + caller *scope + locals pyDict + config *pyConfig + globber *fs.Globber // True if this scope is for a pre- or post-build callback. Callback bool mode core.ParseMode + // cursor points to the statement currently being interpreted + cursor *Statement } // parseAnnotatedLabelInPackage similarly to parseLabelInPackage, parses the label contextualising it to the provided @@ -397,7 +406,7 @@ func (s *scope) subincludePackage() *core.Package { return pkg } // We're probably doing a local subinclude so the package isn't ready yet - return core.NewPackageSubrepo(s.subincludeLabel.PackageName, s.subincludeLabel.Subrepo) + return core.NewPackage(s.subincludeLabel.PackageName, core.WithPackageSubrepo(s.subincludeLabel.Subrepo)) } return nil } @@ -426,6 +435,7 @@ func (s *scope) newScope(pkg *core.Package, mode core.ParseMode, filename string config: s.config, Callback: s.Callback, mode: mode, + cursor: s.cursor, } if pkg != nil && pkg.Subrepo != nil && pkg.Subrepo.State != nil { s2.state = pkg.Subrepo.State @@ -525,6 +535,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 { @@ -1077,6 +1088,46 @@ func (s *scope) Constant(expr *Expression) pyObject { return nil } +// CurrentBuildStatement creates a provider for getting a BuildStatement from the statement +// that is being currently interpreted. A closure is used to avoid unnecessary computation when the +// metadata is not being tracked. +func (s *scope) CurrentBuildStatement() core.BuildStatementProvider { + return func() core.BuildStatement { + // We walk back on the callstack until we find the highest-level function call in the package file. + // This statement should be the root method call, from a possibly long callstack, at the original + // package level that generated the current build target. + stmtScope := s + for curr := s; curr != nil; curr = curr.caller { + if curr.pkg != nil && curr.filename == s.pkg.Filename { + stmtScope = curr + } + } + s.NAssert(stmtScope.cursor == nil, "Cursor is not pointing to a statement") + return NewBuildStatement(stmtScope.cursor) + } +} + +// ActiveSubincludes creates a provider that reports the active/required subincluded targets for a +// certain scope. This gives the explicitly subincluded targets that generate the methods we used +// in the current callstack, actively executing to define this target. +func (s *scope) ActiveSubincludes() core.SubincludesLabelProvider { + return func() core.BuildLabels { + // We walk back on the callstack. For each scope of a method call we walk back at the local/lexical + // scopes in that method's context to find the original/root scope. If that scope includes a "subincludeLabel" + // it means this scope was generated by a subinclude statement and we'll register that label as required. + seen := map[core.BuildLabel]bool{} + for callScope := s; callScope != nil; callScope = callScope.caller { + for lexicalScope := callScope; lexicalScope != nil; lexicalScope = lexicalScope.parent { + if lexicalScope.subincludeLabel != nil { + label := *lexicalScope.subincludeLabel + seen[label] = true + } + } + } + return slices.Collect(maps.Keys(seen)) + } +} + // pkgFilename returns the filename of the current package, or the empty string if there is none. func (s *scope) pkgFilename() string { if s.pkg != nil { @@ -1084,3 +1135,11 @@ func (s *scope) pkgFilename() string { } return "" } + +// NewBuildStatement creates a new core.BuildStatement from an asp.statement. +func NewBuildStatement(stmt *Statement) core.BuildStatement { + return core.BuildStatement{ + Start: int(stmt.Pos), + End: int(stmt.EndPos), + } +} diff --git a/src/parse/asp/interpreter_test.go b/src/parse/asp/interpreter_test.go index 511f00b053..a58aee9154 100644 --- a/src/parse/asp/interpreter_test.go +++ b/src/parse/asp/interpreter_test.go @@ -770,3 +770,125 @@ func TestStrRjust(t *testing.T) { _, err = parseFile("src/parse/asp/test_data/interpreter/str/rjust_multiple_fillchars.build") assert.Error(t, err, "fillchar must be exactly one character long") } + +func TestCurrentBuildStatement(t *testing.T) { + pkg := core.NewPackage("test/package") + pkg.Filename = "test/package/BUILD" + + // Root statement in the BUILD file (e.g. a macro call) + rootStmt := &Statement{Pos: 10, EndPos: 20} + rootScope := &scope{ + pkg: pkg, + filename: pkg.Filename, + cursor: rootStmt, + } + + // A nested call inside the same BUILD file (e.g. function def) + NestedStmt := &Statement{Pos: 30, EndPos: 40} + NestedScope := &scope{ + pkg: pkg, + filename: pkg.Filename, + cursor: NestedStmt, + caller: rootScope, + } + + // A call from a different file (e.g. a function inside a subincluded .build_defs file) + defsRootStmt := &Statement{Pos: 50, EndPos: 60} + defsRootScope := &scope{ + pkg: pkg, + filename: "other/file.build_defs", + cursor: defsRootStmt, + caller: NestedScope, + } + + // Another call deep in the other file + defsNestedStmt := &Statement{Pos: 70, EndPos: 80} + defsNestedScope := &scope{ + pkg: pkg, + filename: "other/file.build_defs", + cursor: defsNestedStmt, + caller: defsRootScope, + } + + t.Run("FindsRootStatementFromBUILD", func(t *testing.T) { + // Calling it from buildNestedScope should walk back to buildRootScope + stmt := NestedScope.CurrentBuildStatement()() + assert.Equal(t, NewBuildStatement(rootStmt), stmt) + }) + + t.Run("FindsRootStatementFromOtherFile", func(t *testing.T) { + // Calling it from defsNestedScope should still find the root statement in the BUILD file + stmt := defsNestedScope.CurrentBuildStatement()() + assert.Equal(t, NewBuildStatement(rootStmt), stmt) + }) + + t.Run("HandlesNoPackageFileInStack", func(t *testing.T) { + // A scope that has no pkg/filename context + standaloneScope := &scope{cursor: rootStmt} + stmt := standaloneScope.CurrentBuildStatement()() + assert.Equal(t, NewBuildStatement(rootStmt), stmt) + }) +} + +func TestActiveSubincludes(t *testing.T) { + labelA := core.ParseBuildLabel("//pkg:labelA", "") + labelB := core.ParseBuildLabel("//pkg:labelB", "") + + t.Run("NoSubincludes", func(t *testing.T) { + // BUILD scope + scopeBUILD := &scope{} + // Function execution + scopeFuncExec := &scope{ + caller: scopeBUILD, + } + labels := scopeFuncExec.ActiveSubincludes()() + assert.Empty(t, labels) + }) + + t.Run("SingleSubinclude", func(t *testing.T) { + // File A scope + scopeA := &scope{ + subincludeLabel: &labelA, + } + // Function defined in File A + scopeFuncDef := &scope{ + parent: scopeA, + } + // BUILD scope + scopeBUILD := &scope{} + // Function execution + scopeFuncExec := &scope{ + parent: scopeFuncDef, + caller: scopeBUILD, + } + + labels := scopeFuncExec.ActiveSubincludes()() + assert.Equal(t, core.BuildLabels{labelA}, labels) + }) + + t.Run("NestedSubincludes", func(t *testing.T) { + // File A scope + scopeA := &scope{ + subincludeLabel: &labelA, + } + // File B scope (subincluded by A) + scopeB := &scope{ + subincludeLabel: &labelB, + parent: scopeA, + } + // Function defined in File B + scopeFuncDef := &scope{ + parent: scopeB, + } + // BUILD scope + scopeBUILD := &scope{} + // Function execution + scopeFuncExec := &scope{ + parent: scopeFuncDef, + caller: scopeBUILD, + } + + labels := scopeFuncExec.ActiveSubincludes()() + assert.ElementsMatch(t, core.BuildLabels{labelA, labelB}, labels) + }) +} diff --git a/src/parse/asp/label_context_test.go b/src/parse/asp/label_context_test.go index 9b99625bfc..023cb00906 100644 --- a/src/parse/asp/label_context_test.go +++ b/src/parse/asp/label_context_test.go @@ -12,7 +12,7 @@ import ( func newScope(pkgName, subrepo, plugin string) *scope { s := &scope{ - pkg: core.NewPackageSubrepo(pkgName, subrepo), + pkg: core.NewPackage(pkgName, core.WithPackageSubrepo(subrepo)), state: core.NewBuildState(core.DefaultConfiguration()), } if plugin != "" { diff --git a/src/parse/asp/objects.go b/src/parse/asp/objects.go index a783b55b5c..9b62a65817 100644 --- a/src/parse/asp/objects.go +++ b/src/parse/asp/objects.go @@ -699,6 +699,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.caller = s // registering previous scope as caller s2.config = s.config s2.Set("CONFIG", s.config) // This needs to be copied across too :( s2.Callback = s.Callback diff --git a/src/parse/asp/parser.go b/src/parse/asp/parser.go index f67a8605ae..74a96f0475 100644 --- a/src/parse/asp/parser.go +++ b/src/parse/asp/parser.go @@ -35,14 +35,14 @@ type Parser struct { // NewParser creates a new parser instance. One is normally sufficient for a process lifetime. func NewParser(state *core.BuildState) *Parser { - p := newParser() + p := NewParserOnly() p.interpreter = newInterpreter(state, p) p.limiter = p.interpreter.limiter return p } -// newParser creates just the parser with no interpreter. -func newParser() *Parser { +// NewParserOnly creates just the parser with no interpreter. +func NewParserOnly() *Parser { return &Parser{ builtins: map[string][]byte{}, limiter: make(semaphore, 10), diff --git a/src/parse/asp/parser_test.go b/src/parse/asp/parser_test.go index fd534b31bc..1089bcc012 100644 --- a/src/parse/asp/parser_test.go +++ b/src/parse/asp/parser_test.go @@ -10,7 +10,7 @@ import ( // TODO(peterebden): Might get rid of this, we may want to expose a similar thing on Parser. func parseFileOnly(filename string) (*File, []*Statement, error) { - stmts, err := newParser().ParseFileOnly(filename) + stmts, err := NewParserOnly().ParseFileOnly(filename) return newFile(filename), stmts, err } @@ -448,7 +448,7 @@ func TestAssert(t *testing.T) { } func TestOptimise(t *testing.T) { - p := newParser() + p := NewParserOnly() statements, err := p.parse(nil, "src/parse/asp/test_data/optimise.build") f := newFile("src/parse/asp/test_data/optimise.build") assert.NoError(t, err) @@ -486,7 +486,7 @@ func TestOptimise(t *testing.T) { } func TestOptimiseJoin(t *testing.T) { - p := newParser() + p := NewParserOnly() statements, err := p.parse(nil, "src/parse/asp/test_data/optimise_join.build") assert.NoError(t, err) assert.Equal(t, 1, len(statements)) @@ -658,12 +658,12 @@ func TestMissingNewlines(t *testing.T) { } func TestRepeatedArguments(t *testing.T) { - _, err := newParser().parse(nil, "src/parse/asp/test_data/repeated_arguments.build") + _, err := NewParserOnly().parse(nil, "src/parse/asp/test_data/repeated_arguments.build") assert.Error(t, err) } func TestConstantAssignments(t *testing.T) { - _, err := newParser().parse(nil, "src/parse/asp/test_data/constant_assign.build") + _, err := NewParserOnly().parse(nil, "src/parse/asp/test_data/constant_assign.build") assert.Error(t, err) } @@ -815,7 +815,7 @@ func TestFStringConcat(t *testing.T) { func TestFStringImplicitStringConcat(t *testing.T) { str := "str('testing that we can carry these ' f'over {multiple} lines' r' \\n')" - prog, err := newParser().parseAndHandleErrors(strings.NewReader(strings.ReplaceAll(str, "\t", ""))) + prog, err := NewParserOnly().parseAndHandleErrors(strings.NewReader(strings.ReplaceAll(str, "\t", ""))) require.NoError(t, err) fString := prog[0].Ident.Action.Call.Arguments[0].Value.Val.FString @@ -827,21 +827,21 @@ func TestFStringImplicitStringConcat(t *testing.T) { // F strings should report a sensible error when the {} aren't complete func TestFStringIncompleteError(t *testing.T) { str := "s = f'some {' '.join([])}'" - _, err := newParser().parseAndHandleErrors(strings.NewReader(str)) + _, err := NewParserOnly().parseAndHandleErrors(strings.NewReader(str)) require.Error(t, err) assert.Contains(t, err.Error(), "Unterminated brace in fstring") } // Continue shouldn't be allowed outside a loop func TestContinueOutsideLoop(t *testing.T) { - _, err := newParser().parseAndHandleErrors(strings.NewReader("continue")) + _, err := NewParserOnly().parseAndHandleErrors(strings.NewReader("continue")) require.Error(t, err) assert.Contains(t, err.Error(), "'continue' outside loop") } // Break shouldn't be allowed outside a loop func TestBreakOutsideLoop(t *testing.T) { - _, err := newParser().parseAndHandleErrors(strings.NewReader("break")) + _, err := NewParserOnly().parseAndHandleErrors(strings.NewReader("break")) require.Error(t, err) assert.Contains(t, err.Error(), "'break' outside loop") } @@ -853,7 +853,7 @@ for i in [1,2,3]: def foo(): break ` - _, err := newParser().parseAndHandleErrors(strings.NewReader(code)) + _, err := NewParserOnly().parseAndHandleErrors(strings.NewReader(code)) require.Error(t, err) assert.Contains(t, err.Error(), "'break' outside loop") } diff --git a/src/parse/parse_step.go b/src/parse/parse_step.go index 2c9689feab..22ddccd61a 100644 --- a/src/parse/parse_step.go +++ b/src/parse/parse_step.go @@ -182,7 +182,11 @@ func maybeParseSubrepoPackage(state *core.BuildState, subrepoPkg, subrepoSubrepo // parsePackage parses a BUILD file and adds the package to the build graph func parsePackage(state *core.BuildState, label, dependent core.BuildLabel, subrepo *core.Subrepo, mode core.ParseMode) (*core.Package, error) { packageName := label.PackageName - pkg := core.NewPackage(packageName) + var opts []core.PackageOptions + if state.ParseMetadata { + opts = append(opts, core.WithPackageMetadata()) + } + pkg := core.NewPackage(packageName, opts...) pkg.Subrepo = subrepo var fileSystem iofs.FS = fs.HostFS if subrepo != nil { diff --git a/src/please.go b/src/please.go index 5eda69dfed..a096e2f24b 100644 --- a/src/please.go +++ b/src/please.go @@ -475,7 +475,7 @@ var opts struct { // Functions are called after args are parsed and return a POSIX exit code (0 means success). var buildFunctions = map[string]func() int{ "build": func() int { - success, state := runBuild(opts.Build.Args.Targets, true, false, false) + success, state := runBuild(opts.Build.Args.Targets, buildOpts{Build: true}) if !success || opts.Build.OutDir == "" { return toExitCode(success, state) } @@ -499,7 +499,7 @@ var buildFunctions = map[string]func() int{ if opts.Hash.Update { opts.BehaviorFlags.NoHashVerification = true } - success, state := runBuild(opts.Hash.Args.Targets, true, false, false) + success, state := runBuild(opts.Hash.Args.Targets, buildOpts{Build: true}) if success { if opts.Hash.Detailed { for _, target := range state.ExpandOriginalLabels() { @@ -556,14 +556,14 @@ var buildFunctions = map[string]func() int{ return toExitCode(success, state) }, "debug": func() int { - success, state := runBuild([]core.BuildLabel{opts.Debug.Args.Target}, true, false, false) + success, state := runBuild([]core.BuildLabel{opts.Debug.Args.Target}, buildOpts{Build: true}) if !success { return toExitCode(success, state) } return debug.Debug(state, opts.Debug.Args.Target, opts.Debug.Args.Args, exec.ConvertEnv(opts.Debug.Env), opts.Debug.Share.Network, opts.Debug.Share.Mount) }, "exec": func() int { - success, state := runBuild([]core.BuildLabel{opts.Exec.Args.Target.BuildLabel}, true, false, false) + success, state := runBuild([]core.BuildLabel{opts.Exec.Args.Target.BuildLabel}, buildOpts{Build: true}) if !success { return toExitCode(success, state) } @@ -594,7 +594,7 @@ var buildFunctions = map[string]func() int{ if len(unannotated) == 0 { return 0 } - success, state := runBuild(unannotated, true, false, false) + success, state := runBuild(unannotated, buildOpts{Build: true}) if !success { return toExitCode(success, state) } @@ -608,7 +608,7 @@ var buildFunctions = map[string]func() int{ if len(unannotated) == 0 { return 0 } - success, state := runBuild(unannotated, true, false, false) + success, state := runBuild(unannotated, buildOpts{Build: true}) if !success { return toExitCode(success, state) } @@ -618,7 +618,7 @@ var buildFunctions = map[string]func() int{ return 0 }, "run": func() int { - if success, state := runBuild([]core.BuildLabel{opts.Run.Args.Target.BuildLabel}, true, false, false); success { + if success, state := runBuild([]core.BuildLabel{opts.Run.Args.Target.BuildLabel}, buildOpts{Build: true}); success { var dir string if opts.Run.WD != "" { dir = getAbsolutePath(opts.Run.WD, originalWorkingDirectory) @@ -642,7 +642,7 @@ var buildFunctions = map[string]func() int{ if len(unannotated) == 0 { return 0 } - if success, state := runBuild(unannotated, true, false, false); success { + if success, state := runBuild(unannotated, buildOpts{Build: true}); success { var dir string if opts.Run.WD != "" { dir = getAbsolutePath(opts.Run.WD, originalWorkingDirectory) @@ -659,7 +659,7 @@ var buildFunctions = map[string]func() int{ if len(unannotated) == 0 { return 0 } - if success, state := runBuild(unannotated, true, false, false); success { + if success, state := runBuild(unannotated, buildOpts{Build: true}); success { var dir string if opts.Run.WD != "" { dir = getAbsolutePath(opts.Run.WD, originalWorkingDirectory) @@ -686,7 +686,7 @@ var buildFunctions = map[string]func() int{ } opts.Clean.Args.Targets = core.WholeGraph } - if success, state := runBuild(opts.Clean.Args.Targets, false, false, false); success { + if success, state := runBuild(opts.Clean.Args.Targets, buildOpts{}); success { clean.Targets(state, state.ExpandOriginalLabels()) return 0 } @@ -708,7 +708,7 @@ var buildFunctions = map[string]func() int{ return 1 }, "gc": func() int { - success, state := runBuild(core.WholeGraph, false, false, true) + success, state := runBuild(core.WholeGraph, buildOpts{IsQuery: true}) if success { gc.GarbageCollect(state, opts.Gc.Args.Targets, state.ExpandLabels(state.Config.Gc.Keep), state.Config.Gc.Keep, state.Config.Gc.KeepLabel, opts.Gc.Conservative, opts.Gc.TargetsOnly, opts.Gc.SrcsOnly, opts.Gc.NoPrompt, opts.Gc.DryRun, opts.Gc.Git) @@ -767,14 +767,14 @@ var buildFunctions = map[string]func() int{ return 0 }, "export": func() int { - success, state := runBuild(opts.Export.Args.Targets, false, false, false) + success, state := runBuild(opts.Export.Args.Targets, buildOpts{ParseMetadata: true}) if success { - export.ToDir(state, opts.Export.Output, opts.Export.NoTrim, state.ExpandOriginalLabels()) + export.Repo(state, opts.Export.Output, opts.Export.NoTrim, state.ExpandOriginalLabels()) } return toExitCode(success, state) }, "export.outputs": func() int { - success, state := runBuild(opts.Export.Outputs.Args.Targets, true, false, true) + success, state := runBuild(opts.Export.Outputs.Args.Targets, buildOpts{Build: true, IsQuery: true, ParseMetadata: true}) if success { export.Outputs(state, opts.Export.Output, state.ExpandOriginalLabels()) } @@ -962,14 +962,14 @@ var buildFunctions = map[string]func() int{ log.Fatalf("%s", err) } readConfig() - _, before := runBuild(core.WholeGraph, false, false, false) + _, before := runBuild(core.WholeGraph, buildOpts{}) // N.B. Ignore failure here; if we can't parse the graph before then it will suffice to // assume that anything we don't know about has changed. if err := scm.Checkout(original); err != nil { log.Fatalf("%s", err) } readConfig() - success, after := runBuild(core.WholeGraph, false, false, false) + success, after := runBuild(core.WholeGraph, buildOpts{}) if !success { return 1 } @@ -1001,7 +1001,7 @@ var buildFunctions = map[string]func() int{ "watch": func() int { targets, args := testTargets(opts.Watch.Args.Target, opts.Watch.Args.Args, false, "") // Don't ask it to test now since we don't know if any of them are tests yet. - success, state := runBuild(targets, true, false, false) + success, state := runBuild(targets, buildOpts{Build: true}) state.NeedRun = opts.Watch.Run watch.Watch(state, state.ExpandOriginalLabels(), args, opts.Watch.NoTest, runPlease) return toExitCode(success, state) @@ -1026,7 +1026,7 @@ var buildFunctions = map[string]func() int{ opts.Generate.Args.Targets = []core.BuildLabel{target} } - if success, state := runBuild(opts.Generate.Args.Targets, true, false, true); success { + if success, state := runBuild(opts.Generate.Args.Targets, buildOpts{Build: true, IsQuery: true}); success { if opts.Generate.Gitignore != "" { err := generate.UpdateGitignore(state.Graph, state.ExpandOriginalLabels(), opts.Generate.Gitignore) if err != nil { @@ -1068,7 +1068,7 @@ func runTool(_tool tool.Tool) int { // We skip loading the repo config in init for `plz tool` to allow this command to work outside of a repo root. If // the tool looks like a build label, we need to set the repo root now. config = mustReadConfigAndSetRoot(false) - if success, state := runBuild(label, true, false, false); success { + if success, state := runBuild(label, buildOpts{Build: true}); success { annotatedOutputLabels := core.AnnotateLabels(label) run.Run(state, annotatedOutputLabels[0], opts.Tool.Args.Args.AsStrings(), false, false, false, "", "") } @@ -1100,7 +1100,7 @@ func runQuery(needFullParse bool, labels []core.BuildLabel, onSuccess func(state if len(labels) == 0 { labels = core.WholeGraph } - if success, state := runBuild(labels, false, false, true); success { + if success, state := runBuild(labels, buildOpts{IsQuery: true}); success { onSuccess(state) return 0 } @@ -1112,7 +1112,7 @@ func doTest(targets []core.BuildLabel, args []string, surefireDir cli.Filepath, fs.RemoveAll(string(resultsFile)) os.MkdirAll(string(surefireDir), core.DirPermissions) opts.Test.StateArgs = args - success, state := runBuild(targets, true, true, false) + success, state := runBuild(targets, buildOpts{Build: true, Test: true}) test.CopySurefireXMLFilesToDir(state, string(surefireDir)) test.WriteResultsToFileOrDie(state.Graph, string(resultsFile), state.Config.Test.StoreTestOutputOnSuccess) return success, state @@ -1127,7 +1127,7 @@ func prettyOutput(interactiveOutput bool, plainOutput bool, verbosity cli.Verbos } // Please starts & runs the main build process through to its completion. -func Please(targets []core.BuildLabel, config *core.Configuration, shouldBuild, shouldTest bool) (bool, *core.BuildState) { +func Please(targets []core.BuildLabel, config *core.Configuration, buildOpts buildOpts) (bool, *core.BuildState) { if opts.BuildFlags.NumThreads > 0 { config.Please.NumThreads = opts.BuildFlags.NumThreads config.Parse.NumThreads = opts.BuildFlags.NumThreads @@ -1150,8 +1150,6 @@ func Please(targets []core.BuildLabel, config *core.Configuration, shouldBuild, state.TestSequentially = opts.Test.Sequentially || opts.Cover.Sequentially // Similarly here. state.TestArgs = opts.Test.StateArgs state.NeedCoverage = opts.Cover.active || config.Build.Config == "cover" - state.NeedBuild = shouldBuild - state.NeedTests = shouldTest state.NeedRun = !opts.Run.Args.Target.IsEmpty() || len(opts.Run.Parallel.PositionalArgs.Targets) > 0 || len(opts.Run.Sequential.PositionalArgs.Targets) > 0 || !opts.Exec.Args.Target.IsEmpty() || len(opts.Exec.Sequential.Args.Targets) > 0 || len(opts.Exec.Parallel.Args.Targets) > 0 || opts.Tool.Args.Tool != "" || debug state.NeedHashesOnly = len(opts.Hash.Args.Targets) > 0 state.PrepareOnly = opts.Build.Shell != "" || opts.Test.Shell != "" || opts.Cover.Shell != "" @@ -1165,6 +1163,9 @@ func Please(targets []core.BuildLabel, config *core.Configuration, shouldBuild, state.ShowAllOutput = opts.OutputFlags.ShowAllOutput state.ParsePackageOnly = opts.ParsePackageOnly state.EnableBreakpoints = opts.BehaviorFlags.Debug + state.NeedBuild = buildOpts.Build + state.NeedTests = buildOpts.Test + state.ParseMetadata = buildOpts.ParseMetadata state.NeedDebugDeps = debug // What outputs get downloaded in remote execution. @@ -1317,10 +1318,18 @@ func readConfig() *core.Configuration { return cfg } +// buildOpts specifies parameter for the core.runBuild method. +type buildOpts struct { + Build bool + Test bool + IsQuery bool + ParseMetadata bool +} + // Runs the actual build // Which phases get run are controlled by shouldBuild and shouldTest. -func runBuild(targets []core.BuildLabel, shouldBuild, shouldTest, isQuery bool) (bool, *core.BuildState) { - if !isQuery { +func runBuild(targets []core.BuildLabel, buildOpts buildOpts) (bool, *core.BuildState) { + if !buildOpts.IsQuery { opts.BuildFlags.Exclude = append(opts.BuildFlags.Exclude, "manual", "manual:"+core.OsArch) } if stat, _ := os.Stdin.Stat(); (stat.Mode()&os.ModeCharDevice) == 0 && !plz.ReadingStdin(targets) { @@ -1332,7 +1341,7 @@ func runBuild(targets []core.BuildLabel, shouldBuild, shouldTest, isQuery bool) if len(targets) == 0 { targets = core.InitialPackage() } - return Please(targets, config, shouldBuild, shouldTest) + return Please(targets, config, buildOpts) } var originalWorkingDirectory string @@ -1430,7 +1439,7 @@ func getCompletions(qry string) (*query.CompletionPackages, []string) { if completions.PackageToParse != "" || completions.IsRoot { labelsToParse := []core.BuildLabel{{PackageName: completions.PackageToParse, Name: "all"}} - if success, state := Please(labelsToParse, config, false, false); success { + if success, state := Please(labelsToParse, config, buildOpts{}); success { return completions, query.Completions(state.Graph, completions, binary, isTest, completions.Hidden) } } diff --git a/src/query/changes_test.go b/src/query/changes_test.go index ed4124b684..d3806a4527 100644 --- a/src/query/changes_test.go +++ b/src/query/changes_test.go @@ -160,7 +160,7 @@ func addTarget(state *core.BuildState, label string, dep *core.BuildTarget, sour } pkg := state.Graph.PackageByLabel(t.Label) if pkg == nil { - pkg = core.NewPackageSubrepo(t.Label.PackageName, t.Label.Subrepo) + pkg = core.NewPackage(t.Label.PackageName, core.WithPackageSubrepo(t.Label.Subrepo)) state.Graph.AddPackage(pkg) } pkg.AddTarget(t) diff --git a/test/export/BUILD b/test/export/BUILD index 545dc35c80..6223161562 100644 --- a/test/export/BUILD +++ b/test/export/BUILD @@ -9,5 +9,6 @@ filegroup( # 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", + cmd = f'plz export --output "$PLZ_EXPORT_DIR" //src/core:core && plz --repo_root="$PLZ_EXPORT_DIR" build //src/core:core', + pre_cmd = f'export PLZ_EXPORT_DIR="$(mktemp -d)"', ) diff --git a/test/export/please_export_e2e_test.build_defs b/test/export/please_export_e2e_test.build_defs index 0f240b5d4b..05e6bde2e5 100644 --- a/test/export/please_export_e2e_test.build_defs +++ b/test/export/please_export_e2e_test.build_defs @@ -40,7 +40,7 @@ def please_export_e2e_test( if enforce_different_repos: test_cmd += [ # Enforce source_repo and expected_repo differences - f'(diff -rq "$DATA_SOURCE_REPO" "$DATA_EXPECTED_REPO" && \ + f'(diff -rq "$DATA_SOURCE_REPO" "$DATA_EXPECTED_REPO" > /dev/null && \ echo "Source and Expected repos must differ" && exit 1 || true)', ] @@ -50,7 +50,7 @@ def please_export_e2e_test( # Golden-Master validation with expected repo. Done before any building to avoid plz-out f'diff -ru "{exported_repo}" "$DATA_EXPECTED_REPO"', # Tests building the exported repo which in turn ensures the sources are included - f'plz --repo_root="{exported_repo}" build //...', + f'plz --repo_root="{exported_repo}" build //... > /dev/null', ] + [f'plz --repo_root="{exported_repo}" {cmd}' for cmd in cmd_on_export] test_cmd = [cmd.replace("plz ", "$TOOLS_PLEASE ") for cmd in test_cmd] diff --git a/test/export/test_builtins/expected_repo/BUILD_FILE b/test/export/test_builtins/expected_repo/BUILD_FILE index a62a98a8d6..b840c2d9f1 100644 --- a/test/export/test_builtins/expected_repo/BUILD_FILE +++ b/test/export/test_builtins/expected_repo/BUILD_FILE @@ -2,5 +2,6 @@ genrule( name = "native_genrule", srcs = ["file.txt"], outs = ["file.wordcount"], - cmd = "wc $SRCS > $OUT", + cmd = "$TOOLS $SRCS > $OUT", + tools = ["//tools:tool"], ) diff --git a/test/export/test_builtins/expected_repo/tools/BUILD_FILE b/test/export/test_builtins/expected_repo/tools/BUILD_FILE new file mode 100644 index 0000000000..71d3129e2c --- /dev/null +++ b/test/export/test_builtins/expected_repo/tools/BUILD_FILE @@ -0,0 +1,6 @@ +export_file( + name = "tool", + src = "tool.sh", + binary = True, + visibility = ["PUBLIC"], +) diff --git a/test/export/test_builtins/expected_repo/tools/tool.sh b/test/export/test_builtins/expected_repo/tools/tool.sh new file mode 100644 index 0000000000..244a9b4ffd --- /dev/null +++ b/test/export/test_builtins/expected_repo/tools/tool.sh @@ -0,0 +1,2 @@ +#!/bin/bash +wc $@ diff --git a/test/export/test_builtins/source_repo/BUILD_FILE b/test/export/test_builtins/source_repo/BUILD_FILE index db7732c487..7c4e63b45d 100644 --- a/test/export/test_builtins/source_repo/BUILD_FILE +++ b/test/export/test_builtins/source_repo/BUILD_FILE @@ -2,7 +2,8 @@ genrule( name = "native_genrule", srcs = ["file.txt"], outs = ["file.wordcount"], - cmd = "wc $SRCS > $OUT", + cmd = "$TOOLS $SRCS > $OUT", + tools = ["//tools:tool"], ) genrule( diff --git a/test/export/test_builtins/source_repo/tools/BUILD_FILE b/test/export/test_builtins/source_repo/tools/BUILD_FILE new file mode 100644 index 0000000000..71d3129e2c --- /dev/null +++ b/test/export/test_builtins/source_repo/tools/BUILD_FILE @@ -0,0 +1,6 @@ +export_file( + name = "tool", + src = "tool.sh", + binary = True, + visibility = ["PUBLIC"], +) diff --git a/test/export/test_builtins/source_repo/tools/tool.sh b/test/export/test_builtins/source_repo/tools/tool.sh new file mode 100644 index 0000000000..244a9b4ffd --- /dev/null +++ b/test/export/test_builtins/source_repo/tools/tool.sh @@ -0,0 +1,2 @@ +#!/bin/bash +wc $@ diff --git a/test/export/test_custom_def_children/expected_repo/build_defs/BUILD_FILE b/test/export/test_custom_def_children/expected_repo/build_defs/BUILD_FILE deleted file mode 100644 index e9b1bc8a20..0000000000 --- a/test/export/test_custom_def_children/expected_repo/build_defs/BUILD_FILE +++ /dev/null @@ -1,5 +0,0 @@ -filegroup( - name = "custom_build_def", - srcs = ["custom.build_defs"], - visibility = ["PUBLIC"], -) diff --git a/test/export/test_custom_def_children/source_repo/build_defs/BUILD_FILE b/test/export/test_custom_def_children/source_repo/build_defs/BUILD_FILE deleted file mode 100644 index e9b1bc8a20..0000000000 --- a/test/export/test_custom_def_children/source_repo/build_defs/BUILD_FILE +++ /dev/null @@ -1,5 +0,0 @@ -filegroup( - name = "custom_build_def", - srcs = ["custom.build_defs"], - visibility = ["PUBLIC"], -) diff --git a/test/export/test_custom_def_children/BUILD b/test/export/test_custom_def_multiple_targets/BUILD similarity index 87% rename from test/export/test_custom_def_children/BUILD rename to test/export/test_custom_def_multiple_targets/BUILD index af8ef5b106..abd0536fd1 100644 --- a/test/export/test_custom_def_children/BUILD +++ b/test/export/test_custom_def_multiple_targets/BUILD @@ -5,7 +5,7 @@ please_export_e2e_test( name = "export_custom_with_adjacent_target", cmd_on_export = [ # Adjacent target of build def - "build //:custom_target#adjacent", + "build //:custom_target_adjacent", ], export_targets = ["//:custom_target"], ) diff --git a/test/export/test_custom_def_children/expected_repo/.plzconfig b/test/export/test_custom_def_multiple_targets/expected_repo/.plzconfig similarity index 100% rename from test/export/test_custom_def_children/expected_repo/.plzconfig rename to test/export/test_custom_def_multiple_targets/expected_repo/.plzconfig diff --git a/test/export/test_custom_def_children/expected_repo/BUILD_FILE b/test/export/test_custom_def_multiple_targets/expected_repo/BUILD_FILE similarity index 100% rename from test/export/test_custom_def_children/expected_repo/BUILD_FILE rename to test/export/test_custom_def_multiple_targets/expected_repo/BUILD_FILE diff --git a/test/export/test_custom_def_multiple_targets/expected_repo/build_defs/BUILD_FILE b/test/export/test_custom_def_multiple_targets/expected_repo/build_defs/BUILD_FILE new file mode 100644 index 0000000000..6e20067074 --- /dev/null +++ b/test/export/test_custom_def_multiple_targets/expected_repo/build_defs/BUILD_FILE @@ -0,0 +1,17 @@ +filegroup( + name = "custom_build_def", + srcs = ["custom.build_defs"], + visibility = ["PUBLIC"], +) + +filegroup( + name = "secondary_build_def", + srcs = ["secondary.build_defs"], + visibility = ["PUBLIC"], +) + +export_file( + name = "secondary_file", + src = "secondary.file", + visibility = ["PUBLIC"], +) diff --git a/test/export/test_custom_def_children/source_repo/build_defs/custom.build_defs b/test/export/test_custom_def_multiple_targets/expected_repo/build_defs/custom.build_defs similarity index 69% rename from test/export/test_custom_def_children/source_repo/build_defs/custom.build_defs rename to test/export/test_custom_def_multiple_targets/expected_repo/build_defs/custom.build_defs index cc0e256e86..00bf12383e 100644 --- a/test/export/test_custom_def_children/source_repo/build_defs/custom.build_defs +++ b/test/export/test_custom_def_multiple_targets/expected_repo/build_defs/custom.build_defs @@ -1,13 +1,13 @@ +subinclude("//build_defs:secondary_build_def") + def custom_target( name:str, srcs:list=[], outs:list=[], outs_adjacent:list=[]): - genrule( - name = f"{name}#adjacent", - srcs = srcs, + secondary_custom_target( + name = f"{name}_adjacent", outs = outs_adjacent, - cmd = "echo 'adjacent' > $OUT && cat $SRCS >> $OUT ", ) return genrule( name = name, diff --git a/test/export/test_custom_def_multiple_targets/expected_repo/build_defs/secondary.build_defs b/test/export/test_custom_def_multiple_targets/expected_repo/build_defs/secondary.build_defs new file mode 100644 index 0000000000..85b6470267 --- /dev/null +++ b/test/export/test_custom_def_multiple_targets/expected_repo/build_defs/secondary.build_defs @@ -0,0 +1,9 @@ +def secondary_custom_target( + name:str, + outs:list=[]): + return genrule( + name = name, + srcs = ["//build_defs:secondary_file"], + outs = outs, + cmd = "echo 'secondary' > $OUT && cat $SRCS >> $OUT ", + ) diff --git a/test/export/test_custom_def_multiple_targets/expected_repo/build_defs/secondary.file b/test/export/test_custom_def_multiple_targets/expected_repo/build_defs/secondary.file new file mode 100644 index 0000000000..7a0bf8eb90 --- /dev/null +++ b/test/export/test_custom_def_multiple_targets/expected_repo/build_defs/secondary.file @@ -0,0 +1 @@ +File for testing diff --git a/test/export/test_custom_def_children/expected_repo/file.txt b/test/export/test_custom_def_multiple_targets/expected_repo/file.txt similarity index 100% rename from test/export/test_custom_def_children/expected_repo/file.txt rename to test/export/test_custom_def_multiple_targets/expected_repo/file.txt diff --git a/test/export/test_custom_def_children/source_repo/.plzconfig b/test/export/test_custom_def_multiple_targets/source_repo/.plzconfig similarity index 100% rename from test/export/test_custom_def_children/source_repo/.plzconfig rename to test/export/test_custom_def_multiple_targets/source_repo/.plzconfig diff --git a/test/export/test_custom_def_children/source_repo/BUILD_FILE b/test/export/test_custom_def_multiple_targets/source_repo/BUILD_FILE similarity index 100% rename from test/export/test_custom_def_children/source_repo/BUILD_FILE rename to test/export/test_custom_def_multiple_targets/source_repo/BUILD_FILE diff --git a/test/export/test_custom_def_multiple_targets/source_repo/build_defs/BUILD_FILE b/test/export/test_custom_def_multiple_targets/source_repo/build_defs/BUILD_FILE new file mode 100644 index 0000000000..6e20067074 --- /dev/null +++ b/test/export/test_custom_def_multiple_targets/source_repo/build_defs/BUILD_FILE @@ -0,0 +1,17 @@ +filegroup( + name = "custom_build_def", + srcs = ["custom.build_defs"], + visibility = ["PUBLIC"], +) + +filegroup( + name = "secondary_build_def", + srcs = ["secondary.build_defs"], + visibility = ["PUBLIC"], +) + +export_file( + name = "secondary_file", + src = "secondary.file", + visibility = ["PUBLIC"], +) diff --git a/test/export/test_custom_def_children/expected_repo/build_defs/custom.build_defs b/test/export/test_custom_def_multiple_targets/source_repo/build_defs/custom.build_defs similarity index 69% rename from test/export/test_custom_def_children/expected_repo/build_defs/custom.build_defs rename to test/export/test_custom_def_multiple_targets/source_repo/build_defs/custom.build_defs index cc0e256e86..00bf12383e 100644 --- a/test/export/test_custom_def_children/expected_repo/build_defs/custom.build_defs +++ b/test/export/test_custom_def_multiple_targets/source_repo/build_defs/custom.build_defs @@ -1,13 +1,13 @@ +subinclude("//build_defs:secondary_build_def") + def custom_target( name:str, srcs:list=[], outs:list=[], outs_adjacent:list=[]): - genrule( - name = f"{name}#adjacent", - srcs = srcs, + secondary_custom_target( + name = f"{name}_adjacent", outs = outs_adjacent, - cmd = "echo 'adjacent' > $OUT && cat $SRCS >> $OUT ", ) return genrule( name = name, diff --git a/test/export/test_custom_def_multiple_targets/source_repo/build_defs/secondary.build_defs b/test/export/test_custom_def_multiple_targets/source_repo/build_defs/secondary.build_defs new file mode 100644 index 0000000000..85b6470267 --- /dev/null +++ b/test/export/test_custom_def_multiple_targets/source_repo/build_defs/secondary.build_defs @@ -0,0 +1,9 @@ +def secondary_custom_target( + name:str, + outs:list=[]): + return genrule( + name = name, + srcs = ["//build_defs:secondary_file"], + outs = outs, + cmd = "echo 'secondary' > $OUT && cat $SRCS >> $OUT ", + ) diff --git a/test/export/test_custom_def_multiple_targets/source_repo/build_defs/secondary.file b/test/export/test_custom_def_multiple_targets/source_repo/build_defs/secondary.file new file mode 100644 index 0000000000..7a0bf8eb90 --- /dev/null +++ b/test/export/test_custom_def_multiple_targets/source_repo/build_defs/secondary.file @@ -0,0 +1 @@ +File for testing diff --git a/test/export/test_custom_def_children/source_repo/file.txt b/test/export/test_custom_def_multiple_targets/source_repo/file.txt similarity index 100% rename from test/export/test_custom_def_children/source_repo/file.txt rename to test/export/test_custom_def_multiple_targets/source_repo/file.txt diff --git a/test/export/test_go_bin/expected_repo/BUILD_FILE b/test/export/test_go_bin/expected_repo/BUILD_FILE index d0d1a145c0..015bd671a5 100644 --- a/test/export/test_go_bin/expected_repo/BUILD_FILE +++ b/test/export/test_go_bin/expected_repo/BUILD_FILE @@ -4,6 +4,6 @@ go_binary( name = "bin_go_dep", srcs = ["main.go"], deps = [ - "///third_party/go/github.com_google_go-cmp//cmp", + "//third_party/go:cmp", ], ) diff --git a/test/export/test_go_bin/expected_repo/third_party/go/BUILD_FILE b/test/export/test_go_bin/expected_repo/third_party/go/BUILD_FILE index e436be33ec..77ef426dd5 100644 --- a/test/export/test_go_bin/expected_repo/third_party/go/BUILD_FILE +++ b/test/export/test_go_bin/expected_repo/third_party/go/BUILD_FILE @@ -20,6 +20,8 @@ go_stdlib( ) go_repo( + name = "cmp", + install = ["..."], module = "github.com/google/go-cmp", version = "v0.5.6", ) diff --git a/test/export/test_go_bin/source_repo/BUILD_FILE b/test/export/test_go_bin/source_repo/BUILD_FILE index d37abc25b4..79cce0e24e 100644 --- a/test/export/test_go_bin/source_repo/BUILD_FILE +++ b/test/export/test_go_bin/source_repo/BUILD_FILE @@ -4,7 +4,7 @@ go_binary( name = "bin_go_dep", srcs = ["main.go"], deps = [ - "///third_party/go/github.com_google_go-cmp//cmp", + "//third_party/go:cmp", ], ) @@ -12,6 +12,6 @@ go_binary( name = "dummy", srcs = ["dummy.go"], deps = [ - "///third_party/go/github.com_stretchr_testify//:testify", + "//third_party/go:uuid", ], ) diff --git a/test/export/test_go_bin/source_repo/dummy.go b/test/export/test_go_bin/source_repo/dummy.go index 3c49d61525..403ada14aa 100644 --- a/test/export/test_go_bin/source_repo/dummy.go +++ b/test/export/test_go_bin/source_repo/dummy.go @@ -3,7 +3,7 @@ package dummy import ( "fmt" - "github.com/stretchr/testify/" + "github.com/google/uuid" ) func main() { diff --git a/test/export/test_go_bin/source_repo/third_party/go/BUILD_FILE b/test/export/test_go_bin/source_repo/third_party/go/BUILD_FILE index 20d92fd1ec..bc30a8e53a 100644 --- a/test/export/test_go_bin/source_repo/third_party/go/BUILD_FILE +++ b/test/export/test_go_bin/source_repo/third_party/go/BUILD_FILE @@ -20,25 +20,15 @@ go_stdlib( ) go_repo( + name = "cmp", + install = ["..."], module = "github.com/google/go-cmp", version = "v0.5.6", ) go_repo( # Dummy, unused target - name = "testify", - install = ["..."], - licences = ["MIT"], - module = "github.com/stretchr/testify", - version = "v1.7.0", - deps = [":yaml.v3"], # test we can depend on go_modules -) - -go_module( - # Dummy, unused target - name = "yaml.v3", - licences = ["MIT"], - module = "gopkg.in/yaml.v3", - version = "v3.0.0-20210107192922-496545a6307b", - visibility = ["PUBLIC"], + name = "uuid", + module = "github.com/google/uuid", + version = "v1.6.0", ) diff --git a/test/export/test_go_test/BUILD b/test/export/test_go_test/BUILD new file mode 100644 index 0000000000..e8c41a89e9 --- /dev/null +++ b/test/export/test_go_test/BUILD @@ -0,0 +1,7 @@ +subinclude("//test/export:export_e2e_test_build_def") + +# Test go_test target with a go third_party dependency that implicitly requires other packages. +please_export_e2e_test( + name = "export_go_test", + export_targets = ["//:test"], +) diff --git a/test/export/test_go_test/expected_repo/.plzconfig b/test/export/test_go_test/expected_repo/.plzconfig new file mode 100644 index 0000000000..7fb433a97e --- /dev/null +++ b/test/export/test_go_test/expected_repo/.plzconfig @@ -0,0 +1,10 @@ +[Parse] +BuildFileName = BUILD # required by subrepos +BuildFileName = BUILD_FILE +preloadsubincludes = ///go//build_defs:go + +[Plugin "go"] +Target = //plugins:go +GoTool = //third_party/go:toolchain|go +STDLib = //third_party/go:std +ModFile = //:go_mod diff --git a/test/export/test_go_test/expected_repo/BUILD_FILE b/test/export/test_go_test/expected_repo/BUILD_FILE new file mode 100644 index 0000000000..516ef528ca --- /dev/null +++ b/test/export/test_go_test/expected_repo/BUILD_FILE @@ -0,0 +1,11 @@ +filegroup( + name = "go_mod", + srcs = ["go.mod"], + visibility = ["PUBLIC"], +) + +go_test( + name = "test", + srcs = ["test.go"], + deps = ["///third_party/go/github.com_stretchr_testify//assert"], +) diff --git a/test/export/test_go_test/expected_repo/go.mod b/test/export/test_go_test/expected_repo/go.mod new file mode 100644 index 0000000000..7fcf309969 --- /dev/null +++ b/test/export/test_go_test/expected_repo/go.mod @@ -0,0 +1,11 @@ +module github.com/thought-machine/please/test_repo + +go 1.23.0 + +require github.com/stretchr/testify v1.9.0 + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/test/export/test_go_test/expected_repo/plugins/BUILD_FILE b/test/export/test_go_test/expected_repo/plugins/BUILD_FILE new file mode 100644 index 0000000000..55e81b8e1d --- /dev/null +++ b/test/export/test_go_test/expected_repo/plugins/BUILD_FILE @@ -0,0 +1,6 @@ +plugin_repo( + name = "go", + owner = "please-build", + plugin = "go-rules", + revision = "v1.30.0", +) diff --git a/test/export/test_go_test/expected_repo/test.go b/test/export/test_go_test/expected_repo/test.go new file mode 100644 index 0000000000..aaf3c0c44b --- /dev/null +++ b/test/export/test_go_test/expected_repo/test.go @@ -0,0 +1,11 @@ +package test + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestSomething(t *testing.T) { + assert.NotEqual(t, 0, 1) +} diff --git a/test/export/test_go_test/expected_repo/third_party/go/BUILD_FILE b/test/export/test_go_test/expected_repo/third_party/go/BUILD_FILE new file mode 100644 index 0000000000..42a26f4f9c --- /dev/null +++ b/test/export/test_go_test/expected_repo/third_party/go/BUILD_FILE @@ -0,0 +1,30 @@ +go_toolchain( + name = "toolchain", + version = "1.26.2", +) + +go_stdlib(name = "std") + +go_repo( + licences = ["MIT"], + module = "github.com/stretchr/testify", + version = "v1.9.0", +) + +go_repo( + licences = ["ISC"], + module = "github.com/davecgh/go-spew", + version = "v1.1.1", +) + +go_repo( + licences = ["BSD-3-Clause"], + module = "github.com/pmezard/go-difflib", + version = "v1.0.0", +) + +go_repo( + licences = ["MIT"], + module = "gopkg.in/yaml.v3", + version = "v3.0.1", +) diff --git a/test/export/test_go_test/source_repo/.plzconfig b/test/export/test_go_test/source_repo/.plzconfig new file mode 100644 index 0000000000..7fb433a97e --- /dev/null +++ b/test/export/test_go_test/source_repo/.plzconfig @@ -0,0 +1,10 @@ +[Parse] +BuildFileName = BUILD # required by subrepos +BuildFileName = BUILD_FILE +preloadsubincludes = ///go//build_defs:go + +[Plugin "go"] +Target = //plugins:go +GoTool = //third_party/go:toolchain|go +STDLib = //third_party/go:std +ModFile = //:go_mod diff --git a/test/export/test_go_test/source_repo/BUILD_FILE b/test/export/test_go_test/source_repo/BUILD_FILE new file mode 100644 index 0000000000..c916f9d154 --- /dev/null +++ b/test/export/test_go_test/source_repo/BUILD_FILE @@ -0,0 +1,19 @@ +filegroup( + name = "go_mod", + srcs = ["go.mod"], + visibility = ["PUBLIC"], +) + +go_test( + name = "test", + srcs = ["test.go"], + deps = ["///third_party/go/github.com_stretchr_testify//assert"], +) + +go_binary( + name = "unused", + srcs = ["main.go"], + deps = [ + "//third_party/go:cmp", + ], +) diff --git a/test/export/test_go_test/source_repo/go.mod b/test/export/test_go_test/source_repo/go.mod new file mode 100644 index 0000000000..7fcf309969 --- /dev/null +++ b/test/export/test_go_test/source_repo/go.mod @@ -0,0 +1,11 @@ +module github.com/thought-machine/please/test_repo + +go 1.23.0 + +require github.com/stretchr/testify v1.9.0 + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/test/export/test_go_test/source_repo/main.go b/test/export/test_go_test/source_repo/main.go new file mode 100644 index 0000000000..0853120a28 --- /dev/null +++ b/test/export/test_go_test/source_repo/main.go @@ -0,0 +1,11 @@ +package main + +import ( + "fmt" + + "github.com/google/go-cmp/cmp" +) + +func main() { + fmt.Print(cmp.Equal(1, 1)) +} diff --git a/test/export/test_go_test/source_repo/plugins/BUILD_FILE b/test/export/test_go_test/source_repo/plugins/BUILD_FILE new file mode 100644 index 0000000000..55e81b8e1d --- /dev/null +++ b/test/export/test_go_test/source_repo/plugins/BUILD_FILE @@ -0,0 +1,6 @@ +plugin_repo( + name = "go", + owner = "please-build", + plugin = "go-rules", + revision = "v1.30.0", +) diff --git a/test/export/test_go_test/source_repo/test.go b/test/export/test_go_test/source_repo/test.go new file mode 100644 index 0000000000..aaf3c0c44b --- /dev/null +++ b/test/export/test_go_test/source_repo/test.go @@ -0,0 +1,11 @@ +package test + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestSomething(t *testing.T) { + assert.NotEqual(t, 0, 1) +} diff --git a/test/export/test_go_test/source_repo/third_party/go/BUILD_FILE b/test/export/test_go_test/source_repo/third_party/go/BUILD_FILE new file mode 100644 index 0000000000..88c12fe2a0 --- /dev/null +++ b/test/export/test_go_test/source_repo/third_party/go/BUILD_FILE @@ -0,0 +1,36 @@ +go_toolchain( + name = "toolchain", + version = "1.26.2", +) + +go_stdlib(name = "std") + +go_repo( + licences = ["BSD-3-Clause"], + module = "github.com/google/go-cmp", + version = "v0.6.0", +) + +go_repo( + licences = ["MIT"], + module = "github.com/stretchr/testify", + version = "v1.9.0", +) + +go_repo( + licences = ["ISC"], + module = "github.com/davecgh/go-spew", + version = "v1.1.1", +) + +go_repo( + licences = ["BSD-3-Clause"], + module = "github.com/pmezard/go-difflib", + version = "v1.0.0", +) + +go_repo( + licences = ["MIT"], + module = "gopkg.in/yaml.v3", + version = "v3.0.1", +) diff --git a/test/export/test_custom_in_file_def/BUILD b/test/export/test_in_file_build_def/BUILD similarity index 83% rename from test/export/test_custom_in_file_def/BUILD rename to test/export/test_in_file_build_def/BUILD index 5f361a61dd..a9670e332f 100644 --- a/test/export/test_custom_in_file_def/BUILD +++ b/test/export/test_in_file_build_def/BUILD @@ -3,6 +3,6 @@ subinclude("//test/export:export_e2e_test_build_def") # Export a target generated by a custom build def defined in the same # BUILD file. please_export_e2e_test( - name = "export_custom_target_in_file", + name = "export_in_file_build_def", export_targets = ["//:simple_custom_target"], ) diff --git a/test/export/test_custom_in_file_def/expected_repo/.plzconfig b/test/export/test_in_file_build_def/expected_repo/.plzconfig similarity index 100% rename from test/export/test_custom_in_file_def/expected_repo/.plzconfig rename to test/export/test_in_file_build_def/expected_repo/.plzconfig diff --git a/test/export/test_custom_in_file_def/expected_repo/BUILD_FILE b/test/export/test_in_file_build_def/expected_repo/BUILD_FILE similarity index 100% rename from test/export/test_custom_in_file_def/expected_repo/BUILD_FILE rename to test/export/test_in_file_build_def/expected_repo/BUILD_FILE diff --git a/test/export/test_custom_in_file_def/expected_repo/file.txt b/test/export/test_in_file_build_def/expected_repo/file.txt similarity index 100% rename from test/export/test_custom_in_file_def/expected_repo/file.txt rename to test/export/test_in_file_build_def/expected_repo/file.txt diff --git a/test/export/test_custom_in_file_def/expected_repo/simple.build_defs b/test/export/test_in_file_build_def/expected_repo/simple.build_defs similarity index 100% rename from test/export/test_custom_in_file_def/expected_repo/simple.build_defs rename to test/export/test_in_file_build_def/expected_repo/simple.build_defs diff --git a/test/export/test_custom_in_file_def/source_repo/.plzconfig b/test/export/test_in_file_build_def/source_repo/.plzconfig similarity index 100% rename from test/export/test_custom_in_file_def/source_repo/.plzconfig rename to test/export/test_in_file_build_def/source_repo/.plzconfig diff --git a/test/export/test_custom_in_file_def/source_repo/BUILD_FILE b/test/export/test_in_file_build_def/source_repo/BUILD_FILE similarity index 100% rename from test/export/test_custom_in_file_def/source_repo/BUILD_FILE rename to test/export/test_in_file_build_def/source_repo/BUILD_FILE diff --git a/test/export/test_custom_in_file_def/source_repo/dummy.in b/test/export/test_in_file_build_def/source_repo/dummy.in similarity index 100% rename from test/export/test_custom_in_file_def/source_repo/dummy.in rename to test/export/test_in_file_build_def/source_repo/dummy.in diff --git a/test/export/test_custom_in_file_def/source_repo/file.txt b/test/export/test_in_file_build_def/source_repo/file.txt similarity index 100% rename from test/export/test_custom_in_file_def/source_repo/file.txt rename to test/export/test_in_file_build_def/source_repo/file.txt diff --git a/test/export/test_custom_in_file_def/source_repo/simple.build_defs b/test/export/test_in_file_build_def/source_repo/simple.build_defs similarity index 100% rename from test/export/test_custom_in_file_def/source_repo/simple.build_defs rename to test/export/test_in_file_build_def/source_repo/simple.build_defs diff --git a/test/export/test_in_file_func_def/BUILD b/test/export/test_in_file_func_def/BUILD new file mode 100644 index 0000000000..cfb1eabd83 --- /dev/null +++ b/test/export/test_in_file_func_def/BUILD @@ -0,0 +1,8 @@ +subinclude("//test/export:export_e2e_test_build_def") + +# Export a target generated by a custom function def defined in the same +# BUILD file. +please_export_e2e_test( + name = "export_in_file_function", + export_targets = ["//:custom_target"], +) diff --git a/test/export/test_in_file_func_def/expected_repo/.plzconfig b/test/export/test_in_file_func_def/expected_repo/.plzconfig new file mode 100644 index 0000000000..8e1ae5655a --- /dev/null +++ b/test/export/test_in_file_func_def/expected_repo/.plzconfig @@ -0,0 +1,3 @@ +[Parse] + +BuildFileName = BUILD_FILE diff --git a/test/export/test_in_file_func_def/expected_repo/BUILD_FILE b/test/export/test_in_file_func_def/expected_repo/BUILD_FILE new file mode 100644 index 0000000000..4638bb33ab --- /dev/null +++ b/test/export/test_in_file_func_def/expected_repo/BUILD_FILE @@ -0,0 +1,16 @@ +def custom( + name:str, + srcs:list=[], + outs:list=[]): + return genrule( + name = name, + srcs = srcs, + outs = outs, + cmd = "cat $SRCS > $OUT", + ) + +custom( + name = "custom_target", + srcs = ["file.txt"], + outs = ["file_simple.out"], +) diff --git a/test/export/test_in_file_func_def/expected_repo/file.txt b/test/export/test_in_file_func_def/expected_repo/file.txt new file mode 100644 index 0000000000..9768ee14c2 --- /dev/null +++ b/test/export/test_in_file_func_def/expected_repo/file.txt @@ -0,0 +1 @@ +Test source file diff --git a/test/export/test_in_file_func_def/source_repo/.plzconfig b/test/export/test_in_file_func_def/source_repo/.plzconfig new file mode 100644 index 0000000000..8e1ae5655a --- /dev/null +++ b/test/export/test_in_file_func_def/source_repo/.plzconfig @@ -0,0 +1,3 @@ +[Parse] + +BuildFileName = BUILD_FILE diff --git a/test/export/test_in_file_func_def/source_repo/BUILD_FILE b/test/export/test_in_file_func_def/source_repo/BUILD_FILE new file mode 100644 index 0000000000..93fc806076 --- /dev/null +++ b/test/export/test_in_file_func_def/source_repo/BUILD_FILE @@ -0,0 +1,22 @@ +def custom( + name:str, + srcs:list=[], + outs:list=[]): + return genrule( + name = name, + srcs = srcs, + outs = outs, + cmd = "cat $SRCS > $OUT", + ) + +custom( + name = "custom_target", + srcs = ["file.txt"], + outs = ["file_simple.out"], +) + +custom( + name = "dummy", + srcs = ["dummy.in"], + outs = ["dummy.out"], +) diff --git a/test/export/test_in_file_func_def/source_repo/dummy.in b/test/export/test_in_file_func_def/source_repo/dummy.in new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/export/test_in_file_func_def/source_repo/file.txt b/test/export/test_in_file_func_def/source_repo/file.txt new file mode 100644 index 0000000000..9768ee14c2 --- /dev/null +++ b/test/export/test_in_file_func_def/source_repo/file.txt @@ -0,0 +1 @@ +Test source file diff --git a/test/export/test_subinclude_trimming/BUILD b/test/export/test_subinclude_trimming/BUILD new file mode 100644 index 0000000000..b363357c3d --- /dev/null +++ b/test/export/test_subinclude_trimming/BUILD @@ -0,0 +1,7 @@ +subinclude("//test/export:export_e2e_test_build_def") + +# Export a target generated by a custom build def and validate that any unused subincludes are trimmed. +please_export_e2e_test( + name = "export_subinclude_trimming", + export_targets = ["//:simple_custom_target"], +) diff --git a/test/export/test_subinclude_trimming/expected_repo/.plzconfig b/test/export/test_subinclude_trimming/expected_repo/.plzconfig new file mode 100644 index 0000000000..8e1ae5655a --- /dev/null +++ b/test/export/test_subinclude_trimming/expected_repo/.plzconfig @@ -0,0 +1,3 @@ +[Parse] + +BuildFileName = BUILD_FILE diff --git a/test/export/test_subinclude_trimming/expected_repo/BUILD_FILE b/test/export/test_subinclude_trimming/expected_repo/BUILD_FILE new file mode 100644 index 0000000000..261729adc3 --- /dev/null +++ b/test/export/test_subinclude_trimming/expected_repo/BUILD_FILE @@ -0,0 +1,7 @@ +subinclude("//build_defs:simple_build_def") + +simple_custom_target( + name = "simple_custom_target", + srcs = ["file.txt"], + outs = ["file_simple.out"], +) diff --git a/test/export/test_subinclude_trimming/expected_repo/build_defs/BUILD_FILE b/test/export/test_subinclude_trimming/expected_repo/build_defs/BUILD_FILE new file mode 100644 index 0000000000..a556ce6d44 --- /dev/null +++ b/test/export/test_subinclude_trimming/expected_repo/build_defs/BUILD_FILE @@ -0,0 +1,5 @@ +filegroup( + name = "simple_build_def", + srcs = ["simple.build_defs"], + visibility = ["PUBLIC"], +) diff --git a/test/export/test_subinclude_trimming/expected_repo/build_defs/simple.build_defs b/test/export/test_subinclude_trimming/expected_repo/build_defs/simple.build_defs new file mode 100644 index 0000000000..8fe3a6021b --- /dev/null +++ b/test/export/test_subinclude_trimming/expected_repo/build_defs/simple.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/test_subinclude_trimming/expected_repo/file.txt b/test/export/test_subinclude_trimming/expected_repo/file.txt new file mode 100644 index 0000000000..9768ee14c2 --- /dev/null +++ b/test/export/test_subinclude_trimming/expected_repo/file.txt @@ -0,0 +1 @@ +Test source file diff --git a/test/export/test_subinclude_trimming/source_repo/.plzconfig b/test/export/test_subinclude_trimming/source_repo/.plzconfig new file mode 100644 index 0000000000..8e1ae5655a --- /dev/null +++ b/test/export/test_subinclude_trimming/source_repo/.plzconfig @@ -0,0 +1,3 @@ +[Parse] + +BuildFileName = BUILD_FILE diff --git a/test/export/test_subinclude_trimming/source_repo/BUILD_FILE b/test/export/test_subinclude_trimming/source_repo/BUILD_FILE new file mode 100644 index 0000000000..b678f4858f --- /dev/null +++ b/test/export/test_subinclude_trimming/source_repo/BUILD_FILE @@ -0,0 +1,15 @@ +subinclude( + "//build_defs:simple_build_def", + "//build_defs:unused_build_def", +) + +simple_custom_target( + name = "simple_custom_target", + srcs = ["file.txt"], + outs = ["file_simple.out"], +) + +unused_target( + name = "dummy", + outs = ["dummy.out"], +) diff --git a/test/export/test_subinclude_trimming/source_repo/build_defs/BUILD_FILE b/test/export/test_subinclude_trimming/source_repo/build_defs/BUILD_FILE new file mode 100644 index 0000000000..562e57a297 --- /dev/null +++ b/test/export/test_subinclude_trimming/source_repo/build_defs/BUILD_FILE @@ -0,0 +1,11 @@ +filegroup( + name = "simple_build_def", + srcs = ["simple.build_defs"], + visibility = ["PUBLIC"], +) + +filegroup( + name = "unused_build_def", + srcs = ["unused.build_defs"], + visibility = ["PUBLIC"], +) diff --git a/test/export/test_subinclude_trimming/source_repo/build_defs/simple.build_defs b/test/export/test_subinclude_trimming/source_repo/build_defs/simple.build_defs new file mode 100644 index 0000000000..8fe3a6021b --- /dev/null +++ b/test/export/test_subinclude_trimming/source_repo/build_defs/simple.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/test_subinclude_trimming/source_repo/build_defs/unused.build_defs b/test/export/test_subinclude_trimming/source_repo/build_defs/unused.build_defs new file mode 100644 index 0000000000..ebdff7de00 --- /dev/null +++ b/test/export/test_subinclude_trimming/source_repo/build_defs/unused.build_defs @@ -0,0 +1,8 @@ +def unused_target( + name:str, + outs:list=[]): + return genrule( + name = name, + outs = outs, + cmd = "echo dummy > $OUT", + ) diff --git a/test/export/test_subinclude_trimming/source_repo/file.txt b/test/export/test_subinclude_trimming/source_repo/file.txt new file mode 100644 index 0000000000..9768ee14c2 --- /dev/null +++ b/test/export/test_subinclude_trimming/source_repo/file.txt @@ -0,0 +1 @@ +Test source file