about summary refs log tree commit diff
diff options
context:
space:
mode:
authorAlex Crichton <alex@alexcrichton.com>2014-02-10 11:33:33 -0800
committerAlex Crichton <alex@alexcrichton.com>2014-02-19 08:33:08 -0800
commit35c6e22fab2d8b9c0911fb795532ca853d124ff1 (patch)
tree92ce1ee710d26320bbf1a96dbf8383c697a15ff4
parent98b07755dd18e086aeb1ba8797821bd4a11cd713 (diff)
downloadrust-35c6e22fab2d8b9c0911fb795532ca853d124ff1.tar.gz
rust-35c6e22fab2d8b9c0911fb795532ca853d124ff1.zip
Tweak how preference factors into linkage
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
-rw-r--r--mk/tests.mk4
-rw-r--r--src/compiletest/header.rs12
-rw-r--r--src/compiletest/runtest.rs12
-rw-r--r--src/librustc/back/link.rs222
-rw-r--r--src/test/auxiliary/issue-12133-dylib.rs13
-rw-r--r--src/test/auxiliary/issue-12133-rlib.rs13
-rw-r--r--src/test/compile-fail/issue-12133-1.rs19
-rw-r--r--src/test/compile-fail/issue-12133-2.rs20
-rw-r--r--src/test/compile-fail/issue-12133-3.rs20
9 files changed, 245 insertions, 90 deletions
diff --git a/mk/tests.mk b/mk/tests.mk
index 494588a1f91..787bbac8e6f 100644
--- a/mk/tests.mk
+++ b/mk/tests.mk
@@ -537,10 +537,6 @@ TEST_SREQ$(1)_T_$(2)_H_$(3) = \
 # remove directive, if present, from CFG_RUSTC_FLAGS (issue #7898).
 CTEST_RUSTC_FLAGS := $$(subst --cfg ndebug,,$$(CFG_RUSTC_FLAGS))
 
-# There's no need our entire test suite to take up gigabytes of space on disk
-# including copies of libstd/libextra all over the place
-CTEST_RUSTC_FLAGS := $$(CTEST_RUSTC_FLAGS) -C prefer-dynamic
-
 # The tests can not be optimized while the rest of the compiler is optimized, so
 # filter out the optimization (if any) from rustc and then figure out if we need
 # to be optimized
diff --git a/src/compiletest/header.rs b/src/compiletest/header.rs
index 38c1162f636..7f8be5ff090 100644
--- a/src/compiletest/header.rs
+++ b/src/compiletest/header.rs
@@ -32,6 +32,8 @@ pub struct TestProps {
     force_host: bool,
     // Check stdout for error-pattern output as well as stderr
     check_stdout: bool,
+    // Don't force a --crate-type=dylib flag on the command line
+    no_prefer_dynamic: bool,
 }
 
 // Load any test directives embedded in the file
