1// Copyright 2018 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package imports
6
7import (
8	"fmt"
9	"io/fs"
10	"path/filepath"
11	"sort"
12	"strconv"
13	"strings"
14
15	"cmd/go/internal/fsys"
16)
17
18func ScanDir(dir string, tags map[string]bool) ([]string, []string, error) {
19	infos, err := fsys.ReadDir(dir)
20	if err != nil {
21		return nil, nil, err
22	}
23	var files []string
24	for _, info := range infos {
25		name := info.Name()
26
27		// If the directory entry is a symlink, stat it to obtain the info for the
28		// link target instead of the link itself.
29		if info.Mode()&fs.ModeSymlink != 0 {
30			info, err = fsys.Stat(filepath.Join(dir, name))
31			if err != nil {
32				continue // Ignore broken symlinks.
33			}
34		}
35
36		if info.Mode().IsRegular() && !strings.HasPrefix(name, "_") && !strings.HasPrefix(name, ".") && strings.HasSuffix(name, ".go") && MatchFile(name, tags) {
37			files = append(files, filepath.Join(dir, name))
38		}
39	}
40	return scanFiles(files, tags, false)
41}
42
43func ScanFiles(files []string, tags map[string]bool) ([]string, []string, error) {
44	return scanFiles(files, tags, true)
45}
46
47func scanFiles(files []string, tags map[string]bool, explicitFiles bool) ([]string, []string, error) {
48	imports := make(map[string]bool)
49	testImports := make(map[string]bool)
50	numFiles := 0
51Files:
52	for _, name := range files {
53		r, err := fsys.Open(name)
54		if err != nil {
55			return nil, nil, err
56		}
57		var list []string
58		data, err := ReadImports(r, false, &list)
59		r.Close()
60		if err != nil {
61			return nil, nil, fmt.Errorf("reading %s: %v", name, err)
62		}
63
64		// import "C" is implicit requirement of cgo tag.
65		// When listing files on the command line (explicitFiles=true)
66		// we do not apply build tag filtering but we still do apply
67		// cgo filtering, so no explicitFiles check here.
68		// Why? Because we always have, and it's not worth breaking
69		// that behavior now.
70		for _, path := range list {
71			if path == `"C"` && !tags["cgo"] && !tags["*"] {
72				continue Files
73			}
74		}
75
76		if !explicitFiles && !ShouldBuild(data, tags) {
77			continue
78		}
79		numFiles++
80		m := imports
81		if strings.HasSuffix(name, "_test.go") {
82			m = testImports
83		}
84		for _, p := range list {
85			q, err := strconv.Unquote(p)
86			if err != nil {
87				continue
88			}
89			m[q] = true
90		}
91	}
92	if numFiles == 0 {
93		return nil, nil, ErrNoGo
94	}
95	return keys(imports), keys(testImports), nil
96}
97
98var ErrNoGo = fmt.Errorf("no Go source files")
99
100func keys(m map[string]bool) []string {
101	var list []string
102	for k := range m {
103		list = append(list, k)
104	}
105	sort.Strings(list)
106	return list
107}
108