Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
119 changes: 103 additions & 16 deletions crates/rattler_build_core/src/macos/link.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,28 @@ pub struct Dylib {
id: Option<PathBuf>,
}

impl Dylib {
/// Resolve a placeholder rpath relative to this binary.
fn resolve_relative_rpath(
&self,
rpath: &Path,
placeholder: &str,
prefix: &Path,
encoded_prefix: &Path,
) -> Option<PathBuf> {
let relative_rpath = rpath.strip_prefix(placeholder).ok()?;
// Get this binary's path in the "encoded prefix".
let self_path =
encoded_prefix.join(self.path.strip_prefix(prefix).expect("dylib not in prefix"));
if let Some(binary_parent) = self_path.parent() {
Some(to_lexical_absolute(relative_rpath, binary_parent))
} else {
tracing::warn!("binary {:?} has no parent directory", self.path);
None
}
}
}

impl Relinker for Dylib {
/// Parse the magic number of a file and check if it
/// is a Mach-O file that should be relinked.
Expand Down Expand Up @@ -142,19 +164,10 @@ impl Relinker for Dylib {
resolved_libraries
}

/// Resolve the rpath and replace `@loader_path` with the path of the dylib
/// Resolve the rpath and replace `@loader_path` with the path of the dylib.
fn resolve_rpath(&self, rpath: &Path, prefix: &Path, encoded_prefix: &Path) -> PathBuf {
// get self path in "encoded prefix"
let self_path =
encoded_prefix.join(self.path.strip_prefix(prefix).expect("dylib not in prefix"));
if let Ok(rpath_without_loader) = rpath.strip_prefix("@loader_path") {
if let Some(library_parent) = self_path.parent() {
return to_lexical_absolute(rpath_without_loader, library_parent);
} else {
tracing::warn!("shared library {:?} has no parent directory", self.path);
}
}
rpath.to_path_buf()
self.resolve_relative_rpath(rpath, "@loader_path", prefix, encoded_prefix)
.unwrap_or_else(|| rpath.to_path_buf())
}

/// Modify a dylib to use relative paths for rpaths and dylibs
Expand Down Expand Up @@ -215,11 +228,27 @@ impl Relinker for Dylib {
} else if rpath_allowlist.is_match(rpath) {
tracing::info!("Rpath in allow list: {}", rpath.display());
final_rpaths.push(rpath.clone());
} else {
tracing::info!(
"Rpath not in prefix or allow-listed: {} - removing it",
rpath.display()
);
}
tracing::info!(
"Rpath not in prefix or allow-listed: {} - removing it",
rpath.display()
);
} else if rpath.starts_with("@executable_path") {
// This path is relative to the process executable and is
// therefore already relocatable. Keep it even if it escapes:
// for a dylib, the actual executable path is only known at runtime.
let resolved = self
.resolve_relative_rpath(rpath, "@executable_path", prefix, encoded_prefix)
.unwrap_or_else(|| rpath.clone());
if !resolved.starts_with(encoded_prefix) {
tracing::warn!(
"Executable-relative rpath {} may resolve outside the prefix ({}); keeping it because the process executable is only known at runtime",
rpath.display(),
resolved.display()
);
}
final_rpaths.push(rpath.clone());
} else if let Ok(rel) = rpath.strip_prefix(encoded_prefix) {
let new_rpath = prefix.join(rel);

Expand Down Expand Up @@ -926,6 +955,53 @@ mod tests {
Ok(())
}

#[test]
fn test_keep_executable_relative_rpaths() -> Result<(), RelinkError> {
let prefix = Path::new(env!("CARGO_MANIFEST_DIR")).join("../../test-data/binary_files");
let tmp_dir = tempdir_in(&prefix)?;
let bin_dir = tmp_dir.path().join("bin");
fs::create_dir(&bin_dir)?;
let binary_path = bin_dir.join("zlink-executable-rpaths");
fs::copy(prefix.join("duplicate-rpath-macos"), &binary_path)?;

let object = Dylib::new(&binary_path)?;
let executable_rpaths = vec![
PathBuf::from("@executable_path/../lib"),
PathBuf::from("@executable_path/../lib/lean"),
];
let changes = DylibChanges {
change_rpath: object
.rpaths
.iter()
.cloned()
.zip(executable_rpaths.iter().cloned())
.map(|(old, new)| (Some(old), Some(new)))
.collect(),
change_id: None,
change_dylib: HashMap::default(),
};
install_name_tool(
&binary_path,
&changes,
&SystemTools::new("rattler-build", "0.0.0"),
)?;

let object = Dylib::new(&binary_path)?;
assert_eq!(object.rpaths, executable_rpaths);

object.relink(
tmp_dir.path(),
tmp_dir.path(),
&[],
&GlobVec::default(),
&SystemTools::new("rattler-build", "0.0.0"),
)?;

let object = Dylib::new(&binary_path)?;
assert_eq!(object.rpaths, executable_rpaths);
Ok(())
}

#[test]
fn test_rpath_resolve() {
let dylib = Dylib {
Expand All @@ -944,6 +1020,17 @@ mod tests {
&encoded_prefix,
);
assert_eq!(resolved, PathBuf::from("/foo/very_long_encoded_prefix/lib"));

let resolved = dylib
.resolve_relative_rpath(
&PathBuf::from("@executable_path/../../outside"),
"@executable_path",
&prefix,
&encoded_prefix,
)
.unwrap();
assert_eq!(resolved, PathBuf::from("/foo/outside"));
assert!(!resolved.starts_with(&encoded_prefix));
}

/// Create a binary with duplicate rpaths by starting from
Expand Down