From 3ccbffac40958159c571ee515b85146397c0a35f Mon Sep 17 00:00:00 2001 From: "Felix S. Klock II" Date: Thu, 3 Apr 2014 17:43:57 +0200 Subject: [PATCH 1/4] After a hash mismatch error, emit file-system paths of crates involved. Fix #13266. There is a little bit of acrobatics in the definition of `crate_paths` to avoid calling `clone()` on the dylib/rlib unless we actually are going to need them. The other oddity is that I have replaced the `root_ident: Option<&str>` parameter with a `root: &Option`, which may surprise one who was expecting to see something like: `root: Option<&CratePaths>`. I went with the approach here because I could not come up with code for the alternative that was acceptable to the borrow checker. --- src/librustc/metadata/creader.rs | 44 +++++++++++++++----------- src/librustc/metadata/loader.rs | 54 ++++++++++++++++++++++++++------ 2 files changed, 70 insertions(+), 28 deletions(-) diff --git a/src/librustc/metadata/creader.rs b/src/librustc/metadata/creader.rs index 36febfc1a09..44f85bdb20f 100644 --- a/src/librustc/metadata/creader.rs +++ b/src/librustc/metadata/creader.rs @@ -21,6 +21,7 @@ use metadata::cstore; use metadata::decoder; use metadata::loader; use metadata::loader::Os; +use metadata::loader::CratePaths; use std::cell::RefCell; use std::rc::Rc; @@ -141,7 +142,7 @@ fn visit_view_item(e: &mut Env, i: &ast::ViewItem) { match extract_crate_info(e, i) { Some(info) => { - let cnum = resolve_crate(e, None, info.ident, &info.crate_id, None, + let cnum = resolve_crate(e, &None, info.ident, &info.crate_id, None, i.span); e.sess.cstore.add_extern_mod_stmt_cnum(info.id, cnum); } @@ -278,13 +279,13 @@ fn existing_match(e: &Env, crate_id: &CrateId, None } -fn resolve_crate(e: &mut Env, - root_ident: Option<&str>, - ident: &str, - crate_id: &CrateId, - hash: Option<&Svh>, - span: Span) - -> ast::CrateNum { +fn resolve_crate<'a>(e: &mut Env, + root: &Option, + ident: &str, + crate_id: &CrateId, + hash: Option<&Svh>, + span: Span) + -> ast::CrateNum { match existing_match(e, crate_id, hash) { None => { let id_hash = link::crate_id_hash(crate_id); @@ -297,11 +298,11 @@ fn resolve_crate(e: &mut Env, hash: hash.map(|a| &*a), os: e.os, intr: e.intr.clone(), - rejected_via_hash: false, + rejected_via_hash: None, }; let loader::Library { dylib, rlib, metadata - } = load_ctxt.load_library_crate(root_ident); + } = load_ctxt.load_library_crate(root); let crate_id = decoder::get_crate_id(metadata.as_slice()); let hash = decoder::get_crate_hash(metadata.as_slice()); @@ -316,15 +317,22 @@ fn resolve_crate(e: &mut Env, }); e.next_crate_num += 1; - // Maintain a reference to the top most crate. - let root_crate = match root_ident { - Some(c) => c, - None => load_ctxt.ident.clone() + // Stash paths for top-most crate locally if necessary. + let crate_paths = if root.is_none() { + Some(CratePaths { + ident: load_ctxt.ident.to_owned(), + dylib: dylib.clone(), + rlib: rlib.clone(), + }) + } else { + None }; + // Maintain a reference to the top most crate. + let root = if root.is_some() { root } else { &crate_paths }; // Now resolve the crates referenced by this crate let cnum_map = resolve_crate_deps(e, - Some(root_crate), + root, metadata.as_slice(), span); @@ -349,7 +357,7 @@ fn resolve_crate(e: &mut Env, // Go through the crate metadata and load any crates that it references fn resolve_crate_deps(e: &mut Env, - root_ident: Option<&str>, + root: &Option, cdata: &[u8], span : Span) -> cstore::cnum_map { debug!("resolving deps of external crate"); @@ -360,7 +368,7 @@ fn resolve_crate_deps(e: &mut Env, for dep in r.iter() { let extrn_cnum = dep.cnum; debug!("resolving dep crate {} hash: `{}`", dep.crate_id, dep.hash); - let local_cnum = resolve_crate(e, root_ident, + let local_cnum = resolve_crate(e, root, dep.crate_id.name.as_slice(), &dep.crate_id, Some(&dep.hash), @@ -393,7 +401,7 @@ impl<'a> Loader<'a> { impl<'a> CrateLoader for Loader<'a> { fn load_crate(&mut self, krate: &ast::ViewItem) -> MacroCrate { let info = extract_crate_info(&self.env, krate).unwrap(); - let cnum = resolve_crate(&mut self.env, None, info.ident, + let cnum = resolve_crate(&mut self.env, &None, info.ident, &info.crate_id, None, krate.span); let library = self.env.sess.cstore.get_used_crate_source(cnum).unwrap(); MacroCrate { diff --git a/src/librustc/metadata/loader.rs b/src/librustc/metadata/loader.rs index 8a3d6567c77..caa2c6c0133 100644 --- a/src/librustc/metadata/loader.rs +++ b/src/librustc/metadata/loader.rs @@ -45,6 +45,10 @@ pub enum Os { OsFreebsd } +pub struct ViaHash { + path: Path, +} + pub struct Context<'a> { pub sess: &'a Session, pub span: Span, @@ -54,7 +58,8 @@ pub struct Context<'a> { pub hash: Option<&'a Svh>, pub os: Os, pub intr: Rc, - pub rejected_via_hash: bool, + /// Some if rejected + pub rejected_via_hash: Option } pub struct Library { @@ -69,6 +74,25 @@ pub struct ArchiveMetadata { data: &'static [u8], } +pub struct CratePaths { + pub ident: ~str, + pub dylib: Option, + pub rlib: Option +} + +impl CratePaths { + fn describe_paths(&self) -> ~str { + match (&self.dylib, &self.rlib) { + (&None, &None) + => ~"", + (&Some(ref p), &None) | (&None, &Some(ref p)) + => format!("{}", p.display()), + (&Some(ref p1), &Some(ref p2)) + => format!("{}, {}", p1.display(), p2.display()), + } + } +} + // FIXME(#11857) this should be a "real" realpath fn realpath(p: &Path) -> Path { use std::os; @@ -82,26 +106,35 @@ fn realpath(p: &Path) -> Path { } impl<'a> Context<'a> { - pub fn load_library_crate(&mut self, root_ident: Option<&str>) -> Library { + pub fn load_library_crate(&mut self, root: &Option) -> Library { match self.find_library_crate() { Some(t) => t, None => { self.sess.abort_if_errors(); - let message = if self.rejected_via_hash { + let message = if self.rejected_via_hash.is_some() { format!("found possibly newer version of crate `{}`", self.ident) } else { format!("can't find crate for `{}`", self.ident) }; - let message = match root_ident { - None => message, - Some(c) => format!("{} which `{}` depends on", message, c), + let message = match root { + &None => message, + &Some(ref r) => format!("{} which `{}` depends on", + message, r.ident) }; self.sess.span_err(self.span, message); - if self.rejected_via_hash { + if self.rejected_via_hash.is_some() { self.sess.span_note(self.span, "perhaps this crate needs \ to be recompiled?"); + self.rejected_via_hash.as_ref().map( + |r| self.sess.note(format!( + "crate `{}` at path: {}", + self.ident, r.path.display()))); + root.as_ref().map( + |r| self.sess.note(format!( + "crate `{}` at path(s): {}", + r.ident, r.describe_paths()))); } self.sess.abort_if_errors(); unreachable!() @@ -291,7 +324,7 @@ impl<'a> Context<'a> { info!("{} reading metadata from: {}", flavor, lib.display()); let metadata = match get_metadata_section(self.os, &lib) { Ok(blob) => { - if self.crate_matches(blob.as_slice()) { + if self.crate_matches(blob.as_slice(), &lib) { blob } else { info!("metadata mismatch"); @@ -326,7 +359,7 @@ impl<'a> Context<'a> { return if error > 0 {None} else {ret} } - fn crate_matches(&mut self, crate_data: &[u8]) -> bool { + fn crate_matches(&mut self, crate_data: &[u8], libpath: &Path) -> bool { match decoder::maybe_get_crate_id(crate_data) { Some(ref id) if self.crate_id.matches(id) => {} _ => return false @@ -338,7 +371,8 @@ impl<'a> Context<'a> { None => true, Some(myhash) => { if *myhash != hash { - self.rejected_via_hash = true; + self.rejected_via_hash = + Some(ViaHash{ path: libpath.clone(), }); false } else { true From 1599d2260347046e6d8841493ffc64cc876fda07 Mon Sep 17 00:00:00 2001 From: "Felix S. Klock II" Date: Sat, 5 Apr 2014 03:46:43 +0200 Subject: [PATCH 2/4] Added session.fileline_note() method and support infrastucture for it. Add way to print notes with just file:linenum prefix (preserving integration with source lookup for e.g. vi and emacs) but don't repeat the other span info. --- src/librustc/driver/session.rs | 3 ++ src/libsyntax/diagnostic.rs | 50 ++++++++++++++++++++++++++++------ 2 files changed, 45 insertions(+), 8 deletions(-) diff --git a/src/librustc/driver/session.rs b/src/librustc/driver/session.rs index c2fec3871ab..c25a2e79824 100644 --- a/src/librustc/driver/session.rs +++ b/src/librustc/driver/session.rs @@ -232,6 +232,9 @@ impl Session { pub fn span_end_note(&self, sp: Span, msg: &str) { self.diagnostic().span_end_note(sp, msg) } + pub fn fileline_note(&self, sp: Span, msg: &str) { + self.diagnostic().fileline_note(sp, msg) + } pub fn note(&self, msg: &str) { self.diagnostic().handler().note(msg) } diff --git a/src/libsyntax/diagnostic.rs b/src/libsyntax/diagnostic.rs index fb0f458b88a..e3514b6f3f3 100644 --- a/src/libsyntax/diagnostic.rs +++ b/src/libsyntax/diagnostic.rs @@ -20,11 +20,37 @@ use term; // maximum number of lines we will print for each error; arbitrary. static MAX_LINES: uint = 6u; +#[deriving(Clone)] +pub enum RenderSpan { + /// A FullSpan renders with both with an initial line for the + /// message, prefixed by file:linenum, followed by a summary of + /// the source code covered by the span. + FullSpan(Span), + + /// A FileLine renders with just a line for the message prefixed + /// by file:linenum. + FileLine(Span), +} + +impl RenderSpan { + fn span(self) -> Span { + match self { + FullSpan(s) | FileLine(s) => s + } + } + fn is_full_span(&self) -> bool { + match self { + &FullSpan(..) => true, + &FileLine(..) => false, + } + } +} + pub trait Emitter { fn emit(&mut self, cmsp: Option<(&codemap::CodeMap, Span)>, msg: &str, lvl: Level); fn custom_emit(&mut self, cm: &codemap::CodeMap, - sp: Span, msg: &str, lvl: Level); + sp: RenderSpan, msg: &str, lvl: Level); } /// This structure is used to signify that a task has failed with a fatal error @@ -60,7 +86,10 @@ impl SpanHandler { self.handler.emit(Some((&self.cm, sp)), msg, Note); } pub fn span_end_note(&self, sp: Span, msg: &str) { - self.handler.custom_emit(&self.cm, sp, msg, Note); + self.handler.custom_emit(&self.cm, FullSpan(sp), msg, Note); + } + pub fn fileline_note(&self, sp: Span, msg: &str) { + self.handler.custom_emit(&self.cm, FileLine(sp), msg, Note); } pub fn span_bug(&self, sp: Span, msg: &str) -> ! { self.handler.emit(Some((&self.cm, sp)), msg, Bug); @@ -132,7 +161,7 @@ impl Handler { self.emit.borrow_mut().emit(cmsp, msg, lvl); } pub fn custom_emit(&self, cm: &codemap::CodeMap, - sp: Span, msg: &str, lvl: Level) { + sp: RenderSpan, msg: &str, lvl: Level) { self.emit.borrow_mut().custom_emit(cm, sp, msg, lvl); } } @@ -258,7 +287,7 @@ impl Emitter for EmitterWriter { msg: &str, lvl: Level) { let error = match cmsp { - Some((cm, sp)) => emit(self, cm, sp, msg, lvl, false), + Some((cm, sp)) => emit(self, cm, FullSpan(sp), msg, lvl, false), None => print_diagnostic(self, "", lvl, msg), }; @@ -269,7 +298,7 @@ impl Emitter for EmitterWriter { } fn custom_emit(&mut self, cm: &codemap::CodeMap, - sp: Span, msg: &str, lvl: Level) { + sp: RenderSpan, msg: &str, lvl: Level) { match emit(self, cm, sp, msg, lvl, true) { Ok(()) => {} Err(e) => fail!("failed to print diagnostics: {}", e), @@ -277,8 +306,9 @@ impl Emitter for EmitterWriter { } } -fn emit(dst: &mut EmitterWriter, cm: &codemap::CodeMap, sp: Span, +fn emit(dst: &mut EmitterWriter, cm: &codemap::CodeMap, rsp: RenderSpan, msg: &str, lvl: Level, custom: bool) -> io::IoResult<()> { + let sp = rsp.span(); let ss = cm.span_to_str(sp); let lines = cm.span_to_lines(sp); if custom { @@ -288,10 +318,14 @@ fn emit(dst: &mut EmitterWriter, cm: &codemap::CodeMap, sp: Span, let span_end = Span { lo: sp.hi, hi: sp.hi, expn_info: sp.expn_info}; let ses = cm.span_to_str(span_end); try!(print_diagnostic(dst, ses, lvl, msg)); - try!(custom_highlight_lines(dst, cm, sp, lvl, lines)); + if rsp.is_full_span() { + try!(custom_highlight_lines(dst, cm, sp, lvl, lines)); + } } else { try!(print_diagnostic(dst, ss, lvl, msg)); - try!(highlight_lines(dst, cm, sp, lvl, lines)); + if rsp.is_full_span() { + try!(highlight_lines(dst, cm, sp, lvl, lines)); + } } print_macro_backtrace(dst, cm, sp) } From 4afd060a5917dfcb6f841e7619d9f9026bd20b9d Mon Sep 17 00:00:00 2001 From: "Felix S. Klock II" Date: Sat, 5 Apr 2014 03:49:03 +0200 Subject: [PATCH 3/4] Accumulate list of paths for crate hash mismatch. (i.e. semi-generalized version of prior errorinfo gathering.) Also revised presentation to put each path on its own line, prefixed by file:linenum information. --- src/librustc/metadata/creader.rs | 2 +- src/librustc/metadata/loader.rs | 48 +++++++++++++++++--------------- 2 files changed, 27 insertions(+), 23 deletions(-) diff --git a/src/librustc/metadata/creader.rs b/src/librustc/metadata/creader.rs index 44f85bdb20f..05503a4becf 100644 --- a/src/librustc/metadata/creader.rs +++ b/src/librustc/metadata/creader.rs @@ -298,7 +298,7 @@ fn resolve_crate<'a>(e: &mut Env, hash: hash.map(|a| &*a), os: e.os, intr: e.intr.clone(), - rejected_via_hash: None, + rejected_via_hash: vec!(), }; let loader::Library { dylib, rlib, metadata diff --git a/src/librustc/metadata/loader.rs b/src/librustc/metadata/loader.rs index caa2c6c0133..695bd9b17ba 100644 --- a/src/librustc/metadata/loader.rs +++ b/src/librustc/metadata/loader.rs @@ -45,7 +45,7 @@ pub enum Os { OsFreebsd } -pub struct ViaHash { +pub struct HashMismatch { path: Path, } @@ -58,8 +58,7 @@ pub struct Context<'a> { pub hash: Option<&'a Svh>, pub os: Os, pub intr: Rc, - /// Some if rejected - pub rejected_via_hash: Option + pub rejected_via_hash: Vec } pub struct Library { @@ -81,14 +80,12 @@ pub struct CratePaths { } impl CratePaths { - fn describe_paths(&self) -> ~str { + fn paths(&self) -> Vec { match (&self.dylib, &self.rlib) { - (&None, &None) - => ~"", - (&Some(ref p), &None) | (&None, &Some(ref p)) - => format!("{}", p.display()), - (&Some(ref p1), &Some(ref p2)) - => format!("{}, {}", p1.display(), p2.display()), + (&None, &None) => vec!(), + (&Some(ref p), &None) | + (&None, &Some(ref p)) => vec!(p.clone()), + (&Some(ref p1), &Some(ref p2)) => vec!(p1.clone(), p2.clone()), } } } @@ -111,7 +108,7 @@ impl<'a> Context<'a> { Some(t) => t, None => { self.sess.abort_if_errors(); - let message = if self.rejected_via_hash.is_some() { + let message = if self.rejected_via_hash.len() > 0 { format!("found possibly newer version of crate `{}`", self.ident) } else { @@ -124,17 +121,25 @@ impl<'a> Context<'a> { }; self.sess.span_err(self.span, message); - if self.rejected_via_hash.is_some() { + if self.rejected_via_hash.len() > 0 { self.sess.span_note(self.span, "perhaps this crate needs \ to be recompiled?"); - self.rejected_via_hash.as_ref().map( - |r| self.sess.note(format!( - "crate `{}` at path: {}", - self.ident, r.path.display()))); - root.as_ref().map( - |r| self.sess.note(format!( - "crate `{}` at path(s): {}", - r.ident, r.describe_paths()))); + let mismatches = self.rejected_via_hash.iter(); + for (i, &HashMismatch{ ref path }) in mismatches.enumerate() { + self.sess.fileline_note(self.span, + format!("crate `{}` path \\#{}: {}", + self.ident, i+1, path.display())); + } + match root { + &None => {} + &Some(ref r) => { + for (i, path) in r.paths().iter().enumerate() { + self.sess.fileline_note(self.span, + format!("crate `{}` path \\#{}: {}", + r.ident, i+1, path.display())); + } + } + } } self.sess.abort_if_errors(); unreachable!() @@ -371,8 +376,7 @@ impl<'a> Context<'a> { None => true, Some(myhash) => { if *myhash != hash { - self.rejected_via_hash = - Some(ViaHash{ path: libpath.clone(), }); + self.rejected_via_hash.push(HashMismatch{ path: libpath.clone() }); false } else { true From f4cde4eddc04733da478ae1030ebc1ddeec04d80 Mon Sep 17 00:00:00 2001 From: "Felix S. Klock II" Date: Sat, 5 Apr 2014 03:53:28 +0200 Subject: [PATCH 4/4] Rough regression test for #13266. All it checks, unfortunately, is that you actually printed at least two lines for crateA paths and at least one line for crateB paths. But that's enough to capture the spirit of the bug, I think. I did not bother trying to verify that the paths themselves reflected where the crates end up. --- .../many-crates-but-no-match/Makefile | 34 +++++++++++++++++++ .../many-crates-but-no-match/crateA1.rs | 14 ++++++++ .../many-crates-but-no-match/crateA2.rs | 14 ++++++++ .../many-crates-but-no-match/crateA3.rs | 14 ++++++++ .../many-crates-but-no-match/crateB.rs | 11 ++++++ .../many-crates-but-no-match/crateC.rs | 13 +++++++ 6 files changed, 100 insertions(+) create mode 100644 src/test/run-make/many-crates-but-no-match/Makefile create mode 100644 src/test/run-make/many-crates-but-no-match/crateA1.rs create mode 100644 src/test/run-make/many-crates-but-no-match/crateA2.rs create mode 100644 src/test/run-make/many-crates-but-no-match/crateA3.rs create mode 100644 src/test/run-make/many-crates-but-no-match/crateB.rs create mode 100644 src/test/run-make/many-crates-but-no-match/crateC.rs diff --git a/src/test/run-make/many-crates-but-no-match/Makefile b/src/test/run-make/many-crates-but-no-match/Makefile new file mode 100644 index 00000000000..4d80c09c26b --- /dev/null +++ b/src/test/run-make/many-crates-but-no-match/Makefile @@ -0,0 +1,34 @@ +-include ../tools.mk + +# Modelled after compile-fail/changing-crates test, but this one puts +# more than one (mismatching) candidate crate into the search path, +# which did not appear directly expressible in compile-fail/aux-build +# infrastructure. +# +# Note that we move the built libraries into target direcrtories rather than +# use the `--out-dir` option because the `../tools.mk` file already bakes a +# use of `--out-dir` into the definition of $(RUSTC). + +A1=$(TMPDIR)/a1 +A2=$(TMPDIR)/a2 +A3=$(TMPDIR)/a3 + +# A hack to match distinct lines of output from a single run. +LOG=$(TMPDIR)/log.txt + +all: + mkdir -p $(A1) $(A2) $(A3) + $(RUSTC) --crate-type=rlib crateA1.rs + mv $(TMPDIR)/$(call RLIB_GLOB,crateA) $(A1) + $(RUSTC) --crate-type=rlib -L$(A1) crateB.rs + $(RUSTC) --crate-type=rlib crateA2.rs + mv $(TMPDIR)/$(call RLIB_GLOB,crateA) $(A2) + $(RUSTC) --crate-type=rlib crateA3.rs + mv $(TMPDIR)/$(call RLIB_GLOB,crateA) $(A3) + # Ensure crateC fails to compile since A1 is "missing" and A2/A3 hashes do not match + $(RUSTC) -L$(A2) -L$(A3) crateC.rs >$(LOG) 2>&1 || true + grep "error: found possibly newer version of crate \`crateA\` which \`crateB\` depends on" $(LOG) + grep "note: perhaps this crate needs to be recompiled?" $(LOG) + grep "note: crate \`crateA\` path #1:" $(LOG) + grep "note: crate \`crateA\` path #2:" $(LOG) + grep "note: crate \`crateB\` path #1:" $(LOG) diff --git a/src/test/run-make/many-crates-but-no-match/crateA1.rs b/src/test/run-make/many-crates-but-no-match/crateA1.rs new file mode 100644 index 00000000000..0c88cf4745a --- /dev/null +++ b/src/test/run-make/many-crates-but-no-match/crateA1.rs @@ -0,0 +1,14 @@ +// Copyright 2014 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#![crate_id="crateA"] + +// Base crate +pub fn func() {} diff --git a/src/test/run-make/many-crates-but-no-match/crateA2.rs b/src/test/run-make/many-crates-but-no-match/crateA2.rs new file mode 100644 index 00000000000..e3fb50e13d0 --- /dev/null +++ b/src/test/run-make/many-crates-but-no-match/crateA2.rs @@ -0,0 +1,14 @@ +// Copyright 2014 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#![crate_id="crateA"] + +// Base crate +pub fn func() { println!("hello"); } diff --git a/src/test/run-make/many-crates-but-no-match/crateA3.rs b/src/test/run-make/many-crates-but-no-match/crateA3.rs new file mode 100644 index 00000000000..ad9d458be24 --- /dev/null +++ b/src/test/run-make/many-crates-but-no-match/crateA3.rs @@ -0,0 +1,14 @@ +// Copyright 2014 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#![crate_id="crateA"] + +// Base crate +pub fn foo() { println!("world!"); } diff --git a/src/test/run-make/many-crates-but-no-match/crateB.rs b/src/test/run-make/many-crates-but-no-match/crateB.rs new file mode 100644 index 00000000000..bf55017c646 --- /dev/null +++ b/src/test/run-make/many-crates-but-no-match/crateB.rs @@ -0,0 +1,11 @@ +// Copyright 2014 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +extern crate crateA; diff --git a/src/test/run-make/many-crates-but-no-match/crateC.rs b/src/test/run-make/many-crates-but-no-match/crateC.rs new file mode 100644 index 00000000000..174d9382b76 --- /dev/null +++ b/src/test/run-make/many-crates-but-no-match/crateC.rs @@ -0,0 +1,13 @@ +// Copyright 2014 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +extern crate crateB; + +fn main() {}