Black Lives Matter. Support the Equal Justice Initiative.

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

Documentation: cmd/go/internal/modload

     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  	"bytes"
     9  	"context"
    10  	"encoding/hex"
    11  	"errors"
    12  	"fmt"
    13  	"internal/goroot"
    14  	"io/fs"
    15  	"os"
    16  	"path/filepath"
    17  	"strings"
    18  
    19  	"cmd/go/internal/base"
    20  	"cmd/go/internal/cfg"
    21  	"cmd/go/internal/modfetch"
    22  	"cmd/go/internal/modinfo"
    23  	"cmd/go/internal/search"
    24  
    25  	"golang.org/x/mod/module"
    26  	"golang.org/x/mod/semver"
    27  )
    28  
    29  var (
    30  	infoStart, _ = hex.DecodeString("3077af0c9274080241e1c107e6d618e6")
    31  	infoEnd, _   = hex.DecodeString("f932433186182072008242104116d8f2")
    32  )
    33  
    34  func isStandardImportPath(path string) bool {
    35  	return findStandardImportPath(path) != ""
    36  }
    37  
    38  func findStandardImportPath(path string) string {
    39  	if path == "" {
    40  		panic("findStandardImportPath called with empty path")
    41  	}
    42  	if search.IsStandardImportPath(path) {
    43  		if goroot.IsStandardPackage(cfg.GOROOT, cfg.BuildContext.Compiler, path) {
    44  			return filepath.Join(cfg.GOROOT, "src", path)
    45  		}
    46  	}
    47  	return ""
    48  }
    49  
    50  // PackageModuleInfo returns information about the module that provides
    51  // a given package. If modules are not enabled or if the package is in the
    52  // standard library or if the package was not successfully loaded with
    53  // LoadPackages or ImportFromFiles, nil is returned.
    54  func PackageModuleInfo(ctx context.Context, pkgpath string) *modinfo.ModulePublic {
    55  	if isStandardImportPath(pkgpath) || !Enabled() {
    56  		return nil
    57  	}
    58  	m, ok := findModule(loaded, pkgpath)
    59  	if !ok {
    60  		return nil
    61  	}
    62  
    63  	rs := LoadModFile(ctx)
    64  	return moduleInfo(ctx, rs, m, 0)
    65  }
    66  
    67  func ModuleInfo(ctx context.Context, path string) *modinfo.ModulePublic {
    68  	if !Enabled() {
    69  		return nil
    70  	}
    71  
    72  	if i := strings.Index(path, "@"); i >= 0 {
    73  		m := module.Version{Path: path[:i], Version: path[i+1:]}
    74  		return moduleInfo(ctx, nil, m, 0)
    75  	}
    76  
    77  	rs := LoadModFile(ctx)
    78  
    79  	var (
    80  		v  string
    81  		ok bool
    82  	)
    83  	if rs.depth == lazy {
    84  		v, ok = rs.rootSelected(path)
    85  	}
    86  	if !ok {
    87  		mg, err := rs.Graph(ctx)
    88  		if err != nil {
    89  			base.Fatalf("go: %v", err)
    90  		}
    91  		v = mg.Selected(path)
    92  	}
    93  
    94  	if v == "none" {
    95  		return &modinfo.ModulePublic{
    96  			Path: path,
    97  			Error: &modinfo.ModuleError{
    98  				Err: "module not in current build",
    99  			},
   100  		}
   101  	}
   102  
   103  	return moduleInfo(ctx, rs, module.Version{Path: path, Version: v}, 0)
   104  }
   105  
   106  // addUpdate fills in m.Update if an updated version is available.
   107  func addUpdate(ctx context.Context, m *modinfo.ModulePublic) {
   108  	if m.Version == "" {
   109  		return
   110  	}
   111  
   112  	info, err := Query(ctx, m.Path, "upgrade", m.Version, CheckAllowed)
   113  	var noVersionErr *NoMatchingVersionError
   114  	if errors.Is(err, fs.ErrNotExist) || errors.As(err, &noVersionErr) {
   115  		// Ignore "not found" and "no matching version" errors.
   116  		// This means the proxy has no matching version or no versions at all.
   117  		//
   118  		// We should report other errors though. An attacker that controls the
   119  		// network shouldn't be able to hide versions by interfering with
   120  		// the HTTPS connection. An attacker that controls the proxy may still
   121  		// hide versions, since the "list" and "latest" endpoints are not
   122  		// authenticated.
   123  		return
   124  	} else if err != nil {
   125  		if m.Error == nil {
   126  			m.Error = &modinfo.ModuleError{Err: err.Error()}
   127  		}
   128  		return
   129  	}
   130  
   131  	if semver.Compare(info.Version, m.Version) > 0 {
   132  		m.Update = &modinfo.ModulePublic{
   133  			Path:    m.Path,
   134  			Version: info.Version,
   135  			Time:    &info.Time,
   136  		}
   137  	}
   138  }
   139  
   140  // addVersions fills in m.Versions with the list of known versions.
   141  // Excluded versions will be omitted. If listRetracted is false, retracted
   142  // versions will also be omitted.
   143  func addVersions(ctx context.Context, m *modinfo.ModulePublic, listRetracted bool) {
   144  	allowed := CheckAllowed
   145  	if listRetracted {
   146  		allowed = CheckExclusions
   147  	}
   148  	var err error
   149  	m.Versions, err = versions(ctx, m.Path, allowed)
   150  	if err != nil && m.Error == nil {
   151  		m.Error = &modinfo.ModuleError{Err: err.Error()}
   152  	}
   153  }
   154  
   155  // addRetraction fills in m.Retracted if the module was retracted by its author.
   156  // m.Error is set if there's an error loading retraction information.
   157  func addRetraction(ctx context.Context, m *modinfo.ModulePublic) {
   158  	if m.Version == "" {
   159  		return
   160  	}
   161  
   162  	err := CheckRetractions(ctx, module.Version{Path: m.Path, Version: m.Version})
   163  	var noVersionErr *NoMatchingVersionError
   164  	var retractErr *ModuleRetractedError
   165  	if err == nil || errors.Is(err, fs.ErrNotExist) || errors.As(err, &noVersionErr) {
   166  		// Ignore "not found" and "no matching version" errors.
   167  		// This means the proxy has no matching version or no versions at all.
   168  		//
   169  		// We should report other errors though. An attacker that controls the
   170  		// network shouldn't be able to hide versions by interfering with
   171  		// the HTTPS connection. An attacker that controls the proxy may still
   172  		// hide versions, since the "list" and "latest" endpoints are not
   173  		// authenticated.
   174  		return
   175  	} else if errors.As(err, &retractErr) {
   176  		if len(retractErr.Rationale) == 0 {
   177  			m.Retracted = []string{"retracted by module author"}
   178  		} else {
   179  			m.Retracted = retractErr.Rationale
   180  		}
   181  	} else if m.Error == nil {
   182  		m.Error = &modinfo.ModuleError{Err: err.Error()}
   183  	}
   184  }
   185  
   186  // addDeprecation fills in m.Deprecated if the module was deprecated by its
   187  // author. m.Error is set if there's an error loading deprecation information.
   188  func addDeprecation(ctx context.Context, m *modinfo.ModulePublic) {
   189  	deprecation, err := CheckDeprecation(ctx, module.Version{Path: m.Path, Version: m.Version})
   190  	var noVersionErr *NoMatchingVersionError
   191  	if errors.Is(err, fs.ErrNotExist) || errors.As(err, &noVersionErr) {
   192  		// Ignore "not found" and "no matching version" errors.
   193  		// This means the proxy has no matching version or no versions at all.
   194  		//
   195  		// We should report other errors though. An attacker that controls the
   196  		// network shouldn't be able to hide versions by interfering with
   197  		// the HTTPS connection. An attacker that controls the proxy may still
   198  		// hide versions, since the "list" and "latest" endpoints are not
   199  		// authenticated.
   200  		return
   201  	}
   202  	if err != nil {
   203  		if m.Error == nil {
   204  			m.Error = &modinfo.ModuleError{Err: err.Error()}
   205  		}
   206  		return
   207  	}
   208  	m.Deprecated = deprecation
   209  }
   210  
   211  // moduleInfo returns information about module m, loaded from the requirements
   212  // in rs (which may be nil to indicate that m was not loaded from a requirement
   213  // graph).
   214  func moduleInfo(ctx context.Context, rs *Requirements, m module.Version, mode ListMode) *modinfo.ModulePublic {
   215  	if m == Target {
   216  		info := &modinfo.ModulePublic{
   217  			Path:    m.Path,
   218  			Version: m.Version,
   219  			Main:    true,
   220  		}
   221  		if v, ok := rawGoVersion.Load(Target); ok {
   222  			info.GoVersion = v.(string)
   223  		} else {
   224  			panic("internal error: GoVersion not set for main module")
   225  		}
   226  		if HasModRoot() {
   227  			info.Dir = ModRoot()
   228  			info.GoMod = ModFilePath()
   229  		}
   230  		return info
   231  	}
   232  
   233  	info := &modinfo.ModulePublic{
   234  		Path:     m.Path,
   235  		Version:  m.Version,
   236  		Indirect: rs != nil && !rs.direct[m.Path],
   237  	}
   238  	if v, ok := rawGoVersion.Load(m); ok {
   239  		info.GoVersion = v.(string)
   240  	}
   241  
   242  	// completeFromModCache fills in the extra fields in m using the module cache.
   243  	completeFromModCache := func(m *modinfo.ModulePublic) {
   244  		checksumOk := func(suffix string) bool {
   245  			return rs == nil || m.Version == "" || cfg.BuildMod == "mod" ||
   246  				modfetch.HaveSum(module.Version{Path: m.Path, Version: m.Version + suffix})
   247  		}
   248  
   249  		if m.Version != "" {
   250  			if q, err := Query(ctx, m.Path, m.Version, "", nil); err != nil {
   251  				m.Error = &modinfo.ModuleError{Err: err.Error()}
   252  			} else {
   253  				m.Version = q.Version
   254  				m.Time = &q.Time
   255  			}
   256  		}
   257  		mod := module.Version{Path: m.Path, Version: m.Version}
   258  
   259  		if m.GoVersion == "" && checksumOk("/go.mod") {
   260  			// Load the go.mod file to determine the Go version, since it hasn't
   261  			// already been populated from rawGoVersion.
   262  			if summary, err := rawGoModSummary(mod); err == nil && summary.goVersion != "" {
   263  				m.GoVersion = summary.goVersion
   264  			}
   265  		}
   266  
   267  		if m.Version != "" {
   268  			if checksumOk("/go.mod") {
   269  				gomod, err := modfetch.CachePath(mod, "mod")
   270  				if err == nil {
   271  					if info, err := os.Stat(gomod); err == nil && info.Mode().IsRegular() {
   272  						m.GoMod = gomod
   273  					}
   274  				}
   275  			}
   276  			if checksumOk("") {
   277  				dir, err := modfetch.DownloadDir(mod)
   278  				if err == nil {
   279  					m.Dir = dir
   280  				}
   281  			}
   282  
   283  			if mode&ListRetracted != 0 {
   284  				addRetraction(ctx, m)
   285  			}
   286  		}
   287  	}
   288  
   289  	if rs == nil {
   290  		// If this was an explicitly-versioned argument to 'go mod download' or
   291  		// 'go list -m', report the actual requested version, not its replacement.
   292  		completeFromModCache(info) // Will set m.Error in vendor mode.
   293  		return info
   294  	}
   295  
   296  	r := Replacement(m)
   297  	if r.Path == "" {
   298  		if cfg.BuildMod == "vendor" {
   299  			// It's tempting to fill in the "Dir" field to point within the vendor
   300  			// directory, but that would be misleading: the vendor directory contains
   301  			// a flattened package tree, not complete modules, and it can even
   302  			// interleave packages from different modules if one module path is a
   303  			// prefix of the other.
   304  		} else {
   305  			completeFromModCache(info)
   306  		}
   307  		return info
   308  	}
   309  
   310  	// Don't hit the network to fill in extra data for replaced modules.
   311  	// The original resolved Version and Time don't matter enough to be
   312  	// worth the cost, and we're going to overwrite the GoMod and Dir from the
   313  	// replacement anyway. See https://golang.org/issue/27859.
   314  	info.Replace = &modinfo.ModulePublic{
   315  		Path:    r.Path,
   316  		Version: r.Version,
   317  	}
   318  	if v, ok := rawGoVersion.Load(m); ok {
   319  		info.Replace.GoVersion = v.(string)
   320  	}
   321  	if r.Version == "" {
   322  		if filepath.IsAbs(r.Path) {
   323  			info.Replace.Dir = r.Path
   324  		} else {
   325  			info.Replace.Dir = filepath.Join(ModRoot(), r.Path)
   326  		}
   327  		info.Replace.GoMod = filepath.Join(info.Replace.Dir, "go.mod")
   328  	}
   329  	if cfg.BuildMod != "vendor" {
   330  		completeFromModCache(info.Replace)
   331  		info.Dir = info.Replace.Dir
   332  		info.GoMod = info.Replace.GoMod
   333  		info.Retracted = info.Replace.Retracted
   334  	}
   335  	info.GoVersion = info.Replace.GoVersion
   336  	return info
   337  }
   338  
   339  // PackageBuildInfo returns a string containing module version information
   340  // for modules providing packages named by path and deps. path and deps must
   341  // name packages that were resolved successfully with LoadPackages.
   342  func PackageBuildInfo(path string, deps []string) string {
   343  	if isStandardImportPath(path) || !Enabled() {
   344  		return ""
   345  	}
   346  
   347  	target := mustFindModule(loaded, path, path)
   348  	mdeps := make(map[module.Version]bool)
   349  	for _, dep := range deps {
   350  		if !isStandardImportPath(dep) {
   351  			mdeps[mustFindModule(loaded, path, dep)] = true
   352  		}
   353  	}
   354  	var mods []module.Version
   355  	delete(mdeps, target)
   356  	for mod := range mdeps {
   357  		mods = append(mods, mod)
   358  	}
   359  	module.Sort(mods)
   360  
   361  	var buf bytes.Buffer
   362  	fmt.Fprintf(&buf, "path\t%s\n", path)
   363  
   364  	writeEntry := func(token string, m module.Version) {
   365  		mv := m.Version
   366  		if mv == "" {
   367  			mv = "(devel)"
   368  		}
   369  		fmt.Fprintf(&buf, "%s\t%s\t%s", token, m.Path, mv)
   370  		if r := Replacement(m); r.Path == "" {
   371  			fmt.Fprintf(&buf, "\t%s\n", modfetch.Sum(m))
   372  		} else {
   373  			fmt.Fprintf(&buf, "\n=>\t%s\t%s\t%s\n", r.Path, r.Version, modfetch.Sum(r))
   374  		}
   375  	}
   376  
   377  	writeEntry("mod", target)
   378  	for _, mod := range mods {
   379  		writeEntry("dep", mod)
   380  	}
   381  
   382  	return buf.String()
   383  }
   384  
   385  // mustFindModule is like findModule, but it calls base.Fatalf if the
   386  // module can't be found.
   387  //
   388  // TODO(jayconrod): remove this. Callers should use findModule and return
   389  // errors instead of relying on base.Fatalf.
   390  func mustFindModule(ld *loader, target, path string) module.Version {
   391  	pkg, ok := ld.pkgCache.Get(path).(*loadPkg)
   392  	if ok {
   393  		if pkg.err != nil {
   394  			base.Fatalf("build %v: cannot load %v: %v", target, path, pkg.err)
   395  		}
   396  		return pkg.mod
   397  	}
   398  
   399  	if path == "command-line-arguments" {
   400  		return Target
   401  	}
   402  
   403  	base.Fatalf("build %v: cannot find module for path %v", target, path)
   404  	panic("unreachable")
   405  }
   406  
   407  // findModule searches for the module that contains the package at path.
   408  // If the package was loaded, its containing module and true are returned.
   409  // Otherwise, module.Version{} and false are returend.
   410  func findModule(ld *loader, path string) (module.Version, bool) {
   411  	if pkg, ok := ld.pkgCache.Get(path).(*loadPkg); ok {
   412  		return pkg.mod, pkg.mod != module.Version{}
   413  	}
   414  	if path == "command-line-arguments" {
   415  		return Target, true
   416  	}
   417  	return module.Version{}, false
   418  }
   419  
   420  func ModInfoProg(info string, isgccgo bool) []byte {
   421  	// Inject a variable with the debug information as runtime.modinfo,
   422  	// but compile it in package main so that it is specific to the binary.
   423  	// The variable must be a literal so that it will have the correct value
   424  	// before the initializer for package main runs.
   425  	//
   426  	// The runtime startup code refers to the variable, which keeps it live
   427  	// in all binaries.
   428  	//
   429  	// Note: we use an alternate recipe below for gccgo (based on an
   430  	// init function) due to the fact that gccgo does not support
   431  	// applying a "//go:linkname" directive to a variable. This has
   432  	// drawbacks in that other packages may want to look at the module
   433  	// info in their init functions (see issue 29628), which won't
   434  	// work for gccgo. See also issue 30344.
   435  
   436  	if !isgccgo {
   437  		return []byte(fmt.Sprintf(`package main
   438  import _ "unsafe"
   439  //go:linkname __debug_modinfo__ runtime.modinfo
   440  var __debug_modinfo__ = %q
   441  `, string(infoStart)+info+string(infoEnd)))
   442  	} else {
   443  		return []byte(fmt.Sprintf(`package main
   444  import _ "unsafe"
   445  //go:linkname __set_debug_modinfo__ runtime.setmodinfo
   446  func __set_debug_modinfo__(string)
   447  func init() { __set_debug_modinfo__(%q) }
   448  `, string(infoStart)+info+string(infoEnd)))
   449  	}
   450  }
   451  

View as plain text