use crate::core::backend::GeneratedFile;
use crate::core::config::{FfiTargetDepOverride, Language, ResolvedCrateConfig};
use crate::core::ir::ApiSurface;
use crate::core::template_versions as tv;
use std::path::PathBuf;
fn render_jni_target_blocks(
crate_name: &str,
rel_path: &str,
default_features: &str,
version: &str,
overrides: &[FfiTargetDepOverride],
) -> String {
if overrides.is_empty() {
return String::new();
}
let cfgs: Vec<&str> = overrides.iter().map(|o| o.cfg.as_str()).collect();
let combined_cfg = if cfgs.len() == 1 {
cfgs[0].to_owned()
} else {
format!("any({})", cfgs.join(", "))
};
let mut entries: Vec<(String, String)> = vec![(
format!("not({combined_cfg})"),
crate::scaffold::render_core_dep(crate_name, rel_path, default_features, version),
)];
for override_ in overrides {
let default_block = if override_.default_features {
String::new()
} else {
", default-features = false".to_string()
};
let features_str = if override_.features.is_empty() {
String::new()
} else {
let quoted: Vec<String> = override_.features.iter().map(|f| format!("\"{f}\"")).collect();
format!(", features = [{}]", quoted.join(", "))
};
let override_features = format!("{default_block}{features_str}");
entries.push((
override_.cfg.clone(),
crate::scaffold::render_core_dep(crate_name, rel_path, &override_features, version),
));
}
let joined = crate::scaffold::join_sorted_target_dep_blocks(entries);
format!("\n{joined}")
}
pub(crate) fn scaffold_jni(api: &ApiSurface, config: &ResolvedCrateConfig) -> anyhow::Result<Vec<GeneratedFile>> {
let jni_crate_name = format!("{}-jni", config.jni_crate_base());
let jni_lib_name = config.jni_lib_name();
let features_str = crate::scaffold::core_dep_features(config, Language::KotlinAndroid);
let umbrella_dep_name = &config.name;
let target_overrides = config
.jni
.as_ref()
.map(|c| c.target_dep_overrides.as_slice())
.unwrap_or(&[]);
let rel_path = config.core_crate_dep_path(std::path::Path::new(&format!("crates/{jni_crate_name}")));
let mut dep_lines: Vec<String> = vec![
crate::scaffold::render_workspace_dep_or(config, "async-trait", &format!("\"{}\"", tv::cargo::ASYNC_TRAIT)),
crate::scaffold::render_workspace_dep_or(config, "base64", &format!("\"{}\"", tv::cargo::BASE64)),
crate::scaffold::render_workspace_dep_or(config, "futures-util", &format!("\"{}\"", tv::cargo::FUTURES_UTIL)),
crate::scaffold::render_workspace_dep_or(config, "jni", &format!("\"{}\"", tv::cargo::JNI)),
crate::scaffold::render_workspace_dep_or(config, "serde_json", &format!("\"{}\"", tv::cargo::SERDE_JSON)),
crate::scaffold::render_workspace_dep_or(
config,
"tokio",
"{ version = \"1\", features = [\"rt-multi-thread\", \"macros\", \"sync\"] }",
),
crate::scaffold::render_workspace_dep_or(config, "tracing", &format!("\"{}\"", tv::cargo::TRACING)),
];
if target_overrides.is_empty() {
dep_lines.push(crate::scaffold::render_core_dep(
umbrella_dep_name,
&rel_path,
&features_str,
&api.version,
));
}
crate::scaffold::sort_dependency_lines(&mut dep_lines);
let deps_section = dep_lines.join("\n");
let target_blocks_section = render_jni_target_blocks(
umbrella_dep_name,
&rel_path,
&features_str,
&api.version,
target_overrides,
);
let lints_section = crate::scaffold::cargo_lints_section(config);
let crate_dir = format!("crates/{jni_crate_name}");
let ws = crate::scaffold::detect_workspace_inheritance_for_crate(config.workspace_root.as_deref(), &crate_dir);
let package_header = crate::scaffold::cargo_package_header(
&jni_crate_name,
&api.version,
"2024",
&crate::scaffold::scaffold_meta(config),
&ws,
);
let content = format!(
r#"# Generated by alef. Do not edit by hand.
{package_header}
# `base64`, `futures-util`, `serde_json`, and `tokio` are emitted unconditionally below
# so the manifest is stable across regens (they are used when the umbrella
# crate declares binary top-level params, async fns, streaming adapters, or JSON-marshalled types),
# but for an umbrella crate that has none of those they are genuinely unused.
# List them here so `cargo machete` doesn't flag the no-async-no-streaming
# case as a real finding.
[package.metadata.cargo-machete]
ignored = ["async-trait", "base64", "futures-util", "serde_json", "tokio", "tracing"]
[lib]
name = "{jni_lib_name}"
crate-type = ["cdylib"]
[dependencies]
{deps_section}
{target_blocks_section}{lints_section}"#,
package_header = package_header,
jni_lib_name = jni_lib_name,
lints_section = lints_section,
deps_section = deps_section,
target_blocks_section = target_blocks_section,
);
let _ = api;
Ok(vec![GeneratedFile {
path: PathBuf::from(format!("crates/{jni_crate_name}/Cargo.toml")),
content,
generated_header: true,
}])
}
#[cfg(test)]
#[path = "jni/tests.rs"]
mod tests;