diff --git a/adapters/cobra/symlink_list.go b/adapters/cobra/symlink_list.go index d601543..ccc6b08 100644 --- a/adapters/cobra/symlink_list.go +++ b/adapters/cobra/symlink_list.go @@ -64,7 +64,9 @@ func SymlinkListCmd(app ctl.Application) *cobra.Command { args = []string{filepath.Join(wollemi.GoSrcPath(), "...")} } - wollemi.SymlinkList(name, broken, prune, exclude, args) + if err := wollemi.SymlinkList(name, broken, prune, exclude, args); err != nil { + return err + } return nil }, diff --git a/domain/wollemi/service.go b/domain/wollemi/service.go index c992f2a..d9bce4d 100644 --- a/domain/wollemi/service.go +++ b/domain/wollemi/service.go @@ -54,19 +54,32 @@ type Service struct { goFormat *goFormat } +func (this *Service) validateAbsolutePaths(paths []string) error { + invalidPaths := make([]string, 0, len(paths)) + for _, path := range paths { + if filepath.IsAbs(path) && !strings.HasPrefix(path, this.root) { + invalidPaths = append(invalidPaths, path) + } + } + if len(invalidPaths) > 0 { + return fmt.Errorf("absolute paths %q are not under the plz repo root %q", invalidPaths, this.root) + } + return nil +} + func (this *Service) normalizePaths(paths []string) []string { if len(paths) == 0 { paths = []string{"..."} } - var relpath string - - if this.wd != this.root && strings.HasPrefix(this.wd, this.root) { - relpath = strings.TrimPrefix(this.wd, this.root+"/") - - for i, path := range paths { - paths[i] = filepath.Join(relpath, strings.TrimSuffix(path, "/")) + for i, path := range paths { + if !filepath.IsAbs(path) { + path = filepath.Join(this.wd, path) + } + if strings.HasPrefix(path, this.root) { + path, _ = filepath.Rel(this.root, path) } + paths[i] = path } return paths diff --git a/domain/wollemi/service_format.go b/domain/wollemi/service_format.go index 3a358a1..331b925 100644 --- a/domain/wollemi/service_format.go +++ b/domain/wollemi/service_format.go @@ -385,6 +385,10 @@ func (this *Service) isInternal(path string) bool { } func (this *Service) GoFormat(config wollemi.Config, paths []string) error { + if err := this.validateAbsolutePaths(paths); err != nil { + return err + } + this.goFormat = newGoFormat(this.normalizePaths(paths)) defer this.goFormat.resolveLimiter.Close() diff --git a/domain/wollemi/service_format_test.go b/domain/wollemi/service_format_test.go index 88d128d..a77c16f 100644 --- a/domain/wollemi/service_format_test.go +++ b/domain/wollemi/service_format_test.go @@ -29,6 +29,11 @@ const ( gopkg = "github.com/example" ) +var ( + root = filepath.Join(gosrc, gopkg) + wd = root +) + func (t *ServiceSuite) TestService_GoFormat() { type T = ServiceSuite @@ -1542,6 +1547,150 @@ func (t *ServiceSuite) TestService_GoFormat() { }, }, }, + }, { // TEST_CASE ------------------------------------------------------------- + Title: "accepts absolute paths when in the root", + Data: &GoFormatTestData{ + Gosrc: gosrc, + Gopkg: gopkg, + Root: "/root", + Wd: "/root", + Paths: []string{"/root/app"}, + Parse: t.WithThirdPartyGo(nil), + ImportDir: map[string]*golang.Package{ + "app": { + Name: "main", + GoFiles: []string{"main.go"}, + GoFileImports: map[string][]string{ + "main.go": { + "github.com/spf13/cobra", + }, + }, + }, + }, + Write: map[string]*please.BuildFile{ + "app/BUILD.plz": { + Stmt: []please.Expr{ + please.NewCallExpr("go_binary", []please.Expr{ + please.NewAssignExpr("=", "name", "app"), + please.NewAssignExpr("=", "srcs", please.NewGlob([]string{"*.go"}, "*_test.go")), + please.NewAssignExpr("=", "visibility", []string{"PUBLIC"}), + please.NewAssignExpr("=", "deps", []string{ + "//third_party/go/github.com/spf13:cobra", + }), + }), + }, + }, + }, + }, + }, { // TEST_CASE ------------------------------------------------------------- + Title: "accepts absolute paths when in a child of the root", + Data: &GoFormatTestData{ + Gosrc: gosrc, + Gopkg: gopkg, + Root: "/root", + Wd: "/root/wd", + Paths: []string{"/root/wd/app"}, + Parse: t.WithThirdPartyGo(nil), + ImportDir: map[string]*golang.Package{ + "wd/app": { + Name: "main", + GoFiles: []string{"main.go"}, + GoFileImports: map[string][]string{ + "main.go": { + "github.com/spf13/cobra", + }, + }, + }, + }, + Write: map[string]*please.BuildFile{ + "wd/app/BUILD.plz": { + Stmt: []please.Expr{ + please.NewCallExpr("go_binary", []please.Expr{ + please.NewAssignExpr("=", "name", "app"), + please.NewAssignExpr("=", "srcs", please.NewGlob([]string{"*.go"}, "*_test.go")), + please.NewAssignExpr("=", "visibility", []string{"PUBLIC"}), + please.NewAssignExpr("=", "deps", []string{ + "//third_party/go/github.com/spf13:cobra", + }), + }), + }, + }, + }, + }, + }, { // TEST_CASE ------------------------------------------------------------- + Title: "accepts relative paths when in a child of the root", + Data: &GoFormatTestData{ + Gosrc: gosrc, + Gopkg: gopkg, + Root: "/root", + Wd: "/root/wd", + Paths: []string{"app"}, + Parse: t.WithThirdPartyGo(nil), + ImportDir: map[string]*golang.Package{ + "wd/app": { + Name: "main", + GoFiles: []string{"main.go"}, + GoFileImports: map[string][]string{ + "main.go": { + "github.com/spf13/cobra", + }, + }, + }, + }, + Write: map[string]*please.BuildFile{ + "wd/app/BUILD.plz": { + Stmt: []please.Expr{ + please.NewCallExpr("go_binary", []please.Expr{ + please.NewAssignExpr("=", "name", "app"), + please.NewAssignExpr("=", "srcs", please.NewGlob([]string{"*.go"}, "*_test.go")), + please.NewAssignExpr("=", "visibility", []string{"PUBLIC"}), + please.NewAssignExpr("=", "deps", []string{ + "//third_party/go/github.com/spf13:cobra", + }), + }), + }, + }, + }, + }, + }, { // TEST_CASE ------------------------------------------------------------- + Title: "reads config from directory when absolute path given", + Config: wollemi.Config{}, + Data: &GoFormatTestData{ + Gosrc: gosrc, + Gopkg: gopkg, + Root: "/root", + Wd: "/root", + Config: map[string]wollemi.Config{ + "parent/app": {KnownDependency: map[string]string{ + "github.com/example/dep": "//third_party/go/github.com/example/override"}}, + }, + Paths: []string{"/root/parent/app"}, + ImportDir: map[string]*golang.Package{ + "parent/app": { + Name: "main", + GoFiles: []string{"main.go"}, + GoFileImports: map[string][]string{ + "main.go": { + "github.com/example/dep", + }, + }, + }, + }, + Write: map[string]*please.BuildFile{ + "parent/app/BUILD.plz": { + Stmt: []please.Expr{ + please.NewCallExpr("go_binary", []please.Expr{ + please.NewAssignExpr("=", "name", "app"), + please.NewAssignExpr("=", "srcs", please.NewGlob([]string{"*.go"}, "*_test.go")), + please.NewAssignExpr("=", "visibility", []string{"PUBLIC"}), + please.NewAssignExpr("=", "deps", []string{ + "//third_party/go/github.com/example/override", + }), + }), + }, + }, + }, + }, }} { focus := "" @@ -1554,7 +1703,15 @@ func (t *ServiceSuite) TestService_GoFormat() { t.MockGoFormat(tt.Data, write) - wollemi := t.New(tt.Data.Gosrc, tt.Data.Gopkg) + root := root + if tt.Data.Root != "" { + root = tt.Data.Root + } + wd := wd + if tt.Data.Wd != "" { + wd = tt.Data.Wd + } + wollemi := t.New(root, wd, tt.Data.Gosrc, tt.Data.Gopkg) require.NoError(t, wollemi.GoFormat(tt.Config, tt.Data.Paths)) close(write) @@ -1572,6 +1729,22 @@ func (t *ServiceSuite) TestService_GoFormat() { } }) } + + t.It("returns an error if given an absolute path which is not under the repo root", func(t *T) { + w := t.New(root, wd, gosrc, gopkg) + + var ( + config = wollemi.Config{} + paths = []string{ + filepath.Join(root, "subdir"), + "/outside/of/root", + } + ) + + err := w.GoFormat(config, paths) + + assert.Error(t, err) + }) } func (t *ServiceSuite) MockGoFormat(data *GoFormatTestData, write chan please.File) { @@ -1706,6 +1879,8 @@ func (t *ServiceSuite) MockGoFormat(data *GoFormatTestData, write chan please.Fi type GoFormatTestData struct { Gosrc string Gopkg string + Root string + Wd string Paths []string Config map[string]wollemi.Config ImportDir map[string]*golang.Package diff --git a/domain/wollemi/service_rules_unused.go b/domain/wollemi/service_rules_unused.go index e379579..caa1926 100644 --- a/domain/wollemi/service_rules_unused.go +++ b/domain/wollemi/service_rules_unused.go @@ -11,6 +11,10 @@ import ( ) func (this *Service) RulesUnused(prune bool, kinds, paths, exclude []string) error { + if err := this.validateAbsolutePaths(paths); err != nil { + return err + } + graph, err := this.please.Graph() if err != nil { return err diff --git a/domain/wollemi/service_rules_unused_test.go b/domain/wollemi/service_rules_unused_test.go index 58a3363..7dd2204 100644 --- a/domain/wollemi/service_rules_unused_test.go +++ b/domain/wollemi/service_rules_unused_test.go @@ -3,6 +3,7 @@ package wollemi_test import ( "bytes" "os" + "path/filepath" "testing" "github.com/stretchr/testify/assert" @@ -18,15 +19,10 @@ func TestService_RulesUnused(t *testing.T) { func (t *ServiceSuite) TestService_RulesUnused() { type T = ServiceSuite - const ( - gopkg = "github.com/wollemi_test" - gosrc = "/go/src" - ) - t.It("can list unused build rules", func(t *T) { t.MockRulesUnused() - wollemi := t.New(gosrc, gopkg) + wollemi := t.New(root, wd, gosrc, gopkg) var ( prune bool @@ -106,7 +102,7 @@ func (t *ServiceSuite) TestService_RulesUnused() { expect.Equal(t, want, have) }) - wollemi := t.New(gosrc, gopkg) + wollemi := t.New(root, wd, gosrc, gopkg) var ( prune bool = true @@ -117,6 +113,24 @@ func (t *ServiceSuite) TestService_RulesUnused() { wollemi.RulesUnused(prune, kinds, paths, excludePaths) }) + + t.It("returns an error if given an absolute path which is not under the repo root", func(t *T) { + wollemi := t.New(root, wd, gosrc, gopkg) + + var ( + prune bool = true + kinds []string + paths = []string{ + filepath.Join(root, "subdir"), + "/outside/of/root", + } + excludePaths []string + ) + + err := wollemi.RulesUnused(prune, kinds, paths, excludePaths) + + assert.Error(t, err) + }) } func (t *ServiceSuite) MockRulesUnused() { diff --git a/domain/wollemi/service_suite_test.go b/domain/wollemi/service_suite_test.go index fa7b1fe..9408ee3 100644 --- a/domain/wollemi/service_suite_test.go +++ b/domain/wollemi/service_suite_test.go @@ -2,7 +2,6 @@ package wollemi_test import ( "fmt" - "path/filepath" "strings" "sync" "testing" @@ -10,9 +9,9 @@ import ( "github.com/golang/mock/gomock" "github.com/tcncloud/wollemi/domain/wollemi" - "github.com/tcncloud/wollemi/ports/golang/mock" - "github.com/tcncloud/wollemi/ports/please/mock" - "github.com/tcncloud/wollemi/ports/wollemi/mock" + mock_golang "github.com/tcncloud/wollemi/ports/golang/mock" + mock_please "github.com/tcncloud/wollemi/ports/please/mock" + mock_wollemi "github.com/tcncloud/wollemi/ports/wollemi/mock" "github.com/tcncloud/wollemi/testdata/mem" ) @@ -61,14 +60,14 @@ func (suite *ServiceSuite) Run(name string, yield func(*ServiceSuite)) { }) } -func (suite *ServiceSuite) New(gosrc, gopkg string) *wollemi.Service { +func (suite *ServiceSuite) New(root, wd, gosrc, gopkg string) *wollemi.Service { return wollemi.New( suite.logger, suite.filesystem, suite.golang, suite.please, - filepath.Join(gosrc, gopkg), - filepath.Join(gosrc, gopkg), + root, + wd, gosrc, gopkg, ) diff --git a/domain/wollemi/service_symlink_go_path.go b/domain/wollemi/service_symlink_go_path.go index f0f8162..e0f6d62 100644 --- a/domain/wollemi/service_symlink_go_path.go +++ b/domain/wollemi/service_symlink_go_path.go @@ -12,6 +12,10 @@ import ( ) func (this *Service) SymlinkGoPath(force bool, paths []string) error { + if err := this.validateAbsolutePaths(paths); err != nil { + return err + } + paths = this.normalizePaths(paths) deps, err := this.please.QueryDeps(paths...) diff --git a/domain/wollemi/service_symlink_go_path_test.go b/domain/wollemi/service_symlink_go_path_test.go index da420f0..19f5263 100644 --- a/domain/wollemi/service_symlink_go_path_test.go +++ b/domain/wollemi/service_symlink_go_path_test.go @@ -37,9 +37,6 @@ func (t *ServiceSuite) TestService_SymlinkGoPath() { "//ports/wollemi:wollemi", } - gopkg := "github.com/wollemi_test" - gosrc := "/go/src" - goSrcPath := func(elems ...string) string { return filepath.Join(gosrc, filepath.Join(elems...)) } @@ -155,7 +152,7 @@ func (t *ServiceSuite) TestService_SymlinkGoPath() { t.DefaultMocks() - wollemi := t.New(gosrc, gopkg) + wollemi := t.New(root, wd, gosrc, gopkg) var ( force = false @@ -167,6 +164,22 @@ func (t *ServiceSuite) TestService_SymlinkGoPath() { wollemi.SymlinkGoPath(force, paths) }) + + t.It("returns an error if given an absolute path which is not under the repo root", func(t *T) { + wollemi := t.New(root, wd, gosrc, gopkg) + + var ( + force = false + paths = []string{ + filepath.Join(root, "subdir"), + "/outside/of/root", + } + ) + + err := wollemi.SymlinkGoPath(force, paths) + + assert.Error(t, err) + }) } func ignoreSkipDir(err error) error { diff --git a/domain/wollemi/service_symlink_list.go b/domain/wollemi/service_symlink_list.go index 434a80f..6bddeeb 100644 --- a/domain/wollemi/service_symlink_list.go +++ b/domain/wollemi/service_symlink_list.go @@ -8,7 +8,10 @@ import ( "github.com/tcncloud/wollemi/ports/please" ) -func (this *Service) SymlinkList(name string, broken, prune bool, exclude, include []string) { +func (this *Service) SymlinkList(name string, broken, prune bool, exclude, include []string) error { + if err := this.validateAbsolutePaths(include); err != nil { + return err + } include = this.normalizePaths(include) for _, targetPath := range include { @@ -101,4 +104,5 @@ func (this *Service) SymlinkList(name string, broken, prune bool, exclude, inclu Warn("could not walk") } } + return nil } diff --git a/domain/wollemi/service_symlink_list_test.go b/domain/wollemi/service_symlink_list_test.go index b0f272b..a89e5c2 100644 --- a/domain/wollemi/service_symlink_list_test.go +++ b/domain/wollemi/service_symlink_list_test.go @@ -7,6 +7,7 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "github.com/tcncloud/wollemi/ports/golang" "github.com/tcncloud/wollemi/testdata/please" @@ -19,11 +20,6 @@ func TestService_SymlinkList(t *testing.T) { func (t *ServiceSuite) TestService_SymlinkList() { type T = ServiceSuite - const ( - gopkg = "github.com/wollemi_test" - gosrc = "/go/src" - ) - t.It("can list all project symlinks", func(t *T) { data := t.GoSymlinkTestData() @@ -55,7 +51,7 @@ func (t *ServiceSuite) TestService_SymlinkList() { return filepath.Join(gosrc, gopkg, s), nil }) - wollemi := t.New(gosrc, gopkg) + wollemi := t.New(root, wd, gosrc, gopkg) var ( name = "*" @@ -65,7 +61,8 @@ func (t *ServiceSuite) TestService_SymlinkList() { include []string ) - wollemi.SymlinkList(name, broken, prune, exclude, include) + err := wollemi.SymlinkList(name, broken, prune, exclude, include) + require.NoError(t, err) want := []map[string]interface{}{ map[string]interface{}{ @@ -147,7 +144,7 @@ func (t *ServiceSuite) TestService_SymlinkList() { return info, nil }) - wollemi := t.New(gosrc, gopkg) + wollemi := t.New(root, wd, gosrc, gopkg) var ( name = "*" @@ -157,7 +154,8 @@ func (t *ServiceSuite) TestService_SymlinkList() { include []string ) - wollemi.SymlinkList(name, broken, prune, exclude, include) + err := wollemi.SymlinkList(name, broken, prune, exclude, include) + require.NoError(t, err) want := []map[string]interface{}{ map[string]interface{}{ @@ -212,7 +210,7 @@ func (t *ServiceSuite) TestService_SymlinkList() { return filepath.Join(gosrc, gopkg, s), nil }) - wollemi := t.New(gosrc, gopkg) + wollemi := t.New(root, wd, gosrc, gopkg) var ( name = "*.mg.go" @@ -222,7 +220,8 @@ func (t *ServiceSuite) TestService_SymlinkList() { include []string ) - wollemi.SymlinkList(name, broken, prune, exclude, include) + err := wollemi.SymlinkList(name, broken, prune, exclude, include) + require.NoError(t, err) want := []map[string]interface{}{ map[string]interface{}{ @@ -292,7 +291,7 @@ func (t *ServiceSuite) TestService_SymlinkList() { t.filesystem.EXPECT().Remove("app/protos/service.pb.go") - wollemi := t.New(gosrc, gopkg) + wollemi := t.New(root, wd, gosrc, gopkg) var ( name = "*.pb.go" @@ -302,7 +301,8 @@ func (t *ServiceSuite) TestService_SymlinkList() { include []string ) - wollemi.SymlinkList(name, broken, prune, exclude, include) + err := wollemi.SymlinkList(name, broken, prune, exclude, include) + require.NoError(t, err) want := []map[string]interface{}{ map[string]interface{}{ @@ -361,7 +361,7 @@ func (t *ServiceSuite) TestService_SymlinkList() { return filepath.Join(gosrc, gopkg, s), nil }) - wollemi := t.New(gosrc, gopkg) + wollemi := t.New(root, wd, gosrc, gopkg) var ( name = "*" @@ -373,7 +373,8 @@ func (t *ServiceSuite) TestService_SymlinkList() { include []string ) - wollemi.SymlinkList(name, broken, prune, exclude, include) + err := wollemi.SymlinkList(name, broken, prune, exclude, include) + require.NoError(t, err) want := []map[string]interface{}{ map[string]interface{}{ @@ -396,6 +397,27 @@ func (t *ServiceSuite) TestService_SymlinkList() { assert.ElementsMatch(t, want, t.logger.Lines()) }) + + t.It("returns an error if given an absolute path which is not under the repo root", func(t *T) { + wollemi := t.New(root, wd, gosrc, gopkg) + + var ( + name = "*" + broken bool + prune bool + exclude []string = []string{ + "app/protos/mock", + } + include = []string{ + filepath.Join(root, "subdir"), + "/outside/of/root", + } + ) + + err := wollemi.SymlinkList(name, broken, prune, exclude, include) + + assert.Error(t, err) + }) } func (t *ServiceSuite) GoSymlinkTestData() *GoFormatTestData { diff --git a/ports/ctl/wollemi.go b/ports/ctl/wollemi.go index 85560a6..d235459 100644 --- a/ports/ctl/wollemi.go +++ b/ports/ctl/wollemi.go @@ -15,7 +15,7 @@ type Wollemi interface { GoFormat(wollemi.Config, []string) error GoPkgPath(...string) string GoSrcPath(...string) string - SymlinkList(string, bool, bool, []string, []string) + SymlinkList(string, bool, bool, []string, []string) error SymlinkGoPath(bool, []string) error RulesUnused(bool, []string, []string, []string) error } diff --git a/third_party/go/github.com/golang/mock/BUILD.plz b/third_party/go/github.com/golang/mock/BUILD.plz index 778524e..f5a10f9 100644 --- a/third_party/go/github.com/golang/mock/BUILD.plz +++ b/third_party/go/github.com/golang/mock/BUILD.plz @@ -1,7 +1,11 @@ +package(default_visibility = ["PUBLIC"]) + +GOMOCK_VERSION = "1.6.0" + mock_download = remote_file( name = "mockgen", _tag = "download", - url = "https://github.com/golang/mock/releases/download/v1.4.3/mock_1.4.3_" + CONFIG.HOSTOS + "_" + CONFIG.HOSTARCH + ".tar.gz", + url = f"https://github.com/golang/mock/releases/download/v{GOMOCK_VERSION}/mock_{GOMOCK_VERSION}_" + CONFIG.HOSTOS + "_" + CONFIG.HOSTARCH + ".tar.gz", ) build_rule( @@ -27,6 +31,5 @@ go_module( "ci", "sample", ], - version = "v1.6.0", - visibility = ["PUBLIC"], + version = f"v{GOMOCK_VERSION}", )