Skip to content

Commit

Permalink
Add support for wasm32-unknown-emscripten target
Browse files Browse the repository at this point in the history
  • Loading branch information
messense committed Jun 19, 2022
1 parent 7108fdd commit fb7e327
Show file tree
Hide file tree
Showing 8 changed files with 177 additions and 23 deletions.
22 changes: 22 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,28 @@ jobs:
export PYO3_CONFIG_FILE=$(pwd)/test-crates/pyo3-mixed/pyo3-config.txt
cargo run -- build -m test-crates/pyo3-mixed/Cargo.toml --target x86_64-unknown-linux-gnu --zig
test-emscripten:
name: Test Emscripten
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: nightly
target: wasm32-unknown-emscripten
override: true
- uses: mymindstorm/setup-emsdk@v11
with:
version: 3.1.13
actions-cache-folder: emsdk-cache
- uses: Swatinem/rust-cache@v1
- name: Build wheels
run: |
set -ex
cargo run build -m test-crates/pyo3-mixed/Cargo.toml -o dist --target wasm32-unknown-emscripten -i python3.11
# TODO: Install Pyodide and run tests

test-alpine:
name: Test Alpine Linux
runs-on: ubuntu-latest
Expand Down
2 changes: 1 addition & 1 deletion MANIFEST.in
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
include Cargo.toml Cargo.lock
include Readme.md
include license-apache license-mit
recursive-include src *.rs
recursive-include src *.rs *.py
recursive-include src/auditwheel *.json
recursive-include src/python_interpreter *.py *.json
recursive-include src/templates *.j2
1 change: 1 addition & 0 deletions src/auditwheel/policy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ impl Policy {
Arch::X86 => "libc.musl-x86.so.1",
Arch::X86_64 => "libc.musl-x86_64.so.1",
Arch::S390X => "libc.musl-s390x.so.1",
_ => "",
};
if !new_soname.is_empty() {
self.lib_whitelist.insert(new_soname.to_string());
Expand Down
44 changes: 43 additions & 1 deletion src/compile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ use fat_macho::FatWriter;
use fs_err::{self as fs, File};
use std::collections::HashMap;
use std::env;
use std::io::{BufReader, Read};
use std::io::{BufReader, Read, Write};
#[cfg(unix)]
use std::os::unix::fs::PermissionsExt;
use std::path::{Path, PathBuf};
use std::process::Stdio;
use std::str;
Expand All @@ -14,6 +16,8 @@ use std::str;
/// without `PYO3_NO_PYTHON` environment variable
const PYO3_ABI3_NO_PYTHON_VERSION: (u64, u64, u64) = (0, 16, 4);

const EMCC_WRAPPER: &str = include_str!("emcc_wrapper.py");

/// Builds the rust crate into a native module (i.e. an .so or .dll) for a
/// specific python version. Returns a mapping from crate type (e.g. cdylib)
/// to artifact location.
Expand Down Expand Up @@ -214,6 +218,22 @@ fn compile_target(
];
rustc_args.extend(mac_args);
}
} else if target.is_emscripten() {
shared_args.push("-Zbuild-std");
rust_flags
.get_or_insert_with(Default::default)
.push(" -C relocation-model=pic");
let emscripten_args = &[
"-C",
"relocation-model=pic",
"-C",
"target-feature=+mutable-globals",
"-C",
"link-arg=-sSIDE_MODULE=2",
"-C",
"link-arg=-sWASM_BIGINT",
];
rustc_args.extend(emscripten_args);
}

if context.strip {
Expand Down Expand Up @@ -270,6 +290,28 @@ fn compile_target(
}
}

if target.is_emscripten() {
// Workaround https://github.com/emscripten-core/emscripten/issues/17191
let cache_dir = dirs::cache_dir()
.unwrap_or_else(|| env::current_dir().expect("Failed to get current dir"))
.join(env!("CARGO_PKG_NAME"));
fs::create_dir_all(&cache_dir)?;
let emcc_wrapper = cache_dir.join("emcc_wrapper.py");
let mut emcc_wrapper_file = fs::File::create(&emcc_wrapper)?;
emcc_wrapper_file.write_all(EMCC_WRAPPER.as_bytes())?;
#[cfg(unix)]
{
let metadata = emcc_wrapper_file.metadata()?;
let mut permissions = metadata.permissions();
permissions.set_mode(0o755);
emcc_wrapper_file.set_permissions(permissions)?;
}
build_command.env(
"CARGO_TARGET_WASM32_UNKNOWN_EMSCRIPTEN_LINKER",
emcc_wrapper,
);
}

build_command
.args(&build_args)
// We need to capture the json messages
Expand Down
23 changes: 23 additions & 0 deletions src/emcc_wrapper.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#!/usr/bin/env python3
import subprocess
import sys


def update_args(args):
# remove -lc. Not sure if it makes a difference but -lc doesn't belong here.
# https://github.com/emscripten-core/emscripten/issues/17191
for i in reversed(range(len(args))):
if args[i] == "c" and args[i - 1] == "-l":
del args[i - 1 : i + 1]

return args


def main(args):
args = update_args(args)
return subprocess.call(["emcc"] + args)


