|
38 | 38 | //!
|
39 | 39 | //! [1]: https://docs.rs/inline-python/#syntax-issues
|
40 | 40 |
|
| 41 | +#![feature(proc_macro_span)] |
| 42 | + |
| 43 | +use proc_macro::{Span, TokenStream}; |
| 44 | +use pyo3::{PyObject, PyResult, Python, prelude::*}; |
| 45 | +use std::{ffi::CString, ptr::null_mut, str::FromStr}; |
| 46 | + |
| 47 | +mod shared; |
| 48 | +use shared::*; |
| 49 | + |
41 | 50 | /// A block of compile-time executed Rust code generating Python code.
|
42 | 51 | ///
|
43 | 52 | /// See [the crate's module level documentation](index.html) for examples.
|
44 |
| -pub use inline_python_macros::ct_python; |
| 53 | +#[proc_macro] |
| 54 | +pub fn ct_python(input: TokenStream) -> TokenStream { |
| 55 | + ct_python_impl(input).unwrap_or_else(|e| e) |
| 56 | +} |
| 57 | + |
| 58 | +fn ct_python_impl(input: TokenStream) -> Result<TokenStream, TokenStream> { |
| 59 | + let python = CString::new(python_from_macro(input.clone(), None)?).unwrap(); |
| 60 | + let filename = CString::new(Span::call_site().file()).unwrap(); |
| 61 | + |
| 62 | + Python::with_gil(|py| { |
| 63 | + let code = compile_python(py, &python, &filename, input.clone())?; |
| 64 | + let output = run_and_capture(py, code).map_err(|err| python_error_to_compile_error(py, err, input))?; |
| 65 | + TokenStream::from_str(&output).map_err(|_| compile_error(None, "produced invalid Rust code")) |
| 66 | + }) |
| 67 | +} |
| 68 | + |
| 69 | +fn run_and_capture(py: Python, code: PyObject) -> PyResult<String> { |
| 70 | + #[cfg(unix)] |
| 71 | + let _ = ensure_libpython_symbols_loaded(py); |
| 72 | + |
| 73 | + let globals = py.import("__main__")?.dict().copy()?; |
| 74 | + |
| 75 | + let sys = py.import("sys")?; |
| 76 | + let stdout = py.import("io")?.getattr("StringIO")?.call0()?; |
| 77 | + let original_stdout = sys.dict().get_item("stdout")?; |
| 78 | + sys.dict().set_item("stdout", &stdout)?; |
| 79 | + |
| 80 | + let result = unsafe { |
| 81 | + let ptr = pyo3::ffi::PyEval_EvalCode(code.as_ptr(), globals.as_ptr(), null_mut()); |
| 82 | + PyObject::from_owned_ptr_or_err(py, ptr) |
| 83 | + }; |
| 84 | + |
| 85 | + sys.dict().set_item("stdout", original_stdout)?; |
| 86 | + |
| 87 | + result?; |
| 88 | + |
| 89 | + stdout.call_method0("getvalue")?.extract() |
| 90 | +} |
| 91 | + |
| 92 | +#[cfg(unix)] |
| 93 | +fn ensure_libpython_symbols_loaded(py: Python) -> PyResult<()> { |
| 94 | + // On Unix, Rustc loads proc-macro crates with RTLD_LOCAL, which (at least |
| 95 | + // on Linux) means all their dependencies (in our case: libpython) don't |
| 96 | + // get their symbols made available globally either. This means that |
| 97 | + // loading modules (e.g. `import math`) will fail, as those modules refer |
| 98 | + // back to symbols of libpython. |
| 99 | + // |
| 100 | + // This function tries to (re)load the right version of libpython, but this |
| 101 | + // time with RTLD_GLOBAL enabled. |
| 102 | + let sysconfig = py.import("sysconfig")?; |
| 103 | + let libdir: String = sysconfig.getattr("get_config_var")?.call1(("LIBDIR",))?.extract()?; |
| 104 | + let so_name: String = sysconfig.getattr("get_config_var")?.call1(("INSTSONAME",))?.extract()?; |
| 105 | + let path = CString::new(format!("{libdir}/{so_name}")).unwrap(); |
| 106 | + unsafe { libc::dlopen(path.as_ptr(), libc::RTLD_NOW | libc::RTLD_GLOBAL) }; |
| 107 | + Ok(()) |
| 108 | +} |
0 commit comments