wasm-bindgen/publish.rs

236 lines
6.8 KiB
Rust
Raw Normal View History

//! Helper script to publish the wasm-bindgen suite of crates
//!
//! Usage:
//!
//! * First, compile this script
//! * Next, set cwd to the root of the wasm-bindgen repository
//! * Execute `./publish bump` to bump versions
//! * Send a PR
//! * Merge when green
//! * Execute `./publish publish` to publish crates
2018-10-29 23:08:22 +03:00
use std::collections::HashMap;
use std::env;
use std::fs;
2018-10-29 23:08:22 +03:00
use std::io;
use std::path::{Path, PathBuf};
use std::process::Command;
2018-10-29 23:08:22 +03:00
// note that this list must be topologically sorted by dependencies
const CRATES_TO_PUBLISH: &[&str] = &[
2018-10-29 23:08:22 +03:00
"wasm-bindgen-shared",
"wasm-bindgen-backend",
"wasm-bindgen-macro-support",
2018-10-29 23:08:22 +03:00
"wasm-bindgen-macro",
"wasm-bindgen-test-macro",
2018-10-29 23:08:22 +03:00
"wasm-bindgen-test",
"wasm-bindgen-wasm-interpreter",
"wasm-bindgen-webidl",
"wasm-bindgen-wasm-conventions",
2018-10-29 22:52:21 +03:00
"wasm-bindgen-threads-xform",
Introduce the `multi-value-xform` crate This crate provides a transformation to turn exported functions that use a return pointer into exported functions that use multi-value. Consider the following function: ```rust pub extern "C" fn pair(a: u32, b: u32) -> [u32; 2] { [a, b] } ``` LLVM will by default compile this down into the following Wasm: ```wasm (func $pair (param i32 i32 i32) local.get 0 local.get 2 i32.store offset=4 local.get 0 local.get 1 i32.store) ``` What's happening here is that the function is not directly returning the pair at all, but instead the first `i32` parameter is a pointer to some scratch space, and the return value is written into the scratch space. LLVM does this because it doesn't yet have support for multi-value Wasm, and so it only knows how to return a single value at a time. Ideally, with multi-value, what we would like instead is this: ```wasm (func $pair (param i32 i32) (result i32 i32) local.get 0 local.get 1) ``` However, that's not what this transformation does at the moment. This transformation is a little simpler than mutating existing functions to produce a multi-value result, instead it introduces new functions that wrap the original function and translate the return pointer to multi-value results in this wrapper function. With our running example, we end up with this: ```wasm ;; The original function. (func $pair (param i32 i32 i32) local.get 0 local.get 2 i32.store offset=4 local.get 0 local.get 1 i32.store) (func $pairWrapper (param i32 i32) (result i32 i32) ;; Our return pointer that points to the scratch space we are allocating ;; on the shadow stack for calling `$pair`. (local i32) ;; Allocate space on the shadow stack for the result. global.get $shadowStackPointer i32.const 8 i32.sub local.tee 2 global.set $shadowStackPointer ;; Call `$pair` with our allocated shadow stack space for its results. local.get 2 local.get 0 local.get 1 call $pair ;; Copy the return values from the shadow stack to the wasm stack. local.get 2 i32.load local.get 2 offset=4 i32.load ;; Finally, restore the shadow stack pointer. local.get 2 i32.const 8 i32.add global.set $shadowStackPointer) ``` This `$pairWrapper` function is what we actually end up exporting instead of `$pair`.
2019-09-10 00:00:04 +03:00
"wasm-bindgen-multi-value-xform",
Add experimental support for the `anyref` type This commit adds experimental support to `wasm-bindgen` to emit and leverage the `anyref` native wasm type. This native type is still in a proposal status (the reference-types proposal). The intention of `anyref` is to be able to directly hold JS values in wasm and pass the to imported functions, namely to empower eventual host bindings (now renamed WebIDL bindings) integration where we can skip JS shims altogether for many imports. This commit doesn't actually affect wasm-bindgen's behavior at all as-is, but rather this support requires an opt-in env var to be configured. Once the support is stable in browsers it's intended that this will add a CLI switch for turning on this support, eventually defaulting it to `true` in the far future. The basic strategy here is to take the `stack` and `slab` globals in the generated JS glue and move them into wasm using a table. This new table in wasm is managed at the fringes via injected shims. At `wasm-bindgen`-time the CLI will rewrite exports and imports with shims that actually use `anyref` if needed, performing loads/stores inside the wasm module instead of externally in the wasm module. This should provide a boost over what we have today, but it's not a fantastic strategy long term. We have a more grand vision for `anyref` being a first-class type in the language, but that's on a much longer horizon and this is currently thought to be the best we can do in terms of integration in the near future. The stack/heap JS tables are combined into one wasm table. The stack starts at the end of the table and grows down with a stack pointer (also injected). The heap starts at the end and grows up (state managed in linear memory). The anyref transformation here will hook up various intrinsics in wasm-bindgen to the runtime functionality if the anyref supoprt is enabled. The main tricky treatment here was applied to closures, where we need JS to use a different function pointer than the one Rust gives it to use a JS function pointer empowered with anyref. This works by switching up a bit how descriptors work, embedding the shims to call inside descriptors rather than communicated at runtime. This means that we're accessing constant values in the generated JS and we can just update the constant value accessed.
2018-10-18 18:43:36 +03:00
"wasm-bindgen-anyref-xform",
2018-10-29 23:08:22 +03:00
"wasm-bindgen-cli-support",
"wasm-bindgen-cli",
"wasm-bindgen",
"wasm-bindgen-futures",
"js-sys",
"web-sys",
];
const CRATES_TO_AVOID_PUBLISH: &[&str] = &[
// We'll publish these when they're ready one day
"wasm-bindgen-typescript",
// These are internal crates, unlikely to ever be published
"ui-tests",
"sample",
"webidl-tests",
2019-03-13 21:02:27 +03:00
"typescript-tests",
];
struct Crate {
manifest: PathBuf,
name: String,
version: String,
next_version: String,
}
fn main() {
let mut crates = Vec::new();
crates.push(read_crate("./Cargo.toml".as_ref()));
find_crates("crates".as_ref(), &mut crates);
find_crates("examples".as_ref(), &mut crates);
2019-09-16 21:20:56 +03:00
let pos = CRATES_TO_PUBLISH
.iter()
2018-10-29 23:08:22 +03:00
.chain(CRATES_TO_AVOID_PUBLISH)
.enumerate()
.map(|(i, c)| (*c, i))
.collect::<HashMap<_, _>>();
crates.sort_by_key(|krate| pos.get(&krate.name[..]));
2018-10-29 23:08:22 +03:00
match &env::args().nth(1).expect("must have one argument")[..] {
"bump" => {
for krate in crates.iter() {
bump_version(&krate, &crates);
}
}
"publish" => {
for krate in crates.iter() {
publish(&krate);
}
}
s => panic!("unknown command: {}", s),
}
}
fn find_crates(dir: &Path, dst: &mut Vec<Crate>) {
if dir.join("Cargo.toml").exists() {
let krate = read_crate(&dir.join("Cargo.toml"));
2019-09-16 21:20:56 +03:00
if CRATES_TO_PUBLISH
.iter()
.chain(CRATES_TO_AVOID_PUBLISH)
.any(|c| krate.name == *c)
{
dst.push(krate);
} else if dir.iter().any(|s| s == "examples") {
dst.push(krate);
} else {
panic!("failed to find {:?} in whitelist or blacklist", krate.name);
}
}
for entry in dir.read_dir().unwrap() {
let entry = entry.unwrap();
if entry.file_type().unwrap().is_dir() {
find_crates(&entry.path(), dst);
}
}
}
fn read_crate(manifest: &Path) -> Crate {
let mut name = None;
let mut version = None;
for line in fs::read_to_string(manifest).unwrap().lines() {
if name.is_none() && line.starts_with("name = \"") {
2019-09-16 21:20:56 +03:00
name = Some(
line.replace("name = \"", "")
.replace("\"", "")
.trim()
.to_string(),
);
}
if version.is_none() && line.starts_with("version = \"") {
2019-09-16 21:20:56 +03:00
version = Some(
line.replace("version = \"", "")
.replace("\"", "")
.trim()
.to_string(),
);
}
}
let name = name.unwrap();
let version = version.unwrap();
let next_version = if CRATES_TO_PUBLISH.contains(&&name[..]) {
bump(&version)
} else {
version.clone()
};
Crate {
manifest: manifest.to_path_buf(),
name,
version,
next_version,
}
}
fn bump_version(krate: &Crate, crates: &[Crate]) {
let contents = fs::read_to_string(&krate.manifest).unwrap();
let mut new_manifest = String::new();
let mut is_deps = false;
for line in contents.lines() {
let mut rewritten = false;
if line.starts_with("version =") {
if CRATES_TO_PUBLISH.contains(&&krate.name[..]) {
2019-09-16 21:20:56 +03:00
println!(
"bump `{}` {} => {}",
krate.name, krate.version, krate.next_version
);
new_manifest.push_str(&line.replace(&krate.version, &krate.next_version));
rewritten = true;
}
}
is_deps = if line.starts_with("[") {
line.contains("dependencies")
} else {
is_deps
};
for other in crates {
if !is_deps || !line.starts_with(&format!("{} ", other.name)) {
2019-09-16 21:20:56 +03:00
continue;
}
if !line.contains(&other.version) {
if !line.contains("version =") {
2019-09-16 21:20:56 +03:00
continue;
}
2019-09-16 21:20:56 +03:00
panic!(
"{:?} has a dep on {} but doesn't list version {}",
krate.manifest, other.name, other.version
);
}
rewritten = true;
new_manifest.push_str(&line.replace(&other.version, &other.next_version));
2019-09-16 21:20:56 +03:00
break;
}
if !rewritten {
new_manifest.push_str(line);
}
new_manifest.push_str("\n");
}
fs::write(&krate.manifest, new_manifest).unwrap();
}
fn bump(version: &str) -> String {
let mut iter = version.split('.').map(|s| s.parse::<u32>().unwrap());
let major = iter.next().expect("major version");
let minor = iter.next().expect("minor version");
let patch = iter.next().expect("patch version");
format!("{}.{}.{}", major, minor, patch + 1)
}
fn publish(krate: &Crate) {
if !CRATES_TO_PUBLISH.iter().any(|s| *s == krate.name) {
2019-09-16 21:20:56 +03:00
return;
}
2018-10-29 23:08:22 +03:00
if krate.name == "wasm-bindgen" {
println!("ABOUT TO PUBLISH wasm-bindgen");
println!("for this to work you need to comment out the `dev-dependencies`");
println!("section in `Cargo.toml` and everything below");
println!("");
println!("hit enter when done");
drop(io::stdin().read_line(&mut String::new()));
}
let status = Command::new("cargo")
.arg("publish")
.current_dir(krate.manifest.parent().unwrap())
.arg("--no-verify")
.arg("--allow-dirty")
.status()
.expect("failed to run cargo");
if !status.success() {
println!("FAIL: failed to publish `{}`: {}", krate.name, status);
}
2018-10-29 23:08:22 +03:00
if krate.name == "wasm-bindgen" {
println!("ok please now uncomment the section you just commented");
println!("");
println!("hit enter when done");
drop(io::stdin().read_line(&mut String::new()));
}
}