Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

make gqlgen generate 10x faster in some projects #707

Merged
merged 1 commit into from
May 14, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion codegen/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ func (c *PackageConfig) normalize() error {
// If Package is not set, first attempt to load the package at the output dir. If that fails
// fallback to just the base dir name of the output filename.
if c.Package == "" {
c.Package = code.NameForPackage(c.ImportPath())
c.Package = code.NameForDir(c.Dir())
}

return nil
Expand Down
3 changes: 1 addition & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,7 @@ require (
github.com/urfave/cli v1.20.0
github.com/vektah/dataloaden v0.2.0
github.com/vektah/gqlparser v1.1.2
golang.org/x/net v0.0.0-20180404174746-b3c676e531a6 // indirect
golang.org/x/tools v0.0.0-20190125232054-d66bd3c5d5a6
golang.org/x/tools v0.0.0-20190511041617-99f201b6807e
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
gopkg.in/yaml.v2 v2.2.2
sourcegraph.com/sourcegraph/appdash v0.0.0-20180110180208-2cc67fd64755
Expand Down
9 changes: 9 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,19 @@ github.com/vektah/dataloaden v0.2.0 h1:lhynDrG7c8mNLahboCo0Wq82tMjmu5yOUv2ds/tBm
github.com/vektah/dataloaden v0.2.0/go.mod h1:vxM6NuRlgiR0M6wbVTJeKp9vQIs81ZMfCYO+4yq/jbE=
github.com/vektah/gqlparser v1.1.2 h1:ZsyLGn7/7jDNI+y4SEhI4yAxRChlv15pUHMjijT+e68=
github.com/vektah/gqlparser v1.1.2/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/net v0.0.0-20180404174746-b3c676e531a6 h1:mge3qS/eMvcfyIAzTMOAy0XUzWG6Lk0N4M8zjuSmdco=
golang.org/x/net v0.0.0-20180404174746-b3c676e531a6/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/tools v0.0.0-20190125232054-d66bd3c5d5a6 h1:iZgcI2DDp6zW5v9Z/5+f0NuqoxNdmzg4hivjk2WLXpY=
golang.org/x/tools v0.0.0-20190125232054-d66bd3c5d5a6/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190125232054-dbeab5af4b8d3204d444b78cafaba18a9a062a50/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190511041617-99f201b6807e h1:wTxRxdzKt8fn3IQa3+kVlPJMxK2hJj2Orm+M2Mzw9eg=
golang.org/x/tools v0.0.0-20190511041617-99f201b6807e/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
Expand Down
92 changes: 73 additions & 19 deletions internal/code/imports.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,53 +2,107 @@ package code

import (
"errors"
"go/build"
"go/parser"
"go/token"
"io/ioutil"
"path/filepath"
"regexp"
"strings"
"sync"

"golang.org/x/tools/go/packages"
)

var pathForDirCache = sync.Map{}
var nameForPackageCache = sync.Map{}

// ImportPathFromDir takes an *absolute* path and returns a golang import path for the package, and returns an error if it isn't on the gopath
func ImportPathForDir(dir string) string {
if v, ok := pathForDirCache.Load(dir); ok {
return v.(string)
var gopaths []string

func init() {
gopaths = filepath.SplitList(build.Default.GOPATH)
for i, p := range gopaths {
gopaths[i] = filepath.ToSlash(filepath.Join(p, "src"))
}
}

p, _ := packages.Load(&packages.Config{
Dir: dir,
}, ".")
// NameForDir manually looks for package stanzas in files located in the given directory. This can be
// much faster than having to consult go list, because we already know exactly where to look.
func NameForDir(dir string) string {
dir, err := filepath.Abs(dir)
if err != nil {
return SanitizePackageName(filepath.Base(dir))
}
files, err := ioutil.ReadDir(dir)
if err != nil {
return SanitizePackageName(filepath.Base(dir))
}
fset := token.NewFileSet()
for _, file := range files {
if !strings.HasSuffix(strings.ToLower(file.Name()), ".go") {
continue
}

filename := filepath.Join(dir, file.Name())
if src, err := parser.ParseFile(fset, filename, nil, parser.PackageClauseOnly); err == nil {
return src.Name.Name
}
}

// If the dir dosent exist yet, keep walking up the directory tree trying to find a match
if len(p) != 1 {
parent, err := filepath.Abs(filepath.Join(dir, ".."))
return SanitizePackageName(filepath.Base(dir))
}

// ImportPathForDir takes a path and returns a golang import path for the package
func ImportPathForDir(dir string) (res string) {
dir, err := filepath.Abs(dir)
if err != nil {
panic(err)
}
dir = filepath.ToSlash(dir)

modDir := dir
assumedPart := ""
for {
f, err := ioutil.ReadFile(filepath.Join(modDir, "/", "go.mod"))
if err == nil {
// found it, stop searching
return string(modregex.FindSubmatch(f)[1]) + assumedPart
}

assumedPart = "/" + filepath.Base(modDir) + assumedPart
modDir, err = filepath.Abs(filepath.Join(modDir, ".."))
if err != nil {
panic(err)
}

// Walked all the way to the root and didnt find anything :'(
if parent == dir {
return ""
if modDir == "/" {
break
}
return ImportPathForDir(parent) + "/" + filepath.Base(dir)
}

pathForDirCache.Store(dir, p[0].PkgPath)
for _, gopath := range gopaths {
if len(gopath) < len(dir) && strings.EqualFold(gopath, dir[0:len(gopath)]) {
return dir[len(gopath)+1:]
}
}

return p[0].PkgPath
return ""
}

var nameForPackageCache = sync.Map{}
var modregex = regexp.MustCompile("module (.*)\n")

// NameForPackage returns the package name for a given import path. This can be really slow.
func NameForPackage(importPath string) string {
if importPath == "" {
panic(errors.New("import path can not be empty"))
}
if v, ok := nameForPackageCache.Load(importPath); ok {
return v.(string)
}
importPath = QualifyPackagePath(importPath)
p, _ := packages.Load(nil, importPath)
//importPath = QualifyPackagePath(importPath)
p, _ := packages.Load(&packages.Config{
Mode: packages.NeedName,
}, importPath)

if len(p) != 1 || p[0].Name == "" {
return SanitizePackageName(filepath.Base(importPath))
Expand Down
10 changes: 10 additions & 0 deletions internal/code/imports_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,13 @@ func TestNameForPackage(t *testing.T) {
assert.Equal(t, "docs", NameForPackage("github.com/99designs/gqlgen/docs"))
assert.Equal(t, "github_com", NameForPackage("github.com"))
}

func TestNameForDir(t *testing.T) {
wd, err := os.Getwd()
require.NoError(t, err)

assert.Equal(t, "tmp", NameForDir("/tmp"))
assert.Equal(t, "code", NameForDir(wd))
assert.Equal(t, "internal", NameForDir(wd+"/.."))
assert.Equal(t, "main", NameForDir(wd+"/../.."))
}
7 changes: 5 additions & 2 deletions plugin/stubgen/stubs.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,11 @@ func (m *Plugin) MutateConfig(cfg *config.Config) error {
}

func (m *Plugin) GenerateCode(data *codegen.Data) error {
pkgPath := code.ImportPathForDir(filepath.Dir(m.filename))
pkgName := code.NameForPackage(pkgPath)
abs, err := filepath.Abs(m.filename)
if err != nil {
return err
}
pkgName := code.NameForDir(filepath.Dir(abs))

return templates.Render(templates.Options{
PackageName: pkgName,
Expand Down