@@ -45,6 +47,7 @@ pub fn load_props(testfile: &Path) -> TestProps {
     let mut check_lines = ~[];
     let mut force_host = false;
     let mut check_stdout = false;
+    let mut no_prefer_dynamic = false;
     iter_header(testfile, |ln| {
         match parse_error_pattern(ln) {
           Some(ep) => error_patterns.push(ep),
@@ -67,6 +70,10 @@ pub fn load_props(testfile: &Path) -> TestProps {
             check_stdout = parse_check_stdout(ln);
         }
 
+        if !no_prefer_dynamic {
+            no_prefer_dynamic = parse_no_prefer_dynamic(ln);
+        }
+
         match parse_aux_build(ln) {
             Some(ab) => { aux_builds.push(ab); }
             None => {}
@@ -99,6 +106,7 @@ pub fn load_props(testfile: &Path) -> TestProps {
         check_lines: check_lines,
         force_host: force_host,
         check_stdout: check_stdout,
+        no_prefer_dynamic: no_prefer_dynamic,
     };
 }
 
@@ -167,6 +175,10 @@ fn parse_check_stdout(line: &str) -> bool {
     parse_name_directive(line, "check-stdout")
 }
 
+fn parse_no_prefer_dynamic(line: &str) -> bool {
+    parse_name_directive(line, "no-prefer-dynamic")
+}
+
 fn parse_exec_env(line: &str) -> Option<(~str, ~str)> {
     parse_name_value_directive(line, ~"exec-env").map(|nv| {
         // nv is either FOO or FOO=BAR
diff --git a/src/compiletest/runtest.rs b/src/compiletest/runtest.rs
index 8b45d987864..a24dde1561b 100644
--- a/src/compiletest/runtest.rs
+++ b/src/compiletest/runtest.rs
@@ -704,9 +704,13 @@ fn compose_and_run_compiler(
     for rel_ab in props.aux_builds.iter() {
         let abs_ab = config.aux_base.join(rel_ab.as_slice());
         let aux_props = load_props(&abs_ab);
+        let crate_type = if aux_props.no_prefer_dynamic {
+            ~[]
+        } else {
+            ~[~"--crate-type=dylib"]
+        };
         let aux_args =
-            make_compile_args(config, &aux_props, ~[~"--crate-type=dylib"]
-                                                  + extra_link_args,
+            make_compile_args(config, &aux_props, crate_type + extra_link_args,
                               |a,b| {
                                   let f = make_lib_name(a, b, testfile);
                                   ThisDirectory(f.dir_path())
@@ -770,6 +774,10 @@ fn make_compile_args(config: &config,
                      ~"-L", config.build_base.as_str().unwrap().to_owned(),
                      ~"--target=" + target]
         + extras;
+    if !props.no_prefer_dynamic {
+        args.push(~"-C");
+        args.push(~"prefer-dynamic");
+    }
     let path = match xform_file {
         ThisFile(path) => { args.push(~"-o"); path }
         ThisDirectory(path) => { args.push(~"--out-dir"); path }
diff --git a/src/librustc/back/link.rs b/src/librustc/back/link.rs
index 6213f7585df..a9d7d231cef 100644
--- a/src/librustc/back/link.rs
+++ b/src/librustc/back/link.rs
@@ -1220,6 +1220,74 @@ fn add_local_native_libraries(args: &mut ~[~str], sess: Session) {
 // the intermediate rlib version)
 fn add_upstream_rust_crates(args: &mut ~[~str], sess: Session,
                             dylib: bool, tmpdir: &Path) {
+
+    // As a limitation of the current implementation, we require that everything
+    // must be static or everything must be dynamic. The reasons for this are a
+    // little subtle, but as with staticlibs and rlibs, the goal is to prevent
+    // duplicate copies of the same library showing up. For example, a static
+    // immediate dependency might show up as an upstream dynamic dependency and
+    // we currently have no way of knowing that. We know that all dynamic
+    // libraries require dynamic dependencies (see above), so it's satisfactory
+    // to include either all static libraries or all dynamic libraries.
+    //
+    // With this limitation, we expose a compiler default linkage type and an
+    // option to reverse that preference. The current behavior looks like:
+    //
+    // * If a dylib is being created, upstream dependencies must be dylibs
+    // * If nothing else is specified, static linking is preferred
+    // * If the -C prefer-dynamic flag is given, dynamic linking is preferred
+    // * If one form of linking fails, the second is also attempted
+    // * If both forms fail, then we emit an error message
+
+    let dynamic = get_deps(sess.cstore, cstore::RequireDynamic);
+    let statik = get_deps(sess.cstore, cstore::RequireStatic);
+    match (dynamic, statik, sess.opts.cg.prefer_dynamic, dylib) {
+        (_, Some(deps), false, false) => {
+            add_static_crates(args, sess, tmpdir, deps)
+        }
+
+        (None, Some(deps), true, false) => {
+            // If you opted in to dynamic linking and we decided to emit a
+            // static output, you should probably be notified of such an event!
+            sess.warn("dynamic linking was preferred, but dependencies \
+                       could not all be found in an dylib format.");
+            sess.warn("linking statically instead, using rlibs");
+            add_static_crates(args, sess, tmpdir, deps)
+        }
+
+        (Some(deps), _, _, _) => add_dynamic_crates(args, sess, deps),
+
+        (None, _, _, true) => {
+            sess.err("dylib output requested, but some depenencies could not \
+                      be found in the dylib format");
+            let deps = sess.cstore.get_used_crates(cstore::RequireDynamic);
+            for (cnum, path) in deps.move_iter() {
+                if path.is_some() { continue }
+                let name = sess.cstore.get_crate_data(cnum).name.clone();
+                sess.note(format!("dylib not found: {}", name));
+            }
+        }
+
+        (None, None, pref, false) => {
+            let (pref, name) = if pref {
+                sess.err("dynamic linking is preferred, but dependencies were \
+                          not found in either dylib or rlib format");
+                (cstore::RequireDynamic, "dylib")
+            } else {
+                sess.err("dependencies were not all found in either dylib or \
+                          rlib format");
+                (cstore::RequireStatic, "rlib")
+            };
+            sess.note(format!("dependencies not found in the `{}` format",
+                              name));
+            for (cnum, path) in sess.cstore.get_used_crates(pref).move_iter() {
+                if path.is_some() { continue }
+                let name = sess.cstore.get_crate_data(cnum).name.clone();
+                sess.note(name);
+            }
+        }
+    }
+
     // Converts a library file-stem into a cc -l argument
     fn unlib(config: @session::Config, stem: &str) -> ~str {
         if stem.starts_with("lib") &&
@@ -1230,96 +1298,82 @@ fn add_upstream_rust_crates(args: &mut ~[~str], sess: Session,
         }
     }
 
-    let cstore = sess.cstore;
-    if !dylib && !sess.opts.cg.prefer_dynamic {
-        // With an executable, things get a little interesting. As a limitation
-        // of the current implementation, we require that everything must be
-        // static or everything must be dynamic. The reasons for this are a
-        // little subtle, but as with the above two cases, the goal is to
-        // prevent duplicate copies of the same library showing up. For example,
-        // a static immediate dependency might show up as an upstream dynamic
-        // dependency and we currently have no way of knowing that. We know that
-        // all dynamic libraries require dynamic dependencies (see above), so
-        // it's satisfactory to include either all static libraries or all
-        // dynamic libraries.
-        let crates = cstore.get_used_crates(cstore::RequireStatic);
+    // Attempts to find all dependencies with a certain linkage preference,
+    // returning `None` if not all libraries could be found with that
+    // preference.
+    fn get_deps(cstore: &cstore::CStore,  preference: cstore::LinkagePreference)
+            -> Option<~[(ast::CrateNum, Path)]>
+    {
+        let crates = cstore.get_used_crates(preference);
         if crates.iter().all(|&(_, ref p)| p.is_some()) {
-            for (cnum, path) in crates.move_iter() {
-                let cratepath = path.unwrap();
-
-                // When performing LTO on an executable output, all of the
-                // bytecode from the upstream libraries has already been
-                // included in our object file output. We need to modify all of
-                // the upstream archives to remove their corresponding object
-                // file to make sure we don't pull the same code in twice.
-                //
-                // We must continue to link to the upstream archives to be sure
-                // to pull in native static dependencies. As the final caveat,
-                // on linux it is apparently illegal to link to a blank archive,
-                // so if an archive no longer has any object files in it after
-                // we remove `lib.o`, then don't link against it at all.
-                //
-                // If we're not doing LTO, then our job is simply to just link
-                // against the archive.
-                if sess.lto() {
-                    let name = sess.cstore.get_crate_data(cnum).name.clone();
-                    time(sess.time_passes(), format!("altering {}.rlib", name),
-                         (), |()| {
-                        let dst = tmpdir.join(cratepath.filename().unwrap());
-                        match fs::copy(&cratepath, &dst) {
-                            Ok(..) => {}
-                            Err(e) => {
-                                sess.err(format!("failed to copy {} to {}: {}",
-                                                 cratepath.display(),
-                                                 dst.display(),
-                                                 e));
-                                sess.abort_if_errors();
-                            }
-                        }
-                        let dst_str = dst.as_str().unwrap().to_owned();
-                        let mut archive = Archive::open(sess, dst);
-                        archive.remove_file(format!("{}.o", name));
-                        let files = archive.files();
-                        if files.iter().any(|s| s.ends_with(".o")) {
-                            args.push(dst_str);
+            Some(crates.move_iter().map(|(a, b)| (a, b.unwrap())).collect())
+        } else {
+            None
+        }
+    }
+
+    // Adds the static "rlib" versions of all crates to the command line.
+    fn add_static_crates(args: &mut ~[~str], sess: Session, tmpdir: &Path,
+                         crates: ~[(ast::CrateNum, Path)]) {
+        for (cnum, cratepath) in crates.move_iter() {
+            // When performing LTO on an executable output, all of the
+            // bytecode from the upstream libraries has already been
+            // included in our object file output. We need to modify all of
+            // the upstream archives to remove their corresponding object
+            // file to make sure we don't pull the same code in twice.
+            //
+            // We must continue to link to the upstream archives to be sure
+            // to pull in native static dependencies. As the final caveat,
+            // on linux it is apparently illegal to link to a blank archive,
+            // so if an archive no longer has any object files in it after
+            // we remove `lib.o`, then don't link against it at all.
+            //
+            // If we're not doing LTO, then our job is simply to just link
+            // against the archive.
+            if sess.lto() {
+                let name = sess.cstore.get_crate_data(cnum).name.clone();
+                time(sess.time_passes(), format!("altering {}.rlib", name),
+                     (), |()| {
+                    let dst = tmpdir.join(cratepath.filename().unwrap());
+                    match fs::copy(&cratepath, &dst) {
+                        Ok(..) => {}
+                        Err(e) => {
+                            sess.err(format!("failed to copy {} to {}: {}",
+                                             cratepath.display(),
+                                             dst.display(),
+                                             e));
+                            sess.abort_if_errors();
                         }
-                    });
-                } else {
-                    args.push(cratepath.as_str().unwrap().to_owned());
-                }
+                    }
+                    let dst_str = dst.as_str().unwrap().to_owned();
+                    let mut archive = Archive::open(sess, dst);
+                    archive.remove_file(format!("{}.o", name));
+                    let files = archive.files();
+                    if files.iter().any(|s| s.ends_with(".o")) {
+                        args.push(dst_str);
+                    }
+                });
+            } else {
+                args.push(cratepath.as_str().unwrap().to_owned());
             }
-            return;
         }
     }
 
-    // If we're performing LTO, then it should have been previously required
-    // that all upstream rust dependencies were available in an rlib format.
-    assert!(!sess.lto());
-
-    // This is a fallback of three different  cases of linking:
-    //
-    // * When creating a dynamic library, all inputs are required to be dynamic
-    //   as well
-    // * If an executable is created with a preference on dynamic linking, then
-    //   this case is the fallback
-    // * If an executable is being created, and one of the inputs is missing as
-    //   a static library, then this is the fallback case.
-    let crates = cstore.get_used_crates(cstore::RequireDynamic);
-    for &(cnum, ref path) in crates.iter() {
-        let cratepath = match *path {
-            Some(ref p) => p.clone(),
-            None => {
-                sess.err(format!("could not find dynamic library for: `{}`",
-                                 sess.cstore.get_crate_data(cnum).name));
-                return
-            }
-        };
-        // Just need to tell the linker about where the library lives and what
-        // its name is
-        let dir = cratepath.dirname_str().unwrap();
-        if !dir.is_empty() { args.push("-L" + dir); }
-        let libarg = unlib(sess.targ_cfg, cratepath.filestem_str().unwrap());
-        args.push("-l" + libarg);
+    // Same thing as above, but for dynamic crates instead of static crates.
+    fn add_dynamic_crates(args: &mut ~[~str], sess: Session,
+                          crates: ~[(ast::CrateNum, Path)]) {
+        // If we're performing LTO, then it should have been previously required
+        // that all upstream rust dependencies were available in an rlib format.
+        assert!(!sess.lto());
+
+        for (_, cratepath) in crates.move_iter() {
+            // Just need to tell the linker about where the library lives and
+            // what its name is
+            let dir = cratepath.dirname_str().unwrap();
+            if !dir.is_empty() { args.push("-L" + dir); }
+            let libarg = unlib(sess.targ_cfg, cratepath.filestem_str().unwrap());
+            args.push("-l" + libarg);
+        }
     }
 }
 
diff --git a/src/test/auxiliary/issue-12133-dylib.rs b/src/test/auxiliary/issue-12133-dylib.rs
new file mode 100644
index 00000000000..57ae3f0851d
--- /dev/null
+++ b/src/test/auxiliary/issue-12133-dylib.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 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+// no-prefer-dynamic
+
+#[crate_type = "dylib"];
diff --git a/src/test/auxiliary/issue-12133-rlib.rs b/src/test/auxiliary/issue-12133-rlib.rs
new file mode 100644
index 00000000000..e5d109bb17e
--- /dev/null
+++ b/src/test/auxiliary/issue-12133-rlib.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 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+// no-prefer-dynamic
+
+#[crate_type = "rlib"];
diff --git a/src/test/compile-fail/issue-12133-1.rs b/src/test/compile-fail/issue-12133-1.rs
new file mode 100644
index 00000000000..63a0352e2ef
--- /dev/null
+++ b/src/test/compile-fail/issue-12133-1.rs
@@ -0,0 +1,19 @@
+// 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 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+// aux-build:issue-12133-rlib.rs
+// aux-build:issue-12133-dylib.rs
+
+// error-pattern: dynamic linking is preferred, but dependencies were not found
+
+extern crate a = "issue-12133-rlib";
+extern crate b = "issue-12133-dylib";
+
+fn main() {}
diff --git a/src/test/compile-fail/issue-12133-2.rs b/src/test/compile-fail/issue-12133-2.rs
new file mode 100644
index 00000000000..3f42d28bf27
--- /dev/null
+++ b/src/test/compile-fail/issue-12133-2.rs
@@ -0,0 +1,20 @@
+// 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 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+// aux-build:issue-12133-rlib.rs
+// aux-build:issue-12133-dylib.rs
+// no-prefer-dynamic
+
+// error-pattern: dependencies were not all found in either dylib or rlib format
+
+extern crate a = "issue-12133-rlib";
+extern crate b = "issue-12133-dylib";
+
+fn main() {}
diff --git a/src/test/compile-fail/issue-12133-3.rs b/src/test/compile-fail/issue-12133-3.rs
new file mode 100644
index 00000000000..f97bb618e29
--- /dev/null
+++ b/src/test/compile-fail/issue-12133-3.rs
@@ -0,0 +1,20 @@
+// 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 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+// aux-build:issue-12133-rlib.rs
+// aux-build:issue-12133-dylib.rs
+// no-prefer-dynamic
+
+// error-pattern: dylib output requested, but some depenencies could not
+
+#[crate_type = "dylib"];
+
+extern crate a = "issue-12133-rlib";
+extern crate b = "issue-12133-dylib";