_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            Ok(())
614        });
615        Ok(Some(ret.into()))
616    } else {
617        Ok(None)
618    }
619}
620
621/// clean_mode(name)
622/// Sanitizes the given mode string and returns it, but will exit the process if it is invalid
623#[pyfunction]
624fn clean_mode(name: &str) -> PyResult<String> {
625    let c = origen::clean_mode(name);
626    Ok(c)
627}
628
629#[pyfunction]
630/// target_file(name, dir)
631/// Sanitizes the given target/env name and returns the matching file, but will exit the process
632/// if it does not uniquely identify a single target/env file.
633fn target_file(name: &str, dir: &str) -> PyResult<String> {
634    let c = clean_target!(name, dir, true);
635    Ok(c)
636}
637
638#[pyfunction]
639fn output_directory(py: Python) -> PyResult<PyObject> {
640    let dir = origen::STATUS.output_dir();
641    Ok(pypath!(py, dir.display()))
642}
643
644#[pyfunction]
645fn website_output_directory(py: Python) -> PyResult<PyObject> {
646    let dir = origen::app().unwrap().website_output_directory();
647    Ok(pypath!(py, dir.display()))
648}
649
650#[pyfunction]
651fn website_source_directory(py: Python) -> PyResult<PyObject> {
652    let dir = origen::app().unwrap().website_source_directory();
653    Ok(pypath!(py, dir.display()))
654}
655
656#[pyfunction]
657/// This will be called by Origen immediately before loading a fresh set of targets
658fn prepare_for_target_load() -> PyResult<()> {
659    origen::prepare_for_target_load()?;
660    Ok(())
661}
662
663#[pyfunction]
664/// Clears the current test (pattern) AST and starts a new one
665fn start_new_test(name: Option<String>) -> PyResult<()> {
666    origen::start_new_test(name);
667    Ok(())
668}
669
670pub fn pickle(py: Python, object: &PyAny) -> PyResult<Vec<u8>> {
671    let pickle = PyModule::import(py, "pickle")?;
672    pickle
673        .getattr("dumps")?
674        .call1((object,))?
675        .extract::<Vec<u8>>()
676}
677
678pub fn depickle<'a>(py: Python<'a>, object: &Vec<u8>) -> PyResult<&'a PyAny> {
679    let pickle = PyModule::import(py, "pickle")?;
680    let bytes = PyBytes::new(py, object);
681    pickle.getattr("loads")?.call1((bytes,))
682}
683
684pub fn with_pycallbacks<T, F>(mut func: F) -> PyResult<T>
685where
686    F: FnMut(Python, &PyAny) -> PyResult<T>,
687{
688    Python::with_gil(|py| {
689        let pycallbacks = py.import("origen.callbacks")?;
690        func(py, pycallbacks)
691    })
692}
693
694pub fn get_full_class_name(obj: &PyAny) -> PyResult<String> {
695    let cls = obj.getattr("__class__")?;
696    let mut n = cls.getattr("__module__")?.extract::<String>()?;
697    n.push_str(&format!(
698        ".{}",
699        cls.getattr("__qualname__")?.extract::<String>()?
700    ));
701    Ok(n)
702}
703
704// TODO probably move this to somewhere else
705#[pyfunction]
706pub fn boot_users(py: Python) -> PyResult<pyapi_metal::framework::users::Users> {
707    lazy_static! {
708        static ref BASE_MSG: &'static str = "Encountered an error when initializing users";
709    }
710
711    log_trace!("Initializing Users...");
712    if let Some(r) = &ORIGEN_CONFIG.session__user_root {
713        log_trace!("Setting user session root to {}", r);
714        let mut users = om::users_mut();
715        let sc = users.default_session_config_mut();
716        sc.root = Some(PathBuf::from(r));
717    }
718
719    let users = pyapi_metal::framework::users::users()?;
720
721    if let Some(pw_cache_option) = &crate::ORIGEN_CONFIG.user__password_cache_option {
722        match users.set_default_password_cache_option(Some(pw_cache_option)) {
723            Ok(_) => {}
724            Err(e) => {
725                om::log_error!(
726                    "{}: Error encountered updating default password cache option",
727                    *BASE_MSG
728                );
729                om::log_error!("{}", e);
730            }
731        }
732    }
733
734    if let Some(dsets) = &crate::ORIGEN_CONFIG.user__datasets {
735        let mut replace_default = true;
736        for (dn, config) in dsets {
737            log_trace!("Adding user dataset {}", dn);
738            match config.try_into() {
739                Ok(om_config) => {
740                    match pyapi_metal::framework::users::UserDatasetConfig::new_py(py, om_config) {
741                        Ok(py_config) => {
742                            if replace_default {
743                                match users.override_default_dataset(
744                                    dn,
745                                    Some(py_config.into_py(py).as_ref(py)),
746                                ) {
747                                    Ok(_) => {
748                                        replace_default = false;
749                                    }
750                                    Err(e) => {
751                                        om::log_error!("{}: Error encountered updating default dataset with config '{}'", *BASE_MSG, dn);
752                                        om::log_error!("{}", e);
753                                    }
754                                }
755                            } else {
756                                match users.add_dataset(
757                                    dn,
758                                    Some(
759                                        pyapi_metal::framework::users::UserDatasetConfig::new_py(
760                                            py,
761                                            config.try_into()?,
762                                        )?
763                                        .into_py(py)
764                                        .as_ref(py),
765                                    ),
766                                    false,
767                                ) {
768                                    Ok(_) => {}
769                                    Err(e) => {
770                                        om::log_error!(
771                                            "{}: Error encountered adding dataset '{}'",
772                                            *BASE_MSG,
773                                            dn
774                                        );
775                                        om::log_error!("{}", e);
776                                    }
777                                }
778                            }
779                        }
780                        Err(e) => {
781                            // Still in the "processing stage - just on the python side
782                            om::log_error!(
783                                "{}: Error encountered processing dataset config for '{}'",
784                                *BASE_MSG,
785                                dn
786                            );
787                            om::log_error!("{}", e);
788                        }
789                    }
790                }
791                Err(e) => {
792                    om::log_error!(
793                        "{}: Error encountered processing dataset config for '{}'",
794                        *BASE_MSG,
795                        dn
796                    );
797                    om::log_error!("{}", e);
798                }
799            }
800        }
801    }
802
803    // Set the data lookup hierarchy
804    if let Some(hierarchy) = &crate::ORIGEN_CONFIG.user__data_lookup_hierarchy {
805        match users.apply_data_lookup_hierarchy(hierarchy.to_owned()) {
806            Ok(_) => {}
807            Err(e) => {
808                om::log_error!(
809                    "{}: Error encountered setting the default lookup hierarchy",
810                    *BASE_MSG
811                );
812                om::log_error!("{}", e);
813                om::log_error!("Forcing empty dataset lookup hierarchy...");
814                users.apply_data_lookup_hierarchy(vec![])?;
815            }
816        }
817    } else {
818        if crate::ORIGEN_CONFIG.user__datasets.is_some()
819            && crate::ORIGEN_CONFIG.user__datasets.as_ref().unwrap().len() > 1
820        {
821            // The config can only be read as an unordered hashmap. If multiple datasets are given,
822            // clear the hierarchy if not explicitly given, otherwise will get non-deterministic behavior
823            users.apply_data_lookup_hierarchy(vec![])?;
824        }
825    }
826
827    // Add dataset motives
828    for (m, ds) in &crate::ORIGEN_CONFIG.user__dataset_motives {
829        match users.add_motive(m, ds, false) {
830            Ok(_) => {}
831            Err(e) => {
832                om::log_error!(
833                    "{}: Error encountered adding dataset motive '{}'",
834                    *BASE_MSG,
835                    m
836                );
837                om::log_error!("{}", e);
838            }
839        }
840    }
841
842    macro_rules! log_error__set_field_for_default_user {
843        ($u:expr, $field:tt, $val:expr, $name:expr ) => {
844            paste! {
845                match $u.[<set_ $field>]($val) {
846                    Ok(_) => {},
847                    Err(e) => {
848                        log_error!("{}: Failed to initialize default user '{}'", *BASE_MSG, $name);
849                        log_error!("    Failed to set field '{}'", stringify!($field));
850                        log_error!("{}", e);
851                        log_error!("Bailing on initializing default user '{}'", $name);
852                        continue;
853                    }
854                }
855            }
856        };
857    }
858
859    // Add any default users
860    for (name, config) in &crate::ORIGEN_CONFIG.default_users {
861        match users.add(name, config.auto_populate) {
862            Ok(u) => {
863                if let Some(s) = config.should_validate_passwords {
864                    log_error__set_field_for_default_user!(
865                        u,
866                        should_validate_passwords,
867                        Some(s),
868                        name
869                    )
870                }
871                if let Some(uname) = &config.username {
872                    log_error__set_field_for_default_user!(
873                        u,
874                        username,
875                        Some(uname.to_owned()),
876                        name
877                    );
878                }
879                if let Some(pw) = &config.password {
880                    log_error__set_field_for_default_user!(u, password, Some(pw.to_owned()), name);
881                }
882
883                if let Some(e) = &config.email {
884                    log_error__set_field_for_default_user!(u, email, Some(e.to_owned()), name);
885                }
886                if let Some(f) = &config.first_name {
887                    log_error__set_field_for_default_user!(u, first_name, Some(f.to_owned()), name);
888                }
889                if let Some(l) = &config.last_name {
890                    log_error__set_field_for_default_user!(u, last_name, Some(l.to_owned()), name);
891                }
892            }
893            Err(e) => {
894                om::log_error!(
895                    "{}: Failed to initialize default user '{}'",
896                    *BASE_MSG,
897                    name
898                );
899                log_error!("{}", e);
900            }
901        }
902    }
903
904    // See if the frontend provides a specific means to lookup the current user
905    if let Some(func) = &crate::ORIGEN_CONFIG.user__current_user_lookup_function {
906        users.set_lookup_current_id_function(Some(
907            pyapi_metal::_helpers::get_qualified_attr(&func)?.as_ref(py),
908        ))?;
909    }
910
911    // Initialize the current user
912    if ORIGEN_CONFIG
913        .initial_user
914        .as_ref()
915        .map_or(true, |u| u.initialize.unwrap_or(true))
916    {
917        match users.lookup_current_id(true) {
918            Ok(_) => {
919                if ORIGEN_CONFIG
920                    .initial_user
921                    .as_ref()
922                    .map_or(true, |u| u.init_home_dir.unwrap_or(true))
923                {
924                    match users.current_user() {
925                        Ok(usr) => match usr {
926                            Some(u) => match u.set_home_dir(None) {
927                                Ok(_) => {}
928                                Err(e) => {
929                                    log_error!(
930                                        "{}: Failed to lookup current user's home directory",
931                                        *BASE_MSG
932                                    );
933                                    log_error!("{}", e);
934                                }
935                            },
936                            None => {
937                                log_error!("{}: Failed to lookup current user", *BASE_MSG);
938                            }
939                        },
940                        Err(e) => {
941                            log_error!("{}: Failed to lookup current user", *BASE_MSG);
942                            log_error!("{}", e);
943                        }
944                    }
945                }
946            }
947            Err(e) => {
948                log_error!("{}: Failed to lookup current user", *BASE_MSG);
949                log_error!("{}", e);
950            }
951        }
952    } else {
953        log_trace!("Bypassing current user initialization.");
954    }
955    Ok(users)
956}
957
958#[cfg(test)]
959mod tests {
960    use super::*;
961
962    #[test]
963    fn initializes_module_and_interoperates_with_pin_actions() -> PyResult<()> {
964        Python::with_gil(|py| {
965            let module = PyModule::new(py, "_origen")?;
966            _origen(py, module)?;
967
968            let pin_actions = module
969                .getattr("dut")?
970                .getattr("pins")?
971                .getattr("PinActions")?;
972            let created = pin_actions.call1(("1",))?;
973            let class_method = pin_actions.call_method0("DriveHigh")?;
974
975            assert!(created.eq(class_method)?);
976            let combined = pin_actions.call1((class_method,))?;
977            assert!(combined.eq(created)?);
978            Ok(())
979        })
980    }
981}