1extern crate embed_js_common;
8extern crate cpp_synmap;
9extern crate cpp_syn;
10extern crate serde_json;
11extern crate uuid;
12extern crate parity_wasm;
13
14use cpp_synmap::SourceMap;
15use cpp_syn::visit::Visitor;
16use cpp_syn::{Mac, TokenTree, Delimited};
17
18use parity_wasm::elements::{Module, Section, ExportEntry, Internal};
19
20use std::env;
21use std::path::{ PathBuf, Path };
22use std::io::{ BufWriter, BufReader, Read };
23use std::fs::File;
24use std::process::Command;
25use std::collections::hash_map::DefaultHasher;
26use std::hash::{Hash, Hasher};
27use std::collections::HashMap;
28
29use embed_js_common::{ JsMac, JsMacArg };
30
31struct JsVisitor<'a> {
32 source_map: &'a mut SourceMap,
33 instances: &'a mut Vec<JsMac>,
34 included_js: &'a mut String
35}
36impl<'a> Visitor for JsVisitor<'a> {
37 fn visit_mac(&mut self, mac: &Mac) {
38 if mac.path.segments.len() != 1 {
39 return;
40 }
41 let tts = match mac.tts[0] {
42 TokenTree::Delimited(Delimited { ref tts, .. }, _) => &**tts,
43 _ => return,
44 };
45 match mac.path.segments[0].ident.as_ref() {
46 "js" => {
47 if let Ok(parsed) = embed_js_common::parse_js_mac_source_map(tts, self.source_map) {
48 self.instances.push(parsed);
49 }
50 }
51 "include_js" => {
52 let js_source = if let (Some(first), Some(last)) = (tts.first(), tts.last()) {
53 self.source_map.source_text(first.span().extend(last.span())).unwrap()
54 } else {
55 ""
56 };
57 self.included_js.push_str(&js_source);
58 self.included_js.push_str("\n");
59 }
60 "include" => {
61 use cpp_syn::{ Token, Lit, LitKind };
62 let mut iter = tts.iter().peekable();
63 match iter.next() {
64 Some(&TokenTree::Token(Token::Literal(Lit { node: LitKind::Str(ref path, _), .. }), span)) => {
65 if iter.next().is_some() {
66 return;
67 }
68 let mut path = PathBuf::from(path);
69 if !path.is_absolute() {
70 let root = self.source_map.filename(span).unwrap();
71 path = root.join(path);
72 }
73 println!("cargo:warning=embed_js_build processing source in included file {}", path.display());
74 let krate = self.source_map.add_crate_root(path).unwrap();
75 self.visit_crate(&krate);
76 }
77 Some(&TokenTree::Token(Token::Ident(ref ident), span)) if ident.as_ref() == "concat" => {
78 match iter.next() {
79 Some(&TokenTree::Token(Token::Not, _)) => {}
80 _ => return
81 }
82 let tts = match iter.next() {
83 Some(&TokenTree::Delimited(Delimited { ref tts, .. }, _)) => {
84 tts
85 }
86 _ => return
87 };
88 let mut path = String::new();
89 let mut iter = tts.iter().peekable();
90 while let Some(t) = iter.next() {
91 match *t {
92 TokenTree::Token(Token::Literal(Lit { node: LitKind::Str(ref s, _), .. }), _) => {
93 path.push_str(s);
94 }
95 TokenTree::Token(Token::Comma, _) => {}
96 TokenTree::Token(Token::Ident(ref ident), _) if ident.as_ref() == "env" => {
97 match iter.next() {
98 Some(&TokenTree::Token(Token::Not, _)) => {}
99 _ => return
100 }
101 let tts = match iter.next() {
102 Some(&TokenTree::Delimited(Delimited { ref tts, .. }, _)) => {
103 tts
104 }
105 _ => return
106 };
107 if let Some(&TokenTree::Token(Token::Literal(Lit { node: LitKind::Str(ref s, _), .. }), _)) = tts.first() {
108 if tts.len() != 1 {
109 return
110 }
111 if let Ok(v) = std::env::var(s) {
112 path.push_str(&v);
113 } else {
114 return
115 }
116 } else {
117 return
118 }
119 }
120 _ => return
121 }
122 }
123 let mut path = PathBuf::from(path);
124 if !path.is_absolute() {
125 let root = self.source_map.filename(span).unwrap();
126 path = root.join(path);
127 }
128 println!("cargo:warning=embed_js_build processing source in included file {}", path.display());
129 let krate = self.source_map.add_crate_root(path).unwrap();
130 self.visit_crate(&krate);
131 }
132 _ => return
133 }
134 }
135 _ => {}
136 }
137 }
138}
139
140pub fn preprocess_crate(lib_root: &Path) {
157 let mut source_map = SourceMap::new();
158 let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
159 let mut instances = Vec::new();
160 let mut included_js = String::new();
161 let krate = source_map.add_crate_root(lib_root).unwrap();
162 JsVisitor {
163 source_map: &mut source_map,
164 instances: &mut instances,
165 included_js: &mut included_js
166 }.visit_crate(&krate);
167
168 let js_path = out_dir.join("embed_js_data.json");
169 serde_json::to_writer(BufWriter::new(File::create(&js_path).unwrap()), &(instances, included_js)).unwrap();
170 let preamble_path = out_dir.join("embed_js_preamble.rs");
171 File::create(preamble_path).unwrap();
172}
173
174pub struct PostProcessData {
176 pub wasm_path: PathBuf,
178 pub wasm: Vec<u8>,
180 pub imports: String,
183 pub included: String
186}
187pub fn postprocess_crate(lib_name: &str, debug: bool) -> std::io::Result<PostProcessData> {
246 let metadata_json = Command::new("cargo").args(&["metadata", "--format-version", "1"]).output().unwrap().stdout;
247 let metadata_json: serde_json::Value = serde_json::from_slice(&metadata_json).unwrap();
248 let target_directory = Path::new(metadata_json.as_object().unwrap().get("target_directory").unwrap().as_str().unwrap());
249 let bin_prefix = target_directory.join(&format!("wasm32-unknown-unknown/{}/{}", if debug { "debug" } else { "release" }, lib_name));
250
251 let d_path = bin_prefix.with_extension("d");
253 let mut d_string = String::new();
254 File::open(&d_path)?.read_to_string(&mut d_string).unwrap();
255 let mut d_pieces: Vec<String> = d_string.split_whitespace().map(String::from).collect::<Vec<_>>();
256 { let mut i = 0;
258 while i < d_pieces.len() {
259 while d_pieces[i].ends_with("\\") && i != d_pieces.len() - 1 {
260 let removed = d_pieces.remove(i+1);
261 d_pieces[i].push_str(&removed);
262 }
263 i += 1;
264 }
265 }
266 d_pieces.remove(0); let mut js_macs: HashMap<String, JsMac> = HashMap::new();
268 let mut included_js = String::new();
269 for path in d_pieces {
270 if path.ends_with("out/embed_js_preamble.rs") || path.ends_with("out\\embed_js_preamble.rs") {
271 let data_path = PathBuf::from(path).with_file_name("embed_js_data.json");
272 let (mut crate_js_macs, crate_included_js): (Vec<JsMac>, String) = serde_json::from_reader(BufReader::new(File::open(data_path)?)).unwrap();
273 included_js.push_str(&crate_included_js);
274 for js_mac in crate_js_macs.drain(..) {
275 let mut hasher = DefaultHasher::new();
276 js_mac.hash(&mut hasher);
277 let mac_hash = hasher.finish();
278 let key = format!("__embed_js__{:x}", mac_hash);
279 if let Some(existing) = js_macs.get(&key) {
280 if *existing != js_mac {
281 panic!("A hash collision has occurred in the embed_js build process. Please raise a bug! Meanwhile, try making small changes to your embedded js to remove the collision.")
282 }
283 }
284 js_macs.insert(key, js_mac);
285 }
286 }
287 }
288
289 let wasm_path = bin_prefix.with_extension("wasm");
290 match Command::new("wasm-gc").args(&[&wasm_path, &wasm_path]).output() {
291 Ok(output) => {
292 if !output.status.success() {
293 panic!("wasm-gc encountered an error.\n\nstatus: {}\n\nstdout:\n\n{}\n\nstderr:\n\n{}",
294 output.status,
295 String::from_utf8(output.stdout).unwrap_or_else(|_| String::from("<error decoding stdout>")),
296 String::from_utf8(output.stderr).unwrap_or_else(|_| String::from("<error decoding stderr>")))
297 }
298 }
299 Err(e) => panic!("Error attempting to run wasm-gc. Have you got it installed? Error message: {}", e)
300 }
301 let mut wasm = Vec::new();
302 BufReader::new(File::open(&wasm_path)?).read_to_end(&mut wasm)?;
303 let mut module: Module = parity_wasm::deserialize_buffer(wasm.clone()).unwrap();
304 let has_table_export = module.export_section()
306 .map(|exports| exports.entries()
307 .iter()
308 .any(|entry| entry.field() == "__table"))
309 .unwrap_or(false);
310 if !has_table_export && module.table_section().is_some() {
311 let sections = module.sections_mut();
312 for section in sections {
313 match *section {
314 Section::Export(ref mut exports) => {
315 exports.entries_mut().push(ExportEntry::new("__table".to_string(), Internal::Table(0)));
316 break;
317 }
318 _ => {}
319 }
320 }
321 }
322 parity_wasm::serialize_to_file(&wasm_path, module.clone()).unwrap();
323 wasm.clear();
324 BufReader::new(File::open(&wasm_path)?).read_to_end(&mut wasm)?;
325 let mut imports = String::new();
326 if let Some(import_section) = module.import_section() {
327 for entry in import_section.entries() {
328 if entry.module() == "env" {
329 if let Some(mac) = js_macs.remove(entry.field()) {
330 if !imports.is_empty() {
331 imports.push_str(",\n");
332 }
333 imports.push_str(&format!("{}:function(", entry.field()));
334 let mut start = true;
335 for arg in mac.args {
336 if !start {
337 imports.push_str(", ");
338 } else {
339 start = false;
340 }
341 match arg {
342 JsMacArg::Ref(_, _, name) |
343 JsMacArg::Primitive(_, name, _) => imports.push_str(&name)
344 }
345 }
346 if let Some(body) = mac.body {
347 imports.push_str(&format!("){{{}}}", body));
348 } else {
349 imports.push_str("){}\n");
350 }
351 }
352 }
353 }
354 }
355
356 Ok(PostProcessData {
358 wasm_path,
359 wasm,
360 included: included_js,
361 imports
362 })
363}