pyodide 0.18.0 → 0.19.0

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/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" && process.release.name !== "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 (typeof process !== "undefined" && process.release.name === "node") {
72
- const pathPromise = import("path").then((M) => M.default);
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((M) => M.default);
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
- addPackage(name);
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
- async function _loadPackage(names, messageCallback, errorCallback) {
151
- // toLoad is a map pkg_name => pkg_uri
152
- let toLoad = recursiveDependencies(names, messageCallback, errorCallback);
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
- // locateFile is the function used by the .js file to locate the .data
155
- // file given the filename
156
- Module.locateFile = (path) => {
157
- // handle packages loaded from custom URLs
158
- let pkg = path.replace(/\.data$/, "");
159
- if (toLoad.has(pkg)) {
160
- let package_uri = toLoad.get(pkg);
161
- if (package_uri != DEFAULT_CHANNEL) {
162
- return package_uri.replace(/\.js$/, ".data");
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
- return baseURL + path;
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
- // is loaded. The promises already handle error and never fail.
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.runPythonSimple(
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
- // It never fails.
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
- * @param {string | string[] | PyProxy} names Either a single package name or URL
301
- * or a list of them. URLs can be absolute or relative. The URLs must have
302
- * file name
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
- * Javascript and the ``PyProxy`` will be destroyed.
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
- * messages (optional)
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
- Module.preloadPlugins.shift(loadPluginOverride);
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 @type {import('emscripten').Module}
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|(() => string)} stdin
15
- * @param {undefined|((text: string) => void)} stdout
16
- * @param {undefined|((text: string) => void)} stderr
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
- function () {
32
- Module.FS.init(createStdinWrapper(stdin), null, null);
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
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pyodide",
3
- "version": "0.18.0",
3
+ "version": "0.19.0",
4
4
  "description": "The Pyodide JavaScript package",
5
5
  "keywords": [
6
6
  "python",