Skip to content

Commit 882c25f

Browse files
committed
auto merge of #12398 : alexcrichton/rust/rlibs-and-dylibs-2, r=cmr
The new methodology can be found in the re-worded comment, but the gist of it is that -C prefer-dynamic doesn't turn off static linkage. The error messages should also be a little more sane now. Closes #12133
2 parents 47b0527 + 35c6e22 commit 882c25f

File tree

9 files changed

+245
-90
lines changed

9 files changed

+245
-90
lines changed

mk/tests.mk

-4
Original file line numberDiff line numberDiff line change
@@ -537,10 +537,6 @@ TEST_SREQ$(1)_T_$(2)_H_$(3) = \
537537
# remove directive, if present, from CFG_RUSTC_FLAGS (issue #7898).
538538
CTEST_RUSTC_FLAGS := $$(subst --cfg ndebug,,$$(CFG_RUSTC_FLAGS))
539539

540-
# There's no need our entire test suite to take up gigabytes of space on disk
541-
# including copies of libstd/libextra all over the place
542-
CTEST_RUSTC_FLAGS := $$(CTEST_RUSTC_FLAGS) -C prefer-dynamic
543-
544540
# The tests can not be optimized while the rest of the compiler is optimized, so
545541
# filter out the optimization (if any) from rustc and then figure out if we need
546542
# to be optimized

src/compiletest/header.rs

+12
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ pub struct TestProps {
3232
force_host: bool,
3333
// Check stdout for error-pattern output as well as stderr
3434
check_stdout: bool,
35+
// Don't force a --crate-type=dylib flag on the command line
36+
no_prefer_dynamic: bool,
3537
}
3638

