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  	"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(pkgpath string) *modinfo.ModulePublic {
    54  	if isStandardImportPath(pkgpath) || !Enabled() {
    55  		return nil
    56  	}
    57  	m, ok := findModule(pkgpath)
    58  	if !ok {
    59  		return nil
    60  	}
    61  	fromBuildList := true
    62  	listRetracted := false
    63  	return moduleInfo(context.TODO(), m, fromBuildList, listRetracted)
    64  }
    65  
    66  func ModuleInfo(ctx context.Context, path string) *modinfo.ModulePublic {
    67  	if !Enabled() {
    68  		return nil
    69  	}
    70  
    71  	listRetracted := false
    72  	if i := strings.Index(path, "@"); i >= 0 {
    73  		m := module.Version{Path: path[:i], Version: path[i+1:]}
    74  		fromBuildList := false
    75  		return moduleInfo(ctx, m, fromBuildList, listRetracted)
    76  	}
    77  
    78  	for _, m := range buildList {
    79  		if m.Path == path {
    80  			fromBuildList := true
    81  			return moduleInfo(ctx, m, fromBuildList, listRetracted)
    82  		}
    83  	}
    84  
    85  	return &modinfo.ModulePublic{
    86  		Path: path,
    87  		Error: &modinfo.ModuleError{
    88  			Err: "module not in current build",
    89  		},
    90  	}
    91  }
    92  
    93  // addUpdate fills in m.Update if an updated version is available.
    94  func addUpdate(ctx context.Context, m *modinfo.ModulePublic) {
    95  	if m.Version == "" {
    96  		return
    97  	}
    98  
    99  	if info, err := Query(ctx, m.Path, "upgrade", m.Version, CheckAllowed); err == nil && semver.Compare(info.Version, m.Version) > 0 {
   100  		m.Update = &modinfo.ModulePublic{
   101  			Path:    m.Path,
   102  			Version: info.Version,
   103  			Time:    &info.Time,
   104  		}
   105  	}
   106  }
   107  
   108  // addVersions fills in m.Versions with the list of known versions.
   109  // Excluded versions will be omitted. If listRetracted is false, retracted
   110  // versions will also be omitted.
   111  func addVersions(ctx context.Context, m *modinfo.ModulePublic, listRetracted bool) {
   112  	allowed := CheckAllowed
   113  	if listRetracted {
   114  		allowed = CheckExclusions
   115  	}
   116  	m.Versions, _ = versions(ctx, m.Path, allowed)
   117  }
   118  
   119  // addRetraction fills in m.Retracted if the module was retracted by its author.
   120  // m.Error is set if there's an error loading retraction information.
   121  func addRetraction(ctx context.Context, m *modinfo.ModulePublic) {
   122  	if m.Version == "" {
   123  		return
   124  	}
   125  
   126  	err := CheckRetractions(ctx, module.Version{Path: m.Path, Version: m.Version})
   127  	var rerr *ModuleRetractedError
   128  	if errors.As(err, &rerr) {
   129  		if len(rerr.Rationale) == 0 {
   130  			m.Retracted = []string{"retracted by module author"}
   131  		} else {
   132  			m.Retracted = rerr.Rationale
   133  		}
   134  	} else if err != nil && m.Error == nil {
   135  		m.Error = &modinfo.ModuleError{Err: err.Error()}
   136  	}
   137  }
   138  
   139  func moduleInfo(ctx context.Context, m module.Version, fromBuildList, listRetracted bool) *modinfo.ModulePublic {
   140  	if m == Target {
   141  		info := &modinfo.ModulePublic{
   142  			Path:    m.Path,
   143  			Version: m.Version,
   144  			Main:    true,
   145  		}
   146  		if HasModRoot() {
   147  			info.Dir = ModRoot()
   148  			info.GoMod = ModFilePath()
   149  			if modFile.Go != nil {
   150  				info.GoVersion = modFile.Go.Version
   151  			}
   152  		}
   153  		return info
   154  	}
   155  
   156  	info := &modinfo.ModulePublic{
   157  		Path:     m.Path,
   158  		Version:  m.Version,
   159  		Indirect: fromBuildList && loaded != nil && !loaded.direct[m.Path],
   160  	}
   161  	if v, ok := rawGoVersion.Load(m); ok {
   162  		info.GoVersion = v.(string)
   163  	}
   164  
   165  	// completeFromModCache fills in the extra fields in m using the module cache.
   166  	completeFromModCache := func(m *modinfo.ModulePublic) {
   167  		mod := module.Version{Path: m.Path, Version: m.Version}
   168  
   169  		if m.Version != "" {
   170  			if q, err := Query(ctx, m.Path, m.Version, "", nil); err != nil {
   171  				m.Error = &modinfo.ModuleError{Err: err.Error()}
   172  			} else {
   173  				m.Version = q.Version
   174  				m.Time = &q.Time
   175  			}
   176  
   177  			gomod, err := modfetch.CachePath(mod, "mod")
   178  			if err == nil {
   179  				if info, err := os.Stat(gomod); err == nil && info.Mode().IsRegular() {
   180  					m.GoMod = gomod
   181  				}
   182  			}
   183  			dir, err := modfetch.DownloadDir(mod)
   184  			if err == nil {
   185  				m.Dir = dir
   186  			}
   187  
   188  			if listRetracted {
   189  				addRetraction(ctx, m)
   190  			}
   191  		}
   192  
   193  		if m.GoVersion == "" {
   194  			if summary, err := rawGoModSummary(mod); err == nil && summary.goVersionV != "" {
   195  				m.GoVersion = summary.goVersionV[1:]
   196  			}
   197  		}
   198  	}
   199  
   200  	if !fromBuildList {
   201  		// If this was an explicitly-versioned argument to 'go mod download' or
   202  		// 'go list -m', report the actual requested version, not its replacement.
   203  		completeFromModCache(info) // Will set m.Error in vendor mode.
   204  		return info
   205  	}
   206  
   207  	r := Replacement(m)
   208  	if r.Path == "" {
   209  		if cfg.BuildMod == "vendor" {
   210  			// It's tempting to fill in the "Dir" field to point within the vendor
   211  			// directory, but that would be misleading: the vendor directory contains
   212  			// a flattened package tree, not complete modules, and it can even
   213  			// interleave packages from different modules if one module path is a
   214  			// prefix of the other.
   215  		} else {
   216  			completeFromModCache(info)
   217  		}
   218  		return info
   219  	}
   220  
   221  	// Don't hit the network to fill in extra data for replaced modules.
   222  	// The original resolved Version and Time don't matter enough to be
   223  	// worth the cost, and we're going to overwrite the GoMod and Dir from the
   224  	// replacement anyway. See https://golang.org/issue/27859.
   225  	info.Replace = &modinfo.ModulePublic{
   226  		Path:    r.Path,
   227  		Version: r.Version,
   228  	}
   229  	if v, ok := rawGoVersion.Load(m); ok {
   230  		info.Replace.GoVersion = v.(string)
   231  	}
   232  	if r.Version == "" {
   233  		if filepath.IsAbs(r.Path) {
   234  			info.Replace.Dir = r.Path
   235  		} else {
   236  			info.Replace.Dir = filepath.Join(ModRoot(), r.Path)
   237  		}
   238  		info.Replace.GoMod = filepath.Join(info.Replace.Dir, "go.mod")
   239  	}
   240  	if cfg.BuildMod != "vendor" {
   241  		completeFromModCache(info.Replace)
   242  		info.Dir = info.Replace.Dir
   243  		info.GoMod = info.Replace.GoMod
   244  		info.Retracted = info.Replace.Retracted
   245  	}
   246  	info.GoVersion = info.Replace.GoVersion
   247  	return info
   248  }
   249  
   250  // PackageBuildInfo returns a string containing module version information
   251  // for modules providing packages named by path and deps. path and deps must
   252  // name packages that were resolved successfully with LoadPackages.
   253  func PackageBuildInfo(path string, deps []string) string {
   254  	if isStandardImportPath(path) || !Enabled() {
   255  		return ""
   256  	}
   257  
   258  	target := mustFindModule(path, path)
   259  	mdeps := make(map[module.Version]bool)
   260  	for _, dep := range deps {
   261  		if !isStandardImportPath(dep) {
   262  			mdeps[mustFindModule(path, dep)] = true
   263  		}
   264  	}
   265  	var mods []module.Version
   266  	delete(mdeps, target)
   267  	for mod := range mdeps {
   268  		mods = append(mods, mod)
   269  	}
   270  	module.Sort(mods)
   271  
   272  	var buf bytes.Buffer
   273  	fmt.Fprintf(&buf, "path\t%s\n", path)
   274  
   275  	writeEntry := func(token string, m module.Version) {
   276  		mv := m.Version
   277  		if mv == "" {
   278  			mv = "(devel)"
   279  		}
   280  		fmt.Fprintf(&buf, "%s\t%s\t%s", token, m.Path, mv)
   281  		if r := Replacement(m); r.Path == "" {
   282  			fmt.Fprintf(&buf, "\t%s\n", modfetch.Sum(m))
   283  		} else {
   284  			fmt.Fprintf(&buf, "\n=>\t%s\t%s\t%s\n", r.Path, r.Version, modfetch.Sum(r))
   285  		}
   286  	}
   287  
   288  	writeEntry("mod", target)
   289  	for _, mod := range mods {
   290  		writeEntry("dep", mod)
   291  	}
   292  
   293  	return buf.String()
   294  }
   295  
   296  // mustFindModule is like findModule, but it calls base.Fatalf if the
   297  // module can't be found.
   298  //
   299  // TODO(jayconrod): remove this. Callers should use findModule and return
   300  // errors instead of relying on base.Fatalf.
   301  func mustFindModule(target, path string) module.Version {
   302  	pkg, ok := loaded.pkgCache.Get(path).(*loadPkg)
   303  	if ok {
   304  		if pkg.err != nil {
   305  			base.Fatalf("build %v: cannot load %v: %v", target, path, pkg.err)
   306  		}
   307  		return pkg.mod
   308  	}
   309  
   310  	if path == "command-line-arguments" {
   311  		return Target
   312  	}
   313  
   314  	base.Fatalf("build %v: cannot find module for path %v", target, path)
   315  	panic("unreachable")
   316  }
   317  
   318  // findModule searches for the module that contains the package at path.
   319  // If the package was loaded, its containing module and true are returned.
   320  // Otherwise, module.Version{} and false are returend.
   321  func findModule(path string) (module.Version, bool) {
   322  	if pkg, ok := loaded.pkgCache.Get(path).(*loadPkg); ok {
   323  		return pkg.mod, pkg.mod != module.Version{}
   324  	}
   325  	if path == "command-line-arguments" {
   326  		return Target, true
   327  	}
   328  	return module.Version{}, false
   329  }
   330  
   331  func ModInfoProg(info string, isgccgo bool) []byte {
   332  	// Inject a variable with the debug information as runtime.modinfo,
   333  	// but compile it in package main so that it is specific to the binary.
   334  	// The variable must be a literal so that it will have the correct value
   335  	// before the initializer for package main runs.
   336  	//
   337  	// The runtime startup code refers to the variable, which keeps it live
   338  	// in all binaries.
   339  	//
   340  	// Note: we use an alternate recipe below for gccgo (based on an
   341  	// init function) due to the fact that gccgo does not support
   342  	// applying a "//go:linkname" directive to a variable. This has
   343  	// drawbacks in that other packages may want to look at the module
   344  	// info in their init functions (see issue 29628), which won't
   345  	// work for gccgo. See also issue 30344.
   346  
   347  	if !isgccgo {
   348  		return []byte(fmt.Sprintf(`package main
   349  import _ "unsafe"
   350  //go:linkname __debug_modinfo__ runtime.modinfo
   351  var __debug_modinfo__ = %q
   352  `, string(infoStart)+info+string(infoEnd)))
   353  	} else {
   354  		return []byte(fmt.Sprintf(`package main
   355  import _ "unsafe"
   356  //go:linkname __set_debug_modinfo__ runtime.setmodinfo
   357  func __set_debug_modinfo__(string)
   358  func init() { __set_debug_modinfo__(%q) }
   359  `, string(infoStart)+info+string(infoEnd)))
   360  	}
   361  }
   362  

View as plain text