Skip to content

Commit

Permalink
make gofmt standard rule kinds mappable to custom kinds
Browse files Browse the repository at this point in the history
  • Loading branch information
Brian Bice committed Mar 22, 2021
1 parent af78aa9 commit f242dc4
Show file tree
Hide file tree
Showing 6 changed files with 238 additions and 44 deletions.
88 changes: 62 additions & 26 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -283,32 +283,6 @@ Symlink all third party deps for specific package into the go path.
---

### Configuration
The behavior of the wollemi gofmt command can be altered through the definition
of `.wollemi.json` config files. The config file is expected to contain a json
object with four optional fields `default_visibility`,
`allow_unresolved_dependency`, `explicit_sources` and `known_dependency`. The
`default_visibility` field is a string which defines the default visibility to
be applied to all `go_library`, `go_test` and `go_binary` rules generated by the
gofmt command. The `allow_unresolved_dependency` field is a boolean which when
set allows the generation of go rules which have unresolved dependencies. When
this field is unset rules with unresolved dependencies will be logged as errors
and will not generated until fixed. The `explicit_sources` field causes wollemi
to force each parsed go rule to explicitly list all source files instead of
using glob star. The `known_dependency` field is a json object which defines a
mapping from go import paths to please third party dependency targets.

When the gofmt command resolves go imports to please third party dependencies
it first checks this mapping. When the config defines a known dependency for a
go import path the command will use that instead of attempting to find a
`go_get` rule which satisfies the go import. This can be useful in a couple of
different ways. First, it's possible to have multiple `go_get` rules which can
satisfy a go import. In cases like these wollemi will not know which rule to
choose unless it's explicitly told through a known dependency mapping. Second,
it's possible to have valid please build files which wollemi is not capable of
parsing. A common example would be a build file which contains string
interpolation. If wollemi is unable to parse the build file then it will also be
unable to resolve any go imports to `go_get` rules defined within that build
file unless explicitly told through a known dependecy mapping.

The following is an example of a valid `.wollemi.json` config file.
```
Expand All @@ -318,6 +292,14 @@ The following is an example of a valid `.wollemi.json` config file.
"explicit_sources": true,
"known_dependency": {
"github.com/olivere/elastic": "//third_party/go/github.com/olivere/elastic:v7"
},
"gofmt": {
"rewrite": true,
"create": ["go_binary", "go_library", "go_test"],
"manage": ["default", "go_custom_binary"]
"mapped": {
"go_test": "go_custom_test"
}
}
}
```
Expand Down Expand Up @@ -349,3 +331,57 @@ If the config in bar sets `default_visibility` then it overrides the previous
overrides or adds to the inherited `known_dependency` mapping. This inheritance
continues up the directory chain and stops at the please root directory which
is identified by the existence of a `.plzconfig` file.

The `wollemi gofmt` `--create`, `--manage` and `--mapped` flags, when explicitly
set will override any configuration found on disk.

##### `default_visibility`
When set all rules created by `wollemi gofmt` will be created using this
visibility. This does not effect the visibility of any existing rules.

##### `allow_unresolved_dependency`
Whenever `wollemi gofmt` encounters a dependency it's unable to resolve it logs
a warning and skips rewriting the rules which required the dependency. When
this flag is set it unresolved depencies are ignored.

##### `explicit_sources`
Whenever `wollemi gofmt` creates a new rule it uses glob patterns to define
a rules srcs. When this flag is set all new rules, as well as all existing
rules, will explicitly list every src file instead of using glob patterns.

##### `known_dependency`
Whenever `wollemi gofmt` encounters a dependency it cannot resolve it checks
to see if a `known_dependency` was manually defined. If a manually defined
mapping can be found it recovers using the target defined.

##### `gofmt.rewrite`
Allows `wollemi gofmt` to create new rules and or managing the src files and
dependencies of existing rules. This is enabled by default but could be
disabled on a per package basis.

##### `gofmt.create`
Whitelist of rule kinds allowed to be created by `wollemi gofmt`. By default
this is `["go_binary", "go_library", "go_test"]`. This can be completely
disabled by setting it to `[]` or `"off"`. Alternatively this can be
re-enabled in a child package by setting it to `"on"`, `"default"` or some
other subset of the default.

