use fs_err as fs;
use rattler_build_recipe::stage1::GlobVec;
use crate::packaging::TempFiles;
use crate::linux::link::SharedObject;
use crate::macos::link::Dylib;
use crate::metadata::Output;
use crate::system_tools::{SystemTools, ToolError};
use crate::windows::link::Dll;
use rattler_conda_types::{Arch, Platform};
use std::collections::{HashMap, HashSet};
use std::path::{Path, PathBuf};
use thiserror::Error;
use super::checks::{LinkingCheckError, perform_linking_checks};
#[derive(Error, Debug)]
#[allow(missing_docs)]
pub enum RelinkError {
#[error("linking check error: {0}")]
LinkingCheck(#[from] LinkingCheckError),
#[error("failed to run install_name_tool")]
InstallNameToolFailed,
#[error("Codesign failed")]
CodesignFailed,
#[error(transparent)]
SystemToolError(#[from] ToolError),
#[error("failed to read or write file: {0}")]
IoError(#[from] std::io::Error),
#[error("failed to strip prefix from path: {0}")]
StripPrefixError(#[from] std::path::StripPrefixError),
#[error("failed to parse dynamic file: {0}")]
ParseError(#[from] goblin::error::Error),
#[error("filetype not handled")]
FileTypeNotHandled,
#[error("could not read string from MachO file: {0}")]
ReadStringError(#[from] scroll::Error),
#[error("failed to get relative path from {from} to {to}")]
PathDiffFailed { from: PathBuf, to: PathBuf },
#[error("failed to relink with built-in relinker")]
BuiltinRelinkFailed,
#[error("shared library has no parent directory")]
NoParentDir,
#[error("failed to run patchelf")]
PatchElfFailed,
#[error("rpath not found in dynamic section")]
RpathNotFound,
#[error("unknown platform for relinking")]
UnknownPlatform,
#[error("unknown file format for relinking")]
UnknownFileFormat,
}
pub trait Relinker {
fn test_file(path: &Path) -> Result<bool, RelinkError>
where
Self: Sized;
fn new(path: &Path) -> Result<Self, RelinkError>
where
Self: Sized;
#[allow(dead_code)]
fn libraries(&self) -> HashSet<PathBuf>;
fn resolve_libraries(
&self,
prefix: &Path,
encoded_prefix: &Path,
) -> HashMap<PathBuf, Option<PathBuf>>;
fn resolve_rpath(&self, rpath: &Path, prefix: &Path, encoded_prefix: &Path) -> PathBuf;
fn relink(
&self,
prefix: &Path,
encoded_prefix: &Path,
custom_rpaths: &[String],
rpath_allowlist: &GlobVec,
system_tools: &SystemTools,
) -> Result<(), RelinkError>;
}
pub fn get_relinker(platform: Platform, path: &Path) -> Result<Box<dyn Relinker>, RelinkError> {
if platform.is_linux() {
if !SharedObject::test_file(path)? {
return Err(RelinkError::UnknownFileFormat);
}
Ok(Box::new(SharedObject::new(path)?))
} else if platform.is_osx() {
if !Dylib::test_file(path)? {
return Err(RelinkError::UnknownFileFormat);
}
Ok(Box::new(Dylib::new(path)?))
} else if platform.is_windows() {
match Dll::try_new(path)? {
Some(dll) => Ok(Box::new(dll)),
None => Err(RelinkError::UnknownFileFormat),
}
} else {
Err(RelinkError::UnknownPlatform)
}
}
pub fn relink(temp_files: &TempFiles, output: &Output) -> Result<(), RelinkError> {
let dynamic_linking = &output.recipe.build().dynamic_linking;
let target_platform = output.build_configuration.target_platform;
let relocation_config = &dynamic_linking.binary_relocation;
if target_platform == Platform::NoArch
|| target_platform.arch() == Some(Arch::Wasm32)
|| relocation_config.is_none()
{
return Ok(());
}
let rpaths = dynamic_linking.rpaths.to_vec();
let rpath_allowlist = &dynamic_linking.rpath_allowlist;
let tmp_prefix = temp_files.temp_dir.path();
let encoded_prefix = &temp_files.encoded_prefix;
let mut binaries = HashSet::new();
let system_tools = output.system_tools.with_build_prefix(output.build_prefix());
use rayon::prelude::*;
let results: Vec<Result<Option<PathBuf>, RelinkError>> = temp_files
.content_type_map()
.par_iter()
.map(|(p, content_type)| {
let metadata = fs::symlink_metadata(p)?;
if metadata.is_symlink() || metadata.is_dir() {
tracing::debug!("Relink skipping symlink or directory: {}", p.display());
return Ok(None);
}
if content_type != &Some(content_inspector::ContentType::BINARY) {
return Ok(None);
}
let rel_path = p.strip_prefix(tmp_prefix)?;
if !relocation_config.is_match(rel_path) {
return Ok(None);
}
match get_relinker(target_platform, p) {
Ok(relinker) => {
if !target_platform.is_windows() {
relinker.relink(
tmp_prefix,
encoded_prefix,
&rpaths,
rpath_allowlist,
&system_tools,
)?;
}
Ok(Some(p.clone()))
}
Err(RelinkError::UnknownFileFormat) => Ok(None),
Err(e) => Err(e),
}
})
.collect();
for result in results {
match result {
Ok(Some(path)) => {
binaries.insert(path);
}
Ok(None) => {}
Err(e) => return Err(e),
}
}
perform_linking_checks(output, &binaries, tmp_prefix)?;
Ok(())
}