mod error_helpers;
mod functions;
mod type_map;
mod types;
use crate::codegen::c_consumer;
use crate::core::backend::GeneratedFile;
use crate::core::config::{ResolvedCrateConfig, resolve_output_dir};
use crate::core::ir::ApiSurface;
use std::path::PathBuf;
use error_helpers::emit_error_helpers;
use functions::emit_function;
use type_map::dart_module_name;
use types::{emit_enum, emit_type};
pub(crate) fn emit(api: &ApiSurface, config: &ResolvedCrateConfig) -> anyhow::Result<Vec<GeneratedFile>> {
let module_name = dart_module_name(&config.name);
let prefix = config.ffi_prefix();
let lib_name = config.ffi_lib_name();
let free_symbol = c_consumer::free_string_symbol(&prefix);
let error_code_symbol = c_consumer::last_error_code_symbol(&prefix);
let error_context_symbol = c_consumer::last_error_context_symbol(&prefix);
let mut content = String::new();
content.push_str(&crate::backends::dart::template_env::render(
"dart_ffi_file_header.jinja",
minijinja::context! {
module_name => module_name.as_str(),
},
));
emit_lib_loader(&lib_name, &mut content);
emit_error_helpers(
&prefix,
&free_symbol,
&error_code_symbol,
&error_context_symbol,
&mut content,
);
let exclude_functions: std::collections::HashSet<&str> = config
.dart
.as_ref()
.map(|c| c.exclude_functions.iter().map(String::as_str).collect())
.unwrap_or_default();
let exclude_types: std::collections::HashSet<&str> = config
.dart
.as_ref()
.map(|c| c.exclude_types.iter().map(String::as_str).collect())
.unwrap_or_default();
for ty in api.types.iter().filter(|t| !exclude_types.contains(t.name.as_str())) {
emit_type(ty, &mut content);
content.push('\n');
}
for en in api.enums.iter().filter(|e| !exclude_types.contains(e.name.as_str())) {
emit_enum(en, &mut content);
content.push('\n');
}
for f in api
.functions
.iter()
.filter(|f| !exclude_functions.contains(f.name.as_str()))
{
emit_function(f, &prefix, &free_symbol, &error_code_symbol, &mut content);
content.push('\n');
}
let dir = resolve_output_dir(None, &config.name, "packages/dart/lib/src");
let ffi_path = PathBuf::from(format!("{dir}/{module_name}_ffi.dart"));
let wrapper_path = PathBuf::from(format!("{dir}/{module_name}.dart"));
let wrapper_content = crate::backends::dart::template_env::render(
"dart_ffi_wrapper_file.jinja",
minijinja::context! {
module_name => module_name.as_str(),
},
);
Ok(vec![
GeneratedFile {
path: ffi_path,
content,
generated_header: false,
},
GeneratedFile {
path: wrapper_path,
content: wrapper_content,
generated_header: false,
},
])
}
fn emit_lib_loader(lib_name: &str, out: &mut String) {
out.push_str("/// Private singleton: the loaded native library.\n");
out.push_str("final DynamicLibrary _lib = DynamicLibrary.open(_libraryPath());\n\n");
out.push_str("/// Resolve the platform-specific path for the native shared library.\n");
out.push_str("String _libraryPath() {\n");
out.push_str(" if (Platform.isMacOS) {\n");
out.push_str(&crate::backends::dart::template_env::render(
"ffi_lib_path_macos_return.jinja",
minijinja::context! {
lib_name => lib_name,
},
));
out.push_str(" } else if (Platform.isWindows) {\n");
out.push_str(&crate::backends::dart::template_env::render(
"ffi_lib_path_windows_return.jinja",
minijinja::context! {
lib_name => lib_name,
},
));
out.push_str(" } else {\n");
out.push_str(&crate::backends::dart::template_env::render(
"ffi_lib_path_linux_return.jinja",
minijinja::context! {
lib_name => lib_name,
},
));
out.push_str(" }\n");
out.push_str("}\n\n");
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn lib_loader_contains_platform_branches() {
let mut out = String::new();
emit_lib_loader("my_lib", &mut out);
assert!(out.contains("Platform.isMacOS"), "missing macOS branch");
assert!(out.contains("Platform.isWindows"), "missing Windows branch");
assert!(out.contains("libmy_lib.dylib"), "missing dylib");
assert!(out.contains("my_lib.dll"), "missing dll");
assert!(out.contains("libmy_lib.so"), "missing so");
}
#[test]
fn error_helpers_reference_correct_symbols() {
let mut out = String::new();
emit_error_helpers(
"mylib",
"mylib_free_string",
"mylib_last_error_code",
"mylib_last_error_context",
&mut out,
);
assert!(out.contains("mylib_free_string"), "missing free_string symbol");
assert!(out.contains("mylib_last_error_code"), "missing error_code symbol");
assert!(out.contains("mylib_last_error_context"), "missing error_context symbol");
assert!(out.contains("_checkError"), "missing _checkError helper");
}
}