Skip to content

Commit 973c9fd

Browse files
committed
Fix #126: fix working with symlinks
1 parent ca558ca commit 973c9fd

File tree

15 files changed

+132
-89
lines changed

15 files changed

+132
-89
lines changed

Gopkg.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/fsutils/fsutils.go

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,22 +4,82 @@ import (
44
"fmt"
55
"os"
66
"path/filepath"
7+
"sync"
78
)
89

910
func IsDir(filename string) bool {
1011
fi, err := os.Stat(filename)
1112
return err == nil && fi.IsDir()
1213
}
1314

15+
var cachedWd string
16+
var cachedWdError error
17+
var getWdOnce sync.Once
18+
var useCache = true
19+
20+
func UseWdCache(use bool) {
21+
useCache = use
22+
}
23+
24+
func Getwd() (string, error) {
25+
if !useCache { // for tests
26+
return os.Getwd()
27+
}
28+
29+
getWdOnce.Do(func() {
30+
cachedWd, cachedWdError = os.Getwd()
31+
if cachedWdError != nil {
32+
return
33+
}
34+
35+
evaledWd, err := EvalSymlinks(cachedWd)
36+
if err != nil {
37+
cachedWd, cachedWdError = "", fmt.Errorf("can't eval symlinks on wd %s: %s", cachedWd, err)
38+
return
39+
}
40+
41+
cachedWd = evaledWd
42+
})
43+
44+
return cachedWd, cachedWdError
45+
}
46+
47+
var evalSymlinkCache sync.Map
48+
49+
type evalSymlinkRes struct {
50+
path string
51+
err error
52+
}
53+
54+
func EvalSymlinks(path string) (string, error) {
55+
r, ok := evalSymlinkCache.Load(path)
56+
if ok {
57+
er := r.(evalSymlinkRes)
58+
return er.path, er.err
59+
}
60+
61+
var er evalSymlinkRes
62+
er.path, er.err = filepath.EvalSymlinks(path)
63+
evalSymlinkCache.Store(path, er)
64+
65+
return er.path, er.err
66+
}
67+
1468
func ShortestRelPath(path string, wd string) (string, error) {
1569
if wd == "" { // get it if user don't have cached working dir
1670
var err error
17-
wd, err = os.Getwd()
71+
wd, err = Getwd()
1872
if err != nil {
1973
return "", fmt.Errorf("can't get working directory: %s", err)
2074
}
2175
}
2276

77+
evaledPath, err := EvalSymlinks(path)
78+
if err != nil {
79+
return "", fmt.Errorf("can't eval symlinks for path %s: %s", path, err)
80+
}
81+
path = evaledPath
82+
2383
// make path absolute and then relative to be able to fix this case:
2484
// we'are in /test dir, we want to normalize ../test, and have file file.go in this dir;
2585
// it must have normalized path file.go, not ../test/file.go,

pkg/golinters/govet.go

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,11 @@ import (
1010
"strings"
1111
"time"
1212

13+
"github.com/golangci/golangci-lint/pkg/fsutils"
1314
"github.com/golangci/golangci-lint/pkg/goutils"
1415
"github.com/golangci/golangci-lint/pkg/lint/linter"
1516
"github.com/golangci/golangci-lint/pkg/logutils"
1617
"github.com/golangci/golangci-lint/pkg/result"
17-
"github.com/golangci/golangci-lint/pkg/result/processors"
1818
"github.com/golangci/golangci-lint/pkg/timeutils"
1919
govetAPI "github.com/golangci/govet"
2020
)
@@ -82,7 +82,7 @@ func (g Govet) runOnInstalledPackages(ctx context.Context, lintCtx *linter.Conte
8282
continue
8383
}
8484
issues, err := govetAPI.Analyze(astFiles, fset, nil,
85-
lintCtx.Settings().Govet.CheckShadowing)
85+
lintCtx.Settings().Govet.CheckShadowing, getPath)
8686
if err != nil {
8787
return nil, err
8888
}
@@ -220,7 +220,7 @@ func runGoCommand(ctx context.Context, log logutils.Log, args ...string) error {
220220
func filterFiles(files []*ast.File, fset *token.FileSet) []*ast.File {
221221
newFiles := make([]*ast.File, 0, len(files))
222222
for _, f := range files {
223-
if !processors.IsCgoFilename(fset.Position(f.Pos()).Filename) {
223+
if !goutils.IsCgoFilename(fset.Position(f.Pos()).Filename) {
224224
newFiles = append(newFiles, f)
225225
}
226226
}
@@ -238,7 +238,7 @@ func (g Govet) runOnSourcePackages(_ context.Context, lintCtx *linter.Context) (
238238

239239
filteredFiles := filterFiles(pkg.Files, lintCtx.Program.Fset)
240240
issues, err := govetAPI.Analyze(filteredFiles, lintCtx.Program.Fset, pkg,
241-
lintCtx.Settings().Govet.CheckShadowing)
241+
lintCtx.Settings().Govet.CheckShadowing, getPath)
242242
if err != nil {
243243
return nil, err
244244
}
@@ -247,3 +247,7 @@ func (g Govet) runOnSourcePackages(_ context.Context, lintCtx *linter.Context) (
247247

248248
return govetIssues, nil
249249
}
250+
251+
func getPath(f *ast.File, fset *token.FileSet) (string, error) {
252+
return fsutils.ShortestRelPath(fset.Position(f.Pos()).Filename, "")
253+
}

pkg/goutils/goutils.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,11 @@ import (
44
"fmt"
55
"os"
66
"os/exec"
7+
"path/filepath"
78
"strings"
89
"sync"
10+
11+
"github.com/golangci/golangci-lint/pkg/fsutils"
912
)
1013

1114
var discoverGoRootOnce sync.Once
@@ -40,11 +43,15 @@ func InGoRoot() (bool, error) {
4043
return false, err
4144
}
4245

43-
wd, err := os.Getwd()
46+
wd, err := fsutils.Getwd()
4447
if err != nil {
4548
return false, err
4649
}
4750

4851
// TODO: strip, then add slashes
4952
return strings.HasPrefix(wd, goroot), nil
5053
}
54+
55+
func IsCgoFilename(f string) bool {
56+
return filepath.Base(f) == "C"
57+
}

pkg/lint/astcache/astcache.go

Lines changed: 11 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
package astcache
22

33
import (
4-
"fmt"
54
"go/ast"
65
"go/parser"
76
"go/token"
8-
"os"
97
"path/filepath"
108

9+
"github.com/golangci/golangci-lint/pkg/fsutils"
10+
"github.com/golangci/golangci-lint/pkg/goutils"
1111
"github.com/golangci/golangci-lint/pkg/logutils"
1212
"golang.org/x/tools/go/loader"
1313
)
@@ -65,27 +65,22 @@ func (c *Cache) prepareValidFiles() {
6565
func LoadFromProgram(prog *loader.Program, log logutils.Log) (*Cache, error) {
6666
c := NewCache(log)
6767

68-
root, err := os.Getwd()
69-
if err != nil {
70-
return nil, fmt.Errorf("can't get working dir: %s", err)
71-
}
72-
7368
for _, pkg := range prog.InitialPackages() {
7469
for _, f := range pkg.Files {
7570
pos := prog.Fset.Position(f.Pos())
7671
if pos.Filename == "" {
7772
continue
7873
}
7974

80-
path := pos.Filename
81-
if filepath.IsAbs(path) {
82-
relPath, err := filepath.Rel(root, pos.Filename)
83-
if err != nil {
84-
c.log.Warnf("Can't get relative path for %s and %s: %s",
85-
root, pos.Filename, err)
86-
continue
87-
}
88-
path = relPath
75+
if goutils.IsCgoFilename(pos.Filename) {
76+
continue
77+
}
78+
79+
path, err := fsutils.ShortestRelPath(pos.Filename, "")
80+
if err != nil {
81+
c.log.Warnf("Can't get relative path for %s: %s",
82+
pos.Filename, err)
83+
continue
8984
}
9085

9186
c.m[path] = &File{

pkg/lint/load.go

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"strings"
1111
"time"
1212

13+
"github.com/golangci/golangci-lint/pkg/fsutils"
1314
"github.com/golangci/golangci-lint/pkg/goutils"
1415
"github.com/golangci/golangci-lint/pkg/logutils"
1516

@@ -50,21 +51,13 @@ func isSSAReprNeeded(linters []linter.Config) bool {
5051
}
5152

5253
func normalizePaths(paths []string) ([]string, error) {
53-
root, err := os.Getwd()
54-
if err != nil {
55-
return nil, fmt.Errorf("can't get working dir: %s", err)
56-
}
57-
5854
ret := make([]string, 0, len(paths))
5955
for _, p := range paths {
60-
if filepath.IsAbs(p) {
61-
relPath, err := filepath.Rel(root, p)
62-
if err != nil {
63-
return nil, fmt.Errorf("can't get relative path for path %s and root %s: %s",
64-
p, root, err)
65-
}
66-
p = relPath
56+
relPath, err := fsutils.ShortestRelPath(p, "")
57+
if err != nil {
58+
return nil, fmt.Errorf("can't get relative path for path %s: %s", p, err)
6759
}
60+
p = relPath
6861

6962
ret = append(ret, "./"+p)
7063
}
@@ -78,7 +71,7 @@ func getCurrentProjectImportPath() (string, error) {
7871
return "", fmt.Errorf("no GOPATH env variable")
7972
}
8073

81-
wd, err := os.Getwd()
74+
wd, err := fsutils.Getwd()
8275
if err != nil {
8376
return "", fmt.Errorf("can't get workind directory: %s", err)
8477
}

pkg/packages/package.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import (
44
"go/build"
55
"path/filepath"
66

7-
"github.com/golangci/golangci-lint/pkg/result/processors"
7+
"github.com/golangci/golangci-lint/pkg/goutils"
88
)
99

1010
type Package struct {
@@ -17,7 +17,7 @@ type Package struct {
1717
func (pkg *Package) Files(includeTest bool) []string {
1818
var pkgFiles []string
1919
for _, f := range pkg.bp.GoFiles {
20-
if !processors.IsCgoFilename(f) {
20+
if !goutils.IsCgoFilename(f) {
2121
// skip cgo at all levels to prevent failures on file reading
2222
pkgFiles = append(pkgFiles, f)
2323
}

pkg/packages/resolver.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ func NewResolver(buildTags, excludeDirs []string, log logutils.Log) (*Resolver,
3636
excludeDirsMap[dir] = re
3737
}
3838

39-
wd, err := os.Getwd()
39+
wd, err := fsutils.Getwd()
4040
if err != nil {
4141
return nil, fmt.Errorf("can't get working dir: %s", err)
4242
}

pkg/packages/resolver_test.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"strings"
99
"testing"
1010

11+
"github.com/golangci/golangci-lint/pkg/fsutils"
1112
"github.com/golangci/golangci-lint/pkg/logutils"
1213
"github.com/golangci/golangci-lint/pkg/packages"
1314
"github.com/stretchr/testify/assert"
@@ -31,7 +32,7 @@ func prepareFS(t *testing.T, paths ...string) *fsPreparer {
3132
root, err := ioutil.TempDir("/tmp", "golangci.test.path_resolver")
3233
assert.NoError(t, err)
3334

34-
prevWD, err := os.Getwd()
35+
prevWD, err := fsutils.Getwd()
3536
assert.NoError(t, err)
3637

3738
err = os.Chdir(root)
@@ -243,6 +244,7 @@ func TestPathResolverCommonCases(t *testing.T) {
243244
},
244245
}
245246

247+
fsutils.UseWdCache(false)
246248
for _, tc := range testCases {
247249
t.Run(tc.name, func(t *testing.T) {
248250
fp := prepareFS(t, tc.prepare...)

pkg/result/processors/cgo.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package processors
22

33
import (
4+
"github.com/golangci/golangci-lint/pkg/goutils"
45
"github.com/golangci/golangci-lint/pkg/result"
56
)
67

@@ -21,7 +22,7 @@ func (p Cgo) Process(issues []result.Issue) ([]result.Issue, error) {
2122
return filterIssues(issues, func(i *result.Issue) bool {
2223
// some linters (.e.g gas, deadcode) return incorrect filepaths for cgo issues,
2324
// it breaks next processing, so skip them
24-
return !IsCgoFilename(i.FilePath())
25+
return !goutils.IsCgoFilename(i.FilePath())
2526
}), nil
2627
}
2728

pkg/result/processors/path_prettifier.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@ package processors
22

33
import (
44
"fmt"
5-
"os"
65
"path/filepath"
76

7+
"github.com/golangci/golangci-lint/pkg/fsutils"
88
"github.com/golangci/golangci-lint/pkg/result"
99
)
1010

@@ -15,7 +15,7 @@ type PathPrettifier struct {
1515
var _ Processor = PathPrettifier{}
1616

1717
func NewPathPrettifier() *PathPrettifier {
18-
root, err := os.Getwd()
18+
root, err := fsutils.Getwd()
1919
if err != nil {
2020
panic(fmt.Sprintf("Can't get working dir: %s", err))
2121
}
@@ -34,7 +34,7 @@ func (p PathPrettifier) Process(issues []result.Issue) ([]result.Issue, error) {
3434
return i
3535
}
3636

37-
rel, err := filepath.Rel(p.root, i.FilePath())
37+
rel, err := fsutils.ShortestRelPath(i.FilePath(), "")
3838
if err != nil {
3939
return i
4040
}

pkg/result/processors/utils.go

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package processors
22

33
import (
44
"fmt"
5-
"path/filepath"
65

76
"github.com/golangci/golangci-lint/pkg/result"
87
)
@@ -45,7 +44,3 @@ func transformIssues(issues []result.Issue, transform func(i *result.Issue) *res
4544

4645
return retIssues
4746
}
48-
49-
func IsCgoFilename(f string) bool {
50-
return filepath.Base(f) == "C"
51-
}

0 commit comments

Comments
 (0)