##### `gofmt.manage`
Whitelist of rule kinds allowed to be managed by `wollemi gofmt`. Manage in
this context means updating an existing rules srcs and/or dependencies
according to the golang source files. By default this is set to
`["go_binary", "go_library", "go_test"]`. This can be completely disabled by
setting it to `[]` or `"off"`. Alternatively this can be re-enabled in a
child package by setting it to `"on"`, `"default"`, some other list. The
keyword `"default"` within a list will expand to the original default managed
rules. Therefore the list `["default", "my_custom_rule"]` is shorthand for
`["go_binary", "go_library", "go_test", "my_custom_rule"]`.

##### `gofmt.mapped`
This setting maps standard go rules such as `go_binary`, `go_library` and
`go_test` to custom go rules. For example, you might define a mapping from
`go_test` to `go_custom_test`. Defining a mapping like this has two effects.
First, whenever `wollemi gofmt` determines a package contains go test files
but lacks a test rule it will create one using `go_custom_test` instead of
`go_test`. Second, `wollemi gofmt` will manage existing `go_custom_test` rules
as if they were `go_test` rules instead.
16 changes: 8 additions & 8 deletions adapters/cobra/gofmt.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ import (
func GoFmtCmd(app ctl.Application) *cobra.Command {
config := wollemi.Config{}

rewrite := config.Gofmt.GetRewrite()
create := config.Gofmt.GetCreate()
manage := config.Gofmt.GetManage()
mapped := map[string]string(nil)

cmd := &cobra.Command{
Use: "gofmt [path...]",
Expand Down Expand Up @@ -82,10 +82,6 @@ func GoFmtCmd(app ctl.Application) *cobra.Command {
return err
}

if cmd.Flags().Changed("rewrite") {
config.Gofmt.Rewrite = &rewrite
}

if cmd.Flags().Changed("create") {
config.Gofmt.Create = create
}
Expand All @@ -94,13 +90,17 @@ func GoFmtCmd(app ctl.Application) *cobra.Command {
config.Gofmt.Manage = manage
}

if cmd.Flags().Changed("mapped") {
config.Gofmt.Mapped = mapped
}

return wollemi.GoFormat(config, args)
},
}

cmd.Flags().BoolVar(&rewrite, "rewrite", rewrite, "allow rewriting of build files")
cmd.Flags().StringSliceVar(&create, "create", create, "allow missing rule kinds to be created")
cmd.Flags().StringSliceVar(&manage, "manage", manage, "allow existing rule kinds to be managed")
cmd.Flags().StringSliceVar(&create, "create", create, "rule kinds to be created when not found")
cmd.Flags().StringSliceVar(&manage, "manage", manage, "rule kinds to be managed")
cmd.Flags().StringToStringVar(&mapped, "mapped", nil, "rule kinds to be mapped")

return cmd
}
4 changes: 3 additions & 1 deletion domain/wollemi/service_format.go
Original file line number Diff line number Diff line change
Expand Up @@ -486,7 +486,7 @@ func (this *Service) formatDirectory(log logging.Logger, dir *Directory) {
var newFiles []string
var external bool

kind := rule.Kind()
kind := config.Gofmt.GetMapped(rule.Kind())

if !inStrings(config.Gofmt.GetManage(), kind) {
return
Expand Down Expand Up @@ -637,6 +637,7 @@ func (this *Service) formatDirectory(log logging.Logger, dir *Directory) {
}

path := filepath.Join(this.wd, dir.Path)
kind := config.Gofmt.GetMapped(kind)
rule = this.please.NewRule(kind, filepath.Base(path))
include, exclude = []string{"*.go"}, []string{"*_test.go"}
case "go_test":
Expand All @@ -645,6 +646,7 @@ func (this *Service) formatDirectory(log logging.Logger, dir *Directory) {
}

pkgFiles = dir.Gopkg.XTestGoFiles
kind := config.Gofmt.GetMapped(kind)
rule = this.please.NewRule(kind, "test")
include = []string{"*_test.go"}
external = true
Expand Down
55 changes: 55 additions & 0 deletions domain/wollemi/service_format_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1052,6 +1052,61 @@ func (t *ServiceSuite) TestService_GoFormat() {
},
},
},
}, { // TEST_CASE ------------------------------------------------------------
Title: "maps created rule kinds when configured",
Data: &GoFormatTestData{
Gosrc: gosrc,
Gopkg: gopkg,
Paths: []string{"app/..."},
Config: map[string]wollemi.Config{
"app": wollemi.Config{
ExplicitSources: optional.BoolValue(true),
Gofmt: wollemi.Gofmt{
Mapped: map[string]string{
"go_binary": "go_custom_binary",
"go_test": "go_custom_test",
},
},
},
},
ImportDir: map[string]*golang.Package{
"app": &golang.Package{
Name: "main",
GoFiles: []string{"main.go"},
TestGoFiles: []string{"main_test.go"},
GoFileImports: map[string][]string{
"main.go": []string{"github.com/spf13/cobra"},
"main_test.go": []string{
"github.com/stretchr/testify",
"testing",
},
},
},
},
Parse: t.WithThirdPartyGo(nil),
Write: map[string]*please.BuildFile{
"app/BUILD.plz": &please.BuildFile{
Stmt: []please.Expr{
please.NewCallExpr("go_custom_binary", []please.Expr{
please.NewAssignExpr("=", "name", "app"),
please.NewAssignExpr("=", "srcs", []string{"main.go"}),
please.NewAssignExpr("=", "visibility", []string{"//app/..."}),
please.NewAssignExpr("=", "deps", []string{
"//third_party/go/github.com/spf13:cobra",
}),
}),
please.NewCallExpr("go_custom_test", []please.Expr{
please.NewAssignExpr("=", "name", "test"),
please.NewAssignExpr("=", "srcs", []string{"main.go", "main_test.go"}),
please.NewAssignExpr("=", "deps", []string{
"//third_party/go/github.com/spf13:cobra",
"//third_party/go/github.com/stretchr:testify",
}),
}),
},
},
},
},
}} {
focus := ""

Expand Down
56 changes: 48 additions & 8 deletions ports/wollemi/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,10 @@ type Config struct {
}

type Gofmt struct {
Rewrite *bool `json:"rewrite,omitempty"`
Create create `json:"create,omitempty"`
Manage manage `json:"manage,omitempty"`
Rewrite *bool `json:"rewrite,omitempty"`
Create gofmtCreate `json:"create,omitempty"`
Manage gofmtManage `json:"manage,omitempty"`
Mapped gofmtMapped `json:"mapped,omitempty"`
}

func (gofmt *Gofmt) GetRewrite() bool {
Expand All @@ -47,6 +48,16 @@ func (gofmt *Gofmt) GetManage() []string {
return []string{"go_binary", "go_library", "go_test"}
}

func (gofmt *Gofmt) GetMapped(kind string) string {
if gofmt != nil && gofmt.Mapped != nil {
if kind, ok := gofmt.Mapped[kind]; ok {
return kind
}
}

return kind
}

func (this Config) Merge(that Config) Config {
merge := this

Expand Down Expand Up @@ -97,12 +108,16 @@ func (this Config) Merge(that Config) Config {
merge.Gofmt.Manage = v
}

if v := that.Gofmt.Mapped; v != nil {
merge.Gofmt.Mapped = v
}

return merge
}

type create []string
type gofmtCreate []string

func (list *create) UnmarshalJSON(buf []byte) error {
func (list *gofmtCreate) UnmarshalJSON(buf []byte) error {
err := json.Unmarshal(buf, (*[]string)(list))
if err != nil {
s, err := strconv.Unquote(string(buf))
Expand All @@ -112,17 +127,16 @@ func (list *create) UnmarshalJSON(buf []byte) error {
*list = (*Gofmt)(nil).GetCreate()
case "off":
*list = []string{}
default:
}
}
}

return nil
}

type manage []string
type gofmtManage []string

func (list *manage) UnmarshalJSON(buf []byte) error {
func (list *gofmtManage) UnmarshalJSON(buf []byte) error {
var gofmt *Gofmt

err := json.Unmarshal(buf, (*[]string)(list))
Expand Down Expand Up @@ -158,6 +172,32 @@ func (list *manage) UnmarshalJSON(buf []byte) error {
return nil
}

type gofmtMapped map[string]string

func (mapped *gofmtMapped) UnmarshalJSON(buf []byte) error {
tmp := make(map[string]string)
err := json.Unmarshal(buf, &tmp)

if err != nil {
s, unquoteErr := strconv.Unquote(string(buf))
if unquoteErr == nil && s == "none" {
err = nil
}
}

*mapped = map[string]string{
"go_binary": "go_binary",
"go_library": "go_library",
"go_test": "go_test",
}

for k, v := range tmp {
(*mapped)[k] = v
}

return err
}

func inStrings(from []string, value string) bool {
return indexStrings(from, value) >= 0
}
Expand Down
Loading

0 comments on commit f242dc4

Please sign in to comment.