use std::fs::{self, File, create_dir_all};
use std::io::{self, Write};
use std::path::Path;
use std::process::Command;
use dodo_bindgen_codegen::{FfiLib, TsModule};
mod cargo;
mod interface;
mod meta_reader;
use cargo::Cargo;
pub type Result<T> = std::result::Result<T, ()>;
fn write_atomic(
destination: &Path,
fill: impl FnOnce(&mut File) -> io::Result<()>,
) -> io::Result<()> {
let mut builder = tempfile::Builder::new();
#[cfg(unix)]
builder.permissions(std::os::unix::fs::PermissionsExt::from_mode(0o666));
let mut file = builder.tempfile_in(destination.parent().unwrap_or(Path::new(".")))?;
fill(file.as_file_mut())?;
file.persist(destination)?;
Ok(())
}
fn main() -> Result<()> {
let args: interface::Cli = clap::Parser::parse();
let metadata = Cargo::get_metadata();
let file_name;
let out_path;
if let Some(output) = &args.output {
file_name = output
.file_name()
.expect("invalid output file name")
.to_str()
.expect("unknown utf8 character on file name")
.to_string();
let mut _out_path = output.clone();
_out_path.pop();
out_path = _out_path;
} else {
file_name = format!("lib{}.ts", metadata.lib_name);
out_path = metadata.pkg_path.join("lib");
}
create_dir_all(&out_path).expect("failed to create output dir");
let targets = if args.targets.is_empty() {
vec![None]
} else {
args.targets
.iter()
.map(|target| Some(target.as_str()))
.collect()
};
let mut items = None;
for target in targets {
let dylib_path = Cargo::build(&metadata.pkg_name, args.profile(), target);
if items.is_none() {
items = Some(meta_reader::read(&dylib_path));
}
let target = target
.map(str::to_string)
.unwrap_or_else(Cargo::host_target);
let extension = dylib_path
.extension()
.and_then(|extension| extension.to_str())
.expect("library artifact has no extension");
let destination = out_path.join(FfiLib::file_name(&metadata.lib_name, &target, extension));
write_atomic(&destination, |file| {
io::copy(&mut File::open(&dylib_path)?, file)?;
file.set_permissions(fs::metadata(&dylib_path)?.permissions())
})
.unwrap_or_else(|err| {
panic!(
"failed to copy `{}` to `{}`: {err}",
dylib_path.display(),
destination.display()
)
});
println!("copied {}", destination.display());
}
let items = items.expect("no library was built");
let opts = args.to_codegen_opts(file_name.to_string(), metadata.lib_name.clone());
let module = TsModule::new(items, &opts);
let module_path = out_path.join(file_name);
let mut written = vec![module_path.clone()];
if args.inline {
let module = module.generate_single(&opts);
write_atomic(&module_path, |file| file.write_all(module.as_bytes()))
.expect("failed to write module file");
} else {
let (module, type_defs) = module.generate_multiple(&opts, "rust_type.ts");
write_atomic(&module_path, |file| file.write_all(module.as_bytes()))
.expect("failed to write module file");
let typedefs_path = out_path.join("rust_type.ts");
write_atomic(&typedefs_path, |file| file.write_all(type_defs.as_bytes()))
.expect("failed to write typedefs file");
written.push(typedefs_path);
}
match Command::new("deno")
.args(["fmt", "--quiet"])
.args(&written)
.current_dir(&out_path)
.status()
{
Ok(status) if status.success() => {},
Ok(status) => {
eprintln!("warning: `deno fmt` exited with {status}, output left unformatted")
},
Err(err) => eprintln!("warning: failed to run `deno fmt`: {err}, output left unformatted"),
}
println!("{} ready", metadata.pkg_name);
Ok(())
}