cache.go raw

   1  // Package cache manages the mxinstall binary and protocol header cache.
   2  package cache
   3  
   4  import (
   5  	"crypto/sha256"
   6  	"encoding/hex"
   7  	"encoding/json"
   8  	"fmt"
   9  	"io/fs"
  10  	"os"
  11  	"path/filepath"
  12  	"sort"
  13  	"strings"
  14  	"time"
  15  
  16  	"moxie/goenv"
  17  )
  18  
  19  // Meta holds installation metadata for a cached binary.
  20  type Meta struct {
  21  	SourceHash      string    `json:"source_hash"`
  22  	CompilerVersion string    `json:"compiler_version"`
  23  	Triple          string    `json:"triple"`
  24  	InstalledAt     time.Time `json:"installed_at"`
  25  }
  26  
  27  // Root returns the root of the mxinstall cache.
  28  func Root() string {
  29  	return filepath.Join(goenv.Get("GOCACHE"), "mxinstall")
  30  }
  31  
  32  // BinPath returns the path for the installed binary.
  33  // triple is the LLVM target triple from compileopts.Config.Triple().
  34  func BinPath(triple, importPath, binaryName string) string {
  35  	return filepath.Join(Root(), triple, importPath, binaryName)
  36  }
  37  
  38  // MXHPath returns the path for the cached .mxh (target-independent).
  39  func MXHPath(importPath string) string {
  40  	return filepath.Join(Root(), "protocols", importPath+".mxh")
  41  }
  42  
  43  // MetaPath returns the path for the .meta JSON file.
  44  func MetaPath(triple, importPath string) string {
  45  	return filepath.Join(Root(), triple, importPath, ".meta")
  46  }
  47  
  48  // SourcePath returns the path where cloned source lives.
  49  func SourcePath(importPath string) string {
  50  	return filepath.Join(Root(), "src", importPath)
  51  }
  52  
  53  // ReadMeta reads the cached metadata for a binary.
  54  func ReadMeta(triple, importPath string) (*Meta, error) {
  55  	data, err := os.ReadFile(MetaPath(triple, importPath))
  56  	if err != nil {
  57  		return nil, err
  58  	}
  59  	var m Meta
  60  	if err := json.Unmarshal(data, &m); err != nil {
  61  		return nil, fmt.Errorf("cache meta decode: %w", err)
  62  	}
  63  	return &m, nil
  64  }
  65  
  66  // WriteMeta writes the cache metadata.
  67  func WriteMeta(triple, importPath string, m *Meta) error {
  68  	path := MetaPath(triple, importPath)
  69  	if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil {
  70  		return err
  71  	}
  72  	data, err := json.Marshal(m)
  73  	if err != nil {
  74  		return err
  75  	}
  76  	return os.WriteFile(path, data, 0644)
  77  }
  78  
  79  // Clean removes the entire mxinstall cache.
  80  func Clean() error {
  81  	return os.RemoveAll(Root())
  82  }
  83  
  84  // List returns the import paths of all installed packages.
  85  // Each import path is listed once regardless of how many triples it has.
  86  func List() ([]string, error) {
  87  	seen := make(map[string]bool)
  88  	var out []string
  89  
  90  	root := Root()
  91  	// Walk triple dirs to find .meta files.
  92  	entries, err := os.ReadDir(root)
  93  	if err != nil {
  94  		if os.IsNotExist(err) {
  95  			return nil, nil
  96  		}
  97  		return nil, err
  98  	}
  99  	for _, tripleEntry := range entries {
 100  		if !tripleEntry.IsDir() || tripleEntry.Name() == "protocols" || tripleEntry.Name() == "src" {
 101  			continue
 102  		}
 103  		tripleDir := filepath.Join(root, tripleEntry.Name())
 104  		_ = filepath.WalkDir(tripleDir, func(path string, d fs.DirEntry, err error) error {
 105  			if err != nil || d.Name() != ".meta" {
 106  				return nil
 107  			}
 108  			// path = root/triple/import/path/.meta
 109  			rel, _ := filepath.Rel(tripleDir, filepath.Dir(path))
 110  			importPath := filepath.ToSlash(rel)
 111  			if !seen[importPath] {
 112  				seen[importPath] = true
 113  				out = append(out, importPath)
 114  			}
 115  			return nil
 116  		})
 117  	}
 118  	sort.Strings(out)
 119  	return out, nil
 120  }
 121  
 122  // SourceHash computes a stable hash of all .mx source files in dir.
 123  // Sorts files before hashing so the result is deterministic.
 124  func SourceHash(dir string) (string, error) {
 125  	var files []string
 126  	_ = filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error {
 127  		if err == nil && !d.IsDir() && strings.HasSuffix(d.Name(), ".mx") {
 128  			files = append(files, path)
 129  		}
 130  		return nil
 131  	})
 132  	sort.Strings(files)
 133  
 134  	h := sha256.New()
 135  	for _, f := range files {
 136  		data, err := os.ReadFile(f)
 137  		if err != nil {
 138  			return "", err
 139  		}
 140  		h.Write(data)
 141  	}
 142  	return hex.EncodeToString(h.Sum(nil)), nil
 143  }
 144