_origen/
lib.rs

1#[allow(unused_imports)]
2#[macro_use]
3extern crate origen;
4#[macro_use]
5extern crate origen_metal;
6
7use pyapi_metal;
8
9#[macro_use]
10mod macros;
11
12mod current_command;
13mod dut;
14mod extensions;
15mod file_handler;
16mod infrastructure;
17mod meta;
18mod model;
19#[macro_use]
20mod pins;
21mod registers;
22mod services;
23#[macro_use]
24mod timesets;
25mod _frontend;
26mod _helpers;
27mod application;
28mod producer;
29mod prog_gen;
30mod standard_sub_blocks;
31pub mod tester;
32#[macro_use]
33mod utility;
34mod plugins;
35
36use crate::registers::bit_collection::BitCollection;
37use num_bigint::BigUint;
38use om::lazy_static::lazy_static;
39use origen::core::status::DependencySrc;
40use origen::{clean_target, Dut, Error, Operation, Result, Value, ORIGEN_CONFIG, STATUS, TEST};
41use origen_metal as om;
42use origen_metal::FLOW;
43use paste::paste;
44use pyapi_metal::{pypath, runtime_error};
45use pyo3::prelude::*;
46use pyo3::types::{PyAny, PyBytes, PyDict};
47use pyo3::wrap_pyfunction;
48use std::path::{Path, PathBuf};
49use std::str::FromStr;
50use std::sync::MutexGuard;
51use utility::location::Location;
52
53pub mod built_info {
54    // The file has been placed there by the build script.
55    include!(concat!(env!("OUT_DIR"), "/built.rs"));
56}
57
58#[pymodule]
59/// This is the top-level _origen module which can be imported by Python
60fn _origen(py: Python, m: &PyModule) -> PyResult<()> {
61    m.add_wrapped(wrap_pyfunction!(initialize))?;
62    m.add_wrapped(wrap_pyfunction!(status))?;
63    m.add_wrapped(wrap_pyfunction!(version))?;
64    m.add_wrapped(wrap_pyfunction!(config))?;
65    m.add_wrapped(wrap_pyfunction!(config_metadata))?;
66    m.add_wrapped(wrap_pyfunction!(app_config))?;
67    m.add_wrapped(wrap_pyfunction!(is_app_present))?;
68    m.add_wrapped(wrap_pyfunction!(clean_mode))?;
69    m.add_wrapped(wrap_pyfunction!(target_file))?;
70    m.add_wrapped(wrap_pyfunction!(test))?;
71    m.add_wrapped(wrap_pyfunction!(test_ast))?;
72    m.add_wrapped(wrap_pyfunction!(flow))?;
73    m.add_wrapped(wrap_pyfunction!(flow_ast))?;
74    m.add_wrapped(wrap_pyfunction!(output_directory))?;
75    m.add_wrapped(wrap_pyfunction!(website_output_directory))?;
76    m.add_wrapped(wrap_pyfunction!(website_source_directory))?;
77    m.add_wrapped(wrap_pyfunction!(prepare_for_target_load))?;
78    m.add_wrapped(wrap_pyfunction!(start_new_test))?;
79    m.add_wrapped(wrap_pyfunction!(unhandled_error_count))?;
80    m.add_wrapped(wrap_pyfunction!(set_output_dir))?;
81    m.add_wrapped(wrap_pyfunction!(set_reference_dir))?;
82    m.add_wrapped(wrap_pyfunction!(exit_pass))?;
83    m.add_wrapped(wrap_pyfunction!(exit_fail))?;
84    m.add_wrapped(wrap_pyfunction!(enable_debug))?;
85    m.add_wrapped(wrap_pyfunction!(set_operation))?;
86    m.add_wrapped(wrap_pyfunction!(boot_users))?;
87
88    dut::define(py, m)?;
89    tester::define(py, m)?;
90    application::define(py, m)?;
91    producer::define(py, m)?;
92    services::define(py, m)?;
93    utility::define(py, m)?;
94    standard_sub_blocks::define(py, m)?;
95    prog_gen::define(py, m)?;
96    file_handler::define(py, m)?;
97    plugins::define(py, m)?;
98    extensions::define(py, m)?;
99    current_command::define(py, m)?;
100    infrastructure::define(py, m)?;
101
102    // Compile the _origen_metal library along with this one
103    // to allow re-use from that library
104    pyapi_metal::define(py, m)?;
105    m.setattr(current_command::ATTR_NAME, py.None())?;
106    Ok(())
107}
108
109fn extract_value<'a>(
110    bits_or_val: &PyAny,
111    size: Option<u32>,
112    dut: &'a MutexGuard<Dut>,
113) -> Result<Value<'a>> {
114    let bits = bits_or_val.extract::<PyRef<BitCollection>>();
115    if bits.is_ok() {
116        return Ok(Value::Bits(bits.unwrap().materialize(dut)?, size));
117    }
118    let value = bits_or_val.extract::<BigUint>();
119    if value.is_ok() {
120        return match size {
121            Some(x) => Ok(Value::Data(value.unwrap(), x)),
122            None => Err(Error::new(
123                "A size argument must be supplied along with a data value",
124            )),
125        };
126    }
127    Err(Error::new("Illegal bits/value argument"))
128}
129
130/// Unpacks/extracts common transaction options, updating the transaction directly
131/// Unpacks: addr(u128), overlay (BigUint), overlay_str(String), mask(BigUint),
132fn unpack_transaction_options(
133    trans: &mut origen::Transaction,
134    kwargs: Option<&PyDict>,
135) -> PyResult<()> {
136    if let Some(opts) = kwargs {
137        if let Some(address) = opts.get_item("address")? {
138            trans.address = Some(address.extract::<BigUint>()?);
139        }
140        if let Some(w) = opts.get_item("address_width")? {
141            trans.address_width = Some(w.extract::<usize>()?);
142        }
143        if let Some(_mask) = opts.get_item("mask")? {
144            panic!("option not supported yet!");
145        }
146        if let Some(_overlay) = opts.get_item("overlay")? {
147            panic!("option not supported yet!");
148        }
149        if let Some(_overlay_str) = opts.get_item("overlay_str")? {
150            panic!("option not supported yet!");
151        }
152    }
153    Ok(())
154}
155
156fn unpack_capture_kwargs(
157    dut: &origen::Dut,
158    cap_trans: &mut origen::Capture,
159    kwargs: Option<&PyDict>,
160    pins_allowed: bool,
161    cycles_allowed: bool,
162) -> PyResult<()> {
163    if let Some(opts) = kwargs {
164        if let Some(sym) = opts.get_item("symbol")? {
165            cap_trans.symbol = Some(sym.extract::<String>()?);
166        }
167        if let Some(enables) = opts.get_item("mask")? {
168            cap_trans.enables = Some(enables.extract::<BigUint>()?);
169        }
170        if let Some(cycles) = opts.get_item("cycles")? {
171            if cycles_allowed {
172                cap_trans.cycles = Some(cycles.extract::<usize>()?);
173            } else {
174                return runtime_error!("'cycles' capture option is not valid in this context");
175            }
176        }
177        if let Some(pins) = opts.get_item("pins")? {
178            if pins_allowed {
179                let pins_vec = pins.extract::<Vec<&PyAny>>()?;
180                cap_trans.pin_ids = Some(pins::vec_to_ppin_ids(&dut, pins_vec)?);
181            } else {
182                return runtime_error!("'pins' capture option is not valid in this context");
183            }
184        }
185    }
186    Ok(())
187}
188
189/// Unpacks/extracts common transaction options, updating the transaction directly
190/// Unpacks: addr(u128), overlay (BigUint), overlay_str(String), mask(BigUint),
191fn unpack_transaction_kwargs(trans: &mut origen::Transaction, kwargs: &PyDict) -> PyResult<()> {
192    if let Some(mask) = kwargs.get_item("mask")? {
193        if let Ok(big_mask) = mask.extract::<num_bigint::BigUint>() {
194            trans.bit_enable = big_mask;
195        } else {
196            return crate::type_error!("Could not extract kwarg 'mask' as an integer");
197        }
198    }
199    if let Some(overlay) = kwargs.get_item("overlay")? {
200        let overlay_mask;
201        let overlay_symbol;
202        let overlay_cycles;
203        if let Some(mask) = kwargs.get_item("overlay_mask")? {
204            if let Ok(big_mask) = mask.extract::<num_bigint::BigUint>() {
205                overlay_mask = Some(big_mask);
206            } else {
207                return crate::type_error!("Could not extract kwarg 'overlay_mask' as an integer");
208            }
209        } else {
210            if let Some(ovl) = trans.overlay.as_ref() {
211                overlay_mask = ovl.enables.clone();
212            } else {
213                overlay_mask = None;
214            }
215        }
216        if let Some(s) = kwargs.get_item("overlay_symbol")? {
217            if let Ok(sym) = s.extract::<String>() {
218                overlay_symbol = Some(sym);
219            } else {
220                return crate::type_error!("Could not extract kwarg 'overlay_symbol' as a String");
221            }
222        } else {
223            if let Some(ovl) = trans.overlay.as_ref() {
224                overlay_symbol = ovl.symbol.clone();
225            } else {
226                overlay_symbol = None;
227            }
228        }
229        if let Some(c) = kwargs.get_item("overlay_cycles")? {
230            if let Ok(i) = c.extract::<usize>() {
231                overlay_cycles = Some(i);
232            } else {
233                return crate::type_error!(
234                    "Could not extract kwarg 'overlay_cycles' as an Integer"
235                );
236            }
237        } else {
238            if let Some(ovl) = trans.overlay.as_ref() {
239                overlay_cycles = ovl.cycles.clone();
240            } else {
241                overlay_cycles = None;
242            }
243        }
244        if let Ok(should_overlay) = overlay.extract::<bool>() {
245            if should_overlay {
246                // Unnamed overlay
247                trans.apply_overlay(None, overlay_symbol, overlay_mask)?;
248            }
249        } else if let Ok(overlay_name) = overlay.extract::<String>() {
250            trans.apply_overlay(Some(overlay_name), overlay_symbol, overlay_mask)?;
251            if overlay_cycles.is_some() {
252                trans.overlay.as_mut().unwrap().cycles = overlay_cycles;
253            }
254        } else {
255            return crate::type_error!(
256                "Could not extract kwarg 'overlay' as either a bool or a string"
257            );
258        }
259    }
260    Ok(())
261}
262
263// fn unpack_register_transaction() -> PyResult<Transaction> {
264//     // ...
265// }
266
267fn resolve_transaction(
268    dut: &std::sync::MutexGuard<origen::Dut>,
269    trans: &PyAny,
270    action: Option<origen::TransactionAction>,
271    kwargs: Option<&PyDict>,
272) -> PyResult<origen::Transaction> {
273    let mut width = 32;
274    if let Some(opts) = kwargs {
275        if let Some(w) = opts.get_item("width")? {
276            width = w.extract::<u32>()?;
277        }
278    }
279    let value = extract_value(trans, Some(width), &dut)?;
280    let mut trans;
281    if let Some(a) = action {
282        match a {
283            origen::TransactionAction::Write => trans = value.to_write_transaction(&dut)?,
284            origen::TransactionAction::Verify => trans = value.to_verify_transaction(&dut)?,
285            origen::TransactionAction::Capture => {
286                trans = value.to_capture_transaction(&dut)?;
287                unpack_capture_kwargs(
288                    &dut,
289                    &mut trans.capture.as_mut().unwrap(),
290                    kwargs,
291                    false,
292                    false,
293                )?;
294                return Ok(trans);
295            }
296            _ => {
297                return Err(PyErr::new::<pyo3::exceptions::PyRuntimeError, _>(format!(
298                    "Resolving transactions for {:?} is not supported",
299                    a
300                )))
301            }
302        }
303    } else {
304        trans = value.to_write_transaction(&dut)?;
305        trans.action = None;
306    }
307
308    if let Some(opts) = kwargs {
309        if let Some(address) = opts.get_item("address")? {
310            if !address.is_none() {
311                trans.address = Some(address.extract::<BigUint>()?);
312            }
313        }
314        if let Some(w) = opts.get_item("address_width")? {
315            trans.address_width = Some(w.extract::<usize>()?);
316        }
317        if let Some(_mask) = opts.get_item("mask")? {
318            panic!("option not supported yet!");
319        }
320        if let Some(_overlay) = opts.get_item("overlay")? {
321            panic!("option not supported yet!");
322        }
323        if let Some(_overlay_str) = opts.get_item("overlay_str")? {
324            panic!("option not supported yet!");
325        }
326    }
327    Ok(trans)
328}
329
330/// Exit with a failing status code and print a big FAIL to the console
331#[pyfunction]
332fn exit_fail() -> PyResult<()> {
333    exit_fail!();
334}
335
336/// Exit with a passing status code and print a big PASS to the console
337#[pyfunction]
338fn exit_pass() -> PyResult<()> {
339    exit_pass!();
340}
341
342fn origen_mod_path() -> PyResult<PathBuf> {
343    Python::with_gil(|py| {
344        let locals = PyDict::new(py);
345        locals.set_item("importlib", py.import("importlib")?)?;
346        let p = PathBuf::from(
347            py.eval(
348                "importlib.util.find_spec('_origen').origin",
349                None,
350                Some(&locals),
351            )?
352            .extract::<String>()?,
353        );
354        Ok(p.parent().unwrap().to_path_buf())
355    })
356}
357
358/// Called automatically when Origen is first loaded
359#[pyfunction]
360#[pyo3(signature=(log_verbosity, verbosity_keywords, cli_location, cli_version, fe_pkg_loc, fe_exe_loc, invocation))]
361fn initialize(
362    py: Python,
363    log_verbosity: Option<u8>,
364    verbosity_keywords: Vec<String>,
365    cli_location: Option<String>,
366    cli_version: Option<String>,
367    fe_pkg_loc: Option<PathBuf>,
368    fe_exe_loc: Option<PathBuf>,
369    invocation: Option<(String, Option<PathBuf>)>,
370) -> PyResult<()> {
371    origen::initialize(
372        log_verbosity,
373        verbosity_keywords,
374        cli_location,
375        cli_version,
376        fe_pkg_loc,
377        fe_exe_loc,
378    );
379    origen::STATUS.update_other_build_info("pyapi_version", built_info::PKG_VERSION)?;
380    if let Some(invoc) = invocation {
381        match DependencySrc::try_from(invoc) {
382            Ok(d) => origen::STATUS.set_dependency_src(Some(d)),
383            Err(e) => log_error!("{}", e.to_string()),
384        }
385    }
386    origen::FRONTEND
387        .write()
388        .unwrap()
389        .set_frontend(Box::new(_frontend::Frontend::new()))?;
390
391    if let Some(app) = &STATUS.app {
392        origen::STATUS.set_in_origen_core_app(origen_mod_path()? == app.root);
393    } else {
394        origen::STATUS.set_in_origen_core_app(false);
395    }
396
397    use crate::pyapi_metal::prelude::frontend::*;
398    with_mut_py_data_stores(|py, mut py_ds| {
399        // TODO remove some of this hardcoding
400        let pymod = PyModule::import(py, "_origen")?;
401        py_ds.add_category(
402            py,
403            "ldaps",
404            Some(pymod.getattr("utility")?.getattr("boot_ldaps")?.into()),
405            Some(true),
406        )?;
407        Ok(())
408    })?;
409    boot_users(py)?;
410    match origen::setup_sessions() {
411        Ok(_) => {}
412        Err(e) => log_error!(
413            "Failed to setup user and application sessions. Received error: \n{}",
414            e
415        ),
416    }
417    Ok(())
418}
419
420#[pyfunction]
421/// Set the output directory to be used instead of <APP ROOT>/output
422fn set_output_dir(dir: &str) -> PyResult<()> {
423    STATUS.set_output_dir(Path::new(dir));
424    Ok(())
425}
426
427#[pyfunction]
428/// Set the output directory to be used instead of <APP ROOT>/output
429fn set_reference_dir(dir: &str) -> PyResult<()> {
430    STATUS.set_reference_dir(Path::new(dir));
431    Ok(())
432}
433
434#[pyfunction]
435/// Enable Python source line tracking
436fn enable_debug() -> PyResult<()> {
437    STATUS.set_debug_enabled(true);
438    Ok(())
439}
440
441#[pyfunction]
442/// Set the current Origen operation (generate, compile, etc.)
443fn set_operation(name: String) -> PyResult<()> {
444    match Operation::from_str(&name) {
445        Ok(op) => {
446            STATUS.set_operation(op);
447            Ok(())
448        }
449        Err(e) => Err(PyErr::from(Error::new(&e))),
450    }
451}
452
453#[pyfunction]
454/// Returns the number of unhandled errors that have been encountered since the Origen
455/// invocation started.
456/// An unhandled error is something that ultimately resulted in a pattern not being generated
457/// or something equally serious.
458fn unhandled_error_count() -> PyResult<usize> {
459    Ok(STATUS.unhandled_error_count())
460}
461
462/// Prints out the AST for the current test to the console
463#[pyfunction]
464fn test() -> PyResult<()> {
465    println!("{}", TEST.to_string());
466    Ok(())
467}
468
469/// Returns the AST for the current test in Python
470#[pyfunction]
471fn test_ast() -> PyResult<Vec<u8>> {
472    Ok(TEST.to_pickle())
473}
474
475/// Prints out the AST for the current flow to the console
476#[pyfunction]
477fn flow() -> PyResult<()> {
478    println!("{}", FLOW.to_string());
479    Ok(())
480}
481
482/// Returns the AST for the current flow in Python
483#[pyfunction]
484fn flow_ast() -> PyResult<Vec<u8>> {
485    Ok(FLOW.to_pickle())
486}
487
488/// Returns the Origen status which informs whether an app is present, the Origen version,
489/// etc.
490#[pyfunction]
491fn status(py: Python) -> PyResult<PyObject> {
492    let ret = PyDict::new(py);
493    // Don't think an error can really happen here, so not handled
494    let _ = ret.set_item("is_app_present", &STATUS.is_app_present);
495    if let Some(app) = origen::app() {
496        let _ = ret.set_item("root", format!("{}", app.root.display()));
497    }
498    let _ = ret.set_item("origen_version", &STATUS.origen_version.to_string());
499    let _ = ret.set_item("home", format!("{}", STATUS.home.display()));
500    let _ = ret.set_item("on_windows", om::running_on_windows());
501    ret.set_item(
502        "origen_core_support_version",
503        STATUS.origen_core_support_version.to_string(),
504    )?;
505    ret.set_item(
506        "origen_metal_backend_version",
507        STATUS.origen_metal_backend_version.to_string(),
508    )?;
509    ret.set_item(
510        "other_build_info",
511        pyapi_metal::_helpers::map_to_pydict(py, &mut STATUS.other_build_info().iter())?,
512    )?;
513    ret.set_item(
514        "cli_version",
515        match STATUS.cli_version() {
516            Some(v) => Some(v.to_string()).to_object(py),
517            None => py.None(),
518        },
519    )?;
520    ret.set_item(
521        "cli_location",
522        match STATUS.cli_location() {
523            Some(path) => pypath!(py, path.display()),
524            None => py.None(),
525        },
526    )?;
527    ret.set_item(
528        "is_app_in_origen_dev_mode",
529        STATUS.is_app_in_origen_dev_mode,
530    )?;
531    ret.set_item("in_origen_core_app", STATUS.in_origen_core_app())?;
532
533    // Invocation details
534    infrastructure::pyproject::populate_status(py, ret)?;
535    Ok(ret.into())
536}
537
538/// Returns the Origen version formatted into PEP440, e.g. "1.2.3.dev4"
539#[pyfunction]
540fn version() -> PyResult<String> {
541    Ok(
542        origen_metal::utils::version::Version::new_pep440(&STATUS.origen_version.to_string())?
543            .to_string(),
544    )
545}
546
547/// Returns the Origen configuration (as defined in origen.toml files)
548#[pyfunction]
549fn config(py: Python) -> PyResult<PyObject> {
550    let ret = PyDict::new(py);
551    // Don't think an error can really happen here, so not handled
552    let _ = ret.set_item("python_cmd", &ORIGEN_CONFIG.python_cmd);
553    let _ = ret.set_item("pkg_server", &ORIGEN_CONFIG.pkg_server);
554    let _ = ret.set_item("pkg_server_push", &ORIGEN_CONFIG.pkg_server_push);
555    let _ = ret.set_item("pkg_server_pull", &ORIGEN_CONFIG.pkg_server_pull);
556    let _ = ret.set_item("some_val", &ORIGEN_CONFIG.some_val);
557    Ok(ret.into())
558}
559
560#[pyfunction]
561fn config_metadata<'py>(py: Python<'py>) -> PyResult<&'py PyDict> {
562    let m = origen::origen_config_metadata();
563    let retn = PyDict::new(py);
564    retn.set_item(
565        "files",
566        m.files
567            .iter()
568            .map(|p| Ok(pypath!(py, p.display())))
569            .collect::<PyResult<Vec<PyObject>>>()?,
570    )?;
571    Ok(retn)
572}
573
574#[pyfunction]
575fn is_app_present() -> PyResult<bool> {
576    Ok(STATUS.is_app_present)
577}
578
579/// Returns the Origen application configuration (as defined in application.toml)
580#[pyfunction]
581fn app_config(py: Python) -> PyResult<Option<PyObject>> {
582    if let Some(app) = origen::app() {
583        let ret = PyDict::new(py);
584        let _ = app.with_config(|config| {
585            let _ = ret.set_item("name", &config.name);
586            let _ = ret.set_item("target", &config.target);
587            let _ = ret.set_item("mode", &config.mode);
588            let _ = ret.set_item("__output_directory__", &config.output_directory);
589            let _ = ret.set_item(
590                "__website_output_directory__",
591                &config.website_output_directory,
592            );
593            let _ = ret.set_item(
594                "__website_source_directory__",
595                &config.website_source_directory,
596            );
597            let _ = ret.set_item(
598                "website_release_location",
599                match &config.website_release_location {
600                    Some(loc) => Py::new(
601                        py,
602                        Location {
603                            location: (*loc).clone(),
604                        },
605                    )
606                    .unwrap()
607                    .to_object(py),
608                    None => py.None(),
609                },
610            );
611            let _ = ret.set_item("website_release_name", &config.website_release_name);
612            let _ = ret.set_item("release", &config.release);
613            let program_generation = PyDict::new(py);
614            let _ = program_generation.set_item(
615                "flow_visualization",
616                config.program_generation.flow_visualization,
617            );
618            let _ = ret.set_item("program_generation", program_generation);
619            Ok(())
620        });
621        Ok(Some(ret.into()))
622    } else {
623        Ok(None)
624    }
625}
626
627/// clean_mode(name)
628/// Sanitizes the given mode string and returns it, but will exit the process if it is invalid
629#[pyfunction]
630fn clean_mode(name: &str) -> PyResult<String> {
631    let c = origen::clean_mode(name);
632    Ok(c)
633}
634
635#[pyfunction]
636/// target_file(name, dir)
637/// Sanitizes the given target/env name and returns the matching file, but will exit the process
638/// if it does not uniquely identify a single target/env file.
639fn target_file(name: &str, dir: &str) -> PyResult<String> {
640    let c = clean_target!(name, dir, true);
641    Ok(c)
642}
643
644#[pyfunction]
645fn output_directory(py: Python) -> PyResult<PyObject> {
646    let dir = origen::STATUS.output_dir();
647    Ok(pypath!(py, dir.display()))
648}
649
650#[pyfunction]
651fn website_output_directory(py: Python) -> PyResult<PyObject> {
652    let dir = origen::app().unwrap().website_output_directory();
653    Ok(pypath!(py, dir.display()))
654}
655
656#[pyfunction]
657fn website_source_directory(py: Python) -> PyResult<PyObject> {
658    let dir = origen::app().unwrap().website_source_directory();
659    Ok(pypath!(py, dir.display()))
660}
661
662#[pyfunction]
663/// This will be called by Origen immediately before loading a fresh set of targets
664fn prepare_for_target_load() -> PyResult<()> {
665    origen::prepare_for_target_load()?;
666    Ok(())
667}
668
669#[pyfunction]
670/// Clears the current test (pattern) AST and starts a new one
671fn start_new_test(name: Option<String>) -> PyResult<()> {
672    origen::start_new_test(name);
673    Ok(())
674}
675
676pub fn pickle(py: Python, object: &PyAny) -> PyResult<Vec<u8>> {
677    let pickle = PyModule::import(py, "pickle")?;
678    pickle
679        .getattr("dumps")?
680        .call1((object,))?
681        .extract::<Vec<u8>>()
682}
683
684pub fn depickle<'a>(py: Python<'a>, object: &Vec<u8>) -> PyResult<&'a PyAny> {
685    let pickle = PyModule::import(py, "pickle")?;
686    let bytes = PyBytes::new(py, object);
687    pickle.getattr("loads")?.call1((bytes,))
688}
689
690pub fn with_pycallbacks<T, F>(mut func: F) -> PyResult<T>
691where
692    F: FnMut(Python, &PyAny) -> PyResult<T>,
693{
694    Python::with_gil(|py| {
695        let pycallbacks = py.import("origen.callbacks")?;
696        func(py, pycallbacks)
697    })
698}
699
700pub fn get_full_class_name(obj: &PyAny) -> PyResult<String> {
701    let cls = obj.getattr("__class__")?;
702    let mut n = cls.getattr("__module__")?.extract::<String>()?;
703    n.push_str(&format!(
704        ".{}",
705        cls.getattr("__qualname__")?.extract::<String>()?
706    ));
707    Ok(n)
708}
709
710// TODO probably move this to somewhere else
711#[pyfunction]
712pub fn boot_users(py: Python) -> PyResult<pyapi_metal::framework::users::Users> {
713    lazy_static! {
714        static ref BASE_MSG: &'static str = "Encountered an error when initializing users";
715    }
716
717    log_trace!("Initializing Users...");
718    if let Some(r) = &ORIGEN_CONFIG.session__user_root {
719        log_trace!("Setting user session root to {}", r);
720        let mut users = om::users_mut();
721        let sc = users.default_session_config_mut();
722        sc.root = Some(PathBuf::from(r));
723    }
724
725    let users = pyapi_metal::framework::users::users()?;
726
727    if let Some(pw_cache_option) = &crate::ORIGEN_CONFIG.user__password_cache_option {
728        match users.set_default_password_cache_option(Some(pw_cache_option)) {
729            Ok(_) => {}
730            Err(e) => {
731                om::log_error!(
732                    "{}: Error encountered updating default password cache option",
733                    *BASE_MSG
734                );
735                om::log_error!("{}", e);
736            }
737        }
738    }
739
740    if let Some(dsets) = &crate::ORIGEN_CONFIG.user__datasets {
741        let mut replace_default = true;
742        for (dn, config) in dsets {
743            log_trace!("Adding user dataset {}", dn);
744            match config.try_into() {
745                Ok(om_config) => {
746                    match pyapi_metal::framework::users::UserDatasetConfig::new_py(py, om_config) {
747                        Ok(py_config) => {
748                            if replace_default {
749                                match users.override_default_dataset(
750                                    dn,
751                                    Some(py_config.into_py(py).as_ref(py)),
752                                ) {
753                                    Ok(_) => {
754                                        replace_default = false;
755                                    }
756                                    Err(e) => {
757                                        om::log_error!("{}: Error encountered updating default dataset with config '{}'", *BASE_MSG, dn);
758                                        om::log_error!("{}", e);
759                                    }
760                                }
761                            } else {
762                                match users.add_dataset(
763                                    dn,
764                                    Some(
765                                        pyapi_metal::framework::users::UserDatasetConfig::new_py(
766                                            py,
767                                            config.try_into()?,
768                                        )?
769                                        .into_py(py)
770                                        .as_ref(py),
771                                    ),
772                                    false,
773                                ) {
774                                    Ok(_) => {}
775                                    Err(e) => {
776                                        om::log_error!(
777                                            "{}: Error encountered adding dataset '{}'",
778                                            *BASE_MSG,
779                                            dn
780                                        );
781                                        om::log_error!("{}", e);
782                                    }
783                                }
784                            }
785                        }
786                        Err(e) => {
787                            // Still in the "processing stage - just on the python side
788                            om::log_error!(
789                                "{}: Error encountered processing dataset config for '{}'",
790                                *BASE_MSG,
791                                dn
792                            );
793                            om::log_error!("{}", e);
794                        }
795                    }
796                }
797                Err(e) => {
798                    om::log_error!(
799                        "{}: Error encountered processing dataset config for '{}'",
800                        *BASE_MSG,
801                        dn
802                    );
803                    om::log_error!("{}", e);
804                }
805            }
806        }
807    }
808
809    // Set the data lookup hierarchy
810    if let Some(hierarchy) = &crate::ORIGEN_CONFIG.user__data_lookup_hierarchy {
811        match users.apply_data_lookup_hierarchy(hierarchy.to_owned()) {
812            Ok(_) => {}
813            Err(e) => {
814                om::log_error!(
815                    "{}: Error encountered setting the default lookup hierarchy",
816                    *BASE_MSG
817                );
818                om::log_error!("{}", e);
819                om::log_error!("Forcing empty dataset lookup hierarchy...");
820                users.apply_data_lookup_hierarchy(vec![])?;
821            }
822        }
823    } else {
824        if crate::ORIGEN_CONFIG.user__datasets.is_some()
825            && crate::ORIGEN_CONFIG.user__datasets.as_ref().unwrap().len() > 1
826        {
827            // The config can only be read as an unordered hashmap. If multiple datasets are given,
828            // clear the hierarchy if not explicitly given, otherwise will get non-deterministic behavior
829            users.apply_data_lookup_hierarchy(vec![])?;
830        }
831    }
832
833    // Add dataset motives
834    for (m, ds) in &crate::ORIGEN_CONFIG.user__dataset_motives {
835        match users.add_motive(m, ds, false) {
836            Ok(_) => {}
837            Err(e) => {
838                om::log_error!(
839                    "{}: Error encountered adding dataset motive '{}'",
840                    *BASE_MSG,
841                    m
842                );
843                om::log_error!("{}", e);
844            }
845        }
846    }
847
848    macro_rules! log_error__set_field_for_default_user {
849        ($u:expr, $field:tt, $val:expr, $name:expr ) => {
850            paste! {
851                match $u.[<set_ $field>]($val) {
852                    Ok(_) => {},
853                    Err(e) => {
854                        log_error!("{}: Failed to initialize default user '{}'", *BASE_MSG, $name);
855                        log_error!("    Failed to set field '{}'", stringify!($field));
856                        log_error!("{}", e);
857                        log_error!("Bailing on initializing default user '{}'", $name);
858                        continue;
859                    }
860                }
861            }
862        };
863    }
864
865    // Add any default users
866    for (name, config) in &crate::ORIGEN_CONFIG.default_users {
867        match users.add(name, config.auto_populate) {
868            Ok(u) => {
869                if let Some(s) = config.should_validate_passwords {
870                    log_error__set_field_for_default_user!(
871                        u,
872                        should_validate_passwords,
873                        Some(s),
874                        name
875                    )
876                }
877                if let Some(uname) = &config.username {
878                    log_error__set_field_for_default_user!(
879                        u,
880                        username,
881                        Some(uname.to_owned()),
882                        name
883                    );
884                }
885                if let Some(pw) = &config.password {
886                    log_error__set_field_for_default_user!(u, password, Some(pw.to_owned()), name);
887                }
888
889                if let Some(e) = &config.email {
890                    log_error__set_field_for_default_user!(u, email, Some(e.to_owned()), name);
891                }
892                if let Some(f) = &config.first_name {
893                    log_error__set_field_for_default_user!(u, first_name, Some(f.to_owned()), name);
894                }
895                if let Some(l) = &config.last_name {
896                    log_error__set_field_for_default_user!(u, last_name, Some(l.to_owned()), name);
897                }
898            }
899            Err(e) => {
900                om::log_error!(
901                    "{}: Failed to initialize default user '{}'",
902                    *BASE_MSG,
903                    name
904                );
905                log_error!("{}", e);
906            }
907        }
908    }
909
910    // See if the frontend provides a specific means to lookup the current user
911    if let Some(func) = &crate::ORIGEN_CONFIG.user__current_user_lookup_function {
912        users.set_lookup_current_id_function(Some(
913            pyapi_metal::_helpers::get_qualified_attr(&func)?.as_ref(py),
914        ))?;
915    }
916
917    // Initialize the current user
918    if ORIGEN_CONFIG
919        .initial_user
920        .as_ref()
921        .map_or(true, |u| u.initialize.unwrap_or(true))
922    {
923        match users.lookup_current_id(true) {
924            Ok(_) => {
925                if ORIGEN_CONFIG
926                    .initial_user
927                    .as_ref()
928                    .map_or(true, |u| u.init_home_dir.unwrap_or(true))
929                {
930                    match users.current_user() {
931                        Ok(usr) => match usr {
932                            Some(u) => match u.set_home_dir(None) {
933                                Ok(_) => {}
934                                Err(e) => {
935                                    log_error!(
936                                        "{}: Failed to lookup current user's home directory",
937                                        *BASE_MSG
938                                    );
939                                    log_error!("{}", e);
940                                }
941                            },
942                            None => {
943                                log_error!("{}: Failed to lookup current user", *BASE_MSG);
944                            }
945                        },
946                        Err(e) => {
947                            log_error!("{}: Failed to lookup current user", *BASE_MSG);
948                            log_error!("{}", e);
949                        }
950                    }
951                }
952            }
953            Err(e) => {
954                log_error!("{}: Failed to lookup current user", *BASE_MSG);
955                log_error!("{}", e);
956            }
957        }
958    } else {
959        log_trace!("Bypassing current user initialization.");
960    }
961    Ok(users)
962}
963
964#[cfg(test)]
965mod tests {
966    use super::*;
967
968    #[test]
969    fn initializes_module_and_interoperates_with_pin_actions() -> PyResult<()> {
970        Python::with_gil(|py| {
971            let module = PyModule::new(py, "_origen")?;
972            _origen(py, module)?;
973
974            let pin_actions = module
975                .getattr("dut")?
976                .getattr("pins")?
977                .getattr("PinActions")?;
978            let created = pin_actions.call1(("1",))?;
979            let class_method = pin_actions.call_method0("DriveHigh")?;
980
981            assert!(created.eq(class_method)?);
982            let combined = pin_actions.call1((class_method,))?;
983            assert!(combined.eq(created)?);
984            Ok(())
985        })
986    }
987}