feat: using system完全実装+旧スモークテストアーカイブ完了
✅ using nyashstd完全動作(ChatGPT実装) - builtin:nyashstd自動解決 - 環境変数不要でデフォルト有効 - console.log等の基本機能完備 ✅ Fixture plugin追加(テスト用最小構成) ✅ v2スモークテスト構造への移行 ✅ 旧tools/test/smoke/削除(100+ファイル) 🤖 Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@ -207,6 +207,12 @@ impl PluginLoaderV2 {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Public helper to load a single library definition directly (bypass nyash.toml sweep).
|
||||
/// Useful for `using kind="dylib"` autoload where only path and a few box names are known.
|
||||
pub fn load_plugin_direct(&self, lib_name: &str, lib_def: &LibraryDefinition) -> BidResult<()> {
|
||||
self.load_plugin(lib_name, lib_def)
|
||||
}
|
||||
|
||||
fn load_plugin_from_root(&self, _plugin_name: &str, _root: &str) -> BidResult<()> {
|
||||
Ok(())
|
||||
}
|
||||
@ -248,31 +254,39 @@ impl PluginLoaderV2 {
|
||||
|
||||
/// Lookup per-Box invoke function pointer for given type_id via loaded TypeBox specs
|
||||
pub fn box_invoke_fn_for_type_id(&self, type_id: u32) -> Option<BoxInvokeFn> {
|
||||
let config = self.config.as_ref()?;
|
||||
let cfg_path = self.config_path.as_ref()?;
|
||||
let toml_str = std::fs::read_to_string(cfg_path).ok()?;
|
||||
let toml_value: toml::Value = toml::from_str(&toml_str).ok()?;
|
||||
let (lib_name, box_type) = self.find_box_by_type_id(config, &toml_value, type_id)?;
|
||||
let key = (lib_name.to_string(), box_type.to_string());
|
||||
let map = self.box_specs.read().ok()?;
|
||||
let spec = map.get(&key);
|
||||
if let Some(s) = spec {
|
||||
if s.invoke_id.is_none() && dbg_on() {
|
||||
eprintln!(
|
||||
"[PluginLoaderV2] WARN: no per-Box invoke for {}.{} (type_id={}). Calls will fail with E_PLUGIN (-5) until plugin migrates to v2.",
|
||||
lib_name, box_type, type_id
|
||||
);
|
||||
// First try config-based resolution
|
||||
if let (Some(config), Some(cfg_path)) = (self.config.as_ref(), self.config_path.as_ref()) {
|
||||
if let (Ok(toml_str), Ok(toml_value)) = (
|
||||
std::fs::read_to_string(cfg_path),
|
||||
toml::from_str::<toml::Value>(&std::fs::read_to_string(cfg_path).unwrap_or_default()),
|
||||
) {
|
||||
let _ = toml_str; // silence
|
||||
if let Some((lib_name, box_type)) = self.find_box_by_type_id(config, &toml_value, type_id) {
|
||||
let key = (lib_name.to_string(), box_type.to_string());
|
||||
let map = self.box_specs.read().ok()?;
|
||||
if let Some(s) = map.get(&key) {
|
||||
if s.invoke_id.is_none() && dbg_on() {
|
||||
eprintln!(
|
||||
"[PluginLoaderV2] WARN: no per-Box invoke for {}.{} (type_id={}). Calls will fail with E_PLUGIN (-5) until plugin migrates to v2.",
|
||||
lib_name, box_type, type_id
|
||||
);
|
||||
}
|
||||
return s.invoke_id;
|
||||
}
|
||||
}
|
||||
}
|
||||
s.invoke_id
|
||||
} else {
|
||||
if dbg_on() {
|
||||
eprintln!(
|
||||
"[PluginLoaderV2] INFO: no TypeBox spec loaded for {}.{} (type_id={}).",
|
||||
lib_name, box_type, type_id
|
||||
);
|
||||
}
|
||||
None
|
||||
}
|
||||
// Fallback: scan box_specs for matching type_id (autoload path without central config)
|
||||
if let Ok(map) = self.box_specs.read() {
|
||||
for ((_lib, _bt), spec) in map.iter() {
|
||||
if let Some(tid) = spec.type_id {
|
||||
if tid == type_id {
|
||||
return spec.invoke_id;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn metadata_for_type_id(&self, type_id: u32) -> Option<PluginBoxMetadata> {
|
||||
@ -315,25 +329,51 @@ impl PluginLoaderV2 {
|
||||
})
|
||||
}
|
||||
|
||||
/// Resolve method_id for (box_type, method_name), consulting config first, then TypeBox resolve() if available.
|
||||
/// Resolve method_id for (box_type, method_name) with graceful fallback when central config is absent.
|
||||
pub(crate) fn resolve_method_id(&self, box_type: &str, method_name: &str) -> BidResult<u32> {
|
||||
use std::ffi::CString;
|
||||
let cfg = self.config.as_ref().ok_or(BidError::PluginError)?;
|
||||
let cfg_path = self.config_path.as_deref().unwrap_or("nyash.toml");
|
||||
let toml_value: toml::Value = super::errors::from_toml(toml::from_str(
|
||||
&std::fs::read_to_string(cfg_path).map_err(|_| BidError::PluginError)?,
|
||||
))?;
|
||||
// 1) config mapping
|
||||
if let Some((lib_name, _)) = cfg.find_library_for_box(box_type) {
|
||||
if let Some(bc) = cfg.get_box_config(&lib_name, box_type, &toml_value) {
|
||||
if let Some(m) = bc.methods.get(method_name) {
|
||||
return Ok(m.method_id);
|
||||
if let Some(cfg) = self.config.as_ref() {
|
||||
let cfg_path = self.config_path.as_deref().unwrap_or("nyash.toml");
|
||||
let toml_value: toml::Value = super::errors::from_toml(toml::from_str(
|
||||
&std::fs::read_to_string(cfg_path).map_err(|_| BidError::PluginError)?,
|
||||
))?;
|
||||
if let Some((lib_name, _)) = cfg.find_library_for_box(box_type) {
|
||||
if let Some(bc) = cfg.get_box_config(&lib_name, box_type, &toml_value) {
|
||||
if let Some(m) = bc.methods.get(method_name) {
|
||||
return Ok(m.method_id);
|
||||
}
|
||||
}
|
||||
let key = (lib_name.to_string(), box_type.to_string());
|
||||
if let Ok(mut map) = self.box_specs.write() {
|
||||
if let Some(spec) = map.get_mut(&key) {
|
||||
if let Some(ms) = spec.methods.get(method_name) {
|
||||
return Ok(ms.method_id);
|
||||
}
|
||||
if let Some(res_fn) = spec.resolve_fn {
|
||||
if let Ok(cstr) = CString::new(method_name) {
|
||||
let mid = res_fn(cstr.as_ptr());
|
||||
if mid != 0 {
|
||||
spec.methods.insert(
|
||||
method_name.to_string(),
|
||||
MethodSpec { method_id: mid, returns_result: false },
|
||||
);
|
||||
if dbg_on() {
|
||||
eprintln!(
|
||||
"[PluginLoaderV2] resolve(name) {}.{} -> id {}",
|
||||
box_type, method_name, mid
|
||||
);
|
||||
}
|
||||
return Ok(mid);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// 2) v2 TypeBox resolve (and cache)
|
||||
let key = (lib_name.to_string(), box_type.to_string());
|
||||
} else {
|
||||
// No config loaded: consult any spec for this box_type
|
||||
if let Ok(mut map) = self.box_specs.write() {
|
||||
if let Some(spec) = map.get_mut(&key) {
|
||||
if let Some((_, spec)) = map.iter_mut().find(|((_, bt), _)| bt == &box_type) {
|
||||
if let Some(ms) = spec.methods.get(method_name) {
|
||||
return Ok(ms.method_id);
|
||||
}
|
||||
@ -341,20 +381,10 @@ impl PluginLoaderV2 {
|
||||
if let Ok(cstr) = CString::new(method_name) {
|
||||
let mid = res_fn(cstr.as_ptr());
|
||||
if mid != 0 {
|
||||
// Cache minimal MethodSpec (returns_result unknown → false)
|
||||
spec.methods.insert(
|
||||
method_name.to_string(),
|
||||
MethodSpec {
|
||||
method_id: mid,
|
||||
returns_result: false,
|
||||
},
|
||||
MethodSpec { method_id: mid, returns_result: false },
|
||||
);
|
||||
if dbg_on() {
|
||||
eprintln!(
|
||||
"[PluginLoaderV2] resolve(name) {}.{} -> id {}",
|
||||
box_type, method_name, mid
|
||||
);
|
||||
}
|
||||
return Ok(mid);
|
||||
}
|
||||
}
|
||||
@ -412,6 +442,77 @@ impl PluginLoaderV2 {
|
||||
None
|
||||
}
|
||||
|
||||
/// Best-effort: ingest specs from nyash_box.toml for autoloaded plugins.
|
||||
pub fn ingest_box_specs_from_nyash_box(
|
||||
&self,
|
||||
lib_name: &str,
|
||||
box_names: &[String],
|
||||
nyash_box_toml_path: &std::path::Path,
|
||||
) {
|
||||
if !nyash_box_toml_path.exists() {
|
||||
return;
|
||||
}
|
||||
let Ok(text) = std::fs::read_to_string(nyash_box_toml_path) else { return; };
|
||||
let Ok(doc) = toml::from_str::<toml::Value>(&text) else { return; };
|
||||
if let Ok(mut map) = self.box_specs.write() {
|
||||
for box_type in box_names {
|
||||
let key = (lib_name.to_string(), box_type.to_string());
|
||||
let mut spec = map.get(&key).cloned().unwrap_or_default();
|
||||
// type_id
|
||||
if let Some(tid) = doc
|
||||
.get(box_type)
|
||||
.and_then(|v| v.get("type_id"))
|
||||
.and_then(|v| v.as_integer())
|
||||
{
|
||||
spec.type_id = Some(tid as u32);
|
||||
}
|
||||
// lifecycle.fini
|
||||
if let Some(fini) = doc
|
||||
.get(box_type)
|
||||
.and_then(|v| v.get("lifecycle"))
|
||||
.and_then(|v| v.get("fini"))
|
||||
.and_then(|v| v.get("id"))
|
||||
.and_then(|v| v.as_integer())
|
||||
{
|
||||
spec.fini_method_id = Some(fini as u32);
|
||||
}
|
||||
// lifecycle.birth (treat as method name "birth")
|
||||
if let Some(birth) = doc
|
||||
.get(box_type)
|
||||
.and_then(|v| v.get("lifecycle"))
|
||||
.and_then(|v| v.get("birth"))
|
||||
.and_then(|v| v.get("id"))
|
||||
.and_then(|v| v.as_integer())
|
||||
{
|
||||
spec.methods.insert(
|
||||
"birth".to_string(),
|
||||
MethodSpec { method_id: birth as u32, returns_result: false },
|
||||
);
|
||||
}
|
||||
// methods.*.id
|
||||
if let Some(methods) = doc
|
||||
.get(box_type)
|
||||
.and_then(|v| v.get("methods"))
|
||||
.and_then(|v| v.as_table())
|
||||
{
|
||||
for (mname, mdef) in methods.iter() {
|
||||
if let Some(id) = mdef
|
||||
.get("id")
|
||||
.and_then(|v| v.as_integer())
|
||||
.map(|x| x as u32)
|
||||
{
|
||||
spec.methods.insert(
|
||||
mname.to_string(),
|
||||
MethodSpec { method_id: id, returns_result: mdef.get("returns_result").and_then(|v| v.as_bool()).unwrap_or(false) },
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
map.insert(key, spec);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn ensure_singleton_handle(&self, lib_name: &str, box_type: &str) -> BidResult<()> {
|
||||
if self
|
||||
.singletons
|
||||
@ -674,20 +775,35 @@ impl PluginLoaderV2 {
|
||||
instance_id: u32,
|
||||
args: &[Box<dyn NyashBox>],
|
||||
) -> BidResult<Option<Box<dyn NyashBox>>> {
|
||||
// Non-recursive direct bridge for minimal methods used by semantics and basic VM paths
|
||||
// Resolve library/type/method ids from cached config
|
||||
let cfg = self.config.as_ref().ok_or(BidError::PluginError)?;
|
||||
let cfg_path = self.config_path.as_deref().unwrap_or("nyash.toml");
|
||||
let toml_value: toml::Value =
|
||||
toml::from_str(&std::fs::read_to_string(cfg_path).map_err(|_| BidError::PluginError)?)
|
||||
.map_err(|_| BidError::PluginError)?;
|
||||
let (lib_name, _lib_def) = cfg
|
||||
.find_library_for_box(box_type)
|
||||
.ok_or(BidError::InvalidType)?;
|
||||
let box_conf = cfg
|
||||
.get_box_config(lib_name, box_type, &toml_value)
|
||||
.ok_or(BidError::InvalidType)?;
|
||||
let type_id = box_conf.type_id;
|
||||
// Resolve (lib_name, type_id) either from config or cached specs
|
||||
let (lib_name, type_id) = if let Some(cfg) = self.config.as_ref() {
|
||||
let cfg_path = self.config_path.as_deref().unwrap_or("nyash.toml");
|
||||
let toml_value: toml::Value =
|
||||
toml::from_str(&std::fs::read_to_string(cfg_path).map_err(|_| BidError::PluginError)?)
|
||||
.map_err(|_| BidError::PluginError)?;
|
||||
if let Some((lib_name, _)) = cfg.find_library_for_box(box_type) {
|
||||
if let Some(bc) = cfg.get_box_config(lib_name, box_type, &toml_value) {
|
||||
(lib_name.to_string(), bc.type_id)
|
||||
} else {
|
||||
let key = (lib_name.to_string(), box_type.to_string());
|
||||
let map = self.box_specs.read().map_err(|_| BidError::PluginError)?;
|
||||
let tid = map
|
||||
.get(&key)
|
||||
.and_then(|s| s.type_id)
|
||||
.ok_or(BidError::InvalidType)?;
|
||||
(lib_name.to_string(), tid)
|
||||
}
|
||||
} else {
|
||||
return Err(BidError::InvalidType);
|
||||
}
|
||||
} else {
|
||||
let map = self.box_specs.read().map_err(|_| BidError::PluginError)?;
|
||||
if let Some(((lib, _), spec)) = map.iter().find(|((_, bt), _)| bt == box_type) {
|
||||
(lib.clone(), spec.type_id.ok_or(BidError::InvalidType)?)
|
||||
} else {
|
||||
return Err(BidError::InvalidType);
|
||||
}
|
||||
};
|
||||
// Resolve method id via config or TypeBox resolve()
|
||||
let method_id = match self.resolve_method_id(box_type, method_name) {
|
||||
Ok(mid) => mid,
|
||||
@ -703,9 +819,15 @@ impl PluginLoaderV2 {
|
||||
};
|
||||
// Get plugin handle
|
||||
let plugins = self.plugins.read().map_err(|_| BidError::PluginError)?;
|
||||
let _plugin = plugins.get(lib_name).ok_or(BidError::PluginError)?;
|
||||
let _plugin = plugins.get(&lib_name).ok_or(BidError::PluginError)?;
|
||||
// Encode TLV args via shared helper (numeric→string→toString)
|
||||
let tlv = crate::runtime::plugin_ffi_common::encode_args(args);
|
||||
if dbg_on() {
|
||||
eprintln!(
|
||||
"[PluginLoaderV2] call {}.{}: type_id={} method_id={} instance_id={}",
|
||||
box_type, method_name, type_id, method_id, instance_id
|
||||
);
|
||||
}
|
||||
let (_code, out_len, out) = super::host_bridge::invoke_alloc(
|
||||
super::super::nyash_plugin_invoke_v2_shim,
|
||||
type_id,
|
||||
@ -784,26 +906,48 @@ impl PluginLoaderV2 {
|
||||
_args: &[Box<dyn NyashBox>],
|
||||
) -> BidResult<Box<dyn NyashBox>> {
|
||||
// Non-recursive: directly call plugin 'birth' and construct PluginBoxV2
|
||||
let cfg = self.config.as_ref().ok_or(BidError::PluginError)?;
|
||||
let cfg_path = self.config_path.as_deref().unwrap_or("nyash.toml");
|
||||
let toml_value: toml::Value =
|
||||
toml::from_str(&std::fs::read_to_string(cfg_path).map_err(|_| BidError::PluginError)?)
|
||||
.map_err(|_| BidError::PluginError)?;
|
||||
let (lib_name, _) = cfg
|
||||
.find_library_for_box(box_type)
|
||||
.ok_or(BidError::InvalidType)?;
|
||||
// Try config mapping first (when available)
|
||||
let (mut type_id_opt, mut birth_id_opt, mut fini_id) = (None, None, None);
|
||||
if let Some(cfg) = self.config.as_ref() {
|
||||
let cfg_path = self.config_path.as_deref().unwrap_or("nyash.toml");
|
||||
let toml_value: toml::Value =
|
||||
toml::from_str(&std::fs::read_to_string(cfg_path).map_err(|_| BidError::PluginError)?)
|
||||
.map_err(|_| BidError::PluginError)?;
|
||||
if let Some((lib_name, _)) = cfg.find_library_for_box(box_type) {
|
||||
if let Some(box_conf) = cfg.get_box_config(lib_name, box_type, &toml_value) {
|
||||
type_id_opt = Some(box_conf.type_id);
|
||||
birth_id_opt = box_conf.methods.get("birth").map(|m| m.method_id);
|
||||
fini_id = box_conf.methods.get("fini").map(|m| m.method_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Resolve type_id and method ids
|
||||
let box_conf = cfg
|
||||
.get_box_config(lib_name, box_type, &toml_value)
|
||||
.ok_or(BidError::InvalidType)?;
|
||||
let type_id = box_conf.type_id;
|
||||
let birth_id = box_conf
|
||||
.methods
|
||||
.get("birth")
|
||||
.map(|m| m.method_id)
|
||||
.ok_or(BidError::InvalidMethod)?;
|
||||
let fini_id = box_conf.methods.get("fini").map(|m| m.method_id);
|
||||
// Fallback: use TypeBox FFI spec if config is missing for this box
|
||||
if type_id_opt.is_none() || birth_id_opt.is_none() {
|
||||
if let Ok(map) = self.box_specs.read() {
|
||||
// Find any spec that matches this box_type
|
||||
if let Some((_, spec)) = map.iter().find(|((_lib, bt), _)| bt == &box_type) {
|
||||
if type_id_opt.is_none() {
|
||||
type_id_opt = spec.type_id;
|
||||
}
|
||||
if birth_id_opt.is_none() {
|
||||
if let Some(ms) = spec.methods.get("birth") {
|
||||
birth_id_opt = Some(ms.method_id);
|
||||
} else if let Some(res_fn) = spec.resolve_fn {
|
||||
if let Ok(cstr) = std::ffi::CString::new("birth") {
|
||||
let mid = res_fn(cstr.as_ptr());
|
||||
if mid != 0 {
|
||||
birth_id_opt = Some(mid);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let type_id = type_id_opt.ok_or(BidError::InvalidType)?;
|
||||
let birth_id = birth_id_opt.ok_or(BidError::InvalidMethod)?;
|
||||
|
||||
// Get loaded plugin invoke
|
||||
let _plugins = self.plugins.read().map_err(|_| BidError::PluginError)?;
|
||||
|
||||
Reference in New Issue
Block a user