vendor.go raw

   1  package loader
   2  
   3  import (
   4  	"crypto/sha256"
   5  	"encoding/hex"
   6  	"fmt"
   7  	"os"
   8  	"path/filepath"
   9  	"sort"
  10  	"strings"
  11  )
  12  
  13  // Vendor copies all required dependencies into the vendor/ directory
  14  // under the module root. It reads requires and replaces from the module
  15  // file, resolves each to the module cache, and copies the source files.
  16  // A vendor/modules.txt is written for compatibility with Go tooling.
  17  // A vendor/moxie.sum is written with SHA256 hashes of each vendored file.
  18  func Vendor(dir string) error {
  19  	modPath, modDir, _, err := readModInfo(dir)
  20  	if err != nil {
  21  		return fmt.Errorf("vendor: %w", err)
  22  	}
  23  	if modPath == "" {
  24  		return fmt.Errorf("vendor: no module file found in %s or parents", dir)
  25  	}
  26  
  27  	modFile := findModFile(modDir)
  28  	requires, err := readModRequires(modFile)
  29  	if err != nil {
  30  		return fmt.Errorf("vendor: %w", err)
  31  	}
  32  	replaces, err := readModReplaces(modFile, modDir)
  33  	if err != nil {
  34  		return fmt.Errorf("vendor: %w", err)
  35  	}
  36  
  37  	if len(requires) == 0 && len(replaces) == 0 {
  38  		fmt.Println("no dependencies to vendor")
  39  		return nil
  40  	}
  41  
  42  	modCache := gomodcache()
  43  
  44  	vendorDir := filepath.Join(modDir, "vendor")
  45  	if err := os.RemoveAll(vendorDir); err != nil {
  46  		return fmt.Errorf("vendor: cleaning vendor dir: %w", err)
  47  	}
  48  	if err := os.MkdirAll(vendorDir, 0755); err != nil {
  49  		return fmt.Errorf("vendor: creating vendor dir: %w", err)
  50  	}
  51  
  52  	var modulesTxt strings.Builder
  53  	var sumLines []string
  54  
  55  	mods := make([]string, 0, len(requires))
  56  	for mod := range requires {
  57  		mods = append(mods, mod)
  58  	}
  59  	sort.Strings(mods)
  60  
  61  	for _, mod := range mods {
  62  		ver := requires[mod]
  63  
  64  		var srcDir string
  65  		if localDir, ok := replaces[mod]; ok {
  66  			srcDir = localDir
  67  		} else {
  68  			srcDir = filepath.Join(modCache, mod+"@"+ver)
  69  		}
  70  
  71  		if !isDir(srcDir) {
  72  			return fmt.Errorf("vendor: source not found for %s@%s at %s", mod, ver, srcDir)
  73  		}
  74  
  75  		dstDir := filepath.Join(vendorDir, mod)
  76  
  77  		hashes, err := copyTree(srcDir, dstDir)
  78  		if err != nil {
  79  			return fmt.Errorf("vendor: copying %s: %w", mod, err)
  80  		}
  81  
  82  		modulesTxt.WriteString(fmt.Sprintf("# %s %s\n## explicit\n", mod, ver))
  83  
  84  		for _, h := range hashes {
  85  			sumLines = append(sumLines, fmt.Sprintf("%s %s", h.hash, h.path))
  86  		}
  87  	}
  88  
  89  	if err := os.WriteFile(filepath.Join(vendorDir, "modules.txt"), []byte(modulesTxt.String()), 0644); err != nil {
  90  		return fmt.Errorf("vendor: writing modules.txt: %w", err)
  91  	}
  92  
  93  	sort.Strings(sumLines)
  94  	sumContent := strings.Join(sumLines, "\n") + "\n"
  95  	if err := os.WriteFile(filepath.Join(vendorDir, "moxie.sum"), []byte(sumContent), 0644); err != nil {
  96  		return fmt.Errorf("vendor: writing moxie.sum: %w", err)
  97  	}
  98  
  99  	fmt.Printf("vendored %d modules into %s\n", len(mods), vendorDir)
 100  	return nil
 101  }
 102  
 103  type fileHash struct {
 104  	path string
 105  	hash string
 106  }
 107  
 108  func copyTree(src, dst string) ([]fileHash, error) {
 109  	var hashes []fileHash
 110  	err := filepath.Walk(src, func(path string, info os.FileInfo, err error) error {
 111  		if err != nil {
 112  			return err
 113  		}
 114  		rel, _ := filepath.Rel(src, path)
 115  		target := filepath.Join(dst, rel)
 116  
 117  		if info.IsDir() {
 118  			base := filepath.Base(path)
 119  			if base == "vendor" || base == ".git" || base[0] == '.' {
 120  				return filepath.SkipDir
 121  			}
 122  			return os.MkdirAll(target, 0755)
 123  		}
 124  
 125  		base := filepath.Base(path)
 126  		if base[0] == '.' || base[0] == '_' {
 127  			return nil
 128  		}
 129  
 130  		data, err := os.ReadFile(path)
 131  		if err != nil {
 132  			return err
 133  		}
 134  		if err := os.WriteFile(target, data, 0644); err != nil {
 135  			return err
 136  		}
 137  
 138  		h := sha256.Sum256(data)
 139  		hashes = append(hashes, fileHash{
 140  			path: filepath.Join(filepath.Base(dst), rel),
 141  			hash: "sha256:" + hex.EncodeToString(h[:]),
 142  		})
 143  		return nil
 144  	})
 145  	return hashes, err
 146  }
 147