use core::fmt;
use lean_rs::error::LeanResult;
use lean_rs::module::{LeanBuiltCapability, LeanCapability, LeanLibrary};
use crate::host::bracketed::{LeanBracketedImportRequest, LeanBracketedImportResult};
use crate::host::cancellation::LeanCancellationToken;
use crate::host::host::LeanHost;
use crate::host::progress::LeanProgressSink;
use crate::host::session::{LeanImportProfileMode, LeanImportProfilerOptions, LeanSession, LeanSessionImportProfile};
use crate::host::shim_bindings::host_shim_export_signatures;
const HOST_CARGO_TEST_IMPORT_OVERRIDE: &str = "LEAN_RS_ALLOW_CARGO_TEST_HOST_IMPORTS";
pub struct LeanCapabilities<'lean, 'h> {
host: &'h LeanHost<'lean>,
_user_library: Option<LeanLibrary<'lean>>,
shim_capability: LeanCapability<'lean>,
}
impl fmt::Debug for LeanCapabilities<'_, '_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("LeanCapabilities").finish_non_exhaustive()
}
}
impl<'lean, 'h> LeanCapabilities<'lean, 'h> {
pub(crate) fn new(
host: &'h LeanHost<'lean>,
user_library: LeanLibrary<'lean>,
package: &str,
lib_name: &str,
) -> LeanResult<Self> {
let shim_capability = load_shim_capability(host)?;
let _user_module = user_library.initialize_module(package, lib_name)?;
Ok(Self {
host,
_user_library: Some(user_library),
shim_capability,
})
}
pub(crate) fn new_shims_only(host: &'h LeanHost<'lean>) -> LeanResult<Self> {
let shim_capability = load_shim_capability(host)?;
Ok(Self {
host,
_user_library: None,
shim_capability,
})
}
pub fn session<'c>(
&'c self,
imports: &[&str],
cancellation: Option<&LeanCancellationToken>,
progress: Option<&dyn LeanProgressSink>,
) -> LeanResult<LeanSession<'lean, 'c>> {
reject_same_process_cargo_test_session_import()?;
LeanSession::import(self, imports, cancellation, progress)
}
pub fn session_with_profile<'c>(
&'c self,
imports: &[&str],
profile: LeanSessionImportProfile,
cancellation: Option<&LeanCancellationToken>,
progress: Option<&dyn LeanProgressSink>,
) -> LeanResult<LeanSession<'lean, 'c>> {
reject_same_process_cargo_test_session_import()?;
LeanSession::import_with_profile(self, imports, profile, cancellation, progress)
}
pub fn profiling_session<'c>(
&'c self,
imports: &[&str],
mode: LeanImportProfileMode,
profiler_options: &LeanImportProfilerOptions,
) -> LeanResult<LeanSession<'lean, 'c>> {
reject_same_process_cargo_test_session_import()?;
LeanSession::import_profiled(self, imports, mode, profiler_options)
}
pub fn bracketed_import_query(
&self,
imports: &[&str],
request: LeanBracketedImportRequest,
progress: Option<&dyn LeanProgressSink>,
) -> LeanResult<LeanBracketedImportResult> {
LeanBracketedImportResult::query(self, imports, request, progress)
}
pub(crate) fn host(&self) -> &'h LeanHost<'lean> {
self.host
}
pub(crate) fn shim_capability(&self) -> &LeanCapability<'lean> {
&self.shim_capability
}
}
fn load_shim_capability<'lean>(host: &LeanHost<'lean>) -> LeanResult<LeanCapability<'lean>> {
let built = crate::host::lake::LakeProject::shim_capability(host_shim_export_signatures())?;
LeanCapability::from_build_manifest(
host.runtime(),
LeanBuiltCapability::manifest_path(built.manifest_path()),
)
}
fn reject_same_process_cargo_test_session_import() -> LeanResult<()> {
if !same_process_cargo_test_import_guard_active() {
return Ok(());
}
Err(lean_rs::__host_internals::host_resource_exhausted(format!(
"lean-rs-host full-session imports are disabled under same-process cargo test; \
run `cargo nextest run -p lean-rs-host ...` for process-per-test isolation, \
or set {HOST_CARGO_TEST_IMPORT_OVERRIDE}=1 for an explicitly budgeted single-test debug run"
)))
}
fn same_process_cargo_test_import_guard_active() -> bool {
same_process_cargo_test_import_guard_active_from_facts(
std::env::var(HOST_CARGO_TEST_IMPORT_OVERRIDE).ok().as_deref(),
std::env::var_os("NEXTEST").is_some() || std::env::var_os("NEXTEST_RUN_ID").is_some(),
is_probably_cargo_test_binary(),
)
}
fn same_process_cargo_test_import_guard_active_from_facts(
override_value: Option<&str>,
nextest_present: bool,
is_cargo_test_binary: bool,
) -> bool {
if env_value_truthy(override_value) || nextest_present {
return false;
}
is_cargo_test_binary
}
fn env_value_truthy(value: Option<&str>) -> bool {
value.is_some_and(|value| matches!(value, "1" | "true" | "TRUE" | "yes" | "YES" | "on" | "ON"))
}
fn is_probably_cargo_test_binary() -> bool {
let Ok(exe) = std::env::current_exe() else {
return false;
};
let Some(parent) = exe.parent() else {
return false;
};
parent
.file_name()
.is_some_and(|name| name == std::ffi::OsStr::new("deps"))
}
#[cfg(test)]
mod tests {
use super::same_process_cargo_test_import_guard_active_from_facts;
#[test]
fn cargo_test_import_guard_blocks_same_process_libtest_harnesses() {
assert!(same_process_cargo_test_import_guard_active_from_facts(
None, false, true,
));
}
#[test]
fn cargo_test_import_guard_allows_nextest_override_and_non_test_binaries() {
assert!(!same_process_cargo_test_import_guard_active_from_facts(
None, true, true,
));
assert!(!same_process_cargo_test_import_guard_active_from_facts(
Some("1"),
false,
true,
));
assert!(!same_process_cargo_test_import_guard_active_from_facts(
None, false, false,
));
}
}