cmd/stringer: add a -tags flag that supports build tags

This is reapplying CL121995 after rolling back the change to the importing
methods. There is still a need for a flag to control tags.
The original CL decription:

The feature has been requested but, like build tags in general,
only works in a directory, not when files are specified explicitly.
Unlike the build tools, report when the feature is misused like this
to avoid confusion.

Fixes golang/go#9449

Change-Id: I732627d5f2e6323367e3bdd5de746923868890a9
Reviewed-on: https://go-review.googlesource.com/122537
Reviewed-by: Ian Lance Taylor <iant@golang.org>
Run-TryBot: Ian Lance Taylor <iant@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
This commit is contained in:
Rob Pike 2018-07-09 12:25:47 +10:00
parent 8cb83b71b4
commit 18f0b668f1
4 changed files with 109 additions and 18 deletions

View File

@ -9,6 +9,7 @@
package main package main
import ( import (
"bytes"
"fmt" "fmt"
"go/build" "go/build"
"io" "io"
@ -26,17 +27,8 @@ import (
// binary panics if the String method for X is not correct, including for error cases. // binary panics if the String method for X is not correct, including for error cases.
func TestEndToEnd(t *testing.T) { func TestEndToEnd(t *testing.T) {
dir, err := ioutil.TempDir("", "stringer") dir, stringer := buildStringer(t)
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(dir) defer os.RemoveAll(dir)
// Create stringer in temporary directory.
stringer := filepath.Join(dir, "stringer.exe")
err = run("go", "build", "-o", stringer)
if err != nil {
t.Fatalf("building stringer: %s", err)
}
// Read the testdata directory. // Read the testdata directory.
fd, err := os.Open("testdata") fd, err := os.Open("testdata")
if err != nil { if err != nil {
@ -53,6 +45,10 @@ func TestEndToEnd(t *testing.T) {
t.Errorf("%s is not a Go file", name) t.Errorf("%s is not a Go file", name)
continue continue
} }
if strings.HasPrefix(name, "tag_") {
// This file is used for tag processing in TestTags, below.
continue
}
if name == "cgo.go" && !build.Default.CgoEnabled { if name == "cgo.go" && !build.Default.CgoEnabled {
t.Logf("cgo is not enabled for %s", name) t.Logf("cgo is not enabled for %s", name)
continue continue
@ -63,9 +59,69 @@ func TestEndToEnd(t *testing.T) {
} }
} }
// TestTags verifies that the -tags flag works as advertised.
func TestTags(t *testing.T) {
dir, stringer := buildStringer(t)
defer os.RemoveAll(dir)
var (
protectedConst = []byte("TagProtected")
output = filepath.Join(dir, "const_string.go")
)
for _, file := range []string{"tag_main.go", "tag_tag.go"} {
err := copy(filepath.Join(dir, file), filepath.Join("testdata", file))
if err != nil {
t.Fatal(err)
}
}
err := run(stringer, "-type", "Const", dir)
if err != nil {
t.Fatal(err)
}
result, err := ioutil.ReadFile(output)
if err != nil {
t.Fatal(err)
}
if bytes.Contains(result, protectedConst) {
t.Fatal("tagged variable appears in untagged run")
}
err = os.Remove(output)
if err != nil {
t.Fatal(err)
}
err = run(stringer, "-type", "Const", "-tags", "tag", dir)
if err != nil {
t.Fatal(err)
}
result, err = ioutil.ReadFile(output)
if err != nil {
t.Fatal(err)
}
if !bytes.Contains(result, protectedConst) {
t.Fatal("tagged variable does not appear in tagged run")
}
}
// buildStringer creates a temporary directory and installs stringer there.
func buildStringer(t *testing.T) (dir string, stringer string) {
t.Helper()
dir, err := ioutil.TempDir("", "stringer")
if err != nil {
t.Fatal(err)
}
stringer = filepath.Join(dir, "stringer.exe")
err = run("go", "build", "-o", stringer)
if err != nil {
t.Fatalf("building stringer: %s", err)
}
return dir, stringer
}
// stringerCompileAndRun runs stringer for the named file and compiles and // stringerCompileAndRun runs stringer for the named file and compiles and
// runs the target binary in directory dir. That binary will panic if the String method is incorrect. // runs the target binary in directory dir. That binary will panic if the String method is incorrect.
func stringerCompileAndRun(t *testing.T, dir, stringer, typeName, fileName string) { func stringerCompileAndRun(t *testing.T, dir, stringer, typeName, fileName string) {
t.Helper()
t.Logf("run: %s %s\n", fileName, typeName) t.Logf("run: %s %s\n", fileName, typeName)
source := filepath.Join(dir, fileName) source := filepath.Join(dir, fileName)
err := copy(source, filepath.Join("testdata", fileName)) err := copy(source, filepath.Join("testdata", fileName))

View File

@ -82,6 +82,7 @@ var (
output = flag.String("output", "", "output file name; default srcdir/<type>_string.go") output = flag.String("output", "", "output file name; default srcdir/<type>_string.go")
trimprefix = flag.String("trimprefix", "", "trim the `prefix` from the generated constant names") trimprefix = flag.String("trimprefix", "", "trim the `prefix` from the generated constant names")
linecomment = flag.Bool("linecomment", false, "use line comment text as printed text when present") linecomment = flag.Bool("linecomment", false, "use line comment text as printed text when present")
buildTags = flag.String("tags", "", "comma-separated list of build tags to apply")
) )
// Usage is a replacement usage function for the flags package. // Usage is a replacement usage function for the flags package.
@ -105,6 +106,10 @@ func main() {
os.Exit(2) os.Exit(2)
} }
types := strings.Split(*typeNames, ",") types := strings.Split(*typeNames, ",")
var tags []string
if len(*buildTags) > 0 {
tags = strings.Split(*buildTags, ",")
}
// We accept either one directory or a list of files. Which do we have? // We accept either one directory or a list of files. Which do we have?
args := flag.Args() args := flag.Args()
@ -121,8 +126,11 @@ func main() {
} }
if len(args) == 1 && isDirectory(args[0]) { if len(args) == 1 && isDirectory(args[0]) {
dir = args[0] dir = args[0]
g.parsePackageDir(args[0]) g.parsePackageDir(args[0], tags)
} else { } else {
if len(tags) != 0 {
log.Fatal("-tags option applies only to directories, not when files are specified")
}
dir = filepath.Dir(args[0]) dir = filepath.Dir(args[0])
g.parsePackageFiles(args) g.parsePackageFiles(args)
} }
@ -197,9 +205,15 @@ type Package struct {
typesPkg *types.Package typesPkg *types.Package
} }
func buildContext(tags []string) *build.Context {
ctx := build.Default
ctx.BuildTags = tags
return &ctx
}
// parsePackageDir parses the package residing in the directory. // parsePackageDir parses the package residing in the directory.
func (g *Generator) parsePackageDir(directory string) { func (g *Generator) parsePackageDir(directory string, tags []string) {
pkg, err := build.Default.ImportDir(directory, 0) pkg, err := buildContext(tags).ImportDir(directory, 0)
if err != nil { if err != nil {
log.Fatalf("cannot process directory %s: %s", directory, err) log.Fatalf("cannot process directory %s: %s", directory, err)
} }
@ -261,14 +275,17 @@ func (g *Generator) parsePackage(directory string, names []string, text interfac
g.pkg.name = astFiles[0].Name.Name g.pkg.name = astFiles[0].Name.Name
g.pkg.files = files g.pkg.files = files
g.pkg.dir = directory g.pkg.dir = directory
// Type check the package. g.pkg.typeCheck(fs, astFiles)
g.pkg.check(fs, astFiles)
} }
// check type-checks the package. The package must be OK to proceed. // check type-checks the package so we can evaluate contants whose values we are printing.
func (pkg *Package) check(fs *token.FileSet, astFiles []*ast.File) { func (pkg *Package) typeCheck(fs *token.FileSet, astFiles []*ast.File) {
pkg.defs = make(map[*ast.Ident]types.Object) pkg.defs = make(map[*ast.Ident]types.Object)
config := types.Config{Importer: defaultImporter(), FakeImportC: true} config := types.Config{
IgnoreFuncBodies: true, // We only need to evaluate constants.
Importer: defaultImporter(),
FakeImportC: true,
}
info := &types.Info{ info := &types.Info{
Defs: pkg.defs, Defs: pkg.defs,
} }

11
cmd/stringer/testdata/tag_main.go vendored Normal file
View File

@ -0,0 +1,11 @@
// No build tag in this file.
package main
type Const int
const (
A Const = iota
B
C
)

7
cmd/stringer/testdata/tag_tag.go vendored Normal file
View File

@ -0,0 +1,7 @@
// This file has a build tag "tag"
// +build tag
package main
const TagProtected Const = C + 1