crossbind 0.0.1 → 2.0.0-beta.50
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 +92 -0
- package/CHANGELOG.md +145 -0
- package/LICENSE +21 -0
- package/README.md +133 -0
- package/package.json +52 -4
- package/src/actions/buildBinTools.js +0 -0
- package/src/actions/buildCargo.js +88 -0
- package/src/actions/buildDependencies.js +157 -0
- package/src/actions/buildExternal.js +57 -0
- package/src/actions/buildJs.js +93 -0
- package/src/actions/buildLib.js +115 -0
- package/src/actions/buildTypes.js +33 -0
- package/src/actions/buildWasiCommand.js +101 -0
- package/src/actions/buildWasm.js +322 -0
- package/src/actions/createInterface.js +172 -0
- package/src/actions/createLib.js +249 -0
- package/src/actions/createXCFramework.js +82 -0
- package/src/actions/extensions.js +7 -0
- package/src/actions/getAllBridges.js +20 -0
- package/src/actions/getCmakeParameters.js +66 -0
- package/src/actions/getData.js +46 -0
- package/src/actions/getDependLibs.js +25 -0
- package/src/actions/isSourceNewer.js +35 -0
- package/src/actions/licenses.js +156 -0
- package/src/actions/run.js +305 -0
- package/src/actions/target.js +54 -0
- package/src/assets/cmake/CMakeLists.txt +61 -0
- package/src/assets/cmake/dist.cmake +57 -0
- package/src/assets/cmake/ios.toolchain.cmake +1176 -0
- package/src/assets/cpp-runtime/browser.cpp +12 -0
- package/src/assets/cpp-runtime/commonBridges.cpp +17 -0
- package/src/assets/cpp-runtime/crossbindEmptySource.cpp +9 -0
- package/src/assets/cpp-runtime/node.cpp +19 -0
- package/src/assets/js-runtime/adapters/exception-decode.js +79 -0
- package/src/assets/js-runtime/adapters/fs-browser.js +173 -0
- package/src/assets/js-runtime/adapters/fs-node.js +28 -0
- package/src/assets/js-runtime/adapters/path-fs.js +6 -0
- package/src/assets/js-runtime/adapters/path-url.js +8 -0
- package/src/assets/js-runtime/adapters/vector-coercion.js +209 -0
- package/src/assets/js-runtime/adapters/worker-comlink.js +309 -0
- package/src/assets/js-runtime/browser.js +16 -0
- package/src/assets/js-runtime/core.js +193 -0
- package/src/assets/js-runtime/edge.js +18 -0
- package/src/assets/js-runtime/node.js +20 -0
- package/src/assets/packaging/crossbind-package.podspec +17 -0
- package/src/assets/wasi-runtime/stubs.c +84 -0
- package/src/bin.js +416 -0
- package/src/index.js +19 -0
- package/src/integration/getCrossbindScript.js +180 -0
- package/src/integration/getDependFilePath.js +132 -0
- package/src/runtime/wasiRun.mjs +118 -0
- package/src/state/calculateDependencyParameters.js +97 -0
- package/src/state/index.js +128 -0
- package/src/state/loadConfig.js +215 -0
- package/src/utils/appRustCrates.js +106 -0
- package/src/utils/binTools.js +76 -0
- package/src/utils/bridgeAsyncGuard.js +37 -0
- package/src/utils/cargoTarget.js +15 -0
- package/src/utils/cppDts.js +201 -0
- package/src/utils/cppFieldBindings.js +23 -0
- package/src/utils/dependencyRebuild.js +99 -0
- package/src/utils/dirLock.js +71 -0
- package/src/utils/downloadAndExtractFile.js +102 -0
- package/src/utils/familyManifest.js +17 -0
- package/src/utils/findFiles.js +5 -0
- package/src/utils/fixPackageName.js +7 -0
- package/src/utils/flattenConfigForTable.js +75 -0
- package/src/utils/getAbsolutePath.js +14 -0
- package/src/utils/getCMakeListsFilePath.js +17 -0
- package/src/utils/getOsUserAndGroupId.js +11 -0
- package/src/utils/getParentPath.js +12 -0
- package/src/utils/hash.js +33 -0
- package/src/utils/inputStamp.js +28 -0
- package/src/utils/licenseReport.js +94 -0
- package/src/utils/linkLayout.js +34 -0
- package/src/utils/loadJs.js +31 -0
- package/src/utils/loadJson.js +16 -0
- package/src/utils/logger.js +110 -0
- package/src/utils/ociArtifact.js +124 -0
- package/src/utils/overrideDependency.js +99 -0
- package/src/utils/provenance.js +54 -0
- package/src/utils/pullDockerImage.js +105 -0
- package/src/utils/replaceBasePathForDocker.js +18 -0
- package/src/utils/resolveEmbindRust.js +66 -0
- package/src/utils/runCargo.js +228 -0
- package/src/utils/rustBridgeGen.js +1013 -0
- package/src/utils/rustMt.js +76 -0
- package/src/utils/rustSysroot.js +171 -0
- package/src/utils/rustSysrootPin.js +11 -0
- package/src/utils/systemKeys.js +29 -0
- package/src/utils/targets.js +109 -0
- package/src/utils/wasiToolchain.js +47 -0
- package/src/utils/writeIfChanged.js +8 -0
- package/src/utils/writeJson.js +9 -0
- package/vitest.config.js +40 -0
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
/* global WorkerGlobalScope */
|
|
2
|
+
import * as Comlink from 'comlink';
|
|
3
|
+
import { mergeDeep } from '../core.js';
|
|
4
|
+
import {
|
|
5
|
+
callWithVectorCoercion, wrapWithVectorCoercion, setCoercionModule, unwrapCoercionProxy,
|
|
6
|
+
} from './vector-coercion.js';
|
|
7
|
+
import { patchModuleForExceptionDecode } from './exception-decode.js';
|
|
8
|
+
|
|
9
|
+
const isWorkerScope = typeof WorkerGlobalScope !== 'undefined'
|
|
10
|
+
&& typeof self !== 'undefined'
|
|
11
|
+
&& self instanceof WorkerGlobalScope;
|
|
12
|
+
|
|
13
|
+
// === Embind <-> Comlink Bridge ===
|
|
14
|
+
// Worker-side registry: id -> original embind object
|
|
15
|
+
// Main-thread registry: Comlink proxy -> id
|
|
16
|
+
const embindRegistry = new Map();
|
|
17
|
+
const embindProxyIds = new WeakMap();
|
|
18
|
+
let nextEmbindId = 1;
|
|
19
|
+
|
|
20
|
+
function registerEmbindObject(obj) {
|
|
21
|
+
const id = nextEmbindId++;
|
|
22
|
+
embindRegistry.set(id, obj);
|
|
23
|
+
return id;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Reorder transfer handlers for correct priority
|
|
27
|
+
const _proxyHandler = Comlink.transferHandlers.get('proxy');
|
|
28
|
+
const _throwHandler = Comlink.transferHandlers.get('throw');
|
|
29
|
+
Comlink.transferHandlers.clear();
|
|
30
|
+
|
|
31
|
+
// 1. embindProxy: when a proxied embind object is sent back as an argument,
|
|
32
|
+
// resolve it to the original object on the worker instead of creating a proxy-of-proxy
|
|
33
|
+
Comlink.transferHandlers.set('embindProxy', {
|
|
34
|
+
canHandle(obj) {
|
|
35
|
+
return embindProxyIds.has(obj);
|
|
36
|
+
},
|
|
37
|
+
serialize(obj) {
|
|
38
|
+
return [embindProxyIds.get(obj), []];
|
|
39
|
+
},
|
|
40
|
+
deserialize(id) {
|
|
41
|
+
return embindRegistry.get(id);
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
// 2. proxy (modified): also registers embind objects created via CONSTRUCT
|
|
46
|
+
Comlink.transferHandlers.set('proxy', {
|
|
47
|
+
canHandle: _proxyHandler.canHandle,
|
|
48
|
+
serialize(obj) {
|
|
49
|
+
if (typeof obj.delete === 'function' && typeof obj.isDeleted === 'function') {
|
|
50
|
+
// CONSTRUCT results (Comlink marks them with proxy()) are exposed through
|
|
51
|
+
// the same coercion wrapper as method-returned objects (embindObject
|
|
52
|
+
// handler), and the registry keeps the RAW object so arguments resolve
|
|
53
|
+
// back to real embind identities.
|
|
54
|
+
const raw = unwrapCoercionProxy(obj);
|
|
55
|
+
const [port, transferables] = _proxyHandler.serialize(wrapWithVectorCoercion(raw));
|
|
56
|
+
const id = registerEmbindObject(raw);
|
|
57
|
+
return [{ __embindId: id, __port: port }, transferables];
|
|
58
|
+
}
|
|
59
|
+
return _proxyHandler.serialize(obj);
|
|
60
|
+
},
|
|
61
|
+
deserialize(data) {
|
|
62
|
+
if (data != null && typeof data === 'object' && '__embindId' in data) {
|
|
63
|
+
const proxy = _proxyHandler.deserialize(data.__port);
|
|
64
|
+
embindProxyIds.set(proxy, data.__embindId);
|
|
65
|
+
return proxy;
|
|
66
|
+
}
|
|
67
|
+
return _proxyHandler.deserialize(data);
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
// 3. throw (restored)
|
|
72
|
+
Comlink.transferHandlers.set('throw', _throwHandler);
|
|
73
|
+
|
|
74
|
+
// 4. embindVector: convert embind vectors to arrays across worker boundary
|
|
75
|
+
Comlink.transferHandlers.set('embindVector', {
|
|
76
|
+
canHandle(obj) {
|
|
77
|
+
return obj != null
|
|
78
|
+
&& typeof obj === 'object'
|
|
79
|
+
&& typeof obj.size === 'function'
|
|
80
|
+
&& typeof obj.get === 'function'
|
|
81
|
+
&& typeof obj.delete === 'function';
|
|
82
|
+
},
|
|
83
|
+
serialize(obj) {
|
|
84
|
+
const len = obj.size();
|
|
85
|
+
const elements = new Array(len);
|
|
86
|
+
let hasObjects = false;
|
|
87
|
+
for (let i = 0; i < len; i++) {
|
|
88
|
+
const elem = obj.get(i);
|
|
89
|
+
elements[i] = elem;
|
|
90
|
+
if (!hasObjects && elem !== null && typeof elem === 'object') {
|
|
91
|
+
hasObjects = true;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
if (!hasObjects) {
|
|
95
|
+
return [elements, []];
|
|
96
|
+
}
|
|
97
|
+
const transferables = [];
|
|
98
|
+
for (let i = 0; i < len; i++) {
|
|
99
|
+
const elem = elements[i];
|
|
100
|
+
if (elem !== null && typeof elem === 'object') {
|
|
101
|
+
const id = registerEmbindObject(elem);
|
|
102
|
+
const { port1, port2 } = new MessageChannel();
|
|
103
|
+
Comlink.expose(wrapWithVectorCoercion(elem), port1);
|
|
104
|
+
transferables.push(port2);
|
|
105
|
+
elements[i] = { __comlinkProxy: true, __embindId: id, port: port2 };
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return [elements, transferables];
|
|
109
|
+
},
|
|
110
|
+
deserialize(elements) {
|
|
111
|
+
return elements.map((elem) => {
|
|
112
|
+
if (elem && typeof elem === 'object' && elem.__comlinkProxy) {
|
|
113
|
+
elem.port.start();
|
|
114
|
+
const proxy = Comlink.wrap(elem.port);
|
|
115
|
+
embindProxyIds.set(proxy, elem.__embindId);
|
|
116
|
+
return proxy;
|
|
117
|
+
}
|
|
118
|
+
return elem;
|
|
119
|
+
});
|
|
120
|
+
},
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
// 4b. embindEnum: embind enum VALUES are class instances (not structured-cloneable) with
|
|
124
|
+
// singleton identity per enumerator. Worker side registers each instance under an id; the
|
|
125
|
+
// main thread gets a frozen, identity-stable token per id, and a token passed back as an
|
|
126
|
+
// argument resolves to the original instance - so `===` survives both directions.
|
|
127
|
+
const enumValueSet = new WeakSet();
|
|
128
|
+
const enumInstanceIds = new WeakMap();
|
|
129
|
+
const enumInstancesById = new Map();
|
|
130
|
+
const enumTokensById = new Map();
|
|
131
|
+
let nextEnumId = 1;
|
|
132
|
+
|
|
133
|
+
// Worker side, at module-ready: collect enum value instances. embind hangs enumerators off
|
|
134
|
+
// the enum TYPE, which is a FUNCTION (plus a `values` meta map), so both function and
|
|
135
|
+
// object holders are walked; a value qualifies when it is an instance (non-Object
|
|
136
|
+
// constructor) carrying a numeric .value and no lifecycle methods - which excludes HEAP
|
|
137
|
+
// views, FS, class statics and config bags.
|
|
138
|
+
export function registerModuleEnums(m) {
|
|
139
|
+
for (const key of Object.keys(m)) {
|
|
140
|
+
const holder = m[key];
|
|
141
|
+
if (holder == null || (typeof holder !== 'object' && typeof holder !== 'function')
|
|
142
|
+
|| ArrayBuffer.isView(holder)) continue;
|
|
143
|
+
for (const v of Object.values(holder)) {
|
|
144
|
+
if (v != null && typeof v === 'object'
|
|
145
|
+
&& typeof v.value === 'number'
|
|
146
|
+
&& typeof v.delete !== 'function'
|
|
147
|
+
&& v.constructor && v.constructor !== Object) {
|
|
148
|
+
enumValueSet.add(v);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
Comlink.transferHandlers.set('embindEnum', {
|
|
155
|
+
canHandle(obj) {
|
|
156
|
+
if (obj == null || typeof obj !== 'object') return false;
|
|
157
|
+
return enumValueSet.has(unwrapCoercionProxy(obj)) || '__embindEnumRef' in obj;
|
|
158
|
+
},
|
|
159
|
+
serialize(obj) {
|
|
160
|
+
const raw = unwrapCoercionProxy(obj);
|
|
161
|
+
if (enumValueSet.has(raw)) {
|
|
162
|
+
let id = enumInstanceIds.get(raw);
|
|
163
|
+
if (!id) {
|
|
164
|
+
id = nextEnumId;
|
|
165
|
+
nextEnumId += 1;
|
|
166
|
+
enumInstanceIds.set(raw, id);
|
|
167
|
+
enumInstancesById.set(id, raw);
|
|
168
|
+
}
|
|
169
|
+
return [{ __embindEnumRef: id, value: raw.value }, []];
|
|
170
|
+
}
|
|
171
|
+
return [{ __embindEnumRef: raw.__embindEnumRef, value: raw.value }, []];
|
|
172
|
+
},
|
|
173
|
+
deserialize(data) {
|
|
174
|
+
const real = enumInstancesById.get(data.__embindEnumRef);
|
|
175
|
+
if (real) return real;
|
|
176
|
+
let token = enumTokensById.get(data.__embindEnumRef);
|
|
177
|
+
if (!token) {
|
|
178
|
+
token = Object.freeze({ __embindEnumRef: data.__embindEnumRef, value: data.value });
|
|
179
|
+
enumTokensById.set(data.__embindEnumRef, token);
|
|
180
|
+
}
|
|
181
|
+
return token;
|
|
182
|
+
},
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
// 5. embindObject: proxy other embind objects (Dataset, etc.)
|
|
186
|
+
Comlink.transferHandlers.set('embindObject', {
|
|
187
|
+
canHandle(obj) {
|
|
188
|
+
return obj != null
|
|
189
|
+
&& typeof obj === 'object'
|
|
190
|
+
&& typeof obj.delete === 'function'
|
|
191
|
+
&& typeof obj.isDeleted === 'function';
|
|
192
|
+
},
|
|
193
|
+
serialize(obj) {
|
|
194
|
+
const id = registerEmbindObject(obj);
|
|
195
|
+
const { port1, port2 } = new MessageChannel();
|
|
196
|
+
Comlink.expose(wrapWithVectorCoercion(obj), port1);
|
|
197
|
+
return [{ __embindId: id, port: port2 }, [port2]];
|
|
198
|
+
},
|
|
199
|
+
deserialize(data) {
|
|
200
|
+
data.port.start();
|
|
201
|
+
const proxy = Comlink.wrap(data.port);
|
|
202
|
+
embindProxyIds.set(proxy, data.__embindId);
|
|
203
|
+
return proxy;
|
|
204
|
+
},
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
// 6. embindProxyArray: a plain JS array that contains proxied embind objects
|
|
208
|
+
// can't be structured-cloned across the
|
|
209
|
+
// worker boundary, because the element proxies are functions. Resolve each
|
|
210
|
+
// registered proxy element back to its worker-side original, the same way the
|
|
211
|
+
// embindProxy handler does for a single argument.
|
|
212
|
+
Comlink.transferHandlers.set('embindProxyArray', {
|
|
213
|
+
canHandle(obj) {
|
|
214
|
+
return Array.isArray(obj) && obj.some((e) => embindProxyIds.has(e));
|
|
215
|
+
},
|
|
216
|
+
serialize(arr) {
|
|
217
|
+
return [
|
|
218
|
+
arr.map((e) => (embindProxyIds.has(e) ? { __embindRef: embindProxyIds.get(e) } : e)),
|
|
219
|
+
[],
|
|
220
|
+
];
|
|
221
|
+
},
|
|
222
|
+
deserialize(arr) {
|
|
223
|
+
return arr.map((e) => (e != null && typeof e === 'object' && '__embindRef' in e
|
|
224
|
+
? embindRegistry.get(e.__embindRef)
|
|
225
|
+
: e));
|
|
226
|
+
},
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
let _worker = null;
|
|
230
|
+
|
|
231
|
+
function resolveScriptUrl(config) {
|
|
232
|
+
const fileName = config.paths.js || config.paths.worker;
|
|
233
|
+
let prefix = '';
|
|
234
|
+
if (config.path) {
|
|
235
|
+
prefix = config.path;
|
|
236
|
+
if (prefix.slice(-1) !== '/') prefix += '/';
|
|
237
|
+
}
|
|
238
|
+
let output = prefix + fileName;
|
|
239
|
+
if (output.substring(0, 4) !== 'http' && output[0] !== '/') output = `/${output}`;
|
|
240
|
+
return output;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function exposeWorker(systemConfig, createModule) {
|
|
244
|
+
const workerApi = {
|
|
245
|
+
async init(userConfig = {}) {
|
|
246
|
+
const config = mergeDeep(systemConfig, userConfig);
|
|
247
|
+
const m = await createModule(config);
|
|
248
|
+
setCoercionModule(m);
|
|
249
|
+
// Decode on the worker side: a real Error (with the C++ message) survives the
|
|
250
|
+
// comlink throw handler, a raw WebAssembly.Exception does not.
|
|
251
|
+
patchModuleForExceptionDecode(m);
|
|
252
|
+
registerModuleEnums(m);
|
|
253
|
+
return Comlink.proxy(wrapWithVectorCoercion(m));
|
|
254
|
+
},
|
|
255
|
+
};
|
|
256
|
+
Comlink.expose(workerApi);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
async function initWithWorker(config, userConfig) {
|
|
260
|
+
const scriptUrl = config.workerUrl || resolveScriptUrl(config);
|
|
261
|
+
_worker = new Worker(scriptUrl);
|
|
262
|
+
const workerApi = Comlink.wrap(_worker);
|
|
263
|
+
|
|
264
|
+
const {
|
|
265
|
+
logHandler, errorHandler, onRuntimeInitialized, getWasmFunction, useWorker, workerUrl,
|
|
266
|
+
...serializableConfig
|
|
267
|
+
} = userConfig;
|
|
268
|
+
const module = await workerApi.init(serializableConfig);
|
|
269
|
+
|
|
270
|
+
return new Proxy(module, {
|
|
271
|
+
get(target, prop) {
|
|
272
|
+
if (prop === 'toArray') {
|
|
273
|
+
return function toArray(vector) {
|
|
274
|
+
if (Array.isArray(vector)) return vector;
|
|
275
|
+
return target.toArray(vector);
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
if (prop === 'toVector') {
|
|
279
|
+
return function toVector(classOrName, array = []) {
|
|
280
|
+
if (typeof classOrName === 'string') {
|
|
281
|
+
return target._createVector(classOrName, array);
|
|
282
|
+
}
|
|
283
|
+
return target.toVector(classOrName, array);
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
return target[prop];
|
|
287
|
+
},
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function terminate() {
|
|
292
|
+
if (_worker) {
|
|
293
|
+
_worker.terminate();
|
|
294
|
+
_worker = null;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
export default {
|
|
299
|
+
isWorkerScope,
|
|
300
|
+
exposeWorker,
|
|
301
|
+
initWithWorker,
|
|
302
|
+
terminate,
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
export {
|
|
306
|
+
isWorkerScope, exposeWorker, initWithWorker, terminate,
|
|
307
|
+
// exported for unit tests
|
|
308
|
+
callWithVectorCoercion, wrapWithVectorCoercion, setCoercionModule,
|
|
309
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import Module from 'crossbind/module';
|
|
2
|
+
import systemConfig from 'crossbind/systemConfig';
|
|
3
|
+
|
|
4
|
+
import { createInitCrossbind, composeAdapters } from './core.js';
|
|
5
|
+
import urlPath from './adapters/path-url.js';
|
|
6
|
+
import browserFs from './adapters/fs-browser.js';
|
|
7
|
+
import workerComlink from './adapters/worker-comlink.js';
|
|
8
|
+
|
|
9
|
+
const adapter = composeAdapters([urlPath, browserFs]);
|
|
10
|
+
|
|
11
|
+
export default createInitCrossbind({
|
|
12
|
+
Module,
|
|
13
|
+
systemConfig,
|
|
14
|
+
adapter,
|
|
15
|
+
worker: workerComlink,
|
|
16
|
+
});
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { setCoercionModule, wrapModuleForCoercion } from './adapters/vector-coercion.js';
|
|
2
|
+
import { patchModuleForExceptionDecode } from './adapters/exception-decode.js';
|
|
3
|
+
|
|
4
|
+
export function isObject(item) {
|
|
5
|
+
return (item && typeof item === 'object' && !Array.isArray(item));
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function mergeDeep(target, ...sources) {
|
|
9
|
+
if (!sources.length) return target;
|
|
10
|
+
const source = sources.shift();
|
|
11
|
+
|
|
12
|
+
if (isObject(target) && isObject(source)) {
|
|
13
|
+
for (const key in source) {
|
|
14
|
+
if (isObject(source[key])) {
|
|
15
|
+
if (!target[key]) Object.assign(target, { [key]: {} });
|
|
16
|
+
mergeDeep(target[key], source[key]);
|
|
17
|
+
} else {
|
|
18
|
+
Object.assign(target, { [key]: source[key] });
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return mergeDeep(target, ...sources);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function composeAdapters(adapters) {
|
|
27
|
+
const composed = {};
|
|
28
|
+
const extendFns = [];
|
|
29
|
+
const readyFns = [];
|
|
30
|
+
for (const a of adapters) {
|
|
31
|
+
if (!a) continue;
|
|
32
|
+
if (a.getDefaultPathPrefix) composed.getDefaultPathPrefix = a.getDefaultPathPrefix;
|
|
33
|
+
if (a.getDataPath) composed.getDataPath = a.getDataPath;
|
|
34
|
+
if (a.finalizePath) composed.finalizePath = a.finalizePath;
|
|
35
|
+
if (a.extendModule) extendFns.push(a.extendModule);
|
|
36
|
+
if (a.onModuleReady) readyFns.push(a.onModuleReady);
|
|
37
|
+
}
|
|
38
|
+
if (extendFns.length) {
|
|
39
|
+
composed.extendModule = (m, config) => extendFns.forEach((fn) => fn(m, config));
|
|
40
|
+
}
|
|
41
|
+
if (readyFns.length) {
|
|
42
|
+
composed.onModuleReady = async (m, config) => {
|
|
43
|
+
for (const fn of readyFns) {
|
|
44
|
+
await fn(m, config);
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
return composed;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function buildLocateFile(config, adapter) {
|
|
52
|
+
return function locateFile(fileName) {
|
|
53
|
+
let path = fileName;
|
|
54
|
+
if (config.paths && config.paths.wasm && fileName.endsWith('.wasm')) {
|
|
55
|
+
path = config.paths.wasm;
|
|
56
|
+
} else if (config.paths && config.paths.data && (fileName.endsWith('.data.txt') || fileName.endsWith('.data'))) {
|
|
57
|
+
path = config.paths.data;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
let prefix = '';
|
|
61
|
+
if (config.path) {
|
|
62
|
+
prefix = config.path;
|
|
63
|
+
if (prefix.slice(-1) !== '/') prefix += '/';
|
|
64
|
+
} else if (adapter.getDefaultPathPrefix) {
|
|
65
|
+
prefix = adapter.getDefaultPathPrefix();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
let output = prefix + path;
|
|
69
|
+
if (output.endsWith('.data')) output += '.txt';
|
|
70
|
+
if (adapter.finalizePath) output = adapter.finalizePath(output);
|
|
71
|
+
|
|
72
|
+
return output;
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function createBaseModule(Module, config, adapter) {
|
|
77
|
+
return new Promise((resolve, reject) => {
|
|
78
|
+
const locateFile = buildLocateFile(config, adapter);
|
|
79
|
+
const dataPath = adapter.getDataPath ? adapter.getDataPath() : '/crossbind';
|
|
80
|
+
|
|
81
|
+
const m = {
|
|
82
|
+
print(text) {
|
|
83
|
+
if (config.logHandler) {
|
|
84
|
+
config.logHandler(text, 'stdout');
|
|
85
|
+
} else {
|
|
86
|
+
console.debug(`wasm stdout: ${text}`);
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
printErr(text) {
|
|
90
|
+
if (config.errorHandler) {
|
|
91
|
+
config.errorHandler(text, 'stderr');
|
|
92
|
+
} else {
|
|
93
|
+
console.error(`wasm stderr: ${text}`);
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
locateFile,
|
|
97
|
+
preRun: [
|
|
98
|
+
({ ENV }) => {
|
|
99
|
+
if (ENV && config && config.env) {
|
|
100
|
+
Object.entries(config.env).forEach(([key, value]) => {
|
|
101
|
+
ENV[key] = value?.replace('_CROSSBIND_DATA_PATH_', dataPath);
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
],
|
|
106
|
+
unmount() {},
|
|
107
|
+
toArray(vector) {
|
|
108
|
+
if (Array.isArray(vector)) return vector;
|
|
109
|
+
const len = vector.size();
|
|
110
|
+
const output = new Array(len);
|
|
111
|
+
for (let i = 0; i < len; i += 1) {
|
|
112
|
+
output[i] = vector.get(i);
|
|
113
|
+
}
|
|
114
|
+
return output;
|
|
115
|
+
},
|
|
116
|
+
toVector(classOrName, array = []) {
|
|
117
|
+
const VectorClass = typeof classOrName === 'string' ? m[classOrName] : classOrName;
|
|
118
|
+
const vector = new VectorClass();
|
|
119
|
+
array.forEach((item) => {
|
|
120
|
+
vector.push_back(item);
|
|
121
|
+
});
|
|
122
|
+
return vector;
|
|
123
|
+
},
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
if (adapter.extendModule) adapter.extendModule(m, config);
|
|
127
|
+
|
|
128
|
+
if (config.getWasmFunction) {
|
|
129
|
+
m.instantiateWasm = function instantiateWasm(info, receive) {
|
|
130
|
+
const instance = new WebAssembly.Instance(config.getWasmFunction(), info);
|
|
131
|
+
receive(instance);
|
|
132
|
+
return instance.exports;
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// onModuleReady may need to await async setup (e.g. the OPFS preflight in
|
|
137
|
+
// fs-browser), which emscripten's onRuntimeInitialized callback cannot, so
|
|
138
|
+
// both hooks run after the factory settles - adapter first, then the user's.
|
|
139
|
+
Module(m).then(async (mod) => {
|
|
140
|
+
if (adapter.onModuleReady) await adapter.onModuleReady(mod, config);
|
|
141
|
+
if (config.onRuntimeInitialized) config.onRuntimeInitialized(mod);
|
|
142
|
+
return mod;
|
|
143
|
+
}).then(resolve).catch(reject);
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export function createInitCrossbind({
|
|
148
|
+
Module, systemConfig, adapter, worker,
|
|
149
|
+
}) {
|
|
150
|
+
let crossbindPromise;
|
|
151
|
+
|
|
152
|
+
function createModule(config) {
|
|
153
|
+
return createBaseModule(Module, config, adapter);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (worker && worker.isWorkerScope) {
|
|
157
|
+
worker.exposeWorker(systemConfig, createModule);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function initNative(userConfig = {}) {
|
|
161
|
+
if (crossbindPromise) return crossbindPromise;
|
|
162
|
+
|
|
163
|
+
const config = mergeDeep(systemConfig, userConfig);
|
|
164
|
+
|
|
165
|
+
if (worker && config.useWorker && !worker.isWorkerScope) {
|
|
166
|
+
crossbindPromise = worker.initWithWorker(config, userConfig);
|
|
167
|
+
} else {
|
|
168
|
+
// Direct (non-worker) mode: wrap the module so plain-array arguments coerce to
|
|
169
|
+
// embind vectors like they do in worker mode. Narrow wrapper — only the module's
|
|
170
|
+
// own calls, leaving HEAP*/FS/returned objects raw (see wrapModuleForCoercion).
|
|
171
|
+
crossbindPromise = createModule(config).then((m) => {
|
|
172
|
+
setCoercionModule(m);
|
|
173
|
+
// Instance/static methods stay raw in direct mode, so the decode has to
|
|
174
|
+
// land on the class prototypes themselves.
|
|
175
|
+
patchModuleForExceptionDecode(m);
|
|
176
|
+
return wrapModuleForCoercion(m);
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return crossbindPromise;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
initNative.terminate = function terminate() {
|
|
184
|
+
if (worker && worker.terminate) worker.terminate();
|
|
185
|
+
crossbindPromise = null;
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
if (typeof globalThis === 'object') {
|
|
189
|
+
globalThis.Crossbind = { initNative };
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return initNative;
|
|
193
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import Module from 'crossbind/module';
|
|
2
|
+
import systemConfig from 'crossbind/systemConfig';
|
|
3
|
+
|
|
4
|
+
import { createInitCrossbind, composeAdapters } from './core.js';
|
|
5
|
+
import pathFs from './adapters/path-fs.js';
|
|
6
|
+
|
|
7
|
+
const adapter = composeAdapters([
|
|
8
|
+
pathFs({
|
|
9
|
+
defaultPathPrefix: '',
|
|
10
|
+
dataPath: '/crossbind',
|
|
11
|
+
}),
|
|
12
|
+
]);
|
|
13
|
+
|
|
14
|
+
export default createInitCrossbind({
|
|
15
|
+
Module,
|
|
16
|
+
systemConfig,
|
|
17
|
+
adapter,
|
|
18
|
+
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import Module from 'crossbind/module';
|
|
2
|
+
import systemConfig from 'crossbind/systemConfig';
|
|
3
|
+
|
|
4
|
+
import { createInitCrossbind, composeAdapters } from './core.js';
|
|
5
|
+
import pathFs from './adapters/path-fs.js';
|
|
6
|
+
import nodeFs from './adapters/fs-node.js';
|
|
7
|
+
|
|
8
|
+
const adapter = composeAdapters([
|
|
9
|
+
pathFs({
|
|
10
|
+
defaultPathPrefix: `${__dirname}/`,
|
|
11
|
+
dataPath: `${__dirname}/data`,
|
|
12
|
+
}),
|
|
13
|
+
nodeFs,
|
|
14
|
+
]);
|
|
15
|
+
|
|
16
|
+
export default createInitCrossbind({
|
|
17
|
+
Module,
|
|
18
|
+
systemConfig,
|
|
19
|
+
adapter,
|
|
20
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
require "json"
|
|
2
|
+
package = JSON.parse(File.read(File.join(__dir__, "package.json")))
|
|
3
|
+
|
|
4
|
+
Pod::Spec.new do |s|
|
|
5
|
+
s.module_name = "___PROJECT_MODULE_NAME___"
|
|
6
|
+
s.name = "___PROJECT_NAME___"
|
|
7
|
+
s.version = package["nativeVersion"]
|
|
8
|
+
s.summary = "___PROJECT_NAME___ package"
|
|
9
|
+
s.homepage = "https://crossbind.dev"
|
|
10
|
+
s.author = "___PROJECT_NAME___ Authors"
|
|
11
|
+
s.source = { :http => "https://crossbind.dev" }
|
|
12
|
+
s.vendored_frameworks = ___PROJECT_FRAMEWORKS___
|
|
13
|
+
s.resources = ___PROJECT_RESOURCES___
|
|
14
|
+
# arm64-only iOS simulator slice; drop x86_64 to avoid linker errors on consumer apps.
|
|
15
|
+
# Cargo producers add a -force_load so their init-array constructors survive dead-strip.
|
|
16
|
+
s.user_target_xcconfig = { 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'x86_64'___PROJECT_LDFLAGS___ }
|
|
17
|
+
end
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/* Clean-failing stubs for symbols the WASI libc lacks; keeps command modules free of undefined imports. */
|
|
2
|
+
|
|
3
|
+
#include <errno.h>
|
|
4
|
+
#include <stddef.h>
|
|
5
|
+
#include <stdio.h>
|
|
6
|
+
#include <string.h>
|
|
7
|
+
|
|
8
|
+
#include <netinet/in.h>
|
|
9
|
+
#include <sys/socket.h>
|
|
10
|
+
|
|
11
|
+
/* Compiled by the C++ driver at the command link; keep C linkage. */
|
|
12
|
+
#ifdef __cplusplus
|
|
13
|
+
extern "C" {
|
|
14
|
+
#endif
|
|
15
|
+
|
|
16
|
+
/* No dynamic loading on WASI; loaders treat NULL as "plugin unavailable". */
|
|
17
|
+
void *dlopen(const char *file, int mode)
|
|
18
|
+
{
|
|
19
|
+
(void)file;
|
|
20
|
+
(void)mode;
|
|
21
|
+
return NULL;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
char *dlerror(void)
|
|
25
|
+
{
|
|
26
|
+
return (char *)"dynamic loading is not supported on WASI";
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
void *dlsym(void *handle, const char *symbol)
|
|
30
|
+
{
|
|
31
|
+
(void)handle;
|
|
32
|
+
(void)symbol;
|
|
33
|
+
return NULL;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
int dlclose(void *handle)
|
|
37
|
+
{
|
|
38
|
+
(void)handle;
|
|
39
|
+
return -1;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/* WASI has no ambient /tmp; POSIX lets tmpfile fail, callers must handle NULL (libtiff's fax2ps does). */
|
|
43
|
+
FILE *tmpfile(void)
|
|
44
|
+
{
|
|
45
|
+
errno = ENOTSUP;
|
|
46
|
+
return NULL;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/* No fork on WASI; registering handlers is safely a no-op (PROJ registers one). */
|
|
50
|
+
int pthread_atfork(void (*prepare)(void), void (*parent)(void), void (*child)(void))
|
|
51
|
+
{
|
|
52
|
+
(void)prepare;
|
|
53
|
+
(void)parent;
|
|
54
|
+
(void)child;
|
|
55
|
+
return 0;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/* wasmtime (46/47) traps in mid-connect getsockname/getpeername; zeroed shadows keep libcurl alive - drop when the runtime catches up. */
|
|
59
|
+
static int crossbind_zeroed_inet(struct sockaddr *addr, socklen_t *len)
|
|
60
|
+
{
|
|
61
|
+
struct sockaddr_in a;
|
|
62
|
+
memset(&a, 0, sizeof a);
|
|
63
|
+
a.sin_family = AF_INET;
|
|
64
|
+
socklen_t n = *len < (socklen_t)sizeof a ? *len : (socklen_t)sizeof a;
|
|
65
|
+
memcpy(addr, &a, n);
|
|
66
|
+
*len = (socklen_t)sizeof a;
|
|
67
|
+
return 0;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
int getsockname(int fd, struct sockaddr *addr, socklen_t *len)
|
|
71
|
+
{
|
|
72
|
+
(void)fd;
|
|
73
|
+
return crossbind_zeroed_inet(addr, len);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
int getpeername(int fd, struct sockaddr *addr, socklen_t *len)
|
|
77
|
+
{
|
|
78
|
+
(void)fd;
|
|
79
|
+
return crossbind_zeroed_inet(addr, len);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
#ifdef __cplusplus
|
|
83
|
+
}
|
|
84
|
+
#endif
|