use std::{fs, io::Cursor, os::unix::fs::PermissionsExt, path::Path};
use crate::library::Library;
use color_eyre::{Help, Result};
pub struct Runner<'a, T: Library> {
version: &'a str,
ni_version: &'a str,
libraries: Vec<T>,
header_contents: &'a str,
allowlist: &'a str,
lib_list: &'a [&'a str],
output_directory: &'a Path,
clang_args: String,
}
impl<'a, T: Library> Runner<'a, T> {
pub fn new(
version: &'a str,
ni_version: &'a str,
libraries: Vec<T>,
header_contents: &'a str,
allowlist: &'a str,
lib_list: &'a [&'a str],
output_directory: &'a Path,
clang_args: String,
) -> Self {
Self {
version,
ni_version,
libraries,
header_contents,
allowlist,
lib_list,
output_directory,
clang_args,
}
}
pub async fn run(&mut self, link_only: bool) -> Result<()> {
let complete_marker_path = self.output_directory.join("arfur.complete");
if !complete_marker_path.exists() && !link_only {
self.download_libraries()
.await
.note("Failed to download libraries.")?;
self.install_libraries()
.await
.note("Failed to install libraries.")?;
#[cfg(feature = "bindgen")]
self.generate_bindings()
.await
.note("Failed to generate bindings.")?;
self.cleanup().note("Failed to clean up after build.")?;
} else {
println!("Built copy found, not building again...");
}
self.link_libraries()
.note("Failed to ask Cargo to link to libraries.")?;
Ok(())
}
pub async fn download_libraries(&mut self) -> Result<()> {
let extracted_dir = self.output_directory.join("raw");
for library in &self.libraries {
let link = library.get_link(self.version, self.ni_version);
let zipped = reqwest::get(link)
.await
.note("Failed to download archive.")?
.bytes()
.await
.note("Failed to convert archive into bytes.")?;
zip_extract::extract(Cursor::new(zipped), &extracted_dir, false)
.note("Failed to extract zip file.")?;
}
Ok(())
}
pub async fn install_libraries(&mut self) -> Result<()> {
let dynamic_library_dir = self
.output_directory
.join("raw")
.join("linux")
.join("athena")
.join("shared");
fs::set_permissions(&dynamic_library_dir, fs::Permissions::from_mode(0o755))?;
for file in fs::read_dir(&dynamic_library_dir)? {
let file = file?;
fs::set_permissions(&file.path(), fs::Permissions::from_mode(0o755))?;
if file.file_name().to_str().unwrap().ends_with(".debug") {
fs::remove_file(file.path())?;
} else if !&file.file_name().to_str().unwrap().ends_with(".so") {
let name = file.file_name();
let mut name = name.to_str().unwrap().chars();
for _ in 1..8 {
name.next_back();
}
let name = name.as_str();
let mut new_name = file.path();
new_name.set_file_name(name);
fs::rename(file.path(), new_name)?;
}
}
Ok(())
}
pub fn link_libraries(&mut self) -> Result<()> {
let dynamic_library_dir = self
.output_directory
.join("raw")
.join("linux")
.join("athena")
.join("shared");
for lib in self.lib_list.iter() {
println!("cargo:rustc-link-lib=dylib={}", lib);
}
println!(
"cargo:rustc-link-search=native={dynamic_library_dir}",
dynamic_library_dir = dynamic_library_dir.to_str().unwrap()
);
Ok(())
}
#[cfg(feature = "bindgen")]
pub async fn generate_bindings(&mut self) -> Result<()> {
let raw_directory = self.output_directory.join("raw");
let bindings = bindgen::Builder::default()
.header_contents("runner-header", self.header_contents)
.parse_callbacks(Box::new(bindgen::CargoCallbacks))
.enable_cxx_namespaces()
.allowlist_function(self.allowlist)
.allowlist_type(self.allowlist)
.allowlist_var(self.allowlist)
.clang_arg(format!("-I{}", raw_directory.to_str().unwrap()))
.clang_arg(self.clang_args.clone())
.clang_arg("-std=c++17")
.clang_args(&["-x", "c++"]);
println!("clang command: {:?}", bindings.command_line_flags());
bindings
.generate()
.note("Failed to generate bindings...")?
.write_to_file(self.output_directory.join("bindings.rs"))
.note("Failed to write bindings file...")?;
Ok(())
}
pub fn cleanup(&mut self) -> Result<()> {
let complete_marker_path = self.output_directory.join("arfur.complete");
fs::File::create(complete_marker_path)?;
Ok(())
}
}