Skip to content

Commit

Permalink
Merge pull request #707 from 99designs/gomodules-performance
Browse files Browse the repository at this point in the history
make gqlgen generate 10x faster in some projects
  • Loading branch information
vektah authored May 14, 2019
2 parents ab961ce + 71cc855 commit eaa61bb
Show file tree
Hide file tree
Showing 6 changed files with 99 additions and 24 deletions.
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

0 comments on commit eaa61bb

Please sign in to comment.