]> git.lizzy.rs Git - rust.git/blob - src/librustc/metadata/creader.rs
auto merge of #17189 : bkoropoff/rust/extern-existing-crate, r=alexcrichton
[rust.git] / src / librustc / metadata / creader.rs
1 // Copyright 2012-2014 The Rust Project Developers. See the COPYRIGHT
2 // file at the top-level directory of this distribution and at
3 // http://rust-lang.org/COPYRIGHT.
4 //
5 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8 // option. This file may not be copied, modified, or distributed
9 // except according to those terms.
10
11 #![allow(non_camel_case_types)]
12
13 //! Validates all used crates and extern libraries and loads their metadata
14
15 use back::svh::Svh;
16 use driver::session::Session;
17 use driver::{driver, config};
18 use metadata::cstore;
19 use metadata::cstore::{CStore, CrateSource};
20 use metadata::decoder;
21 use metadata::loader;
22 use metadata::loader::CratePaths;
23 use plugin::load::PluginMetadata;
24
25 use std::rc::Rc;
26 use std::collections::HashMap;
27 use syntax::ast;
28 use syntax::abi;
29 use syntax::attr;
30 use syntax::attr::AttrMetaMethods;
31 use syntax::codemap::{Span};
32 use syntax::diagnostic::SpanHandler;
33 use syntax::parse::token::InternedString;
34 use syntax::parse::token;
35 use syntax::visit;
36 use util::fs;
37
38 struct Env<'a> {
39     sess: &'a Session,
40     next_crate_num: ast::CrateNum,
41 }
42
43 // Traverses an AST, reading all the information about use'd crates and extern
44 // libraries necessary for later resolving, typechecking, linking, etc.
45 pub fn read_crates(sess: &Session,
46                    krate: &ast::Crate) {
47     let mut e = Env {
48         sess: sess,
49         next_crate_num: sess.cstore.next_crate_num(),
50     };
51     visit_crate(&e, krate);
52     visit::walk_crate(&mut e, krate);
53     dump_crates(&sess.cstore);
54     warn_if_multiple_versions(sess.diagnostic(), &sess.cstore)
55 }
56
57 impl<'a, 'v> visit::Visitor<'v> for Env<'a> {
58     fn visit_view_item(&mut self, a: &ast::ViewItem) {
59         visit_view_item(self, a);
60         visit::walk_view_item(self, a);
61     }
62     fn visit_item(&mut self, a: &ast::Item) {
63         visit_item(self, a);
64         visit::walk_item(self, a);
65     }
66 }
67
68 fn dump_crates(cstore: &CStore) {
69     debug!("resolved crates:");
70     cstore.iter_crate_data_origins(|_, data, opt_source| {
71         debug!("  name: {}", data.name());
72         debug!("  cnum: {}", data.cnum);
73         debug!("  hash: {}", data.hash());
74         opt_source.map(|cs| {
75             let CrateSource { dylib, rlib, cnum: _ } = cs;
76             dylib.map(|dl| debug!("  dylib: {}", dl.display()));
77             rlib.map(|rl|  debug!("   rlib: {}", rl.display()));
78         });
79     })
80 }
81
82 fn warn_if_multiple_versions(diag: &SpanHandler, cstore: &CStore) {
83     let mut map = HashMap::new();
84     cstore.iter_crate_data(|cnum, data| {
85         map.find_or_insert_with(data.name(), |_| Vec::new()).push(cnum);
86     });
87
88     for (name, dupes) in map.move_iter() {
89         if dupes.len() == 1 { continue }
90         diag.handler().warn(
91             format!("using multiple versions of crate `{}`", name).as_slice());
92         for dupe in dupes.move_iter() {
93             let data = cstore.get_crate_data(dupe);
94             diag.span_note(data.span, "used here");
95             loader::note_crate_name(diag, data.name().as_slice());
96         }
97     }
98 }
99
100 fn visit_crate(e: &Env, c: &ast::Crate) {
101     for a in c.attrs.iter().filter(|m| m.name().equiv(&("link_args"))) {
102         match a.value_str() {
103             Some(ref linkarg) => e.sess.cstore.add_used_link_args(linkarg.get()),
104             None => { /* fallthrough */ }
105         }
106     }
107 }
108
109 fn should_link(i: &ast::ViewItem) -> bool {
110     i.attrs.iter().all(|attr| {
111         attr.name().get() != "phase" ||
112             attr.meta_item_list().map_or(false, |phases| {
113                 attr::contains_name(phases.as_slice(), "link")
114             })
115     })
116 }
117
118 fn visit_view_item(e: &mut Env, i: &ast::ViewItem) {
119     if !should_link(i) {
120         return;
121     }
122
123     match extract_crate_info(e, i) {
124         Some(info) => {
125             let (cnum, _, _) = resolve_crate(e,
126                                              &None,
127                                              info.ident.as_slice(),
128                                              info.name.as_slice(),
129                                              None,
130                                              i.span);
131             e.sess.cstore.add_extern_mod_stmt_cnum(info.id, cnum);
132         }
133         None => ()
134     }
135 }
136
137 struct CrateInfo {
138     ident: String,
139     name: String,
140     id: ast::NodeId,
141     should_link: bool,
142 }
143
144 fn extract_crate_info(e: &Env, i: &ast::ViewItem) -> Option<CrateInfo> {
145     match i.node {
146         ast::ViewItemExternCrate(ident, ref path_opt, id) => {
147             let ident = token::get_ident(ident);
148             debug!("resolving extern crate stmt. ident: {} path_opt: {}",
149                    ident, path_opt);
150             let name = match *path_opt {
151                 Some((ref path_str, _)) => {
152                     let name = path_str.get().to_string();
153                     validate_crate_name(Some(e.sess), name.as_slice(),
154                                         Some(i.span));
155                     name
156                 }
157                 None => ident.get().to_string(),
158             };
159             Some(CrateInfo {
160                 ident: ident.get().to_string(),
161                 name: name,
162                 id: id,
163                 should_link: should_link(i),
164             })
165         }
166         _ => None
167     }
168 }
169
170 pub fn validate_crate_name(sess: Option<&Session>, s: &str, sp: Option<Span>) {
171     let err = |s: &str| {
172         match (sp, sess) {
173             (_, None) => fail!("{}", s),
174             (Some(sp), Some(sess)) => sess.span_err(sp, s),
175             (None, Some(sess)) => sess.err(s),
176         }
177     };
178     if s.len() == 0 {
179         err("crate name must not be empty");
180     }
181     for c in s.chars() {
182         if c.is_alphanumeric() { continue }
183         if c == '_' || c == '-' { continue }
184         err(format!("invalid character `{}` in crate name: `{}`", c, s).as_slice());
185     }
186     match sess {
187         Some(sess) => sess.abort_if_errors(),
188         None => {}
189     }
190 }
191
192 fn visit_item(e: &Env, i: &ast::Item) {
193     match i.node {
194         ast::ItemForeignMod(ref fm) => {
195             if fm.abi == abi::Rust || fm.abi == abi::RustIntrinsic {
196                 return;
197             }
198
199             // First, add all of the custom link_args attributes
200             let link_args = i.attrs.iter()
201                 .filter_map(|at| if at.name().equiv(&("link_args")) {
202                     Some(at)
203                 } else {
204                     None
205                 })
206                 .collect::<Vec<&ast::Attribute>>();
207             for m in link_args.iter() {
208                 match m.value_str() {
209                     Some(linkarg) => e.sess.cstore.add_used_link_args(linkarg.get()),
210                     None => { /* fallthrough */ }
211                 }
212             }
213
214             // Next, process all of the #[link(..)]-style arguments
215             let link_args = i.attrs.iter()
216                 .filter_map(|at| if at.name().equiv(&("link")) {
217                     Some(at)
218                 } else {
219                     None
220                 })
221                 .collect::<Vec<&ast::Attribute>>();
222             for m in link_args.iter() {
223                 match m.meta_item_list() {
224                     Some(items) => {
225                         let kind = items.iter().find(|k| {
226                             k.name().equiv(&("kind"))
227                         }).and_then(|a| a.value_str());
228                         let kind = match kind {
229                             Some(k) => {
230                                 if k.equiv(&("static")) {
231                                     cstore::NativeStatic
232                                 } else if (e.sess.targ_cfg.os == abi::OsMacos ||
233                                            e.sess.targ_cfg.os == abi::OsiOS) &&
234                                           k.equiv(&("framework")) {
235                                     cstore::NativeFramework
236                                 } else if k.equiv(&("framework")) {
237                                     e.sess.span_err(m.span,
238                                         "native frameworks are only available \
239                                          on OSX targets");
240                                     cstore::NativeUnknown
241                                 } else {
242                                     e.sess.span_err(m.span,
243                                         format!("unknown kind: `{}`",
244                                                 k).as_slice());
245                                     cstore::NativeUnknown
246                                 }
247                             }
248                             None => cstore::NativeUnknown
249                         };
250                         let n = items.iter().find(|n| {
251                             n.name().equiv(&("name"))
252                         }).and_then(|a| a.value_str());
253                         let n = match n {
254                             Some(n) => n,
255                             None => {
256                                 e.sess.span_err(m.span,
257                                     "#[link(...)] specified without \
258                                      `name = \"foo\"`");
259                                 InternedString::new("foo")
260                             }
261                         };
262                         if n.get().is_empty() {
263                             e.sess.span_err(m.span,
264                                             "#[link(name = \"\")] given with \
265                                              empty name");
266                         } else {
267                             e.sess
268                              .cstore
269                              .add_used_library(n.get().to_string(), kind);
270                         }
271                     }
272                     None => {}
273                 }
274             }
275         }
276         _ => { }
277     }
278 }
279
280 fn existing_match(e: &Env, name: &str,
281                   hash: Option<&Svh>) -> Option<ast::CrateNum> {
282     let mut ret = None;
283     e.sess.cstore.iter_crate_data(|cnum, data| {
284         if data.name.as_slice() != name { return }
285
286         match hash {
287             Some(hash) if *hash == data.hash() => { ret = Some(cnum); return }
288             Some(..) => return,
289             None => {}
290         }
291
292         // When the hash is None we're dealing with a top-level dependency in
293         // which case we may have a specification on the command line for this
294         // library. Even though an upstream library may have loaded something of
295         // the same name, we have to make sure it was loaded from the exact same
296         // location as well.
297         //
298         // We're also sure to compare *paths*, not actual byte slices. The
299         // `source` stores paths which are normalized which may be different
300         // from the strings on the command line.
301         let source = e.sess.cstore.get_used_crate_source(cnum).unwrap();
302         match e.sess.opts.externs.find_equiv(&name) {
303             Some(locs) => {
304                 let found = locs.iter().any(|l| {
305                     let l = fs::realpath(&Path::new(l.as_slice())).ok();
306                     l == source.dylib || l == source.rlib
307                 });
308                 if found {
309                     ret = Some(cnum);
310                 }
311             }
312             None => ret = Some(cnum),
313         }
314     });
315     return ret;
316 }
317
318 fn register_crate<'a>(e: &mut Env,
319                   root: &Option<CratePaths>,
320                   ident: &str,
321                   name: &str,
322                   span: Span,
323                   lib: loader::Library)
324                         -> (ast::CrateNum, Rc<cstore::crate_metadata>,
325                             cstore::CrateSource) {
326     // Claim this crate number and cache it
327     let cnum = e.next_crate_num;
328     e.next_crate_num += 1;
329
330     // Stash paths for top-most crate locally if necessary.
331     let crate_paths = if root.is_none() {
332         Some(CratePaths {
333             ident: ident.to_string(),
334             dylib: lib.dylib.clone(),
335             rlib:  lib.rlib.clone(),
336         })
337     } else {
338         None
339     };
340     // Maintain a reference to the top most crate.
341     let root = if root.is_some() { root } else { &crate_paths };
342
343     let cnum_map = resolve_crate_deps(e, root, lib.metadata.as_slice(), span);
344
345     let loader::Library{ dylib, rlib, metadata } = lib;
346
347     let cmeta = Rc::new( cstore::crate_metadata {
348         name: name.to_string(),
349         data: metadata,
350         cnum_map: cnum_map,
351         cnum: cnum,
352         span: span,
353     });
354
355     let source = cstore::CrateSource {
356         dylib: dylib,
357         rlib: rlib,
358         cnum: cnum,
359     };
360
361     e.sess.cstore.set_crate_data(cnum, cmeta.clone());
362     e.sess.cstore.add_used_crate_source(source.clone());
363     (cnum, cmeta, source)
364 }
365
366 fn resolve_crate<'a>(e: &mut Env,
367                  root: &Option<CratePaths>,
368                  ident: &str,
369                  name: &str,
370                  hash: Option<&Svh>,
371                  span: Span)
372                      -> (ast::CrateNum, Rc<cstore::crate_metadata>,
373                          cstore::CrateSource) {
374     match existing_match(e, name, hash) {
375         None => {
376             let mut load_ctxt = loader::Context {
377                 sess: e.sess,
378                 span: span,
379                 ident: ident,
380                 crate_name: name,
381                 hash: hash.map(|a| &*a),
382                 filesearch: e.sess.target_filesearch(),
383                 os: e.sess.targ_cfg.os,
384                 triple: e.sess.targ_cfg.target_strs.target_triple.as_slice(),
385                 root: root,
386                 rejected_via_hash: vec!(),
387                 rejected_via_triple: vec!(),
388                 should_match_name: true,
389             };
390             let library = load_ctxt.load_library_crate();
391             register_crate(e, root, ident, name, span, library)
392         }
393         Some(cnum) => (cnum,
394                        e.sess.cstore.get_crate_data(cnum),
395                        e.sess.cstore.get_used_crate_source(cnum).unwrap())
396     }
397 }
398
399 // Go through the crate metadata and load any crates that it references
400 fn resolve_crate_deps(e: &mut Env,
401                       root: &Option<CratePaths>,
402                       cdata: &[u8], span : Span)
403                    -> cstore::cnum_map {
404     debug!("resolving deps of external crate");
405     // The map from crate numbers in the crate we're resolving to local crate
406     // numbers
407     decoder::get_crate_deps(cdata).iter().map(|dep| {
408         debug!("resolving dep crate {} hash: `{}`", dep.name, dep.hash);
409         let (local_cnum, _, _) = resolve_crate(e, root,
410                                                dep.name.as_slice(),
411                                                dep.name.as_slice(),
412                                                Some(&dep.hash),
413                                                span);
414         (dep.cnum, local_cnum)
415     }).collect()
416 }
417
418 pub struct PluginMetadataReader<'a> {
419     env: Env<'a>,
420 }
421
422 impl<'a> PluginMetadataReader<'a> {
423     pub fn new(sess: &'a Session) -> PluginMetadataReader<'a> {
424         PluginMetadataReader {
425             env: Env {
426                 sess: sess,
427                 next_crate_num: sess.cstore.next_crate_num(),
428             }
429         }
430     }
431
432     pub fn read_plugin_metadata(&mut self, krate: &ast::ViewItem) -> PluginMetadata {
433         let info = extract_crate_info(&self.env, krate).unwrap();
434         let target_triple = self.env.sess.targ_cfg.target_strs.target_triple.as_slice();
435         let is_cross = target_triple != driver::host_triple();
436         let mut should_link = info.should_link && !is_cross;
437         let os = config::get_os(driver::host_triple()).unwrap();
438         let mut load_ctxt = loader::Context {
439             sess: self.env.sess,
440             span: krate.span,
441             ident: info.ident.as_slice(),
442             crate_name: info.name.as_slice(),
443             hash: None,
444             filesearch: self.env.sess.host_filesearch(),
445             triple: driver::host_triple(),
446             os: os,
447             root: &None,
448             rejected_via_hash: vec!(),
449             rejected_via_triple: vec!(),
450             should_match_name: true,
451         };
452         let library = match load_ctxt.maybe_load_library_crate() {
453             Some(l) => l,
454             None if is_cross => {
455                 // try loading from target crates (only valid if there are
456                 // no syntax extensions)
457                 load_ctxt.triple = target_triple;
458                 load_ctxt.os = self.env.sess.targ_cfg.os;
459                 load_ctxt.filesearch = self.env.sess.target_filesearch();
460                 let lib = load_ctxt.load_library_crate();
461                 if decoder::get_plugin_registrar_fn(lib.metadata.as_slice()).is_some() {
462                     let message = format!("crate `{}` contains a plugin_registrar fn but \
463                                   only a version for triple `{}` could be found (need {})",
464                                   info.ident, target_triple, driver::host_triple());
465                     self.env.sess.span_err(krate.span, message.as_slice());
466                     // need to abort now because the syntax expansion
467                     // code will shortly attempt to load and execute
468                     // code from the found library.
469                     self.env.sess.abort_if_errors();
470                 }
471                 should_link = info.should_link;
472                 lib
473             }
474             None => { load_ctxt.report_load_errs(); unreachable!() },
475         };
476         let macros = decoder::get_exported_macros(library.metadata.as_slice());
477         let registrar = decoder::get_plugin_registrar_fn(library.metadata.as_slice()).map(|id| {
478             decoder::get_symbol(library.metadata.as_slice(), id)
479         });
480         if library.dylib.is_none() && registrar.is_some() {
481             let message = format!("plugin crate `{}` only found in rlib format, \
482                                    but must be available in dylib format",
483                                   info.ident);
484             self.env.sess.span_err(krate.span, message.as_slice());
485             // No need to abort because the loading code will just ignore this
486             // empty dylib.
487         }
488         let pc = PluginMetadata {
489             lib: library.dylib.clone(),
490             macros: macros,
491             registrar_symbol: registrar,
492         };
493         if should_link && existing_match(&self.env, info.name.as_slice(),
494                                          None).is_none() {
495             // register crate now to avoid double-reading metadata
496             register_crate(&mut self.env, &None, info.ident.as_slice(),
497                            info.name.as_slice(), krate.span, library);
498         }
499         pc
500     }
501 }