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  }