pyodide 0.18.2 → 0.19.0-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/README.md +4 -4
- package/api.js +113 -53
- package/index.test-d.ts +12 -9
- package/load-pyodide.js +141 -112
- package/module.js +37 -9
- package/package.json +1 -1
- package/pyodide.js +145 -157
- package/pyproxy.gen.js +35 -87
package/load-pyodide.js
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import { Module } from "./module.js";
|
|
2
2
|
|
|
3
3
|
const IN_NODE =
|
|
4
|
-
typeof process !== "undefined" &&
|
|
4
|
+
typeof process !== "undefined" &&
|
|
5
|
+
process.release &&
|
|
6
|
+
process.release.name === "node" &&
|
|
7
|
+
typeof process.browser ===
|
|
8
|
+
"undefined"; /* This last condition checks if we run the browser shim of process */
|
|
5
9
|
|
|
6
10
|
/** @typedef {import('./pyproxy.js').PyProxy} PyProxy */
|
|
7
11
|
/** @private */
|
|
@@ -14,7 +18,7 @@ export async function initializePackageIndex(indexURL) {
|
|
|
14
18
|
baseURL = indexURL;
|
|
15
19
|
let package_json;
|
|
16
20
|
if (IN_NODE) {
|
|
17
|
-
const fsPromises = await import("fs/promises");
|
|
21
|
+
const fsPromises = await import(/* webpackIgnore: true */ "fs/promises");
|
|
18
22
|
const package_string = await fsPromises.readFile(
|
|
19
23
|
`${indexURL}packages.json`
|
|
20
24
|
);
|
|
@@ -39,6 +43,17 @@ export async function initializePackageIndex(indexURL) {
|
|
|
39
43
|
}
|
|
40
44
|
}
|
|
41
45
|
|
|
46
|
+
export async function _fetchBinaryFile(indexURL, path) {
|
|
47
|
+
if (IN_NODE) {
|
|
48
|
+
const fsPromises = await import(/* webpackIgnore: true */ "fs/promises");
|
|
49
|
+
const tar_buffer = await fsPromises.readFile(`${indexURL}${path}`);
|
|
50
|
+
return tar_buffer.buffer;
|
|
51
|
+
} else {
|
|
52
|
+
let response = await fetch(`${indexURL}${path}`);
|
|
53
|
+
return await response.arrayBuffer();
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
42
57
|
////////////////////////////////////////////////////////////
|
|
43
58
|
// Package loading
|
|
44
59
|
const DEFAULT_CHANNEL = "default channel";
|
|
@@ -61,17 +76,21 @@ function _uri_to_package_name(package_uri) {
|
|
|
61
76
|
export let loadScript;
|
|
62
77
|
if (globalThis.document) {
|
|
63
78
|
// browser
|
|
64
|
-
loadScript = (url) => import(url);
|
|
79
|
+
loadScript = async (url) => await import(/* webpackIgnore: true */ url);
|
|
65
80
|
} else if (globalThis.importScripts) {
|
|
66
81
|
// webworker
|
|
67
82
|
loadScript = async (url) => {
|
|
68
83
|
// This is async only for consistency
|
|
69
84
|
globalThis.importScripts(url);
|
|
70
85
|
};
|
|
71
|
-
} else if (
|
|
72
|
-
const pathPromise = import("path").then(
|
|
86
|
+
} else if (IN_NODE) {
|
|
87
|
+
const pathPromise = import(/* webpackIgnore: true */ "path").then(
|
|
88
|
+
(M) => M.default
|
|
89
|
+
);
|
|
73
90
|
const fetchPromise = import("node-fetch").then((M) => M.default);
|
|
74
|
-
const vmPromise = import("vm").then(
|
|
91
|
+
const vmPromise = import(/* webpackIgnore: true */ "vm").then(
|
|
92
|
+
(M) => M.default
|
|
93
|
+
);
|
|
75
94
|
loadScript = async (url) => {
|
|
76
95
|
if (url.includes("://")) {
|
|
77
96
|
// If it's a url, have to load it with fetch and then eval it.
|
|
@@ -89,6 +108,23 @@ if (globalThis.document) {
|
|
|
89
108
|
throw new Error("Cannot determine runtime environment");
|
|
90
109
|
}
|
|
91
110
|
|
|
111
|
+
function addPackageToLoad(name, toLoad) {
|
|
112
|
+
name = name.toLowerCase();
|
|
113
|
+
if (toLoad.has(name)) {
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
toLoad.set(name, DEFAULT_CHANNEL);
|
|
117
|
+
// If the package is already loaded, we don't add dependencies, but warn
|
|
118
|
+
// the user later. This is especially important if the loaded package is
|
|
119
|
+
// from a custom url, in which case adding dependencies is wrong.
|
|
120
|
+
if (loadedPackages[name] !== undefined) {
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
for (let dep_name of Module.packages[name].depends) {
|
|
124
|
+
addPackageToLoad(dep_name, toLoad);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
92
128
|
function recursiveDependencies(
|
|
93
129
|
names,
|
|
94
130
|
_messageCallback,
|
|
@@ -96,23 +132,6 @@ function recursiveDependencies(
|
|
|
96
132
|
sharedLibsOnly
|
|
97
133
|
) {
|
|
98
134
|
const toLoad = new Map();
|
|
99
|
-
|
|
100
|
-
const addPackage = (name) => {
|
|
101
|
-
name = name.toLowerCase();
|
|
102
|
-
if (toLoad.has(name)) {
|
|
103
|
-
return;
|
|
104
|
-
}
|
|
105
|
-
toLoad.set(name, DEFAULT_CHANNEL);
|
|
106
|
-
// If the package is already loaded, we don't add dependencies, but warn
|
|
107
|
-
// the user later. This is especially important if the loaded package is
|
|
108
|
-
// from a custom url, in which case adding dependencies is wrong.
|
|
109
|
-
if (loadedPackages[name] !== undefined) {
|
|
110
|
-
return;
|
|
111
|
-
}
|
|
112
|
-
for (let dep_name of Module.packages[name].depends) {
|
|
113
|
-
addPackage(dep_name);
|
|
114
|
-
}
|
|
115
|
-
};
|
|
116
135
|
for (let name of names) {
|
|
117
136
|
const pkgname = _uri_to_package_name(name);
|
|
118
137
|
if (toLoad.has(pkgname) && toLoad.get(pkgname) !== name) {
|
|
@@ -129,7 +148,7 @@ function recursiveDependencies(
|
|
|
129
148
|
}
|
|
130
149
|
name = name.toLowerCase();
|
|
131
150
|
if (name in Module.packages) {
|
|
132
|
-
|
|
151
|
+
addPackageToLoad(name, toLoad);
|
|
133
152
|
continue;
|
|
134
153
|
}
|
|
135
154
|
errorCallback(`Skipping unknown package '${name}'`);
|
|
@@ -147,24 +166,46 @@ function recursiveDependencies(
|
|
|
147
166
|
return toLoad;
|
|
148
167
|
}
|
|
149
168
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
169
|
+
// locateFile is the function used by the .js file to locate the .data file
|
|
170
|
+
// given the filename
|
|
171
|
+
Module.locateFile = function (path) {
|
|
172
|
+
// handle packages loaded from custom URLs
|
|
173
|
+
let pkg = path.replace(/\.data$/, "");
|
|
174
|
+
const toLoad = Module.locateFile_packagesToLoad;
|
|
175
|
+
if (toLoad && toLoad.has(pkg)) {
|
|
176
|
+
let package_uri = toLoad.get(pkg);
|
|
177
|
+
if (package_uri != DEFAULT_CHANNEL) {
|
|
178
|
+
return package_uri.replace(/\.js$/, ".data");
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
return baseURL + path;
|
|
182
|
+
};
|
|
153
183
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
if (
|
|
162
|
-
|
|
184
|
+
// When the JS loads, it synchronously adds a runDependency to emscripten. It
|
|
185
|
+
// then loads the data file, and removes the runDependency from emscripten.
|
|
186
|
+
// This function returns a promise that resolves when there are no pending
|
|
187
|
+
// runDependencies.
|
|
188
|
+
function waitRunDependency() {
|
|
189
|
+
const promise = new Promise((r) => {
|
|
190
|
+
Module.monitorRunDependencies = (n) => {
|
|
191
|
+
if (n === 0) {
|
|
192
|
+
r();
|
|
163
193
|
}
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
|
|
194
|
+
};
|
|
195
|
+
});
|
|
196
|
+
// If there are no pending dependencies left, monitorRunDependencies will
|
|
197
|
+
// never be called. Since we can't check the number of dependencies,
|
|
198
|
+
// manually trigger a call.
|
|
199
|
+
Module.addRunDependency("dummy");
|
|
200
|
+
Module.removeRunDependency("dummy");
|
|
201
|
+
return promise;
|
|
202
|
+
}
|
|
167
203
|
|
|
204
|
+
async function _loadPackage(names, messageCallback, errorCallback) {
|
|
205
|
+
// toLoad is a map pkg_name => pkg_uri
|
|
206
|
+
let toLoad = recursiveDependencies(names, messageCallback, errorCallback);
|
|
207
|
+
// Tell Module.locateFile about the packages we're loading
|
|
208
|
+
Module.locateFile_packagesToLoad = toLoad;
|
|
168
209
|
if (toLoad.size === 0) {
|
|
169
210
|
return Promise.resolve("No new packages to load");
|
|
170
211
|
} else {
|
|
@@ -172,8 +213,8 @@ async function _loadPackage(names, messageCallback, errorCallback) {
|
|
|
172
213
|
messageCallback(`Loading ${packageNames}`);
|
|
173
214
|
}
|
|
174
215
|
|
|
175
|
-
// This is a collection of promises that resolve when the package's JS file
|
|
176
|
-
//
|
|
216
|
+
// This is a collection of promises that resolve when the package's JS file is
|
|
217
|
+
// loaded. The promises already handle error and never fail.
|
|
177
218
|
let scriptPromises = [];
|
|
178
219
|
|
|
179
220
|
for (let [pkg, uri] of toLoad) {
|
|
@@ -204,26 +245,6 @@ async function _loadPackage(names, messageCallback, errorCallback) {
|
|
|
204
245
|
);
|
|
205
246
|
}
|
|
206
247
|
|
|
207
|
-
// When the JS loads, it synchronously adds a runDependency to emscripten.
|
|
208
|
-
// It then loads the data file, and removes the runDependency from
|
|
209
|
-
// emscripten. This function returns a promise that resolves when there are
|
|
210
|
-
// no pending runDependencies.
|
|
211
|
-
function waitRunDependency() {
|
|
212
|
-
const promise = new Promise((r) => {
|
|
213
|
-
Module.monitorRunDependencies = (n) => {
|
|
214
|
-
if (n === 0) {
|
|
215
|
-
r();
|
|
216
|
-
}
|
|
217
|
-
};
|
|
218
|
-
});
|
|
219
|
-
// If there are no pending dependencies left, monitorRunDependencies will
|
|
220
|
-
// never be called. Since we can't check the number of dependencies,
|
|
221
|
-
// manually trigger a call.
|
|
222
|
-
Module.addRunDependency("dummy");
|
|
223
|
-
Module.removeRunDependency("dummy");
|
|
224
|
-
return promise;
|
|
225
|
-
}
|
|
226
|
-
|
|
227
248
|
// We must start waiting for runDependencies *after* all the JS files are
|
|
228
249
|
// loaded, since the number of runDependencies may happen to equal zero
|
|
229
250
|
// between package files loading.
|
|
@@ -253,13 +274,11 @@ async function _loadPackage(names, messageCallback, errorCallback) {
|
|
|
253
274
|
|
|
254
275
|
// We have to invalidate Python's import caches, or it won't
|
|
255
276
|
// see the new files.
|
|
256
|
-
Module.
|
|
257
|
-
"import importlib\n" + "importlib.invalidate_caches()\n"
|
|
258
|
-
);
|
|
277
|
+
Module.importlib.invalidate_caches();
|
|
259
278
|
}
|
|
260
279
|
|
|
261
|
-
// This is a promise that is resolved iff there are no pending package loads.
|
|
262
|
-
//
|
|
280
|
+
// This is a promise that is resolved iff there are no pending package loads. It
|
|
281
|
+
// never fails.
|
|
263
282
|
let _package_lock = Promise.resolve();
|
|
264
283
|
|
|
265
284
|
/**
|
|
@@ -286,6 +305,51 @@ async function acquirePackageLock() {
|
|
|
286
305
|
*/
|
|
287
306
|
export let loadedPackages = {};
|
|
288
307
|
|
|
308
|
+
let sharedLibraryWasmPlugin;
|
|
309
|
+
let origWasmPlugin;
|
|
310
|
+
let wasmPluginIndex;
|
|
311
|
+
function initSharedLibraryWasmPlugin() {
|
|
312
|
+
for (let p in Module.preloadPlugins) {
|
|
313
|
+
if (Module.preloadPlugins[p].canHandle("test.so")) {
|
|
314
|
+
origWasmPlugin = Module.preloadPlugins[p];
|
|
315
|
+
wasmPluginIndex = p;
|
|
316
|
+
break;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
sharedLibraryWasmPlugin = {
|
|
320
|
+
canHandle: origWasmPlugin.canHandle,
|
|
321
|
+
handle(byteArray, name, onload, onerror) {
|
|
322
|
+
origWasmPlugin.handle(byteArray, name, onload, onerror);
|
|
323
|
+
origWasmPlugin.asyncWasmLoadPromise = (async () => {
|
|
324
|
+
await origWasmPlugin.asyncWasmLoadPromise;
|
|
325
|
+
Module.loadDynamicLibrary(name, {
|
|
326
|
+
global: true,
|
|
327
|
+
nodelete: true,
|
|
328
|
+
});
|
|
329
|
+
})();
|
|
330
|
+
},
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// override the load plugin so that it calls "Module.loadDynamicLibrary" on any
|
|
335
|
+
// .so files.
|
|
336
|
+
// this only needs to be done for shared library packages because we assume that
|
|
337
|
+
// if a package depends on a shared library it needs to have access to it. not
|
|
338
|
+
// needed for .so in standard module because those are linked together
|
|
339
|
+
// correctly, it is only where linking goes across modules that it needs to be
|
|
340
|
+
// done. Hence, we only put this extra preload plugin in during the shared
|
|
341
|
+
// library load
|
|
342
|
+
function useSharedLibraryWasmPlugin() {
|
|
343
|
+
if (!sharedLibraryWasmPlugin) {
|
|
344
|
+
initSharedLibraryWasmPlugin();
|
|
345
|
+
}
|
|
346
|
+
Module.preloadPlugins[wasmPluginIndex] = sharedLibraryWasmPlugin;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
function restoreOrigWasmPlugin() {
|
|
350
|
+
Module.preloadPlugins[wasmPluginIndex] = origWasmPlugin;
|
|
351
|
+
}
|
|
352
|
+
|
|
289
353
|
/**
|
|
290
354
|
* @callback LogFn
|
|
291
355
|
* @param {string} msg
|
|
@@ -297,17 +361,17 @@ export let loadedPackages = {};
|
|
|
297
361
|
* Load a package or a list of packages over the network. This installs the
|
|
298
362
|
* package in the virtual filesystem. The package needs to be imported from
|
|
299
363
|
* Python before it can be used.
|
|
300
|
-
*
|
|
301
|
-
*
|
|
302
|
-
*
|
|
303
|
-
* ``<package-name>.js`` and there must be a file called
|
|
364
|
+
*
|
|
365
|
+
* @param {string | string[] | PyProxy} names Either a single package name or
|
|
366
|
+
* URL or a list of them. URLs can be absolute or relative. The URLs must have
|
|
367
|
+
* file name ``<package-name>.js`` and there must be a file called
|
|
304
368
|
* ``<package-name>.data`` in the same directory. The argument can be a
|
|
305
|
-
* ``PyProxy`` of a list, in which case the list will be converted to
|
|
306
|
-
*
|
|
369
|
+
* ``PyProxy`` of a list, in which case the list will be converted to JavaScript
|
|
370
|
+
* and the ``PyProxy`` will be destroyed.
|
|
307
371
|
* @param {LogFn=} messageCallback A callback, called with progress messages
|
|
308
372
|
* (optional)
|
|
309
|
-
* @param {LogFn=} errorCallback A callback, called with error/warning
|
|
310
|
-
*
|
|
373
|
+
* @param {LogFn=} errorCallback A callback, called with error/warning messages
|
|
374
|
+
* (optional)
|
|
311
375
|
* @async
|
|
312
376
|
*/
|
|
313
377
|
export async function loadPackage(names, messageCallback, errorCallback) {
|
|
@@ -340,58 +404,23 @@ export async function loadPackage(names, messageCallback, errorCallback) {
|
|
|
340
404
|
} catch (e) {
|
|
341
405
|
// do nothing - let the main load throw any errors
|
|
342
406
|
}
|
|
343
|
-
// override the load plugin so that it imports any dlls also
|
|
344
|
-
// this only needs to be done for shared library packages because
|
|
345
|
-
// we assume that if a package depends on a shared library
|
|
346
|
-
// it needs to have access to it.
|
|
347
|
-
// not needed for so in standard module because those are linked together
|
|
348
|
-
// correctly, it is only where linking goes across modules that it needs to
|
|
349
|
-
// be done. Hence we only put this extra preload plugin in during the shared
|
|
350
|
-
// library load
|
|
351
|
-
let oldPlugin;
|
|
352
|
-
for (let p in Module.preloadPlugins) {
|
|
353
|
-
if (Module.preloadPlugins[p].canHandle("test.so")) {
|
|
354
|
-
oldPlugin = Module.preloadPlugins[p];
|
|
355
|
-
break;
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
let dynamicLoadHandler = {
|
|
359
|
-
get: function (obj, prop) {
|
|
360
|
-
if (prop === "handle") {
|
|
361
|
-
return function (bytes, name) {
|
|
362
|
-
obj[prop].apply(obj, arguments);
|
|
363
|
-
this["asyncWasmLoadPromise"] = this["asyncWasmLoadPromise"].then(
|
|
364
|
-
function () {
|
|
365
|
-
Module.loadDynamicLibrary(name, {
|
|
366
|
-
global: true,
|
|
367
|
-
nodelete: true,
|
|
368
|
-
});
|
|
369
|
-
}
|
|
370
|
-
);
|
|
371
|
-
};
|
|
372
|
-
} else {
|
|
373
|
-
return obj[prop];
|
|
374
|
-
}
|
|
375
|
-
},
|
|
376
|
-
};
|
|
377
|
-
var loadPluginOverride = new Proxy(oldPlugin, dynamicLoadHandler);
|
|
378
|
-
// restore the preload plugin
|
|
379
|
-
Module.preloadPlugins.unshift(loadPluginOverride);
|
|
380
407
|
|
|
381
408
|
let releaseLock = await acquirePackageLock();
|
|
382
409
|
try {
|
|
410
|
+
useSharedLibraryWasmPlugin();
|
|
383
411
|
await _loadPackage(
|
|
384
412
|
sharedLibraryNames,
|
|
385
413
|
messageCallback || console.log,
|
|
386
414
|
errorCallback || console.error
|
|
387
415
|
);
|
|
388
|
-
|
|
416
|
+
restoreOrigWasmPlugin();
|
|
389
417
|
await _loadPackage(
|
|
390
418
|
names,
|
|
391
419
|
messageCallback || console.log,
|
|
392
420
|
errorCallback || console.error
|
|
393
421
|
);
|
|
394
422
|
} finally {
|
|
423
|
+
restoreOrigWasmPlugin();
|
|
395
424
|
releaseLock();
|
|
396
425
|
}
|
|
397
426
|
}
|
package/module.js
CHANGED
|
@@ -1,19 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {import('emscripten').Module} Module
|
|
3
|
+
*/
|
|
4
|
+
|
|
1
5
|
/**
|
|
2
6
|
* The Emscripten Module.
|
|
3
7
|
*
|
|
4
|
-
* @private
|
|
8
|
+
* @private
|
|
9
|
+
* @type {Module}
|
|
5
10
|
*/
|
|
6
11
|
export let Module = {};
|
|
7
12
|
Module.noImageDecoding = true;
|
|
8
13
|
Module.noAudioDecoding = true;
|
|
9
14
|
Module.noWasmDecoding = false; // we preload wasm using the built in plugin now
|
|
10
15
|
Module.preloadedWasm = {};
|
|
16
|
+
Module.preRun = [];
|
|
11
17
|
|
|
12
18
|
/**
|
|
13
19
|
*
|
|
14
|
-
* @param {undefined|(
|
|
15
|
-
* @param {undefined|(
|
|
16
|
-
* @param {undefined|(
|
|
20
|
+
* @param {undefined | function(): string} stdin
|
|
21
|
+
* @param {undefined | function(string)} stdout
|
|
22
|
+
* @param {undefined | function(string)} stderr
|
|
23
|
+
* @private
|
|
17
24
|
*/
|
|
18
25
|
export function setStandardStreams(stdin, stdout, stderr) {
|
|
19
26
|
// For stdout and stderr, emscripten provides convenient wrappers that save us the trouble of converting the bytes into a string
|
|
@@ -27,11 +34,9 @@ export function setStandardStreams(stdin, stdout, stderr) {
|
|
|
27
34
|
|
|
28
35
|
// For stdin, we have to deal with the low level API ourselves
|
|
29
36
|
if (stdin) {
|
|
30
|
-
Module.preRun
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
},
|
|
34
|
-
];
|
|
37
|
+
Module.preRun.push(function () {
|
|
38
|
+
Module.FS.init(createStdinWrapper(stdin), null, null);
|
|
39
|
+
});
|
|
35
40
|
}
|
|
36
41
|
}
|
|
37
42
|
|
|
@@ -79,3 +84,26 @@ function createStdinWrapper(stdin) {
|
|
|
79
84
|
}
|
|
80
85
|
return stdinWrapper;
|
|
81
86
|
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Make the home directory inside the virtual file system,
|
|
90
|
+
* then change the working directory to it.
|
|
91
|
+
*
|
|
92
|
+
* @param {string} path
|
|
93
|
+
* @private
|
|
94
|
+
*/
|
|
95
|
+
export function setHomeDirectory(path) {
|
|
96
|
+
Module.preRun.push(function () {
|
|
97
|
+
const fallbackPath = "/";
|
|
98
|
+
try {
|
|
99
|
+
Module.FS.mkdirTree(path);
|
|
100
|
+
} catch (e) {
|
|
101
|
+
console.error(`Error occurred while making a home directory '${path}':`);
|
|
102
|
+
console.error(e);
|
|
103
|
+
console.error(`Using '${fallbackPath}' for a home directory instead`);
|
|
104
|
+
path = fallbackPath;
|
|
105
|
+
}
|
|
106
|
+
Module.ENV.HOME = path;
|
|
107
|
+
Module.FS.chdir(path);
|
|
108
|
+
});
|
|
109
|
+
}
|