3739
// Load any test directives embedded in the file
@@ -45,6 +47,7 @@ pub fn load_props(testfile: &Path) -> TestProps {
4547
let mut check_lines = ~[];
4648
let mut force_host = false;
4749
let mut check_stdout = false;
50+
let mut no_prefer_dynamic = false;
4851
iter_header(testfile, |ln| {
4952
match parse_error_pattern(ln) {
5053
Some(ep) => error_patterns.push(ep),
@@ -67,6 +70,10 @@ pub fn load_props(testfile: &Path) -> TestProps {
6770
check_stdout = parse_check_stdout(ln);
6871
}
6972

73+
if !no_prefer_dynamic {
74+
no_prefer_dynamic = parse_no_prefer_dynamic(ln);
75+
}
76+
7077
match parse_aux_build(ln) {
7178
Some(ab) => { aux_builds.push(ab); }
7279
None => {}
@@ -99,6 +106,7 @@ pub fn load_props(testfile: &Path) -> TestProps {
99106
check_lines: check_lines,
100107
force_host: force_host,
101108
check_stdout: check_stdout,
109+
no_prefer_dynamic: no_prefer_dynamic,
102110
};
103111
}
104112

@@ -167,6 +175,10 @@ fn parse_check_stdout(line: &str) -> bool {
167175
parse_name_directive(line, "check-stdout")
168176
}
169177

178+
fn parse_no_prefer_dynamic(line: &str) -> bool {
179+
parse_name_directive(line, "no-prefer-dynamic")
180+
}
181+
170182
fn parse_exec_env(line: &str) -> Option<(~str, ~str)> {
171183
parse_name_value_directive(line, ~"exec-env").map(|nv| {
172184
// nv is either FOO or FOO=BAR

src/compiletest/runtest.rs

+10-2
Original file line numberDiff line numberDiff line change
@@ -704,9 +704,13 @@ fn compose_and_run_compiler(
704704
for rel_ab in props.aux_builds.iter() {
705705
let abs_ab = config.aux_base.join(rel_ab.as_slice());
706706
let aux_props = load_props(&abs_ab);
707+
let crate_type = if aux_props.no_prefer_dynamic {
708+
~[]
709+
} else {
710+
~[~"--crate-type=dylib"]
711+
};
707712
let aux_args =
708-
make_compile_args(config, &aux_props, ~[~"--crate-type=dylib"]
709-
+ extra_link_args,
713+
make_compile_args(config, &aux_props, crate_type + extra_link_args,
710714
|a,b| {
711715
let f = make_lib_name(a, b, testfile);
712716
ThisDirectory(f.dir_path())
@@ -770,6 +774,10 @@ fn make_compile_args(config: &config,
770774
~"-L", config.build_base.as_str().unwrap().to_owned(),
771775
~"--target=" + target]
772776
+ extras;
777+
if !props.no_prefer_dynamic {
778+
args.push(~"-C");
779+
args.push(~"prefer-dynamic");
780+
}
773781
let path = match xform_file {
774782
ThisFile(path) => { args.push(~"-o"); path }
775783
ThisDirectory(path) => { args.push(~"--out-dir"); path }

src/librustc/back/link.rs

+138-84
Original file line numberDiff line numberDiff line change
@@ -1220,6 +1220,74 @@ fn add_local_native_libraries(args: &mut ~[~str], sess: Session) {
12201220
// the intermediate rlib version)
12211221
fn add_upstream_rust_crates(args: &mut ~[~str], sess: Session,
12221222
dylib: bool, tmpdir: &Path) {
1223+
1224+
// As a limitation of the current implementation, we require that everything
1225+
// must be static or everything must be dynamic. The reasons for this are a
1226+
// little subtle, but as with staticlibs and rlibs, the goal is to prevent
1227+
// duplicate copies of the same library showing up. For example, a static
1228+
// immediate dependency might show up as an upstream dynamic dependency and
1229+
// we currently have no way of knowing that. We know that all dynamic
1230+
// libraries require dynamic dependencies (see above), so it's satisfactory
1231+
// to include either all static libraries or all dynamic libraries.
1232+
//
1233+
// With this limitation, we expose a compiler default linkage type and an
1234+
// option to reverse that preference. The current behavior looks like:
1235+
//
1236+
// * If a dylib is being created, upstream dependencies must be dylibs
1237+
// * If nothing else is specified, static linking is preferred
1238+
// * If the -C prefer-dynamic flag is given, dynamic linking is preferred
1239+
// * If one form of linking fails, the second is also attempted
1240+
// * If both forms fail, then we emit an error message
1241+
1242+
let dynamic = get_deps(sess.cstore, cstore::RequireDynamic);
1243+
let statik = get_deps(sess.cstore, cstore::RequireStatic);
1244+
match (dynamic, statik, sess.opts.cg.prefer_dynamic, dylib) {
1245+
(_, Some(deps), false, false) => {
1246+
add_static_crates(args, sess, tmpdir, deps)
1247+
}
1248+
1249+
(None, Some(deps), true, false) => {
1250+
// If you opted in to dynamic linking and we decided to emit a
1251+
// static output, you should probably be notified of such an event!
1252+
sess.warn("dynamic linking was preferred, but dependencies \
1253+
could not all be found in an dylib format.");
1254+
sess.warn("linking statically instead, using rlibs");
1255+
add_static_crates(args, sess, tmpdir, deps)
1256+
}
1257+
1258+
(Some(deps), _, _, _) => add_dynamic_crates(args, sess, deps),
1259+
1260+
(None, _, _, true) => {
1261+
sess.err("dylib output requested, but some depenencies could not \
1262+
be found in the dylib format");
1263+
let deps = sess.cstore.get_used_crates(cstore::RequireDynamic);
1264+
for (cnum, path) in deps.move_iter() {
1265+
if path.is_some() { continue }
1266+
let name = sess.cstore.get_crate_data(cnum).name.clone();
1267+
sess.note(format!("dylib not found: {}", name));
1268+
}
1269+
}
1270+
1271+
(None, None, pref, false) => {
1272+
let (pref, name) = if pref {
1273+
sess.err("dynamic linking is preferred, but dependencies were \
1274+
not found in either dylib or rlib format");
1275+
(cstore::RequireDynamic, "dylib")
1276+
} else {
1277+
sess.err("dependencies were not all found in either dylib or \
1278+
rlib format");
1279+
(cstore::RequireStatic, "rlib")
1280+
};
1281+
sess.note(format!("dependencies not found in the `{}` format",
1282+
name));
1283+
for (cnum, path) in sess.cstore.get_used_crates(pref).move_iter() {
1284+
if path.is_some() { continue }
1285+
let name = sess.cstore.get_crate_data(cnum).name.clone();
1286+
sess.note(name);
1287+
}
1288+
}
1289+
}
1290+
12231291
// Converts a library file-stem into a cc -l argument
12241292
fn unlib(config: @session::Config, stem: &str) -> ~str {
12251293
if stem.starts_with("lib") &&
@@ -1230,96 +1298,82 @@ fn add_upstream_rust_crates(args: &mut ~[~str], sess: Session,
12301298
}
12311299
}
12321300

1233-
let cstore = sess.cstore;
1234-
if !dylib && !sess.opts.cg.prefer_dynamic {
1235-
// With an executable, things get a little interesting. As a limitation
1236-
// of the current implementation, we require that everything must be
1237-
// static or everything must be dynamic. The reasons for this are a
1238-
// little subtle, but as with the above two cases, the goal is to
1239-
// prevent duplicate copies of the same library showing up. For example,
1240-
// a static immediate dependency might show up as an upstream dynamic
1241-
// dependency and we currently have no way of knowing that. We know that
1242-
// all dynamic libraries require dynamic dependencies (see above), so
1243-
// it's satisfactory to include either all static libraries or all
1244-
// dynamic libraries.
1245-
let crates = cstore.get_used_crates(cstore::RequireStatic);
1301+
// Attempts to find all dependencies with a certain linkage preference,
1302+
// returning `None` if not all libraries could be found with that
1303+
// preference.
1304+
fn get_deps(cstore: &cstore::CStore, preference: cstore::LinkagePreference)
1305+
-> Option<~[(ast::CrateNum, Path)]>
1306+
{
1307+
let crates = cstore.get_used_crates(preference);
12461308
if crates.iter().all(|&(_, ref p)| p.is_some()) {
1247-
for (cnum, path) in crates.move_iter() {
1248-
let cratepath = path.unwrap();
1249-
1250-
// When performing LTO on an executable output, all of the
1251-
// bytecode from the upstream libraries has already been
1252-
// included in our object file output. We need to modify all of
1253-
// the upstream archives to remove their corresponding object
1254-
// file to make sure we don't pull the same code in twice.
1255-
//
1256-
// We must continue to link to the upstream archives to be sure
1257-
// to pull in native static dependencies. As the final caveat,
1258-
// on linux it is apparently illegal to link to a blank archive,
1259-
// so if an archive no longer has any object files in it after
1260-
// we remove `lib.o`, then don't link against it at all.
1261-
//
1262-
// If we're not doing LTO, then our job is simply to just link
1263-
// against the archive.
1264-
if sess.lto() {
1265-
let name = sess.cstore.get_crate_data(cnum).name.clone();
1266-
time(sess.time_passes(), format!("altering {}.rlib", name),
1267-
(), |()| {
1268-
let dst = tmpdir.join(cratepath.filename().unwrap());
1269-
match fs::copy(&cratepath, &dst) {
1270-
Ok(..) => {}
1271-
Err(e) => {
1272-
sess.err(format!("failed to copy {} to {}: {}",
1273-
cratepath.display(),
1274-
dst.display(),
1275-
e));
1276-
sess.abort_if_errors();
1277-
}
1278-
}
1279-
let dst_str = dst.as_str().unwrap().to_owned();
1280-
let mut archive = Archive::open(sess, dst);
1281-
archive.remove_file(format!("{}.o", name));
1282-
let files = archive.files();
1283-
if files.iter().any(|s| s.ends_with(".o")) {
1284-
args.push(dst_str);
1309+
Some(crates.move_iter().map(|(a, b)| (a, b.unwrap())).collect())
1310+
} else {
1311+
None
1312+
}
1313+
}
1314+
1315+
// Adds the static "rlib" versions of all crates to the command line.
1316+
fn add_static_crates(args: &mut ~[~str], sess: Session, tmpdir: &Path,
1317+
crates: ~[(ast::CrateNum, Path)]) {
1318+
for (cnum, cratepath) in crates.move_iter() {
1319+
// When performing LTO on an executable output, all of the
1320+
// bytecode from the upstream libraries has already been
1321+
// included in our object file output. We need to modify all of
1322+
// the upstream archives to remove their corresponding object
1323+
// file to make sure we don't pull the same code in twice.
1324+
//
1325+
// We must continue to link to the upstream archives to be sure
1326+
// to pull in native static dependencies. As the final caveat,
1327+
// on linux it is apparently illegal to link to a blank archive,
1328+
// so if an archive no longer has any object files in it after
1329+
// we remove `lib.o`, then don't link against it at all.
1330+
//
1331+
// If we're not doing LTO, then our job is simply to just link
1332+
// against the archive.
1333+
if sess.lto() {
1334+
let name = sess.cstore.get_crate_data(cnum).name.clone();
1335+
time(sess.time_passes(), format!("altering {}.rlib", name),
1336+
(), |()| {
1337+
let dst = tmpdir.join(cratepath.filename().unwrap());
1338+
match fs::copy(&cratepath, &dst) {
1339+
Ok(..) => {}
1340+
Err(e) => {
1341+
sess.err(format!("failed to copy {} to {}: {}",
1342+
cratepath.display(),
1343+
dst.display(),
1344+
e));
1345+
sess.abort_if_errors();
12851346
}
1286-
});
1287-
} else {
1288-
args.push(cratepath.as_str().unwrap().to_owned());
1289-
}
1347+
}
1348+
let dst_str = dst.as_str().unwrap().to_owned();
1349+
let mut archive = Archive::open(sess, dst);
1350+
archive.remove_file(format!("{}.o", name));
1351+
let files = archive.files();
1352+
if files.iter().any(|s| s.ends_with(".o")) {
1353+
args.push(dst_str);
1354+
}
1355+
});
1356+
} else {
1357+
args.push(cratepath.as_str().unwrap().to_owned());
12901358
}
1291-
return;
12921359
}
12931360
}
12941361

1295-
// If we're performing LTO, then it should have been previously required
1296-
// that all upstream rust dependencies were available in an rlib format.
1297-
assert!(!sess.lto());
1298-
1299-
// This is a fallback of three different cases of linking:
1300-
//
1301-
// * When creating a dynamic library, all inputs are required to be dynamic
1302-
// as well
1303-
// * If an executable is created with a preference on dynamic linking, then
1304-
// this case is the fallback
1305-
// * If an executable is being created, and one of the inputs is missing as
1306-
// a static library, then this is the fallback case.
1307-
let crates = cstore.get_used_crates(cstore::RequireDynamic);
1308-
for &(cnum, ref path) in crates.iter() {
1309-
let cratepath = match *path {
1310-
Some(ref p) => p.clone(),
1311-
None => {
1312-
sess.err(format!("could not find dynamic library for: `{}`",
1313-
sess.cstore.get_crate_data(cnum).name));
1314-
return
1315-
}
1316-
};
1317-
// Just need to tell the linker about where the library lives and what
1318-
// its name is
1319-
let dir = cratepath.dirname_str().unwrap();
1320-
if !dir.is_empty() { args.push("-L" + dir); }
1321-
let libarg = unlib(sess.targ_cfg, cratepath.filestem_str().unwrap());
1322-
args.push("-l" + libarg);
1362+
// Same thing as above, but for dynamic crates instead of static crates.
1363+
fn add_dynamic_crates(args: &mut ~[~str], sess: Session,
1364+
crates: ~[(ast::CrateNum, Path)]) {
1365+
// If we're performing LTO, then it should have been previously required
1366+
// that all upstream rust dependencies were available in an rlib format.
1367+
assert!(!sess.lto());
1368+
1369+
for (_, cratepath) in crates.move_iter() {
1370+
// Just need to tell the linker about where the library lives and
1371+
// what its name is
1372+
let dir = cratepath.dirname_str().unwrap();
1373+
if !dir.is_empty() { args.push("-L" + dir); }
1374+
let libarg = unlib(sess.targ_cfg, cratepath.filestem_str().unwrap());
1375+
args.push("-l" + libarg);
1376+
}
13231377
}
13241378
}
13251379

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
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_type = "dylib"];
+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
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_type = "rlib"];
+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
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-12133-rlib.rs
12+
// aux-build:issue-12133-dylib.rs
13+
14+
// error-pattern: dynamic linking is preferred, but dependencies were not found
15+
16+
extern crate a = "issue-12133-rlib";
17+
extern crate b = "issue-12133-dylib";
18+
19+
fn main() {}
+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
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-12133-rlib.rs
12+
// aux-build:issue-12133-dylib.rs
13+
// no-prefer-dynamic
14+
15+
// error-pattern: dependencies were not all found in either dylib or rlib format
16+
17+
extern crate a = "issue-12133-rlib";
18+
extern crate b = "issue-12133-dylib";
19+
20+
fn main() {}

0 commit comments

Comments
 (0)