if __name__ == "__main__":
args = sys.argv[1:]
sys.exit(main(args))
5 changes: 5 additions & 0 deletions src/python_interpreter/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@ static WELLKNOWN_SYSCONFIG: Lazy<HashMap<Os, HashMap<Arch, Vec<InterpreterConfig
let sysconfig_netbsd = serde_json::from_slice(include_bytes!("sysconfig-netbsd.json"))
.expect("invalid sysconfig-netbsd.json");
sysconfig.insert(Os::NetBsd, sysconfig_netbsd);
// Emscripten
let sysconfig_emscripten =
serde_json::from_slice(include_bytes!("sysconfig-emscripten.json"))
.expect("invalid sysconfig-emscripten.json");
sysconfig.insert(Os::Emscripten, sysconfig_emscripten);
sysconfig
});

Expand Down
22 changes: 22 additions & 0 deletions src/python_interpreter/sysconfig-emscripten.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"wasm32": [
{
"major": 3,
"minor": 10,
"abiflags": "",
"interpreter": "cpython",
"ext_suffix": ".cpython-310-wasm32-emscripten.so",
"abi_tag": "310",
"pointer_width": 32
},
{
"major": 3,
"minor": 11,
"abiflags": "",
"interpreter": "cpython",
"ext_suffix": ".cpython-311-wasm32-emscripten.so",
"abi_tag": "311",
"pointer_width": 32
}
]
}
81 changes: 60 additions & 21 deletions src/target.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ pub enum Os {
Dragonfly,
Illumos,
Haiku,
Emscripten,
}

impl fmt::Display for Os {
Expand All @@ -38,6 +39,7 @@ impl fmt::Display for Os {
Os::Dragonfly => write!(f, "DragonFly"),
Os::Illumos => write!(f, "Illumos"),
Os::Haiku => write!(f, "Haiku"),
Os::Emscripten => write!(f, "Emscripten"),
}
}
}
Expand All @@ -57,6 +59,7 @@ pub enum Arch {
X86,
X86_64,
S390X,
Wasm32,
}

impl fmt::Display for Arch {
Expand All @@ -70,6 +73,7 @@ impl fmt::Display for Arch {
Arch::X86 => write!(f, "i686"),
Arch::X86_64 => write!(f, "x86_64"),
Arch::S390X => write!(f, "s390x"),
Arch::Wasm32 => write!(f, "wasm32"),
}
}
}
Expand Down Expand Up @@ -100,6 +104,7 @@ fn get_supported_architectures(os: &Os) -> Vec<Arch> {
Os::Dragonfly => vec![Arch::X86_64],
Os::Illumos => vec![Arch::X86_64],
Os::Haiku => vec![Arch::X86_64],
Os::Emscripten => vec![Arch::Wasm32],
}
}

