import std::map; import std::map::hashmap; import metadata::cstore; import driver::session; import metadata::filesearch; export get_rpath_flags; pure fn not_win32(os: session::os) -> bool { match os { session::os_win32 => false, _ => true } } fn get_rpath_flags(sess: session::session, out_filename: ~str) -> ~[~str] { let os = sess.targ_cfg.os; // No rpath on windows if os == session::os_win32 { return ~[]; } debug!{"preparing the RPATH!"}; let cwd = os::getcwd(); let sysroot = sess.filesearch.sysroot(); let output = out_filename; let libs = cstore::get_used_crate_files(sess.cstore); // We don't currently rpath extern libraries, but we know // where rustrt is and we know every rust program needs it let libs = vec::append_one(libs, get_sysroot_absolute_rt_lib(sess)); let target_triple = sess.opts.target_triple; let rpaths = get_rpaths(os, cwd, sysroot, output, libs, target_triple); rpaths_to_flags(rpaths) } fn get_sysroot_absolute_rt_lib(sess: session::session) -> path::Path { let mut path = vec::append(~[sess.filesearch.sysroot()], filesearch::relative_target_lib_path( sess.opts.target_triple)); vec::push(path, os::dll_filename(~"rustrt")); path::connect_many(path) } fn rpaths_to_flags(rpaths: ~[~str]) -> ~[~str] { vec::map(rpaths, |rpath| fmt!{"-Wl,-rpath,%s",rpath} ) } fn get_rpaths(os: session::os, cwd: path::Path, sysroot: path::Path, output: path::Path, libs: ~[path::Path], target_triple: ~str) -> ~[~str] { debug!{"cwd: %s", cwd}; debug!{"sysroot: %s", sysroot}; debug!{"output: %s", output}; debug!{"libs:"}; for libs.each |libpath| { debug!{" %s", libpath}; } debug!{"target_triple: %s", target_triple}; // Use relative paths to the libraries. Binaries can be moved // as long as they maintain the relative relationship to the // crates they depend on. let rel_rpaths = get_rpaths_relative_to_output(os, cwd, output, libs); // Make backup absolute paths to the libraries. Binaries can // be moved as long as the crates they link against don't move. let abs_rpaths = get_absolute_rpaths(cwd, libs); // And a final backup rpath to the global library location. let fallback_rpaths = ~[get_install_prefix_rpath(cwd, target_triple)]; fn log_rpaths(desc: ~str, rpaths: ~[~str]) { debug!{"%s rpaths:", desc}; for rpaths.each |rpath| { debug!{" %s", rpath}; } } log_rpaths(~"relative", rel_rpaths); log_rpaths(~"absolute", abs_rpaths); log_rpaths(~"fallback", fallback_rpaths); let mut rpaths = rel_rpaths; vec::push_all(rpaths, abs_rpaths); vec::push_all(rpaths, fallback_rpaths); // Remove duplicates let rpaths = minimize_rpaths(rpaths); return rpaths; } fn get_rpaths_relative_to_output(os: session::os, cwd: path::Path, output: path::Path, libs: ~[path::Path]) -> ~[~str] { vec::map(libs, |a| { get_rpath_relative_to_output(os, cwd, output, a) }) } fn get_rpath_relative_to_output(os: session::os, cwd: path::Path, output: path::Path, &&lib: path::Path) -> ~str { assert not_win32(os); // Mac doesn't appear to support $ORIGIN let prefix = match os { session::os_linux => ~"$ORIGIN" + path::path_sep(), session::os_freebsd => ~"$ORIGIN" + path::path_sep(), session::os_macos => ~"@executable_path" + path::path_sep(), session::os_win32 => core::unreachable() }; prefix + get_relative_to( get_absolute(cwd, output), get_absolute(cwd, lib)) } // Find the relative path from one file to another fn get_relative_to(abs1: path::Path, abs2: path::Path) -> path::Path { assert path::path_is_absolute(abs1); assert path::path_is_absolute(abs2); debug!{"finding relative path from %s to %s", abs1, abs2}; let normal1 = path::normalize(abs1); let normal2 = path::normalize(abs2); let split1 = path::split(normal1); let split2 = path::split(normal2); let len1 = vec::len(split1); let len2 = vec::len(split2); assert len1 > 0u; assert len2 > 0u; let max_common_path = uint::min(len1, len2) - 1u; let mut start_idx = 0u; while start_idx < max_common_path && split1[start_idx] == split2[start_idx] { start_idx += 1u; } let mut path = ~[]; for uint::range(start_idx, len1 - 1u) |_i| { vec::push(path, ~".."); }; vec::push_all(path, vec::view(split2, start_idx, len2 - 1u)); if vec::is_not_empty(path) { return path::connect_many(path); } else { return ~"."; } } fn get_absolute_rpaths(cwd: path::Path, libs: ~[path::Path]) -> ~[~str] { vec::map(libs, |a| get_absolute_rpath(cwd, a) ) } fn get_absolute_rpath(cwd: path::Path, &&lib: path::Path) -> ~str { path::dirname(get_absolute(cwd, lib)) } fn get_absolute(cwd: path::Path, lib: path::Path) -> path::Path { if path::path_is_absolute(lib) { lib } else { path::connect(cwd, lib) } } fn get_install_prefix_rpath(cwd: path::Path, target_triple: ~str) -> ~str { let install_prefix = env!{"CFG_PREFIX"}; if install_prefix == ~"" { fail ~"rustc compiled without CFG_PREFIX environment variable"; } let path = vec::append( ~[install_prefix], filesearch::relative_target_lib_path(target_triple)); get_absolute(cwd, path::connect_many(path)) } fn minimize_rpaths(rpaths: ~[~str]) -> ~[~str] { let set = map::str_hash::<()>(); let mut minimized = ~[]; for rpaths.each |rpath| { if !set.contains_key(rpath) { vec::push(minimized, rpath); set.insert(rpath, ()); } } return minimized; } #[cfg(unix)] mod test { #[test] fn test_rpaths_to_flags() { let flags = rpaths_to_flags(~[~"path1", ~"path2"]); assert flags == ~[~"-Wl,-rpath,path1", ~"-Wl,-rpath,path2"]; } #[test] fn test_get_absolute1() { let cwd = ~"/dir"; let lib = ~"some/path/lib"; let res = get_absolute(cwd, lib); assert res == ~"/dir/some/path/lib"; } #[test] fn test_get_absolute2() { let cwd = ~"/dir"; let lib = ~"/some/path/lib"; let res = get_absolute(cwd, lib); assert res == ~"/some/path/lib"; } #[test] fn test_prefix_rpath() { let res = get_install_prefix_rpath(~"/usr/lib", ~"triple"); let d = path::connect(env!{"CFG_PREFIX"}, ~"/lib/rustc/triple/lib"); assert str::ends_with(res, d); } #[test] fn test_prefix_rpath_abs() { let res = get_install_prefix_rpath(~"/usr/lib", ~"triple"); assert path::path_is_absolute(res); } #[test] fn test_minimize1() { let res = minimize_rpaths(~[~"rpath1", ~"rpath2", ~"rpath1"]); assert res == ~[~"rpath1", ~"rpath2"]; } #[test] fn test_minimize2() { let res = minimize_rpaths(~[~"1a", ~"2", ~"2", ~"1a", ~"4a", ~"1a", ~"2", ~"3", ~"4a", ~"3"]); assert res == ~[~"1a", ~"2", ~"4a", ~"3"]; } #[test] fn test_relative_to1() { let p1 = ~"/usr/bin/rustc"; let p2 = ~"/usr/lib/mylib"; let res = get_relative_to(p1, p2); assert res == ~"../lib"; } #[test] fn test_relative_to2() { let p1 = ~"/usr/bin/rustc"; let p2 = ~"/usr/bin/../lib/mylib"; let res = get_relative_to(p1, p2); assert res == ~"../lib"; } #[test] fn test_relative_to3() { let p1 = ~"/usr/bin/whatever/rustc"; let p2 = ~"/usr/lib/whatever/mylib"; let res = get_relative_to(p1, p2); assert res == ~"../../lib/whatever"; } #[test] fn test_relative_to4() { let p1 = ~"/usr/bin/whatever/../rustc"; let p2 = ~"/usr/lib/whatever/mylib"; let res = get_relative_to(p1, p2); assert res == ~"../lib/whatever"; } #[test] fn test_relative_to5() { let p1 = ~"/usr/bin/whatever/../rustc"; let p2 = ~"/usr/lib/whatever/../mylib"; let res = get_relative_to(p1, p2); assert res == ~"../lib"; } #[test] fn test_relative_to6() { let p1 = ~"/1"; let p2 = ~"/2/3"; let res = get_relative_to(p1, p2); assert res == ~"2"; } #[test] fn test_relative_to7() { let p1 = ~"/1/2"; let p2 = ~"/3"; let res = get_relative_to(p1, p2); assert res == ~".."; } #[test] fn test_relative_to8() { let p1 = ~"/home/brian/Dev/rust/build/" + ~"stage2/lib/rustc/i686-unknown-linux-gnu/lib/librustc.so"; let p2 = ~"/home/brian/Dev/rust/build/stage2/bin/.." + ~"/lib/rustc/i686-unknown-linux-gnu/lib/libstd.so"; let res = get_relative_to(p1, p2); assert res == ~"."; } #[test] #[cfg(target_os = "linux")] fn test_rpath_relative() { let o = session::os_linux; let res = get_rpath_relative_to_output(o, ~"/usr", ~"bin/rustc", ~"lib/libstd.so"); assert res == ~"$ORIGIN/../lib"; } #[test] #[cfg(target_os = "freebsd")] fn test_rpath_relative() { let o = session::os_freebsd; let res = get_rpath_relative_to_output(o, ~"/usr", ~"bin/rustc", ~"lib/libstd.so"); assert res == ~"$ORIGIN/../lib"; } #[test] #[cfg(target_os = "macos")] fn test_rpath_relative() { // this is why refinements would be nice let o = session::os_macos; let res = get_rpath_relative_to_output(o, ~"/usr", ~"bin/rustc", ~"lib/libstd.so"); assert res == ~"@executable_path/../lib"; } #[test] fn test_get_absolute_rpath() { let res = get_absolute_rpath(~"/usr", ~"lib/libstd.so"); assert res == ~"/usr/lib"; } }