main.mx raw
1 package main
2
3 import (
4 "os"
5 "unsafe"
6 )
7
8 //export mxc_run
9 func cRun(argv unsafe.Pointer, argc int32) int32
10
11 //export mxc_getenv
12 func cGetenv(name unsafe.Pointer, nameLen int32, buf unsafe.Pointer, bufCap int32) int32
13
14 //export mxc_listdir
15 func cListdir(dir unsafe.Pointer, dirLen int32, buf unsafe.Pointer, bufCap int32) int32
16
17 //export write
18 func cWrite(fd int32, buf unsafe.Pointer, count uint32) int32
19
20 //export mxc_writefile
21 func cWritefile(path unsafe.Pointer, pathLen int32, data unsafe.Pointer, dataLen int32, mode uint32) int32
22
23 //export mxc_mkdir
24 func cMkdir(path unsafe.Pointer, pathLen int32, mode uint32) int32
25
26 //export mxc_readfile
27 func cReadfile(path unsafe.Pointer, pathLen int32, buf unsafe.Pointer, bufCap int32) int32
28
29 //export mxc_filesize
30 func cFilesize(path unsafe.Pointer, pathLen int32) int32
31
32 func writeStr(fd int32, s string) {
33 b := []byte(s)
34 if len(b) > 0 {
35 cWrite(fd, unsafe.Pointer(unsafe.SliceData(b)), uint32(len(b)))
36 }
37 }
38
39 func writeFile(path string, data []byte, mode uint32) {
40 if len(path) == 0 {
41 return
42 }
43 cWritefile(unsafe.Pointer(unsafe.SliceData([]byte(path))), int32(len(path)),
44 unsafe.Pointer(unsafe.SliceData(data)), int32(len(data)), mode)
45 }
46
47 func mkdirAll(path string, mode uint32) {
48 if len(path) == 0 {
49 return
50 }
51 cMkdir(unsafe.Pointer(unsafe.SliceData([]byte(path))), int32(len(path)), mode)
52 }
53
54 func readFile(path string) ([]byte, bool) {
55 if len(path) == 0 {
56 return nil, false
57 }
58 pb := []byte(path)
59 sz := cFilesize(unsafe.Pointer(unsafe.SliceData(pb)), int32(len(pb)))
60 if sz < 0 {
61 return nil, false
62 }
63 if sz == 0 {
64 sz = 65536
65 }
66 buf := []byte{:sz}
67 n := cReadfile(unsafe.Pointer(unsafe.SliceData(pb)), int32(len(pb)),
68 unsafe.Pointer(unsafe.SliceData(buf)), sz)
69 if n < 0 {
70 return nil, false
71 }
72 return buf[:n], true
73 }
74
75 func fatal(msg string) {
76 writeStr(2, "mxc: " | msg | "\n")
77 os.Exit(1)
78 }
79
80 func getenv(name string) string {
81 nb := []byte(name)
82 buf := []byte{:4096}
83 n := cGetenv(unsafe.Pointer(unsafe.SliceData(nb)), int32(len(nb)), unsafe.Pointer(unsafe.SliceData(buf)), int32(len(buf)))
84 if n <= 0 {
85 return ""
86 }
87 return string(buf[:n])
88 }
89
90 func getArgs() []string {
91 data, ok := readFile("/proc/self/cmdline")
92 if !ok {
93 return nil
94 }
95 var args []string
96 start := int32(0)
97 for i := int32(0); i < int32(len(data)); i++ {
98 if data[i] == 0 {
99 if i > start {
100 args = append(args, string(data[start:i]))
101 }
102 start = i + 1
103 }
104 }
105 if start < int32(len(data)) {
106 args = append(args, string(data[start:]))
107 }
108 return args
109 }
110
111 func listFiles(dir, ext string) []string {
112 db := []byte(dir)
113 buf := []byte{:65536}
114 n := cListdir(unsafe.Pointer(unsafe.SliceData(db)), int32(len(db)),
115 unsafe.Pointer(unsafe.SliceData(buf)), int32(len(buf)))
116 if n <= 0 {
117 return nil
118 }
119 var result []string
120 start := int32(0)
121 for i := int32(0); i < n; i++ {
122 if buf[i] == 0 {
123 name := string(buf[start:i])
124 if hasSuffix(name, ext) {
125 result = append(result, name)
126 }
127 start = i + 1
128 }
129 }
130 return result
131 }
132
133 func run(args []string) int32 {
134 argv := []unsafe.Pointer{:len(args) + 1}
135 cstrs := [][]byte{:len(args)}
136 for i, a := range args {
137 b := []byte{:len(a) + 1}
138 copy(b, a)
139 b[len(a)] = 0
140 cstrs[i] = b
141 argv[i] = unsafe.Pointer(unsafe.SliceData(b))
142 }
143 return cRun(unsafe.Pointer(unsafe.SliceData(argv)), int32(len(args)))
144 }
145
146 func splitLines(s string) []string {
147 var result []string
148 start := int32(0)
149 for i := int32(0); i < int32(len(s)); i++ {
150 if s[i] == '\n' {
151 if i > start {
152 result = append(result, s[start:i])
153 }
154 start = i + 1
155 }
156 }
157 if start < int32(len(s)) {
158 result = append(result, s[start:])
159 }
160 return result
161 }
162
163 func hasPrefix(s, prefix string) bool {
164 return len(s) >= len(prefix) && s[:len(prefix)] == prefix
165 }
166
167 func hasSuffix(s, suffix string) bool {
168 return len(s) >= len(suffix) && s[len(s)-len(suffix):] == suffix
169 }
170
171 func trimSpace(s string) string {
172 i := int32(0)
173 for i < int32(len(s)) && (s[i] == ' ' || s[i] == '\t' || s[i] == '\n' || s[i] == '\r') {
174 i++
175 }
176 j := int32(len(s))
177 for j > i && (s[j-1] == ' ' || s[j-1] == '\t' || s[j-1] == '\n' || s[j-1] == '\r') {
178 j--
179 }
180 return s[i:j]
181 }
182
183 func joinPath(a, b string) string {
184 if len(a) == 0 {
185 return b
186 }
187 if a[len(a)-1] == '/' {
188 return a | b
189 }
190 return a | "/" | b
191 }
192
193 func pathBase(path string) string {
194 for i := int32(len(path)) - 1; i >= 0; i-- {
195 if path[i] == '/' {
196 return path[i+1:]
197 }
198 }
199 return path
200 }
201
202 func pathDir(path string) string {
203 for i := int32(len(path)) - 1; i >= 0; i-- {
204 if path[i] == '/' {
205 if i == 0 {
206 return "/"
207 }
208 return path[:i]
209 }
210 }
211 return "."
212 }
213
214 func splitSlash(s string) []string {
215 var parts []string
216 start := int32(0)
217 for i := int32(0); i < int32(len(s)); i++ {
218 if s[i] == '/' {
219 parts = append(parts, s[start:i])
220 start = i + 1
221 }
222 }
223 parts = append(parts, s[start:])
224 return parts
225 }
226
227 func cleanPath(path string) string {
228 parts := splitSlash(path)
229 var out []string
230 for _, p := range parts {
231 if p == "." || p == "" {
232 continue
233 }
234 if p == ".." && len(out) > 0 && out[len(out)-1] != ".." {
235 out = out[:len(out)-1]
236 } else {
237 out = append(out, p)
238 }
239 }
240 if len(out) == 0 {
241 return "."
242 }
243 result := out[0]
244 for i := 1; i < len(out); i++ {
245 result = result | "/" | out[i]
246 }
247 if len(path) > 0 && path[0] == '/' {
248 result = "/" | result
249 }
250 return result
251 }
252
253 func getMoxiePath() string {
254 p := getenv("MOXIEPATH")
255 if p != "" {
256 return p
257 }
258 home := getenv("HOME")
259 if home == "" {
260 return "/tmp/moxie"
261 }
262 return joinPath(home, "moxie")
263 }
264
265 func mxcVersion() string { return "1.7.0" }
266
267 func moxieCacheDir() string {
268 return joinPath(joinPath(getMoxiePath(), "cache"), "mxc-" | mxcVersion())
269 }
270
271 var globalModPrefix string
272 var globalModDir string
273 var globalReplaces map[string]string
274
275 func parseReplaceLine(line string, baseDir string) (string, string, bool) {
276 arrow := int32(-1)
277 for k := int32(0); k < int32(len(line))-1; k++ {
278 if line[k] == '=' && line[k+1] == '>' {
279 arrow = k
280 break
281 }
282 }
283 if arrow < 0 {
284 return "", "", false
285 }
286 from := trimSpace(line[:arrow])
287 to := trimSpace(line[arrow+2:])
288 for fi := int32(0); fi < int32(len(from)); fi++ {
289 if from[fi] == ' ' {
290 from = from[:fi]
291 break
292 }
293 }
294 if hasPrefix(to, "./") || hasPrefix(to, "../") || hasPrefix(to, "/") {
295 to = cleanPath(joinPath(baseDir, to))
296 }
297 return from, to, true
298 }
299
300 func findModuleRoot(startDir string) (string, string) {
301 dir := startDir
302 for {
303 data, ok := readFile(joinPath(dir, "moxie.mod"))
304 if ok {
305 modName := ""
306 globalReplaces = map[string]string{}
307 lines := splitLines(string(data))
308 inReplace := false
309 for _, line := range lines {
310 line = trimSpace(line)
311 if hasPrefix(line, "module ") {
312 modName = trimSpace(line[7:])
313 continue
314 }
315 if line == "replace (" {
316 inReplace = true
317 continue
318 }
319 if inReplace {
320 if line == ")" {
321 inReplace = false
322 continue
323 }
324 from, to, ok2 := parseReplaceLine(line, dir)
325 if ok2 {
326 globalReplaces[from] = to
327 }
328 continue
329 }
330 if hasPrefix(line, "replace ") {
331 from, to, ok2 := parseReplaceLine(line[8:], dir)
332 if ok2 {
333 globalReplaces[from] = to
334 }
335 }
336 }
337 if modName != "" {
338 return modName, dir
339 }
340 }
341 parent := pathDir(dir)
342 if parent == dir || parent == "" || parent == "." {
343 break
344 }
345 dir = parent
346 }
347 return "", ""
348 }
349
350 func mergeModReplaces(dir string) {
351 data, ok := readFile(joinPath(dir, "moxie.mod"))
352 if !ok {
353 return
354 }
355 if globalReplaces == nil {
356 globalReplaces = map[string]string{}
357 }
358 inReplace := false
359 for _, line := range splitLines(string(data)) {
360 line = trimSpace(line)
361 if line == "replace (" {
362 inReplace = true
363 continue
364 }
365 if inReplace {
366 if line == ")" {
367 inReplace = false
368 continue
369 }
370 from, to, ok2 := parseReplaceLine(line, dir)
371 if ok2 {
372 if _, exists := globalReplaces[from]; !exists {
373 globalReplaces[from] = to
374 }
375 }
376 continue
377 }
378 if hasPrefix(line, "replace ") {
379 from, to, ok2 := parseReplaceLine(line[8:], dir)
380 if ok2 {
381 if _, exists := globalReplaces[from]; !exists {
382 globalReplaces[from] = to
383 }
384 }
385 }
386 }
387 }
388
389 type modRequire struct {
390 path string
391 version string
392 }
393
394 func parseModRequires(dir string) (string, []modRequire, map[string]string) {
395 data, ok := readFile(joinPath(dir, "moxie.mod"))
396 if !ok {
397 return "", nil, nil
398 }
399 modName := ""
400 var reqs []modRequire
401 repls := map[string]string{}
402 inRequire := false
403 inReplace := false
404 for _, line := range splitLines(string(data)) {
405 line = trimSpace(line)
406 if hasPrefix(line, "module ") {
407 modName = trimSpace(line[7:])
408 continue
409 }
410 if line == "require (" {
411 inRequire = true
412 continue
413 }
414 if inRequire {
415 if line == ")" {
416 inRequire = false
417 continue
418 }
419 parts := splitBySpace(line)
420 if len(parts) >= 2 {
421 reqs = append(reqs, modRequire{path: parts[0], version: parts[1]})
422 } else if len(parts) == 1 {
423 reqs = append(reqs, modRequire{path: parts[0], version: ""})
424 }
425 continue
426 }
427 if hasPrefix(line, "require ") {
428 parts := splitBySpace(line[8:])
429 if len(parts) >= 2 {
430 reqs = append(reqs, modRequire{path: parts[0], version: parts[1]})
431 } else if len(parts) == 1 {
432 reqs = append(reqs, modRequire{path: parts[0], version: ""})
433 }
434 continue
435 }
436 if line == "replace (" {
437 inReplace = true
438 continue
439 }
440 if inReplace {
441 if line == ")" {
442 inReplace = false
443 continue
444 }
445 from, to, ok2 := parseReplaceLine(line, dir)
446 if ok2 {
447 repls[from] = to
448 }
449 continue
450 }
451 if hasPrefix(line, "replace ") {
452 from, to, ok2 := parseReplaceLine(line[8:], dir)
453 if ok2 {
454 repls[from] = to
455 }
456 }
457 }
458 return modName, reqs, repls
459 }
460
461 func isRemotePath(path string) bool {
462 slash := int32(-1)
463 for i := int32(0); i < int32(len(path)); i++ {
464 if path[i] == '/' {
465 slash = i
466 break
467 }
468 }
469 if slash < 0 {
470 return false
471 }
472 host := path[:slash]
473 for i := int32(0); i < int32(len(host)); i++ {
474 if host[i] == '.' {
475 return true
476 }
477 }
478 return false
479 }
480
481 func repoLockPath(mpath, repoPath string) string {
482 return joinPath(mpath, repoPath | ".lock")
483 }
484
485 func acquireRepoLock(mpath, repoPath string) bool {
486 lp := repoLockPath(mpath, repoPath)
487 _, locked := readFile(lp)
488 if locked {
489 return false
490 }
491 mkdirAll(pathDir(lp), 0755)
492 writeFile(lp, []byte("1"), 0644)
493 return true
494 }
495
496 func releaseRepoLock(mpath, repoPath string) {
497 os.Remove(repoLockPath(mpath, repoPath))
498 }
499
500 func autoFetchRepo(pkgPath, version string) string {
501 mpath := getMoxiePath()
502 repoPath := pkgPath
503 for {
504 dest := joinPath(mpath, repoPath)
505 gitHead := joinPath(dest, ".git/HEAD")
506 _, exists := readFile(gitHead)
507 if exists {
508 break
509 }
510 parent := pathDir(repoPath)
511 if parent == repoPath || parent == "." || parent == "" {
512 break
513 }
514 parentDest := joinPath(mpath, parent)
515 parentGit := joinPath(parentDest, ".git/HEAD")
516 _, parentExists := readFile(parentGit)
517 if parentExists {
518 repoPath = parent
519 break
520 }
521 repoPath = parent
522 }
523
524 if !acquireRepoLock(mpath, repoPath) {
525 writeStr(2, " waiting for lock: " | repoPath | "\n")
526 fatal("repo locked: " | repoPath)
527 }
528
529 dest := joinPath(mpath, repoPath)
530 gitHead := joinPath(dest, ".git/HEAD")
531 _, exists := readFile(gitHead)
532
533 if !exists {
534 mkdirAll(pathDir(dest), 0755)
535 url := "https://" | repoPath
536 writeStr(2, " auto-fetch " | repoPath | "\n")
537 if run([]string{"git", "clone", url, dest}) != 0 {
538 releaseRepoLock(mpath, repoPath)
539 return ""
540 }
541 }
542
543 if version != "" && version != "v0.0.0" {
544 if run([]string{"git", "-C", dest, "checkout", version}) != 0 {
545 run([]string{"git", "-C", dest, "fetch", "--tags"})
546 run([]string{"git", "-C", dest, "pull"})
547 run([]string{"git", "-C", dest, "checkout", version})
548 }
549 }
550
551 releaseRepoLock(mpath, repoPath)
552 return joinPath(mpath, pkgPath)
553 }
554
555 func cmdFetch() {
556 mpath := getMoxiePath()
557 mkdirAll(mpath, 0755)
558
559 modName, reqs, repls := parseModRequires(".")
560 if modName == "" {
561 fatal("no moxie.mod in current directory")
562 }
563 writeStr(2, "module: " | modName | "\n")
564
565 var queue []modRequire
566 for _, r := range reqs {
567 if _, ok := repls[r.path]; ok {
568 writeStr(2, " skip (replaced): " | r.path | "\n")
569 continue
570 }
571 queue = append(queue, r)
572 }
573
574 seen := map[string]bool{}
575 for len(queue) > 0 {
576 req := queue[0]
577 queue = queue[1:]
578 if seen[req.path] {
579 continue
580 }
581 seen[req.path] = true
582
583 if !acquireRepoLock(mpath, req.path) {
584 writeStr(2, " locked: " | req.path | " (another build in progress)\n")
585 fatal("repo locked: " | req.path)
586 }
587
588 dest := joinPath(mpath, req.path)
589 gitHead := joinPath(dest, ".git/HEAD")
590 _, exists := readFile(gitHead)
591
592 if !exists {
593 mkdirAll(pathDir(dest), 0755)
594 url := "https://" | req.path
595 writeStr(2, " clone " | req.path | "\n")
596 if run([]string{"git", "clone", url, dest}) != 0 {
597 releaseRepoLock(mpath, req.path)
598 writeStr(2, " FAIL: clone " | req.path | "\n")
599 continue
600 }
601 }
602
603 if req.version != "" && req.version != "v0.0.0" {
604 if run([]string{"git", "-C", dest, "checkout", req.version}) != 0 {
605 writeStr(2, " tag " | req.version | " not found, pulling\n")
606 run([]string{"git", "-C", dest, "pull"})
607 if run([]string{"git", "-C", dest, "checkout", req.version}) != 0 {
608 writeStr(2, " WARN: " | req.version | " unavailable for " | req.path | "\n")
609 }
610 } else {
611 writeStr(2, " " | req.path | " @ " | req.version | "\n")
612 }
613 } else if exists {
614 writeStr(2, " pull " | req.path | "\n")
615 run([]string{"git", "-C", dest, "pull"})
616 } else {
617 writeStr(2, " fetched " | req.path | "\n")
618 }
619
620 releaseRepoLock(mpath, req.path)
621
622 _, subReqs, _ := parseModRequires(dest)
623 for _, sr := range subReqs {
624 if !seen[sr.path] {
625 queue = append(queue, sr)
626 }
627 }
628 }
629
630 writeStr(2, "fetch done\n")
631 }
632
633 func joinStrings(ss []string, sep string) string {
634 if len(ss) == 0 {
635 return ""
636 }
637 n := int32(0)
638 for _, s := range ss {
639 n += int32(len(s))
640 }
641 n += int32(len(sep)) * (int32(len(ss)) - 1)
642 buf := []byte{:0:n}
643 for i, s := range ss {
644 if i > 0 {
645 buf = append(buf, sep...)
646 }
647 buf = append(buf, s...)
648 }
649 return string(buf)
650 }
651
652 func appendUniq(ss []string, s string) []string {
653 for _, x := range ss {
654 if x == s {
655 return ss
656 }
657 }
658 return append(ss, s)
659 }
660
661 func fnvHash(data []byte) string {
662 h := uint32(2166136261)
663 for i := int32(0); i < int32(len(data)); i++ {
664 h ^= uint32(data[i])
665 h *= uint32(16777619)
666 }
667 hex := "0123456789abcdef"
668 buf := []byte{:8}
669 for i := int32(7); i >= 0; i-- {
670 buf[i] = hex[h&0xf]
671 h >>= 4
672 }
673 return string(buf)
674 }
675
676 func pkgCacheDir() string {
677 return joinPath(moxieCacheDir(), "pkg")
678 }
679
680 func pkgCacheKey(dir string, files []string) string {
681 var sorted []string
682 for _, f := range files {
683 sorted = append(sorted, f)
684 }
685 for i := int32(1); i < int32(len(sorted)); i++ {
686 for j := i; j > 0 && sorted[j] < sorted[j-1]; j-- {
687 sorted[j], sorted[j-1] = sorted[j-1], sorted[j]
688 }
689 }
690 var all []byte
691 for _, f := range sorted {
692 p := joinPath(dir, f)
693 data, ok := readFile(p)
694 if !ok {
695 continue
696 }
697 all = append(all, data...)
698 all = append(all, 0)
699 }
700 return fnvHash(all)
701 }
702
703 func versionedCacheName(hash, version, ext string) string {
704 if version != "" {
705 return hash | "_" | version | ext
706 }
707 return hash | ext
708 }
709
710 func cachedBcPath(cacheBase, pkgPath, hash, version string) string {
711 return joinPath(joinPath(cacheBase, safeName(pkgPath)), versionedCacheName(hash, version, ".bc"))
712 }
713
714 func isStdlib(path string) bool {
715 return !hasPrefix(path, "/") && !hasPrefix(path, "./") && !hasPrefix(path, "../")
716 }
717
718 var builtinOnly []string
719
720 func isBuiltinOnly(path string) bool {
721 for _, b := range builtinOnly {
722 if path == b {
723 return true
724 }
725 }
726 return false
727 }
728
729 func loadSysroot(path string) {
730 data, ok := readFile(path)
731 if !ok {
732 return
733 }
734 for _, line := range splitLines(string(data)) {
735 line = trimSpace(line)
736 if len(line) == 0 || line[0] == '#' {
737 continue
738 }
739 eq := int32(-1)
740 for i := int32(0); i < int32(len(line)); i++ {
741 if line[i] == '=' {
742 eq = i
743 break
744 }
745 }
746 if eq < 0 {
747 continue
748 }
749 key := line[:eq]
750 val := line[eq+1:]
751 os.Setenv(key, val)
752 }
753 }
754
755 func splitBySpace(s string) []string {
756 var result []string
757 start := int32(0)
758 inWord := false
759 for i := int32(0); i < int32(len(s)); i++ {
760 if s[i] == ' ' || s[i] == '\t' {
761 if inWord {
762 result = append(result, s[start:i])
763 inWord = false
764 }
765 } else {
766 if !inWord {
767 start = i
768 inWord = true
769 }
770 }
771 }
772 if inWord {
773 result = append(result, s[start:])
774 }
775 return result
776 }
777
778 func safeName(pkg string) string {
779 b := []byte{:len(pkg)}
780 for i := int32(0); i < int32(len(pkg)); i++ {
781 if pkg[i] == '/' {
782 b[i] = '_'
783 } else {
784 b[i] = pkg[i]
785 }
786 }
787 return string(b)
788 }
789
790 func extractPkgName(data []byte) string {
791 for i := int32(0); i < int32(len(data)); i++ {
792 if i+8 < int32(len(data)) && string(data[i:i+8]) == "package " {
793 j := i + 8
794 for j < int32(len(data)) && data[j] != '\n' && data[j] != '\r' && data[j] != ' ' {
795 j++
796 }
797 return string(data[i+8 : j])
798 }
799 if data[i] == '\n' {
800 continue
801 }
802 if data[i] == '/' && i+1 < int32(len(data)) && data[i+1] == '/' {
803 for i < int32(len(data)) && data[i] != '\n' {
804 i++
805 }
806 continue
807 }
808 break
809 }
810 return "main"
811 }
812
813 func extractImports(data []byte) []string {
814 var imports []string
815 lines := splitLines(string(data))
816 inBlock := false
817 for _, line := range lines {
818 line = trimSpace(line)
819 if line == "import (" {
820 inBlock = true
821 continue
822 }
823 if inBlock && line == ")" {
824 inBlock = false
825 continue
826 }
827 if inBlock {
828 imp := extractQuoted(line)
829 if imp != "" {
830 imports = appendUniq(imports, imp)
831 }
832 }
833 if hasPrefix(line, "import \"") {
834 imp := extractQuoted(line[7:])
835 if imp != "" {
836 imports = appendUniq(imports, imp)
837 }
838 }
839 }
840 return imports
841 }
842
843 func extractQuoted(s string) string {
844 start := int32(-1)
845 for i := int32(0); i < int32(len(s)); i++ {
846 if s[i] == '"' {
847 if start < 0 {
848 start = i + 1
849 } else {
850 return s[start:i]
851 }
852 }
853 }
854 return ""
855 }
856
857 func concatSources(dir string, files []string) []byte {
858 imports := map[string]bool{}
859 var bodies [][]byte
860 pkgName := ""
861 for _, f := range files {
862 data, ok := readFile(joinPath(dir, f))
863 if !ok {
864 continue
865 }
866 if pkgName == "" {
867 pkgName = extractPkgName(data)
868 }
869 lines := splitLines(string(data))
870 var body []string
871 i := int32(0)
872 for i < int32(len(lines)) {
873 line := trimSpace(lines[i])
874 if hasPrefix(line, "package ") {
875 i++
876 continue
877 }
878 if line == "import (" {
879 i++
880 for i < int32(len(lines)) {
881 imp := trimSpace(lines[i])
882 i++
883 if imp == ")" {
884 break
885 }
886 if imp != "" {
887 imports[imp] = true
888 }
889 }
890 continue
891 }
892 if hasPrefix(line, "import ") && !hasPrefix(line, "import (") {
893 imp := line[7:]
894 imports[imp] = true
895 i++
896 continue
897 }
898 body = append(body, lines[i])
899 i++
900 }
901 bodies = append(bodies, []byte(joinStrings(body, "\n")))
902 }
903 var out []byte
904 out = append(out, ("package " | pkgName | "\n")...)
905 if len(imports) > 0 {
906 var impKeys []string
907 for imp := range imports {
908 impKeys = append(impKeys, imp)
909 }
910 for i := int32(1); i < int32(len(impKeys)); i++ {
911 for j := i; j > 0 && impKeys[j] < impKeys[j-1]; j-- {
912 impKeys[j], impKeys[j-1] = impKeys[j-1], impKeys[j]
913 }
914 }
915 out = append(out, "import (\n"...)
916 for _, imp := range impKeys {
917 out = append(out, '\t')
918 out = append(out, imp...)
919 out = append(out, '\n')
920 }
921 out = append(out, ")\n"...)
922 }
923 for _, b := range bodies {
924 out = append(out, b...)
925 out = append(out, '\n')
926 }
927 return out
928 }
929
930 func fileContainsPart(base string, part string) bool {
931 plen := len(part)
932 for i := 0; i <= len(base)-plen; i++ {
933 if base[i:i+plen] == part {
934 before := i == 0 || base[i-1] == '_'
935 after := i+plen == len(base) || base[i+plen] == '_'
936 if before && after {
937 return true
938 }
939 }
940 }
941 return false
942 }
943
944 func shouldSkipFile(name string) bool {
945 if hasSuffix(name, "_test.go") || hasSuffix(name, "_test.mx") {
946 return true
947 }
948 base := name
949 if hasSuffix(base, ".go") {
950 base = base[:len(base)-3]
951 } else if hasSuffix(base, ".mx") {
952 base = base[:len(base)-3]
953 }
954 archSkip := []string{"arm64"}
955 for _, a := range archSkip {
956 if fileContainsPart(base, a) {
957 return true
958 }
959 }
960 suffixSkip := []string{"_wasm"}
961 for _, s := range suffixSkip {
962 if hasSuffix(base, s) {
963 return true
964 }
965 }
966 exact := []string{"pipe", "multi", "scan", "reader", "replace", "search",
967 "ioutil"}
968 for _, s := range exact {
969 if base == s {
970 return true
971 }
972 }
973 return false
974 }
975
976 func hasBuildConstraint(data []byte, forbidden []string) bool {
977 for i := int32(0); i < int32(len(data)) && i < 512; i++ {
978 if data[i] == 'p' {
979 return false
980 }
981 if i+11 < int32(len(data)) && string(data[i:i+11]) == "//go:build " {
982 var buf []byte
983 for j := i + 11; j < int32(len(data)) && data[j] != '\n'; j++ {
984 buf = append(buf, data[j])
985 }
986 line := string(buf)
987 for _, f := range forbidden {
988 if line == f || hasPrefix(line, f | " ") || hasPrefix(line, f | "\t") {
989 return true
990 }
991 }
992 return false
993 }
994 if data[i] == '\n' {
995 continue
996 }
997 if data[i] == '/' && i+1 < int32(len(data)) && data[i+1] == '/' {
998 for i < int32(len(data)) && data[i] != '\n' {
999 i++
1000 }
1001 continue
1002 }
1003 }
1004 return false
1005 }
1006
1007 func hasImport(data []byte, pkg string) bool {
1008 target := "\"" | pkg | "\""
1009 for i := int32(0); i <= int32(len(data))-int32(len(target)); i++ {
1010 if string(data[i:i+int32(len(target))]) == target {
1011 return true
1012 }
1013 }
1014 return false
1015 }
1016
1017 type pkgInfo struct {
1018 path string
1019 name string
1020 dir string
1021 files []string
1022 cfiles []string
1023 bcfiles []string
1024 imports []string
1025 version string
1026 }
1027
1028 func discoverPkg(pkgPath, root string) *pkgInfo {
1029 var dir string
1030 if pkgPath == "." || pkgPath == ".." || hasPrefix(pkgPath, "/") || hasPrefix(pkgPath, "./") || hasPrefix(pkgPath, "../") {
1031 dir = pkgPath
1032 } else if globalReplaces != nil {
1033 if rdir, ok := globalReplaces[pkgPath]; ok {
1034 dir = rdir
1035 }
1036 if dir == "" {
1037 for from, to := range globalReplaces {
1038 if hasPrefix(pkgPath, from|"/") {
1039 rel := pkgPath[len(from)+1:]
1040 dir = joinPath(to, rel)
1041 break
1042 }
1043 }
1044 }
1045 }
1046 if dir == "" && globalModPrefix != "" && pkgPath == globalModPrefix {
1047 dir = globalModDir
1048 }
1049 if dir == "" && globalModPrefix != "" && hasPrefix(pkgPath, globalModPrefix|"/") {
1050 rel := pkgPath[len(globalModPrefix)+1:]
1051 dir = joinPath(globalModDir, rel)
1052 }
1053 if dir == "" {
1054 candidate := joinPath(getMoxiePath(), pkgPath)
1055 mxF := listFiles(candidate, ".mx")
1056 if len(mxF) > 0 {
1057 dir = candidate
1058 }
1059 }
1060 if dir == "" && isRemotePath(pkgPath) {
1061 version := ""
1062 if globalModDir != "" {
1063 _, reqs, _ := parseModRequires(globalModDir)
1064 for _, r := range reqs {
1065 if r.path == pkgPath {
1066 version = r.version
1067 break
1068 }
1069 }
1070 }
1071 fetched := autoFetchRepo(pkgPath, version)
1072 if fetched != "" {
1073 mxF := listFiles(fetched, ".mx")
1074 if len(mxF) > 0 {
1075 dir = fetched
1076 }
1077 }
1078 }
1079 if dir != "" && dir != pkgPath {
1080 mergeModReplaces(dir)
1081 }
1082 if dir == "" {
1083 dir = joinPath(root, joinPath("src", pkgPath))
1084 mxFiles := listFiles(dir, ".mx")
1085 if len(mxFiles) == 0 {
1086 dir = joinPath(root, joinPath("src/vendor", pkgPath))
1087 }
1088 }
1089 goFiles := listFiles(dir, ".go")
1090 mxFiles := listFiles(dir, ".mx")
1091 cFiles := listFiles(dir, ".c")
1092 bcFiles := listFiles(dir, ".bc")
1093 allMx := append(mxFiles, goFiles...)
1094 forbidden := []string{"wasm", "js", "ignore", "compiler_bootstrap",
1095 "scheduler.tasks", "scheduler.cores", "scheduler.asyncify",
1096 "faketime", "baremetal", "nintendoswitch"}
1097 var files []string
1098 for _, f := range allMx {
1099 if shouldSkipFile(f) {
1100 continue
1101 }
1102 data, ok := readFile(joinPath(dir, f))
1103 if !ok {
1104 continue
1105 }
1106 bc := hasBuildConstraint(data, forbidden)
1107 if bc {
1108 writeStr(2, " skip-constraint: " | f | "\n")
1109 continue
1110 }
1111 fileImps := extractImports(data)
1112 skipIter := false
1113 for _, imp := range fileImps {
1114 if imp == "iter" {
1115 skipIter = true
1116 break
1117 }
1118 }
1119 if skipIter {
1120 writeStr(2, " skip-iter-import: " | f | "\n")
1121 continue
1122 }
1123 writeStr(2, " include: " | f | "\n")
1124 files = append(files, f)
1125 }
1126 if len(files) == 0 {
1127 return nil
1128 }
1129 var allImports []string
1130 pkgName := ""
1131 for _, f := range files {
1132 data, ok := readFile(joinPath(dir, f))
1133 if !ok {
1134 continue
1135 }
1136 if pkgName == "" {
1137 pkgName = extractPkgName(data)
1138 }
1139 for _, imp := range extractImports(data) {
1140 allImports = appendUniq(allImports, imp)
1141 }
1142 }
1143 writeStr(2, " discover " | pkgPath | " [")
1144 for fi, ff := range files {
1145 if fi > 0 {
1146 writeStr(2, ", ")
1147 }
1148 writeStr(2, ff)
1149 }
1150 writeStr(2, "]\n")
1151 version := ""
1152 if isRemotePath(pkgPath) && globalModDir != "" {
1153 _, reqs, _ := parseModRequires(globalModDir)
1154 for _, r := range reqs {
1155 if r.path == pkgPath || hasPrefix(pkgPath, r.path|"/") {
1156 version = r.version
1157 break
1158 }
1159 }
1160 }
1161 return &pkgInfo{
1162 path: pkgPath,
1163 name: pkgName,
1164 dir: dir,
1165 files: files,
1166 cfiles: cFiles,
1167 bcfiles: bcFiles,
1168 imports: allImports,
1169 version: version,
1170 }
1171 }
1172
1173 type resolver struct {
1174 visited map[string]bool
1175 order []*pkgInfo
1176 root string
1177 }
1178
1179 func (r *resolver) walk(path string) {
1180 if r.visited[path] {
1181 return
1182 }
1183 r.visited[path] = true
1184 if isBuiltinOnly(path) {
1185 return
1186 }
1187 pkg := discoverPkg(path, r.root)
1188 if pkg == nil {
1189 return
1190 }
1191 for _, imp := range pkg.imports {
1192 r.walk(imp)
1193 }
1194 r.order = append(r.order, pkg)
1195 }
1196
1197 func resolveAll(rootPkg string, root string) []*pkgInfo {
1198 r := &resolver{
1199 visited: map[string]bool{},
1200 root: root,
1201 }
1202 r.walk(rootPkg)
1203 return r.order
1204 }
1205
1206 func registerBuiltins() {
1207 initUniverse()
1208 ensureImportRegistry()
1209 importRegistry = map[string]*TCPackage{}
1210
1211 // unsafe - compiler builtin, no source
1212 unsafePkg := NewTCPackage("unsafe", "unsafe")
1213 unsafePkg.Scope().Insert(NewTypeName(unsafePkg, "Pointer", Typ[UnsafePointer]))
1214 unsafePkg.Scope().Insert(NewTCFunc(unsafePkg, "Sizeof", parseSignatureDesc("interface{}->int32")))
1215 unsafePkg.Scope().Insert(NewTCFunc(unsafePkg, "Offsetof", parseSignatureDesc("interface{}->int32")))
1216 unsafePkg.Scope().Insert(NewTCFunc(unsafePkg, "Alignof", parseSignatureDesc("interface{}->int32")))
1217 unsafePkg.Scope().Insert(NewTCFunc(unsafePkg, "Slice", parseSignatureDesc("ptr,int32->[]uint8")))
1218 unsafePkg.Scope().Insert(NewTCFunc(unsafePkg, "SliceData", parseSignatureDesc("[]uint8->ptr")))
1219 importRegistry["unsafe"] = unsafePkg
1220
1221 // moxie - spawn/codec runtime, special handling
1222 importRegistry["moxie"] = NewTCPackage("moxie", "moxie")
1223
1224 // runtime - assembly + C, pre-compiled in runtime.bc
1225 rtPkg := NewTCPackage("runtime", "runtime")
1226 rtPkg.Scope().Insert(NewTCFunc(rtPkg, "InitCShared", parseSignatureDesc("")))
1227 importRegistry["runtime"] = rtPkg
1228 }
1229
1230 func addPtrMethod(pkg *TCPackage, named *Named, name, sigDesc string) {
1231 var sig *Signature
1232 if sigDesc == "" {
1233 sig = NewSignature(nil, nil, nil, false)
1234 } else {
1235 sig = parseSignatureDesc(sigDesc)
1236 }
1237 fn := NewTCFunc(pkg, name, sig)
1238 fn.hasPtrRecv = true
1239 named.AddMethod(fn)
1240 }
1241
1242 func compileSource(src []byte, pkgName, triple string) string {
1243 ir := CompileToIR(src, pkgName, triple)
1244 if len(ir) > 0 && ir[0] == ';' {
1245 writeStr(2, ir)
1246 fatal("compile error for " | pkgName)
1247 }
1248 if len(ir) == 0 {
1249 fatal("empty IR for " | pkgName)
1250 }
1251 return ir
1252 }
1253
1254 func typeDescStr(t Type) string {
1255 if t == nil {
1256 return "interface{}"
1257 }
1258 switch v := t.(type) {
1259 case *Basic:
1260 switch v.kind {
1261 case Bool:
1262 return "bool"
1263 case Int8:
1264 return "int8"
1265 case Int16:
1266 return "int16"
1267 case Int32:
1268 return "int32"
1269 case Int64:
1270 return "int64"
1271 case Uint8:
1272 return "uint8"
1273 case Uint16:
1274 return "uint16"
1275 case Uint32:
1276 return "uint32"
1277 case Uint64:
1278 return "uint64"
1279 case Float32:
1280 return "float32"
1281 case Float64:
1282 return "float64"
1283 case TCString:
1284 return "string"
1285 case UnsafePointer:
1286 return "ptr"
1287 }
1288 return "int32"
1289 case *Slice:
1290 return "[]" | typeDescStr(v.Elem())
1291 case *Pointer:
1292 return "*" | typeDescStr(v.Elem())
1293 case *TCInterface:
1294 if v.IsEmpty() {
1295 return "interface{}"
1296 }
1297 return "interface{}"
1298 case *Named:
1299 if v.obj != nil {
1300 return v.obj.name
1301 }
1302 return typeDescStr(v.underlying)
1303 }
1304 return "interface{}"
1305 }
1306
1307 func sigDescStr(sig *Signature) string {
1308 var out string
1309 if sig.params != nil {
1310 for i := int32(0); i < sig.params.Len(); i++ {
1311 if i > 0 {
1312 out = out | ","
1313 }
1314 out = out | typeDescStr(sig.params.At(i).Type())
1315 }
1316 }
1317 if sig.results != nil && sig.results.Len() > 0 {
1318 out = out | "->"
1319 for i := int32(0); i < sig.results.Len(); i++ {
1320 if i > 0 {
1321 out = out | ","
1322 }
1323 out = out | typeDescStr(sig.results.At(i).Type())
1324 }
1325 }
1326 return out
1327 }
1328
1329 func generateMxh(pkg *TCPackage) string {
1330 path := pkg.Path()
1331 name := pkg.Name()
1332 var out string
1333 out = "package " | path | " " | name | "\n"
1334 for _, objName := range pkg.Scope().Names() {
1335 if len(objName) == 0 || objName[0] < 'A' || objName[0] > 'Z' {
1336 continue
1337 }
1338 obj := pkg.Scope().Lookup(objName)
1339 if obj == nil {
1340 continue
1341 }
1342 switch v := obj.(type) {
1343 case *TCFunc:
1344 sig := v.Signature()
1345 if sig != nil {
1346 out = out | "func " | objName | " " | sigDescStr(sig) | "\n"
1347 }
1348 case *TCVar:
1349 out = out | "var " | objName | " " | typeDescStr(v.Type()) | "\n"
1350 case *TCConst:
1351 td := typeDescStr(v.Type())
1352 vs := ""
1353 if v.val != nil {
1354 vs = v.val.String()
1355 }
1356 out = out | "const " | objName | " " | td | " " | vs | "\n"
1357 case *TypeName:
1358 typ := v.Type()
1359 if named, ok := typ.(*Named); ok {
1360 ud := ""
1361 if named.underlying != nil {
1362 ud = typeDescStr(named.underlying)
1363 }
1364 out = out | "type " | objName | " " | ud | "\n"
1365 for _, m := range named.methods {
1366 ptrRecv := "0"
1367 if m.hasPtrRecv {
1368 ptrRecv = "1"
1369 }
1370 msig := m.Signature()
1371 if msig != nil {
1372 out = out | "method " | objName | " " | m.Name() | " " | sigDescStr(msig) | " " | ptrRecv | "\n"
1373 }
1374 }
1375 } else if iface, ok := typ.(*TCInterface); ok {
1376 var methods string
1377 for mi := int32(0); mi < iface.NumMethods(); mi++ {
1378 m := iface.Method(mi)
1379 if mi > 0 {
1380 methods = methods | ";"
1381 }
1382 methods = methods | m.name | "=" | sigDescStr(m.sig)
1383 }
1384 out = out | "iface " | objName | " " | methods | "\n"
1385 }
1386 }
1387 }
1388 return out
1389 }
1390
1391 func loadMxh(path string) bool {
1392 data, ok := readFile(path)
1393 if !ok {
1394 return false
1395 }
1396 ensureImportRegistry()
1397 lines := splitLines(string(data))
1398 if len(lines) == 0 {
1399 return false
1400 }
1401 first := lines[0]
1402 if !hasPrefix(first, "package ") {
1403 return false
1404 }
1405 rest := first[8:]
1406 spaceIdx := int32(-1)
1407 for i := int32(0); i < int32(len(rest)); i++ {
1408 if rest[i] == ' ' {
1409 spaceIdx = i
1410 break
1411 }
1412 }
1413 if spaceIdx < 0 {
1414 return false
1415 }
1416 pkgPath := rest[:spaceIdx]
1417 pkgName := rest[spaceIdx+1:]
1418 pkg := NewTCPackage(pkgPath, pkgName)
1419
1420 for li := 1; li < len(lines); li++ {
1421 line := lines[li]
1422 if hasPrefix(line, "func ") {
1423 parts := splitBySpace(line[5:])
1424 if len(parts) >= 2 {
1425 sig := parseSignatureDesc(parts[1])
1426 pkg.Scope().Insert(NewTCFunc(pkg, parts[0], sig))
1427 } else if len(parts) == 1 {
1428 sig := parseSignatureDesc("")
1429 pkg.Scope().Insert(NewTCFunc(pkg, parts[0], sig))
1430 }
1431 } else if hasPrefix(line, "var ") {
1432 parts := splitBySpace(line[4:])
1433 if len(parts) >= 2 {
1434 typ := parseTypeDesc(parts[1])
1435 pkg.Scope().Insert(NewTCVar(pkg, parts[0], typ))
1436 }
1437 } else if hasPrefix(line, "const ") {
1438 parts := splitBySpace(line[6:])
1439 if len(parts) >= 2 {
1440 typ := parseTypeDesc(parts[1])
1441 var val ConstVal
1442 if len(parts) >= 3 {
1443 val = constStr{parts[2]}
1444 } else {
1445 val = constInt{0}
1446 }
1447 pkg.Scope().Insert(NewTCConst(pkg, parts[0], typ, val))
1448 }
1449 } else if hasPrefix(line, "type ") {
1450 parts := splitBySpace(line[5:])
1451 if len(parts) >= 2 {
1452 underlying := parseTypeDesc(parts[1])
1453 tn := NewTypeName(pkg, parts[0], nil)
1454 NewNamed(tn, underlying)
1455 pkg.Scope().Insert(tn)
1456 } else if len(parts) == 1 {
1457 underlying := NewTCStruct(nil, nil)
1458 tn := NewTypeName(pkg, parts[0], nil)
1459 NewNamed(tn, underlying)
1460 pkg.Scope().Insert(tn)
1461 }
1462 } else if hasPrefix(line, "method ") {
1463 parts := splitBySpace(line[7:])
1464 if len(parts) >= 3 {
1465 typeName := parts[0]
1466 methodName := parts[1]
1467 sigDesc := parts[2]
1468 ptrRecv := false
1469 if len(parts) >= 4 && parts[3] == "1" {
1470 ptrRecv = true
1471 }
1472 obj := pkg.Scope().Lookup(typeName)
1473 if obj != nil {
1474 if tn, ok := obj.(*TypeName); ok {
1475 if named, ok := tn.typ.(*Named); ok {
1476 sig := parseSignatureDesc(sigDesc)
1477 fn := NewTCFunc(pkg, methodName, sig)
1478 fn.hasPtrRecv = ptrRecv
1479 named.AddMethod(fn)
1480 }
1481 }
1482 }
1483 }
1484 } else if hasPrefix(line, "iface ") {
1485 parts := splitBySpace(line[6:])
1486 if len(parts) >= 1 {
1487 ifaceName := parts[0]
1488 methodsDesc := ""
1489 if len(parts) >= 2 {
1490 methodsDesc = parts[1]
1491 }
1492 var methods []*IfaceMethod
1493 if methodsDesc != "" {
1494 mparts := splitSemicolon(methodsDesc)
1495 for _, p := range mparts {
1496 eqIdx := -1
1497 for i := 0; i < len(p); i++ {
1498 if p[i] == '=' {
1499 eqIdx = i
1500 break
1501 }
1502 }
1503 if eqIdx < 0 {
1504 continue
1505 }
1506 mname := p[:eqIdx]
1507 msig := parseSignatureDesc(p[eqIdx+1:])
1508 methods = append(methods, NewTCIfaceMethod(mname, msig))
1509 }
1510 }
1511 iface := NewTCInterface(methods, nil)
1512 iface.Complete()
1513 tn := NewTypeName(pkg, ifaceName, iface)
1514 pkg.Scope().Insert(tn)
1515 }
1516 }
1517 }
1518
1519 importRegistry[pkgPath] = pkg
1520 return true
1521 }
1522
1523 func cachedMxhPath(cacheBase, pkgPath, hash, version string) string {
1524 return joinPath(joinPath(cacheBase, safeName(pkgPath)), versionedCacheName(hash, version, ".mxh"))
1525 }
1526
1527 func removeAll(path string) {
1528 entries := listFiles(path, "")
1529 if entries == nil {
1530 return
1531 }
1532 for _, e := range entries {
1533 os.Remove(joinPath(path, e))
1534 }
1535 os.Remove(path)
1536 }
1537
1538 func cacheClean() {
1539 base := pkgCacheDir()
1540 writeStr(2, "cleaning cache: " | base | "\n")
1541 entries := listFiles(base, "")
1542 if entries == nil {
1543 writeStr(2, " (empty)\n")
1544 return
1545 }
1546 for _, e := range entries {
1547 removeAll(joinPath(base, e))
1548 }
1549 os.Remove(base)
1550 writeStr(2, " done\n")
1551 }
1552
1553 func cmdUpdate(rebuildAll bool) {
1554 cacheRoot := joinPath(getMoxiePath(), "cache")
1555 currentDir := "mxc-" | mxcVersion()
1556 entries := listFiles(cacheRoot, "")
1557 if entries == nil {
1558 writeStr(2, "cache is empty\n")
1559 return
1560 }
1561 for _, e := range entries {
1562 if hasPrefix(e, "mxc-") && e != currentDir {
1563 writeStr(2, " remove old: " | e | "\n")
1564 fullPath := joinPath(cacheRoot, e)
1565 pkgEntries := listFiles(fullPath, "")
1566 if pkgEntries != nil {
1567 for _, pe := range pkgEntries {
1568 removeAll(joinPath(fullPath, pe))
1569 }
1570 }
1571 os.Remove(fullPath)
1572 }
1573 }
1574 if !rebuildAll {
1575 writeStr(2, "update done\n")
1576 return
1577 }
1578 writeStr(2, "rebuilding all cached packages...\n")
1579 mpath := getMoxiePath()
1580 root := getenv("MOXIEROOT")
1581 if root == "" {
1582 root = "."
1583 }
1584 cacheBase := pkgCacheDir()
1585 pkgDirs := listFiles(cacheBase, "")
1586 if pkgDirs == nil {
1587 writeStr(2, " no packages to rebuild\n")
1588 return
1589 }
1590 triple := "x86_64-unknown-linux-musleabihf"
1591 clang := "clang-21"
1592 tmpdir := "/tmp/mxc-build"
1593 mkdirAll(tmpdir, 0755)
1594 registerBuiltins()
1595 for _, pkgDir := range pkgDirs {
1596 pkgPath := ""
1597 for i := 0; i < len(pkgDir); i++ {
1598 if pkgDir[i] == '_' {
1599 pkgPath = pkgPath | "/"
1600 } else {
1601 pkgPath = pkgPath | string([]byte{pkgDir[i]})
1602 }
1603 }
1604 writeStr(2, " rebuild " | pkgPath | "\n")
1605 pkg := discoverPkg(pkgPath, root)
1606 if pkg == nil {
1607 candidate := joinPath(mpath, pkgPath)
1608 mxF := listFiles(candidate, ".mx")
1609 if len(mxF) > 0 {
1610 pkg = discoverPkg(pkgPath, root)
1611 }
1612 }
1613 if pkg == nil {
1614 writeStr(2, " skip (source not found)\n")
1615 continue
1616 }
1617 compilePath := pkg.path
1618 if hasPrefix(compilePath, "/") || hasPrefix(compilePath, "./") || hasPrefix(compilePath, "../") {
1619 compilePath = pkg.name
1620 }
1621 src := concatSources(pkg.dir, pkg.files)
1622 ir := compileSource(src, compilePath, triple)
1623 src = nil
1624 llFile := joinPath(tmpdir, safeName(pkg.path) | ".ll")
1625 writeFile(llFile, []byte(ir), 0644)
1626 ir = ""
1627 bcFile := joinPath(tmpdir, safeName(pkg.path) | ".bc")
1628 rc := run([]string{clang, "--target=" | triple, "-emit-llvm", "-c", llFile, "-o", bcFile})
1629 if rc != 0 {
1630 writeStr(2, " FAIL: clang for " | pkg.path | "\n")
1631 continue
1632 }
1633 hash := pkgCacheKey(pkg.dir, pkg.files)
1634 cached := cachedBcPath(cacheBase, pkg.path, hash, pkg.version)
1635 cdir := joinPath(cacheBase, safeName(pkg.path))
1636 mkdirAll(cdir, 0755)
1637 data, rok := readFile(bcFile)
1638 if rok {
1639 writeFile(cached, data, 0644)
1640 }
1641 data = nil
1642 regPkg := importRegistry[compilePath]
1643 if regPkg != nil {
1644 cachedMxh := cachedMxhPath(cacheBase, pkg.path, hash, pkg.version)
1645 mxhData := generateMxh(regPkg)
1646 writeFile(cachedMxh, []byte(mxhData), 0644)
1647 }
1648 }
1649 writeStr(2, "rebuild done\n")
1650 }
1651
1652 func main() {
1653 builtinOnly = []string{"unsafe", "runtime", "moxie"}
1654 args := getArgs()
1655 if len(args) < 2 {
1656 writeStr(2, "usage: mxc <build|version|cache|fetch|update> ...\n")
1657 os.Exit(1)
1658 }
1659
1660 cmd := args[1]
1661 if cmd == "version" {
1662 writeStr(1, "mxc " | mxcVersion() | "\n")
1663 return
1664 }
1665 if cmd == "cache" {
1666 if len(args) >= 3 && args[2] == "clean" {
1667 cacheClean()
1668 return
1669 }
1670 writeStr(2, "usage: mxc cache clean\n")
1671 os.Exit(1)
1672 }
1673 if cmd == "fetch" {
1674 cmdFetch()
1675 return
1676 }
1677 if cmd == "update" {
1678 rebuildAll := false
1679 for ai := int32(2); ai < int32(len(args)); ai++ {
1680 if args[ai] == "--all" {
1681 rebuildAll = true
1682 }
1683 }
1684 cmdUpdate(rebuildAll)
1685 return
1686 }
1687 if cmd != "build" {
1688 writeStr(2, "usage: mxc <build|version|cache|fetch|update> ...\n")
1689 os.Exit(1)
1690 }
1691
1692 outpath := ""
1693 pkgPath := ""
1694 printIR := false
1695 forceRebuild := false
1696 i := int32(2)
1697 for i < int32(len(args)) {
1698 if args[i] == "-o" && i+1 < int32(len(args)) {
1699 outpath = args[i+1]
1700 i += 2
1701 } else if args[i] == "-print-ir" {
1702 printIR = true
1703 i++
1704 } else if args[i] == "-a" {
1705 forceRebuild = true
1706 i++
1707 } else {
1708 pkgPath = args[i]
1709 i++
1710 }
1711 }
1712 if pkgPath == "" {
1713 fatal("no package specified")
1714 }
1715 if outpath == "" {
1716 outpath = pathBase(pkgPath)
1717 }
1718
1719 root := getenv("MOXIEROOT")
1720 if root == "" {
1721 root = "."
1722 }
1723 triple := "x86_64-unknown-linux-musleabihf"
1724 clang := "clang-21"
1725
1726 if hasPrefix(pkgPath, "./") || hasPrefix(pkgPath, "../") || pkgPath == "." {
1727 absDir := pkgPath
1728 if absDir == "." {
1729 absDir = ""
1730 }
1731 globalModPrefix, globalModDir = findModuleRoot(absDir)
1732 }
1733
1734 registerBuiltins()
1735 pkgs := resolveAll(pkgPath, root)
1736 if len(pkgs) == 0 {
1737 fatal("no packages to compile")
1738 }
1739
1740 tmpdir := "/tmp/mxc-build"
1741 mkdirAll(tmpdir, 0755)
1742
1743 cacheBase := pkgCacheDir()
1744 var allBcFiles []string
1745
1746 for _, pkg := range pkgs {
1747 compilePath := pkg.path
1748 if hasPrefix(compilePath, "/") || hasPrefix(compilePath, "./") || hasPrefix(compilePath, "../") {
1749 compilePath = pkg.name
1750 }
1751
1752 hash := pkgCacheKey(pkg.dir, pkg.files)
1753 cached := cachedBcPath(cacheBase, pkg.path, hash, pkg.version)
1754 cachedMxh := cachedMxhPath(cacheBase, pkg.path, hash, pkg.version)
1755
1756 _, bcOk := readFile(cached)
1757 _, mxhOk := readFile(cachedMxh)
1758 if bcOk && mxhOk && !forceRebuild {
1759 writeStr(2, " cached " | pkg.path)
1760 if pkg.version != "" {
1761 writeStr(2, " @ " | pkg.version)
1762 }
1763 writeStr(2, "\n")
1764 loadMxh(cachedMxh)
1765 allBcFiles = append(allBcFiles, cached)
1766 for _, bf := range pkg.bcfiles {
1767 allBcFiles = append(allBcFiles, joinPath(pkg.dir, bf))
1768 }
1769 continue
1770 }
1771
1772 if isStdlib(pkg.path) && bcOk && !forceRebuild {
1773 writeStr(2, " cached " | pkg.path | "\n")
1774 src := concatSources(pkg.dir, pkg.files)
1775 TypeCheckOnly(src, compilePath)
1776 src = nil
1777 regPkg := importRegistry[compilePath]
1778 if regPkg != nil {
1779 cacheDir := joinPath(cacheBase, safeName(pkg.path))
1780 mkdirAll(cacheDir, 0755)
1781 mxhData := generateMxh(regPkg)
1782 writeFile(cachedMxh, []byte(mxhData), 0644)
1783 }
1784 allBcFiles = append(allBcFiles, cached)
1785 continue
1786 }
1787
1788 writeStr(2, " compile " | pkg.path | "\n")
1789 src := concatSources(pkg.dir, pkg.files)
1790 ir := compileSource(src, compilePath, triple)
1791 src = nil
1792 if printIR {
1793 writeStr(1, ir)
1794 }
1795 llFile := joinPath(tmpdir, safeName(pkg.path) | ".ll")
1796 writeFile(llFile, []byte(ir), 0644)
1797 ir = ""
1798 bcFile := joinPath(tmpdir, safeName(pkg.path) | ".bc")
1799 rc := run([]string{clang, "--target=" | triple, "-emit-llvm", "-c", llFile, "-o", bcFile})
1800 if rc != 0 {
1801 fatal("clang failed for " | pkg.path)
1802 }
1803 cacheDir := joinPath(cacheBase, safeName(pkg.path))
1804 mkdirAll(cacheDir, 0755)
1805 data, rok := readFile(bcFile)
1806 if rok {
1807 writeFile(cached, data, 0644)
1808 }
1809 data = nil
1810 regPkg := importRegistry[compilePath]
1811 if regPkg != nil {
1812 mxhData := generateMxh(regPkg)
1813 writeFile(cachedMxh, []byte(mxhData), 0644)
1814 }
1815 allBcFiles = append(allBcFiles, bcFile)
1816 for _, cf := range pkg.cfiles {
1817 cPath := joinPath(pkg.dir, cf)
1818 cbcFile := joinPath(tmpdir, safeName(pkg.path) | "_" | cf | ".bc")
1819 rc = run([]string{clang, "--target=" | triple, "-emit-llvm", "-c", cPath, "-o", cbcFile})
1820 if rc != 0 {
1821 fatal("clang failed for " | cf)
1822 }
1823 allBcFiles = append(allBcFiles, cbcFile)
1824 }
1825 for _, bf := range pkg.bcfiles {
1826 allBcFiles = append(allBcFiles, joinPath(pkg.dir, bf))
1827 }
1828 }
1829
1830 loadSysroot(joinPath(root, "_sysroot.env"))
1831
1832 sysroot := joinPath(root, "sysroot")
1833 runtimeBc := getenv("RUNTIME_BC")
1834 if runtimeBc == "" {
1835 runtimeBc = joinPath(sysroot, "runtime.bc")
1836 _, rok := readFile(runtimeBc)
1837 if !rok {
1838 runtimeBc = joinPath(root, "_runtime.bc")
1839 }
1840 }
1841 muslDir := getenv("MUSL_DIR")
1842 if muslDir == "" {
1843 muslDir = joinPath(sysroot, "musl")
1844 }
1845 comprtLib := getenv("COMPRT_LIB")
1846 if comprtLib == "" {
1847 comprtLib = joinPath(joinPath(sysroot, "compiler-rt"), "lib.a")
1848 }
1849 bdwgcLib := getenv("BDWGC_LIB")
1850 if bdwgcLib == "" {
1851 bdwgcLib = joinPath(joinPath(sysroot, "bdwgc"), "lib.a")
1852 }
1853 muslLib := getenv("MUSL_LIB")
1854 if muslLib == "" {
1855 muslLib = joinPath(joinPath(sysroot, "musl"), "lib.a")
1856 }
1857
1858 writeStr(2, " generate initAll\n")
1859 var initCalls string
1860 for _, pkg := range pkgs {
1861 compilePath := pkg.path
1862 if hasPrefix(compilePath, "/") || hasPrefix(compilePath, "./") || hasPrefix(compilePath, "../") {
1863 compilePath = pkg.name
1864 }
1865 if compilePath == "main" {
1866 continue
1867 }
1868 initSym := irGlobalSymbol(compilePath, "main")
1869 initCalls = initCalls | " call void " | initSym | "(ptr %context)\n"
1870 }
1871 initAllIR := "\ndefine void @runtime.initAll(ptr %context) {\nentry:\n" | initCalls | " ret void\n}\n"
1872 declared := map[string]bool{}
1873 for _, pkg := range pkgs {
1874 compilePath := pkg.path
1875 if hasPrefix(compilePath, "/") || hasPrefix(compilePath, "./") || hasPrefix(compilePath, "../") {
1876 compilePath = pkg.name
1877 }
1878 if compilePath == "main" {
1879 continue
1880 }
1881 declSym := irGlobalSymbol(compilePath, "main")
1882 if declared[declSym] {
1883 continue
1884 }
1885 declared[declSym] = true
1886 initAllIR = initAllIR | "declare void " | declSym | "(ptr)\n"
1887 }
1888 initAllFile := joinPath(tmpdir, "initall.ll")
1889 writeFile(initAllFile, []byte(initAllIR), 0644)
1890 initAllBc := joinPath(tmpdir, "initall.bc")
1891 rc := run([]string{clang, "--target=" | triple, "-emit-llvm", "-c", initAllFile, "-o", initAllBc})
1892 if rc != 0 {
1893 fatal("clang failed for initall")
1894 }
1895
1896 writeStr(2, " merge bitcode\n")
1897 llvmLink := "llvm-link-21"
1898 mergedBc := joinPath(tmpdir, "merged.bc")
1899 linkArgs := []string{llvmLink, "-o", mergedBc, runtimeBc}
1900 shimsBc := joinPath(sysroot, "hashmap_shims.bc")
1901 linkArgs = append(linkArgs, shimsBc)
1902 runtimeGoBc := joinPath(sysroot, "runtime_go.bc")
1903 if _, gok := readFile(runtimeGoBc); gok {
1904 overrideArg := "--override=" | runtimeGoBc
1905 linkArgs = append(linkArgs, overrideArg)
1906 }
1907 linkArgs = append(linkArgs, allBcFiles...)
1908 linkArgs = append(linkArgs, initAllBc)
1909 rc = run(linkArgs)
1910 if rc != 0 {
1911 fatal("llvm-link failed")
1912 }
1913
1914 writeStr(2, " link\n")
1915 objFiles := getenv("OBJ_FILES")
1916 if objFiles == "" {
1917 objEntries := listFiles(joinPath(sysroot, "obj"), ".bc")
1918 if objEntries != nil {
1919 for _, e := range objEntries {
1920 objFiles = objFiles | joinPath(joinPath(sysroot, "obj"), e) | " "
1921 }
1922 }
1923 }
1924 linker := "ld.lld-21"
1925 ldArgs := []string{linker, "--gc-sections", "-o", outpath,
1926 joinPath(muslDir, "crt1.o"),
1927 mergedBc}
1928 for _, f := range splitBySpace(objFiles) {
1929 if f != "" {
1930 ldArgs = append(ldArgs, f)
1931 }
1932 }
1933 ldArgs = append(ldArgs, comprtLib, bdwgcLib, muslLib, "--lto-O0", "-z", "stack-size=67108864")
1934 rc = run(ldArgs)
1935 if rc != 0 {
1936 fatal("linker failed")
1937 }
1938 writeStr(2, " -> " | outpath | "\n")
1939 }
1940