romdevtools 0.71.1 → 0.84.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +19 -16
- package/CHANGELOG.md +184 -0
- package/README.md +5 -5
- package/examples/README.md +1 -0
- package/examples/gametank/templates/gt_draw.h +91 -0
- package/examples/gametank/templates/gt_hud.h +89 -0
- package/examples/gametank/templates/gt_palette.h +59 -0
- package/examples/gametank/templates/gt_sound.h +90 -0
- package/examples/gametank/templates/gt_sprites.h +53 -0
- package/examples/gametank/templates/platformer.c +318 -0
- package/examples/gametank/templates/puzzle.c +295 -0
- package/examples/gametank/templates/racing.c +139 -0
- package/examples/gametank/templates/shmup.c +277 -0
- package/examples/gametank/templates/sports.c +119 -0
- package/package.json +24 -22
- package/src/cores/capabilities.js +24 -0
- package/src/cores/registry.js +7 -1
- package/src/host/LibretroHost.js +90 -17
- package/src/host/callbacks.js +20 -2
- package/src/host/cpu-state.js +17 -0
- package/src/host/gametank-acp-state.js +53 -0
- package/src/http/skill-doc.js +10 -6
- package/src/mcp/tools/audio.js +1 -1
- package/src/mcp/tools/cart-parts.js +76 -0
- package/src/mcp/tools/lifecycle.js +1 -1
- package/src/mcp/tools/platform-tools.js +9 -1
- package/src/platforms/gametank/lib/gt/audio/acp_image.bin +0 -0
- package/src/platforms/gametank/lib/gt/audio/audio_coprocessor.c +55 -0
- package/src/platforms/gametank/lib/gt/audio/audio_coprocessor.h +34 -0
- package/src/platforms/gametank/lib/gt/audio/audio_fw.asm +223 -0
- package/src/platforms/gametank/lib/gt/audio/audio_fw_blob.s +8 -0
- package/src/platforms/gametank/lib/gt/audio/instruments.c +79 -0
- package/src/platforms/gametank/lib/gt/audio/instruments.h +25 -0
- package/src/platforms/gametank/lib/gt/audio/music.c +370 -0
- package/src/platforms/gametank/lib/gt/audio/music.h +35 -0
- package/src/platforms/gametank/lib/gt/audio/note_numbers.h +132 -0
- package/src/platforms/gametank/lib/gt/audio/scaled_sines.raw +0 -0
- package/src/platforms/gametank/lib/gt/audio/sine_256_-63_63.bin +0 -0
- package/src/platforms/gametank/lib/gt/banking.c +29 -0
- package/src/platforms/gametank/lib/gt/banking.h +8 -0
- package/src/platforms/gametank/lib/gt/banking2.s +41 -0
- package/src/platforms/gametank/lib/gt/crt0.s +102 -0
- package/src/platforms/gametank/lib/gt/draw_logo.s +55 -0
- package/src/platforms/gametank/lib/gt/feature/persist/persist.c +103 -0
- package/src/platforms/gametank/lib/gt/feature/persist/persist.h +13 -0
- package/src/platforms/gametank/lib/gt/feature/random/random.c +40 -0
- package/src/platforms/gametank/lib/gt/feature/random/random.h +18 -0
- package/src/platforms/gametank/lib/gt/feature/text/text.c +111 -0
- package/src/platforms/gametank/lib/gt/feature/text/text.h +25 -0
- package/src/platforms/gametank/lib/gt/gametank.c +12 -0
- package/src/platforms/gametank/lib/gt/gametank.h +80 -0
- package/src/platforms/gametank/lib/gt/gametank_logo.inc +393 -0
- package/src/platforms/gametank/lib/gt/gen/assets/assets_index.h +9 -0
- package/src/platforms/gametank/lib/gt/gen/assets/sdk_default.h +5 -0
- package/src/platforms/gametank/lib/gt/gen/bank_nums.h +11 -0
- package/src/platforms/gametank/lib/gt/gen/modules_enabled.h +4 -0
- package/src/platforms/gametank/lib/gt/gen/modules_enabled.inc +4 -0
- package/src/platforms/gametank/lib/gt/gfx/draw_direct.c +132 -0
- package/src/platforms/gametank/lib/gt/gfx/draw_direct.h +75 -0
- package/src/platforms/gametank/lib/gt/gfx/draw_queue.c +177 -0
- package/src/platforms/gametank/lib/gt/gfx/draw_queue.h +35 -0
- package/src/platforms/gametank/lib/gt/gfx/draw_util.s +157 -0
- package/src/platforms/gametank/lib/gt/gfx/gfx_sys.c +43 -0
- package/src/platforms/gametank/lib/gt/gfx/gfx_sys.h +46 -0
- package/src/platforms/gametank/lib/gt/gfx/sprites.c +216 -0
- package/src/platforms/gametank/lib/gt/gfx/sprites.h +41 -0
- package/src/platforms/gametank/lib/gt/init.c +9 -0
- package/src/platforms/gametank/lib/gt/input.c +26 -0
- package/src/platforms/gametank/lib/gt/input.h +20 -0
- package/src/platforms/gametank/lib/gt/interrupt.s +67 -0
- package/src/platforms/gametank/lib/gt/vectors.s +14 -0
- package/src/platforms/gametank/lib/gt/wait.s +53 -0
- package/src/playtest/playtest.js +174 -26
- package/src/toolchains/arm-none-eabi-gcc/gcc.js +39 -188
- package/src/toolchains/asar/asar.js +10 -15
- package/src/toolchains/cc65/cc65.js +82 -92
- package/src/toolchains/cc65/da65.js +12 -17
- package/src/toolchains/cc65/preset-resolver.js +13 -2
- package/src/toolchains/cc65/presets/gametank/gametank.h +80 -0
- package/src/toolchains/cc65/presets/gametank/sdk.cfg +32 -0
- package/src/toolchains/cc65/presets/gametank/sdk.crt0.s +61 -0
- package/src/toolchains/cc65/presets/gametank/sdk.vectors.s +11 -0
- package/src/toolchains/cc65/presets/gametank/single-bank.cfg +28 -0
- package/src/toolchains/cc65/presets/gametank/single-bank.crt0.s +71 -0
- package/src/toolchains/cc65/presets/gametank/single-bank.vectors.s +12 -0
- package/src/toolchains/common/c-build.js +109 -0
- package/src/toolchains/common/gcc-toolchain.js +164 -0
- package/src/toolchains/common/wasm-tool.js +101 -0
- package/src/toolchains/dasm/dasm.js +12 -18
- package/src/toolchains/gba-c/gba-c.js +253 -305
- package/src/toolchains/genesis-c/genesis-c.js +196 -225
- package/src/toolchains/index.js +58 -4
- package/src/toolchains/m68k-elf-gcc/gcc.js +39 -202
- package/src/toolchains/mips-c/mips-c.js +68 -78
- package/src/toolchains/mips-elf-gcc/gcc.js +55 -118
- package/src/toolchains/rgbds/rgbds.js +7 -19
- package/src/toolchains/sdcc/sdcc.js +35 -26
- package/src/toolchains/sh-c/sh-c.js +44 -52
- package/src/toolchains/sh-elf-gcc/gcc.js +55 -110
- package/src/toolchains/sjasm/sjasm.js +10 -14
- package/src/toolchains/snes-c/snes-c.js +125 -150
- package/src/toolchains/tcc816/tcc816.js +10 -15
- package/src/toolchains/vasm68k/vasm68k.js +11 -16
- package/src/toolchains/wladx/wladx.js +5 -17
- package/src/toolchains/z80/binutils.js +5 -11
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
// Shared C-SDK build-pipeline helper (0.81.0). The gba-c / genesis-c / mips-c / sh-c
|
|
2
|
+
// builders all walk the SAME spine — compile each .c (cc1 → as), assemble raw .s,
|
|
3
|
+
// assemble a crt0, link, optionally objcopy, finalize — and at EVERY stage repeat the
|
|
4
|
+
// identical 5-part dance:
|
|
5
|
+
//
|
|
6
|
+
// const r = await runX(...);
|
|
7
|
+
// log += `--- stage ---\n${r.log || "(ok)"}\n`;
|
|
8
|
+
// if (r.exitCode !== 0 || !r.<output>)
|
|
9
|
+
// return { ok: false, binary: null, log, exitCode: r.exitCode || 1, stage, ...(r.crash ? { crash } : {}) };
|
|
10
|
+
//
|
|
11
|
+
// That `if (...) return { ok:false, ... }` line appeared ~73 times across the 5 builders.
|
|
12
|
+
// CBuild owns it: each builder makes one `new CBuild()`, calls `.stage(name, () => runX(...), pick)`,
|
|
13
|
+
// and the helper accumulates the log + THROWS a BuildError on any failure. The builder
|
|
14
|
+
// wraps its body in try/catch and turns a BuildError into the same `{ ok:false, ... }` shape
|
|
15
|
+
// it returned before — so the public contract is byte-for-byte unchanged, the boilerplate
|
|
16
|
+
// is gone, and each builder keeps only its genuinely-per-platform wiring (which crt0, which
|
|
17
|
+
// libs, finalize). No behavior change.
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Thrown by CBuild.stage() when a stage fails. Carries everything the builder's
|
|
21
|
+
* error return needs. The builder catches it and maps to its result object.
|
|
22
|
+
*/
|
|
23
|
+
export class BuildError extends Error {
|
|
24
|
+
/** @param {{stage:string, exitCode:number, log:string, crash?:any}} info */
|
|
25
|
+
constructor(info) {
|
|
26
|
+
super(`build failed at stage: ${info.stage}`);
|
|
27
|
+
this.name = "BuildError";
|
|
28
|
+
this.stage = info.stage;
|
|
29
|
+
this.exitCode = info.exitCode;
|
|
30
|
+
this.log = info.log;
|
|
31
|
+
if (info.crash) this.crash = info.crash;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* The canonical failure result the GCC/tcc C-SDK builders return (includes
|
|
35
|
+
* `ok: false`). Pass extra fields (e.g. a platform-specific `{ runtime }`) to merge.
|
|
36
|
+
* @param {Record<string, any>} [extra]
|
|
37
|
+
*/
|
|
38
|
+
toResult(extra = {}) {
|
|
39
|
+
return { ok: false, ...this.fields(extra) };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* The failure fields WITHOUT a forced `ok` — for builders (cc65/sdcc) whose
|
|
44
|
+
* lower layer returns `{ binary, log, exitCode, stage }` and lets the caller
|
|
45
|
+
* (index.js) add `ok`. Same { binary:null, log, exitCode, stage, crash? } core
|
|
46
|
+
* + any `extra` (e.g. sdcc's `failedTU` / `compiledOK`). Note: crash is only
|
|
47
|
+
* spread when present (cc65/sdcc historically never carried it — pass nothing).
|
|
48
|
+
* @param {Record<string, any>} [extra]
|
|
49
|
+
*/
|
|
50
|
+
fields(extra = {}) {
|
|
51
|
+
return {
|
|
52
|
+
binary: null,
|
|
53
|
+
log: this.log,
|
|
54
|
+
exitCode: this.exitCode,
|
|
55
|
+
stage: this.stage,
|
|
56
|
+
...(this.crash ? { crash: this.crash } : {}),
|
|
57
|
+
...extra,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* A build-log accumulator + stage runner. One per build.
|
|
64
|
+
*
|
|
65
|
+
* Usage:
|
|
66
|
+
* const cb = new CBuild();
|
|
67
|
+
* const cc = await cb.stage(`cc1 (${name})`, () => runCc1x({...}), r => r.asmSource);
|
|
68
|
+
* // cc is the full run result; cb.log has the accumulated log.
|
|
69
|
+
* // a failure THROWS BuildError — wrap the build body in try/catch.
|
|
70
|
+
*/
|
|
71
|
+
export class CBuild {
|
|
72
|
+
constructor() {
|
|
73
|
+
/** @type {string} accumulated build log */
|
|
74
|
+
this.log = "";
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Run one toolchain stage, append its log, and throw BuildError on failure.
|
|
79
|
+
*
|
|
80
|
+
* @template T
|
|
81
|
+
* @param {string} name stage label — the failure `stage` field AND, by default,
|
|
82
|
+
* the log header. Most builders use one string for both.
|
|
83
|
+
* @param {() => Promise<T>} run the toolchain call (returns {log, exitCode, crash?, ...output})
|
|
84
|
+
* @param {(r: T) => any} pick selects the stage's required output (e.g. r => r.object);
|
|
85
|
+
* a falsy result is treated as failure even if exitCode is 0
|
|
86
|
+
* @param {{logName?:string}} [opts] `logName` overrides ONLY the log header when a
|
|
87
|
+
* builder historically logged a label that differs from the
|
|
88
|
+
* failure `stage` string (e.g. log "as (x → .o)" / stage "as (x)").
|
|
89
|
+
* @returns {Promise<T>} the full run result (on success)
|
|
90
|
+
*/
|
|
91
|
+
async stage(name, run, pick, opts = {}) {
|
|
92
|
+
const r = await run();
|
|
93
|
+
this.log += `--- ${opts.logName ?? name} ---\n${r.log || "(ok)"}\n`;
|
|
94
|
+
if (r.exitCode !== 0 || !pick(r)) {
|
|
95
|
+
throw new BuildError({
|
|
96
|
+
stage: name,
|
|
97
|
+
exitCode: r.exitCode || 1,
|
|
98
|
+
log: this.log,
|
|
99
|
+
crash: r.crash,
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
return r;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** Append a freeform note to the log (e.g. a sub-step that isn't a toolchain stage). */
|
|
106
|
+
note(text) {
|
|
107
|
+
this.log += text.endsWith("\n") ? text : text + "\n";
|
|
108
|
+
}
|
|
109
|
+
}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
// Shared GCC-family toolchain factory (0.81.0). The arm/m68k/mips/sh "gcc" wrappers
|
|
2
|
+
// were ~73% identical: each exported runCc1X / runXAs / runXLd / runXObjcopy that did
|
|
3
|
+
// the same input-marshalling + runIsolated() + output-decoding, differing only in:
|
|
4
|
+
// - the npm package the glue ships in + the glue filenames
|
|
5
|
+
// - the per-stage arch flags (cc1 / as / ld), which for MIPS depend on endian
|
|
6
|
+
// - the linker-script filename + the objcopy output filename
|
|
7
|
+
// makeGccToolchain(config) returns the 4 run functions, so each arch shrinks to a
|
|
8
|
+
// small config object. Glue resolution is lazy+memoized (booting never loads a
|
|
9
|
+
// toolchain package until something is actually built with it).
|
|
10
|
+
//
|
|
11
|
+
// All four stages keep the exact pre-refactor argv, /work paths, output encodings,
|
|
12
|
+
// and return shapes (including `...(r.crash ? { crash, stage:"crash" } : {})`), so
|
|
13
|
+
// this is a pure de-duplication — no behavior change.
|
|
14
|
+
import { runIsolated, getOutputBytes, getOutputText } from "../_worker/run.js";
|
|
15
|
+
import { makeGlueResolver, marshalInputs } from "./wasm-tool.js";
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* @typedef {Object} GccArchConfig
|
|
19
|
+
* @property {string} pkg npm package the glue ships in
|
|
20
|
+
* @property {string} localDir the wrapper's __dirname (dev fallback base)
|
|
21
|
+
* @property {string} label tool label for "not found" errors
|
|
22
|
+
* @property {Object} glue glue filenames per stage
|
|
23
|
+
* @property {string} glue.cc1 e.g. "cc1-m68k.mjs"
|
|
24
|
+
* @property {string} glue.as e.g. "m68k-elf-as.mjs"
|
|
25
|
+
* @property {string} glue.ld e.g. "m68k-elf-ld.mjs"
|
|
26
|
+
* @property {string} glue.objcopy e.g. "m68k-elf-objcopy.mjs"
|
|
27
|
+
* @property {string[]|((endian:string)=>string[])} cc1Flags arch flags for cc1 (before -iquote/-I)
|
|
28
|
+
* @property {string[]|((endian:string)=>string[])} asFlags arch flags for as (before -I)
|
|
29
|
+
* @property {string[]|((endian:string)=>string[])} [ldFlags] arch flags for ld (before -T); default []
|
|
30
|
+
* @property {string} ldScriptName name the link script is mounted as (e.g. "genesis.ld")
|
|
31
|
+
* @property {string} outputName objcopy output filename (e.g. "main.bin", "main.gba")
|
|
32
|
+
* @property {string} [defaultEndian] "big"|"little" — only meaningful when flags are fns
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
/** Resolve a flags entry that may be a constant array or an endian-dependent fn. */
|
|
36
|
+
function flags(entry, endian) {
|
|
37
|
+
return typeof entry === "function" ? entry(endian) : (entry ?? []);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Build the 4 GCC-stage run functions for one architecture.
|
|
42
|
+
* @param {GccArchConfig} config
|
|
43
|
+
* @returns {{ runCc1:Function, runAs:Function, runLd:Function, runObjcopy:Function }}
|
|
44
|
+
*/
|
|
45
|
+
export function makeGccToolchain(config) {
|
|
46
|
+
const glue = makeGlueResolver({ pkg: config.pkg, localDir: config.localDir, label: config.label });
|
|
47
|
+
const endianOf = (args) => args.endian ?? config.defaultEndian ?? "big";
|
|
48
|
+
|
|
49
|
+
/** cc1: C source → assembly (.s). */
|
|
50
|
+
async function runCc1(args) {
|
|
51
|
+
const { source, options = [] } = args;
|
|
52
|
+
const inputFiles = marshalInputs({ primary: { name: "main.c", text: source }, text: args.headers ?? {} });
|
|
53
|
+
const argv = [
|
|
54
|
+
...flags(config.cc1Flags, endianOf(args)),
|
|
55
|
+
"-iquote", "/work",
|
|
56
|
+
"-I", "/work",
|
|
57
|
+
...options,
|
|
58
|
+
"/work/main.c",
|
|
59
|
+
"-o", "/work/main.s",
|
|
60
|
+
];
|
|
61
|
+
const r = await runIsolated({
|
|
62
|
+
gluePath: glue(config.glue.cc1),
|
|
63
|
+
argv,
|
|
64
|
+
inputFiles,
|
|
65
|
+
outputFiles: [{ vfsPath: "/work/main.s", encoding: "utf8" }],
|
|
66
|
+
});
|
|
67
|
+
return {
|
|
68
|
+
log: r.log,
|
|
69
|
+
exitCode: r.exitCode,
|
|
70
|
+
asmSource: getOutputText(r, "/work/main.s") || null,
|
|
71
|
+
...(r.crash ? { crash: r.crash, stage: "crash" } : {}),
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** as: assembly (.s) → object (.o). */
|
|
76
|
+
async function runAs(args) {
|
|
77
|
+
const { source, options = [] } = args;
|
|
78
|
+
const inputFiles = marshalInputs({
|
|
79
|
+
primary: { name: "main.s", text: source },
|
|
80
|
+
text: args.includes ?? {},
|
|
81
|
+
binary: args.binaryIncludes ?? {},
|
|
82
|
+
});
|
|
83
|
+
const argv = [
|
|
84
|
+
...flags(config.asFlags, endianOf(args)),
|
|
85
|
+
"-I", "/work",
|
|
86
|
+
...options,
|
|
87
|
+
"/work/main.s",
|
|
88
|
+
"-o", "/work/main.o",
|
|
89
|
+
];
|
|
90
|
+
const r = await runIsolated({
|
|
91
|
+
gluePath: glue(config.glue.as),
|
|
92
|
+
argv,
|
|
93
|
+
inputFiles,
|
|
94
|
+
outputFiles: [{ vfsPath: "/work/main.o", encoding: "base64" }],
|
|
95
|
+
});
|
|
96
|
+
return {
|
|
97
|
+
log: r.log,
|
|
98
|
+
exitCode: r.exitCode,
|
|
99
|
+
object: getOutputBytes(r, "/work/main.o"),
|
|
100
|
+
...(r.crash ? { crash: r.crash, stage: "crash" } : {}),
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** ld: objects + link script → ELF (+ map). */
|
|
105
|
+
async function runLd(args) {
|
|
106
|
+
const { objects, linkScript, libraries = [], libraryPaths = [], options = [] } = args;
|
|
107
|
+
const inputFiles = marshalInputs({
|
|
108
|
+
text: { [config.ldScriptName]: linkScript },
|
|
109
|
+
binary: { ...objects, ...(args.archives ?? {}) },
|
|
110
|
+
});
|
|
111
|
+
const argv = [
|
|
112
|
+
...flags(config.ldFlags, endianOf(args)),
|
|
113
|
+
"-T", "/work/" + config.ldScriptName,
|
|
114
|
+
"-o", "/work/main.elf",
|
|
115
|
+
"-Map=/work/main.map",
|
|
116
|
+
...libraryPaths.flatMap((p) => ["-L", p]),
|
|
117
|
+
...Object.keys(objects).map((n) => "/work/" + n),
|
|
118
|
+
...libraries.map((l) => `-l${l}`),
|
|
119
|
+
...options,
|
|
120
|
+
];
|
|
121
|
+
const r = await runIsolated({
|
|
122
|
+
gluePath: glue(config.glue.ld),
|
|
123
|
+
argv,
|
|
124
|
+
inputFiles,
|
|
125
|
+
outputFiles: [
|
|
126
|
+
{ vfsPath: "/work/main.elf", encoding: "base64" },
|
|
127
|
+
{ vfsPath: "/work/main.map", encoding: "utf8" },
|
|
128
|
+
],
|
|
129
|
+
});
|
|
130
|
+
return {
|
|
131
|
+
log: r.log,
|
|
132
|
+
exitCode: r.exitCode,
|
|
133
|
+
elf: getOutputBytes(r, "/work/main.elf"),
|
|
134
|
+
map: getOutputText(r, "/work/main.map") || null,
|
|
135
|
+
...(r.crash ? { crash: r.crash, stage: "crash" } : {}),
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/** objcopy: ELF → raw binary. */
|
|
140
|
+
async function runObjcopy(args) {
|
|
141
|
+
const { elf, options = [] } = args;
|
|
142
|
+
const inputFiles = marshalInputs({ binary: { "main.elf": elf } });
|
|
143
|
+
const argv = [
|
|
144
|
+
"-O", "binary",
|
|
145
|
+
...options,
|
|
146
|
+
"/work/main.elf",
|
|
147
|
+
"/work/" + config.outputName,
|
|
148
|
+
];
|
|
149
|
+
const r = await runIsolated({
|
|
150
|
+
gluePath: glue(config.glue.objcopy),
|
|
151
|
+
argv,
|
|
152
|
+
inputFiles,
|
|
153
|
+
outputFiles: [{ vfsPath: "/work/" + config.outputName, encoding: "base64" }],
|
|
154
|
+
});
|
|
155
|
+
return {
|
|
156
|
+
log: r.log,
|
|
157
|
+
exitCode: r.exitCode,
|
|
158
|
+
binary: getOutputBytes(r, "/work/" + config.outputName),
|
|
159
|
+
...(r.crash ? { crash: r.crash, stage: "crash" } : {}),
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return { runCc1, runAs, runLd, runObjcopy };
|
|
164
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
// Shared WASM-toolchain plumbing (0.81.0). Every toolchain wrapper used to
|
|
2
|
+
// re-implement the SAME two things:
|
|
3
|
+
// 1. Resolve a tool's emscripten glue from its npm package, with a local
|
|
4
|
+
// src/ fallback for dev, throwing an install hint if neither exists.
|
|
5
|
+
// 2. Marshal { name: text|bytes } maps into the runIsolated() inputFiles[]
|
|
6
|
+
// shape (textFile / binaryFile per entry).
|
|
7
|
+
// This module owns both so the per-tool wrappers carry only what's genuinely
|
|
8
|
+
// tool-specific (package name, glue filename, argv, output decoding).
|
|
9
|
+
//
|
|
10
|
+
// Resolution stays LAZY at the call site (the wrappers memoize) so booting the
|
|
11
|
+
// server never touches a 100MB+ toolchain package unless that toolchain is
|
|
12
|
+
// actually used to build something.
|
|
13
|
+
import { existsSync } from "node:fs";
|
|
14
|
+
import { fileURLToPath } from "node:url";
|
|
15
|
+
import path from "node:path";
|
|
16
|
+
import { textFile, binaryFile } from "../_worker/run.js";
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Resolve a single tool glue FILE from its package, falling back to a local
|
|
20
|
+
* in-tree copy. This is the arm/m68k/mips/sh-gcc + asar + wladx + rgbds + dasm +
|
|
21
|
+
* vasm68k + sjasm + tcc816 shape (one .js/.mjs glue per tool).
|
|
22
|
+
*
|
|
23
|
+
* @param {Object} a
|
|
24
|
+
* @param {string} a.pkg npm package the glue ships in (e.g. "romdev-platform-gba")
|
|
25
|
+
* @param {string} a.file glue filename inside the package's wasm/ dir (e.g. "cc1-arm.mjs")
|
|
26
|
+
* @param {string} a.localDir the wrapper's __dirname (its wasm/ holds the dev fallback)
|
|
27
|
+
* @param {string} [a.label] tool name for the "not found" error (defaults to file)
|
|
28
|
+
* @returns {string} absolute path to the glue file
|
|
29
|
+
*/
|
|
30
|
+
export function resolveGlueFile({ pkg, file, localDir, label }) {
|
|
31
|
+
try {
|
|
32
|
+
const u = import.meta.resolve(pkg);
|
|
33
|
+
const p = path.join(path.dirname(fileURLToPath(u)), "wasm", file);
|
|
34
|
+
if (existsSync(p)) return p;
|
|
35
|
+
} catch { /* package not resolvable — fall through to local */ }
|
|
36
|
+
const local = path.join(localDir, "wasm", file);
|
|
37
|
+
if (existsSync(local)) return local;
|
|
38
|
+
throw new Error(`${label ?? file} WASM not found — install ${pkg}`);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Resolve a tool's BASE directory (the caller derives wasm/ + share/ from it).
|
|
43
|
+
* This is the cc65 + sdcc shape, where one package holds several tools + shared
|
|
44
|
+
* data and the wrapper wants the root, validated by a sentinel file's presence.
|
|
45
|
+
*
|
|
46
|
+
* @param {Object} a
|
|
47
|
+
* @param {string} a.pkg npm package (e.g. "romdev-toolchain-cc65")
|
|
48
|
+
* @param {string} a.sentinel a path under the base that must exist (e.g. "wasm/cc65.js")
|
|
49
|
+
* @param {string} a.localDir the wrapper's __dirname (the dev fallback base)
|
|
50
|
+
* @param {string} [a.label] tool name for the "not found" error
|
|
51
|
+
* @returns {string} absolute path to the base dir
|
|
52
|
+
*/
|
|
53
|
+
export function resolveToolBaseDir({ pkg, sentinel, localDir, label }) {
|
|
54
|
+
try {
|
|
55
|
+
const u = import.meta.resolve(pkg);
|
|
56
|
+
const dir = path.dirname(fileURLToPath(u));
|
|
57
|
+
if (existsSync(path.join(dir, sentinel))) return dir;
|
|
58
|
+
} catch { /* package not resolvable — fall through to local */ }
|
|
59
|
+
if (existsSync(path.join(localDir, sentinel))) return localDir;
|
|
60
|
+
throw new Error(`${label ?? pkg} WASM not found — install ${pkg}`);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* A lazy, memoized glue resolver bound to a package + local dir. Returns a
|
|
65
|
+
* function `glue(file)` that resolves+caches each glue path on first use.
|
|
66
|
+
* Replaces the `const _glue = {}; const xGlue = (f) => (_glue[f] ??= ...)`
|
|
67
|
+
* boilerplate every multi-tool wrapper repeats.
|
|
68
|
+
*
|
|
69
|
+
* @param {Object} a
|
|
70
|
+
* @param {string} a.pkg
|
|
71
|
+
* @param {string} a.localDir
|
|
72
|
+
* @param {string} [a.label]
|
|
73
|
+
* @returns {(file: string) => string}
|
|
74
|
+
*/
|
|
75
|
+
export function makeGlueResolver({ pkg, localDir, label }) {
|
|
76
|
+
const cache = {};
|
|
77
|
+
return (file) =>
|
|
78
|
+
(cache[file] ??= resolveGlueFile({ pkg, file, localDir, label }));
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Marshal text + binary virtual-file maps into the runIsolated() inputFiles[]
|
|
83
|
+
* array, all rooted at /work. Order: the primary source first (if given), then
|
|
84
|
+
* text entries, then binary entries — matching what the wrappers built by hand.
|
|
85
|
+
*
|
|
86
|
+
* @param {Object} a
|
|
87
|
+
* @param {{name: string, text: string}} [a.primary] the main source (e.g. main.c → /work/main.c)
|
|
88
|
+
* @param {Record<string,string>} [a.text] virtual text files (headers, includes, link scripts)
|
|
89
|
+
* @param {Record<string,Uint8Array>} [a.binary] virtual binary files (objects, archives, binary includes)
|
|
90
|
+
* @returns {import("../_worker/run.js").InputFile[]}
|
|
91
|
+
*/
|
|
92
|
+
export function marshalInputs({ primary, text = {}, binary = {} } = {}) {
|
|
93
|
+
/** @type {import("../_worker/run.js").InputFile[]} */
|
|
94
|
+
const files = [];
|
|
95
|
+
if (primary) files.push(textFile("/work/" + primary.name, primary.text));
|
|
96
|
+
for (const [name, content] of Object.entries(text))
|
|
97
|
+
files.push(textFile("/work/" + name, content));
|
|
98
|
+
for (const [name, bytes] of Object.entries(binary))
|
|
99
|
+
files.push(binaryFile("/work/" + name, bytes));
|
|
100
|
+
return files;
|
|
101
|
+
}
|
|
@@ -3,32 +3,26 @@
|
|
|
3
3
|
// Runs in an isolated child worker for crash isolation.
|
|
4
4
|
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
|
-
import { existsSync } from "node:fs";
|
|
7
6
|
import path from "node:path";
|
|
8
7
|
|
|
9
8
|
import { runIsolated, textFile, getOutputBytes, getOutputText } from "../_worker/run.js";
|
|
9
|
+
import { resolveGlueFile } from "../common/wasm-tool.js";
|
|
10
10
|
|
|
11
11
|
const __filename = fileURLToPath(import.meta.url);
|
|
12
12
|
const __dirname = path.dirname(__filename);
|
|
13
13
|
|
|
14
|
-
// dasm's WASM ships in romdev-platform-atari2600 (the only platform that
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
|
|
18
|
-
try {
|
|
19
|
-
const u = import.meta.resolve("romdev-platform-atari2600");
|
|
20
|
-
const p = path.join(path.dirname(fileURLToPath(u)), "wasm", "dasm.js");
|
|
21
|
-
if (existsSync(p)) return p;
|
|
22
|
-
} catch { /* package not resolvable — fall through to local */ }
|
|
23
|
-
const local = path.join(__dirname, "wasm", "dasm.js");
|
|
24
|
-
if (existsSync(local)) return local;
|
|
25
|
-
throw new Error("dasm WASM not found — install romdev-platform-atari2600");
|
|
26
|
-
}
|
|
27
|
-
// Lazy + memoized: resolve (and possibly throw "not installed") only on the
|
|
28
|
-
// FIRST dasm build, not at module load — so booting the server never touches
|
|
29
|
-
// this package unless an Atari 2600 ROM is actually built.
|
|
14
|
+
// dasm's WASM ships in romdev-platform-atari2600 (the only platform that uses
|
|
15
|
+
// it); a local src/ copy is the dev fallback. Lazy + memoized: resolve only on
|
|
16
|
+
// the FIRST dasm build, not at boot — so booting the server never touches this
|
|
17
|
+
// package unless an Atari 2600 ROM is actually built.
|
|
30
18
|
let _gluePath;
|
|
31
|
-
const gluePath = () =>
|
|
19
|
+
const gluePath = () =>
|
|
20
|
+
(_gluePath ??= resolveGlueFile({
|
|
21
|
+
pkg: "romdev-platform-atari2600",
|
|
22
|
+
file: "dasm.js",
|
|
23
|
+
localDir: __dirname,
|
|
24
|
+
label: "dasm",
|
|
25
|
+
}));
|
|
32
26
|
|
|
33
27
|
/**
|
|
34
28
|
* Run dasm on a source program in-memory.
|