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  	"internal/goroot"
    13  	"io/fs"
    14  	"os"
    15  	"path/filepath"
    16  	"strings"
    17  
    18  	"cmd/go/internal/base"
    19  	"cmd/go/internal/cfg"
    20  	"cmd/go/internal/modfetch"
    21  	"cmd/go/internal/modinfo"
    22  	"cmd/go/internal/search"
    23  
    24  	"golang.org/x/mod/module"
    25  	"golang.org/x/mod/semver"
    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 goroot.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)
    64  }
    65  
    66  func ModuleInfo(ctx context.Context, path string) *modinfo.ModulePublic {
    67  	if !Enabled() {
    68  		return nil
    69  	}
    70  
    71  	if i := strings.Index(path, "@"); i >= 0 {
    72  		m := module.Version{Path: path[:i], Version: path[i+1:]}
    73  		return moduleInfo(ctx, nil, m, 0)
    74  	}
    75  
    76  	rs := LoadModFile(ctx)
    77  
    78  	var (
    79  		v  string
    80  		ok bool
    81  	)
    82  	if rs.pruning == pruned {
    83  		v, ok = rs.rootSelected(path)
    84  	}
    85  	if !ok {
    86  		mg, err := rs.Graph(ctx)
    87  		if err != nil {
    88  			base.Fatalf("go: %v", err)
    89  		}
    90  		v = mg.Selected(path)
    91  	}
    92  
    93  	if v == "none" {
    94  		return &modinfo.ModulePublic{
    95  			Path: path,
    96  			Error: &modinfo.ModuleError{
    97  				Err: "module not in current build",
    98  			},
    99  		}
   100  	}
   101  
   102  	return moduleInfo(ctx, rs, module.Version{Path: path, Version: v}, 0)
   103  }
   104  
   105  // addUpdate fills in m.Update if an updated version is available.
   106  func addUpdate(ctx context.Context, m *modinfo.ModulePublic) {
   107  	if m.Version == "" {
   108  		return
   109  	}
   110  
   111  	info, err := Query(ctx, m.Path, "upgrade", m.Version, CheckAllowed)
   112  	var noVersionErr *NoMatchingVersionError
   113  	if errors.Is(err, fs.ErrNotExist) || errors.As(err, &noVersionErr) {
   114  		// Ignore "not found" and "no matching version" errors.
   115  		// This means the proxy has no matching version or no versions at all.
   116  		//
   117  		// We should report other errors though. An attacker that controls the
   118  		// network shouldn't be able to hide versions by interfering with
   119  		// the HTTPS connection. An attacker that controls the proxy may still
   120  		// hide versions, since the "list" and "latest" endpoints are not
   121  		// authenticated.
   122  		return
   123  	} else if err != nil {
   124  		if m.Error == nil {
   125  			m.Error = &modinfo.ModuleError{Err: err.Error()}
   126  		}
   127  		return
   128  	}
   129  
   130  	if semver.Compare(info.Version, m.Version) > 0 {
   131  		m.Update = &modinfo.ModulePublic{
   132  			Path:    m.Path,
   133  			Version: info.Version,
   134  			Time:    &info.Time,
   135  		}
   136  	}
   137  }
   138  
   139  // addVersions fills in m.Versions with the list of known versions.
   140  // Excluded versions will be omitted. If listRetracted is false, retracted
   141  // versions will also be omitted.
   142  func addVersions(ctx context.Context, m *modinfo.ModulePublic, listRetracted bool) {
   143  	allowed := CheckAllowed
   144  	if listRetracted {
   145  		allowed = CheckExclusions
   146  	}
   147  	var err error
   148  	m.Versions, err = versions(ctx, m.Path, allowed)
   149  	if err != nil && m.Error == nil {
   150  		m.Error = &modinfo.ModuleError{Err: err.Error()}
   151  	}
   152  }
   153  
   154  // addRetraction fills in m.Retracted if the module was retracted by its author.
   155  // m.Error is set if there's an error loading retraction information.
   156  func addRetraction(ctx context.Context, m *modinfo.ModulePublic) {
   157  	if m.Version == "" {
   158  		return
   159  	}
   160  
   161  	err := CheckRetractions(ctx, module.Version{Path: m.Path, Version: m.Version})
   162  	var noVersionErr *NoMatchingVersionError
   163  	var retractErr *ModuleRetractedError
   164  	if err == nil || errors.Is(err, fs.ErrNotExist) || errors.As(err, &noVersionErr) {
   165  		// Ignore "not found" and "no matching version" errors.
   166  		// This means the proxy has no matching version or no versions at all.
   167  		//
   168  		// We should report other errors though. An attacker that controls the
   169  		// network shouldn't be able to hide versions by interfering with
   170  		// the HTTPS connection. An attacker that controls the proxy may still
   171  		// hide versions, since the "list" and "latest" endpoints are not
   172  		// authenticated.
   173  		return
   174  	} else if errors.As(err, &retractErr) {
   175  		if len(retractErr.Rationale) == 0 {
   176  			m.Retracted = []string{"retracted by module author"}
   177  		} else {
   178  			m.Retracted = retractErr.Rationale
   179  		}
   180  	} else if m.Error == nil {
   181  		m.Error = &modinfo.ModuleError{Err: err.Error()}
   182  	}
   183  }
   184  
   185  // addDeprecation fills in m.Deprecated if the module was deprecated by its
   186  // author. m.Error is set if there's an error loading deprecation information.
   187  func addDeprecation(ctx context.Context, m *modinfo.ModulePublic) {
   188  	deprecation, err := CheckDeprecation(ctx, module.Version{Path: m.Path, Version: m.Version})
   189  	var noVersionErr *NoMatchingVersionError
   190  	if errors.Is(err, fs.ErrNotExist) || errors.As(err, &noVersionErr) {
   191  		// Ignore "not found" and "no matching version" errors.
   192  		// This means the proxy has no matching version or no versions at all.
   193  		//
   194  		// We should report other errors though. An attacker that controls the
   195  		// network shouldn't be able to hide versions by interfering with
   196  		// the HTTPS connection. An attacker that controls the proxy may still
   197  		// hide versions, since the "list" and "latest" endpoints are not
   198  		// authenticated.
   199  		return
   200  	}
   201  	if err != nil {
   202  		if m.Error == nil {
   203  			m.Error = &modinfo.ModuleError{Err: err.Error()}
   204  		}
   205  		return
   206  	}
   207  	m.Deprecated = deprecation
   208  }
   209  
   210  // moduleInfo returns information about module m, loaded from the requirements
   211  // in rs (which may be nil to indicate that m was not loaded from a requirement
   212  // graph).
   213  func moduleInfo(ctx context.Context, rs *Requirements, m module.Version, mode ListMode) *modinfo.ModulePublic {
   214  	if m.Version == "" && MainModules.Contains(m.Path) {
   215  		info := &modinfo.ModulePublic{
   216  			Path:    m.Path,
   217  			Version: m.Version,
   218  			Main:    true,
   219  		}
   220  		if v, ok := rawGoVersion.Load(m); ok {
   221  			info.GoVersion = v.(string)
   222  		} else {
   223  			panic("internal error: GoVersion not set for main module")
   224  		}
   225  		if modRoot := MainModules.ModRoot(m); modRoot != "" {
   226  			info.Dir = modRoot
   227  			info.GoMod = modFilePath(modRoot)
   228  		}
   229  		return info
   230  	}
   231  
   232  	info := &modinfo.ModulePublic{
   233  		Path:     m.Path,
   234  		Version:  m.Version,
   235  		Indirect: rs != nil && !rs.direct[m.Path],
   236  	}
   237  	if v, ok := rawGoVersion.Load(m); ok {
   238  		info.GoVersion = v.(string)
   239  	}
   240  
   241  	// completeFromModCache fills in the extra fields in m using the module cache.
   242  	completeFromModCache := func(m *modinfo.ModulePublic) {
   243  		checksumOk := func(suffix string) bool {
   244  			return rs == nil || m.Version == "" || cfg.BuildMod == "mod" ||
   245  				modfetch.HaveSum(module.Version{Path: m.Path, Version: m.Version + suffix})
   246  		}
   247  
   248  		if m.Version != "" {
   249  			if q, err := Query(ctx, m.Path, m.Version, "", nil); err != nil {
   250  				m.Error = &modinfo.ModuleError{Err: err.Error()}
   251  			} else {
   252  				m.Version = q.Version
   253  				m.Time = &q.Time
   254  			}
   255  		}
   256  		mod := module.Version{Path: m.Path, Version: m.Version}
   257  
   258  		if m.GoVersion == "" && checksumOk("/go.mod") {
   259  			// Load the go.mod file to determine the Go version, since it hasn't
   260  			// already been populated from rawGoVersion.
   261  			if summary, err := rawGoModSummary(mod); err == nil && summary.goVersion != "" {
   262  				m.GoVersion = summary.goVersion
   263  			}
   264  		}
   265  
   266  		if m.Version != "" {
   267  			if checksumOk("/go.mod") {
   268  				gomod, err := modfetch.CachePath(mod, "mod")
   269  				if err == nil {
   270  					if info, err := os.Stat(gomod); err == nil && info.Mode().IsRegular() {
   271  						m.GoMod = gomod
   272  					}
   273  				}
   274  			}
   275  			if checksumOk("") {
   276  				dir, err := modfetch.DownloadDir(mod)
   277  				if err == nil {
   278  					m.Dir = dir
   279  				}
   280  			}
   281  
   282  			if mode&ListRetracted != 0 {
   283  				addRetraction(ctx, m)
   284  			}
   285  		}
   286  	}
   287  
   288  	if rs == nil {
   289  		// If this was an explicitly-versioned argument to 'go mod download' or
   290  		// 'go list -m', report the actual requested version, not its replacement.
   291  		completeFromModCache(info) // Will set m.Error in vendor mode.
   292  		return info
   293  	}
   294  
   295  	r := Replacement(m)
   296  	if r.Path == "" {
   297  		if cfg.BuildMod == "vendor" {
   298  			// It's tempting to fill in the "Dir" field to point within the vendor
   299  			// directory, but that would be misleading: the vendor directory contains
   300  			// a flattened package tree, not complete modules, and it can even
   301  			// interleave packages from different modules if one module path is a
   302  			// prefix of the other.
   303  		} else {
   304  			completeFromModCache(info)
   305  		}
   306  		return info
   307  	}
   308  
   309  	// Don't hit the network to fill in extra data for replaced modules.
   310  	// The original resolved Version and Time don't matter enough to be
   311  	// worth the cost, and we're going to overwrite the GoMod and Dir from the
   312  	// replacement anyway. See https://golang.org/issue/27859.
   313  	info.Replace = &modinfo.ModulePublic{
   314  		Path:    r.Path,
   315  		Version: r.Version,
   316  	}
   317  	if v, ok := rawGoVersion.Load(m); ok {
   318  		info.Replace.GoVersion = v.(string)
   319  	}
   320  	if r.Version == "" {
   321  		if filepath.IsAbs(r.Path) {
   322  			info.Replace.Dir = r.Path
   323  		} else {
   324  			info.Replace.Dir = filepath.Join(replaceRelativeTo(), r.Path)
   325  		}
   326  		info.Replace.GoMod = filepath.Join(info.Replace.Dir, "go.mod")
   327  	}
   328  	if cfg.BuildMod != "vendor" {
   329  		completeFromModCache(info.Replace)
   330  		info.Dir = info.Replace.Dir
   331  		info.GoMod = info.Replace.GoMod
   332  		info.Retracted = info.Replace.Retracted
   333  	}
   334  	info.GoVersion = info.Replace.GoVersion
   335  	return info
   336  }
   337  
   338  // findModule searches for the module that contains the package at path.
   339  // If the package was loaded, its containing module and true are returned.
   340  // Otherwise, module.Version{} and false are returned.
   341  func findModule(ld *loader, path string) (module.Version, bool) {
   342  	if pkg, ok := ld.pkgCache.Get(path).(*loadPkg); ok {
   343  		return pkg.mod, pkg.mod != module.Version{}
   344  	}
   345  	return module.Version{}, false
   346  }
   347  
   348  func ModInfoProg(info string, isgccgo bool) []byte {
   349  	// Inject an init function to set runtime.modinfo.
   350  	// This is only used for gccgo - with gc we hand the info directly to the linker.
   351  	// The init function has the drawback that packages may want to
   352  	// look at the module info in their init functions (see issue 29628),
   353  	// which won't work. See also issue 30344.
   354  	if isgccgo {
   355  		return []byte(fmt.Sprintf(`package main
   356  import _ "unsafe"
   357  //go:linkname __set_debug_modinfo__ runtime.setmodinfo
   358  func __set_debug_modinfo__(string)
   359  func init() { __set_debug_modinfo__(%q) }
   360  `, ModInfoData(info)))
   361  	}
   362  	return nil
   363  }
   364  
   365  func ModInfoData(info string) []byte {
   366  	return []byte(string(infoStart) + info + string(infoEnd))
   367  }
   368  

View as plain text