Merge pull request #161 from rustwasm/versions

Add support for version specifications
This commit is contained in:
Alex Crichton 2018-04-26 00:23:31 -05:00 committed by GitHub
commit 7937d02bcb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 183 additions and 0 deletions

View File

@ -886,6 +886,37 @@ controlling precisely how imports are imported and what they map to in JS. This
section is intended to hopefully be an exhaustive reference of the section is intended to hopefully be an exhaustive reference of the
possibilities! possibilities!
* `module` and `version` - we've seen `module` so far indicating where we can
import items from but `version` is also allowed:
```rust
#[wasm_bindgen(module = "moment", version = "^2.0.0")]
extern {
type Moment;
fn moment() -> Moment;
#[wasm_bindgen(method)]
fn format(this: &Moment) -> String;
}
```
The `module` key is used to configure the module that each item is imported
from. The `version` key does not affect the generated wasm itself but rather
it's an informative directive for tools like [wasm-pack]. Tools like wasm-pack
will generate a `package.json` for you and the `version` listed here, when
`module` is also an NPM package, will correspond to what to write down in
`package.json`.
In other words the usage of `module` as the name of an NPM package and
`version` as the version requirement allows you to, inline in Rust, depend on
the NPM ecosystem and import functionality from those packages. When bundled
with a tool like [wasm-pack] everything will automatically get wired up with
bundlers and you should be good to go!
Note that the `version` is *required* if `module` doesn't start with `./`. If
`module` starts with `./` then it is an error to provide a version.
[wasm-pack]: https://github.com/ashleygwilliams/wasm-pack
* `catch` - as we saw before the `catch` attribute allows catching a JS * `catch` - as we saw before the `catch` attribute allows catching a JS
exception. This can be attached to any imported function and the function must exception. This can be attached to any imported function and the function must
return a `Result` where the `Err` payload is a `JsValue`, like so: return a `Result` where the `Err` payload is a `JsValue`, like so:

View File

