Source file src/cmd/go/internal/modload/build.go

     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  
     5  package modload
     6  
     7  import (
     8  	"context"
     9  	"encoding/hex"
    10  	"errors"
    11  	"fmt"
    12  	"io/fs"
    13  	"os"
    14  	"path/filepath"
    15  
    16  	"cmd/go/internal/base"
    17  	"cmd/go/internal/cfg"
    18  	"cmd/go/internal/gover"
    19  	"cmd/go/internal/modfetch"
    20  	"cmd/go/internal/modfetch/codehost"
    21  	"cmd/go/internal/modindex"
    22  	"cmd/go/internal/modinfo"
    23  	"cmd/go/internal/search"
    24  
    25  	"golang.org/x/mod/module"
    26  )
    27  
    28  var (
    29  	infoStart, _ = hex.DecodeString("3077af0c9274080241e1c107e6d618e6")
    30  	infoEnd, _   = hex.DecodeString("f932433186182072008242104116d8f2")
    31  )
    32  
    33  func isStandardImportPath(path string) bool {
    34  	return findStandardImportPath(path) != ""
    35  }
    36  
    37  func findStandardImportPath(path string) string {
    38  	if path == "" {
    39  		panic("findStandardImportPath called with empty path")
    40  	}
    41  	if search.IsStandardImportPath(path) {
    42  		if modindex.IsStandardPackage(cfg.GOROOT, cfg.BuildContext.Compiler, path) {
    43  			return filepath.Join(cfg.GOROOT, "src", path)
    44  		}
    45  	}
    46  	return ""
    47  }
    48  
    49  // PackageModuleInfo returns information about the module that provides
    50  // a given package. If modules are not enabled or if the package is in the
    51  // standard library or if the package was not successfully loaded with
    52  // LoadPackages or ImportFromFiles, nil is returned.
    53  func PackageModuleInfo(loaderstate *State, ctx context.Context, pkgpath string) *modinfo.ModulePublic {
    54  	if isStandardImportPath(pkgpath) || !loaderstate.Enabled() {
    55  		return nil
    56  	}
    57  	m, ok := findModule(loaded, pkgpath)
    58  	if !ok {
    59  		return nil
    60  	}
    61  
    62  	rs := LoadModFile(loaderstate, ctx)
    63  	return moduleInfo(loaderstate, ctx, rs, m, 0, nil)
    64  }
    65  
    66  // PackageModRoot returns the module root directory for the module that provides
    67  // a given package. If modules are not enabled or if the package is in the
    68  // standard library or if the package was not successfully loaded with
    69  // LoadPackages or ImportFromFiles, the empty string is returned.
    70  func PackageModRoot(loaderstate *State, ctx context.Context, pkgpath string) string {
    71  	if isStandardImportPath(pkgpath) || !loaderstate.Enabled() || cfg.BuildMod == "vendor" {
    72  		return ""
    73  	}
    74  	m, ok := findModule(loaded, pkgpath)
    75  	if !ok {
    76  		return ""
    77  	}
    78  	root, _, err := fetch(loaderstate, ctx, m)
    79  	if err != nil {
    80  		return ""
    81  	}
    82  	return root
    83  }
    84  
    85  func ModuleInfo(loaderstate *State, ctx context.Context, path string) *modinfo.ModulePublic {
    86  	if !loaderstate.Enabled() {
    87  		return nil
    88  	}
    89  
    90  	path, vers, found, err := ParsePathVersion(path)
    91  	if err != nil {
    92  		return &modinfo.ModulePublic{
    93  			Path: path,
    94  			Error: &modinfo.ModuleError{
    95  				Err: err.Error(),
    96  			},
    97  		}
    98  	}
    99  	if found {
   100  		m := module.Version{Path: path, Version: vers}
   101  		return moduleInfo(loaderstate, ctx, nil, m, 0, nil)
   102  	}
   103  
   104  	rs := LoadModFile(loaderstate, ctx)
   105  
   106  	var (
   107  		v  string
   108  		ok bool
   109  	)
   110  	if rs.pruning == pruned {
   111  		v, ok = rs.rootSelected(loaderstate, path)
   112  	}
   113  	if !ok {
   114  		mg, err := rs.Graph(loaderstate, ctx)
   115  		if err != nil {
   116  			base.Fatal(err)
   117  		}
   118  		v = mg.Selected(path)
   119  	}
   120  
   121  	if v == "none" {
   122  		return &modinfo.ModulePublic{
   123  			Path: path,
   124  			Error: &modinfo.ModuleError{
   125  				Err: "module not in current build",
   126  			},
   127  		}
   128  	}
   129  
   130  	return moduleInfo(loaderstate, ctx, rs, module.Version{Path: path, Version: v}, 0, nil)
   131  }
   132  
   133  // addUpdate fills in m.Update if an updated version is available.
   134  func addUpdate(loaderstate *State, ctx context.Context, m *modinfo.ModulePublic) {
   135  	if m.Version == "" {
   136  		return
   137  	}
   138  
   139  	info, err := Query(loaderstate, ctx, m.Path, "upgrade", m.Version, loaderstate.CheckAllowed)
   140  	if _, ok := errors.AsType[*NoMatchingVersionError](err); ok ||
   141  		errors.Is(err, fs.ErrNotExist) ||
   142  		errors.Is(err, ErrDisallowed) {
   143  		// Ignore "no matching version" and "not found" errors.
   144  		// This means the proxy has no matching version or no versions at all.
   145  		//
   146  		// Ignore "disallowed" errors. This means the current version is
   147  		// excluded or retracted and there are no higher allowed versions.
   148  		//
   149  		// We should report other errors though. An attacker that controls the
   150  		// network shouldn't be able to hide versions by interfering with
   151  		// the HTTPS connection. An attacker that controls the proxy may still
   152  		// hide versions, since the "list" and "latest" endpoints are not
   153  		// authenticated.
   154  		return
   155  	} else if err != nil {
   156  		if m.Error == nil {
   157  			m.Error = &modinfo.ModuleError{Err: err.Error()}
   158  		}
   159  		return
   160  	}
   161  
   162  	if gover.ModCompare(m.Path, info.Version, m.Version) > 0 {
   163  		m.Update = &modinfo.ModulePublic{
   164  			Path:    m.Path,
   165  			Version: info.Version,
   166  			Time:    &info.Time,
   167  		}
   168  	}
   169  }
   170  
   171  // mergeOrigin returns the union of data from two origins,
   172  // returning either a new origin or one of its unmodified arguments.
   173  // If the two origins conflict including if either is nil,
   174  // mergeOrigin returns nil.
   175  func mergeOrigin(m1, m2 *codehost.Origin) *codehost.Origin {
   176  	if m1 == nil || m2 == nil {
   177  		return nil
   178  	}
   179  
   180  	if m2.VCS != m1.VCS ||
   181  		m2.URL != m1.URL ||
   182  		m2.Subdir != m1.Subdir {
   183  		return nil
   184  	}
   185  
   186  	merged := *m1
   187  	if m2.Hash != "" {
   188  		if m1.Hash != "" && m1.Hash != m2.Hash {
   189  			return nil
   190  		}
   191  		merged.Hash = m2.Hash
   192  	}
   193  	if m2.TagSum != "" {
   194  		if m1.TagSum != "" && (m1.TagSum != m2.TagSum || m1.TagPrefix != m2.TagPrefix) {
   195  			return nil
   196  		}
   197  		merged.TagSum = m2.TagSum
   198  		merged.TagPrefix = m2.TagPrefix
   199  	}
   200  	if m2.RepoSum != "" {
   201  		if m1.RepoSum != "" && m1.RepoSum != m2.RepoSum {
   202  			return nil
   203  		}
   204  		merged.RepoSum = m2.RepoSum
   205  	}
   206  	if m2.Ref != "" {
   207  		if m1.Ref != "" && m1.Ref != m2.Ref {
   208  			return nil
   209  		}
   210  		merged.Ref = m2.Ref
   211  	}
   212  
   213  	switch {
   214  	case merged == *m1:
   215  		return m1
   216  	case merged == *m2:
   217  		return m2
   218  	default:
   219  		// Clone the result to avoid an alloc for merged
   220  		// if the result is equal to one of the arguments.
   221  		clone := merged
   222  		return &clone
   223  	}
   224  }
   225  
   226  // addVersions fills in m.Versions with the list of known versions.
   227  // Excluded versions will be omitted. If listRetracted is false, retracted
   228  // versions will also be omitted.
   229  func addVersions(loaderstate *State, ctx context.Context, m *modinfo.ModulePublic, listRetracted bool) {
   230  	// TODO(bcmills): Would it make sense to check for reuse here too?
   231  	// Perhaps that doesn't buy us much, though: we would always have to fetch
   232  	// all of the version tags to list the available versions anyway.
   233  
   234  	allowed := loaderstate.CheckAllowed
   235  	if listRetracted {
   236  		allowed = loaderstate.CheckExclusions
   237  	}
   238  	v, origin, err := versions(loaderstate, ctx, m.Path, allowed)
   239  	if err != nil && m.Error == nil {
   240  		m.Error = &modinfo.ModuleError{Err: err.Error()}
   241  	}
   242  	m.Versions = v
   243  	m.Origin = mergeOrigin(m.Origin, origin)
   244  }
   245  
   246  // addRetraction fills in m.Retracted if the module was retracted by its author.
   247  // m.Error is set if there's an error loading retraction information.
   248  func addRetraction(loaderstate *State, ctx context.Context, m *modinfo.ModulePublic) {
   249  	if m.Version == "" {
   250  		return
   251  	}
   252  
   253  	err := loaderstate.CheckRetractions(ctx, module.Version{Path: m.Path, Version: m.Version})
   254  	if err == nil {
   255  		return
   256  	} else if _, ok := errors.AsType[*NoMatchingVersionError](err); ok || errors.Is(err, fs.ErrNotExist) {
   257  		// Ignore "no matching version" and "not found" errors.
   258  		// This means the proxy has no matching version or no versions at all.
   259  		//
   260  		// We should report other errors though. An attacker that controls the
   261  		// network shouldn't be able to hide versions by interfering with
   262  		// the HTTPS connection. An attacker that controls the proxy may still
   263  		// hide versions, since the "list" and "latest" endpoints are not
   264  		// authenticated.
   265  		return
   266  	} else if retractErr, ok := errors.AsType[*ModuleRetractedError](err); ok {
   267  		if len(retractErr.Rationale) == 0 {
   268  			m.Retracted = []string{"retracted by module author"}
   269  		} else {
   270  			m.Retracted = retractErr.Rationale
   271  		}
   272  	} else if m.Error == nil {
   273  		m.Error = &modinfo.ModuleError{Err: err.Error()}
   274  	}
   275  }
   276  
   277  // addDeprecation fills in m.Deprecated if the module was deprecated by its
   278  // author. m.Error is set if there's an error loading deprecation information.
   279  func addDeprecation(loaderstate *State, ctx context.Context, m *modinfo.ModulePublic) {
   280  	deprecation, err := CheckDeprecation(loaderstate, ctx, module.Version{Path: m.Path, Version: m.Version})
   281  	if _, ok := errors.AsType[*NoMatchingVersionError](err); ok || errors.Is(err, fs.ErrNotExist) {
   282  		// Ignore "no matching version" and "not found" errors.
   283  		// This means the proxy has no matching version or no versions at all.
   284  		//
   285  		// We should report other errors though. An attacker that controls the
   286  		// network shouldn't be able to hide versions by interfering with
   287  		// the HTTPS connection. An attacker that controls the proxy may still
   288  		// hide versions, since the "list" and "latest" endpoints are not
   289  		// authenticated.
   290  		return
   291  	}
   292  	if err != nil {
   293  		if m.Error == nil {
   294  			m.Error = &modinfo.ModuleError{Err: err.Error()}
   295  		}
   296  		return
   297  	}
   298  	m.Deprecated = deprecation
   299  }
   300  
   301  // moduleInfo returns information about module m, loaded from the requirements
   302  // in rs (which may be nil to indicate that m was not loaded from a requirement
   303  // graph).
   304  func moduleInfo(loaderstate *State, ctx context.Context, rs *Requirements, m module.Version, mode ListMode, reuse map[module.Version]*modinfo.ModulePublic) *modinfo.ModulePublic {
   305  	if m.Version == "" && loaderstate.MainModules.Contains(m.Path) {
   306  		info := &modinfo.ModulePublic{
   307  			Path:    m.Path,
   308  			Version: m.Version,
   309  			Main:    true,
   310  		}
   311  		if v, ok := rawGoVersion.Load(m); ok {
   312  			info.GoVersion = v.(string)
   313  		} else {
   314  			panic("internal error: GoVersion not set for main module")
   315  		}
   316  		if modRoot := loaderstate.MainModules.ModRoot(m); modRoot != "" {
   317  			info.Dir = modRoot
   318  			info.GoMod = modFilePath(modRoot)
   319  		}
   320  		return info
   321  	}
   322  
   323  	info := &modinfo.ModulePublic{
   324  		Path:     m.Path,
   325  		Version:  m.Version,
   326  		Indirect: rs != nil && !rs.direct[m.Path],
   327  	}
   328  	if v, ok := rawGoVersion.Load(m); ok {
   329  		info.GoVersion = v.(string)
   330  	}
   331  
   332  	// completeFromModCache fills in the extra fields in m using the module cache.
   333  	completeFromModCache := func(m *modinfo.ModulePublic) {
   334  		if gover.IsToolchain(m.Path) {
   335  			return
   336  		}
   337  
   338  		checksumOk := func(suffix string) bool {
   339  			return rs == nil || m.Version == "" || !mustHaveSums(loaderstate) ||
   340  				modfetch.HaveSum(loaderstate.Fetcher(), module.Version{Path: m.Path, Version: m.Version + suffix})
   341  		}
   342  
   343  		mod := module.Version{Path: m.Path, Version: m.Version}
   344  
   345  		if m.Version != "" {
   346  			if old := reuse[mod]; old != nil {
   347  				if err := checkReuse(loaderstate, ctx, mod, old.Origin); err == nil {
   348  					*m = *old
   349  					m.Query = ""
   350  					m.Dir = ""
   351  					return
   352  				}
   353  			}
   354  
   355  			if q, err := Query(loaderstate, ctx, m.Path, m.Version, "", nil); err != nil {
   356  				m.Error = &modinfo.ModuleError{Err: err.Error()}
   357  			} else {
   358  				m.Version = q.Version
   359  				m.Time = &q.Time
   360  			}
   361  		}
   362  
   363  		if m.GoVersion == "" && checksumOk("/go.mod") {
   364  			// Load the go.mod file to determine the Go version, since it hasn't
   365  			// already been populated from rawGoVersion.
   366  			if summary, err := rawGoModSummary(loaderstate, mod); err == nil && summary.goVersion != "" {
   367  				m.GoVersion = summary.goVersion
   368  			}
   369  		}
   370  
   371  		if m.Version != "" {
   372  			if checksumOk("/go.mod") {
   373  				gomod, err := modfetch.CachePath(ctx, mod, "mod")
   374  				if err == nil {
   375  					if info, err := os.Stat(gomod); err == nil && info.Mode().IsRegular() {
   376  						m.GoMod = gomod
   377  					}
   378  				}
   379  				if gomodsum, ok := loaderstate.fetcher.RecordedSum(modkey(mod)); ok {
   380  					m.GoModSum = gomodsum
   381  				}
   382  			}
   383  			if checksumOk("") {
   384  				dir, err := modfetch.DownloadDir(ctx, mod)
   385  				if err == nil {
   386  					m.Dir = dir
   387  				}
   388  				if sum, ok := loaderstate.fetcher.RecordedSum(mod); ok {
   389  					m.Sum = sum
   390  				}
   391  			}
   392  
   393  			if mode&ListRetracted != 0 {
   394  				addRetraction(loaderstate, ctx, m)
   395  			}
   396  		}
   397  	}
   398  
   399  	if rs == nil {
   400  		// If this was an explicitly-versioned argument to 'go mod download' or
   401  		// 'go list -m', report the actual requested version, not its replacement.
   402  		completeFromModCache(info) // Will set m.Error in vendor mode.
   403  		return info
   404  	}
   405  
   406  	r := Replacement(loaderstate, m)
   407  	if r.Path == "" {
   408  		if cfg.BuildMod == "vendor" {
   409  			// It's tempting to fill in the "Dir" field to point within the vendor
   410  			// directory, but that would be misleading: the vendor directory contains
   411  			// a flattened package tree, not complete modules, and it can even
   412  			// interleave packages from different modules if one module path is a
   413  			// prefix of the other.
   414  		} else {
   415  			completeFromModCache(info)
   416  		}
   417  		return info
   418  	}
   419  
   420  	// Don't hit the network to fill in extra data for replaced modules.
   421  	// The original resolved Version and Time don't matter enough to be
   422  	// worth the cost, and we're going to overwrite the GoMod and Dir from the
   423  	// replacement anyway. See https://golang.org/issue/27859.
   424  	info.Replace = &modinfo.ModulePublic{
   425  		Path:    r.Path,
   426  		Version: r.Version,
   427  	}
   428  	if v, ok := rawGoVersion.Load(m); ok {
   429  		info.Replace.GoVersion = v.(string)
   430  	}
   431  	if r.Version == "" {
   432  		if filepath.IsAbs(r.Path) {
   433  			info.Replace.Dir = r.Path
   434  		} else {
   435  			info.Replace.Dir = filepath.Join(replaceRelativeTo(loaderstate), r.Path)
   436  		}
   437  		info.Replace.GoMod = filepath.Join(info.Replace.Dir, "go.mod")
   438  	}
   439  	if cfg.BuildMod != "vendor" {
   440  		completeFromModCache(info.Replace)
   441  		info.Dir = info.Replace.Dir
   442  		info.GoMod = info.Replace.GoMod
   443  		info.Retracted = info.Replace.Retracted
   444  	}
   445  	info.GoVersion = info.Replace.GoVersion
   446  	return info
   447  }
   448  
   449  // findModule searches for the module that contains the package at path.
   450  // If the package was loaded, its containing module and true are returned.
   451  // Otherwise, module.Version{} and false are returned.
   452  func findModule(ld *loader, path string) (module.Version, bool) {
   453  	if pkg, ok := ld.pkgCache.Get(path); ok {
   454  		return pkg.mod, pkg.mod != module.Version{}
   455  	}
   456  	return module.Version{}, false
   457  }
   458  
   459  func ModInfoProg(info string, isgccgo bool) []byte {
   460  	// Inject an init function to set runtime.modinfo.
   461  	// This is only used for gccgo - with gc we hand the info directly to the linker.
   462  	// The init function has the drawback that packages may want to
   463  	// look at the module info in their init functions (see issue 29628),
   464  	// which won't work. See also issue 30344.
   465  	if isgccgo {
   466  		return fmt.Appendf(nil, `package main
   467  import _ "unsafe"
   468  //go:linkname __set_debug_modinfo__ runtime.setmodinfo
   469  func __set_debug_modinfo__(string)
   470  func init() { __set_debug_modinfo__(%q) }
   471  `, ModInfoData(info))
   472  	}
   473  	return nil
   474  }
   475  
   476  func ModInfoData(info string) []byte {
   477  	return []byte(string(infoStart) + info + string(infoEnd))
   478  }
   479  

View as plain text