Skip to content

Commit d70f909

Browse files
committed
auto merge of #12164 : alexcrichton/rust/rlibs-and-dylibs, r=cmr
The first commit improves error messages during linking, and the second commit improves error messages during crate-loading time. Closes #12297 Closes #12377
2 parents 7e9bcc5 + afa5f57 commit d70f909

File tree

5 files changed

+240
-94
lines changed

5 files changed

+240
-94
lines changed

src/librustc/metadata/loader.rs

+167-94
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ use syntax::attr::AttrMetaMethods;
2626

2727
use std::c_str::ToCStr;
2828
use std::cast;
29+
use std::hashmap::{HashMap, HashSet};
2930
use std::cmp;
3031
use std::io;
3132
use std::os::consts::{macos, freebsd, linux, android, win32};
@@ -69,6 +70,7 @@ impl Context {
6970
match self.find_library_crate() {
7071
Some(t) => t,
7172
None => {
73+
self.sess.abort_if_errors();
7274
let message = match root_ident {
7375
None => format!("can't find crate for `{}`", self.ident),
7476
Some(c) => format!("can't find crate for `{}` which `{}` depends on",
@@ -82,78 +84,107 @@ impl Context {
8284

8385
fn find_library_crate(&self) -> Option<Library> {
8486
let filesearch = self.sess.filesearch;
85-
let crate_name = self.name.clone();
8687
let (dyprefix, dysuffix) = self.dylibname();
8788

8889
// want: crate_name.dir_part() + prefix + crate_name.file_part + "-"
89-
let dylib_prefix = format!("{}{}-", dyprefix, crate_name);
90-
let rlib_prefix = format!("lib{}-", crate_name);
90+
let dylib_prefix = format!("{}{}-", dyprefix, self.name);
91+
let rlib_prefix = format!("lib{}-", self.name);
9192

92-
let mut matches = ~[];
93-
filesearch.search(|path| {
94-
match path.filename_str() {
95-
None => FileDoesntMatch,
96-
Some(file) => {
97-
let (candidate, existing) = if file.starts_with(rlib_prefix) &&
98-
file.ends_with(".rlib") {
99-
debug!("{} is an rlib candidate", path.display());
100-
(true, self.add_existing_rlib(matches, path, file))
101-
} else if file.starts_with(dylib_prefix) &&
102-
file.ends_with(dysuffix) {
103-
debug!("{} is a dylib candidate", path.display());
104-
(true, self.add_existing_dylib(matches, path, file))
105-
} else {
106-
(false, false)
107-
};
93+
let mut candidates = HashMap::new();
10894

109-
if candidate && existing {
95+
// First, find all possible candidate rlibs and dylibs purely based on
96+
// the name of the files themselves. We're trying to match against an
97+
// exact crate_id and a possibly an exact hash.
98+
//
99+
// During this step, we can filter all found libraries based on the
100+
// name and id found in the crate id (we ignore the path portion for
101+
// filename matching), as well as the exact hash (if specified). If we
102+
// end up having many candidates, we must look at the metadata to
103+
// perform exact matches against hashes/crate ids. Note that opening up
104+
// the metadata is where we do an exact match against the full contents
105+
// of the crate id (path/name/id).
106+
//
107+
// The goal of this step is to look at as little metadata as possible.
108+
filesearch.search(|path| {
109+
let file = match path.filename_str() {
110+
None => return FileDoesntMatch,
111+
Some(file) => file,
112+
};
113+
if file.starts_with(rlib_prefix) && file.ends_with(".rlib") {
114+
info!("rlib candidate: {}", path.display());
115+
match self.try_match(file, rlib_prefix, ".rlib") {
116+
Some(hash) => {
117+
info!("rlib accepted, hash: {}", hash);
118+
let slot = candidates.find_or_insert_with(hash, |_| {
119+
(HashSet::new(), HashSet::new())
120+
});
121+
let (ref mut rlibs, _) = *slot;
122+
rlibs.insert(path.clone());
110123
FileMatches
111-
} else if candidate {
112-
match get_metadata_section(self.os, path) {
113-
Some(cvec) =>
114-
if crate_matches(cvec.as_slice(),
115-
self.name.clone(),
116-
self.version.clone(),
117-
self.hash.clone()) {
118-
debug!("found {} with matching crate_id",
119-
path.display());
120-
let (rlib, dylib) = if file.ends_with(".rlib") {
121-
(Some(path.clone()), None)
122-
} else {
123-
(None, Some(path.clone()))
124-
};
125-
matches.push(Library {
126-
rlib: rlib,
127-
dylib: dylib,
128-
metadata: cvec,
129-
});
130-
FileMatches
131-
} else {
132-
debug!("skipping {}, crate_id doesn't match",
133-
path.display());
134-
FileDoesntMatch
135-
},
136-
_ => {
137-
debug!("could not load metadata for {}",
138-
path.display());
139-
FileDoesntMatch
140-
}
141-
}
142-
} else {
124+
}
125+
None => {
126+
info!("rlib rejected");
143127
FileDoesntMatch
144128
}
145129
}
130+
} else if file.starts_with(dylib_prefix) && file.ends_with(dysuffix){
131+
info!("dylib candidate: {}", path.display());
132+
match self.try_match(file, dylib_prefix, dysuffix) {
133+
Some(hash) => {
134+
info!("dylib accepted, hash: {}", hash);
135+
let slot = candidates.find_or_insert_with(hash, |_| {
136+
(HashSet::new(), HashSet::new())
137+
});
138+
let (_, ref mut dylibs) = *slot;
139+
dylibs.insert(path.clone());
140+
FileMatches
141+
}
142+
None => {
143+
info!("dylib rejected");
144+
FileDoesntMatch
145+
}
146+
}
147+
} else {
148+
FileDoesntMatch
146149
}
147150
});
148151

149-
match matches.len() {
152+
// We have now collected all known libraries into a set of candidates
153+
// keyed of the filename hash listed. For each filename, we also have a
154+
// list of rlibs/dylibs that apply. Here, we map each of these lists
155+
// (per hash), to a Library candidate for returning.
156+
//
157+
// A Library candidate is created if the metadata for the set of
158+
// libraries corresponds to the crate id and hash criteria that this
159+
// serach is being performed for.
160+
let mut libraries = ~[];
161+
for (_hash, (rlibs, dylibs)) in candidates.move_iter() {
162+
let mut metadata = None;
163+
let rlib = self.extract_one(rlibs, "rlib", &mut metadata);
164+
let dylib = self.extract_one(dylibs, "dylib", &mut metadata);
165+
match metadata {
166+
Some(metadata) => {
167+
libraries.push(Library {
168+
dylib: dylib,
169+
rlib: rlib,
170+
metadata: metadata,
171+
})
172+
}
173+
None => {}
174+
}
175+
}
176+
177+
// Having now translated all relevant found hashes into libraries, see
178+
// what we've got and figure out if we found multiple candidates for
179+
// libraries or not.
180+
match libraries.len() {
150181
0 => None,
151-
1 => Some(matches[0]),
182+
1 => Some(libraries[0]),
152183
_ => {
153184
self.sess.span_err(self.span,
154-
format!("multiple matching crates for `{}`", crate_name));
185+
format!("multiple matching crates for `{}`", self.name));
155186
self.sess.note("candidates:");
156-
for lib in matches.iter() {
187+
for lib in libraries.iter() {
157188
match lib.dylib {
158189
Some(ref p) => {
159190
self.sess.note(format!("path: {}", p.display()));
@@ -175,50 +206,90 @@ impl Context {
175206
}
176207
}
177208
}
178-
self.sess.abort_if_errors();
179209
None
180210
}
181211
}
182212
}
183213

184-
fn add_existing_rlib(&self, libs: &mut [Library],
185-
path: &Path, file: &str) -> bool {
186-
let (prefix, suffix) = self.dylibname();
187-
let file = file.slice_from(3); // chop off 'lib'
188-
let file = file.slice_to(file.len() - 5); // chop off '.rlib'
189-
let file = format!("{}{}{}", prefix, file, suffix);
190-
191-
for lib in libs.mut_iter() {
192-
match lib.dylib {
193-
Some(ref p) if p.filename_str() == Some(file.as_slice()) => {
194-
assert!(lib.rlib.is_none()); // FIXME: legit compiler error
195-
lib.rlib = Some(path.clone());
196-
return true;
197-
}
198-
Some(..) | None => {}
199-
}
214+
// Attempts to match the requested version of a library against the file
215+
// specified. The prefix/suffix are specified (disambiguates between
216+
// rlib/dylib).
217+
//
218+
// The return value is `None` if `file` doesn't look like a rust-generated
219+
// library, or if a specific version was requested and it doens't match the
220+
// apparent file's version.
221+
//
222+
// If everything checks out, then `Some(hash)` is returned where `hash` is
223+
// the listed hash in the filename itself.
224+
fn try_match(&self, file: &str, prefix: &str, suffix: &str) -> Option<~str>{
225+
let middle = file.slice(prefix.len(), file.len() - suffix.len());
226+
debug!("matching -- {}, middle: {}", file, middle);
227+
let mut parts = middle.splitn('-', 1);
228+
let hash = match parts.next() { Some(h) => h, None => return None };
229+
debug!("matching -- {}, hash: {}", file, hash);
230+
let vers = match parts.next() { Some(v) => v, None => return None };
231+
debug!("matching -- {}, vers: {}", file, vers);
232+
if !self.version.is_empty() && self.version.as_slice() != vers {
233+
return None
234+
}
235+
debug!("matching -- {}, vers ok (requested {})", file,
236+
self.version);
237+
// hashes in filenames are prefixes of the "true hash"
238+
if self.hash.is_empty() || self.hash.starts_with(hash) {
239+
debug!("matching -- {}, hash ok (requested {})", file, self.hash);
240+
Some(hash.to_owned())
241+
} else {
242+
None
200243
}
201-
return false;
202244
}
203245

204-
fn add_existing_dylib(&self, libs: &mut [Library],
205-
path: &Path, file: &str) -> bool {
206-
let (prefix, suffix) = self.dylibname();
207-
let file = file.slice_from(prefix.len());
208-
let file = file.slice_to(file.len() - suffix.len());
209-
let file = format!("lib{}.rlib", file);
246+
// Attempts to extract *one* library from the set `m`. If the set has no
247+
// elements, `None` is returned. If the set has more than one element, then
248+
// the errors and notes are emitted about the set of libraries.
249+
//
250+
// With only one library in the set, this function will extract it, and then
251+
// read the metadata from it if `*slot` is `None`. If the metadata couldn't
252+
// be read, it is assumed that the file isn't a valid rust library (no
253+
// errors are emitted).
254+
//
255+
// FIXME(#10786): for an optimization, we only read one of the library's
256+
// metadata sections. In theory we should read both, but
257+
// reading dylib metadata is quite slow.
258+
fn extract_one(&self, m: HashSet<Path>, flavor: &str,
259+
slot: &mut Option<MetadataBlob>) -> Option<Path> {
260+
if m.len() == 0 { return None }
261+
if m.len() > 1 {
262+
self.sess.span_err(self.span,
263+
format!("multiple {} candidates for `{}` \
264+
found", flavor, self.name));
265+
for (i, path) in m.iter().enumerate() {
266+
self.sess.span_note(self.span,
267+
format!(r"candidate \#{}: {}", i + 1,
268+
path.display()));
269+
}
270+
return None
271+
}
210272

211-
for lib in libs.mut_iter() {
212-
match lib.rlib {
213-
Some(ref p) if p.filename_str() == Some(file.as_slice()) => {
214-
assert!(lib.dylib.is_none()); // FIXME: legit compiler error
215-
lib.dylib = Some(path.clone());
216-
return true;
273+
let lib = m.move_iter().next().unwrap();
274+
if slot.is_none() {
275+
info!("{} reading meatadata from: {}", flavor, lib.display());
276+
match get_metadata_section(self.os, &lib) {
277+
Some(blob) => {
278+
if crate_matches(blob.as_slice(), self.name,
279+
self.version, self.hash) {
280+
*slot = Some(blob);
281+
} else {
282+
info!("metadata mismatch");
283+
return None;
284+
}
285+
}
286+
None => {
287+
info!("no metadata found");
288+
return None
217289
}
218-
Some(..) | None => {}
219290
}
220291
}
221-
return false;
292+
return Some(lib);
222293
}
223294

224295
// Returns the corresponding (prefix, suffix) that files need to have for
@@ -239,16 +310,16 @@ pub fn note_crateid_attr(diag: @SpanHandler, crateid: &CrateId) {
239310
}
240311

241312
fn crate_matches(crate_data: &[u8],
242-
name: ~str,
243-
version: ~str,
244-
hash: ~str) -> bool {
313+
name: &str,
314+
version: &str,
315+
hash: &str) -> bool {
245316
let attrs = decoder::get_crate_attributes(crate_data);
246317
match attr::find_crateid(attrs) {
247318
None => false,
248319
Some(crateid) => {
249320
if !hash.is_empty() {
250321
let chash = decoder::get_crate_hash(crate_data);
251-
if chash != hash { return false; }
322+
if chash.as_slice() != hash { return false; }
252323
}
253324
name == crateid.name &&
254325
(version.is_empty() ||
@@ -383,7 +454,9 @@ pub fn read_meta_section_name(os: Os) -> &'static str {
383454
pub fn list_file_metadata(os: Os, path: &Path,
384455
out: &mut io::Writer) -> io::IoResult<()> {
385456
match get_metadata_section(os, path) {
386-
Some(bytes) => decoder::list_crate_metadata(bytes.as_slice(), out),
387-
None => write!(out, "could not find metadata in {}.\n", path.display())
457+
Some(bytes) => decoder::list_crate_metadata(bytes.as_slice(), out),
458+
None => {
459+
write!(out, "could not find metadata in {}.\n", path.display())
460+
}
388461
}
389462
}

src/test/auxiliary/issue-11908-1.rs

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// Copyright 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+
// no-prefer-dynamic
12+
13+
#[crate_id = "collections#0.10-pre"];
14+
#[crate_type = "dylib"];

src/test/auxiliary/issue-11908-2.rs

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// Copyright 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+
// no-prefer-dynamic
12+
13+
#[crate_id = "collections#0.10-pre"];
14+
#[crate_type = "rlib"];
+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// Copyright 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+
// aux-build:issue-11908-1.rs
12+
// ignore-android this test is incompatible with the android test runner
13+
// error-pattern: multiple dylib candidates for `collections` found
14+
15+
// This test ensures that if you have the same rlib or dylib at two locations
16+
// in the same path that you don't hit an assertion in the compiler.
17+
//
18+
// Note that this relies on `libcollections` to be in the path somewhere else,
19+
// and then our aux-built libraries will collide with libcollections (they have
20+
// the same version listed)
21+
22+
extern crate collections;
23+
24+
fn main() {}

0 commit comments

Comments
 (0)