use flate2::read::GzDecoder;
use log::info;
use std::env;
use std::fs::{self, File};
use std::io;
use std::os::unix::fs::symlink;
use std::path::{Path, PathBuf};
const LIBHDFS_BASE_URL: &str = "https://repo.hops.works/master/libhdfs";
const LIB_DIR_ENV: &str = "HDFS_LIB_DIR";
fn main() -> Result<(), Box<dyn std::error::Error>> {
env_logger::init();
println!("cargo:rerun-if-env-changed={}", LIB_DIR_ENV);
println!("cargo:rerun-if-changed=HOPS_VERSION");
println!("cargo:rerun-if-changed=build.rs");
let out_dir = PathBuf::from(env::var("OUT_DIR")?);
let lib_dir = resolve_lib_dir(&out_dir)?;
set_libraries(&out_dir, &lib_dir);
Ok(())
}
fn resolve_lib_dir(out_dir: &Path) -> Result<PathBuf, Box<dyn std::error::Error>> {
if let Ok(dir) = env::var(LIB_DIR_ENV) {
let dir = PathBuf::from(dir);
if !dir.is_dir() {
return Err(format!("{}={:?} is not a directory", LIB_DIR_ENV, dir).into());
}
info!("Using prebuilt libhdfs from {:?}", dir);
return Ok(dir);
}
let lib_dir = out_dir.join("lib");
download_and_extract_libhdfs(&lib_dir)?;
Ok(lib_dir)
}
fn download_and_extract_libhdfs(lib_dir: &Path) -> Result<(), Box<dyn std::error::Error>> {
let version = fs::read_to_string("HOPS_VERSION")
.expect("Failed to read HOPS_VERSION file")
.trim()
.to_string();
let tarball_url = format!("{}/libhdfs-golang-{}.tar.gz", LIBHDFS_BASE_URL, version);
info!("Downloading libhdfs-golang from {}", tarball_url);
let client = reqwest::blocking::Client::new();
let response = client.get(&tarball_url).send()?;
if !response.status().is_success() {
return Err(format!(
"Failed to download libhdfs-golang: HTTP {}",
response.status()
)
.into());
}
if lib_dir.exists() {
fs::remove_dir_all(lib_dir)?;
}
fs::create_dir_all(lib_dir)?;
let decoder = GzDecoder::new(response);
let mut archive = tar::Archive::new(decoder);
for entry in archive.entries()? {
let mut entry = entry?;
if !entry.header().entry_type().is_file() {
continue;
}
let path = entry.path()?.to_path_buf();
let file_name = path
.file_name()
.ok_or("Invalid file name in archive")?
.to_str()
.ok_or("Non-UTF8 file name")?
.to_string();
let dest_path = lib_dir.join(&file_name);
let mut dest_file = File::create(&dest_path)?;
io::copy(&mut entry, &mut dest_file)?;
info!("Extracted: {}", file_name);
}
info!("Successfully extracted libhdfs-golang to {:?}", lib_dir);
Ok(())
}
#[cfg(target_os = "macos")]
fn set_libraries(out_dir: &Path, lib_dir: &Path) {
create_symlinks(out_dir, lib_dir, "macos", false);
println!("cargo:rustc-link-search=native={}", out_dir.display());
println!("cargo:rustc-link-lib=static=hdfs");
println!("cargo:rustc-link-lib=framework=Security");
println!("cargo:rustc-link-lib=framework=CoreFoundation");
println!("cargo:rustc-link-lib=resolv");
}
#[cfg(target_os = "linux")]
fn set_libraries(out_dir: &Path, lib_dir: &Path) {
create_symlinks(out_dir, lib_dir, "linux", true);
println!("cargo:rustc-link-search=native={}", out_dir.display());
println!("cargo:rustc-link-lib=hdfs");
println!("cargo:rustc-link-arg=-Wl,-rpath,$ORIGIN");
}
#[cfg(not(any(target_os = "macos", target_os = "linux")))]
fn set_libraries(_out_dir: &Path, _lib_dir: &Path) {
panic!("Unsupported target OS: HopsFS object store only supports macOS and Linux.");
}
fn create_symlinks(out_dir: &Path, lib_dir: &Path, target_os: &str, shared: bool) {
let filter = match target_os {
"linux" => "linux-amd64",
"macos" => "arm64",
other => panic!("Unsupported target OS: {}", other),
};
let lib_ext = if shared {
match target_os {
"linux" => ".so",
"macos" => ".dylib",
other => panic!("Unsupported target OS: {}", other),
}
} else {
".a"
};
let mut lib_file = None;
let mut header_file = None;
for entry in fs::read_dir(lib_dir).expect("Could not read lib directory") {
let entry = entry.expect("Error reading directory entry");
let file_name = entry
.file_name()
.into_string()
.expect("Invalid file name");
if file_name.ends_with(lib_ext) && file_name.contains(filter) {
lib_file = Some(entry.path());
} else if file_name.ends_with(".h") && file_name.contains(filter) {
header_file = Some(entry.path());
}
}
let lib_file = lib_file.expect("Library file not found");
let header_file = header_file.expect("Header file not found");
let symlink_lib = out_dir.join(format!("libhdfs{}", lib_ext));
let symlink_header = out_dir.join("libhdfs.h");
if symlink_lib.symlink_metadata().is_ok() {
fs::remove_file(&symlink_lib).expect("Failed to remove existing library symlink");
}
if symlink_header.symlink_metadata().is_ok() {
fs::remove_file(&symlink_header).expect("Failed to remove existing header symlink");
}
symlink(&lib_file, &symlink_lib).expect("Failed to create symlink for library");
symlink(&header_file, &symlink_header).expect("Failed to create symlink for header");
info!(
"Created symlinks: {:?} -> {:?}, {:?} -> {:?}",
symlink_lib, lib_file, symlink_header, header_file
);
}