diff --git a/src/cargo/ops/cargo_rustc/mod.rs b/src/cargo/ops/cargo_rustc/mod.rs index 4a7553d3f39..5da63efe422 100644 --- a/src/cargo/ops/cargo_rustc/mod.rs +++ b/src/cargo/ops/cargo_rustc/mod.rs @@ -423,7 +423,15 @@ fn build_deps_args(mut cmd: ProcessBuilder, target: &Target, package: &Package, // Traverse the entire dependency graph looking for -L paths to pass for // native dependencies. - cmd = push_native_dirs(cmd, &layout, package, cx, &mut HashSet::new()); + let mut dirs = Vec::new(); + each_dep(package, cx, |pkg| { + if pkg.get_manifest().get_build().len() > 0 { + dirs.push(layout.native(pkg)); + } + }); + for dir in dirs.into_iter() { + cmd = cmd.arg("-L").arg(dir); + } for &(_, target) in cx.dep_targets(package).iter() { cmd = try!(link_to(cmd, target, cx, kind, Dependency)); @@ -471,37 +479,51 @@ fn build_deps_args(mut cmd: ProcessBuilder, target: &Target, package: &Package, } return Ok(cmd); } - - fn push_native_dirs(mut cmd: ProcessBuilder, layout: &layout::LayoutProxy, - pkg: &Package, cx: &Context, - visited: &mut HashSet) -> ProcessBuilder { - if !visited.insert(pkg.get_package_id().clone()) { return cmd } - - if pkg.get_manifest().get_build().len() > 0 { - cmd = cmd.arg("-L").arg(layout.native(pkg)); - } - - match cx.resolve.deps(pkg.get_package_id()) { - Some(mut pkgids) => { - pkgids.fold(cmd, |cmd, dep_id| { - let dep = cx.get_package(dep_id); - push_native_dirs(cmd, layout, dep, cx, visited) - }) - } - None => cmd - } - } } pub fn process(cmd: T, pkg: &Package, cx: &Context) -> ProcessBuilder { // When invoking a tool, we need the *host* deps directory in the dynamic // library search path for plugins and such which have dynamic dependencies. + let layout = cx.layout(KindPlugin); let mut search_path = DynamicLibrary::search_path(); - search_path.push(cx.layout(KindPlugin).deps().clone()); - let search_path = os::join_paths(search_path.as_slice()).unwrap(); + search_path.push(layout.deps().clone()); + + // Also be sure to pick up any native build directories required by plugins + // or their dependencies + let mut native_search_paths = HashSet::new(); + for &(dep, target) in cx.dep_targets(pkg).iter() { + if !target.get_profile().is_plugin() { continue } + each_dep(dep, cx, |dep| { + if dep.get_manifest().get_build().len() > 0 { + native_search_paths.insert(layout.native(dep)); + } + }); + } + search_path.extend(native_search_paths.into_iter()); // We want to use the same environment and such as normal processes, but we // want to override the dylib search path with the one we just calculated. + let search_path = os::join_paths(search_path.as_slice()).unwrap(); cx.compilation.process(cmd, pkg) .env(DynamicLibrary::envvar(), Some(search_path.as_slice())) } + +fn each_dep<'a>(pkg: &Package, cx: &'a Context, f: |&'a Package|) { + let mut visited = HashSet::new(); + let pkg = cx.get_package(pkg.get_package_id()); + visit_deps(pkg, cx, &mut visited, f); + + fn visit_deps<'a>(pkg: &'a Package, cx: &'a Context, + visited: &mut HashSet<&'a PackageId>, + f: |&'a Package|) { + if !visited.insert(pkg.get_package_id()) { return } + f(pkg); + let mut deps = match cx.resolve.deps(pkg.get_package_id()) { + Some(deps) => deps, + None => return, + }; + for dep_id in deps { + visit_deps(cx.get_package(dep_id), cx, visited, |p| f(p)) + } + } +} diff --git a/tests/test_cargo_compile_plugins.rs b/tests/test_cargo_compile_plugins.rs index 0d4f94322a2..b59c8df3411 100644 --- a/tests/test_cargo_compile_plugins.rs +++ b/tests/test_cargo_compile_plugins.rs @@ -1,3 +1,6 @@ +use std::io::fs; +use std::os; + use support::{project, execs, cargo_dir}; use hamcrest::assert_that; @@ -78,3 +81,89 @@ test!(plugin_to_the_max { assert_that(foo.process(cargo_dir().join("cargo")).arg("doc"), execs().with_status(0)); }) + +test!(plugin_with_dynamic_native_dependency { + let build = project("build") + .file("Cargo.toml", r#" + [package] + name = "build" + version = "0.0.1" + authors = [] + + [lib] + name = "build" + crate-type = ["dylib"] + "#) + .file("src/main.rs", r#" + use std::io::fs; + use std::os; + + fn main() { + let src = Path::new(os::getenv("SRC").unwrap()); + let dst = Path::new(os::getenv("OUT_DIR").unwrap()); + let dst = dst.join(src.filename().unwrap()); + fs::rename(&src, &dst).unwrap(); + } + "#) + .file("src/lib.rs", r#" + #[no_mangle] + pub extern fn foo() {} + "#); + assert_that(build.cargo_process("build"), + execs().with_status(0).with_stderr("")); + let src = build.root().join("target"); + let lib = fs::readdir(&src).unwrap().into_iter().find(|lib| { + let lib = lib.filename_str().unwrap(); + lib.starts_with(os::consts::DLL_PREFIX) && + lib.ends_with(os::consts::DLL_SUFFIX) + }).unwrap(); + let libname = lib.filename_str().unwrap(); + let libname = libname.slice(os::consts::DLL_PREFIX.len(), + libname.len() - os::consts::DLL_SUFFIX.len()); + + let foo = project("foo") + .file("Cargo.toml", r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + + [dependencies.bar] + path = "bar" + "#) + .file("src/main.rs", r#" + #![feature(phase)] + #[phase(plugin)] extern crate bar; + + fn main() {} + "#) + .file("bar/Cargo.toml", format!(r#" + [package] + name = "bar" + version = "0.0.1" + authors = [] + build = '{}' + + [lib] + name = "bar" + plugin = true + "#, build.bin("build").display())) + .file("bar/src/lib.rs", format!(r#" + #![feature(plugin_registrar)] + + extern crate rustc; + + use rustc::plugin::Registry; + + #[link(name = "{}")] + extern {{ fn foo(); }} + + #[plugin_registrar] + pub fn bar(_reg: &mut Registry) {{ + unsafe {{ foo() }} + }} + "#, libname)); + + assert_that(foo.cargo_process("build").env("SRC", Some(lib.as_vec())), + execs().with_status(0)); +})