1#![allow(non_camel_case_types)]
4#![allow(non_upper_case_globals)]
5#![allow(clippy::undocumented_unsafe_blocks)]
6#![deny(clippy::missing_safety_doc)]
7
8pub mod js_native_api;
20pub mod node_api;
21pub mod util;
22pub mod uv;
23
24use core::ptr::NonNull;
25use std::borrow::Cow;
26use std::cell::Cell;
27use std::cell::RefCell;
28use std::collections::HashMap;
29pub use std::ffi::CStr;
30pub use std::os::raw::c_char;
31pub use std::os::raw::c_void;
32use std::path::Path;
33use std::path::PathBuf;
34pub use std::ptr;
35use std::rc::Rc;
36use std::thread_local;
37
38use deno_core::ExternalOpsTracker;
39use deno_core::OpState;
40use deno_core::V8CrossThreadTaskSpawner;
41use deno_core::op2;
42use deno_core::parking_lot::RwLock;
43use deno_core::url::Url;
44pub use deno_core::v8;
47use deno_permissions::PermissionCheckError;
48pub use denort_helper::DenoRtNativeAddonLoader;
49pub use denort_helper::DenoRtNativeAddonLoaderRc;
50#[cfg(unix)]
51use libloading::os::unix::*;
52#[cfg(windows)]
53use libloading::os::windows::*;
54pub use value::napi_value;
55
56pub mod function;
57mod value;
58
59#[derive(Debug, thiserror::Error, deno_error::JsError)]
60pub enum NApiError {
61 #[class(type)]
62 #[error("Invalid path")]
63 InvalidPath,
64 #[class(type)]
65 #[error(transparent)]
66 DenoRtLoad(#[from] denort_helper::LoadError),
67 #[class(type)]
68 #[error(transparent)]
69 LibLoading(#[from] libloading::Error),
70 #[class(type)]
71 #[error("Unable to find register Node-API module at {}", .0.display())]
72 ModuleNotFound(PathBuf),
73 #[class(inherit)]
74 #[error(transparent)]
75 Permission(#[from] PermissionCheckError),
76}
77
78pub type napi_status = i32;
79pub type napi_env = *mut c_void;
80pub type napi_callback_info = *mut c_void;
81pub type napi_deferred = *mut c_void;
82pub type napi_ref = *mut c_void;
83pub type napi_threadsafe_function = *mut c_void;
84pub type napi_handle_scope = *mut c_void;
85pub type napi_callback_scope = *mut c_void;
86pub type napi_escapable_handle_scope = *mut c_void;
87pub type napi_async_cleanup_hook_handle = *mut c_void;
88pub type napi_async_work = *mut c_void;
89pub type napi_async_context = *mut c_void;
90
91pub const napi_ok: napi_status = 0;
92pub const napi_invalid_arg: napi_status = 1;
93pub const napi_object_expected: napi_status = 2;
94pub const napi_string_expected: napi_status = 3;
95pub const napi_name_expected: napi_status = 4;
96pub const napi_function_expected: napi_status = 5;
97pub const napi_number_expected: napi_status = 6;
98pub const napi_boolean_expected: napi_status = 7;
99pub const napi_array_expected: napi_status = 8;
100pub const napi_generic_failure: napi_status = 9;
101pub const napi_pending_exception: napi_status = 10;
102pub const napi_cancelled: napi_status = 11;
103pub const napi_escape_called_twice: napi_status = 12;
104pub const napi_handle_scope_mismatch: napi_status = 13;
105pub const napi_callback_scope_mismatch: napi_status = 14;
106pub const napi_queue_full: napi_status = 15;
107pub const napi_closing: napi_status = 16;
108pub const napi_bigint_expected: napi_status = 17;
109pub const napi_date_expected: napi_status = 18;
110pub const napi_arraybuffer_expected: napi_status = 19;
111pub const napi_detachable_arraybuffer_expected: napi_status = 20;
112pub const napi_would_deadlock: napi_status = 21;
113pub const napi_no_external_buffers_allowed: napi_status = 22;
114pub const napi_cannot_run_js: napi_status = 23;
115
116pub static ERROR_MESSAGES: &[&CStr] = &[
117 c"",
118 c"Invalid argument",
119 c"An object was expected",
120 c"A string was expected",
121 c"A string or symbol was expected",
122 c"A function was expected",
123 c"A number was expected",
124 c"A boolean was expected",
125 c"An array was expected",
126 c"Unknown failure",
127 c"An exception is pending",
128 c"The async work item was cancelled",
129 c"napi_escape_handle already called on scope",
130 c"Invalid handle scope usage",
131 c"Invalid callback scope usage",
132 c"Thread-safe function queue is full",
133 c"Thread-safe function handle is closing",
134 c"A bigint was expected",
135 c"A date was expected",
136 c"An arraybuffer was expected",
137 c"A detachable arraybuffer was expected",
138 c"Main thread would deadlock",
139 c"External buffers are not allowed",
140 c"Cannot run JavaScript",
141];
142
143pub const NAPI_AUTO_LENGTH: usize = usize::MAX;
144
145thread_local! {
146 pub static MODULE_TO_REGISTER: RefCell<Option<*const NapiModule>> = const { RefCell::new(None) };
147}
148
149type napi_addon_register_func =
150 unsafe extern "C" fn(env: napi_env, exports: napi_value) -> napi_value;
151type napi_register_module_v1 =
152 unsafe extern "C" fn(env: napi_env, exports: napi_value) -> napi_value;
153
154#[repr(C)]
155#[derive(Clone)]
156pub struct NapiModule {
157 pub nm_version: i32,
158 pub nm_flags: u32,
159 nm_filename: *const c_char,
160 pub nm_register_func: napi_addon_register_func,
161 nm_modname: *const c_char,
162 nm_priv: *mut c_void,
163 reserved: [*mut c_void; 4],
164}
165
166pub type napi_valuetype = i32;
167
168pub const napi_undefined: napi_valuetype = 0;
169pub const napi_null: napi_valuetype = 1;
170pub const napi_boolean: napi_valuetype = 2;
171pub const napi_number: napi_valuetype = 3;
172pub const napi_string: napi_valuetype = 4;
173pub const napi_symbol: napi_valuetype = 5;
174pub const napi_object: napi_valuetype = 6;
175pub const napi_function: napi_valuetype = 7;
176pub const napi_external: napi_valuetype = 8;
177pub const napi_bigint: napi_valuetype = 9;
178
179pub type napi_threadsafe_function_release_mode = i32;
180
181pub const napi_tsfn_release: napi_threadsafe_function_release_mode = 0;
182pub const napi_tsfn_abort: napi_threadsafe_function_release_mode = 1;
183
184pub type napi_threadsafe_function_call_mode = i32;
185
186pub const napi_tsfn_nonblocking: napi_threadsafe_function_call_mode = 0;
187pub const napi_tsfn_blocking: napi_threadsafe_function_call_mode = 1;
188
189pub type napi_key_collection_mode = i32;
190
191pub const napi_key_include_prototypes: napi_key_collection_mode = 0;
192pub const napi_key_own_only: napi_key_collection_mode = 1;
193
194pub type napi_key_filter = i32;
195
196pub const napi_key_all_properties: napi_key_filter = 0;
197pub const napi_key_writable: napi_key_filter = 1;
198pub const napi_key_enumerable: napi_key_filter = 1 << 1;
199pub const napi_key_configurable: napi_key_filter = 1 << 2;
200pub const napi_key_skip_strings: napi_key_filter = 1 << 3;
201pub const napi_key_skip_symbols: napi_key_filter = 1 << 4;
202
203pub type napi_key_conversion = i32;
204
205pub const napi_key_keep_numbers: napi_key_conversion = 0;
206pub const napi_key_numbers_to_strings: napi_key_conversion = 1;
207
208pub type napi_typedarray_type = i32;
209
210pub const napi_int8_array: napi_typedarray_type = 0;
211pub const napi_uint8_array: napi_typedarray_type = 1;
212pub const napi_uint8_clamped_array: napi_typedarray_type = 2;
213pub const napi_int16_array: napi_typedarray_type = 3;
214pub const napi_uint16_array: napi_typedarray_type = 4;
215pub const napi_int32_array: napi_typedarray_type = 5;
216pub const napi_uint32_array: napi_typedarray_type = 6;
217pub const napi_float32_array: napi_typedarray_type = 7;
218pub const napi_float64_array: napi_typedarray_type = 8;
219pub const napi_bigint64_array: napi_typedarray_type = 9;
220pub const napi_biguint64_array: napi_typedarray_type = 10;
221
222#[repr(C)]
223#[derive(Clone, Copy, PartialEq)]
224pub struct napi_type_tag {
225 pub lower: u64,
226 pub upper: u64,
227}
228
229pub type napi_callback = unsafe extern "C" fn(
230 env: napi_env,
231 info: napi_callback_info,
232) -> napi_value<'static>;
233
234pub type napi_finalize = unsafe extern "C" fn(
235 env: napi_env,
236 data: *mut c_void,
237 finalize_hint: *mut c_void,
238);
239
240pub type napi_async_execute_callback =
241 unsafe extern "C" fn(env: napi_env, data: *mut c_void);
242
243pub type napi_async_complete_callback =
244 unsafe extern "C" fn(env: napi_env, status: napi_status, data: *mut c_void);
245
246pub type napi_threadsafe_function_call_js = unsafe extern "C" fn(
247 env: napi_env,
248 js_callback: napi_value,
249 context: *mut c_void,
250 data: *mut c_void,
251);
252
253pub type napi_async_cleanup_hook = unsafe extern "C" fn(
254 handle: napi_async_cleanup_hook_handle,
255 data: *mut c_void,
256);
257
258pub type napi_cleanup_hook = unsafe extern "C" fn(data: *mut c_void);
259
260pub type napi_property_attributes = i32;
261
262pub const napi_default: napi_property_attributes = 0;
263pub const napi_writable: napi_property_attributes = 1 << 0;
264pub const napi_enumerable: napi_property_attributes = 1 << 1;
265pub const napi_configurable: napi_property_attributes = 1 << 2;
266pub const napi_static: napi_property_attributes = 1 << 10;
267pub const napi_default_method: napi_property_attributes =
268 napi_writable | napi_configurable;
269pub const napi_default_jsproperty: napi_property_attributes =
270 napi_enumerable | napi_configurable | napi_writable;
271
272#[repr(C)]
273#[derive(Copy, Clone, Debug)]
274pub struct napi_property_descriptor<'a> {
275 pub utf8name: *const c_char,
276 pub name: napi_value<'a>,
277 pub method: Option<napi_callback>,
278 pub getter: Option<napi_callback>,
279 pub setter: Option<napi_callback>,
280 pub value: napi_value<'a>,
281 pub attributes: napi_property_attributes,
282 pub data: *mut c_void,
283}
284
285#[repr(C)]
286#[derive(Debug)]
287pub struct napi_extended_error_info {
288 pub error_message: *const c_char,
289 pub engine_reserved: *mut c_void,
290 pub engine_error_code: i32,
291 pub error_code: Cell<napi_status>,
292}
293
294#[repr(C)]
295#[derive(Debug)]
296pub struct napi_node_version {
297 pub major: u32,
298 pub minor: u32,
299 pub patch: u32,
300 pub release: *const c_char,
301}
302
303pub trait PendingNapiAsyncWork: FnOnce() + Send + 'static {}
304impl<T> PendingNapiAsyncWork for T where T: FnOnce() + Send + 'static {}
305
306pub struct NapiState {
307 pub env_cleanup_hooks: Rc<RefCell<Vec<(napi_cleanup_hook, *mut c_void)>>>,
309 pub env_shared_ptrs: Vec<*mut EnvShared>,
310}
311
312unsafe impl Send for NapiState {}
315
316impl Drop for NapiState {
317 fn drop(&mut self) {
318 let hooks = {
319 let h = self.env_cleanup_hooks.borrow_mut();
320 h.clone()
321 };
322
323 let hooks_to_run = hooks.into_iter().rev();
325
326 for hook in hooks_to_run {
327 if !self
329 .env_cleanup_hooks
330 .borrow()
331 .iter()
332 .any(|pair| std::ptr::fn_addr_eq(pair.0, hook.0) && pair.1 == hook.1)
333 {
334 continue;
335 }
336
337 unsafe {
338 (hook.0)(hook.1);
339 }
340
341 {
342 self.env_cleanup_hooks.borrow_mut().retain(|pair| {
343 !(std::ptr::fn_addr_eq(pair.0, hook.0) && pair.1 == hook.1)
344 });
345 }
346 }
347
348 debug_assert!(
351 {
352 let mut seen = std::collections::HashSet::new();
353 self.env_shared_ptrs.iter().all(|p| seen.insert(*p))
354 },
355 "env_shared_ptrs contains duplicate entries"
356 );
357 for env_shared_ptr in &self.env_shared_ptrs {
358 let env_shared = unsafe { &mut **env_shared_ptr };
361 if let Some(instance_data) = env_shared.instance_data.take()
362 && let Some(cb) = instance_data.finalize_cb
363 {
364 unsafe {
365 cb(
366 std::ptr::null_mut(),
367 instance_data.data,
368 instance_data.finalize_hint,
369 );
370 }
371 }
372 }
373 }
374}
375
376#[repr(C)]
377#[derive(Debug)]
378pub struct InstanceData {
379 pub data: *mut c_void,
380 pub finalize_cb: Option<napi_finalize>,
381 pub finalize_hint: *mut c_void,
382}
383
384#[repr(C)]
385#[derive(Debug)]
386pub struct EnvShared {
388 pub instance_data: Option<InstanceData>,
389 pub napi_wrap: v8::Global<v8::Private>,
390 pub type_tag: v8::Global<v8::Private>,
391 pub finalize: Option<napi_finalize>,
392 pub finalize_hint: *mut c_void,
393 pub filename: String,
394}
395
396impl EnvShared {
397 pub fn new(
398 napi_wrap: v8::Global<v8::Private>,
399 type_tag: v8::Global<v8::Private>,
400 filename: String,
401 ) -> Self {
402 Self {
403 instance_data: None,
404 napi_wrap,
405 type_tag,
406 finalize: None,
407 finalize_hint: std::ptr::null_mut(),
408 filename,
409 }
410 }
411}
412
413#[repr(C)]
414pub struct Env {
415 context: NonNull<v8::Context>,
416 pub isolate_ptr: v8::UnsafeRawIsolatePtr,
417 pub open_handle_scopes: usize,
418 pub shared: *mut EnvShared,
419 pub async_work_sender: V8CrossThreadTaskSpawner,
420 cleanup_hooks: Rc<RefCell<Vec<(napi_cleanup_hook, *mut c_void)>>>,
421 external_ops_tracker: ExternalOpsTracker,
422 pub last_error: napi_extended_error_info,
423 pub last_exception: Option<v8::Global<v8::Value>>,
424 pub global: v8::Global<v8::Object>,
425 pub create_buffer: v8::Global<v8::Function>,
426 pub report_error: v8::Global<v8::Function>,
427}
428
429unsafe impl Send for Env {}
430unsafe impl Sync for Env {}
431
432impl Env {
433 #[allow(clippy::too_many_arguments)]
434 pub fn new(
435 isolate_ptr: v8::UnsafeRawIsolatePtr,
436 context: v8::Global<v8::Context>,
437 global: v8::Global<v8::Object>,
438 create_buffer: v8::Global<v8::Function>,
439 report_error: v8::Global<v8::Function>,
440 sender: V8CrossThreadTaskSpawner,
441 cleanup_hooks: Rc<RefCell<Vec<(napi_cleanup_hook, *mut c_void)>>>,
442 external_ops_tracker: ExternalOpsTracker,
443 ) -> Self {
444 Self {
445 isolate_ptr,
446 context: context.into_raw(),
447 global,
448 create_buffer,
449 report_error,
450 shared: std::ptr::null_mut(),
451 open_handle_scopes: 0,
452 async_work_sender: sender,
453 cleanup_hooks,
454 external_ops_tracker,
455 last_error: napi_extended_error_info {
456 error_message: std::ptr::null(),
457 engine_reserved: std::ptr::null_mut(),
458 engine_error_code: 0,
459 error_code: Cell::new(napi_ok),
460 },
461 last_exception: None,
462 }
463 }
464
465 pub fn shared(&self) -> &EnvShared {
466 unsafe { &*self.shared }
468 }
469
470 pub fn shared_mut(&mut self) -> &mut EnvShared {
471 unsafe { &mut *self.shared }
473 }
474
475 pub fn add_async_work(&mut self, async_work: impl FnOnce() + Send + 'static) {
476 self.async_work_sender.spawn(|_| async_work());
477 }
478
479 #[inline]
480 pub fn isolate(&mut self) -> &mut v8::Isolate {
481 unsafe {
483 v8::Isolate::ref_from_raw_isolate_ptr_mut_unchecked(&mut self.isolate_ptr)
484 }
485 }
486
487 pub fn context<'s>(&'s self) -> v8::Local<'s, v8::Context> {
488 unsafe {
491 std::mem::transmute::<NonNull<v8::Context>, v8::Local<v8::Context>>(
492 self.context,
493 )
494 }
495 }
496
497 pub fn threadsafe_function_ref(&mut self) {
498 self.external_ops_tracker.ref_op();
499 }
500
501 pub fn threadsafe_function_unref(&mut self) {
502 self.external_ops_tracker.unref_op();
503 }
504
505 pub fn add_cleanup_hook(
506 &mut self,
507 hook: napi_cleanup_hook,
508 data: *mut c_void,
509 ) {
510 let mut hooks = self.cleanup_hooks.borrow_mut();
511 if hooks
512 .iter()
513 .any(|pair| std::ptr::fn_addr_eq(pair.0, hook) && pair.1 == data)
514 {
515 panic!("Cannot register cleanup hook with same data twice");
516 }
517 hooks.push((hook, data));
518 }
519
520 pub fn remove_cleanup_hook(
521 &mut self,
522 hook: napi_cleanup_hook,
523 data: *mut c_void,
524 ) {
525 let mut hooks = self.cleanup_hooks.borrow_mut();
526 match hooks
527 .iter()
528 .rposition(|&pair| std::ptr::fn_addr_eq(pair.0, hook) && pair.1 == data)
529 {
530 Some(index) => {
531 hooks.remove(index);
532 }
533 None => panic!("Cannot remove cleanup hook which was not registered"),
534 }
535 }
536}
537
538deno_core::extension!(deno_napi,
539 ops = [
540 op_napi_open
541 ],
542 options = {
543 deno_rt_native_addon_loader: Option<DenoRtNativeAddonLoaderRc>,
544 },
545 state = |state, options| {
546 state.put(NapiState {
547 env_cleanup_hooks: Rc::new(RefCell::new(vec![])),
548 env_shared_ptrs: vec![],
549 });
550 if let Some(loader) = options.deno_rt_native_addon_loader {
551 state.put(loader);
552 }
553 },
554);
555
556unsafe impl Sync for NapiModuleHandle {}
557unsafe impl Send for NapiModuleHandle {}
558
559#[derive(Clone, Copy)]
560struct NapiModuleHandle(*const NapiModule);
561
562static NAPI_LOADED_MODULES: std::sync::LazyLock<
563 RwLock<HashMap<PathBuf, NapiModuleHandle>>,
564> = std::sync::LazyLock::new(|| RwLock::new(HashMap::new()));
565
566#[op2(reentrant, stack_trace)]
567fn op_napi_open<'scope>(
568 scope: &mut v8::PinScope<'scope, '_>,
569 isolate: &mut v8::Isolate,
570 op_state: Rc<RefCell<OpState>>,
571 #[string] path: &str,
572 global: v8::Local<'scope, v8::Object>,
573 create_buffer: v8::Local<'scope, v8::Function>,
574 report_error: v8::Local<'scope, v8::Function>,
575) -> Result<v8::Local<'scope, v8::Value>, NApiError> {
576 let (
579 async_work_sender,
580 cleanup_hooks,
581 external_ops_tracker,
582 deno_rt_native_addon_loader,
583 path,
584 ) = {
585 let mut op_state = op_state.borrow_mut();
586 let permissions =
587 op_state.borrow_mut::<deno_permissions::PermissionsContainer>();
588 let path = permissions.check_ffi(Cow::Borrowed(Path::new(path)))?;
589 let napi_state = op_state.borrow::<NapiState>();
590 (
591 op_state.borrow::<V8CrossThreadTaskSpawner>().clone(),
592 napi_state.env_cleanup_hooks.clone(),
593 op_state.external_ops_tracker.clone(),
594 op_state.try_borrow::<DenoRtNativeAddonLoaderRc>().cloned(),
595 path,
596 )
597 };
598
599 let napi_wrap_name = v8::String::new(scope, "napi_wrap").unwrap();
600 let napi_wrap = v8::Private::new(scope, Some(napi_wrap_name));
601 let napi_wrap = v8::Global::new(scope, napi_wrap);
602
603 let type_tag_name = v8::String::new(scope, "type_tag").unwrap();
604 let type_tag = v8::Private::new(scope, Some(type_tag_name));
605 let type_tag = v8::Global::new(scope, type_tag);
606
607 let url_filename =
608 Url::from_file_path(&path).map_err(|_| NApiError::InvalidPath)?;
609 let env_shared =
610 EnvShared::new(napi_wrap, type_tag, format!("{url_filename}\0"));
611
612 let ctx = scope.get_current_context();
613 let mut env = Env::new(
614 unsafe { isolate.as_raw_isolate_ptr() },
615 v8::Global::new(scope, ctx),
616 v8::Global::new(scope, global),
617 v8::Global::new(scope, create_buffer),
618 v8::Global::new(scope, report_error),
619 async_work_sender,
620 cleanup_hooks,
621 external_ops_tracker,
622 );
623 env.shared = Box::into_raw(Box::new(env_shared));
624 op_state
628 .borrow_mut()
629 .borrow_mut::<NapiState>()
630 .env_shared_ptrs
631 .push(env.shared);
632 let env_ptr = Box::into_raw(Box::new(env)) as _;
633
634 #[cfg(unix)]
635 let flags = RTLD_LAZY;
636 #[cfg(not(unix))]
637 let flags = 0x00000008;
638
639 let real_path = match deno_rt_native_addon_loader {
640 Some(loader) => loader.load_and_resolve_path(&path)?,
641 None => Cow::Borrowed(path.as_ref()),
642 };
643
644 #[cfg(unix)]
646 let library = unsafe { Library::open(Some(real_path.as_ref()), flags) }?;
647
648 #[cfg(not(unix))]
650 let library = unsafe { Library::load_with_flags(real_path.as_ref(), flags) }?;
651
652 let maybe_module = MODULE_TO_REGISTER.with(|cell| {
653 let mut slot = cell.borrow_mut();
654 slot.take()
655 });
656
657 let exports = v8::Object::new(scope);
659
660 let maybe_exports = if let Some(module_to_register) = maybe_module {
661 NAPI_LOADED_MODULES.write().insert(
662 real_path.to_path_buf(),
663 NapiModuleHandle(module_to_register),
664 );
665 let nm = unsafe { &*module_to_register };
667 assert_eq!(nm.nm_version, 1);
668 unsafe { (nm.nm_register_func)(env_ptr, exports.into()) }
670 } else if let Some(module_to_register) =
671 { NAPI_LOADED_MODULES.read().get(real_path.as_ref()).copied() }
672 {
673 let nm = unsafe { &*module_to_register.0 };
676 assert_eq!(nm.nm_version, 1);
677 unsafe { (nm.nm_register_func)(env_ptr, exports.into()) }
679 } else {
680 match unsafe {
681 library.get::<napi_register_module_v1>(b"napi_register_module_v1")
682 } {
683 Ok(init) => {
684 unsafe { init(env_ptr, exports.into()) }
687 }
688 _ => {
689 return Err(NApiError::ModuleNotFound(path.into_owned()));
690 }
691 }
692 };
693
694 let exports = maybe_exports.unwrap_or(exports.into());
695
696 std::mem::forget(library);
699
700 Ok(exports)
701}
702
703#[allow(clippy::print_stdout)]
704pub fn print_linker_flags(name: &str) {
705 let symbols_path =
706 include_str!(concat!(env!("OUT_DIR"), "/napi_symbol_path.txt"));
707
708 #[cfg(target_os = "windows")]
709 println!("cargo:rustc-link-arg-bin={name}=/DEF:{}", symbols_path);
710
711 #[cfg(target_os = "macos")]
712 println!(
713 "cargo:rustc-link-arg-bin={name}=-Wl,-exported_symbols_list,{}",
714 symbols_path,
715 );
716
717 #[cfg(any(
718 target_os = "linux",
719 target_os = "freebsd",
720 target_os = "openbsd"
721 ))]
722 println!(
723 "cargo:rustc-link-arg-bin={name}=-Wl,--export-dynamic-symbol-list={}",
724 symbols_path,
725 );
726
727 #[cfg(target_os = "android")]
728 println!(
729 "cargo:rustc-link-arg-bin={name}=-Wl,--export-dynamic-symbol-list={}",
730 symbols_path,
731 );
732}