Skip to content

Commit 5b37673

Browse files
authored
Fix problem with input dir not detecting controlling workspace in certain cases (#3233)
1 parent 915ae13 commit 5b37673

File tree

9 files changed

+161
-7
lines changed

9 files changed

+161
-7
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
## [Unreleased]
44

55
- Add `--http3` flag to `buf curl` which forces `buf curl` to use HTTP/3 as the transport.
6+
- Fix issue with directory inputs for v2 workspaces where the specified directory was not itself
7+
a path to a module, but contained directories with modules, and the modules would not build.
68
- Stop creating empty `buf.lock` files when `buf dep update` does not find new dependencies
79
to update and there is no existing `buf.lock`.
810

private/buf/buftarget/terminate.go

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,8 @@ type TerminateFunc func(
3333
originalInputPath string,
3434
) (ControllingWorkspace, error)
3535

36-
// TerminateAtControllingWorkspace implements a TerminateFunc and returns controlling workspace
37-
// if one is found at the given prefix.
36+
// TerminateAtControllingWorkspace implements a TerminateFunc and returns the workspace controlling
37+
// the input, if one is found at the given prefix.
3838
func TerminateAtControllingWorkspace(
3939
ctx context.Context,
4040
bucket storage.ReadBucket,
@@ -77,28 +77,51 @@ func terminateAtControllingWorkspace(
7777
// This isn't actually the external directory path, but we do the best we can here for now.
7878
return nil, fmt.Errorf("cannot have a buf.work.yaml and buf.yaml in the same directory %q", prefix)
7979
}
80-
relDirPath, err := normalpath.Rel(prefix, originalInputPath)
80+
relInputPath, err := normalpath.Rel(prefix, originalInputPath)
8181
if err != nil {
8282
return nil, err
8383
}
8484
if bufYAMLExists && bufYAMLFile.FileVersion() == bufconfig.FileVersionV2 {
85+
// A input directory with a v2 buf.yaml is the controlling workspace for itself.
8586
if prefix == originalInputPath {
8687
return newControllingWorkspace(prefix, nil, bufYAMLFile), nil
8788
}
8889
for _, moduleConfig := range bufYAMLFile.ModuleConfigs() {
89-
if normalpath.EqualsOrContainsPath(moduleConfig.DirPath(), relDirPath, normalpath.Relative) {
90+
// For a prefix/buf.yaml with:
91+
// version: v2
92+
// modules:
93+
// - path: foo
94+
// - path: dir/bar
95+
// - path: dir/baz
96+
// ...
97+
// - If the input is a module path (one of prefix/foo, prefix/dir/bar or prefix/dir/baz),
98+
// then the input is a module controlled by the workspace at prefix.
99+
// - If the input is inside one of the module DirPaths (e.g. prefix/foo/suffix or prefix/dir/bar/suffix)
100+
// we still consider prefix to be the workspace that controls the input. It is then up
101+
// to the caller to decide what to do with this information. For example, the caller could
102+
// say this is equivalent to input being prefix/foo with --path=prefix/foo/suffix specified,
103+
// or it could say this is invalid, or the caller is not be concerned with validity.
104+
if normalpath.EqualsOrContainsPath(moduleConfig.DirPath(), relInputPath, normalpath.Relative) {
105+
return newControllingWorkspace(prefix, nil, bufYAMLFile), nil
106+
}
107+
// Only in v2: if the input is not any of the module paths but contains a module path,
108+
// e.g. prefix/dir, we also consider prefix to be the controlling workspace, because in v2
109+
// an input is allowed to be a subset of a workspace's modules. In this example, input prefix/dir
110+
// is two modules, one at prefix/dir/bar and the other at prefix/dir/baz.
111+
if normalpath.EqualsOrContainsPath(relInputPath, moduleConfig.DirPath(), normalpath.Relative) {
90112
return newControllingWorkspace(prefix, nil, bufYAMLFile), nil
91113
}
92114
}
93115
}
94116
if bufWorkYAMLExists {
95-
// For v1 workspaces, we ensure that the module path list actually contains the original
96-
// input paths.
117+
// A input directory with a buf.work.yaml is the controlling workspace for itself.
97118
if prefix == originalInputPath {
98119
return newControllingWorkspace(prefix, bufWorkYAMLFile, nil), nil
99120
}
100121
for _, dirPath := range bufWorkYAMLFile.DirPaths() {
101-
if normalpath.EqualsOrContainsPath(dirPath, relDirPath, normalpath.Relative) {
122+
// Unlike v2 workspaces, we only check whether the input is a module path or is contained
123+
// in a module path.
124+
if normalpath.EqualsOrContainsPath(dirPath, relInputPath, normalpath.Relative) {
102125
return newControllingWorkspace(prefix, bufWorkYAMLFile, nil), nil
103126
}
104127
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
version: v2
2+
modules:
3+
- path: standalone
4+
- path: parent/foo
5+
- path: parent/nextlayer/bar
6+
- path: parent/nextlayer/baz
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package foo;
2+
3+
import "imported.proto";
4+
5+
message Foo {
6+
optional standalone.Imported i = 1;
7+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package bar;
2+
3+
import "foo.proto";
4+
5+
message Bar {
6+
optional foo.Foo foo = 1;
7+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package baz;
2+
3+
import "bar.proto";
4+
5+
message Baz {
6+
optional bar.Bar bar = 1;
7+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
package standalone;
2+
3+
message Imported {}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
package standalone;
2+
3+
message Standalone {}

private/buf/cmd/buf/workspace_test.go

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,17 @@ import (
2424
"testing"
2525

2626
"github.com/bufbuild/buf/private/buf/bufctl"
27+
"github.com/bufbuild/buf/private/buf/cmd/buf/internal/internaltesting"
2728
"github.com/bufbuild/buf/private/bufpkg/bufmodule"
29+
imagev1 "github.com/bufbuild/buf/private/gen/proto/go/buf/alpha/image/v1"
30+
"github.com/bufbuild/buf/private/pkg/app/appcmd"
31+
"github.com/bufbuild/buf/private/pkg/app/appcmd/appcmdtesting"
2832
"github.com/bufbuild/buf/private/pkg/osext"
33+
"github.com/bufbuild/buf/private/pkg/slicesext"
2934
"github.com/bufbuild/buf/private/pkg/storage/storagearchive"
3035
"github.com/bufbuild/buf/private/pkg/storage/storageos"
3136
"github.com/stretchr/testify/require"
37+
"google.golang.org/protobuf/proto"
3238
)
3339

3440
func TestWorkspaceDir(t *testing.T) {
@@ -1427,6 +1433,58 @@ func TestWorkspaceWithInvalidArchiveAbsolutePathFail(t *testing.T) {
14271433
)
14281434
}
14291435

1436+
func TestWorkspaceWithTargettingModuleCommonParentDir(t *testing.T) {
1437+
workspaceDir := filepath.Join("testdata", "workspace", "success", "shared_parent_dir")
1438+
requireBuildOutputFilePaths(
1439+
t,
1440+
map[string]expectedFileInfo{
1441+
"foo.proto": {},
1442+
"bar.proto": {},
1443+
"baz.proto": {},
1444+
"imported.proto": {},
1445+
"standalone.proto": {},
1446+
},
1447+
workspaceDir,
1448+
)
1449+
requireBuildOutputFilePaths(
1450+
t,
1451+
map[string]expectedFileInfo{
1452+
"imported.proto": {},
1453+
"standalone.proto": {},
1454+
},
1455+
filepath.Join(workspaceDir, "standalone"),
1456+
)
1457+
requireBuildOutputFilePaths(
1458+
t,
1459+
map[string]expectedFileInfo{
1460+
"foo.proto": {},
1461+
"bar.proto": {},
1462+
"baz.proto": {},
1463+
"imported.proto": {isImport: true},
1464+
},
1465+
filepath.Join(workspaceDir, "parent"),
1466+
)
1467+
requireBuildOutputFilePaths(
1468+
t,
1469+
map[string]expectedFileInfo{
1470+
"foo.proto": {isImport: true},
1471+
"bar.proto": {},
1472+
"baz.proto": {},
1473+
"imported.proto": {isImport: true},
1474+
},
1475+
filepath.Join(workspaceDir, "parent/nextlayer"),
1476+
)
1477+
requireBuildOutputFilePaths(
1478+
t,
1479+
map[string]expectedFileInfo{
1480+
"foo.proto": {isImport: true},
1481+
"bar.proto": {},
1482+
"imported.proto": {isImport: true},
1483+
},
1484+
filepath.Join(workspaceDir, "parent/nextlayer/bar"),
1485+
)
1486+
}
1487+
14301488
func createZipFromDir(t *testing.T, rootPath string, archiveName string) string {
14311489
zipDir := filepath.Join(t.TempDir(), rootPath)
14321490
require.NoError(t, os.MkdirAll(zipDir, 0755))
@@ -1466,3 +1524,41 @@ func createZipFromDir(t *testing.T, rootPath string, archiveName string) string
14661524
require.NoError(t, err)
14671525
return zipDir
14681526
}
1527+
1528+
type expectedFileInfo struct {
1529+
isImport bool
1530+
}
1531+
1532+
func requireBuildOutputFilePaths(t *testing.T, expectedFilePathToInfo map[string]expectedFileInfo, buildArgs ...string) {
1533+
stdout := bytes.NewBuffer(nil)
1534+
stderr := bytes.NewBuffer(nil)
1535+
appcmdtesting.RunCommandExitCode(
1536+
t,
1537+
func(use string) *appcmd.Command { return NewRootCommand(use) },
1538+
0,
1539+
internaltesting.NewEnvFunc(t),
1540+
nil,
1541+
stdout,
1542+
stderr,
1543+
append(
1544+
[]string{
1545+
"build",
1546+
"-o=-#format=binpb",
1547+
},
1548+
buildArgs...,
1549+
)...,
1550+
)
1551+
outputImage := &imagev1.Image{}
1552+
require.NoError(t, proto.Unmarshal(stdout.Bytes(), outputImage))
1553+
1554+
filesToCheck := slicesext.ToStructMap(slicesext.MapKeysToSlice(expectedFilePathToInfo))
1555+
1556+
for _, imageFile := range outputImage.File {
1557+
filePath := imageFile.GetName()
1558+
expectedFileInfo, ok := expectedFilePathToInfo[filePath]
1559+
require.Truef(t, ok, "unexpected file in the image built: %s", filePath)
1560+
require.Equal(t, expectedFileInfo.isImport, imageFile.BufExtension.GetIsImport())
1561+
delete(filesToCheck, filePath)
1562+
}
1563+
require.Zerof(t, len(filesToCheck), "expected files missing from image built: %v", slicesext.MapKeysToSortedSlice(filesToCheck))
1564+
}

0 commit comments

Comments
 (0)