Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
64 commits
Select commit Hold shift + click to select a range
0ef91bc
Package level metadata that includes a mapping of build statements to…
Mar 27, 2026
63f1b0d
export using BuildFileMetadata logic
Mar 27, 2026
419bd56
register caller scope for callstack-like traversal
Apr 7, 2026
8e1bd00
bufio writer for build statements
Apr 7, 2026
258b170
rewrite export logic into explicit flow
Apr 20, 2026
4110735
export targets related to build statement
Apr 20, 2026
a4252c9
enrich target with subincludes by looping though all scopes
Apr 20, 2026
82f117f
separate buildstmt register from adding target
Apr 20, 2026
eb657de
select and write subincludes
Apr 20, 2026
6535794
skip statement for preloaded subincludes
Apr 21, 2026
8e6c9c0
test: trim subincludes
Apr 21, 2026
9f0b4fe
rename file
Apr 21, 2026
0e597c6
use slices.collect and sort interface
Apr 21, 2026
de76e2c
rewrite export with 2 concrete interfaces: default and notrim
Apr 22, 2026
03ed361
notrim: full copy BUILD file and visit sources
Apr 22, 2026
717aba1
double new lines between targets
Apr 22, 2026
94160bf
suppress diff output when enforcing repo differences
Apr 24, 2026
582319e
export by filtering the original BUILD file
Apr 24, 2026
e6a5293
subincludes use label short string
Apr 24, 2026
ea329c3
Simplify package filtering method into a more explicit "switch" case
Apr 24, 2026
1a47c22
skip subrepos and internal packages when writing package file
Apr 24, 2026
a35119a
fix reusing err var resulted in failed filtering
Apr 24, 2026
9c1522c
doc strings for package metadata
Apr 26, 2026
9395a59
export missing doc strings
Apr 26, 2026
3c3d3d0
test: named go_repo and change testify for slimmer UUID
Apr 29, 2026
13e95cb
export dependencies of subrepos - 13/14 tests passing
Apr 29, 2026
28b5441
test: internal repo test using temp directory to avoid stale data
Apr 29, 2026
781af3a
fix: 0 label subinclude
Apr 29, 2026
2f8355c
adjusting dependency lookup and adjacent target test to include secon…
Apr 29, 2026
3e89c85
test: add custom tool to native test
Apr 30, 2026
9c5af3e
test: go_test export with several deps
Apr 30, 2026
922da6f
optional metadata parsing
May 1, 2026
5aeb360
test: minimal subinclude statement
May 1, 2026
517486a
collect map keys on active subinclude labels
May 1, 2026
f8f8e3f
non-fatal warning for missing source while exporting
May 1, 2026
8aa98e3
skip internal package export
May 1, 2026
5fa64f4
move some fatal to error and continue
May 1, 2026
e60dbb6
missing docstrings
May 1, 2026
c7409ab
run go fmt and plz fmt
May 1, 2026
be87020
move to error and continue for target lookup
May 1, 2026
6f73f78
rename new parser method on test files
May 1, 2026
762c016
use pkg.Metadata directly and remove intermediate pkg methods
May 11, 2026
406f95a
mutex in packagemetadata
May 12, 2026
b86ff9f
update stmt provider to avoid dereference
May 12, 2026
c7cd118
NewPackage with variadic optional functions
May 12, 2026
faf8c33
infof to debugf
May 12, 2026
dd8cfca
package metadata doc comments improvements
May 12, 2026
084f233
improve doc comments and adjust method visibility for export.go
May 12, 2026
bf0f734
hide build output when testing export
May 12, 2026
33d2bc2
open and write of exported package file merged into the same method
May 12, 2026
54f577c
update export_test.go with suggestions
May 12, 2026
ae5f520
apply review suggestions to export.go
May 13, 2026
5f0e549
rename and doc fields for scope
May 13, 2026
e59ab5b
revert new line change of target
May 14, 2026
917fe61
set builtin code filename for scope for debugging
May 14, 2026
b012ab8
Rework packagemetadata to return empty slices or nil instead of error…
May 14, 2026
2edfee0
linter fix imports
May 14, 2026
f59b5d0
ensure subrepo target exists. Failing to export //... on this repo
May 14, 2026
790a437
go test for filtered package
May 15, 2026
f589998
Add doc comments for providers in asp.interpreter
May 15, 2026
75521e2
test: function def in package file
May 15, 2026
2e2edde
unit testing current statement and active subincludes
May 15, 2026
0e74e53
remove nested maps and pointers in favor of using build labels
May 18, 2026
ef76bca
unexport noTrim implementation
May 18, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions src/BUILD.plz
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -29,7 +29,6 @@ go_binary(
"//src/help",
"//src/metrics",
"//src/output",
"//src/parse",
"//src/plz",
"//src/plzinit",
"//src/process",
Expand Down
43 changes: 33 additions & 10 deletions src/core/package.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
Comment thread
DuBento marked this conversation as resolved.
}

// 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.
Expand Down
183 changes: 183 additions & 0 deletions src/core/package_metadata.go
Original file line number Diff line number Diff line change
@@ -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
}
2 changes: 2 additions & 0 deletions src/core/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
15 changes: 14 additions & 1 deletion src/export/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -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",
],
)
Loading
Loading