runtara_workflows/
agents_library.rs1use std::io;
9use std::path::PathBuf;
10use std::sync::OnceLock;
11use tracing::info;
12
13#[derive(Debug, Clone)]
15pub struct NativeLibraryInfo {
16 pub scenario_lib_path: PathBuf,
18 pub deps_dir: PathBuf,
20}
21
22static NATIVE_LIBRARY: OnceLock<NativeLibraryInfo> = OnceLock::new();
24
25pub fn get_stdlib_name() -> String {
38 std::env::var("RUNTARA_STDLIB_NAME").unwrap_or_else(|_| "runtara_workflow_stdlib".to_string())
39}
40
41fn get_native_library_dir() -> PathBuf {
43 if let Ok(cache_dir) = std::env::var("RUNTARA_NATIVE_LIBRARY_DIR") {
45 let path = PathBuf::from(cache_dir);
46 if path.exists() {
47 return path;
48 }
49 }
50
51 let installed_path = PathBuf::from("/usr/share/runtara/library_cache/native");
53 if installed_path.exists() {
54 return installed_path;
55 }
56
57 let deduplicated_path = PathBuf::from("target/native_cache");
59 if deduplicated_path.exists() {
60 return deduplicated_path;
61 }
62
63 for profile in &["debug", "release"] {
66 let build_dir = PathBuf::from(format!("target/{}/build", profile));
67 if build_dir.exists() {
68 if let Ok(entries) = std::fs::read_dir(&build_dir) {
70 for entry in entries.flatten() {
71 let name = entry.file_name();
72 let name_str = name.to_string_lossy();
73 if name_str.starts_with("runtara-") {
74 let native_path = entry.path().join("out/native_cache/native");
75 if native_path.exists() {
76 return native_path;
77 }
78 }
79 }
80 }
81 }
82 }
83
84 let data_dir = std::env::var("DATA_DIR").unwrap_or_else(|_| ".data".to_string());
86 let data_path = PathBuf::from(data_dir).join("library_cache").join("native");
87 if data_path.exists() {
88 return data_path;
89 }
90
91 PathBuf::from(".data/library_cache/native")
93}
94
95fn load_native_library() -> io::Result<NativeLibraryInfo> {
97 let lib_dir = get_native_library_dir();
98
99 if !lib_dir.exists() {
100 return Err(io::Error::other(format!(
101 "Pre-compiled native library not found. Expected at: {:?}",
102 lib_dir
103 )));
104 }
105
106 let stdlib_name = get_stdlib_name();
108 let scenario_lib_path = lib_dir.join(format!("lib{}.rlib", stdlib_name));
109
110 if !scenario_lib_path.exists() {
111 return Err(io::Error::other(format!(
112 "{} library not found at: {:?}",
113 stdlib_name, scenario_lib_path
114 )));
115 }
116
117 let deps_dir = lib_dir.join("deps");
119
120 if !deps_dir.exists() {
121 return Err(io::Error::other(format!(
122 "Native deps directory not found at: {:?}",
123 deps_dir
124 )));
125 }
126
127 tracing::debug!(
128 scenario_lib = %scenario_lib_path.display(),
129 deps_dir = %deps_dir.display(),
130 "Loaded pre-compiled native library"
131 );
132
133 Ok(NativeLibraryInfo {
134 scenario_lib_path,
135 deps_dir,
136 })
137}
138
139pub fn get_native_library() -> io::Result<NativeLibraryInfo> {
147 if let Some(info) = NATIVE_LIBRARY.get() {
149 return Ok(info.clone());
150 }
151
152 let info = load_native_library()?;
154
155 let _ = NATIVE_LIBRARY.set(info.clone());
157
158 info!(
159 scenario_lib = %info.scenario_lib_path.display(),
160 deps_dir = %info.deps_dir.display(),
161 "Native library ready (pre-compiled during build)"
162 );
163
164 Ok(info)
165}
166
167#[cfg(test)]
168mod tests {
169 use super::*;
170 use std::env;
171 use std::sync::Mutex;
172
173 static ENV_MUTEX: Mutex<()> = Mutex::new(());
175
176 struct EnvGuard {
178 vars: Vec<(String, Option<String>)>,
179 }
180
181 impl EnvGuard {
182 fn new() -> Self {
183 Self { vars: Vec::new() }
184 }
185
186 fn set(&mut self, key: &str, value: &str) {
187 let old = env::var(key).ok();
188 self.vars.push((key.to_string(), old));
189 unsafe { env::set_var(key, value) };
191 }
192
193 fn remove(&mut self, key: &str) {
194 let old = env::var(key).ok();
195 self.vars.push((key.to_string(), old));
196 unsafe { env::remove_var(key) };
198 }
199 }
200
201 impl Drop for EnvGuard {
202 fn drop(&mut self) {
203 for (key, value) in self.vars.drain(..).rev() {
204 unsafe {
206 match value {
207 Some(v) => env::set_var(&key, v),
208 None => env::remove_var(&key),
209 }
210 }
211 }
212 }
213 }
214
215 #[test]
220 fn test_native_library_info_debug() {
221 let info = NativeLibraryInfo {
222 scenario_lib_path: PathBuf::from("/usr/lib/libruntara_workflow_stdlib.rlib"),
223 deps_dir: PathBuf::from("/usr/lib/deps"),
224 };
225
226 let debug_str = format!("{:?}", info);
227 assert!(debug_str.contains("NativeLibraryInfo"));
228 assert!(debug_str.contains("scenario_lib_path"));
229 assert!(debug_str.contains("deps_dir"));
230 }
231
232 #[test]
233 fn test_native_library_info_clone() {
234 let info = NativeLibraryInfo {
235 scenario_lib_path: PathBuf::from("/path/to/lib.rlib"),
236 deps_dir: PathBuf::from("/path/to/deps"),
237 };
238
239 let cloned = info.clone();
240
241 assert_eq!(info.scenario_lib_path, cloned.scenario_lib_path);
242 assert_eq!(info.deps_dir, cloned.deps_dir);
243 }
244
245 #[test]
246 fn test_native_library_info_paths() {
247 let info = NativeLibraryInfo {
248 scenario_lib_path: PathBuf::from("/custom/path/libworkflow.rlib"),
249 deps_dir: PathBuf::from("/custom/path/deps"),
250 };
251
252 assert_eq!(
253 info.scenario_lib_path,
254 PathBuf::from("/custom/path/libworkflow.rlib")
255 );
256 assert_eq!(info.deps_dir, PathBuf::from("/custom/path/deps"));
257 }
258
259 #[test]
264 fn test_get_stdlib_name_default() {
265 let _lock = ENV_MUTEX.lock().unwrap();
266 let mut guard = EnvGuard::new();
267
268 guard.remove("RUNTARA_STDLIB_NAME");
269
270 let name = get_stdlib_name();
271 assert_eq!(name, "runtara_workflow_stdlib");
272 }
273
274 #[test]
275 fn test_get_stdlib_name_custom() {
276 let _lock = ENV_MUTEX.lock().unwrap();
277 let mut guard = EnvGuard::new();
278
279 guard.set("RUNTARA_STDLIB_NAME", "smo_workflow_stdlib");
280
281 let name = get_stdlib_name();
282 assert_eq!(name, "smo_workflow_stdlib");
283 }
284
285 #[test]
286 fn test_get_stdlib_name_custom_with_underscores() {
287 let _lock = ENV_MUTEX.lock().unwrap();
288 let mut guard = EnvGuard::new();
289
290 guard.set("RUNTARA_STDLIB_NAME", "my_custom_stdlib_name");
291
292 let name = get_stdlib_name();
293 assert_eq!(name, "my_custom_stdlib_name");
294 }
295
296 #[test]
297 fn test_get_stdlib_name_empty_uses_default() {
298 let _lock = ENV_MUTEX.lock().unwrap();
299 let mut guard = EnvGuard::new();
300
301 guard.set("RUNTARA_STDLIB_NAME", "");
303
304 let name = get_stdlib_name();
305 assert_eq!(name, "");
307 }
308
309 #[test]
314 fn test_get_native_library_dir_from_env() {
315 let _lock = ENV_MUTEX.lock().unwrap();
316 let mut guard = EnvGuard::new();
317
318 let temp_dir = tempfile::TempDir::new().unwrap();
320 guard.set(
321 "RUNTARA_NATIVE_LIBRARY_DIR",
322 temp_dir.path().to_str().unwrap(),
323 );
324
325 let dir = get_native_library_dir();
326 assert_eq!(dir, temp_dir.path());
327 }
328
329 #[test]
330 fn test_get_native_library_dir_env_nonexistent_falls_through() {
331 let _lock = ENV_MUTEX.lock().unwrap();
332 let mut guard = EnvGuard::new();
333
334 guard.set("RUNTARA_NATIVE_LIBRARY_DIR", "/nonexistent/path/12345");
336 guard.remove("DATA_DIR");
337
338 let dir = get_native_library_dir();
339 assert_ne!(dir, PathBuf::from("/nonexistent/path/12345"));
341 }
342
343 #[test]
344 fn test_get_native_library_dir_data_dir_env() {
345 let _lock = ENV_MUTEX.lock().unwrap();
346 let mut guard = EnvGuard::new();
347
348 let temp_dir = tempfile::TempDir::new().unwrap();
350 let lib_cache = temp_dir.path().join("library_cache").join("native");
351 std::fs::create_dir_all(&lib_cache).unwrap();
352
353 guard.remove("RUNTARA_NATIVE_LIBRARY_DIR");
354 guard.set("DATA_DIR", temp_dir.path().to_str().unwrap());
355
356 let dir = get_native_library_dir();
357 assert!(dir.to_str().is_some());
360 }
361
362 #[test]
367 fn test_load_native_library_missing_dir() {
368 let _lock = ENV_MUTEX.lock().unwrap();
369 let mut guard = EnvGuard::new();
370
371 let temp_dir = tempfile::TempDir::new().unwrap();
373 let nonexistent = temp_dir.path().join("nonexistent");
374 guard.set("RUNTARA_NATIVE_LIBRARY_DIR", nonexistent.to_str().unwrap());
375
376 guard.remove("DATA_DIR");
378
379 let result = load_native_library();
382 if let Err(e) = result {
384 assert!(e.to_string().contains("not found") || e.to_string().contains("library"));
385 }
386 }
387
388 #[test]
389 fn test_load_native_library_missing_rlib() {
390 let _lock = ENV_MUTEX.lock().unwrap();
391 let mut guard = EnvGuard::new();
392
393 let temp_dir = tempfile::TempDir::new().unwrap();
395 guard.set(
396 "RUNTARA_NATIVE_LIBRARY_DIR",
397 temp_dir.path().to_str().unwrap(),
398 );
399 guard.remove("RUNTARA_STDLIB_NAME");
400
401 let result = load_native_library();
402 assert!(result.is_err());
403 let err = result.unwrap_err();
404 assert!(err.to_string().contains("not found"));
405 }
406
407 #[test]
408 fn test_load_native_library_missing_deps() {
409 let _lock = ENV_MUTEX.lock().unwrap();
410 let mut guard = EnvGuard::new();
411
412 let temp_dir = tempfile::TempDir::new().unwrap();
414 std::fs::write(
415 temp_dir.path().join("libruntara_workflow_stdlib.rlib"),
416 b"fake rlib",
417 )
418 .unwrap();
419
420 guard.set(
421 "RUNTARA_NATIVE_LIBRARY_DIR",
422 temp_dir.path().to_str().unwrap(),
423 );
424 guard.remove("RUNTARA_STDLIB_NAME");
425
426 let result = load_native_library();
427 assert!(result.is_err());
428 let err = result.unwrap_err();
429 assert!(err.to_string().contains("deps"));
430 }
431
432 #[test]
433 fn test_load_native_library_success() {
434 let _lock = ENV_MUTEX.lock().unwrap();
435 let mut guard = EnvGuard::new();
436
437 let temp_dir = tempfile::TempDir::new().unwrap();
439 let deps_dir = temp_dir.path().join("deps");
440 std::fs::create_dir(&deps_dir).unwrap();
441 std::fs::write(
442 temp_dir.path().join("libruntara_workflow_stdlib.rlib"),
443 b"fake rlib",
444 )
445 .unwrap();
446
447 guard.set(
448 "RUNTARA_NATIVE_LIBRARY_DIR",
449 temp_dir.path().to_str().unwrap(),
450 );
451 guard.remove("RUNTARA_STDLIB_NAME");
452
453 let result = load_native_library();
454 assert!(result.is_ok());
455
456 let info = result.unwrap();
457 assert_eq!(
458 info.scenario_lib_path,
459 temp_dir.path().join("libruntara_workflow_stdlib.rlib")
460 );
461 assert_eq!(info.deps_dir, deps_dir);
462 }
463
464 #[test]
465 fn test_load_native_library_custom_stdlib_name() {
466 let _lock = ENV_MUTEX.lock().unwrap();
467 let mut guard = EnvGuard::new();
468
469 let temp_dir = tempfile::TempDir::new().unwrap();
471 let deps_dir = temp_dir.path().join("deps");
472 std::fs::create_dir(&deps_dir).unwrap();
473 std::fs::write(temp_dir.path().join("libcustom_stdlib.rlib"), b"fake rlib").unwrap();
474
475 guard.set(
476 "RUNTARA_NATIVE_LIBRARY_DIR",
477 temp_dir.path().to_str().unwrap(),
478 );
479 guard.set("RUNTARA_STDLIB_NAME", "custom_stdlib");
480
481 let result = load_native_library();
482 assert!(result.is_ok());
483
484 let info = result.unwrap();
485 assert_eq!(
486 info.scenario_lib_path,
487 temp_dir.path().join("libcustom_stdlib.rlib")
488 );
489 }
490}