Skip to content

Commit 1c58d75

Browse files
authored
Ensure deterministic order for filtered public imports (#3734)
1 parent 5a1458c commit 1c58d75

File tree

6 files changed

+372
-72
lines changed

6 files changed

+372
-72
lines changed

private/bufpkg/bufimage/bufimageutil/bufimageutil.go

Lines changed: 5 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -266,9 +266,9 @@ func StripSourceRetentionOptions(image bufimage.Image) (bufimage.Image, error) {
266266
type transitiveClosure struct {
267267
// The elements included in the transitive closure.
268268
elements map[namedDescriptor]closureInclusionMode
269-
// The ordered set of imports for each file. This allows for re-writing imports
269+
// The set of imports for each file. This allows for re-writing imports
270270
// for files whose contents have been pruned.
271-
imports map[string]*orderedImports
271+
imports map[string]map[string]struct{}
272272
}
273273

274274
type closureInclusionMode int
@@ -295,7 +295,7 @@ const (
295295
func newTransitiveClosure() *transitiveClosure {
296296
return &transitiveClosure{
297297
elements: map[namedDescriptor]closureInclusionMode{},
298-
imports: map[string]*orderedImports{},
298+
imports: map[string]map[string]struct{}{},
299299
}
300300
}
301301

@@ -414,10 +414,10 @@ func (t *transitiveClosure) addImport(fromPath, toPath string) {
414414
}
415415
imps := t.imports[fromPath]
416416
if imps == nil {
417-
imps = newOrderedImports()
417+
imps = make(map[string]struct{}, 2)
418418
t.imports[fromPath] = imps
419419
}
420-
imps.add(toPath)
420+
imps[toPath] = struct{}{}
421421
}
422422

423423
func (t *transitiveClosure) addElement(
@@ -1066,60 +1066,3 @@ func stripSourceRetentionOptionsFromFile(imageFile bufimage.ImageFile) (bufimage
10661066
imageFile.UnusedDependencyIndexes(),
10671067
)
10681068
}
1069-
1070-
// orderedImports is a structure to maintain an ordered set of imports. This is needed
1071-
// because we want to be able to iterate through imports in a deterministic way when filtering
1072-
// the image.
1073-
type orderedImports struct {
1074-
pathToIndex map[string]int
1075-
paths []string
1076-
}
1077-
1078-
// newOrderedImports creates a new orderedImports structure.
1079-
func newOrderedImports() *orderedImports {
1080-
return &orderedImports{
1081-
pathToIndex: map[string]int{},
1082-
}
1083-
}
1084-
1085-
// index returns the index for a given path. If the path does not exist in index map, -1
1086-
// is returned and should be considered deleted.
1087-
func (o *orderedImports) index(path string) int {
1088-
if index, ok := o.pathToIndex[path]; ok {
1089-
return index
1090-
}
1091-
return -1
1092-
}
1093-
1094-
// add appends a path to the paths list and the index in the map. If a key already exists,
1095-
// then this is a no-op.
1096-
func (o *orderedImports) add(path string) {
1097-
if _, ok := o.pathToIndex[path]; !ok {
1098-
o.pathToIndex[path] = len(o.paths)
1099-
o.paths = append(o.paths, path)
1100-
}
1101-
}
1102-
1103-
// delete removes a key from the index map of ordered imports. If a non-existent path is
1104-
// set for deletion, then this is a no-op.
1105-
// Note that the path is not removed from the paths list. If you want to iterate through
1106-
// the paths, use keys() to get all non-deleted keys.
1107-
func (o *orderedImports) delete(path string) {
1108-
delete(o.pathToIndex, path)
1109-
}
1110-
1111-
// keys provides all non-deleted keys from the ordered imports.
1112-
func (o *orderedImports) keys() []string {
1113-
keys := make([]string, 0, len(o.pathToIndex))
1114-
for _, path := range o.paths {
1115-
if _, ok := o.pathToIndex[path]; ok {
1116-
keys = append(keys, path)
1117-
}
1118-
}
1119-
return keys
1120-
}
1121-
1122-
// len returns the number of paths in the ordered imports.
1123-
func (o *orderedImports) len() int {
1124-
return len(o.pathToIndex)
1125-
}

private/bufpkg/bufimage/bufimageutil/bufimageutil_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -494,6 +494,10 @@ func TestDependencies(t *testing.T) {
494494
t.Parallel()
495495
runDiffTest(t, "testdata/deps", "test.EnumA.txtar", WithIncludeTypes("test.EnumA"))
496496
})
497+
t.Run("PublicOrder", func(t *testing.T) {
498+
t.Parallel()
499+
runDiffTest(t, "testdata/deps", "test.PublicOrder.txtar", WithIncludeTypes("test.PublicOrder"))
500+
})
497501
}
498502

