github.com/octohelm/cuemod@v0.9.4/internal/cmd/go/internals/modload/build.go (about)

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