www.github.com/golangci/golangci-lint.git@v1.10.1/pkg/lint/load.go (about) 1 package lint 2 3 import ( 4 "fmt" 5 "go/build" 6 "go/parser" 7 "go/types" 8 "os" 9 "path/filepath" 10 "strings" 11 "time" 12 13 "github.com/golangci/golangci-lint/pkg/exitcodes" 14 "github.com/golangci/golangci-lint/pkg/fsutils" 15 "github.com/golangci/golangci-lint/pkg/goutils" 16 "github.com/golangci/golangci-lint/pkg/logutils" 17 18 "github.com/golangci/golangci-lint/pkg/config" 19 "github.com/golangci/golangci-lint/pkg/lint/astcache" 20 "github.com/golangci/golangci-lint/pkg/lint/linter" 21 "github.com/golangci/golangci-lint/pkg/packages" 22 "golang.org/x/tools/go/loader" 23 "golang.org/x/tools/go/ssa" 24 "golang.org/x/tools/go/ssa/ssautil" 25 ) 26 27 var loadDebugf = logutils.Debug("load") 28 29 func isFullImportNeeded(linters []linter.Config, cfg *config.Config) bool { 30 for _, linter := range linters { 31 if linter.NeedsProgramLoading() { 32 if linter.Linter.Name() == "govet" && cfg.LintersSettings.Govet.UseInstalledPackages { 33 // TODO: remove this hack 34 continue 35 } 36 37 return true 38 } 39 } 40 41 return false 42 } 43 44 func isSSAReprNeeded(linters []linter.Config) bool { 45 for _, linter := range linters { 46 if linter.NeedsSSARepresentation() { 47 return true 48 } 49 } 50 51 return false 52 } 53 54 func normalizePaths(paths []string) ([]string, error) { 55 ret := make([]string, 0, len(paths)) 56 for _, p := range paths { 57 relPath, err := fsutils.ShortestRelPath(p, "") 58 if err != nil { 59 return nil, fmt.Errorf("can't get relative path for path %s: %s", p, err) 60 } 61 p = relPath 62 63 ret = append(ret, "./"+p) 64 } 65 66 return ret, nil 67 } 68 69 func getCurrentProjectImportPath() (string, error) { 70 gopath := os.Getenv("GOPATH") 71 if gopath == "" { 72 return "", fmt.Errorf("no GOPATH env variable") 73 } 74 75 wd, err := fsutils.Getwd() 76 if err != nil { 77 return "", fmt.Errorf("can't get workind directory: %s", err) 78 } 79 80 if !strings.HasPrefix(wd, gopath) { 81 return "", fmt.Errorf("currently no in gopath: %q isn't a prefix of %q", gopath, wd) 82 } 83 84 path := strings.TrimPrefix(wd, gopath) 85 path = strings.TrimPrefix(path, string(os.PathSeparator)) // if GOPATH contains separator at the end 86 src := "src" + string(os.PathSeparator) 87 if !strings.HasPrefix(path, src) { 88 return "", fmt.Errorf("currently no in gopath/src: %q isn't a prefix of %q", src, path) 89 } 90 91 path = strings.TrimPrefix(path, src) 92 path = strings.Replace(path, string(os.PathSeparator), "/", -1) 93 return path, nil 94 } 95 96 func isLocalProjectAnalysis(args []string) bool { 97 for _, arg := range args { 98 if strings.HasPrefix(arg, "..") || filepath.IsAbs(arg) { 99 return false 100 } 101 } 102 103 return true 104 } 105 106 func getTypeCheckFuncBodies(cfg *config.Run, linters []linter.Config, 107 pkgProg *packages.Program, log logutils.Log) func(string) bool { 108 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 log.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 154 func loadWholeAppIfNeeded(linters []linter.Config, cfg *config.Config, 155 pkgProg *packages.Program, log logutils.Log) (*loader.Program, *loader.Config, error) { 156 157 if !isFullImportNeeded(linters, cfg) { 158 return nil, nil, nil 159 } 160 161 startedAt := time.Now() 162 defer func() { 163 log.Infof("Program loading took %s", time.Since(startedAt)) 164 }() 165 166 bctx := pkgProg.BuildContext() 167 loadcfg := &loader.Config{ 168 Build: bctx, 169 AllowErrors: true, // Try to analyze partially 170 ParserMode: parser.ParseComments, // AST will be reused by linters 171 TypeCheckFuncBodies: getTypeCheckFuncBodies(&cfg.Run, linters, pkgProg, log), 172 TypeChecker: types.Config{ 173 Sizes: types.SizesFor(build.Default.Compiler, build.Default.GOARCH), 174 }, 175 } 176 177 var loaderArgs []string 178 dirs := pkgProg.Dirs() 179 if len(dirs) != 0 { 180 loaderArgs = dirs // dirs run 181 } else { 182 loaderArgs = pkgProg.Files(cfg.Run.AnalyzeTests) // files run 183 } 184 185 nLoaderArgs, err := normalizePaths(loaderArgs) 186 if err != nil { 187 return nil, nil, err 188 } 189 190 rest, err := loadcfg.FromArgs(nLoaderArgs, cfg.Run.AnalyzeTests) 191 if err != nil { 192 return nil, nil, fmt.Errorf("can't parepare load config with paths: %s", err) 193 } 194 if len(rest) > 0 { 195 return nil, nil, fmt.Errorf("unhandled loading paths: %v", rest) 196 } 197 198 prog, err := loadcfg.Load() 199 if err != nil { 200 return nil, nil, fmt.Errorf("can't load program from paths %v: %s", loaderArgs, err) 201 } 202 203 if len(prog.InitialPackages()) == 1 { 204 pkg := prog.InitialPackages()[0] 205 var files []string 206 for _, f := range pkg.Files { 207 files = append(files, prog.Fset.Position(f.Pos()).Filename) 208 } 209 log.Infof("pkg %s files: %s", pkg, files) 210 } 211 212 return prog, loadcfg, nil 213 } 214 215 func buildSSAProgram(lprog *loader.Program, log logutils.Log) *ssa.Program { 216 startedAt := time.Now() 217 defer func() { 218 log.Infof("SSA repr building took %s", time.Since(startedAt)) 219 }() 220 221 ssaProg := ssautil.CreateProgram(lprog, ssa.GlobalDebug) 222 ssaProg.Build() 223 return ssaProg 224 } 225 226 // separateNotCompilingPackages moves not compiling packages into separate slices: 227 // a lot of linters crash on such packages. Leave them only for those linters 228 // which can work with them. 229 //nolint:gocyclo 230 func separateNotCompilingPackages(lintCtx *linter.Context) { 231 prog := lintCtx.Program 232 233 notCompilingPackagesSet := map[*loader.PackageInfo]bool{} 234 235 if prog.Created != nil { 236 compilingCreated := make([]*loader.PackageInfo, 0, len(prog.Created)) 237 for _, info := range prog.Created { 238 if len(info.Errors) != 0 { 239 lintCtx.NotCompilingPackages = append(lintCtx.NotCompilingPackages, info) 240 notCompilingPackagesSet[info] = true 241 } else { 242 compilingCreated = append(compilingCreated, info) 243 } 244 } 245 prog.Created = compilingCreated 246 } 247 248 if prog.Imported != nil { 249 for k, info := range prog.Imported { 250 if len(info.Errors) == 0 { 251 continue 252 } 253 254 lintCtx.NotCompilingPackages = append(lintCtx.NotCompilingPackages, info) 255 notCompilingPackagesSet[info] = true 256 delete(prog.Imported, k) 257 } 258 } 259 260 if prog.AllPackages != nil { 261 for k, info := range prog.AllPackages { 262 if len(info.Errors) == 0 { 263 continue 264 } 265 266 if !notCompilingPackagesSet[info] { 267 lintCtx.NotCompilingPackages = append(lintCtx.NotCompilingPackages, info) 268 notCompilingPackagesSet[info] = true 269 } 270 delete(prog.AllPackages, k) 271 } 272 } 273 274 if len(lintCtx.NotCompilingPackages) != 0 { 275 lintCtx.Log.Infof("Not compiling packages: %+v", lintCtx.NotCompilingPackages) 276 } 277 } 278 279 //nolint:gocyclo 280 func LoadContext(linters []linter.Config, cfg *config.Config, log logutils.Log) (*linter.Context, error) { 281 // Set GOROOT to have working cross-compilation: cross-compiled binaries 282 // have invalid GOROOT. XXX: can't use runtime.GOROOT(). 283 goroot, err := goutils.DiscoverGoRoot() 284 if err != nil { 285 return nil, fmt.Errorf("can't discover GOROOT: %s", err) 286 } 287 os.Setenv("GOROOT", goroot) 288 build.Default.GOROOT = goroot 289 290 args := cfg.Run.Args 291 if len(args) == 0 { 292 args = []string{"./..."} 293 } 294 295 skipDirs := append([]string{}, packages.StdExcludeDirRegexps...) 296 skipDirs = append(skipDirs, cfg.Run.SkipDirs...) 297 r, err := packages.NewResolver(cfg.Run.BuildTags, skipDirs, log.Child("path_resolver")) 298 if err != nil { 299 return nil, err 300 } 301 302 pkgProg, err := r.Resolve(args...) 303 if err != nil { 304 return nil, err 305 } 306 307 if len(pkgProg.Packages()) == 0 { 308 return nil, exitcodes.ErrNoGoFiles 309 } 310 311 prog, loaderConfig, err := loadWholeAppIfNeeded(linters, cfg, pkgProg, log) 312 if err != nil { 313 return nil, err 314 } 315 316 var ssaProg *ssa.Program 317 if prog != nil && isSSAReprNeeded(linters) { 318 ssaProg = buildSSAProgram(prog, log) 319 } 320 321 astLog := log.Child("astcache") 322 var astCache *astcache.Cache 323 if prog != nil { 324 astCache, err = astcache.LoadFromProgram(prog, astLog) 325 } else { 326 astCache, err = astcache.LoadFromFiles(pkgProg.Files(cfg.Run.AnalyzeTests), astLog) 327 } 328 if err != nil { 329 return nil, err 330 } 331 332 ret := &linter.Context{ 333 PkgProgram: pkgProg, 334 Cfg: cfg, 335 Program: prog, 336 SSAProgram: ssaProg, 337 LoaderConfig: loaderConfig, 338 ASTCache: astCache, 339 Log: log, 340 } 341 342 if prog != nil { 343 separateNotCompilingPackages(ret) 344 } 345 346 return ret, nil 347 }