@ -20,6 +20,7 @@ pub struct Export {
pub struct Import { pub struct Import {
pub module: Option<String>, pub module: Option<String>,
pub version: Option<String>,
pub js_namespace: Option<syn::Ident>, pub js_namespace: Option<syn::Ident>,
pub kind: ImportKind, pub kind: ImportKind,
} }
@ -294,6 +295,7 @@ impl Program {
BindgenAttrs::find(attrs) BindgenAttrs::find(attrs)
}; };
let module = item_opts.module().or(opts.module()).map(|s| s.to_string()); let module = item_opts.module().or(opts.module()).map(|s| s.to_string());
let version = item_opts.version().or(opts.version()).map(|s| s.to_string());
let js_namespace = item_opts.js_namespace().or(opts.js_namespace()); let js_namespace = item_opts.js_namespace().or(opts.js_namespace());
let mut kind = match item { let mut kind = match item {
syn::ForeignItem::Fn(f) => self.push_foreign_fn(f, item_opts), syn::ForeignItem::Fn(f) => self.push_foreign_fn(f, item_opts),
@ -304,6 +306,7 @@ impl Program {
self.imports.push(Import { self.imports.push(Import {
module, module,
version,
js_namespace, js_namespace,
kind, kind,
}); });
@ -584,8 +587,29 @@ impl Variant {
impl Import { impl Import {
fn shared(&self) -> shared::Import { fn shared(&self) -> shared::Import {
match (&self.module, &self.version) {
(&Some(ref m), None) if m.starts_with("./") => {}
(&Some(ref m), &Some(_)) if m.starts_with("./") => {
panic!("when a module path starts with `./` that indicates \
that a local file is being imported so the `version` \
key cannot also be specified");
}
(&Some(_), &Some(_)) => {}
(&Some(_), &None) => {
panic!("when the `module` directive doesn't start with `./` \
then it's interpreted as an NPM package which requires \
a `version` to be specified as well, try using \
#[wasm_bindgen(module = \"...\", version = \"...\")]")
}
(&None, &Some(_)) => {
panic!("the #[wasm_bindgen(version = \"...\")] attribute can only \
be used when `module = \"...\"` is also specified");
}
(&None, &None) => {}
}
shared::Import { shared::Import {
module: self.module.clone(), module: self.module.clone(),
version: self.version.clone(),
js_namespace: self.js_namespace.map(|s| s.as_ref().to_string()), js_namespace: self.js_namespace.map(|s| s.as_ref().to_string()),
kind: self.kind.shared(), kind: self.kind.shared(),
} }
@ -746,6 +770,16 @@ impl BindgenAttrs {
.next() .next()
} }
fn version(&self) -> Option<&str> {
self.attrs
.iter()
.filter_map(|a| match *a {
BindgenAttr::Version(ref s) => Some(&s[..]),
_ => None,
})
.next()
}
pub fn catch(&self) -> bool { pub fn catch(&self) -> bool {
self.attrs.iter().any(|a| match *a { self.attrs.iter().any(|a| match *a {
BindgenAttr::Catch => true, BindgenAttr::Catch => true,
@ -844,6 +878,7 @@ enum BindgenAttr {
Method, Method,
JsNamespace(syn::Ident), JsNamespace(syn::Ident),
Module(String), Module(String),
Version(String),
Getter(Option<syn::Ident>), Getter(Option<syn::Ident>),
Setter(Option<syn::Ident>), Setter(Option<syn::Ident>),
Structural, Structural,
@ -897,6 +932,13 @@ impl syn::synom::Synom for BindgenAttr {
(s.value()) (s.value())
)=> { BindgenAttr::Module } )=> { BindgenAttr::Module }
| |
do_parse!(
call!(term, "version") >>
punct!(=) >>
s: syn!(syn::LitStr) >>
(s.value())
)=> { BindgenAttr::Version }
|
do_parse!( do_parse!(
call!(term, "js_name") >> call!(term, "js_name") >>
punct!(=) >> punct!(=) >>

View File

@ -14,6 +14,8 @@ Shared support for the wasm-bindgen-cli package, an internal dependency
base64 = "0.9" base64 = "0.9"
failure = "0.1" failure = "0.1"
parity-wasm = "0.27" parity-wasm = "0.27"
serde = "1.0"
serde_derive = "1.0"
serde_json = "1.0" serde_json = "1.0"
wasm-bindgen-shared = { path = "../shared", version = '=0.2.5' } wasm-bindgen-shared = { path = "../shared", version = '=0.2.5' }
wasm-gc-api = "0.1" wasm-gc-api = "0.1"

View File

@ -5,6 +5,7 @@ use std::mem;
use failure::{Error, ResultExt}; use failure::{Error, ResultExt};
use parity_wasm::elements::*; use parity_wasm::elements::*;
use parity_wasm; use parity_wasm;
use serde_json;
use shared; use shared;
use wasm_gc; use wasm_gc;
@ -29,6 +30,7 @@ pub struct Context<'a> {
pub exported_classes: HashMap<String, ExportedClass>, pub exported_classes: HashMap<String, ExportedClass>,
pub function_table_needed: bool, pub function_table_needed: bool,
pub run_descriptor: &'a Fn(&str) -> Vec<u32>, pub run_descriptor: &'a Fn(&str) -> Vec<u32>,
pub module_versions: Vec<(String, String)>,
} }
#[derive(Default)] #[derive(Default)]
@ -341,6 +343,7 @@ impl<'a> Context<'a> {
self.export_table(); self.export_table();
self.gc()?; self.gc()?;
self.add_wasm_pack_section();
while js.contains("\n\n\n") { while js.contains("\n\n\n") {
js = js.replace("\n\n\n", "\n\n"); js = js.replace("\n\n\n", "\n\n");
@ -1333,6 +1336,28 @@ impl<'a> Context<'a> {
self.globals.push_str(s); self.globals.push_str(s);
self.globals.push_str("\n"); self.globals.push_str("\n");
} }
fn add_wasm_pack_section(&mut self) {
if self.module_versions.len() == 0 {
return
}
#[derive(Serialize)]
struct WasmPackSchema<'a> {
version: &'a str,
modules: &'a [(String, String)],
}
let contents = serde_json::to_string(&WasmPackSchema {
version: "0.0.1",
modules: &self.module_versions,
}).unwrap();
let mut section = CustomSection::default();
*section.name_mut() = "__wasm_pack_unstable".to_string();
*section.payload_mut() = contents.into_bytes();
self.module.sections_mut().push(Section::Custom(section));
}
} }
impl<'a, 'b> SubContext<'a, 'b> { impl<'a, 'b> SubContext<'a, 'b> {
@ -1423,6 +1448,7 @@ impl<'a, 'b> SubContext<'a, 'b> {
} }
fn generate_import(&mut self, import: &shared::Import) -> Result<(), Error> { fn generate_import(&mut self, import: &shared::Import) -> Result<(), Error> {
self.validate_import_module(import)?;
match import.kind { match import.kind {
shared::ImportKind::Function(ref f) => { shared::ImportKind::Function(ref f) => {
self.generate_import_function(import, f) self.generate_import_function(import, f)
@ -1443,6 +1469,40 @@ impl<'a, 'b> SubContext<'a, 'b> {
Ok(()) Ok(())
} }
fn validate_import_module(&mut self, import: &shared::Import)
-> Result<(), Error>
{
let version = match import.version {
Some(ref s) => s,
None => return Ok(()),
};
let module = match import.module {
Some(ref s) => s,
None => return Ok(()),
};
if module.starts_with("./") {
return Ok(())
}
let pkg = if module.starts_with("@") {
// Translate `@foo/bar/baz` to `@foo/bar` and `@foo/bar` to itself
let first_slash = match module.find('/') {
Some(i) => i,
None => {
bail!("packages starting with `@` must be of the form \
`@foo/bar`, but found: `{}`", module)
}
};
match module[first_slash + 1..].find('/') {
Some(i) => &module[..i],
None => module,
}
} else {
module.split('/').next().unwrap()
};
self.cx.module_versions.push((pkg.to_string(), version.clone()));
Ok(())
}
fn generate_import_static( fn generate_import_static(
&mut self, &mut self,
info: &shared::Import, info: &shared::Import,

View File

@ -1,5 +1,7 @@
extern crate parity_wasm; extern crate parity_wasm;
extern crate wasm_bindgen_shared as shared; extern crate wasm_bindgen_shared as shared;
#[macro_use]
extern crate serde_derive;
extern crate serde_json; extern crate serde_json;
extern crate wasm_gc; extern crate wasm_gc;
extern crate wasmi; extern crate wasmi;
@ -131,6 +133,7 @@ impl Bindgen {
config: &self, config: &self,
module: &mut module, module: &mut module,
function_table_needed: false, function_table_needed: false,
module_versions: Default::default(),
run_descriptor: &|name| { run_descriptor: &|name| {
let mut v = MyExternals(Vec::new()); let mut v = MyExternals(Vec::new());
let ret = instance let ret = instance

View File

@ -22,6 +22,7 @@ pub struct Program {
#[derive(Deserialize, Serialize)] #[derive(Deserialize, Serialize)]
pub struct Import { pub struct Import {
pub module: Option<String>, pub module: Option<String>,
pub version: Option<String>,
pub js_namespace: Option<String>, pub js_namespace: Option<String>,
pub kind: ImportKind, pub kind: ImportKind,
} }

View File

@ -351,3 +351,47 @@ fn rename() {
"#) "#)
.test(); .test();
} }
#[test]
fn versions() {
project()
.file("src/lib.rs", r#"
#![feature(proc_macro, wasm_custom_section, wasm_import_module)]
extern crate wasm_bindgen;
use wasm_bindgen::prelude::*;
#[wasm_bindgen(module = "webpack", version = "^0.2.0")]
extern {
fn foo();
}
#[wasm_bindgen]
pub fn run() {
foo();
}
"#)
.file("test.js", r#"
const fs = require("fs");
const assert = require("assert");
exports.test = function() {
const bytes = fs.readFileSync('out_bg.wasm');
const m = new WebAssembly.Module(bytes);
const name = '__wasm_pack_unstable';
const sections = WebAssembly.Module.customSections(m, name);
assert.strictEqual(sections.length, 1);
const b = new Uint8Array(sections[0]);
const buf = new Buffer(b);
const map = JSON.parse(buf.toString());
assert.deepStrictEqual(map, {
version: '0.0.1',
modules: [
['webpack', '^0.2.0']
]
});
};
"#)
.test();
}