main.go raw

   1  package main
   2  
   3  import (
   4  	"fmt"
   5  	"os"
   6  	"path/filepath"
   7  	"runtime"
   8  	"unsafe"
   9  
  10  	"github.com/ebitengine/purego"
  11  )
  12  
  13  func main() {
  14  	if len(os.Args) < 2 {
  15  		fmt.Fprintf(os.Stderr, "usage: %s <path-to-loader.so>\n", os.Args[0])
  16  		os.Exit(1)
  17  	}
  18  	soPath := os.Args[1]
  19  	if !filepath.IsAbs(soPath) {
  20  		wd, _ := os.Getwd()
  21  		soPath = filepath.Join(wd, soPath)
  22  	}
  23  
  24  	lib, err := purego.Dlopen(soPath, purego.RTLD_NOW|purego.RTLD_GLOBAL)
  25  	if err != nil {
  26  		fmt.Fprintf(os.Stderr, "dlopen %s: %v\n", soPath, err)
  27  		os.Exit(1)
  28  	}
  29  
  30  	var loaderLoad func(uintptr, int32, uintptr, int32, uintptr, int32) int32
  31  	purego.RegisterLibFunc(&loaderLoad, lib, "moxie_loader_load")
  32  
  33  	var pkgCount func(int32) int32
  34  	purego.RegisterLibFunc(&pkgCount, lib, "moxie_loader_pkg_count")
  35  
  36  	var modPath func(int32, uintptr, int32) int32
  37  	purego.RegisterLibFunc(&modPath, lib, "moxie_loader_mod_path")
  38  
  39  	var pkgPath func(int32, int32, uintptr, int32) int32
  40  	purego.RegisterLibFunc(&pkgPath, lib, "moxie_loader_pkg_path")
  41  
  42  	var pkgDir func(int32, int32, uintptr, int32) int32
  43  	purego.RegisterLibFunc(&pkgDir, lib, "moxie_loader_pkg_dir")
  44  
  45  	var pkgName func(int32, int32, uintptr, int32) int32
  46  	purego.RegisterLibFunc(&pkgName, lib, "moxie_loader_pkg_name")
  47  
  48  	var pkgFileCount func(int32, int32) int32
  49  	purego.RegisterLibFunc(&pkgFileCount, lib, "moxie_loader_pkg_file_count")
  50  
  51  	var pkgImportCount func(int32, int32) int32
  52  	purego.RegisterLibFunc(&pkgImportCount, lib, "moxie_loader_pkg_import_count")
  53  
  54  	var pkgImport func(int32, int32, int32, uintptr, int32) int32
  55  	purego.RegisterLibFunc(&pkgImport, lib, "moxie_loader_pkg_import")
  56  
  57  	var pkgIsMXH func(int32, int32) int32
  58  	purego.RegisterLibFunc(&pkgIsMXH, lib, "moxie_loader_pkg_is_mxh")
  59  
  60  	var loaderFree func(int32)
  61  	purego.RegisterLibFunc(&loaderFree, lib, "moxie_loader_free")
  62  
  63  	pass := 0
  64  	fail := 0
  65  	assert := func(name string, cond bool) {
  66  		if cond {
  67  			pass++
  68  		} else {
  69  			fail++
  70  			fmt.Fprintf(os.Stderr, "FAIL: %s\n", name)
  71  		}
  72  	}
  73  
  74  	readStr := func(fn func(int32, uintptr, int32) int32, h int32) string {
  75  		buf := make([]byte, 512)
  76  		n := fn(h, uintptr(unsafe.Pointer(&buf[0])), 512)
  77  		return string(buf[:n])
  78  	}
  79  
  80  	readPkgStr := func(fn func(int32, int32, uintptr, int32) int32, h int32, idx int32) string {
  81  		buf := make([]byte, 512)
  82  		n := fn(h, idx, uintptr(unsafe.Pointer(&buf[0])), 512)
  83  		return string(buf[:n])
  84  	}
  85  
  86  	readImport := func(h int32, idx int32, impIdx int32) string {
  87  		buf := make([]byte, 512)
  88  		n := pkgImport(h, idx, impIdx, uintptr(unsafe.Pointer(&buf[0])), 512)
  89  		return string(buf[:n])
  90  	}
  91  
  92  	// Create a temporary multi-package project.
  93  	tmpDir, err := os.MkdirTemp("", "loader-probe-*")
  94  	if err != nil {
  95  		fmt.Fprintf(os.Stderr, "mktempdir: %v\n", err)
  96  		os.Exit(1)
  97  	}
  98  	defer os.RemoveAll(tmpDir)
  99  
 100  	// Write moxie.mod.
 101  	os.WriteFile(filepath.Join(tmpDir, "moxie.mod"), []byte("module example.com/testpkg\n"), 0644)
 102  
 103  	// Write main package.
 104  	os.WriteFile(filepath.Join(tmpDir, "main.mx"), []byte(`package main
 105  
 106  import (
 107  	"fmt"
 108  	"example.com/testpkg/util"
 109  )
 110  
 111  func main() {
 112  	fmt.Println(util.Hello())
 113  }
 114  `), 0644)
 115  
 116  	// Write util subpackage.
 117  	os.MkdirAll(filepath.Join(tmpDir, "util"), 0755)
 118  	os.WriteFile(filepath.Join(tmpDir, "util", "util.mx"), []byte(`package util
 119  
 120  import "bytes"
 121  
 122  func Hello() string {
 123  	return bytes.Join([]string{"hello", "world"}, " ")
 124  }
 125  `), 0644)
 126  
 127  	// Find the Moxie GOROOT.
 128  	moxieRoot := os.Getenv("MOXIEROOT")
 129  	if moxieRoot == "" {
 130  		// Try relative to this binary's location.
 131  		exe, _ := os.Executable()
 132  		moxieRoot = filepath.Dir(filepath.Dir(filepath.Dir(exe)))
 133  	}
 134  	goroot := filepath.Join(moxieRoot, "lib", "moxie")
 135  	// If that doesn't exist, try the synthetic goroot path.
 136  	if _, err := os.Stat(goroot); err != nil {
 137  		goroot = ""
 138  	}
 139  
 140  	// Load the project.
 141  	rootDirB := []byte(tmpDir)
 142  	inputPkgB := []byte(".")
 143  	gorootB := []byte(goroot)
 144  	if len(gorootB) == 0 {
 145  		gorootB = []byte{0}
 146  	}
 147  
 148  	h := loaderLoad(
 149  		uintptr(unsafe.Pointer(&rootDirB[0])), int32(len(rootDirB)),
 150  		uintptr(unsafe.Pointer(&inputPkgB[0])), int32(len(inputPkgB)),
 151  		uintptr(unsafe.Pointer(&gorootB[0])), int32(len(goroot)),
 152  	)
 153  	fmt.Printf("loader_load: handle = %d\n", h)
 154  	assert("loader returns valid handle", h >= 0)
 155  
 156  	if h < 0 {
 157  		fmt.Printf("%d/%d tests passed\n", pass, pass+fail)
 158  		os.Exit(1)
 159  	}
 160  
 161  	// Check module path.
 162  	mp := readStr(modPath, h)
 163  	fmt.Printf("mod_path: %s\n", mp)
 164  	assert("module path is example.com/testpkg", mp == "example.com/testpkg")
 165  
 166  	// Check package count.
 167  	count := pkgCount(h)
 168  	fmt.Printf("pkg_count: %d\n", count)
 169  	// Should have at least: runtime, moxie, fmt, bytes, util, main (exact count depends on stdlib resolution)
 170  	assert(fmt.Sprintf("pkg_count >= 2, got %d", count), count >= 2)
 171  
 172  	// Print all packages.
 173  	for i := int32(0); i < count; i++ {
 174  		p := readPkgStr(pkgPath, h, i)
 175  		n := readPkgStr(pkgName, h, i)
 176  		d := readPkgStr(pkgDir, h, i)
 177  		fc := pkgFileCount(h, i)
 178  		ic := pkgImportCount(h, i)
 179  		mxh := pkgIsMXH(h, i)
 180  		fmt.Printf("  [%d] path=%s name=%s dir=%s files=%d imports=%d mxh=%d\n", i, p, n, d, fc, ic, mxh)
 181  
 182  		// Print imports.
 183  		for j := int32(0); j < ic; j++ {
 184  			imp := readImport(h, i, j)
 185  			fmt.Printf("       import: %s\n", imp)
 186  		}
 187  	}
 188  
 189  	// Find the util package and main package in the list.
 190  	foundUtil := false
 191  	foundMain := false
 192  	utilIdx := int32(-1)
 193  	mainIdx := int32(-1)
 194  	for i := int32(0); i < count; i++ {
 195  		p := readPkgStr(pkgPath, h, i)
 196  		if p == "example.com/testpkg/util" {
 197  			foundUtil = true
 198  			utilIdx = i
 199  		}
 200  		if p == "example.com/testpkg" {
 201  			foundMain = true
 202  			mainIdx = i
 203  		}
 204  	}
 205  	assert("util package found", foundUtil)
 206  	assert("main package found", foundMain)
 207  
 208  	// util should come before main in dependency order.
 209  	if foundUtil && foundMain {
 210  		assert("util before main in dep order", utilIdx < mainIdx)
 211  	}
 212  
 213  	// util should have 1 file.
 214  	if foundUtil {
 215  		fc := pkgFileCount(h, utilIdx)
 216  		assert(fmt.Sprintf("util has 1 file, got %d", fc), fc == 1)
 217  	}
 218  
 219  	// main should have 1 file.
 220  	if foundMain {
 221  		fc := pkgFileCount(h, mainIdx)
 222  		assert(fmt.Sprintf("main has 1 file, got %d", fc), fc == 1)
 223  	}
 224  
 225  	// main should import util and fmt.
 226  	if foundMain {
 227  		ic := pkgImportCount(h, mainIdx)
 228  		mainImports := make(map[string]bool)
 229  		for j := int32(0); j < ic; j++ {
 230  			imp := readImport(h, mainIdx, j)
 231  			mainImports[imp] = true
 232  		}
 233  		assert("main imports fmt", mainImports["fmt"])
 234  		assert("main imports example.com/testpkg/util", mainImports["example.com/testpkg/util"])
 235  	}
 236  
 237  	// util should import bytes.
 238  	if foundUtil {
 239  		ic := pkgImportCount(h, utilIdx)
 240  		utilImports := make(map[string]bool)
 241  		for j := int32(0); j < ic; j++ {
 242  			imp := readImport(h, utilIdx, j)
 243  			utilImports[imp] = true
 244  		}
 245  		assert("util imports bytes", utilImports["bytes"])
 246  	}
 247  
 248  	loaderFree(h)
 249  
 250  	runtime.KeepAlive(rootDirB)
 251  	runtime.KeepAlive(inputPkgB)
 252  	runtime.KeepAlive(gorootB)
 253  
 254  	fmt.Printf("%d/%d tests passed\n", pass, pass+fail)
 255  	if fail > 0 {
 256  		os.Exit(1)
 257  	}
 258  }
 259