Add Pyodide support (#12731)

This includes some initial work on adding Pyodide support (issue
#12729). It is enough to get
```
uv pip compile -p /path/to/pyodide --extra-index-url file:/path/to/simple-index
```
to work which should already be quite useful.

## Test Plan

* added a unit test for `pyodide_platform`
* integration tested manually with:
```
cargo run pip install \
-p /home/rchatham/Documents/programming/tmp/pyodide-venv-test/.pyodide-xbuildenv-0.29.3/0.27.4/xbuildenv/pyodide-root/dist/python \
--extra-index-url file:/home/rchatham/Documents/programming/tmp/pyodide-venv-test/.pyodide-xbuildenv-0.29.3/0.27.4/xbuildenv/pyodide-root/package_index \
--index-strategy unsafe-best-match --target blah --no-build \
numpy pydantic
```

---------

Co-authored-by: konsti <konstin@mailbox.org>
Co-authored-by: Zanie Blue <contact@zanie.dev>
This commit is contained in:
Hood Chatham 2025-06-03 10:01:26 -07:00 committed by GitHub
parent 37e22e4da6
commit f9d3f24728
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 168 additions and 2 deletions

View file

@ -1294,6 +1294,45 @@ jobs:
run: |
.\uv.exe pip install anyio
integration-test-pyodide-linux:
timeout-minutes: 10
needs: build-binary-linux-libc
name: "integration test | pyodide on ubuntu"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: "Download binary"
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
with:
name: uv-linux-libc-${{ github.sha }}
- name: "Prepare binary"
run: chmod +x ./uv
- name: "Create a native virtual environment"
run: |
./uv venv venv-native -p 3.12
# We use features added in 0.30.3 but there is no known breakage in
# newer versions.
./uv pip install -p venv-native/bin/python pyodide-build==0.30.3 pip
- name: "Install pyodide interpreter"
run: |
source ./venv-native/bin/activate
pyodide xbuildenv install 0.27.5
PYODIDE_PYTHON=$(pyodide config get interpreter)
PYODIDE_INDEX=$(pyodide config get package_index)
echo "PYODIDE_PYTHON=$PYODIDE_PYTHON" >> $GITHUB_ENV
echo "PYODIDE_INDEX=$PYODIDE_INDEX" >> $GITHUB_ENV
- name: "Create pyodide virtual environment"
run: |
./uv venv -p $PYODIDE_PYTHON venv-pyodide
source ./venv-pyodide/bin/activate
./uv pip install --extra-index-url=$PYODIDE_INDEX --no-build numpy
python -c 'import numpy'
integration-test-github-actions:
timeout-minutes: 10
needs: build-binary-linux-libc

View file

@ -226,6 +226,10 @@ pub enum TargetTriple {
#[serde(rename = "aarch64-manylinux_2_40")]
#[serde(alias = "aarch64-manylinux240")]
Aarch64Manylinux240,
/// A wasm32 target using the the Pyodide 2024 platform. Meant for use with Python 3.12.
#[cfg_attr(feature = "clap", value(name = "wasm32-pyodide2024"))]
Wasm32Pyodide2024,
}
impl TargetTriple {
@ -450,6 +454,13 @@ impl TargetTriple {
},
Arch::Aarch64,
),
Self::Wasm32Pyodide2024 => Platform::new(
Os::Pyodide {
major: 2024,
minor: 0,
},
Arch::Wasm32,
),
}
}
@ -490,6 +501,7 @@ impl TargetTriple {
Self::Aarch64Manylinux238 => "aarch64",
Self::Aarch64Manylinux239 => "aarch64",
Self::Aarch64Manylinux240 => "aarch64",
Self::Wasm32Pyodide2024 => "wasm32",
}
}
@ -530,6 +542,7 @@ impl TargetTriple {
Self::Aarch64Manylinux238 => "Linux",
Self::Aarch64Manylinux239 => "Linux",
Self::Aarch64Manylinux240 => "Linux",
Self::Wasm32Pyodide2024 => "Emscripten",
}
}
@ -570,6 +583,10 @@ impl TargetTriple {
Self::Aarch64Manylinux238 => "",
Self::Aarch64Manylinux239 => "",
Self::Aarch64Manylinux240 => "",
// This is the value Emscripten gives for its version:
// https://github.com/emscripten-core/emscripten/blob/4.0.8/system/lib/libc/emscripten_syscall_stubs.c#L63
// It doesn't really seem to mean anything? But for completeness we include it here.
Self::Wasm32Pyodide2024 => "#1",
}
}
@ -610,6 +627,9 @@ impl TargetTriple {
Self::Aarch64Manylinux238 => "",
Self::Aarch64Manylinux239 => "",
Self::Aarch64Manylinux240 => "",
// This is the Emscripten compiler version for Pyodide 2024.
// See https://pyodide.org/en/stable/development/abi.html#pyodide-2024-0
Self::Wasm32Pyodide2024 => "3.1.58",
}
}
@ -650,6 +670,7 @@ impl TargetTriple {
Self::Aarch64Manylinux238 => "posix",
Self::Aarch64Manylinux239 => "posix",
Self::Aarch64Manylinux240 => "posix",
Self::Wasm32Pyodide2024 => "posix",
}
}
@ -690,6 +711,7 @@ impl TargetTriple {
Self::Aarch64Manylinux238 => "linux",
Self::Aarch64Manylinux239 => "linux",
Self::Aarch64Manylinux240 => "linux",
Self::Wasm32Pyodide2024 => "emscripten",
}
}
@ -730,6 +752,7 @@ impl TargetTriple {
Self::Aarch64Manylinux238 => true,
Self::Aarch64Manylinux239 => true,
Self::Aarch64Manylinux240 => true,
Self::Wasm32Pyodide2024 => false,
}
}

