blob: e9f9a82fabb2efdc972c38e046373201f6f7b2b4 [file] [log] [blame]
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package modload
import (
"bytes"
"context"
"encoding/hex"
"errors"
"fmt"
"internal/goroot"
"os"
"path/filepath"
"runtime/debug"
"strings"
"cmd/go/internal/base"
"cmd/go/internal/cfg"
"cmd/go/internal/modfetch"
"cmd/go/internal/modinfo"
"cmd/go/internal/search"
"golang.org/x/mod/module"
"golang.org/x/mod/semver"
)
var (
infoStart, _ = hex.DecodeString("3077af0c9274080241e1c107e6d618e6")
infoEnd, _ = hex.DecodeString("f932433186182072008242104116d8f2")
)
func isStandardImportPath(path string) bool {
return findStandardImportPath(path) != ""
}
func findStandardImportPath(path string) string {
if path == "" {
panic("findStandardImportPath called with empty path")
}
if search.IsStandardImportPath(path) {
if goroot.IsStandardPackage(cfg.GOROOT, cfg.BuildContext.Compiler, path) {
return filepath.Join(cfg.GOROOT, "src", path)
}
}
return ""
}
// PackageModuleInfo returns information about the module that provides
// a given package. If modules are not enabled or if the package is in the
// standard library or if the package was not successfully loaded with
// ImportPaths or a similar loading function, nil is returned.
func PackageModuleInfo(pkgpath string) *modinfo.ModulePublic {
if isStandardImportPath(pkgpath) || !Enabled() {
return nil
}
m, ok := findModule(pkgpath)
if !ok {
return nil
}
fromBuildList := true
listRetracted := false
return moduleInfo(context.TODO(), m, fromBuildList, listRetracted)
}
func ModuleInfo(ctx context.Context, path string) *modinfo.ModulePublic {
if !Enabled() {
return nil
}
listRetracted := false
if i := strings.Index(path, "@"); i >= 0 {
m := module.Version{Path: path[:i], Version: path[i+1:]}
fromBuildList := false
return moduleInfo(ctx, m, fromBuildList, listRetracted)
}
for _, m := range BuildList() {
if m.Path == path {
fromBuildList := true
return moduleInfo(ctx, m, fromBuildList, listRetracted)
}
}
return &modinfo.ModulePublic{
Path: path,
Error: &modinfo.ModuleError{
Err: "module not in current build",
},
}
}
// addUpdate fills in m.Update if an updated version is available.
func addUpdate(ctx context.Context, m *modinfo.ModulePublic) {
if m.Version == "" {
return
}
if info, err := Query(ctx, m.Path, "upgrade", m.Version, CheckAllowed); err == nil && semver.Compare(info.Version, m.Version) > 0 {
m.Update = &modinfo.ModulePublic{
Path: m.Path,
Version: info.Version,
Time: &info.Time,
}
}
}
// addVersions fills in m.Versions with the list of known versions.
// Excluded versions will be omitted. If listRetracted is false, retracted
// versions will also be omitted.
func addVersions(ctx context.Context, m *modinfo.ModulePublic, listRetracted bool) {
allowed := CheckAllowed
if listRetracted {
allowed = CheckExclusions
}
m.Versions, _ = versions(ctx, m.Path, allowed)
}
// addRetraction fills in m.Retracted if the module was retracted by its author.
// m.Error is set if there's an error loading retraction information.
func addRetraction(ctx context.Context, m *modinfo.ModulePublic) {
if m.Version == "" {
return
}
err := checkRetractions(ctx, module.Version{Path: m.Path, Version: m.Version})
var rerr *retractedError
if errors.As(err, &rerr) {
if len(rerr.rationale) == 0 {
m.Retracted = []string{"retracted by module author"}
} else {
m.Retracted = rerr.rationale
}
} else if err != nil && m.Error == nil {
m.Error = &modinfo.ModuleError{Err: err.Error()}
}
}
func moduleInfo(ctx context.Context, m module.Version, fromBuildList, listRetracted bool) *modinfo.ModulePublic {
if m == Target {
info := &modinfo.ModulePublic{
Path: m.Path,
Version: m.Version,
Main: true,
}
if HasModRoot() {
info.Dir = ModRoot()
info.GoMod = ModFilePath()
if modFile.Go != nil {
info.GoVersion = modFile.Go.Version
}
}
return info
}
info := &modinfo.ModulePublic{
Path: m.Path,
Version: m.Version,
Indirect: fromBuildList && loaded != nil && !loaded.direct[m.Path],
}
if v, ok := rawGoVersion.Load(m); ok {
info.GoVersion = v.(string)
}
// completeFromModCache fills in the extra fields in m using the module cache.
completeFromModCache := func(m *modinfo.ModulePublic) {
mod := module.Version{Path: m.Path, Version: m.Version}
if m.Version != "" {
if q, err := Query(ctx, m.Path, m.Version, "", nil); err != nil {
m.Error = &modinfo.ModuleError{Err: err.Error()}
} else {
m.Version = q.Version
m.Time = &q.Time
}
gomod, err := modfetch.CachePath(mod, "mod")
if err == nil {
if info, err := os.Stat(gomod); err == nil && info.Mode().IsRegular() {
m.GoMod = gomod
}
}
dir, err := modfetch.DownloadDir(mod)
if err == nil {
m.Dir = dir
}
if listRetracted {
addRetraction(ctx, m)
}
}
if m.GoVersion == "" {
if summary, err := rawGoModSummary(mod); err == nil && summary.goVersionV != "" {
m.GoVersion = summary.goVersionV[1:]
}
}
}
if !fromBuildList {
// If this was an explicitly-versioned argument to 'go mod download' or
// 'go list -m', report the actual requested version, not its replacement.
completeFromModCache(info) // Will set m.Error in vendor mode.
return info
}
r := Replacement(m)
if r.Path == "" {
if cfg.BuildMod == "vendor" {
// It's tempting to fill in the "Dir" field to point within the vendor
// directory, but that would be misleading: the vendor directory contains
// a flattened package tree, not complete modules, and it can even
// interleave packages from different modules if one module path is a
// prefix of the other.
} else {
completeFromModCache(info)
}
return info
}
// Don't hit the network to fill in extra data for replaced modules.
// The original resolved Version and Time don't matter enough to be
// worth the cost, and we're going to overwrite the GoMod and Dir from the
// replacement anyway. See https://golang.org/issue/27859.
info.Replace = &modinfo.ModulePublic{
Path: r.Path,
Version: r.Version,
}
if v, ok := rawGoVersion.Load(m); ok {
info.Replace.GoVersion = v.(string)
}
if r.Version == "" {
if filepath.IsAbs(r.Path) {
info.Replace.Dir = r.Path
} else {
info.Replace.Dir = filepath.Join(ModRoot(), r.Path)
}
info.Replace.GoMod = filepath.Join(info.Replace.Dir, "go.mod")
}
if cfg.BuildMod != "vendor" {
completeFromModCache(info.Replace)
info.Dir = info.Replace.Dir
info.GoMod = info.Replace.GoMod
info.Retracted = info.Replace.Retracted
}
info.GoVersion = info.Replace.GoVersion
return info
}
// PackageBuildInfo returns a string containing module version information
// for modules providing packages named by path and deps. path and deps must
// name packages that were resolved successfully with ImportPaths or one of
// the Load functions.
func PackageBuildInfo(path string, deps []string) string {
if isStandardImportPath(path) || !Enabled() {
return ""
}
target := mustFindModule(path, path)
mdeps := make(map[module.Version]bool)
for _, dep := range deps {
if !isStandardImportPath(dep) {
mdeps[mustFindModule(path, dep)] = true
}
}
var mods []module.Version
delete(mdeps, target)
for mod := range mdeps {
mods = append(mods, mod)
}
module.Sort(mods)
var buf bytes.Buffer
fmt.Fprintf(&buf, "path\t%s\n", path)
writeEntry := func(token string, m module.Version) {
mv := m.Version
if mv == "" {
mv = "(devel)"
}
fmt.Fprintf(&buf, "%s\t%s\t%s", token, m.Path, mv)
if r := Replacement(m); r.Path == "" {
fmt.Fprintf(&buf, "\t%s\n", modfetch.Sum(m))
} else {
fmt.Fprintf(&buf, "\n=>\t%s\t%s\t%s\n", r.Path, r.Version, modfetch.Sum(r))
}
}
writeEntry("mod", target)
for _, mod := range mods {
writeEntry("dep", mod)
}
return buf.String()
}
// mustFindModule is like findModule, but it calls base.Fatalf if the
// module can't be found.
//
// TODO(jayconrod): remove this. Callers should use findModule and return
// errors instead of relying on base.Fatalf.
func mustFindModule(target, path string) module.Version {
pkg, ok := loaded.pkgCache.Get(path).(*loadPkg)
if ok {
if pkg.err != nil {
base.Fatalf("build %v: cannot load %v: %v", target, path, pkg.err)
}
return pkg.mod
}
if path == "command-line-arguments" {
return Target
}
if printStackInDie {
debug.PrintStack()
}
base.Fatalf("build %v: cannot find module for path %v", target, path)
panic("unreachable")
}
// findModule searches for the module that contains the package at path.
// If the package was loaded with ImportPaths or one of the other loading
// functions, its containing module and true are returned. Otherwise,
// module.Version{} and false are returend.
func findModule(path string) (module.Version, bool) {
if pkg, ok := loaded.pkgCache.Get(path).(*loadPkg); ok {
return pkg.mod, pkg.mod != module.Version{}
}
if path == "command-line-arguments" {
return Target, true
}
return module.Version{}, false
}
func ModInfoProg(info string, isgccgo bool) []byte {
// Inject a variable with the debug information as runtime.modinfo,
// but compile it in package main so that it is specific to the binary.
// The variable must be a literal so that it will have the correct value
// before the initializer for package main runs.
//
// The runtime startup code refers to the variable, which keeps it live
// in all binaries.
//
// Note: we use an alternate recipe below for gccgo (based on an
// init function) due to the fact that gccgo does not support
// applying a "//go:linkname" directive to a variable. This has
// drawbacks in that other packages may want to look at the module
// info in their init functions (see issue 29628), which won't
// work for gccgo. See also issue 30344.
if !isgccgo {
return []byte(fmt.Sprintf(`package main
import _ "unsafe"
//go:linkname __debug_modinfo__ runtime.modinfo
var __debug_modinfo__ = %q
`, string(infoStart)+info+string(infoEnd)))
} else {
return []byte(fmt.Sprintf(`package main
import _ "unsafe"
//go:linkname __set_debug_modinfo__ runtime.setmodinfo
func __set_debug_modinfo__(string)
func init() { __set_debug_modinfo__(%q) }
`, string(infoStart)+info+string(infoEnd)))
}
}