Expand All @@ -123,7 +128,7 @@ impl Target {
///
/// Fails if the target triple isn't supported
pub fn from_target_triple(target_triple: Option<String>) -> Result<Self> {
use target_lexicon::ArmArchitecture;
use target_lexicon::{Architecture, ArmArchitecture, OperatingSystem};

let host_triple = get_host_target()?;
let (platform, triple) = if let Some(ref target_triple) = target_triple {
Expand All @@ -139,30 +144,31 @@ impl Target {
};

let os = match platform.operating_system {
target_lexicon::OperatingSystem::Linux => Os::Linux,
target_lexicon::OperatingSystem::Windows => Os::Windows,
target_lexicon::OperatingSystem::MacOSX { .. }
| target_lexicon::OperatingSystem::Darwin => Os::Macos,
target_lexicon::OperatingSystem::Netbsd => Os::NetBsd,
target_lexicon::OperatingSystem::Freebsd => Os::FreeBsd,
target_lexicon::OperatingSystem::Openbsd => Os::OpenBsd,
target_lexicon::OperatingSystem::Dragonfly => Os::Dragonfly,
target_lexicon::OperatingSystem::Illumos => Os::Illumos,
target_lexicon::OperatingSystem::Haiku => Os::Haiku,
OperatingSystem::Linux => Os::Linux,
OperatingSystem::Windows => Os::Windows,
OperatingSystem::MacOSX { .. } | OperatingSystem::Darwin => Os::Macos,
OperatingSystem::Netbsd => Os::NetBsd,
OperatingSystem::Freebsd => Os::FreeBsd,
OperatingSystem::Openbsd => Os::OpenBsd,
OperatingSystem::Dragonfly => Os::Dragonfly,
OperatingSystem::Illumos => Os::Illumos,
OperatingSystem::Haiku => Os::Haiku,
OperatingSystem::Emscripten => Os::Emscripten,
unsupported => bail!("The operating system {:?} is not supported", unsupported),
};

let arch = match platform.architecture {
target_lexicon::Architecture::X86_64 => Arch::X86_64,
target_lexicon::Architecture::X86_32(_) => Arch::X86,
target_lexicon::Architecture::Arm(arm_arch) => match arm_arch {
Architecture::X86_64 => Arch::X86_64,
Architecture::X86_32(_) => Arch::X86,
Architecture::Arm(arm_arch) => match arm_arch {
ArmArchitecture::Arm | ArmArchitecture::Armv6 => Arch::Armv6L,
_ => Arch::Armv7L,
},
target_lexicon::Architecture::Aarch64(_) => Arch::Aarch64,
target_lexicon::Architecture::Powerpc64 => Arch::Powerpc64,
target_lexicon::Architecture::Powerpc64le => Arch::Powerpc64Le,
target_lexicon::Architecture::S390x => Arch::S390X,
Architecture::Aarch64(_) => Arch::Aarch64,
Architecture::Powerpc64 => Arch::Powerpc64,
Architecture::Powerpc64le => Arch::Powerpc64Le,
Architecture::S390x => Arch::S390X,
Architecture::Wasm32 => Arch::Wasm32,
unsupported => bail!("The architecture {} is not supported", unsupported),
};

Expand Down Expand Up @@ -235,6 +241,7 @@ impl Target {
"x86_64"
)
}
// Illumos
(Os::Illumos, Arch::X86_64) => {
let info = PlatformInfo::new()?;
let mut release = info.release().replace('.', "_").replace('-', "_");
Expand All @@ -258,6 +265,7 @@ impl Target {
arch
)
}
// Linux
(Os::Linux, _) => {
let arch = if self.cross_compiling {
self.arch.to_string()
Expand All @@ -277,6 +285,7 @@ impl Target {
}
tags.join(".")
}
// macOS
(Os::Macos, Arch::X86_64) => {
let ((x86_64_major, x86_64_minor), (arm64_major, arm64_minor)) = macosx_deployment_target(env::var("MACOSX_DEPLOYMENT_TARGET").ok().as_deref(), universal2)?;
if universal2 {
Expand Down Expand Up @@ -305,9 +314,15 @@ impl Target {
format!("macosx_{}_{}_arm64", arm64_major, arm64_minor)
}
}
// Windows
(Os::Windows, Arch::X86) => "win32".to_string(),
(Os::Windows, Arch::X86_64) => "win_amd64".to_string(),
(Os::Windows, Arch::Aarch64) => "win_arm64".to_string(),
// Emscripten
(Os::Emscripten, Arch::Wasm32) => {
let version = emcc_version()?;
format!("emscripten_{}_wasm32", version.replace('.', "_"))
}
(_, _) => panic!("unsupported target should not have reached get_platform_tag()"),
};
Ok(tag)
Expand All @@ -324,6 +339,7 @@ impl Target {
Arch::X86 => "i386",
Arch::X86_64 => "x86_64",
Arch::S390X => "s390x",
Arch::Wasm32 => "wasm32",
}
}

Expand All @@ -339,6 +355,7 @@ impl Target {
Os::Dragonfly => "dragonfly",
Os::Illumos => "sunos",
Os::Haiku => "haiku",
Os::Emscripten => "emscripten",
}
}

Expand All @@ -349,15 +366,15 @@ impl Target {
PlatformTag::manylinux2014()
}
Arch::X86 | Arch::X86_64 => PlatformTag::manylinux2010(),
Arch::Armv6L => PlatformTag::Linux,
Arch::Armv6L | Arch::Wasm32 => PlatformTag::Linux,
}
}

/// Returns whether the platform is 64 bit or 32 bit
pub fn pointer_width(&self) -> usize {
match self.arch {
Arch::Aarch64 | Arch::Powerpc64 | Arch::Powerpc64Le | Arch::X86_64 | Arch::S390X => 64,
Arch::Armv6L | Arch::Armv7L | Arch::X86 => 32,
Arch::Armv6L | Arch::Armv7L | Arch::X86 | Arch::Wasm32 => 32,
}
}

Expand All @@ -377,7 +394,8 @@ impl Target {
| Os::OpenBsd
| Os::Dragonfly
| Os::Illumos
| Os::Haiku => true,
| Os::Haiku
| Os::Emscripten => true,
}
}

Expand Down Expand Up @@ -431,6 +449,11 @@ impl Target {
self.os == Os::Haiku
}

/// Returns true if the current platform is Emscripten
pub fn is_emscripten(&self) -> bool {
self.os == Os::Emscripten
}

/// Returns true if the current platform's target env is Musl
pub fn is_musl_target(&self) -> bool {
matches!(
Expand Down Expand Up @@ -580,6 +603,22 @@ fn macosx_deployment_target(
Ok((x86_64_ver, arm64_ver))
}

fn emcc_version() -> Result<String> {
use regex::bytes::Regex;
use std::process::Command;

let emcc = Command::new("emcc")
.arg("--version")
.output()
.context("Failed to run emcc to get the version")?;
let pattern = Regex::new(r"^emcc .+? (\d+\.\d+\.\d+).*").unwrap();
let caps = pattern
.captures(&emcc.stdout)
.context("Failed to parse emcc version")?;
let version = caps.get(1).context("Failed to parse emcc version")?;
Ok(String::from_utf8(version.as_bytes().to_vec())?)
}

#[cfg(test)]
mod test {
use super::macosx_deployment_target;
Expand Down

0 comments on commit fb7e327

Please sign in to comment.