View file

@ -43,6 +43,7 @@ pub enum Os {
Manylinux { major: u16, minor: u16 },
Musllinux { major: u16, minor: u16 },
Windows,
Pyodide { major: u16, minor: u16 },
Macos { major: u16, minor: u16 },
FreeBsd { release: String },
NetBsd { release: String },
@ -67,6 +68,7 @@ impl fmt::Display for Os {
Self::Illumos { .. } => write!(f, "illumos"),
Self::Haiku { .. } => write!(f, "haiku"),
Self::Android { .. } => write!(f, "android"),
Self::Pyodide { .. } => write!(f, "pyodide"),
}
}
}
@ -109,6 +111,7 @@ pub enum Arch {
S390X,
LoongArch64,
Riscv64,
Wasm32,
}
impl fmt::Display for Arch {
@ -126,6 +129,7 @@ impl fmt::Display for Arch {
Self::S390X => write!(f, "s390x"),
Self::LoongArch64 => write!(f, "loongarch64"),
Self::Riscv64 => write!(f, "riscv64"),
Self::Wasm32 => write!(f, "wasm32"),
}
}
}
@ -168,7 +172,7 @@ impl Arch {
// manylinux_2_36
Self::LoongArch64 => Some(36),
// unsupported
Self::Powerpc | Self::Armv5TEL | Self::Armv6L => None,
Self::Powerpc | Self::Armv5TEL | Self::Armv6L | Self::Wasm32 => None,
}
}
@ -187,6 +191,7 @@ impl Arch {
Self::S390X => "s390x",
Self::LoongArch64 => "loongarch64",
Self::Riscv64 => "riscv64",
Self::Wasm32 => "wasm32",
}
}

View file

