Skip to content

Commit a1a9215

Browse files
committed
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.
1 parent 6480aa8 commit a1a9215

File tree

4 files changed

+104
-10
lines changed

4 files changed

+104
-10
lines changed

Diff for: pkg/commands/run.go

+9-6
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"github.com/golangci/golangci-lint/pkg/config"
1515
"github.com/golangci/golangci-lint/pkg/lint"
1616
"github.com/golangci/golangci-lint/pkg/lint/lintersdb"
17+
"github.com/golangci/golangci-lint/pkg/logutils"
1718
"github.com/golangci/golangci-lint/pkg/printers"
1819
"github.com/golangci/golangci-lint/pkg/result"
1920
"github.com/golangci/golangci-lint/pkg/result/processors"
@@ -231,12 +232,14 @@ func setOutputToDevNull() (savedStdout, savedStderr *os.File) {
231232
}
232233

233234
func (e *Executor) runAndPrint(ctx context.Context, args []string) error {
234-
// Don't allow linters and loader to print anything
235-
log.SetOutput(ioutil.Discard)
236-
savedStdout, savedStderr := setOutputToDevNull()
237-
defer func() {
238-
os.Stdout, os.Stderr = savedStdout, savedStderr
239-
}()
235+
if !logutils.HaveDebugTag("linters_output") {
236+
// Don't allow linters and loader to print anything
237+
log.SetOutput(ioutil.Discard)
238+
savedStdout, savedStderr := setOutputToDevNull()
239+
defer func() {
240+
os.Stdout, os.Stderr = savedStdout, savedStderr
241+
}()
242+
}
240243

241244
issues, err := e.runAnalysis(ctx, args)
242245
if err != nil {

Diff for: pkg/golinters/gofmt.go

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

Diff for: pkg/lint/load.go

+91-3
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ import (
1111
"strings"
1212
"time"
1313

14+
"github.com/golangci/golangci-lint/pkg/logutils"
15+
1416
"github.com/golangci/go-tools/ssa"
1517
"github.com/golangci/go-tools/ssa/ssautil"
1618
"github.com/golangci/golangci-lint/pkg/config"
@@ -21,6 +23,8 @@ import (
2123
"golang.org/x/tools/go/loader"
2224
)
2325

26+
var loadDebugf = logutils.Debug("load")
27+
2428
func isFullImportNeeded(linters []linter.Config) bool {
2529
for _, linter := range linters {
2630
if linter.NeedsProgramLoading() {
@@ -64,6 +68,89 @@ func normalizePaths(paths []string) ([]string, error) {
6468
return ret, nil
6569
}
6670

71+
func getCurrentProjectImportPath() (string, error) {
72+
gopath := os.Getenv("GOPATH")
73+
if gopath == "" {
74+
return "", fmt.Errorf("no GOPATH env variable")
75+
}
76+
77+
wd, err := os.Getwd()
78+
if err != nil {
79+
return "", fmt.Errorf("can't get workind directory: %s", err)
80+
}
81+
82+
if !strings.HasPrefix(wd, gopath) {
83+
return "", fmt.Errorf("currently no in gopath: %q isn't a prefix of %q", gopath, wd)
84+
}
85+
86+
path := strings.TrimPrefix(wd, gopath)
87+
path = strings.TrimPrefix(path, string(os.PathSeparator)) // if GOPATH contains separator at the end
88+
src := "src" + string(os.PathSeparator)
89+
if !strings.HasPrefix(path, src) {
90+
return "", fmt.Errorf("currently no in gopath/src: %q isn't a prefix of %q", src, path)
91+
}
92+
93+
path = strings.TrimPrefix(path, src)
94+
path = strings.Replace(path, string(os.PathSeparator), "/", -1)
95+
return path, nil
96+
}
97+
98+
func isLocalProjectAnalysis(args []string) bool {
99+
for _, arg := range args {
100+
if strings.HasPrefix(arg, "..") || filepath.IsAbs(arg) {
101+
return false
102+
}
103+
}
104+
105+
return true
106+
}
107+
108+
func getTypeCheckFuncBodies(cfg *config.Run, linters []linter.Config, pkgProg *packages.Program) func(string) bool {
109+
if !isLocalProjectAnalysis(cfg.Args) {
110+
loadDebugf("analysis in nonlocal, don't optimize loading by not typechecking func bodies")
111+
return nil
112+
}
113+
114+
if isSSAReprNeeded(linters) {
115+
loadDebugf("ssa repr is needed, don't optimize loading by not typechecking func bodies")
116+
return nil
117+
}
118+
119+
if len(pkgProg.Dirs()) == 0 {
120+
// files run, in this mode packages are fake: can't check their path properly
121+
return nil
122+
}
123+
124+
projPath, err := getCurrentProjectImportPath()
125+
if err != nil {
126+
logrus.Infof("can't get cur project path: %s", err)
127+
return nil
128+
}
129+
130+
return func(path string) bool {
131+
if strings.HasPrefix(path, ".") {
132+
loadDebugf("%s: dot import: typecheck func bodies", path)
133+
return true
134+
}
135+
136+
isLocalPath := strings.HasPrefix(path, projPath)
137+
if isLocalPath {
138+
localPath := strings.TrimPrefix(path, projPath)
139+
localPath = strings.TrimPrefix(localPath, "/")
140+
if strings.HasPrefix(localPath, "vendor/") {
141+
loadDebugf("%s: local vendor import: DO NOT typecheck func bodies", path)
142+
return false
143+
}
144+
145+
loadDebugf("%s: local import: typecheck func bodies", path)
146+
return true
147+
}
148+
149+
loadDebugf("%s: not local import: DO NOT typecheck func bodies", path)
150+
return false
151+
}
152+
}
153+
67154
func loadWholeAppIfNeeded(ctx context.Context, linters []linter.Config, cfg *config.Run, pkgProg *packages.Program) (*loader.Program, *loader.Config, error) {
68155
if !isFullImportNeeded(linters) {
69156
return nil, nil, nil
@@ -76,9 +163,10 @@ func loadWholeAppIfNeeded(ctx context.Context, linters []linter.Config, cfg *con
76163

77164
bctx := pkgProg.BuildContext()
78165
loadcfg := &loader.Config{
79-
Build: bctx,
80-
AllowErrors: true, // Try to analyze partially
81-
ParserMode: parser.ParseComments, // AST will be reused by linters
166+
Build: bctx,
167+
AllowErrors: true, // Try to analyze partially
168+
ParserMode: parser.ParseComments, // AST will be reused by linters
169+
TypeCheckFuncBodies: getTypeCheckFuncBodies(cfg, linters, pkgProg),
82170
}
83171

84172
var loaderArgs []string

Diff for: pkg/logutils/logutils.go

+4
Original file line numberDiff line numberDiff line change
@@ -51,3 +51,7 @@ func Debug(tag string) DebugFunc {
5151
func IsDebugEnabled() bool {
5252
return len(enabledDebugs) != 0
5353
}
54+
55+
func HaveDebugTag(tag string) bool {
56+
return enabledDebugs[tag]
57+
}

0 commit comments

Comments
 (0)