crossbind 0.0.1 → 2.0.0-beta.51
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 +156 -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 +307 -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 +46 -0
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
#include <emscripten.h>
|
|
2
|
+
#include <emscripten/bind.h>
|
|
3
|
+
#include <emscripten/wasmfs.h>
|
|
4
|
+
|
|
5
|
+
void crossbind_init_opfs() {
|
|
6
|
+
backend_t opfs_backend = wasmfs_create_opfs_backend();
|
|
7
|
+
wasmfs_create_directory("/opfs", 0777, opfs_backend);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
EMSCRIPTEN_BINDINGS(Browser) {
|
|
11
|
+
emscripten::function("crossbind_init_opfs", &crossbind_init_opfs);
|
|
12
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
#include <emscripten/bind.h>
|
|
2
|
+
|
|
3
|
+
EMSCRIPTEN_BINDINGS(CommonBridges) {
|
|
4
|
+
emscripten::register_vector<char>("VectorChar");
|
|
5
|
+
emscripten::register_vector<unsigned char>("VectorUChar");
|
|
6
|
+
emscripten::register_vector<short>("VectorShort");
|
|
7
|
+
emscripten::register_vector<int>("VectorInt");
|
|
8
|
+
emscripten::register_vector<int64_t>("VectorInt64");
|
|
9
|
+
emscripten::register_vector<float>("VectorFloat");
|
|
10
|
+
emscripten::register_vector<double>("VectorDouble");
|
|
11
|
+
emscripten::register_vector<std::string>("VectorString");
|
|
12
|
+
|
|
13
|
+
emscripten::register_map<int, int>("MapIntInt");
|
|
14
|
+
emscripten::register_map<int, std::string>("MapIntString");
|
|
15
|
+
emscripten::register_map<std::string, std::string>("MapStringString");
|
|
16
|
+
emscripten::register_map<std::string, int>("MapStringInt");
|
|
17
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// Placeholder TU so the static archive always exports at least one symbol;
|
|
2
|
+
// otherwise ranlib emits "has no symbols" warnings.
|
|
3
|
+
//
|
|
4
|
+
// `inline` gives the variable C++17 vague/COMDAT linkage so the same file
|
|
5
|
+
// compiled into multiple libraries can be linked into one binary without
|
|
6
|
+
// ODR conflicts. `[[gnu::used]]` (clang/gcc) forces the compiler to emit
|
|
7
|
+
// the symbol even though nothing references it; without it, the optimizer
|
|
8
|
+
// drops the inline variable and the warning persists.
|
|
9
|
+
[[maybe_unused, gnu::used]] inline int crossbind_empty_source_marker = 0;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#include <emscripten.h>
|
|
2
|
+
#include <emscripten/wasmfs.h>
|
|
3
|
+
#include <limits.h>
|
|
4
|
+
#include <unistd.h>
|
|
5
|
+
|
|
6
|
+
backend_t wasmfs_create_root_dir(void) {
|
|
7
|
+
return wasmfs_create_node_backend("/");
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
void wasmfs_before_preload(void) {
|
|
11
|
+
backend_t mem = wasmfs_create_memory_backend();
|
|
12
|
+
wasmfs_create_directory("/memfs", 0777, mem);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
__attribute__((constructor)) static void setup_cwd(void) {
|
|
16
|
+
char cwd[PATH_MAX];
|
|
17
|
+
EM_ASM({ stringToUTF8(process.cwd(), $0, $1); }, cwd, (int)sizeof(cwd));
|
|
18
|
+
chdir(cwd);
|
|
19
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// === wasm-EH exception decoding (shared by the worker and direct runtimes) ===
|
|
2
|
+
// Builds link with -fwasm-exceptions, so an uncaught C++ exception reaches JS as a raw
|
|
3
|
+
// WebAssembly.Exception with no .message. The glue exports getExceptionMessage (via
|
|
4
|
+
// -sEXPORT_EXCEPTION_HANDLING_HELPERS), which decodes it back into [type, what()]. This
|
|
5
|
+
// adapter turns such values into real JS Errors at the binding boundary; anything else
|
|
6
|
+
// passes through untouched, so it is safe to apply on every call path.
|
|
7
|
+
let decodeModule = null;
|
|
8
|
+
|
|
9
|
+
export function setExceptionDecodeModule(m) {
|
|
10
|
+
decodeModule = m;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function decodeCppException(value) {
|
|
14
|
+
const m = decodeModule;
|
|
15
|
+
if (m == null
|
|
16
|
+
|| typeof m.getExceptionMessage !== 'function'
|
|
17
|
+
|| typeof WebAssembly === 'undefined'
|
|
18
|
+
|| typeof WebAssembly.Exception !== 'function'
|
|
19
|
+
|| !(value instanceof WebAssembly.Exception)) {
|
|
20
|
+
return value;
|
|
21
|
+
}
|
|
22
|
+
try {
|
|
23
|
+
const [type, message] = m.getExceptionMessage(value);
|
|
24
|
+
const error = new Error(message ? `${type}: ${message}` : `${type}`);
|
|
25
|
+
error.cppType = type;
|
|
26
|
+
error.cppMessage = message;
|
|
27
|
+
return error;
|
|
28
|
+
} catch (decodeFailure) {
|
|
29
|
+
return value;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const STATIC_SKIP = new Set(['prototype', 'name', 'length', 'caller', 'arguments', 'argCount']);
|
|
34
|
+
|
|
35
|
+
function wrapOwnMethod(holder, name) {
|
|
36
|
+
const desc = Object.getOwnPropertyDescriptor(holder, name);
|
|
37
|
+
if (!desc || typeof desc.value !== 'function' || desc.writable === false) return;
|
|
38
|
+
const original = desc.value;
|
|
39
|
+
function decoded(...args) {
|
|
40
|
+
let result;
|
|
41
|
+
try {
|
|
42
|
+
result = original.apply(this, args);
|
|
43
|
+
} catch (e) {
|
|
44
|
+
throw decodeCppException(e);
|
|
45
|
+
}
|
|
46
|
+
if (result && typeof result.then === 'function') {
|
|
47
|
+
// Async (JSPI-style) binding: decode the rejection reason as well.
|
|
48
|
+
return result.then(undefined, (e) => { throw decodeCppException(e); });
|
|
49
|
+
}
|
|
50
|
+
return result;
|
|
51
|
+
}
|
|
52
|
+
// Overload dispatch reads properties (e.g. overloadTable) off the exposed function.
|
|
53
|
+
Object.assign(decoded, original);
|
|
54
|
+
Object.defineProperty(holder, name, { ...desc, value: decoded });
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Direct (non-worker) mode exposes embind class instances raw, so a thrown C++ exception in
|
|
58
|
+
// an instance or static method never crosses a wrapper. Patching each bound class's
|
|
59
|
+
// prototype and statics once at module-ready keeps object identity intact (embind checks
|
|
60
|
+
// prototypes, not method identity) while every call site gains the decode.
|
|
61
|
+
export function patchModuleForExceptionDecode(m) {
|
|
62
|
+
setExceptionDecodeModule(m);
|
|
63
|
+
if (m == null || typeof m !== 'object') return m;
|
|
64
|
+
for (const key of Object.keys(m)) {
|
|
65
|
+
const Cls = m[key];
|
|
66
|
+
if (typeof Cls !== 'function') continue;
|
|
67
|
+
if (Cls.prototype) {
|
|
68
|
+
// Static-only classes are common (pure class_function surfaces), so statics are
|
|
69
|
+
// wrapped below regardless of whether the prototype carries any method.
|
|
70
|
+
Object.getOwnPropertyNames(Cls.prototype)
|
|
71
|
+
.filter((n) => n !== 'constructor')
|
|
72
|
+
.forEach((n) => wrapOwnMethod(Cls.prototype, n));
|
|
73
|
+
}
|
|
74
|
+
Object.getOwnPropertyNames(Cls)
|
|
75
|
+
.filter((n) => !STATIC_SKIP.has(n))
|
|
76
|
+
.forEach((n) => wrapOwnMethod(Cls, n));
|
|
77
|
+
}
|
|
78
|
+
return m;
|
|
79
|
+
}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
/* global WorkerGlobalScope */
|
|
2
|
+
import * as Comlink from 'comlink';
|
|
3
|
+
|
|
4
|
+
const STATIC_PATHS = {
|
|
5
|
+
opfs: '/opfs',
|
|
6
|
+
memfs: '/memfs',
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
const isWorkerScope = typeof WorkerGlobalScope !== 'undefined'
|
|
10
|
+
&& typeof self !== 'undefined'
|
|
11
|
+
&& self instanceof WorkerGlobalScope;
|
|
12
|
+
|
|
13
|
+
const isBrowserSupportOPFS = typeof navigator !== 'undefined'
|
|
14
|
+
&& navigator.storage
|
|
15
|
+
&& navigator.storage.getDirectory;
|
|
16
|
+
|
|
17
|
+
// Presence of the API does not guarantee a working backend: Playwright's WebKit
|
|
18
|
+
// rejects getDirectory() with UnknownError, and storage-restricted contexts
|
|
19
|
+
// reject too. false only after the onModuleReady preflight actually failed.
|
|
20
|
+
let isOpfsFunctional = true;
|
|
21
|
+
|
|
22
|
+
export default {
|
|
23
|
+
extendModule(m, config) {
|
|
24
|
+
// Hand the pthread spawn script to the glue via a crossbind-specific key. Using emscripten's
|
|
25
|
+
// own mainScriptUrlOrBlob aborts emsdk 6 debug builds ("supplied but not included in
|
|
26
|
+
// INCOMING_MODULE_JS_API") on single-thread targets; a custom key is not validated, and
|
|
27
|
+
// buildWasm rewrites the pthread bootstrap to read it (falling back to _scriptName).
|
|
28
|
+
const pthreadScript = config.paths.worker || config.paths.js;
|
|
29
|
+
if (pthreadScript) {
|
|
30
|
+
m.crossbindMainScript = m.locateFile(pthreadScript);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
m.getDefaultPath = () => STATIC_PATHS[(config.fs?.opfs !== false && isOpfsFunctional) ? 'opfs' : 'memfs'];
|
|
34
|
+
|
|
35
|
+
m.getFinalPath = function getFinalPath(path) {
|
|
36
|
+
let returnPath = path;
|
|
37
|
+
if (!path.startsWith('/')) {
|
|
38
|
+
throw new Error(`Path ${path} does not start with /`);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const opfsConfigured = config.fs?.opfs !== false;
|
|
42
|
+
|
|
43
|
+
let backend;
|
|
44
|
+
Object.entries(STATIC_PATHS).forEach(([key, value]) => {
|
|
45
|
+
if (path.startsWith(value)) {
|
|
46
|
+
backend = key;
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
if (!backend) {
|
|
50
|
+
throw new Error(`Path ${path} does not start with any of the static paths: ${Object.values(STATIC_PATHS).join(', ')}`);
|
|
51
|
+
}
|
|
52
|
+
if (backend === 'opfs' && !opfsConfigured) {
|
|
53
|
+
throw new Error(`Path ${path} starts with ${STATIC_PATHS.opfs} but OPFS is disabled. Enable fs.opfs in config to mount under ${STATIC_PATHS.opfs}/.`);
|
|
54
|
+
}
|
|
55
|
+
if (backend === 'opfs' && !isWorkerScope) {
|
|
56
|
+
throw new Error(`Path ${path} starts with ${STATIC_PATHS.opfs} but OPFS is only available inside a Worker scope. Enable useWorker or mount under ${STATIC_PATHS.memfs}/ instead. Falling back to ${STATIC_PATHS.memfs}/.`);
|
|
57
|
+
}
|
|
58
|
+
if (backend === 'opfs' && (!isBrowserSupportOPFS || !isOpfsFunctional)) {
|
|
59
|
+
console.error(`Path ${path} starts with ${STATIC_PATHS.opfs} but OPFS is not usable in this browser. Falling back to ${STATIC_PATHS.memfs}/.`);
|
|
60
|
+
returnPath = returnPath.replace(STATIC_PATHS.opfs, STATIC_PATHS.memfs);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return returnPath;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
m.getRandomPath = function getRandomPath(startPath = m.getDefaultPath()) {
|
|
67
|
+
const appName = config.general?.name;
|
|
68
|
+
const rand = Math.floor(Math.random() * 1000000);
|
|
69
|
+
const path = m.getFinalPath(`${startPath}/${appName}/automounted/${rand}`);
|
|
70
|
+
m.FS.mkdirTree(path);
|
|
71
|
+
return path;
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
m.autoMountFiles = function autoMountFiles(files, parentPath = null) {
|
|
75
|
+
return new Promise((resolve, reject) => {
|
|
76
|
+
if (files.length === 0) {
|
|
77
|
+
resolve([]);
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const mountPath = parentPath ? m.getFinalPath(parentPath) : m.getRandomPath();
|
|
82
|
+
m.FS.mkdirTree(mountPath);
|
|
83
|
+
|
|
84
|
+
(async () => {
|
|
85
|
+
try {
|
|
86
|
+
const paths = [];
|
|
87
|
+
for (const f of files) {
|
|
88
|
+
const filePath = `${mountPath}/${f.name}`;
|
|
89
|
+
const stream = m.FS.open(filePath, 'w');
|
|
90
|
+
const reader = f.stream().getReader();
|
|
91
|
+
let offset = 0;
|
|
92
|
+
|
|
93
|
+
while (true) {
|
|
94
|
+
const { done, value } = await reader.read();
|
|
95
|
+
if (done) break;
|
|
96
|
+
m.FS.write(stream, value, 0, value.length, offset);
|
|
97
|
+
offset += value.length;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
m.FS.close(stream);
|
|
101
|
+
paths.push(filePath);
|
|
102
|
+
}
|
|
103
|
+
resolve(paths);
|
|
104
|
+
} catch (e) {
|
|
105
|
+
reject(e);
|
|
106
|
+
}
|
|
107
|
+
})();
|
|
108
|
+
});
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
m.getFileBytes = function getFileBytes(path) {
|
|
112
|
+
if (!path) return new Uint8Array();
|
|
113
|
+
return m.FS.readFile(path, { encoding: 'binary' });
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
m.getFileList = function getFileList(path = m.getDefaultPath()) {
|
|
117
|
+
const fileList = [];
|
|
118
|
+
for (const name of m.FS.readdir(path)) {
|
|
119
|
+
if (name === '.' || name === '..') continue;
|
|
120
|
+
const fullPath = path === '/' ? `/${name}` : `${path}/${name}`;
|
|
121
|
+
const stat = m.FS.stat(fullPath);
|
|
122
|
+
const type = stat.mode & 0o170000;
|
|
123
|
+
if (type === 0o040000) {
|
|
124
|
+
fileList.push(...m.getFileList(fullPath));
|
|
125
|
+
} else if (type === 0o100000) {
|
|
126
|
+
fileList.push({ path: fullPath, size: stat.size });
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return fileList;
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
m._createVector = function _createVector(className, array = []) {
|
|
133
|
+
const vector = m.toVector(className, array);
|
|
134
|
+
return Comlink.proxy(vector);
|
|
135
|
+
};
|
|
136
|
+
},
|
|
137
|
+
|
|
138
|
+
async onModuleReady(m, config) {
|
|
139
|
+
const appName = config.general?.name;
|
|
140
|
+
try {
|
|
141
|
+
m.FS.mkdirTree(`${STATIC_PATHS.memfs}/${appName}/automounted`);
|
|
142
|
+
} catch (e) {
|
|
143
|
+
console.error(e);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (!(isWorkerScope && isBrowserSupportOPFS && config.fs?.opfs !== false && typeof m.crossbind_init_opfs === 'function')) {
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// crossbind_init_opfs() blocks until the OPFS root opens; if the backend is
|
|
151
|
+
// broken, that rejection is swallowed inside the WASMFS proxy pthread and
|
|
152
|
+
// init deadlocks with no signal. Probe the root here first instead.
|
|
153
|
+
try {
|
|
154
|
+
await navigator.storage.getDirectory();
|
|
155
|
+
} catch (error) {
|
|
156
|
+
isOpfsFunctional = false;
|
|
157
|
+
console.error(`OPFS is present but not functional in this environment (${error}). Falling back to ${STATIC_PATHS.memfs}/.`);
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
m.crossbind_init_opfs();
|
|
162
|
+
if (appName) {
|
|
163
|
+
const appDir = `${STATIC_PATHS.opfs}/${appName}/automounted`;
|
|
164
|
+
try {
|
|
165
|
+
m.FS.mkdirTree(appDir);
|
|
166
|
+
} catch (e) {
|
|
167
|
+
console.error(e);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
},
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
export { STATIC_PATHS, isWorkerScope, isBrowserSupportOPFS };
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
extendModule(m) {
|
|
3
|
+
m.getPreloadedPackage = function getPreloadedPackage(packageName) {
|
|
4
|
+
return require('fs').readFileSync(`./${packageName}`, { flag: 'r' }).buffer;
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
m.getFileBytes = function getFileBytes(path) {
|
|
8
|
+
if (!path) return new Uint8Array();
|
|
9
|
+
return m.FS.readFile(path, { encoding: 'binary' });
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
m.getFileList = function getFileList(path = '/memfs') {
|
|
13
|
+
const fileList = [];
|
|
14
|
+
for (const name of m.FS.readdir(path)) {
|
|
15
|
+
if (name === '.' || name === '..') continue;
|
|
16
|
+
const fullPath = path === '/' ? `/${name}` : `${path}/${name}`;
|
|
17
|
+
const stat = m.FS.stat(fullPath);
|
|
18
|
+
const type = stat.mode & 0o170000;
|
|
19
|
+
if (type === 0o040000) {
|
|
20
|
+
fileList.push(...m.getFileList(fullPath));
|
|
21
|
+
} else if (type === 0o100000) {
|
|
22
|
+
fileList.push({ path: fullPath, size: stat.size });
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return fileList;
|
|
26
|
+
};
|
|
27
|
+
},
|
|
28
|
+
};
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
// === Plain array -> embind vector coercion (shared by the worker and direct runtimes) ===
|
|
2
|
+
// Vectors RETURNED across the worker boundary arrive as plain arrays, so callers naturally
|
|
3
|
+
// try to pass plain arrays back as vector PARAMETERS - which embind rejects with
|
|
4
|
+
// 'Cannot pass "..." as a VectorX'. Coercion catches that error, converts the first
|
|
5
|
+
// remaining plain-array argument with Module.toVector, and retries, so mixed signatures
|
|
6
|
+
// (VectorDataset + VectorString, ...) resolve left to right, matching embind's argument
|
|
7
|
+
// order. embind passes std::vector parameters by value, so the temporaries are deleted
|
|
8
|
+
// after the call instead of leaking wasm memory.
|
|
9
|
+
//
|
|
10
|
+
// This is a REACTIVE strategy: embind does not expose parameter types to JS, so we can
|
|
11
|
+
// only learn that an argument should have been a vector from the failed call. See
|
|
12
|
+
// EXPECTED_VECTOR_RE below - the coercion silently disengages (rethrows) if embind ever
|
|
13
|
+
// changes that message, which the canary test in vectorCoercion.test.js guards against.
|
|
14
|
+
import { decodeCppException } from './exception-decode.js';
|
|
15
|
+
|
|
16
|
+
let coercionModule = null;
|
|
17
|
+
|
|
18
|
+
// Matches embind's "Cannot pass <value> as a VectorX" BindingError message.
|
|
19
|
+
const EXPECTED_VECTOR_RE = /as a (Vector\w+)\b/;
|
|
20
|
+
|
|
21
|
+
export function setCoercionModule(m) {
|
|
22
|
+
coercionModule = m;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Proxies created by the coercion wrappers, mapped back to their raw targets. embind
|
|
26
|
+
// rejects a proxied `this` or argument (its class checks compare $$.ptrType.registeredClass
|
|
27
|
+
// by identity), so every invocation unwraps them back to the raw objects first.
|
|
28
|
+
const coercionProxies = new WeakMap();
|
|
29
|
+
|
|
30
|
+
export function unwrapCoercionProxy(value) {
|
|
31
|
+
const raw = coercionProxies.get(value);
|
|
32
|
+
return raw === undefined ? value : raw;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// The retry-on-vector-error loop shared by call and construct. `invoke(args)` performs the
|
|
36
|
+
// actual apply/construct; on a 'Cannot pass ... as a VectorX' error that names a registered
|
|
37
|
+
// Vector class while a plain-array argument remains, the first remaining array is converted
|
|
38
|
+
// with Module.toVector and the invocation retried. Each retry converts exactly one array, so
|
|
39
|
+
// the array count strictly decreases and the loop always terminates.
|
|
40
|
+
function retryWithCoercion(invoke, args) {
|
|
41
|
+
const temp = [];
|
|
42
|
+
const cleanup = () => {
|
|
43
|
+
for (const v of temp) {
|
|
44
|
+
try {
|
|
45
|
+
v.delete();
|
|
46
|
+
} catch (e) { /* already deleted by the callee */ }
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
const current = args.slice();
|
|
50
|
+
for (;;) {
|
|
51
|
+
let result;
|
|
52
|
+
try {
|
|
53
|
+
result = invoke(current);
|
|
54
|
+
} catch (rawError) {
|
|
55
|
+
const e = decodeCppException(rawError);
|
|
56
|
+
const match = EXPECTED_VECTOR_RE.exec(String((e && e.message) || e));
|
|
57
|
+
const VectorClass = match && coercionModule ? coercionModule[match[1]] : undefined;
|
|
58
|
+
const index = current.findIndex((a) => Array.isArray(a));
|
|
59
|
+
if (typeof VectorClass !== 'function' || index < 0) {
|
|
60
|
+
cleanup();
|
|
61
|
+
throw e;
|
|
62
|
+
}
|
|
63
|
+
let vector;
|
|
64
|
+
try {
|
|
65
|
+
vector = coercionModule.toVector(VectorClass, current[index]);
|
|
66
|
+
} catch (convError) {
|
|
67
|
+
cleanup();
|
|
68
|
+
throw e;
|
|
69
|
+
}
|
|
70
|
+
temp.push(vector);
|
|
71
|
+
current[index] = vector;
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
if (temp.length && result && typeof result.then === 'function') {
|
|
75
|
+
// Async (JSPI-style) binding: arguments were already converted synchronously,
|
|
76
|
+
// but hold the temporaries until it settles.
|
|
77
|
+
return result.then(
|
|
78
|
+
(value) => {
|
|
79
|
+
cleanup();
|
|
80
|
+
return value;
|
|
81
|
+
},
|
|
82
|
+
(err) => {
|
|
83
|
+
cleanup();
|
|
84
|
+
throw decodeCppException(err);
|
|
85
|
+
},
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
cleanup();
|
|
89
|
+
return result;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function callWithVectorCoercion(fn, thisArg, args) {
|
|
94
|
+
return retryWithCoercion((current) => Reflect.apply(fn, thisArg, current), args);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function constructWithVectorCoercion(Class, args, newTarget) {
|
|
98
|
+
return retryWithCoercion((current) => Reflect.construct(Class, current, newTarget || Class), args);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Deep wrapper (WORKER mode): every method reached through the wrapped object - at any depth
|
|
102
|
+
// (Module.Gdal.openEx, dataset.translate, ...) - is invoked through callWithVectorCoercion,
|
|
103
|
+
// and every `new` through the construct trap below. Safe here because the worker world is
|
|
104
|
+
// entirely opaque Comlink proxies - nothing accesses raw memory or object identity through it.
|
|
105
|
+
export function wrapWithVectorCoercion(value, thisArg) {
|
|
106
|
+
if (value == null || (typeof value !== 'object' && typeof value !== 'function')) {
|
|
107
|
+
return value;
|
|
108
|
+
}
|
|
109
|
+
const proxy = new Proxy(value, {
|
|
110
|
+
get(target, prop) {
|
|
111
|
+
const member = Reflect.get(target, prop);
|
|
112
|
+
// Comlink invokes exposed methods as rawValue.apply(parent, args) with parent
|
|
113
|
+
// being the WRAPPED object. Handle Function.prototype apply/call here so `this`
|
|
114
|
+
// and the argument list reach callWithVectorCoercion unwrapped and flat (retries
|
|
115
|
+
// depend on seeing the real argument positions).
|
|
116
|
+
if (typeof target === 'function' && member === Function.prototype.apply) {
|
|
117
|
+
return (applyThis, applyArgs) => callWithVectorCoercion(
|
|
118
|
+
target,
|
|
119
|
+
applyThis === undefined && thisArg !== undefined
|
|
120
|
+
? thisArg
|
|
121
|
+
: unwrapCoercionProxy(applyThis),
|
|
122
|
+
applyArgs ? Array.prototype.map.call(applyArgs, unwrapCoercionProxy) : [],
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
if (typeof target === 'function' && member === Function.prototype.call) {
|
|
126
|
+
return (callThis, ...callArgs) => callWithVectorCoercion(
|
|
127
|
+
target,
|
|
128
|
+
callThis === undefined && thisArg !== undefined
|
|
129
|
+
? thisArg
|
|
130
|
+
: unwrapCoercionProxy(callThis),
|
|
131
|
+
callArgs.map(unwrapCoercionProxy),
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
if (typeof member === 'function' || (member !== null && typeof member === 'object')) {
|
|
135
|
+
// Proxy invariant: non-configurable, non-writable data properties (a class's
|
|
136
|
+
// .prototype, notably) must be returned as-is, or the engine throws on access.
|
|
137
|
+
const desc = Reflect.getOwnPropertyDescriptor(target, prop);
|
|
138
|
+
if (desc && desc.configurable === false && desc.writable === false) {
|
|
139
|
+
return member;
|
|
140
|
+
}
|
|
141
|
+
return wrapWithVectorCoercion(member, target);
|
|
142
|
+
}
|
|
143
|
+
return member;
|
|
144
|
+
},
|
|
145
|
+
set(target, prop, value) {
|
|
146
|
+
// 3-arg Reflect.set keeps the RAW object as receiver: embind property setters
|
|
147
|
+
// identity-check `this`, and the default (proxy receiver) fails that check.
|
|
148
|
+
return Reflect.set(target, prop, unwrapCoercionProxy(value));
|
|
149
|
+
},
|
|
150
|
+
apply(target, applyThis, args) {
|
|
151
|
+
const boundThis = thisArg !== undefined ? thisArg : unwrapCoercionProxy(applyThis);
|
|
152
|
+
return callWithVectorCoercion(target, boundThis, args.map(unwrapCoercionProxy));
|
|
153
|
+
},
|
|
154
|
+
construct(target, args) {
|
|
155
|
+
// Comlink's CONSTRUCT handler does `new rawValue(...args)` on the wrapped
|
|
156
|
+
// class. Without this trap the construction falls through with the PROXY as
|
|
157
|
+
// newTarget, whose get trap hands out a wrapped .prototype (embind classes
|
|
158
|
+
// are plain functions, so their prototype is writable and the proxy
|
|
159
|
+
// invariant early-return does not apply) - and embind's prototype-identity
|
|
160
|
+
// check rejects the instance with "Use 'new' to construct X". Constructing
|
|
161
|
+
// against the raw class keeps the real prototype and adds the same
|
|
162
|
+
// plain-array vector coercion that method calls get.
|
|
163
|
+
return constructWithVectorCoercion(target, args.map(unwrapCoercionProxy));
|
|
164
|
+
},
|
|
165
|
+
});
|
|
166
|
+
coercionProxies.set(proxy, value);
|
|
167
|
+
return proxy;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Narrow wrapper (DIRECT / non-worker mode): only the module's own function calls and
|
|
171
|
+
// constructions are coerced. Data members (HEAP*, FS, typed arrays) and returned objects
|
|
172
|
+
// stay raw, because direct-mode code accesses those by index and by identity - a blanket
|
|
173
|
+
// proxy (like the worker wrapper above) would break them. Returned embind objects are
|
|
174
|
+
// already real vectors/objects in direct mode (they are not flattened to plain arrays the
|
|
175
|
+
// way the worker boundary does), so module-level coercion covers the plain-array-literal
|
|
176
|
+
// case without changing any object's identity.
|
|
177
|
+
export function wrapModuleForCoercion(m) {
|
|
178
|
+
if (m == null || (typeof m !== 'object' && typeof m !== 'function')) {
|
|
179
|
+
return m;
|
|
180
|
+
}
|
|
181
|
+
const wrappedFns = new Map();
|
|
182
|
+
const moduleProxy = new Proxy(m, {
|
|
183
|
+
get(target, prop) {
|
|
184
|
+
const member = Reflect.get(target, prop);
|
|
185
|
+
if (typeof member !== 'function') {
|
|
186
|
+
return member; // HEAP*, FS, typed arrays and other data pass through raw
|
|
187
|
+
}
|
|
188
|
+
let wrapped = wrappedFns.get(prop);
|
|
189
|
+
if (!wrapped) {
|
|
190
|
+
wrapped = new Proxy(member, {
|
|
191
|
+
apply(fn, thisArg, args) {
|
|
192
|
+
return callWithVectorCoercion(
|
|
193
|
+
fn,
|
|
194
|
+
unwrapCoercionProxy(thisArg),
|
|
195
|
+
args.map(unwrapCoercionProxy),
|
|
196
|
+
);
|
|
197
|
+
},
|
|
198
|
+
construct(Class, args) {
|
|
199
|
+
return constructWithVectorCoercion(Class, args.map(unwrapCoercionProxy));
|
|
200
|
+
},
|
|
201
|
+
});
|
|
202
|
+
wrappedFns.set(prop, wrapped);
|
|
203
|
+
}
|
|
204
|
+
return wrapped;
|
|
205
|
+
},
|
|
206
|
+
});
|
|
207
|
+
coercionProxies.set(moduleProxy, m);
|
|
208
|
+
return moduleProxy;
|
|
209
|
+
}
|