use crate::core::config::ResolvedCrateConfig;
use crate::core::ir::ApiSurface;
use anyhow::Result;
pub(super) fn gen_component_manager(api: &ApiSurface, prefix: &str, config: &ResolvedCrateConfig) -> Result<String> {
let runtime = crate::backends::native_components::generate(config, "/components.lock.json");
let activation = crate::backends::native_components::generate_activation(api, config)?;
Ok(format!(
r#"{runtime}
{activation}
unsafe fn alef_component_id<'a>(component: *const c_char) -> Result<&'a str, String> {{
if component.is_null() {{
return Err("component must not be NULL".to_string());
}}
// SAFETY: the caller promises a valid, NUL-terminated C string for the duration of this call.
unsafe {{ CStr::from_ptr(component) }}
.to_str()
.map_err(|error| format!("component is not valid UTF-8: {{error}}"))
}}
fn alef_component_owned_string(value: String) -> Result<*mut c_char, String> {{
CString::new(value)
.map(CString::into_raw)
.map_err(|_| "component result contains an interior NUL byte".to_string())
}}
/// Download and verify a configured component's artifact.
/// Returns 0 on success and -1 on failure. Failure details are available through
/// `{prefix}_last_error_code` and `{prefix}_last_error_context`.
/// # Safety
/// `component` must point to a valid, NUL-terminated UTF-8 string.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn {prefix}_component_load(component: *const c_char) -> i32 {{
clear_last_error();
let result = (|| {{
// SAFETY: upheld by this function's caller contract.
let component = unsafe {{ alef_component_id(component) }}?;
alef_component_load(component)
}})();
match result {{
Ok(()) => 0,
Err(error) => {{
set_last_error(99, &error);
-1
}}
}}
}}
/// Download and verify one component, or all configured components when `component` is NULL.
/// Returns an owned JSON array of cache paths, freed with `{prefix}_free_string`.
/// # Safety
/// A non-null `component` must point to a valid, NUL-terminated UTF-8 string.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn {prefix}_component_prefetch(component: *const c_char) -> *mut c_char {{
clear_last_error();
let result = (|| {{
let requested = if component.is_null() {{
None
}} else {{
// SAFETY: upheld by this function's caller contract.
Some(vec![unsafe {{ alef_component_id(component) }}?.to_string()])
}};
let paths = alef_component_prefetch(requested)?;
let json = serde_json::to_string(&paths).map_err(|error| error.to_string())?;
alef_component_owned_string(json)
}})();
match result {{
Ok(value) => value,
Err(error) => {{
set_last_error(99, &error);
std::ptr::null_mut()
}}
}}
}}
/// Return `ready`, `cached`, `not_downloaded`, `bundled`, or `unsupported:<reason>` for a
/// configured component. The returned string is owned and must be freed with
/// `{prefix}_free_string`. See `{prefix}_component_status_code` for the matching numeric code.
/// # Safety
/// `component` must point to a valid, NUL-terminated UTF-8 string.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn {prefix}_component_status(component: *const c_char) -> *mut c_char {{
clear_last_error();
let result = (|| {{
// SAFETY: upheld by this function's caller contract.
let component = unsafe {{ alef_component_id(component) }}?;
alef_component_owned_string(alef_component_status(component)?)
}})();
match result {{
Ok(value) => value,
Err(error) => {{
set_last_error(99, &error);
std::ptr::null_mut()
}}
}}
}}
/// The numeric counterpart to `{prefix}_component_status`, stable across releases. Returns
/// -1 and sets the last error on failure.
/// # Safety
/// `component` must point to a valid, NUL-terminated UTF-8 string.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn {prefix}_component_status_code(component: *const c_char) -> i32 {{
clear_last_error();
let result = (|| {{
// SAFETY: upheld by this function's caller contract.
let component = unsafe {{ alef_component_id(component) }}?;
alef_component_status_code(component)
}})();
match result {{
Ok(code) => code,
Err(error) => {{
set_last_error(99, &error);
-1
}}
}}
}}
/// Return the content-addressed cache path for a configured component.
/// The returned string is owned and must be freed with `{prefix}_free_string`.
/// # Safety
/// `component` must point to a valid, NUL-terminated UTF-8 string.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn {prefix}_component_cache_path(component: *const c_char) -> *mut c_char {{
clear_last_error();
let result = (|| {{
// SAFETY: upheld by this function's caller contract.
let component = unsafe {{ alef_component_id(component) }}?;
alef_component_owned_string(alef_component_cache_path(component)?)
}})();
match result {{
Ok(value) => value,
Err(error) => {{
set_last_error(99, &error);
std::ptr::null_mut()
}}
}}
}}
/// Download, verify, load, and register a configured component's contracts so
/// core-crate code that looks them up through `alef_component_abi::provider`
/// finds them. Returns 0 on success and -1 on failure; never runs implicitly.
/// # Safety
/// `component` must point to a valid, NUL-terminated UTF-8 string.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn {prefix}_component_activate(component: *const c_char) -> i32 {{
clear_last_error();
let result = (|| {{
// SAFETY: upheld by this function's caller contract.
let component = unsafe {{ alef_component_id(component) }}?;
alef_component_activate(component)
}})();
match result {{
Ok(()) => 0,
Err(error) => {{
set_last_error(99, &error);
-1
}}
}}
}}"#,
))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::config::{ComponentConfig, ComponentContractConfig, ComponentProvidesConfig};
use crate::core::ir::{MethodDef, ReceiverKind, TypeDef, TypeRef};
fn fixture() -> (ApiSurface, ResolvedCrateConfig) {
let api = ApiSurface {
crate_name: "demo_core".into(),
version: "1.0.0".into(),
types: vec![TypeDef {
name: "Engine".into(),
rust_path: "demo_core::Engine".into(),
is_trait: true,
is_opaque: true,
methods: vec![MethodDef {
name: "run".into(),
return_type: TypeRef::Bytes,
receiver: Some(ReceiverKind::Ref),
..MethodDef::default()
}],
..TypeDef::default()
}],
..ApiSurface::default()
};
let config = ResolvedCrateConfig {
name: "demo-core".into(),
component_contracts: vec![ComponentContractConfig {
name: "engine".into(),
trait_path: "demo_core::Engine".into(),
interface_version: 1,
}],
components: vec![ComponentConfig {
name: "fast".into(),
provides: vec![ComponentProvidesConfig {
contract: "engine".into(),
implementation: "demo_core::FastEngine".into(),
}],
features: vec!["fast".into()],
default_features: false,
targets: Some(vec!["x86_64-unknown-linux-gnu".into()]),
bundled_on: Vec::new(),
}],
..ResolvedCrateConfig::default()
};
(api, config)
}
#[test]
fn emits_shared_c_component_manager_abi() {
let (api, config) = fixture();
let generated = gen_component_manager(&api, "demo", &config).unwrap();
assert!(generated.contains("fn demo_component_load"));
assert!(generated.contains("fn demo_component_prefetch"));
assert!(generated.contains("fn demo_component_status"));
assert!(generated.contains("fn demo_component_status_code"));
assert!(generated.contains("fn demo_component_cache_path"));
assert!(generated.contains("fn demo_component_activate"));
assert!(generated.contains("vec![\"fast\"]"));
assert!(generated.contains("DEMO_CORE_COMPONENT_CACHE"));
assert!(generated.contains("components.lock.json"));
assert!(generated.contains("downloadable native components are unsupported"));
assert!(generated.contains("pub struct EngineProxy"));
}
#[test]
fn cbindgen_emits_documented_component_manager_declarations() {
let (api, config) = fixture();
let directory = tempfile::tempdir().unwrap();
let source = directory.path().join("lib.rs");
std::fs::write(&source, gen_component_manager(&api, "demo", &config).unwrap()).unwrap();
let bindings = cbindgen::Builder::new()
.with_language(cbindgen::Language::C)
.with_src(source)
.generate()
.expect("cbindgen accepts generated component manager");
let mut header = Vec::new();
bindings.write(&mut header);
let header = String::from_utf8(header).unwrap();
assert!(header.contains("int32_t demo_component_load(const char *component);"));
assert!(header.contains("char *demo_component_prefetch(const char *component);"));
assert!(header.contains("char *demo_component_status(const char *component);"));
assert!(header.contains("int32_t demo_component_status_code(const char *component);"));
assert!(header.contains("char *demo_component_cache_path(const char *component);"));
assert!(header.contains("int32_t demo_component_activate(const char *component);"));
assert!(header.contains("freed with `demo_free_string`"));
assert!(header.contains("when `component` is NULL"));
}
}