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:
parent
8cb83b71b4
commit
18f0b668f1
|
@ -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))
|
||||||
|
|
|
@ -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,
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
// No build tag in this file.
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
type Const int
|
||||||
|
|
||||||
|
const (
|
||||||
|
A Const = iota
|
||||||
|
B
|
||||||
|
C
|
||||||
|
)
|
|
@ -0,0 +1,7 @@
|
||||||
|
// This file has a build tag "tag"
|
||||||
|
|
||||||
|
// +build tag
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
const TagProtected Const = C + 1
|
Loading…
Reference in New Issue