Speedup program loading on 20%.

Don't typecheck func bodies for non-local packages.
Works only if megacheck and interfacer are disabled: they require all
func bodies to build SSA repr.
Export GL_DEBUG=load to get logs for this feature.
This commit is contained in:
Denis Isaev 2018-06-12 19:20:43 +03:00 committed by Isaev Denis
parent 6480aa8b19
commit a1a9215fcc
4 changed files with 104 additions and 10 deletions

View File

@ -14,6 +14,7 @@ import (
"github.com/golangci/golangci-lint/pkg/config" "github.com/golangci/golangci-lint/pkg/config"
"github.com/golangci/golangci-lint/pkg/lint" "github.com/golangci/golangci-lint/pkg/lint"
"github.com/golangci/golangci-lint/pkg/lint/lintersdb" "github.com/golangci/golangci-lint/pkg/lint/lintersdb"
"github.com/golangci/golangci-lint/pkg/logutils"
"github.com/golangci/golangci-lint/pkg/printers" "github.com/golangci/golangci-lint/pkg/printers"
"github.com/golangci/golangci-lint/pkg/result" "github.com/golangci/golangci-lint/pkg/result"
"github.com/golangci/golangci-lint/pkg/result/processors" "github.com/golangci/golangci-lint/pkg/result/processors"
@ -231,12 +232,14 @@ func setOutputToDevNull() (savedStdout, savedStderr *os.File) {
} }
func (e *Executor) runAndPrint(ctx context.Context, args []string) error { func (e *Executor) runAndPrint(ctx context.Context, args []string) error {
if !logutils.HaveDebugTag("linters_output") {
// Don't allow linters and loader to print anything // Don't allow linters and loader to print anything
log.SetOutput(ioutil.Discard) log.SetOutput(ioutil.Discard)
savedStdout, savedStderr := setOutputToDevNull() savedStdout, savedStderr := setOutputToDevNull()
defer func() { defer func() {
os.Stdout, os.Stderr = savedStdout, savedStderr os.Stdout, os.Stderr = savedStdout, savedStderr
}() }()
}
issues, err := e.runAnalysis(ctx, args) issues, err := e.runAnalysis(ctx, args)
if err != nil { if err != nil {

View File

@ -75,7 +75,6 @@ func (g Gofmt) extractIssuesFromPatch(patch string) ([]result.Issue, error) {
for _, hunk := range d.Hunks { for _, hunk := range d.Hunks {
deletedLine, addedLine, err := getFirstDeletedAndAddedLineNumberInHunk(hunk) deletedLine, addedLine, err := getFirstDeletedAndAddedLineNumberInHunk(hunk)
if err != nil { if err != nil {
logrus.Infof("Can't get first deleted line number for hunk: %s", err)
if addedLine > 1 { if addedLine > 1 {
deletedLine = addedLine - 1 // use previous line, TODO: use both prev and next lines deletedLine = addedLine - 1 // use previous line, TODO: use both prev and next lines
} else { } else {

View File

@ -11,6 +11,8 @@ import (
"strings" "strings"
"time" "time"
"github.com/golangci/golangci-lint/pkg/logutils"
"github.com/golangci/go-tools/ssa" "github.com/golangci/go-tools/ssa"
"github.com/golangci/go-tools/ssa/ssautil" "github.com/golangci/go-tools/ssa/ssautil"
"github.com/golangci/golangci-lint/pkg/config" "github.com/golangci/golangci-lint/pkg/config"
@ -21,6 +23,8 @@ import (
"golang.org/x/tools/go/loader" "golang.org/x/tools/go/loader"
) )
var loadDebugf = logutils.Debug("load")
func isFullImportNeeded(linters []linter.Config) bool { func isFullImportNeeded(linters []linter.Config) bool {
for _, linter := range linters { for _, linter := range linters {
if linter.NeedsProgramLoading() { if linter.NeedsProgramLoading() {
@ -64,6 +68,89 @@ func normalizePaths(paths []string) ([]string, error) {
return ret, nil return ret, nil
} }
func getCurrentProjectImportPath() (string, error) {
gopath := os.Getenv("GOPATH")
if gopath == "" {
return "", fmt.Errorf("no GOPATH env variable")
}
wd, err := os.Getwd()
if err != nil {
return "", fmt.Errorf("can't get workind directory: %s", err)
}
if !strings.HasPrefix(wd, gopath) {
return "", fmt.Errorf("currently no in gopath: %q isn't a prefix of %q", gopath, wd)
}
path := strings.TrimPrefix(wd, gopath)
path = strings.TrimPrefix(path, string(os.PathSeparator)) // if GOPATH contains separator at the end
src := "src" + string(os.PathSeparator)
if !strings.HasPrefix(path, src) {
return "", fmt.Errorf("currently no in gopath/src: %q isn't a prefix of %q", src, path)
}
path = strings.TrimPrefix(path, src)
path = strings.Replace(path, string(os.PathSeparator), "/", -1)
return path, nil
}
func isLocalProjectAnalysis(args []string) bool {
for _, arg := range args {
if strings.HasPrefix(arg, "..") || filepath.IsAbs(arg) {
return false
}
}
return true
}
func getTypeCheckFuncBodies(cfg *config.Run, linters []linter.Config, pkgProg *packages.Program) func(string) bool {
if !isLocalProjectAnalysis(cfg.Args) {
loadDebugf("analysis in nonlocal, don't optimize loading by not typechecking func bodies")
return nil
}
if isSSAReprNeeded(linters) {
loadDebugf("ssa repr is needed, don't optimize loading by not typechecking func bodies")
return nil
}
if len(pkgProg.Dirs()) == 0 {
// files run, in this mode packages are fake: can't check their path properly
return nil
}
projPath, err := getCurrentProjectImportPath()
if err != nil {
logrus.Infof("can't get cur project path: %s", err)
return nil
}
return func(path string) bool {
if strings.HasPrefix(path, ".") {
loadDebugf("%s: dot import: typecheck func bodies", path)
return true
}
isLocalPath := strings.HasPrefix(path, projPath)
if isLocalPath {
localPath := strings.TrimPrefix(path, projPath)
localPath = strings.TrimPrefix(localPath, "/")
if strings.HasPrefix(localPath, "vendor/") {
loadDebugf("%s: local vendor import: DO NOT typecheck func bodies", path)
return false
}
loadDebugf("%s: local import: typecheck func bodies", path)
return true
}
loadDebugf("%s: not local import: DO NOT typecheck func bodies", path)
return false
}
}
func loadWholeAppIfNeeded(ctx context.Context, linters []linter.Config, cfg *config.Run, pkgProg *packages.Program) (*loader.Program, *loader.Config, error) { func loadWholeAppIfNeeded(ctx context.Context, linters []linter.Config, cfg *config.Run, pkgProg *packages.Program) (*loader.Program, *loader.Config, error) {
if !isFullImportNeeded(linters) { if !isFullImportNeeded(linters) {
return nil, nil, nil return nil, nil, nil
@ -79,6 +166,7 @@ func loadWholeAppIfNeeded(ctx context.Context, linters []linter.Config, cfg *con
Build: bctx, Build: bctx,
AllowErrors: true, // Try to analyze partially AllowErrors: true, // Try to analyze partially
ParserMode: parser.ParseComments, // AST will be reused by linters ParserMode: parser.ParseComments, // AST will be reused by linters
TypeCheckFuncBodies: getTypeCheckFuncBodies(cfg, linters, pkgProg),
} }
var loaderArgs []string var loaderArgs []string

View File

@ -51,3 +51,7 @@ func Debug(tag string) DebugFunc {
func IsDebugEnabled() bool { func IsDebugEnabled() bool {
return len(enabledDebugs) != 0 return len(enabledDebugs) != 0
} }
func HaveDebugTag(tag string) bool {
return enabledDebugs[tag]
}