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

View as plain text