Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Upgrade original targets #2942

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ issues:
linters:
- errcheck
- dupl
- gocritic
- path: src/core/config.go # The config struct is big & complex and there's usually only one.
text: fieldalignment
linters:
Expand Down
1 change: 1 addition & 0 deletions ChangeLog
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ Version 17.4.0
* Add all commit date formats to supported git_show() format verbs (#2930)
* Fix reduce builtin (#2925)
* Fix issue with completing idents in the language server (#2917)
* Improve performance when a large number of input targets are supplied (#2942)

Version 17.3.1
--------------
Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
17.4.0-beta.3
17.4.0-beta.4
54 changes: 6 additions & 48 deletions src/core/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -2,30 +2,10 @@ subinclude("//build_defs:benchmark")

go_library(
name = "core",
srcs = [
"atomic_bool.go",
"atomic_float32.go",
"build_env.go",
"build_input.go",
"build_label.go",
"build_target.go",
"cache.go",
"command_replacements.go",
"config.go",
"config_flags.go",
"cycle_detector.go",
"graph.go",
"lock.go",
"package.go",
"previous_op.go",
"resources.go",
"stamp.go",
"state.go",
"subrepo.go",
"test_results.go",
"utils.go",
"version.go",
],
srcs = glob(
["*.go"],
exclude = ["*_test.go"],
),
pgo_file = "//:pgo",
visibility = ["PUBLIC"],
deps = [
Expand All @@ -52,29 +32,7 @@ go_library(

go_test(
name = "core_test",
srcs = [
"build_env_test.go",
"build_input_test.go",
"build_label_fuzz_test.go",
"build_label_test.go",
"build_target_benchmark_test.go",
"build_target_test.go",
"command_replacements_test.go",
"config_test.go",
"cycle_detector_test.go",
"graph_benchmark_test.go",
"graph_test.go",
"label_parse_test.go",
"lock_test.go",
"package_test.go",
"previous_op_test.go",
"stamp_test.go",
"state_test.go",
"subrepo_test.go",
"test_results_test.go",
"utils_benchmark_test.go",
"utils_test.go",
],
srcs = glob(["*_test.go"]),
data = ["test_data"],
filter_srcs = False, # As above
deps = [
Expand Down Expand Up @@ -102,4 +60,4 @@ benchmark(
name = "graph_benchmark",
srcs = ["graph_benchmark_test.go"],
deps = [":core"],
)
)
38 changes: 35 additions & 3 deletions src/core/build_label.go
Original file line number Diff line number Diff line change
Expand Up @@ -284,12 +284,35 @@ func (label BuildLabel) Includes(that BuildLabel) bool {
return false
}

// Compare compares this build label to another one (suitable for using with slices.SortFunc)
func (label BuildLabel) Compare(other BuildLabel) int {
if label.Subrepo != other.Subrepo {
if label.Subrepo < other.Subrepo {
return -1
}
return 1
} else if label.PackageName != other.PackageName {
if label.PackageName < other.PackageName {
return -1
}
return 1
} else if label.Name != other.Name {
if label.Name < other.Name {
return -1
}
return 1
}
return 0
}

// Less returns true if this build label would sort less than another one.
func (label BuildLabel) Less(other BuildLabel) bool {
if label.PackageName == other.PackageName {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm restructuring this a bit but also it was wrong for ages because it didn't look at subrepos.

return label.Name < other.Name
if label.Subrepo != other.Subrepo {
return label.Subrepo < other.Subrepo
} else if label.PackageName != other.PackageName {
return label.PackageName < other.PackageName
}
return label.PackageName < other.PackageName
return label.Name < other.Name
}

// Paths is an implementation of BuildInput interface; we use build labels directly as inputs.
Expand Down Expand Up @@ -535,6 +558,15 @@ func (key packageKey) String() string {
return key.Name
}

// BuildLabel returns a build label representing this package key.
func (key packageKey) BuildLabel() BuildLabel {
return BuildLabel{
Subrepo: key.Subrepo,
PackageName: key.Name,
Name: "all",
}
}

// LooksLikeABuildLabel returns true if the string appears to be a build label, false if not.
// Useful for cases like rule sources where sources can be a filename or a label.
func LooksLikeABuildLabel(str string) bool {
Expand Down
32 changes: 32 additions & 0 deletions src/core/build_label_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,38 @@ func TestSingleSlash(t *testing.T) {
assert.Equal(t, BuildLabel{PackageName: "common/go/pool", Name: "pool"}, label)
}

func TestLess(t *testing.T) {
foo := ParseBuildLabel("//third_party:foo", "")
bar := ParseBuildLabel("//third_party:bar", "")
assert.True(t, bar.Less(foo))
assert.False(t, foo.Less(bar))
assert.False(t, foo.Less(foo))
}

func TestLessSubrepo(t *testing.T) {
foo := ParseBuildLabel("//third_party:foo", "")
bar := ParseBuildLabel("///baz//third_party:bar", "")
assert.False(t, bar.Less(foo))
assert.True(t, foo.Less(bar))
assert.False(t, foo.Less(foo))
}

func TestCompare(t *testing.T) {
foo := ParseBuildLabel("//third_party:foo", "")
bar := ParseBuildLabel("//third_party:bar", "")
assert.Equal(t, -1, bar.Compare(foo))
assert.Equal(t, 1, foo.Compare(bar))
assert.Equal(t, 0, foo.Compare(foo))
}

func TestCompareSubrepo(t *testing.T) {
foo := ParseBuildLabel("//third_party:foo", "")
bar := ParseBuildLabel("///baz//third_party:bar", "")
assert.Equal(t, 1, bar.Compare(foo))
assert.Equal(t, -1, foo.Compare(bar))
assert.Equal(t, 0, foo.Compare(foo))
}

func TestMain(m *testing.M) {
// Used to support TestComplete, the function it's testing re-execs
// itself thinking that it's actually plz.
Expand Down
29 changes: 9 additions & 20 deletions src/core/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -286,8 +286,7 @@ type stateProgress struct {
// The set of known states
allStates []*BuildState
// Targets that we were originally requested to build
originalTargets []BuildLabel
originalTargetMutex sync.Mutex
originalTargets *TargetSet
// True if something about the build has failed.
failed atomicBool
// True if >= 1 target has failed to build
Expand Down Expand Up @@ -423,14 +422,11 @@ func (state *BuildState) IsOriginalTarget(target *BuildTarget) bool {
}

func (state *BuildState) isOriginalTarget(target *BuildTarget, exact bool) bool {
state.progress.originalTargetMutex.Lock()
defer state.progress.originalTargetMutex.Unlock()
for _, original := range state.progress.originalTargets {
if original == target.Label || (!exact && original.IsAllTargets() && original.PackageName == target.Label.PackageName && state.ShouldInclude(target)) {
return true
}
if exact {
return state.progress.originalTargets.MatchExact(target.Label)
}
return false
matched, wasExact := state.progress.originalTargets.Match(target.Label)
return matched && (wasExact || state.ShouldInclude(target))
}

// IsOriginalTargetOrParent is like IsOriginalTarget but checks the target's parent too (if it has one)
Expand Down Expand Up @@ -480,9 +476,7 @@ func (state *BuildState) AddOriginalTarget(label BuildLabel, addToList bool) {
}
}
if addToList {
state.progress.originalTargetMutex.Lock()
state.progress.originalTargets = append(state.progress.originalTargets, label)
state.progress.originalTargetMutex.Unlock()
state.progress.originalTargets.Add(label)
}
state.addPendingParse(label, OriginalTarget, ParseModeNormal)
}
Expand Down Expand Up @@ -702,18 +696,12 @@ func (state *BuildState) NumDone() int {
// ExpandOriginalLabels expands any pseudo-labels (ie. :all, ... has already been resolved to a bunch :all targets)
// from the set of original labels. This will exclude non-test targets when we're building for test.
func (state *BuildState) ExpandOriginalLabels() BuildLabels {
state.progress.originalTargetMutex.Lock()
targets := state.progress.originalTargets[:]
state.progress.originalTargetMutex.Unlock()
return state.ExpandLabels(targets)
return state.ExpandLabels(state.progress.originalTargets.AllTargets())
}

// ExpandAllOriginalLabels is the same as ExpandOriginalLabels except it always includes non-test targets
func (state *BuildState) ExpandAllOriginalLabels() BuildLabels {
state.progress.originalTargetMutex.Lock()
targets := state.progress.originalTargets[:]
state.progress.originalTargetMutex.Unlock()
return state.expandLabels(targets, false)
return state.expandLabels(state.progress.originalTargets.AllTargets(), false)
}

func AnnotateLabels(labels []BuildLabel) []AnnotatedOutputLabel {
Expand Down Expand Up @@ -1394,6 +1382,7 @@ func NewBuildState(config *Configuration) *BuildState {
packageWaits: cmap.New[packageKey, chan struct{}](cmap.DefaultShardCount, hashPackageKey),
internalResults: make(chan *BuildResult, 1000),
cycleDetector: cycleDetector{graph: graph},
originalTargets: NewTargetSet(),
},
initOnce: new(sync.Once),
preloadDownloadOnce: new(sync.Once),
Expand Down
6 changes: 4 additions & 2 deletions src/core/state_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,8 @@ func TestExpandVisibleOriginalTargets(t *testing.T) {

func TestExpandOriginalSubLabels(t *testing.T) {
state := NewDefaultBuildState()
state.AddOriginalTarget(BuildLabel{PackageName: "src/core", Name: "..."}, true)
Copy link
Collaborator Author

@peterebden peterebden Nov 2, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we ever actually needed to support this. The new implementation just panics but things like plz test //... are still fine - those are turned into a sequence of :all targets elsewhere.

state.AddOriginalTarget(BuildLabel{PackageName: "src/core", Name: "all"}, true)
state.AddOriginalTarget(BuildLabel{PackageName: "src/core/tests", Name: "all"}, true)
state.Include = []string{"go"}
state.Exclude = []string{"py"}
addTarget(state, "//src/core:target1", "go")
Expand All @@ -76,7 +77,8 @@ func TestExpandOriginalSubLabels(t *testing.T) {
func TestExpandOriginalLabelsOrdering(t *testing.T) {
state := NewDefaultBuildState()
state.AddOriginalTarget(BuildLabel{PackageName: "src/parse", Name: "parse"}, true)
state.AddOriginalTarget(BuildLabel{PackageName: "src/core", Name: "..."}, true)
state.AddOriginalTarget(BuildLabel{PackageName: "src/core", Name: "all"}, true)
state.AddOriginalTarget(BuildLabel{PackageName: "src/core/tests", Name: "all"}, true)
state.AddOriginalTarget(BuildLabel{PackageName: "src/build", Name: "build"}, true)
addTarget(state, "//src/core:target1", "go")
addTarget(state, "//src/core:target2", "py")
Expand Down
63 changes: 63 additions & 0 deletions src/core/target_set.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package core

import (
"sync"
)

// A TargetSet contains a series of targets and supports efficiently checking for membership
// The zero value is not safe for use.
type TargetSet struct {
targets map[BuildLabel]struct{}
packages map[packageKey]struct{}
everything []BuildLabel
mutex sync.RWMutex
}

// NewTargetSet returns a new TargetSet.
func NewTargetSet() *TargetSet {
return &TargetSet{
targets: map[BuildLabel]struct{}{},
packages: map[packageKey]struct{}{},
}
}

// Add adds a new target to this set.
func (ts *TargetSet) Add(label BuildLabel) {
ts.mutex.Lock()
defer ts.mutex.Unlock()
if label.IsAllSubpackages() {
panic("TargetSet doesn't support ... labels")
} else if label.IsAllTargets() {
ts.packages[label.packageKey()] = struct{}{}
} else {
ts.targets[label] = struct{}{}
}
ts.everything = append(ts.everything, label)
}

// Match checks if this label is covered by the set (either explicitly or via :all)
// The first return checks if it's matched, the second if the match was exact or not.
func (ts *TargetSet) Match(label BuildLabel) (bool, bool) {
ts.mutex.RLock()
defer ts.mutex.RUnlock()
if _, present := ts.targets[label]; present {
return true, true
}
_, present := ts.packages[label.packageKey()]
return present, false
}

// MatchExact checks if this label was explicitly added to the set (i.e. :all doesn't count)
func (ts *TargetSet) MatchExact(label BuildLabel) bool {
ts.mutex.RLock()
defer ts.mutex.RUnlock()
_, present := ts.targets[label]
return present
}

// AllTargets returns a copy of the set of targets
func (ts *TargetSet) AllTargets() []BuildLabel {
ts.mutex.RLock()
defer ts.mutex.RUnlock()
return ts.everything[:]
}
53 changes: 53 additions & 0 deletions src/core/target_set_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package core

import (
"testing"

"github.com/stretchr/testify/assert"
)

func TestTargetSetMatch(t *testing.T) {
ts := NewTargetSet()
ts.Add(ParseBuildLabel("//src/core:core", ""))
ts.Add(ParseBuildLabel("//src/parse:all", ""))
matched, exact := ts.Match(ParseBuildLabel("//src/core:core", ""))
assert.True(t, matched)
assert.True(t, exact)
matched, exact = ts.Match(ParseBuildLabel("//src/core:core_test", ""))
assert.False(t, matched)
assert.False(t, exact)
matched, exact = ts.Match(ParseBuildLabel("//src/parse:parse", ""))
assert.True(t, matched)
assert.False(t, exact)
matched, exact = ts.Match(ParseBuildLabel("//src/parse:parse_test", ""))
assert.True(t, matched)
assert.False(t, exact)
matched, exact = ts.Match(ParseBuildLabel("//src/build", ""))
assert.False(t, matched)
assert.False(t, exact)
}

func TestTargetSetMatchExact(t *testing.T) {
ts := NewTargetSet()
ts.Add(ParseBuildLabel("//src/core:core", ""))
ts.Add(ParseBuildLabel("//src/parse:all", ""))
assert.True(t, ts.MatchExact(ParseBuildLabel("//src/core:core", "")))
assert.False(t, ts.MatchExact(ParseBuildLabel("//src/core:core_test", "")))
assert.False(t, ts.MatchExact(ParseBuildLabel("//src/parse:parse", "")))
assert.False(t, ts.MatchExact(ParseBuildLabel("//src/parse:parse_test", "")))
assert.False(t, ts.MatchExact(ParseBuildLabel("//src/build", "")))
}

func TestAllTargets(t *testing.T) {
ts := NewTargetSet()
labels := []BuildLabel{
ParseBuildLabel("//src/core:core", ""),
ParseBuildLabel("//src/core:core_test", ""),
ParseBuildLabel("//src/parse:all", ""),
ParseBuildLabel("//src/parse:parse_test", ""),
}
for _, label := range labels {
ts.Add(label)
}
assert.Equal(t, labels, ts.AllTargets())
}
Loading