use anyhow::{Context, Result};
use std::fs;
use std::path::{Path, PathBuf};
use tracing::warn;
fn copy_dir_recursive(src: &Path, dst: &Path) -> Result<()> {
fs::create_dir_all(dst).context(format!("creating directory: {}", dst.display()))?;
for entry in fs::read_dir(src).context(format!("reading directory: {}", src.display()))? {
let entry = entry.context(format!("reading entry in {}", src.display()))?;
let path = entry.path();
let file_name = entry.file_name();
let dst_path = dst.join(&file_name);
if path.is_dir() {
copy_dir_recursive(&path, &dst_path)?;
} else {
fs::copy(&path, &dst_path)
.with_context(|| format!("copying file {} to {}", path.display(), dst_path.display()))?;
}
}
Ok(())
}
#[derive(Debug, Clone)]
struct NativeLibPattern {
rid: &'static str,
rust_target: &'static str,
formats: &'static [NativeLibFormat],
}
#[derive(Debug, Clone, Copy)]
enum NativeLibFormat {
MacosDylib,
MacosFramework,
UnixSharedObject,
WindowsDll,
}
impl NativeLibFormat {
fn filename(self, stem: &str) -> String {
match self {
Self::MacosDylib => format!("lib{stem}.dylib"),
Self::MacosFramework => format!("{stem}.framework/{stem}"),
Self::UnixSharedObject => format!("lib{stem}.so"),
Self::WindowsDll => format!("{stem}.dll"),
}
}
}
const MACOS_NATIVE_LIB_FORMATS: &[NativeLibFormat] = &[NativeLibFormat::MacosDylib, NativeLibFormat::MacosFramework];
const LINUX_NATIVE_LIB_FORMATS: &[NativeLibFormat] = &[NativeLibFormat::UnixSharedObject];
const WINDOWS_NATIVE_LIB_FORMATS: &[NativeLibFormat] = &[NativeLibFormat::WindowsDll];
const NATIVE_LIB_PATTERNS: &[NativeLibPattern] = &[
NativeLibPattern {
rid: "macos-x64",
rust_target: "x86_64-apple-darwin",
formats: MACOS_NATIVE_LIB_FORMATS,
},
NativeLibPattern {
rid: "macos-arm64",
rust_target: "aarch64-apple-darwin",
formats: MACOS_NATIVE_LIB_FORMATS,
},
NativeLibPattern {
rid: "linux-x64",
rust_target: "x86_64-unknown-linux-gnu",
formats: LINUX_NATIVE_LIB_FORMATS,
},
NativeLibPattern {
rid: "linux-arm64",
rust_target: "aarch64-unknown-linux-gnu",
formats: LINUX_NATIVE_LIB_FORMATS,
},
NativeLibPattern {
rid: "windows-x64",
rust_target: "x86_64-pc-windows-msvc",
formats: WINDOWS_NATIVE_LIB_FORMATS,
},
NativeLibPattern {
rid: "windows-arm64",
rust_target: "aarch64-pc-windows-msvc",
formats: WINDOWS_NATIVE_LIB_FORMATS,
},
];
fn find_native_libraries(
workspace_root: &Path,
rust_target: &str,
filenames: &[String],
) -> Result<Vec<(PathBuf, PathBuf)>> {
let target_dir = workspace_root.join("target").join(rust_target).join("release");
let mut found = Vec::new();
if !target_dir.exists() {
return Ok(found); }
for filename in filenames {
let relative_path = PathBuf::from(filename);
let lib_path = target_dir.join(&relative_path);
if lib_path.exists() {
found.push((lib_path, relative_path));
}
}
Ok(found)
}
pub fn stage_dart_native_libraries(workspace_root: &Path, package_root: &Path, stem: &str) -> Result<()> {
let native_base = package_root.join("lib/src/native");
let mut staged_any = false;
for pattern in NATIVE_LIB_PATTERNS {
let filenames = pattern
.formats
.iter()
.map(|format| format.filename(stem))
.collect::<Vec<_>>();
let libs = find_native_libraries(workspace_root, pattern.rust_target, &filenames)?;
if libs.is_empty() {
continue;
}
let rid_dir = native_base.join(pattern.rid);
fs::create_dir_all(&rid_dir).context(format!("creating native library directory: {}", rid_dir.display()))?;
for (lib_path, relative_path) in libs {
let dest = rid_dir.join(relative_path);
if let Some(parent) = dest.parent() {
fs::create_dir_all(parent)
.with_context(|| format!("creating native library parent directory: {}", parent.display()))?;
}
if lib_path.is_dir() {
copy_dir_recursive(&lib_path, &dest)
.with_context(|| format!("copying native library directory {} to {}", lib_path.display(), dest.display()))?;
} else {
fs::copy(&lib_path, &dest)
.with_context(|| format!("copying native library {} to {}", lib_path.display(), dest.display()))?;
}
staged_any = true;
}
}
if !staged_any {
warn!(
"no prebuilt native libraries found for Dart binding '{}'; packages will require local build",
stem
);
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn stage_native_libraries_uses_package_stem() {
let tmp = tempfile::tempdir().expect("tempdir");
let target_dir = tmp.path().join("target/aarch64-apple-darwin/release");
fs::create_dir_all(&target_dir).unwrap();
fs::write(target_dir.join("libmy_lib_dart.dylib"), "native").unwrap();
let package_root = tmp.path().join("packages/dart");
fs::create_dir_all(&package_root).unwrap();
stage_dart_native_libraries(tmp.path(), &package_root, "my_lib_dart").unwrap();
assert!(
package_root
.join("lib/src/native/macos-arm64/libmy_lib_dart.dylib")
.exists()
);
}
#[test]
fn stage_native_libraries_preserves_framework_layout() {
let tmp = tempfile::tempdir().expect("tempdir");
let target_dir = tmp
.path()
.join("target/aarch64-apple-darwin/release/my_lib_dart.framework");
fs::create_dir_all(&target_dir).unwrap();
fs::write(target_dir.join("my_lib_dart"), "native").unwrap();
let package_root = tmp.path().join("packages/dart");
fs::create_dir_all(&package_root).unwrap();
stage_dart_native_libraries(tmp.path(), &package_root, "my_lib_dart").unwrap();
assert!(
package_root
.join("lib/src/native/macos-arm64/my_lib_dart.framework/my_lib_dart")
.exists()
);
}
}