pyodide 0.20.0 → 0.20.1-alpha.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/package.json +31 -9
- package/pyodide.ts +31 -39
- package/api.ts +0 -495
- package/compat.ts +0 -146
- package/error_handling.gen.ts +0 -335
- package/index.d.ts +0 -1
- package/index.test-d.ts +0 -167
- package/load-package.ts +0 -414
- package/module.ts +0 -117
- package/pyproxy.gen.ts +0 -1507
- package/rollup.config.js +0 -43
- package/test/conftest.js +0 -7
- package/test/filesystem.test.js +0 -12
- package/test/module.test.js +0 -10
- package/test/pyodide.test.mjs +0 -26
- package/tsconfig.json +0 -15
package/load-package.ts
DELETED
|
@@ -1,414 +0,0 @@
|
|
|
1
|
-
import { Module, API, Tests } from "./module.js";
|
|
2
|
-
import { IN_NODE, nodeFsPromisesMod, _loadBinaryFile } from "./compat.js";
|
|
3
|
-
import { PyProxy, isPyProxy } from "./pyproxy.gen";
|
|
4
|
-
|
|
5
|
-
/** @private */
|
|
6
|
-
let baseURL: string;
|
|
7
|
-
/**
|
|
8
|
-
* Initialize the packages index. This is called as early as possible in
|
|
9
|
-
* loadPyodide so that fetching packages.json can occur in parallel with other
|
|
10
|
-
* operations.
|
|
11
|
-
* @param indexURL
|
|
12
|
-
* @private
|
|
13
|
-
*/
|
|
14
|
-
export async function initializePackageIndex(indexURL: string) {
|
|
15
|
-
baseURL = indexURL;
|
|
16
|
-
let package_json;
|
|
17
|
-
if (IN_NODE) {
|
|
18
|
-
const package_string = await nodeFsPromisesMod.readFile(
|
|
19
|
-
`${indexURL}packages.json`
|
|
20
|
-
);
|
|
21
|
-
package_json = JSON.parse(package_string);
|
|
22
|
-
} else {
|
|
23
|
-
let response = await fetch(`${indexURL}packages.json`);
|
|
24
|
-
package_json = await response.json();
|
|
25
|
-
}
|
|
26
|
-
if (!package_json.packages) {
|
|
27
|
-
throw new Error(
|
|
28
|
-
"Loaded packages.json does not contain the expected key 'packages'."
|
|
29
|
-
);
|
|
30
|
-
}
|
|
31
|
-
API.packages = package_json.packages;
|
|
32
|
-
|
|
33
|
-
// compute the inverted index for imports to package names
|
|
34
|
-
API._import_name_to_package_name = new Map();
|
|
35
|
-
for (let name of Object.keys(API.packages)) {
|
|
36
|
-
for (let import_name of API.packages[name].imports) {
|
|
37
|
-
API._import_name_to_package_name.set(import_name, name);
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
//
|
|
43
|
-
// Dependency resolution
|
|
44
|
-
//
|
|
45
|
-
const DEFAULT_CHANNEL = "default channel";
|
|
46
|
-
// Regexp for validating package name and URI
|
|
47
|
-
const package_uri_regexp = /^.*?([^\/]*)\.whl$/;
|
|
48
|
-
|
|
49
|
-
function _uri_to_package_name(package_uri: string): string | undefined {
|
|
50
|
-
let match = package_uri_regexp.exec(package_uri);
|
|
51
|
-
if (match) {
|
|
52
|
-
let wheel_name = match[1].toLowerCase();
|
|
53
|
-
return wheel_name.split("-").slice(0, -4).join("-");
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Recursively add a package and its dependencies to toLoad and toLoadShared.
|
|
59
|
-
* A helper function for recursiveDependencies.
|
|
60
|
-
* @param name The package to add
|
|
61
|
-
* @param toLoad The set of names of packages to load
|
|
62
|
-
* @param toLoadShared The set of names of shared libraries to load
|
|
63
|
-
* @private
|
|
64
|
-
*/
|
|
65
|
-
function addPackageToLoad(
|
|
66
|
-
name: string,
|
|
67
|
-
toLoad: Map<string, string>,
|
|
68
|
-
toLoadShared: Map<string, string>
|
|
69
|
-
) {
|
|
70
|
-
name = name.toLowerCase();
|
|
71
|
-
if (toLoad.has(name)) {
|
|
72
|
-
return;
|
|
73
|
-
}
|
|
74
|
-
const pkg_info = API.packages[name];
|
|
75
|
-
if (!pkg_info) {
|
|
76
|
-
throw new Error(`No known package with name '${name}'`);
|
|
77
|
-
}
|
|
78
|
-
if (pkg_info.shared_library) {
|
|
79
|
-
toLoadShared.set(name, DEFAULT_CHANNEL);
|
|
80
|
-
} else {
|
|
81
|
-
toLoad.set(name, DEFAULT_CHANNEL);
|
|
82
|
-
}
|
|
83
|
-
// If the package is already loaded, we don't add dependencies, but warn
|
|
84
|
-
// the user later. This is especially important if the loaded package is
|
|
85
|
-
// from a custom url, in which case adding dependencies is wrong.
|
|
86
|
-
if (loadedPackages[name] !== undefined) {
|
|
87
|
-
return;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
for (let dep_name of pkg_info.depends) {
|
|
91
|
-
addPackageToLoad(dep_name, toLoad, toLoadShared);
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* Calculate the dependencies of a set of packages
|
|
97
|
-
* @param names The list of names whose dependencies we need to calculate.
|
|
98
|
-
* @returns Two sets, the set of normal dependencies and the set of shared
|
|
99
|
-
* dependencies
|
|
100
|
-
* @private
|
|
101
|
-
*/
|
|
102
|
-
function recursiveDependencies(
|
|
103
|
-
names: string[],
|
|
104
|
-
errorCallback: (err: string) => void
|
|
105
|
-
) {
|
|
106
|
-
const toLoad = new Map();
|
|
107
|
-
const toLoadShared = new Map();
|
|
108
|
-
for (let name of names) {
|
|
109
|
-
const pkgname = _uri_to_package_name(name);
|
|
110
|
-
if (pkgname === undefined) {
|
|
111
|
-
addPackageToLoad(name, toLoad, toLoadShared);
|
|
112
|
-
continue;
|
|
113
|
-
}
|
|
114
|
-
if (toLoad.has(pkgname) && toLoad.get(pkgname) !== name) {
|
|
115
|
-
errorCallback(
|
|
116
|
-
`Loading same package ${pkgname} from ${name} and ${toLoad.get(
|
|
117
|
-
pkgname
|
|
118
|
-
)}`
|
|
119
|
-
);
|
|
120
|
-
continue;
|
|
121
|
-
}
|
|
122
|
-
toLoad.set(pkgname, name);
|
|
123
|
-
}
|
|
124
|
-
return [toLoad, toLoadShared];
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
//
|
|
128
|
-
// Dependency download and install
|
|
129
|
-
//
|
|
130
|
-
|
|
131
|
-
/**
|
|
132
|
-
* Download a package. If `channel` is `DEFAULT_CHANNEL`, look up the wheel URL
|
|
133
|
-
* relative to baseURL from `packages.json`, otherwise use the URL specified by
|
|
134
|
-
* `channel`.
|
|
135
|
-
* @param name The name of the package
|
|
136
|
-
* @param channel Either `DEFAULT_CHANNEL` or the absolute URL to the
|
|
137
|
-
* wheel or the path to the wheel relative to baseURL.
|
|
138
|
-
* @returns The binary data for the package
|
|
139
|
-
* @private
|
|
140
|
-
*/
|
|
141
|
-
async function downloadPackage(
|
|
142
|
-
name: string,
|
|
143
|
-
channel: string
|
|
144
|
-
): Promise<Uint8Array> {
|
|
145
|
-
let file_name;
|
|
146
|
-
if (channel === DEFAULT_CHANNEL) {
|
|
147
|
-
if (!(name in API.packages)) {
|
|
148
|
-
throw new Error(`Internal error: no entry for package named ${name}`);
|
|
149
|
-
}
|
|
150
|
-
file_name = API.packages[name].file_name;
|
|
151
|
-
} else {
|
|
152
|
-
file_name = channel;
|
|
153
|
-
}
|
|
154
|
-
return await _loadBinaryFile(baseURL, file_name);
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
/**
|
|
158
|
-
* Install the package into the file system.
|
|
159
|
-
* @param name The name of the package
|
|
160
|
-
* @param buffer The binary data returned by downloadPkgBuffer
|
|
161
|
-
* @private
|
|
162
|
-
*/
|
|
163
|
-
async function installPackage(name: string, buffer: Uint8Array) {
|
|
164
|
-
let pkg = API.packages[name];
|
|
165
|
-
if (!pkg) {
|
|
166
|
-
pkg = {
|
|
167
|
-
file_name: ".whl",
|
|
168
|
-
install_dir: "site",
|
|
169
|
-
shared_library: false,
|
|
170
|
-
depends: [],
|
|
171
|
-
imports: [] as string[],
|
|
172
|
-
};
|
|
173
|
-
}
|
|
174
|
-
const filename = pkg.file_name;
|
|
175
|
-
// This Python helper function unpacks the buffer and lists out any so files therein.
|
|
176
|
-
const dynlibs = API.package_loader.unpack_buffer.callKwargs({
|
|
177
|
-
buffer,
|
|
178
|
-
filename,
|
|
179
|
-
target: pkg.install_dir,
|
|
180
|
-
calculate_dynlibs: true,
|
|
181
|
-
});
|
|
182
|
-
for (const dynlib of dynlibs) {
|
|
183
|
-
await loadDynlib(dynlib, pkg.shared_library);
|
|
184
|
-
}
|
|
185
|
-
loadedPackages[name] = pkg;
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
/**
|
|
189
|
-
* @returns A new asynchronous lock
|
|
190
|
-
* @private
|
|
191
|
-
*/
|
|
192
|
-
function createLock() {
|
|
193
|
-
// This is a promise that is resolved when the lock is open, not resolved when lock is held.
|
|
194
|
-
let _lock = Promise.resolve();
|
|
195
|
-
|
|
196
|
-
/**
|
|
197
|
-
* Acquire the async lock
|
|
198
|
-
* @returns A zero argument function that releases the lock.
|
|
199
|
-
* @private
|
|
200
|
-
*/
|
|
201
|
-
async function acquireLock() {
|
|
202
|
-
const old_lock = _lock;
|
|
203
|
-
let releaseLock: () => void;
|
|
204
|
-
_lock = new Promise((resolve) => (releaseLock = resolve));
|
|
205
|
-
await old_lock;
|
|
206
|
-
// @ts-ignore
|
|
207
|
-
return releaseLock;
|
|
208
|
-
}
|
|
209
|
-
return acquireLock;
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
// Emscripten has a lock in the corresponding code in library_browser.js. I
|
|
213
|
-
// don't know why we need it, but quite possibly bad stuff will happen without
|
|
214
|
-
// it.
|
|
215
|
-
const acquireDynlibLock = createLock();
|
|
216
|
-
|
|
217
|
-
/**
|
|
218
|
-
* Load a dynamic library. This is an async operation and Python imports are
|
|
219
|
-
* synchronous so we have to do it ahead of time. When we add more support for
|
|
220
|
-
* synchronous I/O, we could consider doing this later as a part of a Python
|
|
221
|
-
* import hook.
|
|
222
|
-
*
|
|
223
|
-
* @param lib The file system path to the library.
|
|
224
|
-
* @param shared Is this a shared library or not?
|
|
225
|
-
* @private
|
|
226
|
-
*/
|
|
227
|
-
async function loadDynlib(lib: string, shared: boolean) {
|
|
228
|
-
const node = Module.FS.lookupPath(lib).node;
|
|
229
|
-
let byteArray;
|
|
230
|
-
if (node.mount.type == Module.FS.filesystems.MEMFS) {
|
|
231
|
-
byteArray = Module.FS.filesystems.MEMFS.getFileDataAsTypedArray(
|
|
232
|
-
Module.FS.lookupPath(lib).node
|
|
233
|
-
);
|
|
234
|
-
} else {
|
|
235
|
-
byteArray = Module.FS.readFile(lib);
|
|
236
|
-
}
|
|
237
|
-
const releaseDynlibLock = await acquireDynlibLock();
|
|
238
|
-
try {
|
|
239
|
-
const module = await Module.loadWebAssemblyModule(byteArray, {
|
|
240
|
-
loadAsync: true,
|
|
241
|
-
nodelete: true,
|
|
242
|
-
allowUndefined: true,
|
|
243
|
-
});
|
|
244
|
-
Module.preloadedWasm[lib] = module;
|
|
245
|
-
Module.preloadedWasm[lib.split("/").pop()!] = module;
|
|
246
|
-
if (shared) {
|
|
247
|
-
Module.loadDynamicLibrary(lib, {
|
|
248
|
-
global: true,
|
|
249
|
-
nodelete: true,
|
|
250
|
-
});
|
|
251
|
-
}
|
|
252
|
-
} catch (e) {
|
|
253
|
-
if (e.message.includes("need to see wasm magic number")) {
|
|
254
|
-
console.warn(
|
|
255
|
-
`Failed to load dynlib ${lib}. We probably just tried to load a linux .so file or something.`
|
|
256
|
-
);
|
|
257
|
-
return;
|
|
258
|
-
}
|
|
259
|
-
throw e;
|
|
260
|
-
} finally {
|
|
261
|
-
releaseDynlibLock();
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
Tests.loadDynlib = loadDynlib;
|
|
265
|
-
|
|
266
|
-
const acquirePackageLock = createLock();
|
|
267
|
-
|
|
268
|
-
/**
|
|
269
|
-
* Load a package or a list of packages over the network. This installs the
|
|
270
|
-
* package in the virtual filesystem. The package needs to be imported from
|
|
271
|
-
* Python before it can be used.
|
|
272
|
-
*
|
|
273
|
-
* @param names Either a single package name or
|
|
274
|
-
* URL or a list of them. URLs can be absolute or relative. The URLs must have
|
|
275
|
-
* file name ``<package-name>.js`` and there must be a file called
|
|
276
|
-
* ``<package-name>.data`` in the same directory. The argument can be a
|
|
277
|
-
* ``PyProxy`` of a list, in which case the list will be converted to JavaScript
|
|
278
|
-
* and the ``PyProxy`` will be destroyed.
|
|
279
|
-
* @param messageCallback A callback, called with progress messages
|
|
280
|
-
* (optional)
|
|
281
|
-
* @param errorCallback A callback, called with error/warning messages
|
|
282
|
-
* (optional)
|
|
283
|
-
* @async
|
|
284
|
-
*/
|
|
285
|
-
export async function loadPackage(
|
|
286
|
-
names: string | PyProxy | Array<string>,
|
|
287
|
-
messageCallback?: (msg: string) => void,
|
|
288
|
-
errorCallback?: (msg: string) => void
|
|
289
|
-
) {
|
|
290
|
-
messageCallback = messageCallback || console.log;
|
|
291
|
-
errorCallback = errorCallback || console.error;
|
|
292
|
-
if (isPyProxy(names)) {
|
|
293
|
-
names = names.toJs();
|
|
294
|
-
}
|
|
295
|
-
if (!Array.isArray(names)) {
|
|
296
|
-
names = [names as string];
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
const [toLoad, toLoadShared] = recursiveDependencies(names, errorCallback);
|
|
300
|
-
|
|
301
|
-
for (const [pkg, uri] of [...toLoad, ...toLoadShared]) {
|
|
302
|
-
const loaded = loadedPackages[pkg];
|
|
303
|
-
if (loaded === undefined) {
|
|
304
|
-
continue;
|
|
305
|
-
}
|
|
306
|
-
toLoad.delete(pkg);
|
|
307
|
-
toLoadShared.delete(pkg);
|
|
308
|
-
// If uri is from the DEFAULT_CHANNEL, we assume it was added as a
|
|
309
|
-
// dependency, which was previously overridden.
|
|
310
|
-
if (loaded === uri || uri === DEFAULT_CHANNEL) {
|
|
311
|
-
messageCallback(`${pkg} already loaded from ${loaded}`);
|
|
312
|
-
} else {
|
|
313
|
-
errorCallback(
|
|
314
|
-
`URI mismatch, attempting to load package ${pkg} from ${uri} ` +
|
|
315
|
-
`while it is already loaded from ${loaded}. To override a dependency, ` +
|
|
316
|
-
`load the custom package first.`
|
|
317
|
-
);
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
if (toLoad.size === 0 && toLoadShared.size === 0) {
|
|
322
|
-
messageCallback("No new packages to load");
|
|
323
|
-
return;
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
const packageNames = [...toLoad.keys(), ...toLoadShared.keys()].join(", ");
|
|
327
|
-
const releaseLock = await acquirePackageLock();
|
|
328
|
-
try {
|
|
329
|
-
messageCallback(`Loading ${packageNames}`);
|
|
330
|
-
const sharedLibraryLoadPromises: { [name: string]: Promise<Uint8Array> } =
|
|
331
|
-
{};
|
|
332
|
-
const packageLoadPromises: { [name: string]: Promise<Uint8Array> } = {};
|
|
333
|
-
for (const [name, channel] of toLoadShared) {
|
|
334
|
-
if (loadedPackages[name]) {
|
|
335
|
-
// Handle the race condition where the package was loaded between when
|
|
336
|
-
// we did dependency resolution and when we acquired the lock.
|
|
337
|
-
toLoadShared.delete(name);
|
|
338
|
-
continue;
|
|
339
|
-
}
|
|
340
|
-
sharedLibraryLoadPromises[name] = downloadPackage(name, channel);
|
|
341
|
-
}
|
|
342
|
-
for (const [name, channel] of toLoad) {
|
|
343
|
-
if (loadedPackages[name]) {
|
|
344
|
-
// Handle the race condition where the package was loaded between when
|
|
345
|
-
// we did dependency resolution and when we acquired the lock.
|
|
346
|
-
toLoad.delete(name);
|
|
347
|
-
continue;
|
|
348
|
-
}
|
|
349
|
-
packageLoadPromises[name] = downloadPackage(name, channel);
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
const loaded: string[] = [];
|
|
353
|
-
const failed: { [name: string]: any } = {};
|
|
354
|
-
// TODO: add support for prefetching modules by awaiting on a promise right
|
|
355
|
-
// here which resolves in loadPyodide when the bootstrap is done.
|
|
356
|
-
const sharedLibraryInstallPromises: { [name: string]: Promise<void> } = {};
|
|
357
|
-
const packageInstallPromises: { [name: string]: Promise<void> } = {};
|
|
358
|
-
for (const [name, channel] of toLoadShared) {
|
|
359
|
-
sharedLibraryInstallPromises[name] = sharedLibraryLoadPromises[name]
|
|
360
|
-
.then(async (buffer) => {
|
|
361
|
-
await installPackage(name, buffer);
|
|
362
|
-
loaded.push(name);
|
|
363
|
-
loadedPackages[name] = channel;
|
|
364
|
-
})
|
|
365
|
-
.catch((err) => {
|
|
366
|
-
console.warn(err);
|
|
367
|
-
failed[name] = err;
|
|
368
|
-
});
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
await Promise.all(Object.values(sharedLibraryInstallPromises));
|
|
372
|
-
for (const [name, channel] of toLoad) {
|
|
373
|
-
packageInstallPromises[name] = packageLoadPromises[name]
|
|
374
|
-
.then(async (buffer) => {
|
|
375
|
-
await installPackage(name, buffer);
|
|
376
|
-
loaded.push(name);
|
|
377
|
-
loadedPackages[name] = channel;
|
|
378
|
-
})
|
|
379
|
-
.catch((err) => {
|
|
380
|
-
console.warn(err);
|
|
381
|
-
failed[name] = err;
|
|
382
|
-
});
|
|
383
|
-
}
|
|
384
|
-
await Promise.all(Object.values(packageInstallPromises));
|
|
385
|
-
|
|
386
|
-
Module.reportUndefinedSymbols();
|
|
387
|
-
if (loaded.length > 0) {
|
|
388
|
-
const successNames = loaded.join(", ");
|
|
389
|
-
messageCallback(`Loaded ${successNames}`);
|
|
390
|
-
}
|
|
391
|
-
if (Object.keys(failed).length > 0) {
|
|
392
|
-
const failedNames = Object.keys(failed).join(", ");
|
|
393
|
-
messageCallback(`Failed to load ${failedNames}`);
|
|
394
|
-
for (const [name, err] of Object.entries(failed)) {
|
|
395
|
-
console.warn(`The following error occurred while loading ${name}:`);
|
|
396
|
-
console.error(err);
|
|
397
|
-
}
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
// We have to invalidate Python's import caches, or it won't
|
|
401
|
-
// see the new files.
|
|
402
|
-
API.importlib.invalidate_caches();
|
|
403
|
-
} finally {
|
|
404
|
-
releaseLock();
|
|
405
|
-
}
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
/**
|
|
409
|
-
* The list of packages that Pyodide has loaded.
|
|
410
|
-
* Use ``Object.keys(pyodide.loadedPackages)`` to get the list of names of
|
|
411
|
-
* loaded packages, and ``pyodide.loadedPackages[package_name]`` to access
|
|
412
|
-
* install location for a particular ``package_name``.
|
|
413
|
-
*/
|
|
414
|
-
export let loadedPackages: { [key: string]: string } = {};
|
package/module.ts
DELETED
|
@@ -1,117 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The Emscripten Module.
|
|
3
|
-
*
|
|
4
|
-
* @private
|
|
5
|
-
*/
|
|
6
|
-
export let Module: any = {};
|
|
7
|
-
Module.noImageDecoding = true;
|
|
8
|
-
Module.noAudioDecoding = true;
|
|
9
|
-
Module.noWasmDecoding = false; // we preload wasm using the built in plugin now
|
|
10
|
-
Module.preloadedWasm = {};
|
|
11
|
-
Module.preRun = [];
|
|
12
|
-
|
|
13
|
-
export let API: any = {};
|
|
14
|
-
Module.API = API;
|
|
15
|
-
export let Hiwire: any = {};
|
|
16
|
-
Module.hiwire = Hiwire;
|
|
17
|
-
|
|
18
|
-
// Put things that are exposed only for testing purposes here.
|
|
19
|
-
export let Tests: any = {};
|
|
20
|
-
API.tests = Tests;
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
*
|
|
24
|
-
* @param stdin
|
|
25
|
-
* @param stdout
|
|
26
|
-
* @param stderr
|
|
27
|
-
* @private
|
|
28
|
-
*/
|
|
29
|
-
export function setStandardStreams(
|
|
30
|
-
stdin?: () => string,
|
|
31
|
-
stdout?: (a: string) => void,
|
|
32
|
-
stderr?: (a: string) => void
|
|
33
|
-
) {
|
|
34
|
-
// For stdout and stderr, emscripten provides convenient wrappers that save us the trouble of converting the bytes into a string
|
|
35
|
-
if (stdout) {
|
|
36
|
-
Module.print = stdout;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
if (stderr) {
|
|
40
|
-
Module.printErr = stderr;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// For stdin, we have to deal with the low level API ourselves
|
|
44
|
-
if (stdin) {
|
|
45
|
-
Module.preRun.push(function () {
|
|
46
|
-
Module.FS.init(createStdinWrapper(stdin), null, null);
|
|
47
|
-
});
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
function createStdinWrapper(stdin: () => string) {
|
|
52
|
-
// When called, it asks the user for one whole line of input (stdin)
|
|
53
|
-
// Then, it passes the individual bytes of the input to emscripten, one after another.
|
|
54
|
-
// And finally, it terminates it with null.
|
|
55
|
-
const encoder = new TextEncoder();
|
|
56
|
-
let input = new Uint8Array(0);
|
|
57
|
-
let inputIndex = -1; // -1 means that we just returned null
|
|
58
|
-
function stdinWrapper() {
|
|
59
|
-
try {
|
|
60
|
-
if (inputIndex === -1) {
|
|
61
|
-
let text = stdin();
|
|
62
|
-
if (text === undefined || text === null) {
|
|
63
|
-
return null;
|
|
64
|
-
}
|
|
65
|
-
if (typeof text !== "string") {
|
|
66
|
-
throw new TypeError(
|
|
67
|
-
`Expected stdin to return string, null, or undefined, got type ${typeof text}.`
|
|
68
|
-
);
|
|
69
|
-
}
|
|
70
|
-
if (!text.endsWith("\n")) {
|
|
71
|
-
text += "\n";
|
|
72
|
-
}
|
|
73
|
-
input = encoder.encode(text);
|
|
74
|
-
inputIndex = 0;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
if (inputIndex < input.length) {
|
|
78
|
-
let character = input[inputIndex];
|
|
79
|
-
inputIndex++;
|
|
80
|
-
return character;
|
|
81
|
-
} else {
|
|
82
|
-
inputIndex = -1;
|
|
83
|
-
return null;
|
|
84
|
-
}
|
|
85
|
-
} catch (e) {
|
|
86
|
-
// emscripten will catch this and set an IOError which is unhelpful for
|
|
87
|
-
// debugging.
|
|
88
|
-
console.error("Error thrown in stdin:");
|
|
89
|
-
console.error(e);
|
|
90
|
-
throw e;
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
return stdinWrapper;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* Make the home directory inside the virtual file system,
|
|
98
|
-
* then change the working directory to it.
|
|
99
|
-
*
|
|
100
|
-
* @param path
|
|
101
|
-
* @private
|
|
102
|
-
*/
|
|
103
|
-
export function setHomeDirectory(path: string) {
|
|
104
|
-
Module.preRun.push(function () {
|
|
105
|
-
const fallbackPath = "/";
|
|
106
|
-
try {
|
|
107
|
-
Module.FS.mkdirTree(path);
|
|
108
|
-
} catch (e) {
|
|
109
|
-
console.error(`Error occurred while making a home directory '${path}':`);
|
|
110
|
-
console.error(e);
|
|
111
|
-
console.error(`Using '${fallbackPath}' for a home directory instead`);
|
|
112
|
-
path = fallbackPath;
|
|
113
|
-
}
|
|
114
|
-
Module.ENV.HOME = path;
|
|
115
|
-
Module.FS.chdir(path);
|
|
116
|
-
});
|
|
117
|
-
}
|