Skip to content

Commit fab2143

Browse files
authored
Add support for loading dynamic libraries from python_modules (#5249)
1 parent 5c9651c commit fab2143

File tree

11 files changed

+71
-13
lines changed

11 files changed

+71
-13
lines changed

build/python_metadata.bzl

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -109,8 +109,8 @@ BUNDLE_VERSION_INFO = _make_bundle_version_info([
109109
"pyodide_version": "0.26.0a2",
110110
"pyodide_date": "2024-03-01",
111111
"packages": PACKAGES_20240829_4,
112-
"backport": "63",
113-
"integrity": "sha256-xrG65VJvao9GYH07C73Uq2jA9DW7O1DP16fiZo36Xq0=",
112+
"backport": "75",
113+
"integrity": "sha256-E1OCAgukWZU6gchqkxgYlvMSieJ8xz6aPFhN/KeqhTw=",
114114
"flag": "pythonWorkers",
115115
"enable_flag_name": "python_workers",
116116
"emscripten_version": "3.1.52",
@@ -139,15 +139,20 @@ BUNDLE_VERSION_INFO = _make_bundle_version_info([
139139
"abi": None,
140140
"sha256": "fc4fb50f73973c257277155b3cb113aa2cf68e9da8ef424ecb049b41bc463183",
141141
},
142+
{
143+
"name": "scipy",
144+
"abi": "3.12",
145+
"sha256": "787e45be6969a5609093b3df9cc2dba2afec9e10bace977f5045697cc329aa7c",
146+
},
142147
],
143148
},
144149
{
145150
"name": "0.28.2",
146151
"pyodide_version": "0.28.2",
147152
"pyodide_date": "2025-01-16",
148153
"packages": PACKAGES_20250808,
149-
"backport": "3",
150-
"integrity": "sha256-SCMwCLKzdE65vBQmdeUPs1enbE8TzOu57LBupZzwJY4=",
154+
"backport": "6",
155+
"integrity": "sha256-bsCa4xEXjgtTDJu7h28GzjJEGqCVawPMqlQqkauCVp8=",
151156
"flag": "pythonWorkers20250116",
152157
"enable_flag_name": "python_workers_20250116",
153158
"emscripten_version": "4.0.9",
@@ -175,6 +180,11 @@ BUNDLE_VERSION_INFO = _make_bundle_version_info([
175180
"abi": None,
176181
"sha256": "fc4fb50f73973c257277155b3cb113aa2cf68e9da8ef424ecb049b41bc463183",
177182
},
183+
{
184+
"name": "scipy",
185+
"abi": "3.13",
186+
"sha256": "4f1b6fc179bd5c6d3de68abc4aa9fca2aaecd09c5c8d357c2ecfedce7d621f3d",
187+
},
178188
],
179189
},
180190
{

src/pyodide/create_vendor_zip.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,6 @@ def run_pywrangler_sync(work_dir: Path, python: str | None) -> Path:
4949
# TODO: Make pywrangler understand how to use Python 3.13 correctly and
5050
# remove these extra commands
5151
run(["uv", "venv"], cwd=work_dir, env=env)
52-
run(["uv", "pip", "install", "pyodide-build"], cwd=work_dir, env=env)
53-
run(["uv", "run", "pyodide", "xbuildenv", "install"], cwd=work_dir, env=env)
5452
run(["uv", "run", "pywrangler", "sync"], cwd=work_dir, env=env)
5553
python_modules_dir = work_dir / "python_modules"
5654
if not python_modules_dir.exists():

src/pyodide/internal/pool/builtin_wrappers.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,11 +45,24 @@ export function reportUndefinedSymbolsPatched(Module: Module): void {
4545
}
4646

4747
export function patchDynlibLookup(Module: Module, libName: string): Uint8Array {
48+
// This function is for 0.26.0a2 only. In newer versions, we set LD_LIBRARY_PATH instead.
49+
if (Module.API.version !== '0.26.0a2') {
50+
throw new Error('Should not happen');
51+
}
4852
try {
4953
return Module.FS.readFile('/usr/lib/' + libName);
5054
} catch (e) {
51-
console.error('Failed to read ', libName, e);
52-
throw e;
55+
try {
56+
// For scipy and similar libraries that depend on Pyodide's dynamic library deps, we may need
57+
// extra "system libraries". These we'll put in python_modules/lib. So try loading system
58+
// libraries from there too.
59+
return Module.FS.readFile(
60+
'/session/metadata/python_modules/lib/' + libName
61+
);
62+
} catch (e) {
63+
console.error('Failed to read ', libName, e);
64+
throw e;
65+
}
5366
}
5467
}
5568

src/pyodide/internal/pool/emscriptenSetup.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -77,9 +77,12 @@ function getPrepareFileSystem(pythonStdlib: ArrayBuffer): PreRunHook {
7777
Module.API.pyVersionTuple = computeVersionTuple(Module);
7878
const [pymajor, pyminor] = Module.API.pyVersionTuple;
7979
Module.FS.sitePackages = `/lib/python${pymajor}.${pyminor}/site-packages`;
80-
// finalizeBootstrap() will set LD_LIBRARY_PATH to this same value in a bit, but it's too late
81-
// for us when we preload dynamic libraries.
82-
Module.ENV.LD_LIBRARY_PATH = ['/usr/lib', Module.FS.sitePackages].join(':');
80+
Module.LD_LIBRARY_PATH = [
81+
'/usr/lib',
82+
Module.FS.sitePackages,
83+
'/session/metadata/python_modules/lib/',
84+
].join(':');
85+
Module.ENV.LD_LIBRARY_PATH = Module.LD_LIBRARY_PATH;
8386
Module.FS.sessionSitePackages = '/session' + Module.FS.sitePackages;
8487
Module.FS.mkdirTree(Module.FS.sitePackages);
8588
Module.FS.writeFile(

src/pyodide/internal/snapshot.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -810,6 +810,11 @@ export function finalizeBootstrap(
810810
customHiwireStateDeserializer
811811
);
812812
});
813+
// finalizeBootstrap overrides LD_LIBRARY_PATH. Restore it.
814+
simpleRunPython(
815+
Module,
816+
`import os; os.environ["LD_LIBRARY_PATH"] += ":/session/metadata/python_modules/lib/"; del os`
817+
);
813818
if (IS_CREATING_SNAPSHOT) {
814819
return;
815820
}

src/pyodide/types/emscripten.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,4 +125,5 @@ interface Module {
125125
snapshotDebug?: boolean;
126126
getEmptyTableSlot(): number;
127127
freeTableIndexes: number[];
128+
LD_LIBRARY_PATH: string;
128129
}

src/workerd/server/tests/python/vendor_pkg_tests/BUILD

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,5 @@ vendored_py_wd_test("beautifulsoup4")
77
vendored_py_wd_test(
88
"python-workers-runtime-sdk",
99
)
10+
11+
vendored_py_wd_test("scipy")

src/workerd/server/tests/python/vendor_pkg_tests/generate_modules.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
# without `external/` prefix
2828
embed_path = ("../" * 7) + str(Path(*components[1:])).replace("\\", "\\\\")
2929
# without `external/fastapi_src/` prefix
30-
module_path = str(Path(*components[2:])).replace("\\", "/")
30+
module_path = str(Path("python_modules", *components[2:])).replace("\\", "/")
3131

3232
# Format as a Cap'n Proto module definition.
3333
if f_path.endswith(".py"):
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import numpy as np
2+
from scipy import linalg
3+
4+
5+
async def test():
6+
c = np.array([1, 3, 6, 10])
7+
r = np.array([1, -1, -2, -3])
8+
b = np.array([1, 2, 2, 5])
9+
res = linalg.solve_toeplitz((c, r), b)
10+
assert repr(res) == "array([ 1.66666667, -1. , -2.66666667, 2.33333333])"
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
using Workerd = import "/workerd/workerd.capnp";
2+
3+
const unitTests :Workerd.Config = (
4+
services = [
5+
( name = "scipy-vendor-test",
6+
worker = (
7+
modules = [
8+
(name = "main.py", pythonModule = embed "scipy.py"),
9+
%PYTHON_VENDORED_MODULES%
10+
],
11+
compatibilityDate = "2024-01-15",
12+
compatibilityFlags = [%PYTHON_FEATURE_FLAGS],
13+
)
14+
),
15+
],
16+
);

0 commit comments

Comments
 (0)