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