space-data-module-sdk 0.5.14 → 0.5.15
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/README.md +67 -1
- package/package.json +1 -1
- package/src/compliance/pluginCompliance.js +1 -131
- package/src/index.d.ts +18 -1
- package/src/invoke/codec.js +9 -7
- package/src/manifest/typeRefs.js +12 -49
- package/src/testing/buildWasmEdgeRunner.js +214 -0
- package/src/testing/index.d.ts +190 -0
- package/src/testing/index.js +20 -0
- package/src/testing/moduleHarness.js +62 -0
- package/src/testing/native/wasmedge_emscripten_pthread_runner.c +1001 -0
- package/src/testing/processInvoke.js +226 -0
- package/src/testing/publicationProtectionDemo.js +271 -0
- package/src/transport/records.js +41 -53
package/README.md
CHANGED
|
@@ -14,11 +14,28 @@ This repository is the source of truth for module-level concerns:
|
|
|
14
14
|
- the `sds.bundle` single-file container
|
|
15
15
|
- deployment authorization plus SDS publication records (`REC`, `PNM`, `ENC`)
|
|
16
16
|
- the first canonical module hostcall/import ABI surface
|
|
17
|
+
- shared module-level testing/runtime harnesses, including the WasmEdge
|
|
18
|
+
process runner used by package-level validation suites
|
|
17
19
|
|
|
18
20
|
<p align="center">
|
|
19
21
|
<img src="docs/architecture.svg" alt="Module architecture overview" width="820" />
|
|
20
22
|
</p>
|
|
21
23
|
|
|
24
|
+
## Shared Harness Ownership
|
|
25
|
+
|
|
26
|
+
This repo owns the generic module-side runtime harnesses used across the stack:
|
|
27
|
+
|
|
28
|
+
- `createModuleHarness(...)` for process and WasmEdge-backed module invocation
|
|
29
|
+
- `resolveModuleHarnessLaunchPlan(...)` for portable launch planning
|
|
30
|
+
- `buildWasmEdgeEmscriptenPthreadRunner(...)` plus the shared native runner
|
|
31
|
+
source and runner-level pthread smoke test
|
|
32
|
+
|
|
33
|
+
Flow-specific standalone harnessing lives in `sdn-flow`, which layers flow
|
|
34
|
+
enqueue/drain behavior on top of its compiled standalone runtime host. Package-
|
|
35
|
+
specific validation suites, including conjunction replay/V&V, are expected to
|
|
36
|
+
sit on top of these shared harnesses instead of defining their own runtime
|
|
37
|
+
process model.
|
|
38
|
+
|
|
22
39
|
## Module Artifact Model
|
|
23
40
|
|
|
24
41
|
A compliant module built with this SDK is always a valid `.wasm` artifact with:
|
|
@@ -178,6 +195,11 @@ semantics.
|
|
|
178
195
|
This repo now exposes a manifest-driven harness generator from
|
|
179
196
|
`space-data-module-sdk/testing` and two complementary integration suites:
|
|
180
197
|
|
|
198
|
+
- shared process-level helpers for command-surface runtimes:
|
|
199
|
+
- `createPluginInvokeProcessClient(...)`
|
|
200
|
+
- `resolveWasmEdgePluginLaunchPlan(...)`
|
|
201
|
+
- `buildWasmEdgeEmscriptenPthreadRunner(...)`
|
|
202
|
+
|
|
181
203
|
- `npm run test:runtime-matrix`
|
|
182
204
|
- cross-language runtime smoke across the same WASM in Node.js, Go, Python,
|
|
183
205
|
Rust, Java, C#, and Swift
|
|
@@ -299,6 +321,46 @@ The reference path lives in
|
|
|
299
321
|
Standard bundle payloads now include the optional `deployment-plan` JSON entry
|
|
300
322
|
for resolved protocol installations and producer input bindings.
|
|
301
323
|
|
|
324
|
+
## Publication Protection Extensions
|
|
325
|
+
|
|
326
|
+
Digital signature and encrypted-delivery metadata are publication-layer
|
|
327
|
+
extensions, not a second module format. The canonical module is still:
|
|
328
|
+
|
|
329
|
+
- valid `.wasm`
|
|
330
|
+
- optionally carrying `sds.bundle` as a wasm custom section
|
|
331
|
+
- optionally carrying one appended SDS `REC` trailer after the wasm bytes
|
|
332
|
+
|
|
333
|
+
That trailing `REC` FlatBuffer is the standards-backed container for
|
|
334
|
+
publication records:
|
|
335
|
+
|
|
336
|
+
- `PNM` is the publication notice / digital-signature record
|
|
337
|
+
- identifies the artifact
|
|
338
|
+
- carries the CID and publish timestamp
|
|
339
|
+
- carries signature metadata so a loader can verify who published the module
|
|
340
|
+
- `ENC` is the encrypted-delivery record
|
|
341
|
+
- carries the X25519 / AES-CTR / HKDF parameters needed to decrypt the
|
|
342
|
+
protected transport payload
|
|
343
|
+
- does not change the canonical module ABI or manifest shape
|
|
344
|
+
|
|
345
|
+
The loader contract is always:
|
|
346
|
+
|
|
347
|
+
1. Start with the protected blob.
|
|
348
|
+
2. Scan backward for the trailing `REC` footer.
|
|
349
|
+
3. Parse `REC` using the standards-generated `REC`, `PNM`, and `ENC`
|
|
350
|
+
FlatBuffers from `spacedatastandards.org`.
|
|
351
|
+
4. Resolve `PNM` for signature/publication metadata.
|
|
352
|
+
5. Resolve `ENC` if the delivery was encrypted.
|
|
353
|
+
6. Strip the trailer or decrypt the protected payload.
|
|
354
|
+
7. Hand the remaining raw wasm bytes to the runtime.
|
|
355
|
+
|
|
356
|
+
Aligned-binary payloads are a separate invoke-ABI optimization. They do not
|
|
357
|
+
replace the canonical FlatBuffer schema. If a port advertises
|
|
358
|
+
`wireFormat: "aligned-binary"`, it must also advertise the regular
|
|
359
|
+
`wireFormat: "flatbuffer"` fallback for the same schema and file identifier in
|
|
360
|
+
the same accepted type set. The publication demo in the local lab uses a
|
|
361
|
+
regular `OMM.fbs` request and a `StateVector.fbs` response that advertises both
|
|
362
|
+
the canonical FlatBuffer fallback and the aligned-binary layout metadata.
|
|
363
|
+
|
|
302
364
|
## Module Publication
|
|
303
365
|
|
|
304
366
|
Packages that publish Space Data modules use the canonical `sdn-module`
|
|
@@ -402,10 +464,14 @@ npm run start:lab
|
|
|
402
464
|
|
|
403
465
|
Then open `http://localhost:4318`.
|
|
404
466
|
|
|
467
|
+
Use the `Run Publication Demo` button to generate a protected demo artifact and
|
|
468
|
+
inspect the parsed `REC`, `PNM`, and `ENC` records produced by the real
|
|
469
|
+
`spacedatastandards.org` generated message classes.
|
|
470
|
+
|
|
405
471
|
## Related Projects
|
|
406
472
|
|
|
407
473
|
- [`spacedatastandards.org`](https://spacedatastandards.org)
|
|
408
|
-
- [
|
|
474
|
+
- [`sdn-flow`](https://github.com/DigitalArsenal/sdn-flow)
|
|
409
475
|
- [`hd-wallet-wasm`](https://github.com/nicktj-dev/hd-wallet-wasm)
|
|
410
476
|
|
|
411
477
|
## Development
|
package/package.json
CHANGED
|
@@ -167,134 +167,6 @@ function normalizePayloadWireFormatName(value) {
|
|
|
167
167
|
return null;
|
|
168
168
|
}
|
|
169
169
|
|
|
170
|
-
function normalizedPluginFamilyName(value) {
|
|
171
|
-
if (!isNonEmptyString(value)) {
|
|
172
|
-
return null;
|
|
173
|
-
}
|
|
174
|
-
return value
|
|
175
|
-
.trim()
|
|
176
|
-
.toLowerCase()
|
|
177
|
-
.replace(/-/g, "_");
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
function typeSetHasDualWireFormats(typeSet) {
|
|
181
|
-
if (!Array.isArray(typeSet?.allowedTypes) || typeSet.allowedTypes.length === 0) {
|
|
182
|
-
return false;
|
|
183
|
-
}
|
|
184
|
-
let hasFlatbuffer = false;
|
|
185
|
-
let hasAlignedBinary = false;
|
|
186
|
-
for (const allowedType of typeSet.allowedTypes) {
|
|
187
|
-
const wireFormat = normalizePayloadWireFormatName(allowedType?.wireFormat);
|
|
188
|
-
if (wireFormat === "flatbuffer") {
|
|
189
|
-
hasFlatbuffer = true;
|
|
190
|
-
} else if (wireFormat === "aligned-binary") {
|
|
191
|
-
hasAlignedBinary = true;
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
return hasFlatbuffer && hasAlignedBinary;
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
function validateCanonicalDualFormatPort(port, issues, location, methodId, portId) {
|
|
198
|
-
if (!port) {
|
|
199
|
-
pushIssue(
|
|
200
|
-
issues,
|
|
201
|
-
"error",
|
|
202
|
-
"propagator-missing-canonical-port",
|
|
203
|
-
`Canonical propagator method "${methodId}" must declare port "${portId}".`,
|
|
204
|
-
location,
|
|
205
|
-
);
|
|
206
|
-
return;
|
|
207
|
-
}
|
|
208
|
-
if (!Array.isArray(port.acceptedTypeSets) || port.acceptedTypeSets.length === 0) {
|
|
209
|
-
pushIssue(
|
|
210
|
-
issues,
|
|
211
|
-
"error",
|
|
212
|
-
"propagator-missing-canonical-port",
|
|
213
|
-
`Canonical propagator method "${methodId}" port "${portId}" must declare acceptedTypeSets.`,
|
|
214
|
-
`${location}.acceptedTypeSets`,
|
|
215
|
-
);
|
|
216
|
-
return;
|
|
217
|
-
}
|
|
218
|
-
port.acceptedTypeSets.forEach((typeSet, index) => {
|
|
219
|
-
if (!typeSetHasDualWireFormats(typeSet)) {
|
|
220
|
-
pushIssue(
|
|
221
|
-
issues,
|
|
222
|
-
"error",
|
|
223
|
-
"canonical-port-missing-dual-wire-format",
|
|
224
|
-
`Canonical propagator method "${methodId}" port "${portId}" acceptedTypeSet "${typeSet?.setId ?? index}" must accept both regular flatbuffer and aligned-binary payloads.`,
|
|
225
|
-
`${location}.acceptedTypeSets[${index}]`,
|
|
226
|
-
);
|
|
227
|
-
}
|
|
228
|
-
});
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
function validateCanonicalPropagatorContract(manifest, issues, sourceName) {
|
|
232
|
-
if (normalizedPluginFamilyName(manifest?.pluginFamily) !== "propagator") {
|
|
233
|
-
return;
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
const methods = Array.isArray(manifest?.methods) ? manifest.methods : [];
|
|
237
|
-
const methodRequirements = [
|
|
238
|
-
{
|
|
239
|
-
methodId: "ingest_omm",
|
|
240
|
-
inputPorts: ["omm"],
|
|
241
|
-
outputPorts: [],
|
|
242
|
-
},
|
|
243
|
-
{
|
|
244
|
-
methodId: "describe_sources_batch",
|
|
245
|
-
inputPorts: ["request"],
|
|
246
|
-
outputPorts: ["result"],
|
|
247
|
-
},
|
|
248
|
-
{
|
|
249
|
-
methodId: "propagate_state",
|
|
250
|
-
inputPorts: ["request"],
|
|
251
|
-
outputPorts: ["state"],
|
|
252
|
-
},
|
|
253
|
-
];
|
|
254
|
-
|
|
255
|
-
for (const requirement of methodRequirements) {
|
|
256
|
-
const methodIndex = methods.findIndex(
|
|
257
|
-
(method) => method?.methodId === requirement.methodId,
|
|
258
|
-
);
|
|
259
|
-
if (methodIndex === -1) {
|
|
260
|
-
pushIssue(
|
|
261
|
-
issues,
|
|
262
|
-
"error",
|
|
263
|
-
"propagator-missing-canonical-method",
|
|
264
|
-
`Propagator plugins must declare canonical method "${requirement.methodId}".`,
|
|
265
|
-
`${sourceName}.methods`,
|
|
266
|
-
);
|
|
267
|
-
continue;
|
|
268
|
-
}
|
|
269
|
-
const method = methods[methodIndex];
|
|
270
|
-
const location = `${sourceName}.methods[${methodIndex}]`;
|
|
271
|
-
for (const portId of requirement.inputPorts) {
|
|
272
|
-
const port = Array.isArray(method.inputPorts)
|
|
273
|
-
? method.inputPorts.find((entry) => entry?.portId === portId)
|
|
274
|
-
: null;
|
|
275
|
-
validateCanonicalDualFormatPort(
|
|
276
|
-
port,
|
|
277
|
-
issues,
|
|
278
|
-
`${location}.inputPorts`,
|
|
279
|
-
requirement.methodId,
|
|
280
|
-
portId,
|
|
281
|
-
);
|
|
282
|
-
}
|
|
283
|
-
for (const portId of requirement.outputPorts) {
|
|
284
|
-
const port = Array.isArray(method.outputPorts)
|
|
285
|
-
? method.outputPorts.find((entry) => entry?.portId === portId)
|
|
286
|
-
: null;
|
|
287
|
-
validateCanonicalDualFormatPort(
|
|
288
|
-
port,
|
|
289
|
-
issues,
|
|
290
|
-
`${location}.outputPorts`,
|
|
291
|
-
requirement.methodId,
|
|
292
|
-
portId,
|
|
293
|
-
);
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
|
|
298
170
|
function validateStringField(issues, value, location, label) {
|
|
299
171
|
if (!isNonEmptyString(value)) {
|
|
300
172
|
pushIssue(issues, "error", "missing-string", `${label} must be a non-empty string.`, location);
|
|
@@ -494,7 +366,7 @@ function validateAllowedType(type, issues, location) {
|
|
|
494
366
|
"Allowed type fixedStringLength",
|
|
495
367
|
{ min: 0 },
|
|
496
368
|
);
|
|
497
|
-
|
|
369
|
+
validateIntegerField(
|
|
498
370
|
issues,
|
|
499
371
|
type.byteLength,
|
|
500
372
|
`${location}.byteLength`,
|
|
@@ -1387,8 +1259,6 @@ export function validatePluginManifest(manifest, options = {}) {
|
|
|
1387
1259
|
}
|
|
1388
1260
|
});
|
|
1389
1261
|
|
|
1390
|
-
validateCanonicalPropagatorContract(manifest, issues, sourceName);
|
|
1391
|
-
|
|
1392
1262
|
if (manifest.timers !== undefined && !Array.isArray(manifest.timers)) {
|
|
1393
1263
|
pushIssue(
|
|
1394
1264
|
issues,
|
package/src/index.d.ts
CHANGED
|
@@ -11,7 +11,7 @@ export type ProtocolRoleName = "handle" | "dial" | "both";
|
|
|
11
11
|
|
|
12
12
|
export interface PayloadTypeRef {
|
|
13
13
|
schemaName?: string;
|
|
14
|
-
fileIdentifier?: string
|
|
14
|
+
fileIdentifier?: string;
|
|
15
15
|
schemaHash?: string | number[] | Uint8Array;
|
|
16
16
|
acceptsAnyFlatbuffer?: boolean;
|
|
17
17
|
wireFormat?: PayloadWireFormat;
|
|
@@ -662,12 +662,29 @@ export type {
|
|
|
662
662
|
HarnessInvokeScenario,
|
|
663
663
|
HarnessRawScenario,
|
|
664
664
|
ManifestHarnessPlan,
|
|
665
|
+
ModuleHarness,
|
|
666
|
+
ModuleHarnessRuntimeDescriptor,
|
|
667
|
+
PublicationProtectionDemoAlignedType,
|
|
668
|
+
PublicationProtectionDemoSummary,
|
|
669
|
+
PluginInvokeProcessClient,
|
|
670
|
+
PluginInvokeProcessLaunchPlan,
|
|
671
|
+
WasmEdgeRunnerBuildPlan,
|
|
665
672
|
} from "./testing/index.js";
|
|
666
673
|
|
|
667
674
|
export {
|
|
675
|
+
buildWasmEdgeEmscriptenPthreadRunner,
|
|
676
|
+
buildWasmEdgeSpawnEnv,
|
|
677
|
+
createPublicationProtectionDemoManifest,
|
|
678
|
+
createPublicationProtectionDemoSummary,
|
|
679
|
+
createModuleHarness,
|
|
680
|
+
createPluginInvokeProcessClient,
|
|
668
681
|
describeCapabilityRuntimeSurface,
|
|
669
682
|
generateManifestHarnessPlan,
|
|
670
683
|
materializeHarnessScenario,
|
|
684
|
+
resolveModuleHarnessLaunchPlan,
|
|
685
|
+
resolveWasmEdgeRunnerBuildPlan,
|
|
686
|
+
resolveWasmEdgeRunnerSourcePath,
|
|
687
|
+
resolveWasmEdgePluginLaunchPlan,
|
|
671
688
|
serializeHarnessPlan,
|
|
672
689
|
} from "./testing/index.js";
|
|
673
690
|
|
package/src/invoke/codec.js
CHANGED
|
@@ -12,7 +12,7 @@ import { InvokeSurface } from "../generated/orbpro/manifest/invoke-surface.js";
|
|
|
12
12
|
import { BufferMutability } from "../generated/orbpro/stream/buffer-mutability.js";
|
|
13
13
|
import { BufferOwnership } from "../generated/orbpro/stream/buffer-ownership.js";
|
|
14
14
|
import { FlatBufferTypeRefT } from "../generated/orbpro/stream/flat-buffer-type-ref.js";
|
|
15
|
-
import {
|
|
15
|
+
import { TypedArenaBufferT } from "../generated/orbpro/stream/typed-arena-buffer.js";
|
|
16
16
|
import { toUint8Array } from "../runtime/bufferLike.js";
|
|
17
17
|
|
|
18
18
|
function toByteBuffer(data) {
|
|
@@ -149,20 +149,22 @@ function normalizeArenaFrame(frame = {}, offset) {
|
|
|
149
149
|
|
|
150
150
|
function packArenaFrames(frames = []) {
|
|
151
151
|
const packedFrames = [];
|
|
152
|
-
const
|
|
152
|
+
const normalizedFrames = [];
|
|
153
153
|
let offset = 0;
|
|
154
154
|
for (const frame of frames) {
|
|
155
155
|
const normalized = normalizeArenaFrame(frame, offset);
|
|
156
|
-
for (let index = 0; index < normalized.padding; index += 1) {
|
|
157
|
-
arena.push(0);
|
|
158
|
-
}
|
|
159
|
-
arena.push(...normalized.payload);
|
|
160
156
|
offset = normalized.buffer.offset + normalized.buffer.size;
|
|
161
157
|
packedFrames.push(normalized.buffer);
|
|
158
|
+
normalizedFrames.push(normalized);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const arena = new Uint8Array(offset);
|
|
162
|
+
for (const normalized of normalizedFrames) {
|
|
163
|
+
arena.set(normalized.payload, normalized.buffer.offset);
|
|
162
164
|
}
|
|
163
165
|
return {
|
|
164
166
|
frames: packedFrames,
|
|
165
|
-
arena
|
|
167
|
+
arena,
|
|
166
168
|
};
|
|
167
169
|
}
|
|
168
170
|
|
package/src/manifest/typeRefs.js
CHANGED
|
@@ -1,62 +1,31 @@
|
|
|
1
1
|
function cloneSchemaHash(value) {
|
|
2
2
|
if (value instanceof Uint8Array) {
|
|
3
|
-
return
|
|
3
|
+
return new Uint8Array(value);
|
|
4
4
|
}
|
|
5
5
|
if (Array.isArray(value)) {
|
|
6
|
-
return
|
|
6
|
+
return [...value];
|
|
7
7
|
}
|
|
8
8
|
return value ?? undefined;
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
function normalizeNullableString(value) {
|
|
12
|
-
if (value === undefined || value === null) {
|
|
13
|
-
return null;
|
|
14
|
-
}
|
|
15
|
-
const normalized = String(value).trim();
|
|
16
|
-
return normalized.length > 0 ? normalized : null;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
function normalizeAlignedMetadataScalar(value) {
|
|
20
|
-
if (value === undefined || value === null || value === "") {
|
|
21
|
-
return undefined;
|
|
22
|
-
}
|
|
23
|
-
const numeric = Number(value);
|
|
24
|
-
if (Number.isFinite(numeric)) {
|
|
25
|
-
return numeric === 0 ? undefined : numeric;
|
|
26
|
-
}
|
|
27
|
-
return value;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
11
|
export function clonePayloadTypeRef(value = null) {
|
|
31
12
|
if (!value || typeof value !== "object") {
|
|
32
|
-
return { acceptsAnyFlatbuffer: true
|
|
13
|
+
return { acceptsAnyFlatbuffer: true };
|
|
33
14
|
}
|
|
34
15
|
return {
|
|
35
|
-
schemaName:
|
|
36
|
-
|
|
37
|
-
),
|
|
38
|
-
fileIdentifier: normalizeNullableString(
|
|
39
|
-
value.fileIdentifier ?? value.file_identifier,
|
|
40
|
-
),
|
|
16
|
+
schemaName: value.schemaName ?? value.schema_name ?? undefined,
|
|
17
|
+
fileIdentifier: value.fileIdentifier ?? value.file_identifier ?? undefined,
|
|
41
18
|
schemaHash: cloneSchemaHash(value.schemaHash ?? value.schema_hash),
|
|
42
19
|
acceptsAnyFlatbuffer: Boolean(
|
|
43
20
|
value.acceptsAnyFlatbuffer ?? value.accepts_any_flatbuffer ?? false,
|
|
44
21
|
),
|
|
45
|
-
wireFormat:
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
value.fixedStringLength ?? value.fixed_string_length,
|
|
53
|
-
),
|
|
54
|
-
byteLength: normalizeAlignedMetadataScalar(
|
|
55
|
-
value.byteLength ?? value.byte_length,
|
|
56
|
-
),
|
|
57
|
-
requiredAlignment: normalizeAlignedMetadataScalar(
|
|
58
|
-
value.requiredAlignment ?? value.required_alignment,
|
|
59
|
-
),
|
|
22
|
+
wireFormat: value.wireFormat ?? value.wire_format ?? undefined,
|
|
23
|
+
rootTypeName: value.rootTypeName ?? value.root_type_name ?? undefined,
|
|
24
|
+
fixedStringLength:
|
|
25
|
+
value.fixedStringLength ?? value.fixed_string_length ?? undefined,
|
|
26
|
+
byteLength: value.byteLength ?? value.byte_length ?? undefined,
|
|
27
|
+
requiredAlignment:
|
|
28
|
+
value.requiredAlignment ?? value.required_alignment ?? undefined,
|
|
60
29
|
};
|
|
61
30
|
}
|
|
62
31
|
|
|
@@ -64,12 +33,6 @@ export function normalizePayloadWireFormatName(value) {
|
|
|
64
33
|
if (value === undefined || value === null || value === "") {
|
|
65
34
|
return null;
|
|
66
35
|
}
|
|
67
|
-
if (value === 1 || value === "1") {
|
|
68
|
-
return "aligned-binary";
|
|
69
|
-
}
|
|
70
|
-
if (value === 0 || value === "0") {
|
|
71
|
-
return "flatbuffer";
|
|
72
|
-
}
|
|
73
36
|
const normalized = String(value).trim().toLowerCase().replace(/_/g, "-");
|
|
74
37
|
if (normalized === "aligned-binary") {
|
|
75
38
|
return "aligned-binary";
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import { accessSync, existsSync } from "node:fs";
|
|
2
|
+
import { execFile } from "node:child_process";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import process from "node:process";
|
|
5
|
+
import { promisify } from "node:util";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
|
|
8
|
+
const execFileAsync = promisify(execFile);
|
|
9
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
10
|
+
const __dirname = path.dirname(__filename);
|
|
11
|
+
|
|
12
|
+
function normalizeResolvedPath(value) {
|
|
13
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
return path.resolve(value);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function ensureReadableSync(targetPath, label) {
|
|
20
|
+
try {
|
|
21
|
+
accessSync(targetPath);
|
|
22
|
+
} catch {
|
|
23
|
+
throw new Error(`${label} not found: ${targetPath}`);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function resolveWasmEdgeSharedLibraryFilename() {
|
|
28
|
+
if (process.platform === "darwin") {
|
|
29
|
+
return "libwasmedge.0.dylib";
|
|
30
|
+
}
|
|
31
|
+
if (process.platform === "win32") {
|
|
32
|
+
return "wasmedge.dll";
|
|
33
|
+
}
|
|
34
|
+
return "libwasmedge.so.0";
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function resolveGeneratedIncludeDir(requestedIncludeDir) {
|
|
38
|
+
const directHeaderPath = path.join(
|
|
39
|
+
requestedIncludeDir,
|
|
40
|
+
"wasmedge",
|
|
41
|
+
"enum_configure.h",
|
|
42
|
+
);
|
|
43
|
+
if (existsSync(directHeaderPath)) {
|
|
44
|
+
return requestedIncludeDir;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const repoRoot = path.resolve(requestedIncludeDir, "..", "..");
|
|
48
|
+
const generatedDir = path.join(repoRoot, "build", "include", "api");
|
|
49
|
+
if (existsSync(path.join(generatedDir, "wasmedge", "enum_configure.h"))) {
|
|
50
|
+
return generatedDir;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
throw new Error(
|
|
54
|
+
`WasmEdge include directory does not contain wasmedge/enum_configure.h: ${requestedIncludeDir}`,
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function resolveWasmEdgeRunnerSourcePath() {
|
|
59
|
+
return path.resolve(
|
|
60
|
+
__dirname,
|
|
61
|
+
"native",
|
|
62
|
+
"wasmedge_emscripten_pthread_runner.c",
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function resolveWasmEdgeRunnerBuildPlan(options = {}) {
|
|
67
|
+
const requestedIncludeDir = normalizeResolvedPath(
|
|
68
|
+
options.wasmedgeIncludeDir ?? process.env.WASMEDGE_INCLUDE_DIR,
|
|
69
|
+
);
|
|
70
|
+
const wasmedgeLibDir = normalizeResolvedPath(
|
|
71
|
+
options.wasmedgeLibDir ?? process.env.WASMEDGE_LIB_DIR,
|
|
72
|
+
);
|
|
73
|
+
const outputPath = normalizeResolvedPath(
|
|
74
|
+
options.outputPath ?? options.output,
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
if (!requestedIncludeDir) {
|
|
78
|
+
throw new Error(
|
|
79
|
+
"Missing WasmEdge include directory. Set wasmedgeIncludeDir, --wasmedge-include-dir, or WASMEDGE_INCLUDE_DIR.",
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
if (!wasmedgeLibDir) {
|
|
83
|
+
throw new Error(
|
|
84
|
+
"Missing WasmEdge library directory. Set wasmedgeLibDir, --wasmedge-lib-dir, or WASMEDGE_LIB_DIR.",
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
if (!outputPath) {
|
|
88
|
+
throw new Error("Missing runner outputPath.");
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const runnerSourcePath = resolveWasmEdgeRunnerSourcePath();
|
|
92
|
+
const wasmedgeSharedLibraryPath = path.join(
|
|
93
|
+
wasmedgeLibDir,
|
|
94
|
+
resolveWasmEdgeSharedLibraryFilename(),
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
return {
|
|
98
|
+
runnerSourcePath,
|
|
99
|
+
requestedIncludeDir,
|
|
100
|
+
wasmedgeIncludeDir: requestedIncludeDir,
|
|
101
|
+
wasmedgeLibDir,
|
|
102
|
+
wasmedgeSharedLibraryPath,
|
|
103
|
+
outputPath,
|
|
104
|
+
compilerCommand:
|
|
105
|
+
process.platform === "darwin" ? "xcrun" : process.env.CC ?? "cc",
|
|
106
|
+
compilerArgs:
|
|
107
|
+
process.platform === "darwin"
|
|
108
|
+
? [
|
|
109
|
+
"clang",
|
|
110
|
+
runnerSourcePath,
|
|
111
|
+
"-std=c11",
|
|
112
|
+
"-O2",
|
|
113
|
+
"-pthread",
|
|
114
|
+
"-Wall",
|
|
115
|
+
"-Wextra",
|
|
116
|
+
"-Werror",
|
|
117
|
+
"-Wno-unused-parameter",
|
|
118
|
+
"-Wno-error=visibility",
|
|
119
|
+
`-I${requestedIncludeDir}`,
|
|
120
|
+
`-L${wasmedgeLibDir}`,
|
|
121
|
+
"-lwasmedge",
|
|
122
|
+
`-Wl,-rpath,${wasmedgeLibDir}`,
|
|
123
|
+
"-o",
|
|
124
|
+
outputPath,
|
|
125
|
+
]
|
|
126
|
+
: [
|
|
127
|
+
runnerSourcePath,
|
|
128
|
+
"-std=c11",
|
|
129
|
+
"-O2",
|
|
130
|
+
"-pthread",
|
|
131
|
+
"-Wall",
|
|
132
|
+
"-Wextra",
|
|
133
|
+
"-Werror",
|
|
134
|
+
"-Wno-unused-parameter",
|
|
135
|
+
"-Wno-error=visibility",
|
|
136
|
+
`-I${requestedIncludeDir}`,
|
|
137
|
+
`-L${wasmedgeLibDir}`,
|
|
138
|
+
"-lwasmedge",
|
|
139
|
+
`-Wl,-rpath,${wasmedgeLibDir}`,
|
|
140
|
+
"-o",
|
|
141
|
+
outputPath,
|
|
142
|
+
],
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export async function buildWasmEdgeEmscriptenPthreadRunner(options = {}) {
|
|
147
|
+
const basePlan = resolveWasmEdgeRunnerBuildPlan(options);
|
|
148
|
+
const wasmedgeIncludeDir = resolveGeneratedIncludeDir(
|
|
149
|
+
basePlan.requestedIncludeDir,
|
|
150
|
+
);
|
|
151
|
+
const compilerArgs =
|
|
152
|
+
process.platform === "darwin"
|
|
153
|
+
? [
|
|
154
|
+
"clang",
|
|
155
|
+
basePlan.runnerSourcePath,
|
|
156
|
+
"-std=c11",
|
|
157
|
+
"-O2",
|
|
158
|
+
"-pthread",
|
|
159
|
+
"-Wall",
|
|
160
|
+
"-Wextra",
|
|
161
|
+
"-Werror",
|
|
162
|
+
"-Wno-unused-parameter",
|
|
163
|
+
"-Wno-error=visibility",
|
|
164
|
+
`-I${wasmedgeIncludeDir}`,
|
|
165
|
+
`-L${basePlan.wasmedgeLibDir}`,
|
|
166
|
+
"-lwasmedge",
|
|
167
|
+
`-Wl,-rpath,${basePlan.wasmedgeLibDir}`,
|
|
168
|
+
"-o",
|
|
169
|
+
basePlan.outputPath,
|
|
170
|
+
]
|
|
171
|
+
: [
|
|
172
|
+
basePlan.runnerSourcePath,
|
|
173
|
+
"-std=c11",
|
|
174
|
+
"-O2",
|
|
175
|
+
"-pthread",
|
|
176
|
+
"-Wall",
|
|
177
|
+
"-Wextra",
|
|
178
|
+
"-Werror",
|
|
179
|
+
"-Wno-unused-parameter",
|
|
180
|
+
"-Wno-error=visibility",
|
|
181
|
+
`-I${wasmedgeIncludeDir}`,
|
|
182
|
+
`-L${basePlan.wasmedgeLibDir}`,
|
|
183
|
+
"-lwasmedge",
|
|
184
|
+
`-Wl,-rpath,${basePlan.wasmedgeLibDir}`,
|
|
185
|
+
"-o",
|
|
186
|
+
basePlan.outputPath,
|
|
187
|
+
];
|
|
188
|
+
const plan = {
|
|
189
|
+
...basePlan,
|
|
190
|
+
wasmedgeIncludeDir,
|
|
191
|
+
compilerArgs,
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
ensureReadableSync(plan.runnerSourcePath, "Runner source");
|
|
195
|
+
ensureReadableSync(plan.wasmedgeIncludeDir, "WasmEdge include directory");
|
|
196
|
+
ensureReadableSync(plan.wasmedgeLibDir, "WasmEdge library directory");
|
|
197
|
+
ensureReadableSync(
|
|
198
|
+
plan.wasmedgeSharedLibraryPath,
|
|
199
|
+
"WasmEdge shared library",
|
|
200
|
+
);
|
|
201
|
+
|
|
202
|
+
await execFileAsync(plan.compilerCommand, plan.compilerArgs, {
|
|
203
|
+
cwd: options.cwd ?? process.cwd(),
|
|
204
|
+
});
|
|
205
|
+
if (process.platform === "darwin") {
|
|
206
|
+
await execFileAsync("install_name_tool", [
|
|
207
|
+
"-change",
|
|
208
|
+
"@rpath/libwasmedge.0.dylib",
|
|
209
|
+
plan.wasmedgeSharedLibraryPath,
|
|
210
|
+
plan.outputPath,
|
|
211
|
+
]);
|
|
212
|
+
}
|
|
213
|
+
return plan.outputPath;
|
|
214
|
+
}
|