github.com/mxk/go-gomod@v0.1.1/gomod.go (about) 1 // Package gomod provides information about modules compiled into the binary. 2 package gomod 3 4 import ( 5 "os" 6 "reflect" 7 "runtime" 8 "sort" 9 "strings" 10 "sync" 11 "sync/atomic" 12 _ "unsafe" // For go:linkname 13 ) 14 15 // File returns the file that defines function fn. 16 func File(fn interface{}) string { 17 v := reflect.ValueOf(fn) 18 if v.Kind() != reflect.Func { 19 panic("gomod: not a function: " + v.Type().String()) 20 } 21 pc := v.Pointer() 22 file, _ := runtime.FuncForPC(pc).FileLine(pc) 23 return file 24 } 25 26 // Module is a file system path to a module directory. The entire path is only 27 // valid on the system where the module was compiled, but it is used for 28 // extracting module name and version. All methods return empty strings if the 29 // module was not found. 30 type Module struct { 31 path string 32 base int 33 at int 34 } 35 36 // All returns all modules compiled into the current binary, sorted by name. 37 func All() []Module { 38 names := names() 39 all := make([]Module, 0, len(names)) 40 for _, name := range names { 41 all = append(all, modMap[name]) 42 } 43 return all 44 } 45 46 // Get returns the named module. 47 func Get(name string) Module { 48 once.Do(load) 49 return modMap[name] 50 } 51 52 // Root returns the module of function fn. 53 func Root(fn interface{}) Module { 54 file := File(fn) 55 if base, at := parse(file); at > 0 { 56 return Get(file[base:at]) 57 } 58 return Module{} 59 } 60 61 // Path returns the module root directory. 62 func (m Module) Path() string { return m.path } 63 64 // Name returns the module name. 65 func (m Module) Name() string { return m.path[m.base:m.at] } 66 67 // Version returns the module version without the 'v' prefix. 68 func (m Module) Version() string { 69 if v := m.at + 2; v < len(m.path) { 70 return strings.TrimSuffix(m.path[v:], "+incompatible") 71 } 72 return "" 73 } 74 75 var ( 76 once sync.Once 77 modMap map[string]Module 78 nameCache atomic.Value 79 ) 80 81 //go:linkname fmd runtime.firstmoduledata 82 var fmd struct { 83 pclntable []byte 84 ftab []struct{ entry, funcoff uintptr } 85 filetab []uint32 86 } 87 88 //go:linkname gostringnocopy runtime.gostringnocopy 89 func gostringnocopy(_ *byte) string 90 91 // load finds all modules compiled into the current binary. 92 func load() { 93 modMap = make(map[string]Module, len(fmd.filetab)>>5) 94 for _, off := range fmd.filetab { 95 file := gostringnocopy(&fmd.pclntable[off]) 96 if base, at := parse(file); at > 0 { 97 name := file[base:at] 98 if _, ok := modMap[name]; !ok { 99 modMap[name] = Module{trim(file, at), base, at} 100 } 101 } 102 } 103 } 104 105 // names returns all module names in sorted order. 106 func names() []string { 107 once.Do(load) 108 names, _ := nameCache.Load().([]string) 109 if names == nil { 110 names = make([]string, 0, len(modMap)) 111 for name := range modMap { 112 names = append(names, name) 113 } 114 sort.Strings(names) 115 nameCache.Store(names) 116 } 117 return names 118 } 119 120 // parse returns the indices for the module name and '@' in path p. 121 func parse(p string) (base, at int) { 122 for { 123 if at = strings.IndexByte(p[base:], '@'); at < 0 { 124 return 0, 0 125 } 126 if at += base; 0 < at && at+2 < len(p) && p[at+1] == 'v' && 127 !os.IsPathSeparator(p[at-1]) { 128 if v := p[at+2]; '0' <= v && v <= '9' { 129 for base = at - 2; base >= 0; base-- { 130 if os.IsPathSeparator(p[base]) { 131 break 132 } 133 } 134 return base + 1, at 135 } 136 } 137 base = at + 1 138 } 139 } 140 141 // trim removes all path components after the module root directory. 142 func trim(path string, at int) string { 143 for i, c := range []byte(path[at:]) { 144 if os.IsPathSeparator(c) { 145 return path[:at+i] 146 } 147 } 148 return path 149 }