-
Notifications
You must be signed in to change notification settings - Fork 0
/
mod.go
142 lines (130 loc) · 3.47 KB
/
mod.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
package mod
import (
"errors"
"fmt"
"go/build"
"io/fs"
"os"
"path/filepath"
"strings"
"golang.org/x/mod/modfile"
)
// ErrFileNotFound occurs when no go.mod can be found
var ErrFileNotFound = fmt.Errorf(`mod: unable to find "go.mod": %w`, fs.ErrNotExist)
// Build with:
//
// go build -trimpath -ldflags " -X github.com/livebud/mod.path=$(go list -m) -X github.com/livebud/mod.dir=$(go list -m -f {{.Dir}})" ./
var (
path string
dir string
isEmbedded = path != "" && dir != ""
)
// New module
func New(dir string) *Module {
modulePath := modulePathFromGoPath(dir)
if modulePath == "" {
modulePath = "change.me"
}
module, err := Parse(filepath.Join(dir, "go.mod"), []byte(`module `+modulePath))
if err != nil {
panic("mod: invalid module data: " + err.Error())
}
return module
}
// Find the first go.mod file in one of the directories below or return an
// error. Find will also search parent directories for a go.mod file.
func Find(dirs ...string) (*Module, error) {
if len(dirs) == 0 {
return find(".")
}
for _, dir := range dirs {
module, err := find(dir)
if err != nil {
if !errors.Is(err, ErrFileNotFound) {
return nil, err
}
continue
}
return module, nil
}
return nil, ErrFileNotFound
}
func find(dir string) (*Module, error) {
if isEmbedded {
return Parse(filepath.Join(dir, "go.mod"), []byte(`module `+path))
}
abs, err := filepath.Abs(dir)
if err != nil {
return nil, err
}
modPath, err := lookup(abs)
if err != nil {
return nil, err
}
data, err := os.ReadFile(modPath)
if err != nil {
return nil, fmt.Errorf(`mod: unable to read "go.mod": %w`, err)
}
return Parse(modPath, data)
}
// MustFind a go.mod file in this directory or any parent directory. If no
// go.mod file is found, this will panic.
func MustFind(dirs ...string) *Module {
module, err := Find(dirs...)
if err != nil {
panic(err)
}
return module
}
// Parse a go.mod file
func Parse(path string, data []byte) (*Module, error) {
modfile, err := modfile.Parse(path, data, nil)
if err != nil {
return nil, err
}
if modfile.Module == nil {
modFile, err := modfile.Format()
if err != nil {
return nil, fmt.Errorf("mod: missing module statement in %q and got an error while formatting %s", path, err)
}
return nil, fmt.Errorf("mod: missing module statement in %q, received %q", path, string(modFile))
}
dir := filepath.Dir(path)
return &Module{dir, modfile, os.DirFS(dir)}, nil
}
// Lookup finds the absolute path of the go.mod file in the given directory
func Lookup(directory string) (path string, err error) {
if dir != "" {
return filepath.Join(dir, "go.mod"), nil
}
path, err = lookup(directory)
if err != nil {
return "", err
}
return filepath.Abs(path)
}
func lookup(directory string) (path string, err error) {
path = filepath.Join(directory, "go.mod")
// Check if this path exists, otherwise recursively traverse towards root
if _, err = os.Stat(path); err != nil {
if !errors.Is(err, fs.ErrNotExist) {
return "", err
}
nextDir := filepath.Dir(directory)
if nextDir == directory {
return "", ErrFileNotFound
}
return lookup(nextDir)
}
return filepath.EvalSymlinks(path)
}
// modulePathFromGoPath tries inferring the module path of directory. This only
// works if you're in working within the $GOPATH
func modulePathFromGoPath(path string) string {
src := filepath.Join(build.Default.GOPATH, "src") + "/"
if !strings.HasPrefix(path, src) {
return ""
}
modulePath := strings.TrimPrefix(path, src)
return modulePath
}