github.com/gernest/nezuko@v0.1.2/internal/modload/init.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  	"bytes"
     9  	"encoding/json"
    10  	"fmt"
    11  	"io/ioutil"
    12  	"os"
    13  	"path"
    14  	"path/filepath"
    15  	"regexp"
    16  	"runtime/debug"
    17  	"sort"
    18  	"strconv"
    19  	"strings"
    20  	"text/template"
    21  
    22  	"github.com/gernest/nezuko/internal/base"
    23  	"github.com/gernest/nezuko/internal/cache"
    24  	"github.com/gernest/nezuko/internal/cfg"
    25  	"github.com/gernest/nezuko/internal/load"
    26  	"github.com/gernest/nezuko/internal/modfetch"
    27  	"github.com/gernest/nezuko/internal/modfetch/codehost"
    28  	"github.com/gernest/nezuko/internal/modfile"
    29  	"github.com/gernest/nezuko/internal/module"
    30  	"github.com/gernest/nezuko/internal/mvs"
    31  	"github.com/gernest/nezuko/internal/renameio"
    32  	"github.com/gernest/nezuko/internal/search"
    33  )
    34  
    35  var (
    36  	cwd         string // TODO(bcmills): Is this redundant with base.Cwd?
    37  	initialized bool
    38  
    39  	modRoot     string
    40  	modFile     *modfile.File
    41  	modFileData []byte
    42  	excluded    map[module.Version]bool
    43  	Target      module.Version
    44  
    45  	gopath string
    46  
    47  	CmdModInit   bool   // running 'z mod init'
    48  	CmdModModule string // module argument for 'z mod init'
    49  )
    50  
    51  // ModFile returns the parsed z.mod file.
    52  //
    53  // Note that after calling ImportPaths or LoadBuildList,
    54  // the require statements in the modfile.File are no longer
    55  // the source of truth and will be ignored: edits made directly
    56  // will be lost at the next call to WriteGoMod.
    57  // To make permanent changes to the require statements
    58  // in z.mod, edit it before calling ImportPaths or LoadBuildList.
    59  func ModFile() *modfile.File {
    60  	Init()
    61  	if modFile == nil {
    62  		die()
    63  	}
    64  	return modFile
    65  }
    66  
    67  func BinDir() string {
    68  	Init()
    69  	return filepath.Join(gopath, "bin")
    70  }
    71  
    72  // mustUseModules reports whether we are invoked as vgo
    73  // (as opposed to go).
    74  // If so, we only support builds with z.mod files.
    75  func mustUseModules() bool {
    76  	name := os.Args[0]
    77  	name = name[strings.LastIndex(name, "/")+1:]
    78  	name = name[strings.LastIndex(name, `\`)+1:]
    79  	return strings.HasPrefix(name, "vgo")
    80  }
    81  
    82  // Init determines whether module mode is enabled, locates the root of the
    83  // current module (if any), sets environment variables for Git subprocesses, and
    84  // configures the cfg, codehost, load, modfetch, and search packages for use
    85  // with modules.
    86  func Init() {
    87  	if initialized {
    88  		return
    89  	}
    90  	initialized = true
    91  
    92  	// Disable any prompting for passwords by Git.
    93  	// Only has an effect for 2.3.0 or later, but avoiding
    94  	// the prompt in earlier versions is just too hard.
    95  	// If user has explicitly set GIT_TERMINAL_PROMPT=1, keep
    96  	// prompting.
    97  	// See golang.org/issue/9341 and golang.org/issue/12706.
    98  	if os.Getenv("GIT_TERMINAL_PROMPT") == "" {
    99  		os.Setenv("GIT_TERMINAL_PROMPT", "0")
   100  	}
   101  
   102  	// Disable any ssh connection pooling by Git.
   103  	// If a Git subprocess forks a child into the background to cache a new connection,
   104  	// that child keeps stdout/stderr open. After the Git subprocess exits,
   105  	// os /exec expects to be able to read from the stdout/stderr pipe
   106  	// until EOF to get all the data that the Git subprocess wrote before exiting.
   107  	// The EOF doesn't come until the child exits too, because the child
   108  	// is holding the write end of the pipe.
   109  	// This is unfortunate, but it has come up at least twice
   110  	// (see golang.org/issue/13453 and golang.org/issue/16104)
   111  	// and confuses users when it does.
   112  	// If the user has explicitly set GIT_SSH or GIT_SSH_COMMAND,
   113  	// assume they know what they are doing and don't step on it.
   114  	// But default to turning off ControlMaster.
   115  	if os.Getenv("GIT_SSH") == "" && os.Getenv("GIT_SSH_COMMAND") == "" {
   116  		os.Setenv("GIT_SSH_COMMAND", "ssh -o ControlMaster=no")
   117  	}
   118  
   119  	var err error
   120  	cwd, err = os.Getwd()
   121  	if err != nil {
   122  		base.Fatalf("z: %v", err)
   123  	}
   124  
   125  	if CmdModInit {
   126  		// Running 'z mod init': z.mod will be created in current directory.
   127  		modRoot = cwd
   128  	} else {
   129  		modRoot, _ = FindModuleRoot(cwd, "", true)
   130  		if modRoot == "" {
   131  		} else if search.InDir(modRoot, os.TempDir()) == "." {
   132  			// If you create /tmp/z.mod for experimenting,
   133  			// then any tests that create work directories under /tmp
   134  			// will find it and get modules when they're not expecting them.
   135  			// It's a bit of a peculiar thing to disallow but quite mysterious
   136  			// when it happens. See golang.org/issue/26708.
   137  			modRoot = ""
   138  			fmt.Fprintf(os.Stderr, "z: warning: ignoring z.mod in system temp root %v\n", os.TempDir())
   139  		}
   140  	}
   141  
   142  	// We're in module mode. Install the hooks to make it work.
   143  
   144  	if c := cache.Default(); c == nil {
   145  		// With modules, there are no install locations for packages
   146  		// other than the build cache.
   147  		base.Fatalf("z: cannot use modules with build cache disabled")
   148  	}
   149  
   150  	list := filepath.SplitList(cfg.BuildContext.ZIGPATH)
   151  	if len(list) == 0 || list[0] == "" {
   152  		base.Fatalf("missing $ZIGPATH")
   153  	}
   154  	gopath = list[0]
   155  	if _, err := os.Stat(filepath.Join(gopath, "z.mod")); err == nil {
   156  		base.Fatalf("$GOPATH/z.mod exists but should not")
   157  	}
   158  
   159  	oldSrcMod := filepath.Join(list[0], "src/mod")
   160  	pkgMod := filepath.Join(list[0], "pkg/mod")
   161  	infoOld, errOld := os.Stat(oldSrcMod)
   162  	_, errMod := os.Stat(pkgMod)
   163  	if errOld == nil && infoOld.IsDir() && errMod != nil && os.IsNotExist(errMod) {
   164  		os.Rename(oldSrcMod, pkgMod)
   165  	}
   166  
   167  	modfetch.PkgMod = pkgMod
   168  	codehost.WorkRoot = filepath.Join(pkgMod, "cache/vcs")
   169  
   170  	cfg.ModulesEnabled = true
   171  	load.ModBinDir = BinDir
   172  	load.ModLookup = Lookup
   173  	load.ModPackageModuleInfo = PackageModuleInfo
   174  	load.ModImportPaths = ImportPaths
   175  	load.ModPackageBuildInfo = PackageBuildInfo
   176  	load.ModInfoProg = ModInfoProg
   177  	load.ModImportFromFiles = ImportFromFiles
   178  	load.ModDirImportPath = DirImportPath
   179  
   180  	if modRoot == "" {
   181  		// We're in module mode, but not inside a module.
   182  		//
   183  		// If the command is 'go get' or 'go list' and all of the args are in the
   184  		// same existing module, we could use that module's download directory in
   185  		// the module cache as the module root, applying any replacements and/or
   186  		// exclusions specified by that module. However, that would leave us in a
   187  		// strange state: we want 'go get' to be consistent with 'go list', and 'go
   188  		// list' should be able to operate on multiple modules. Moreover, the 'get'
   189  		// target might specify relative file paths (e.g. in the same repository) as
   190  		// replacements, and we would not be able to apply those anyway: we would
   191  		// need to either error out or ignore just those replacements, when a build
   192  		// from an empty module could proceed without error.
   193  		//
   194  		// Instead, we'll operate as though we're in some ephemeral external module,
   195  		// ignoring all replacements and exclusions uniformly.
   196  
   197  		// Normally we check sums using the go.sum file from the main module, but
   198  		// without a main module we do not have an authoritative go.sum file.
   199  		//
   200  		// TODO(bcmills): In Go 1.13, check sums when outside the main module.
   201  		//
   202  		// One possible approach is to merge the go.sum files from all of the
   203  		// modules we download: that doesn't protect us against bad top-level
   204  		// modules, but it at least ensures consistency for transitive dependencies.
   205  	} else {
   206  		modfetch.ZigSumFile = filepath.Join(modRoot, "z.sum")
   207  		search.SetModRoot(modRoot)
   208  	}
   209  }
   210  
   211  func init() {
   212  	load.ModInit = Init
   213  
   214  	// Set modfetch.PkgMod unconditionally, so that go clean -modcache can run even without modules enabled.
   215  	if list := filepath.SplitList(cfg.BuildContext.ZIGPATH); len(list) > 0 && list[0] != "" {
   216  		modfetch.PkgMod = filepath.Join(list[0], "pkg/mod")
   217  	}
   218  }
   219  
   220  // ModRoot returns the root of the main module.
   221  // It calls base.Fatalf if there is no main module.
   222  func ModRoot() string {
   223  	if !HasModRoot() {
   224  		die()
   225  	}
   226  	return modRoot
   227  }
   228  
   229  // HasModRoot reports whether a main module is present.
   230  // HasModRoot may return false even if Enabled returns true: for example, 'get'
   231  // does not require a main module.
   232  func HasModRoot() bool {
   233  	Init()
   234  	return modRoot != ""
   235  }
   236  
   237  // printStackInDie causes die to print a stack trace.
   238  //
   239  // It is enabled by the testgo tag, and helps to diagnose paths that
   240  // unexpectedly require a main module.
   241  var printStackInDie = false
   242  
   243  func die() {
   244  	if printStackInDie {
   245  		debug.PrintStack()
   246  	}
   247  	base.Fatalf("z: cannot find main module; see 'z help modules'")
   248  }
   249  
   250  // InitMod sets Target and, if there is a main module, parses the initial build
   251  // list from its z.mod file, creating and populating that file if needed.
   252  func InitMod() {
   253  	if len(buildList) > 0 {
   254  		return
   255  	}
   256  
   257  	Init()
   258  	if modRoot == "" {
   259  		Target = module.Version{Path: "command-line-arguments"}
   260  		buildList = []module.Version{Target}
   261  		return
   262  	}
   263  
   264  	if CmdModInit {
   265  		// Running z mod init: do legacy module conversion
   266  		legacyModInit()
   267  		modFileToBuildList()
   268  		WriteGoMod()
   269  		return
   270  	}
   271  
   272  	gomod := filepath.Join(modRoot, "z.mod")
   273  	data, err := ioutil.ReadFile(gomod)
   274  	if err != nil {
   275  		if os.IsNotExist(err) {
   276  			legacyModInit()
   277  			modFileToBuildList()
   278  			WriteGoMod()
   279  			return
   280  		}
   281  		base.Fatalf("z: %v", err)
   282  	}
   283  
   284  	f, err := modfile.Parse(gomod, data, fixVersion)
   285  	if err != nil {
   286  		// Errors returned by modfile.Parse begin with file:line.
   287  		base.Fatalf("z: errors parsing z.mod:\n%s\n", err)
   288  	}
   289  	modFile = f
   290  	modFileData = data
   291  
   292  	if len(f.Syntax.Stmt) == 0 || f.Module == nil {
   293  		// Empty mod file. Must add module path.
   294  		path, err := FindModulePath(modRoot)
   295  		if err != nil {
   296  			base.Fatalf("z: %v", err)
   297  		}
   298  		f.AddModuleStmt(path)
   299  	}
   300  
   301  	if len(f.Syntax.Stmt) == 1 && f.Module != nil {
   302  		// Entire file is just a module statement.
   303  		// Populate require if possible.
   304  		legacyModInit()
   305  	}
   306  
   307  	excluded = make(map[module.Version]bool)
   308  	for _, x := range f.Exclude {
   309  		excluded[x.Mod] = true
   310  	}
   311  	modFileToBuildList()
   312  	WriteGoMod()
   313  }
   314  
   315  // modFileToBuildList initializes buildList from the modFile.
   316  func modFileToBuildList() {
   317  	Target = modFile.Module.Mod
   318  	list := []module.Version{Target}
   319  	for _, r := range modFile.Require {
   320  		list = append(list, r.Mod)
   321  	}
   322  	buildList = list
   323  }
   324  
   325  // Allowed reports whether module m is allowed (not excluded) by the main module's z.mod.
   326  func Allowed(m module.Version) bool {
   327  	return !excluded[m]
   328  }
   329  
   330  func legacyModInit() {
   331  	if modFile == nil {
   332  		path, err := FindModulePath(modRoot)
   333  		if err != nil {
   334  			base.Fatalf("z: %v", err)
   335  		}
   336  		fmt.Fprintf(os.Stderr, "z: creating new z.mod: module %s\n", path)
   337  		modFile = new(modfile.File)
   338  		modFile.AddModuleStmt(path)
   339  	}
   340  
   341  	addExportsStmt()
   342  }
   343  
   344  // InitGoStmt adds a go statement, unless there already is one.
   345  func InitGoStmt() {
   346  	if modFile.Exports == nil {
   347  		addExportsStmt()
   348  	}
   349  }
   350  
   351  // addGoStmt adds a go statement referring to the current version.
   352  func addExportsStmt() {
   353  	name := path.Base(modFile.Module.Mod.Path)
   354  	if !modfile.IsValidExport(name) {
   355  		base.Fatalf("z: unrecognized export name %q", name)
   356  	}
   357  	if err := modFile.AddExportsStmt(name); err != nil {
   358  		base.Fatalf("z: internal error: %v", err)
   359  	}
   360  }
   361  
   362  // Exported only for testing.
   363  func FindModuleRoot(dir, limit string, legacyConfigOK bool) (root, file string) {
   364  	dir = filepath.Clean(dir)
   365  	limit = filepath.Clean(limit)
   366  
   367  	// Look for enclosing z.mod.
   368  	for {
   369  		if fi, err := os.Stat(filepath.Join(dir, "z.mod")); err == nil && !fi.IsDir() {
   370  			return dir, "z.mod"
   371  		}
   372  		if dir == limit {
   373  			break
   374  		}
   375  		d := filepath.Dir(dir)
   376  		if d == dir {
   377  			break
   378  		}
   379  		dir = d
   380  	}
   381  	return "", ""
   382  }
   383  
   384  // Exported only for testing.
   385  func FindModulePath(dir string) (string, error) {
   386  	if CmdModModule != "" {
   387  		// Running z mod init x/y/z; return x/y/z.
   388  		return CmdModModule, nil
   389  	}
   390  
   391  	// Cast about for import comments,
   392  	// first in top-level directory, then in subdirectories.
   393  	list, _ := ioutil.ReadDir(dir)
   394  	for _, info := range list {
   395  		if info.Mode().IsRegular() && strings.HasSuffix(info.Name(), ".go") {
   396  			if com := findImportComment(filepath.Join(dir, info.Name())); com != "" {
   397  				return com, nil
   398  			}
   399  		}
   400  	}
   401  	for _, info1 := range list {
   402  		if info1.IsDir() {
   403  			files, _ := ioutil.ReadDir(filepath.Join(dir, info1.Name()))
   404  			for _, info2 := range files {
   405  				if info2.Mode().IsRegular() && strings.HasSuffix(info2.Name(), ".go") {
   406  					if com := findImportComment(filepath.Join(dir, info1.Name(), info2.Name())); com != "" {
   407  						return path.Dir(com), nil
   408  					}
   409  				}
   410  			}
   411  		}
   412  	}
   413  
   414  	// Look for Godeps.json declaring import path.
   415  	data, _ := ioutil.ReadFile(filepath.Join(dir, "Godeps/Godeps.json"))
   416  	var cfg1 struct{ ImportPath string }
   417  	json.Unmarshal(data, &cfg1)
   418  	if cfg1.ImportPath != "" {
   419  		return cfg1.ImportPath, nil
   420  	}
   421  
   422  	// Look for vendor.json declaring import path.
   423  	data, _ = ioutil.ReadFile(filepath.Join(dir, "vendor/vendor.json"))
   424  	var cfg2 struct{ RootPath string }
   425  	json.Unmarshal(data, &cfg2)
   426  	if cfg2.RootPath != "" {
   427  		return cfg2.RootPath, nil
   428  	}
   429  
   430  	// Look for path in GOPATH.
   431  	for _, gpdir := range filepath.SplitList(cfg.BuildContext.ZIGPATH) {
   432  		if gpdir == "" {
   433  			continue
   434  		}
   435  		if rel := search.InDir(dir, filepath.Join(gpdir, "src")); rel != "" && rel != "." {
   436  			return filepath.ToSlash(rel), nil
   437  		}
   438  	}
   439  
   440  	// Look for .git/config with github origin as last resort.
   441  	data, _ = ioutil.ReadFile(filepath.Join(dir, ".git/config"))
   442  	if m := gitOriginRE.FindSubmatch(data); m != nil {
   443  		return "github.com/" + string(m[1]), nil
   444  	}
   445  
   446  	return "", fmt.Errorf("cannot determine module path for source directory %s (outside GOPATH, no import comments)", dir)
   447  }
   448  
   449  var (
   450  	gitOriginRE     = regexp.MustCompile(`(?m)^\[remote "origin"\]\r?\n\turl = (?:https://github.com/|git@github.com:|gh:)([^/]+/[^/]+?)(\.git)?\r?\n`)
   451  	importCommentRE = regexp.MustCompile(`(?m)^package[ \t]+[^ \t\r\n/]+[ \t]+//[ \t]+import[ \t]+(\"[^"]+\")[ \t]*\r?\n`)
   452  )
   453  
   454  func findImportComment(file string) string {
   455  	data, err := ioutil.ReadFile(file)
   456  	if err != nil {
   457  		return ""
   458  	}
   459  	m := importCommentRE.FindSubmatch(data)
   460  	if m == nil {
   461  		return ""
   462  	}
   463  	path, err := strconv.Unquote(string(m[1]))
   464  	if err != nil {
   465  		return ""
   466  	}
   467  	return path
   468  }
   469  
   470  var allowWriteGoMod = true
   471  
   472  // DisallowWriteGoMod causes future calls to WriteGoMod to do nothing at all.
   473  func DisallowWriteGoMod() {
   474  	allowWriteGoMod = false
   475  }
   476  
   477  // AllowWriteGoMod undoes the effect of DisallowWriteGoMod:
   478  // future calls to WriteGoMod will update z.mod if needed.
   479  // Note that any past calls have been discarded, so typically
   480  // a call to AlowWriteGoMod should be followed by a call to WriteGoMod.
   481  func AllowWriteGoMod() {
   482  	allowWriteGoMod = true
   483  }
   484  
   485  // MinReqs returns a Reqs with minimal dependencies of Target,
   486  // as will be written to z.mod.
   487  func MinReqs() mvs.Reqs {
   488  	var direct []string
   489  	for _, m := range buildList[1:] {
   490  		if loaded.direct[m.Path] {
   491  			direct = append(direct, m.Path)
   492  		}
   493  	}
   494  	min, err := mvs.Req(Target, buildList, direct, Reqs())
   495  	if err != nil {
   496  		base.Fatalf("z: %v", err)
   497  	}
   498  	return &mvsReqs{buildList: append([]module.Version{Target}, min...)}
   499  }
   500  
   501  // WriteGoMod writes the current build list back to z.mod.
   502  func WriteGoMod() {
   503  	// If we're using -mod=vendor we basically ignored
   504  	// z.mod, so definitely don't try to write back our
   505  	// incomplete view of the world.
   506  	if !allowWriteGoMod || cfg.BuildMod == "vendor" {
   507  		return
   508  	}
   509  
   510  	// If we aren't in a module, we don't have anywhere to write a z.mod file.
   511  	if modRoot == "" {
   512  		return
   513  	}
   514  
   515  	if loaded != nil {
   516  		reqs := MinReqs()
   517  		min, err := reqs.Required(Target)
   518  		if err != nil {
   519  			base.Fatalf("z: %v", err)
   520  		}
   521  		var list []*modfile.Require
   522  		for _, m := range min {
   523  			list = append(list, &modfile.Require{
   524  				Mod:      m,
   525  				Indirect: !loaded.direct[m.Path],
   526  			})
   527  		}
   528  		modFile.SetRequire(list)
   529  	}
   530  
   531  	modFile.Cleanup() // clean file after edits
   532  	new, err := modFile.Format()
   533  	if err != nil {
   534  		base.Fatalf("z: %v", err)
   535  	}
   536  
   537  	// Always update z.sum, even if we didn't change z.mod: we may have
   538  	// downloaded modules that we didn't have before.
   539  	modfetch.WriteZigSum()
   540  
   541  	if bytes.Equal(new, modFileData) {
   542  		// We don't need to modify z.mod from what we read previously.
   543  		// Ignore any intervening edits.
   544  		return
   545  	}
   546  	if cfg.BuildMod == "readonly" {
   547  		base.Fatalf("z: updates to z.mod needed, disabled by -mod=readonly")
   548  	}
   549  
   550  	unlock := modfetch.SideLock()
   551  	defer unlock()
   552  
   553  	file := filepath.Join(modRoot, "z.mod")
   554  	old, err := ioutil.ReadFile(file)
   555  	if !bytes.Equal(old, modFileData) {
   556  		if bytes.Equal(old, new) {
   557  			// Some other process wrote the same z.mod file that we were about to write.
   558  			modFileData = new
   559  			return
   560  		}
   561  		if err != nil {
   562  			base.Fatalf("z: can't determine whether z.mod has changed: %v", err)
   563  		}
   564  		// The contents of the z.mod file have changed. In theory we could add all
   565  		// of the new modules to the build list, recompute, and check whether any
   566  		// module in *our* build list got bumped to a different version, but that's
   567  		// a lot of work for marginal benefit. Instead, fail the command: if users
   568  		// want to run concurrent commands, they need to start with a complete,
   569  		// consistent module definition.
   570  		base.Fatalf("z: updates to z.mod needed, but contents have changed")
   571  
   572  	}
   573  
   574  	if err := renameio.WriteFile(file, new); err != nil {
   575  		base.Fatalf("error writing z.mod: %v", err)
   576  	}
   577  	modFileData = new
   578  }
   579  
   580  const buildFileTpl = `const build_pkg = @import("std").build;
   581  const Builder = build_pkg.Builder;
   582  const LibExeObjStep = build_pkg.LibExeObjStep;
   583  const mod = @import("mod.zig");
   584  const ward = @import("std").debug.warn;
   585  
   586  pub fn build(b: *Builder) void {
   587      mod.build(b);
   588  }
   589  
   590  const Pkg = struct {
   591      name: []const u8,
   592      path: []const u8,
   593  };
   594  const packages = [_]Pkg{
   595  	<<range . ->>
   596  	Pkg{.name= "<<.Export>>", .path="<<.Path>>"},
   597  	<<end>>
   598  };
   599  
   600  // addPackages looks for all top level steps and adds packages to them.
   601  fn addPackages(b: *Builder) void {
   602      if (packages.len == 0) return;
   603      var top_steps = b.top_level_steps.toSlice();
   604      var i: usize = 0;
   605      while (i < top_steps.len) : (i += 1) {
   606          var step_ptr = &top_steps[i].step;
   607          var step = @fieldParentPtr(LibExeObjStep, "step", step_ptr);
   608          for (packages) |pkg| {
   609              step.addPackagePath(pkg.name, pkg.path);
   610          }
   611      }
   612  }
   613  `
   614  
   615  var bTpl = template.Must(template.New("build").Delims("<<", ">>").Parse(buildFileTpl))
   616  
   617  type Pkg struct {
   618  	Export string
   619  	Path   string
   620  }
   621  
   622  func WriteZigBuildFile() {
   623  	loaded.load(func() []string {
   624  		return []string{Target.Path}
   625  	})
   626  	var pkgs []*Pkg
   627  	var dir string
   628  	for _, pkg := range loaded.pkgs {
   629  		if pkg.mod.Path == Target.Path {
   630  			dir = pkg.dir
   631  			continue
   632  		}
   633  		pkgs = append(pkgs, &Pkg{
   634  			Export: pkg.exports,
   635  			Path:   pkg.entryFile,
   636  		})
   637  	}
   638  	sort.Slice(pkgs, func(i, j int) bool {
   639  		return pkgs[i].Export < pkgs[j].Export
   640  	})
   641  	var buf bytes.Buffer
   642  	err := bTpl.Execute(&buf, pkgs)
   643  	if err != nil {
   644  		base.Fatalf("error generating build.zig: %v", err)
   645  		return
   646  	}
   647  	file := cfg.BuildContext.JoinPath(dir, "build.zig")
   648  	if err := renameio.WriteFile(file, buf.Bytes()); err != nil {
   649  		base.Fatalf("error writing build.zig: %v", err)
   650  	}
   651  }
   652  
   653  func getEntryFile() {
   654  
   655  }
   656  
   657  func fixVersion(path, vers string) (string, error) {
   658  	// Special case: remove the old -gopkgin- hack.
   659  	if strings.HasPrefix(path, "gopkg.in/") && strings.Contains(vers, "-gopkgin-") {
   660  		vers = vers[strings.Index(vers, "-gopkgin-")+len("-gopkgin-"):]
   661  	}
   662  
   663  	// fixVersion is called speculatively on every
   664  	// module, version pair from every z.mod file.
   665  	// Avoid the query if it looks OK.
   666  	_, pathMajor, ok := module.SplitPathVersion(path)
   667  	if !ok {
   668  		return "", fmt.Errorf("malformed module path: %s", path)
   669  	}
   670  	if vers != "" && module.CanonicalVersion(vers) == vers && module.MatchPathMajor(vers, pathMajor) {
   671  		return vers, nil
   672  	}
   673  
   674  	info, err := Query(path, vers, nil)
   675  	if err != nil {
   676  		return "", err
   677  	}
   678  	return info.Version, nil
   679  }