499503
func getImage(ctx context.Context, logger *slog.Logger, testdataDir string, options ...bufimage.BuildImageOption) (storage.ReadWriteBucket, bufimage.Image, error) {

private/bufpkg/bufimage/bufimageutil/image_filter.go

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ package bufimageutil
1717
import (
1818
"fmt"
1919
"slices"
20+
"sort"
2021
"strings"
2122

2223
"github.com/bufbuild/buf/private/bufpkg/bufimage"
@@ -240,12 +241,9 @@ func (b *sourcePathsBuilder) remapDependencies(
240241

241242
// Check if the imports need to be remapped.
242243
importsRequired := b.closure.imports[fileDescriptor.GetName()]
243-
importsCount := 0
244-
if importsRequired != nil {
245-
importsCount = importsRequired.len()
246-
}
244+
importsCount := len(importsRequired)
247245
for _, importPath := range dependencies {
248-
if importsRequired != nil && importsRequired.index(importPath) != -1 {
246+
if _, ok := importsRequired[importPath]; ok {
249247
importsCount--
250248
} else {
251249
importsCount = -1
@@ -266,26 +264,31 @@ func (b *sourcePathsBuilder) remapDependencies(
266264
dependencyChanges := make([]int32, len(dependencies))
267265
for _, importPath := range dependencies {
268266
path := append(dependencyPath, indexFrom)
269-
if importsRequired != nil && importsRequired.index(importPath) != -1 {
267+
if _, ok := importsRequired[importPath]; ok {
270268
dependencyChanges[indexFrom] = indexTo
271269
if indexTo != indexFrom {
272270
sourcePathsRemap.markMoved(path, indexTo)
273271
}
274272
newDependencies = append(newDependencies, importPath)
275273
indexTo++
276274
// delete them as we go, so we know which ones weren't in the list
277-
importsRequired.delete(importPath)
275+
delete(importsRequired, importPath)
278276
} else {
279277
sourcePathsRemap.markDeleted(path)
280278
dependencyChanges[indexFrom] = -1
281279
}
282280
indexFrom++
283281
}
284-
if importsRequired != nil {
285-
newDependencies = append(newDependencies, importsRequired.keys()...)
282+
// Add imports picked up via a public import. The filtered files do not use public imports.
283+
if publicImportCount := len(importsRequired); publicImportCount > 0 {
284+
for importPath := range importsRequired {
285+
newDependencies = append(newDependencies, importPath)
286+
}
287+
// Sort the public imports to ensure the output is deterministic.
288+
sort.Strings(newDependencies[len(newDependencies)-publicImportCount:])
286289
}
287290

288-
// Pulbic dependencies are always removed on remapping.
291+
// Public dependencies are always removed on remapping.
289292
publicDependencyPath := append(sourcePath, filePublicDependencyTag)
290293
sourcePathsRemap.markDeleted(publicDependencyPath)
291294

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
syntax = "proto3";
2+
package old;
3+
4+
import public "a.proto";
5+
import public "b.proto";
6+
import public "c.proto";
7+
8+
message Old {}

0 commit comments

Comments
 (0)