@ -71,6 +71,8 @@ pub enum PlatformTag {
Illumos { release_arch: SmallString },
/// Ex) `solaris_11_4_x86_64`
Solaris { release_arch: SmallString },
/// Ex) `pyodide_2024_0_wasm32`
Pyodide { major: u16, minor: u16 },
}
impl PlatformTag {
@ -97,6 +99,7 @@ impl PlatformTag {
PlatformTag::Haiku { .. } => Some("Haiku"),
PlatformTag::Illumos { .. } => Some("Illumos"),
PlatformTag::Solaris { .. } => Some("Solaris"),
PlatformTag::Pyodide { .. } => Some("Pyodide"),
}
}
}
@ -262,6 +265,7 @@ impl std::fmt::Display for PlatformTag {
Self::Haiku { release_arch } => write!(f, "haiku_{release_arch}"),
Self::Illumos { release_arch } => write!(f, "illumos_{release_arch}"),
Self::Solaris { release_arch } => write!(f, "solaris_{release_arch}_64bit"),
Self::Pyodide { major, minor } => write!(f, "pyodide_{major}_{minor}_wasm32"),
}
}
}
@ -616,6 +620,35 @@ impl FromStr for PlatformTag {
});
}
if let Some(rest) = s.strip_prefix("pyodide_") {
let mid =
rest.strip_suffix("_wasm32")
.ok_or_else(|| ParsePlatformTagError::InvalidArch {
platform: "pyodide",
tag: s.to_string(),
})?;
let underscore = memchr::memchr(b'_', mid.as_bytes()).ok_or_else(|| {
ParsePlatformTagError::InvalidFormat {
platform: "pyodide",
tag: s.to_string(),
}
})?;
let major: u16 = mid[..underscore].parse().map_err(|_| {
ParsePlatformTagError::InvalidMajorVersion {
platform: "pyodide",
tag: s.to_string(),
}
})?;
let minor: u16 = mid[underscore + 1..].parse().map_err(|_| {
ParsePlatformTagError::InvalidMinorVersion {
platform: "pyodide",
tag: s.to_string(),
}
})?;
return Ok(Self::Pyodide { major, minor });
}
Err(ParsePlatformTagError::UnknownFormat(s.to_string()))
}
}
@ -900,6 +933,27 @@ mod tests {
);
}
#[test]
fn pyodide_platform() {
let tag = PlatformTag::Pyodide {
major: 2024,
minor: 0,
};
assert_eq!(
PlatformTag::from_str("pyodide_2024_0_wasm32").as_ref(),
Ok(&tag)
);
assert_eq!(tag.to_string(), "pyodide_2024_0_wasm32");
assert_eq!(
PlatformTag::from_str("pyodide_2024_0_wasm64"),
Err(ParsePlatformTagError::InvalidArch {
platform: "pyodide",
tag: "pyodide_2024_0_wasm64".to_string()
})
);
}
#[test]
fn unknown_platform() {
assert_eq!(

View file

@ -617,6 +617,12 @@ fn compatible_tags(platform: &Platform) -> Result<Vec<PlatformTag>, PlatformErro
arch,
}]
}
(Os::Pyodide { major, minor }, Arch::Wasm32) => {
vec![PlatformTag::Pyodide {
major: *major,
minor: *minor,
}]
}
_ => {
return Err(PlatformError::OsVersionDetectionError(format!(
"Unsupported operating system and architecture combination: {os} {arch}"

View file

@ -510,6 +510,24 @@ def get_operating_system_and_architecture():
"major": int(version[0]),
"minor": int(version[1]),
}
elif operating_system == "emscripten":
pyodide_abi_version = sysconfig.get_config_var("PYODIDE_ABI_VERSION")
if not pyodide_abi_version:
print(
json.dumps(
{
"result": "error",
"kind": "emscripten_not_pyodide",
}
)
)
sys.exit(0)
version = pyodide_abi_version.split("_")
operating_system = {
"name": "pyodide",
"major": int(version[0]),
"minor": int(version[1]),
}
elif operating_system in [
"freebsd",
"netbsd",

View file

@ -757,6 +757,8 @@ pub enum InterpreterInfoError {
python_major: usize,
python_minor: usize,
},
#[error("Only Pyodide is support for Emscripten Python")]
EmscriptenNotPyodide,
}
#[derive(Debug, Deserialize, Serialize, Clone)]

View file

@ -348,6 +348,10 @@ impl From<&uv_platform_tags::Arch> for Arch {
),
variant: None,
},
uv_platform_tags::Arch::Wasm32 => Self {
family: target_lexicon::Architecture::Wasm32,
variant: None,
},
}
}
}
@ -380,6 +384,9 @@ impl From<&uv_platform_tags::Os> for Os {
uv_platform_tags::Os::NetBsd { .. } => Self(target_lexicon::OperatingSystem::Netbsd),
uv_platform_tags::Os::OpenBsd { .. } => Self(target_lexicon::OperatingSystem::Openbsd),
uv_platform_tags::Os::Windows => Self(target_lexicon::OperatingSystem::Windows),
uv_platform_tags::Os::Pyodide { .. } => {
Self(target_lexicon::OperatingSystem::Emscripten)
}
}
}
}

View file

