Skip to content

Commit a7cd571

Browse files
committed
Add build tags support
`go build` allows the user to specify build tags. These tags enable the user to discard files based on various tags when they build the application binary. `weaver generate` which is a wrapper around `go build` doesn't allow the user to specify build tags. This PR adds built tags support for the `weaver generate` command. The syntax of passing build tags to the `weaver generate` command is similar to how build tags are specified when using `go build`. E.g., weaver generate -tags good,prod weaver generate --tags=good
1 parent 3f55afa commit a7cd571

File tree

5 files changed

+158
-10
lines changed

5 files changed

+158
-10
lines changed

cmd/weaver/main.go

+15-1
Original file line numberDiff line numberDiff line change
@@ -74,11 +74,25 @@ func main() {
7474
switch flag.Arg(0) {
7575
case "generate":
7676
generateFlags := flag.NewFlagSet("generate", flag.ExitOnError)
77+
tags := generateFlags.String("tags", "not specified", "Optional tags for the generate command")
7778
generateFlags.Usage = func() {
7879
fmt.Fprintln(os.Stderr, generate.Usage)
7980
}
8081
generateFlags.Parse(flag.Args()[1:])
81-
if err := generate.Generate(".", flag.Args()[1:], generate.Options{}); err != nil {
82+
buildTags := "--tags=ignoreWeaverGen"
83+
if *tags != "not specified" { // tags flag was specified
84+
if *tags == "" || *tags == "." {
85+
// User specified the tags flag but didn't provide any tags.
86+
fmt.Fprintln(os.Stderr, "No tags provided.")
87+
os.Exit(1)
88+
}
89+
// TODO(rgrandl): we assume that the user specify the tags properly. I.e.,
90+
// a single tag, or a list of tags separated by comma. We may want to do
91+
// extra validation at some point.
92+
buildTags = buildTags + "," + *tags
93+
}
94+
idx := len(flag.Args()) - len(generateFlags.Args())
95+
if err := generate.Generate(".", flag.Args()[idx:], generate.Options{BuildTags: buildTags}); err != nil {
8296
fmt.Fprintln(os.Stderr, err)
8397
os.Exit(1)
8498
}

internal/tool/generate/generator.go

+22-8
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ const (
5454
Usage = `Generate code for a Service Weaver application.
5555
5656
Usage:
57-
weaver generate [packages]
57+
weaver generate [tags] [packages]
5858
5959
Description:
6060
"weaver generate" generates code for the Service Weaver applications in the
@@ -64,6 +64,9 @@ Description:
6464
file in the package's directory. For example, "weaver generate . ./foo" will
6565
create ./weaver_gen.go and ./foo/weaver_gen.go.
6666
67+
You specify build tags for "weaver generate" in the same way you specify build
68+
tags for go build. See "go help build" for more information.
69+
6770
You specify packages for "weaver generate" in the same way you specify
6871
packages for go build, go test, go vet, etc. See "go help packages" for more
6972
information.
@@ -86,13 +89,22 @@ Examples:
8689
weaver generate ./foo
8790
8891
# Generate code for all packages in all subdirectories of current directory.
89-
weaver generate ./...`
92+
weaver generate ./...
93+
94+
# Generate code for all files that have a "// +build good" line at the top of
95+
the file.
96+
weaver generate -tags good
97+
98+
# Generate code for all files that have a "// +build good,prod" line at the
99+
top of the file.
100+
weaver generate -tags good,prod`
90101
)
91102

92103
// Options controls the operation of Generate.
93104
type Options struct {
94105
// If non-nil, use the specified function to report warnings.
95-
Warn func(error)
106+
Warn func(error)
107+
BuildTags string
96108
}
97109

98110
// Generate generates Service Weaver code for the specified packages.
@@ -104,11 +116,13 @@ func Generate(dir string, pkgs []string, opt Options) error {
104116
}
105117
fset := token.NewFileSet()
106118
cfg := &packages.Config{
107-
Mode: packages.NeedName | packages.NeedSyntax | packages.NeedImports | packages.NeedTypes | packages.NeedTypesInfo,
108-
Dir: dir,
109-
Fset: fset,
110-
ParseFile: parseNonWeaverGenFile,
111-
BuildFlags: []string{"--tags=ignoreWeaverGen"},
119+
Mode: packages.NeedName | packages.NeedSyntax | packages.NeedImports | packages.NeedTypes | packages.NeedTypesInfo,
120+
Dir: dir,
121+
Fset: fset,
122+
ParseFile: parseNonWeaverGenFile,
123+
}
124+
if len(opt.BuildTags) > 0 {
125+
cfg.BuildFlags = []string{opt.BuildTags}
112126
}
113127
pkgList, err := packages.Load(cfg, pkgs...)
114128
if err != nil {

internal/tool/generate/generator_test.go

+76-1
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,8 @@ func runGenerator(t *testing.T, directory, filename, contents string, subdirs []
102102

103103
// Run "weaver generate".
104104
opt := Options{
105-
Warn: func(err error) { t.Log(err) },
105+
Warn: func(err error) { t.Log(err) },
106+
BuildTags: "--tags=ignoreWeaverGen",
106107
}
107108
if err := Generate(tmp, []string{tmp}, opt); err != nil {
108109
return "", err
@@ -237,6 +238,80 @@ func TestGenerator(t *testing.T) {
237238
}
238239
}
239240

241+
// TestGeneratorBuildTags runs "weaver generate" on all the files in
242+
// testdata/tags and checks if the command succeeds. Each file should have some build tags.
243+
func TestGeneratorBuildTags(t *testing.T) {
244+
const dir = "testdata/tags"
245+
files, err := os.ReadDir(dir)
246+
if err != nil {
247+
t.Fatalf("cannot list files in %q", dir)
248+
}
249+
250+
tmp := t.TempDir()
251+
save := func(f, data string) {
252+
if err := os.WriteFile(filepath.Join(tmp, f), []byte(data), 0644); err != nil {
253+
t.Fatalf("error writing %s: %v", f, err)
254+
}
255+
}
256+
257+
// Copy the files from dir to the temp directory.
258+
for _, file := range files {
259+
filename := file.Name()
260+
if !strings.HasSuffix(filename, ".go") || strings.HasSuffix(filename, generatedCodeFile) {
261+
continue
262+
}
263+
264+
// Read the test file.
265+
bits, err := os.ReadFile(filepath.Join(dir, filename))
266+
if err != nil {
267+
t.Fatalf("cannot read %q: %v", filename, err)
268+
}
269+
contents := string(bits)
270+
save(filename, contents)
271+
}
272+
save("go.mod", goModFile)
273+
274+
// Run "go mod tidy"
275+
tidy := exec.Command("go", "mod", "tidy")
276+
tidy.Dir = tmp
277+
tidy.Stdout = os.Stdout
278+
tidy.Stderr = os.Stderr
279+
if err := tidy.Run(); err != nil {
280+
t.Fatalf("go mod tidy: %v", err)
281+
}
282+
283+
// Run the "weaver generate" command with no build tags. Verify that the command
284+
// doesn't succeed because bad.go does not compile.
285+
err = Generate(tmp, []string{tmp}, Options{Warn: func(err error) { t.Log(err) }})
286+
if err == nil {
287+
t.Fatal("expected generator to return an error; got nil error")
288+
}
289+
// Verify that no weaver_gen.go file was generated.
290+
_, err = os.ReadFile(filepath.Join(tmp, generatedCodeFile))
291+
if err == nil {
292+
t.Fatal("expected no generated file")
293+
}
294+
295+
// Run the "weaver generate" command with the build tag "good". Verify that the
296+
// command succeeds because the bad.go file is ignored, and the weaver_gen.go
297+
// contains only generated code for the good.go.
298+
err = Generate(tmp, []string{tmp}, Options{Warn: func(err error) { t.Log(err) }, BuildTags: "--tags=good"})
299+
if err != nil {
300+
t.Fatalf("unexpected generator error: %v", err)
301+
}
302+
// Verify that the weaver_gen.go file doesn't contain generated code for the bad service.
303+
output, err := os.ReadFile(filepath.Join(tmp, generatedCodeFile))
304+
if err != nil {
305+
t.Fatalf("unable to read the ")
306+
}
307+
if strings.Contains(string(output), "bad") {
308+
t.Fatalf("unexpected generated code for the bad service")
309+
}
310+
if !strings.Contains(string(output), "good") {
311+
t.Fatalf("expected generated code for the good service")
312+
}
313+
}
314+
240315
// TestGeneratorErrors runs "weaver generate" on all of the files in
241316
// testdata/errors.
242317
// Every file in testdata/errors must begin with a single line header that looks
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
//go:build !good
2+
3+
package tags
4+
5+
import (
6+
"context"
7+
"fmt"
8+
9+
"github.com/ServiceWeaver/weaver"
10+
)
11+
12+
type BadService interface {
13+
DoSomething(context.Context) error
14+
}
15+
16+
type badServiceImpl struct {
17+
weaver.Implements[BadService]
18+
}
19+
20+
func (b *badServiceImpl) DoSomething(context.Context) error {
21+
Some code that does not compile
22+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
//go:build good
2+
3+
package tags
4+
5+
import (
6+
"context"
7+
"fmt"
8+
9+
"github.com/ServiceWeaver/weaver"
10+
)
11+
12+
type GoodService interface {
13+
DoSomething(context.Context) error
14+
}
15+
16+
type goodServiceImpl struct {
17+
weaver.Implements[GoodService]
18+
}
19+
20+
func (g *goodServiceImpl) DoSomething(context.Context) error {
21+
fmt.Println("Hello world!")
22+
return nil
23+
}

0 commit comments

Comments
 (0)