Use static parsing instead of requiring a binary.
Using go/parser requires a built binary of the code to be enum'ed to exist. This leads to two problems: 1. The binary may be very out of date with the code, for example, when pulling into an existing repo that was last built locally some time ago, running jsonenums on newly added files will fail; 2. If the binary has never been built, but the code already expects the output of jsonenums, it will be impossible to generate the enums, making it impossible to build the binary. In response to a similar issue, Alan Donovan of the Go team suggested that instead generating tools should be using (the not-yet stable) x/tools/go/loader package to parse the actual Go code itself rather than the compilation output: https://github.com/golang/go/issues/11415 This change uses the suggested approach to break the bootstrapping dependency cycle.
This commit is contained in:
parent
0c6ac79cc6
commit
7fbe89f60d
13
jsonenums.go
13
jsonenums.go
@ -75,7 +75,7 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/thought-machine/jsonenums/parser"
|
"github.com/adfin/jsonenums/parser"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -92,14 +92,19 @@ func main() {
|
|||||||
types := strings.Split(*typeNames, ",")
|
types := strings.Split(*typeNames, ",")
|
||||||
|
|
||||||
// Only one directory at a time can be processed, and the default is ".".
|
// Only one directory at a time can be processed, and the default is ".".
|
||||||
dir := "."
|
in_dir := "."
|
||||||
if args := flag.Args(); len(args) == 1 {
|
if args := flag.Args(); len(args) == 1 {
|
||||||
dir = args[0]
|
in_dir = args[0]
|
||||||
} else if len(args) > 1 {
|
} else if len(args) > 1 {
|
||||||
log.Fatalf("only one directory at a time")
|
log.Fatalf("only one directory at a time")
|
||||||
}
|
}
|
||||||
|
dir, err := filepath.Abs(in_dir)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Unable to determine absolute filepath for requested path %s: %v",
|
||||||
|
in_dir, err)
|
||||||
|
}
|
||||||
|
|
||||||
pkg, err := parser.ParsePackage(dir, *outputPrefix, *outputSuffix+".go")
|
pkg, err := parser.ParsePackage(dir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("parsing package: %v", err)
|
log.Fatalf("parsing package: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -10,15 +10,14 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"go/ast"
|
"go/ast"
|
||||||
"go/build"
|
"go/build"
|
||||||
"go/parser"
|
"go/constant"
|
||||||
"go/token"
|
"go/token"
|
||||||
|
"go/types"
|
||||||
"log"
|
"log"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"go/constant"
|
"golang.org/x/tools/go/loader"
|
||||||
"go/importer"
|
|
||||||
"go/types"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// A Package contains all the information related to a parsed package.
|
// A Package contains all the information related to a parsed package.
|
||||||
@ -30,45 +29,25 @@ type Package struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ParsePackage parses the package in the given directory and returns it.
|
// ParsePackage parses the package in the given directory and returns it.
|
||||||
func ParsePackage(directory, skipPrefix, skipSuffix string) (*Package, error) {
|
func ParsePackage(directory string) (*Package, error) {
|
||||||
pkgDir, err := build.Default.ImportDir(directory, 0)
|
relDir, err := filepath.Rel(filepath.Join(build.Default.GOPATH, "src"), directory)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("cannot process directory %s: %s", directory, err)
|
return nil, fmt.Errorf("provided directory not under GOPATH (%s): %v",
|
||||||
|
build.Default.GOPATH, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var files []*ast.File
|
loaderConf := loader.Config{TypeChecker: types.Config{FakeImportC: true}}
|
||||||
fs := token.NewFileSet()
|
loaderConf.Import(relDir)
|
||||||
for _, name := range pkgDir.GoFiles {
|
program, err := loaderConf.Load()
|
||||||
if !strings.HasSuffix(name, ".go") ||
|
if err != nil {
|
||||||
(skipSuffix != "" && strings.HasPrefix(name, skipPrefix) &&
|
return nil, fmt.Errorf("Couldn't load package: %v", err)
|
||||||
strings.HasSuffix(name, skipSuffix)) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if directory != "." {
|
|
||||||
name = filepath.Join(directory, name)
|
|
||||||
}
|
|
||||||
f, err := parser.ParseFile(fs, name, nil, 0)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("parsing file %v: %v", name, err)
|
|
||||||
}
|
|
||||||
files = append(files, f)
|
|
||||||
}
|
|
||||||
if len(files) == 0 {
|
|
||||||
return nil, fmt.Errorf("%s: no buildable Go files", directory)
|
|
||||||
}
|
|
||||||
|
|
||||||
// type-check the package
|
|
||||||
defs := make(map[*ast.Ident]types.Object)
|
|
||||||
config := types.Config{FakeImportC: true, Importer: importer.Default()}
|
|
||||||
info := &types.Info{Defs: defs}
|
|
||||||
if _, err := config.Check(directory, fs, files, info); err != nil {
|
|
||||||
return nil, fmt.Errorf("type-checking package: %v", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pkgInfo := program.Package(relDir)
|
||||||
return &Package{
|
return &Package{
|
||||||
Name: files[0].Name.Name,
|
Name: pkgInfo.Pkg.Name(),
|
||||||
files: files,
|
files: pkgInfo.Files,
|
||||||
defs: defs,
|
defs: pkgInfo.Defs,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ import (
|
|||||||
|
|
||||||
"go/format"
|
"go/format"
|
||||||
|
|
||||||
"github.com/thought-machine/jsonenums/parser"
|
"github.com/adfin/jsonenums/parser"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
Loading…
Reference in New Issue
Block a user