@ -220,7 +220,8 @@ impl TorchStrategy {
| Os::Dragonfly { .. }
| Os::Illumos { .. }
| Os::Haiku { .. }
| Os::Android { .. } => {
| Os::Android { .. }
| Os::Pyodide { .. } => {
Either::Right(std::iter::once(TorchBackend::Cpu.index_url()))
}
}

View file

@ -1702,6 +1702,7 @@ interpreter. Use <code>--universal</code> to display the tree for all platforms,
<li><code>aarch64-manylinux_2_38</code>: An ARM64 target for the <code>manylinux_2_38</code> platform</li>
<li><code>aarch64-manylinux_2_39</code>: An ARM64 target for the <code>manylinux_2_39</code> platform</li>
<li><code>aarch64-manylinux_2_40</code>: An ARM64 target for the <code>manylinux_2_40</code> platform</li>
<li><code>wasm32-pyodide2024</code>: A wasm32 target using the the Pyodide 2024 platform. Meant for use with Python 3.12</li>
</ul></dd><dt id="uv-tree--python-version"><a href="#uv-tree--python-version"><code>--python-version</code></a> <i>python-version</i></dt><dd><p>The Python version to use when filtering the tree.</p>
<p>For example, pass <code>--python-version 3.10</code> to display the dependencies that would be included when installing on Python 3.10.</p>
<p>Defaults to the version of the discovered Python interpreter.</p>
@ -3295,6 +3296,7 @@ by <code>--python-version</code>.</p>
<li><code>aarch64-manylinux_2_38</code>: An ARM64 target for the <code>manylinux_2_38</code> platform</li>
<li><code>aarch64-manylinux_2_39</code>: An ARM64 target for the <code>manylinux_2_39</code> platform</li>
<li><code>aarch64-manylinux_2_40</code>: An ARM64 target for the <code>manylinux_2_40</code> platform</li>
<li><code>wasm32-pyodide2024</code>: A wasm32 target using the the Pyodide 2024 platform. Meant for use with Python 3.12</li>
</ul></dd><dt id="uv-pip-compile--python-version"><a href="#uv-pip-compile--python-version"><code>--python-version</code></a> <i>python-version</i></dt><dd><p>The Python version to use for resolution.</p>
<p>For example, <code>3.8</code> or <code>3.8.17</code>.</p>
<p>Defaults to the version of the Python interpreter used for resolution.</p>
@ -3533,6 +3535,7 @@ be used with caution, as it can modify the system Python installation.</p>
<li><code>aarch64-manylinux_2_38</code>: An ARM64 target for the <code>manylinux_2_38</code> platform</li>
<li><code>aarch64-manylinux_2_39</code>: An ARM64 target for the <code>manylinux_2_39</code> platform</li>
<li><code>aarch64-manylinux_2_40</code>: An ARM64 target for the <code>manylinux_2_40</code> platform</li>
<li><code>wasm32-pyodide2024</code>: A wasm32 target using the the Pyodide 2024 platform. Meant for use with Python 3.12</li>
</ul></dd><dt id="uv-pip-sync--python-version"><a href="#uv-pip-sync--python-version"><code>--python-version</code></a> <i>python-version</i></dt><dd><p>The minimum Python version that should be supported by the requirements (e.g., <code>3.7</code> or <code>3.7.9</code>).</p>
<p>If a patch version is omitted, the minimum patch version is assumed. For example, <code>3.7</code> is mapped to <code>3.7.0</code>.</p>
</dd><dt id="uv-pip-sync--quiet"><a href="#uv-pip-sync--quiet"><code>--quiet</code></a>, <code>-q</code></dt><dd><p>Use quiet output.</p>
@ -3796,6 +3799,7 @@ should be used with caution, as it can modify the system Python installation.</p
<li><code>aarch64-manylinux_2_38</code>: An ARM64 target for the <code>manylinux_2_38</code> platform</li>
<li><code>aarch64-manylinux_2_39</code>: An ARM64 target for the <code>manylinux_2_39</code> platform</li>
<li><code>aarch64-manylinux_2_40</code>: An ARM64 target for the <code>manylinux_2_40</code> platform</li>
<li><code>wasm32-pyodide2024</code>: A wasm32 target using the the Pyodide 2024 platform. Meant for use with Python 3.12</li>
</ul></dd><dt id="uv-pip-install--python-version"><a href="#uv-pip-install--python-version"><code>--python-version</code></a> <i>python-version</i></dt><dd><p>The minimum Python version that should be supported by the requirements (e.g., <code>3.7</code> or <code>3.7.9</code>).</p>
<p>If a patch version is omitted, the minimum patch version is assumed. For example, <code>3.7</code> is mapped to <code>3.7.0</code>.</p>
</dd><dt id="uv-pip-install--quiet"><a href="#uv-pip-install--quiet"><code>--quiet</code></a>, <code>-q</code></dt><dd><p>Use quiet output.</p>

7
uv.schema.json generated
View file

@ -2338,6 +2338,13 @@
"enum": [
"aarch64-manylinux_2_40"
]
},
{
"description": "A wasm32 target using the the Pyodide 2024 platform. Meant for use with Python 3.12.",
"type": "string",
"enum": [
"wasm32-pyodide2024"
]
}
]
},