rustpython/
lib.rs

1//! This is the `rustpython` binary. If you're looking to embed RustPython into your application,
2//! you're likely looking for the [`rustpython-vm`](https://docs.rs/rustpython-vm) crate.
3//!
4//! You can install `rustpython` with `cargo install rustpython`, or if you'd like to inject your
5//! own native modules you can make a binary crate that depends on the `rustpython` crate (and
6//! probably `rustpython-vm`, too), and make a `main.rs` that looks like:
7//!
8//! ```no_run
9//! use rustpython_vm::{pymodule, py_freeze};
10//! fn main() {
11//!     rustpython::run(|vm| {
12//!         vm.add_native_module("mymod".to_owned(), Box::new(mymod::make_module));
13//!         vm.add_frozen(py_freeze!(source = "def foo(): pass", module_name = "otherthing"));
14//!     });
15//! }
16//!
17//! #[pymodule]
18//! mod mymod {
19//!     use rustpython_vm::builtins::PyStrRef;
20//TODO: use rustpython_vm::prelude::*;
21//!
22//!     #[pyfunction]
23//!     fn do_thing(x: i32) -> i32 {
24//!         x + 1
25//!     }
26//!
27//!     #[pyfunction]
28//!     fn other_thing(s: PyStrRef) -> (String, usize) {
29//!         let new_string = format!("hello from rust, {}!", s);
30//!         let prev_len = s.as_str().len();
31//!         (new_string, prev_len)
32//!     }
33//! }
34//! ```
35//!
36//! The binary will have all the standard arguments of a python interpreter (including a REPL!) but
37//! it will have your modules loaded into the vm.
38#![allow(clippy::needless_doctest_main)]
39
40#[macro_use]
41extern crate clap;
42extern crate env_logger;
43#[macro_use]
44extern crate log;
45
46#[cfg(feature = "flame-it")]
47use vm::Settings;
48
49mod interpreter;
50mod settings;
51mod shell;
52
53use atty::Stream;
54use rustpython_vm::{scope::Scope, PyResult, VirtualMachine};
55use std::{env, process::ExitCode};
56
57pub use interpreter::InterpreterConfig;
58pub use rustpython_vm as vm;
59pub use settings::{opts_with_clap, RunMode};
60
61/// The main cli of the `rustpython` interpreter. This function will return `std::process::ExitCode`
62/// based on the return code of the python code ran through the cli.
63pub fn run(init: impl FnOnce(&mut VirtualMachine) + 'static) -> ExitCode {
64    env_logger::init();
65
66    // NOTE: This is not a WASI convention. But it will be convenient since POSIX shell always defines it.
67    #[cfg(target_os = "wasi")]
68    {
69        if let Ok(pwd) = env::var("PWD") {
70            let _ = env::set_current_dir(pwd);
71        };
72    }
73
74    let (settings, run_mode) = opts_with_clap();
75
76    // Be quiet if "quiet" arg is set OR stdin is not connected to a terminal
77    let quiet_var = settings.quiet || !atty::is(Stream::Stdin);
78
79    // don't translate newlines (\r\n <=> \n)
80    #[cfg(windows)]
81    {
82        extern "C" {
83            fn _setmode(fd: i32, flags: i32) -> i32;
84        }
85        unsafe {
86            _setmode(0, libc::O_BINARY);
87            _setmode(1, libc::O_BINARY);
88            _setmode(2, libc::O_BINARY);
89        }
90    }
91
92    let mut config = InterpreterConfig::new().settings(settings);
93    #[cfg(feature = "stdlib")]
94    {
95        config = config.init_stdlib();
96    }
97    config = config.init_hook(Box::new(init));
98
99    let interp = config.interpreter();
100    let exitcode = interp.run(move |vm| run_rustpython(vm, run_mode, quiet_var));
101
102    ExitCode::from(exitcode)
103}
104
105fn setup_main_module(vm: &VirtualMachine) -> PyResult<Scope> {
106    let scope = vm.new_scope_with_builtins();
107    let main_module = vm.new_module("__main__", scope.globals.clone(), None);
108    main_module
109        .dict()
110        .set_item("__annotations__", vm.ctx.new_dict().into(), vm)
111        .expect("Failed to initialize __main__.__annotations__");
112
113    vm.sys_module
114        .get_attr("modules", vm)?
115        .set_item("__main__", main_module.into(), vm)?;
116
117    Ok(scope)
118}
119
120#[cfg(feature = "ssl")]
121fn get_pip(scope: Scope, vm: &VirtualMachine) -> PyResult<()> {
122    let get_getpip = rustpython_vm::py_compile!(
123        source = r#"\
124__import__("io").TextIOWrapper(
125    __import__("urllib.request").request.urlopen("https://bootstrap.pypa.io/get-pip.py")
126).read()
127"#,
128        mode = "eval"
129    );
130    eprintln!("downloading get-pip.py...");
131    let getpip_code = vm.run_code_obj(vm.ctx.new_code(get_getpip), scope.clone())?;
132    let getpip_code: rustpython_vm::builtins::PyStrRef = getpip_code
133        .downcast()
134        .expect("TextIOWrapper.read() should return str");
135    eprintln!("running get-pip.py...");
136    vm.run_code_string(scope, getpip_code.as_str(), "get-pip.py".to_owned())?;
137    Ok(())
138}
139
140#[cfg(feature = "ssl")]
141fn ensurepip(_: Scope, vm: &VirtualMachine) -> PyResult<()> {
142    vm.run_module("ensurepip")
143}
144
145fn install_pip(_installer: &str, _scope: Scope, vm: &VirtualMachine) -> PyResult<()> {
146    #[cfg(feature = "ssl")]
147    {
148        match _installer {
149            "ensurepip" => ensurepip(_scope, vm),
150            "get-pip" => get_pip(_scope, vm),
151            _ => unreachable!(),
152        }
153    }
154
155    #[cfg(not(feature = "ssl"))]
156    Err(vm.new_exception_msg(
157        vm.ctx.exceptions.system_error.to_owned(),
158        "install-pip requires rustpython be build with '--features=ssl'".to_owned(),
159    ))
160}
161
162fn run_rustpython(vm: &VirtualMachine, run_mode: RunMode, quiet: bool) -> PyResult<()> {
163    #[cfg(feature = "flame-it")]
164    let main_guard = flame::start_guard("RustPython main");
165
166    let scope = setup_main_module(vm)?;
167
168    if !vm.state.settings.safe_path {
169        // TODO: The prepending path depends on running mode
170        // See https://docs.python.org/3/using/cmdline.html#cmdoption-P
171        vm.run_code_string(
172            vm.new_scope_with_builtins(),
173            "import sys; sys.path.insert(0, '')",
174            "<embedded>".to_owned(),
175        )?;
176    }
177
178    let site_result = vm.import("site", 0);
179    if site_result.is_err() {
180        warn!(
181            "Failed to import site, consider adding the Lib directory to your RUSTPYTHONPATH \
182             environment variable",
183        );
184    }
185
186    match run_mode {
187        RunMode::Command(command) => {
188            debug!("Running command {}", command);
189            vm.run_code_string(scope, &command, "<stdin>".to_owned())?;
190        }
191        RunMode::Module(module) => {
192            debug!("Running module {}", module);
193            vm.run_module(&module)?;
194        }
195        RunMode::InstallPip(installer) => {
196            install_pip(&installer, scope, vm)?;
197        }
198        RunMode::ScriptInteractive(script, interactive) => {
199            if let Some(script) = script {
200                debug!("Running script {}", &script);
201                vm.run_script(scope.clone(), &script)?;
202            } else if !quiet {
203                println!(
204                    "Welcome to the magnificent Rust Python {} interpreter \u{1f631} \u{1f596}",
205                    crate_version!()
206                );
207            }
208            if interactive {
209                shell::run_shell(vm, scope)?;
210            }
211        }
212    }
213    #[cfg(feature = "flame-it")]
214    {
215        main_guard.end();
216        if let Err(e) = write_profile(&vm.state.as_ref().settings) {
217            error!("Error writing profile information: {}", e);
218        }
219    }
220    Ok(())
221}
222
223#[cfg(feature = "flame-it")]
224fn write_profile(settings: &Settings) -> Result<(), Box<dyn std::error::Error>> {
225    use std::{fs, io};
226
227    enum ProfileFormat {
228        Html,
229        Text,
230        Speedscope,
231    }
232    let profile_output = settings.profile_output.as_deref();
233    let profile_format = match settings.profile_format.as_deref() {
234        Some("html") => ProfileFormat::Html,
235        Some("text") => ProfileFormat::Text,
236        None if profile_output == Some("-".as_ref()) => ProfileFormat::Text,
237        Some("speedscope") | None => ProfileFormat::Speedscope,
238        Some(other) => {
239            error!("Unknown profile format {}", other);
240            // TODO: Need to change to ExitCode or Termination
241            std::process::exit(1);
242        }
243    };
244
245    let profile_output = profile_output.unwrap_or_else(|| match profile_format {
246        ProfileFormat::Html => "flame-graph.html".as_ref(),
247        ProfileFormat::Text => "flame.txt".as_ref(),
248        ProfileFormat::Speedscope => "flamescope.json".as_ref(),
249    });
250
251    let profile_output: Box<dyn io::Write> = if profile_output == "-" {
252        Box::new(io::stdout())
253    } else {
254        Box::new(fs::File::create(profile_output)?)
255    };
256
257    let profile_output = io::BufWriter::new(profile_output);
258
259    match profile_format {
260        ProfileFormat::Html => flame::dump_html(profile_output)?,
261        ProfileFormat::Text => flame::dump_text_to_writer(profile_output)?,
262        ProfileFormat::Speedscope => flamescope::dump(profile_output)?,
263    }
264
265    Ok(())
266}
267
268#[cfg(test)]
269mod tests {
270    use super::*;
271    use rustpython_vm::Interpreter;
272
273    fn interpreter() -> Interpreter {
274        InterpreterConfig::new().init_stdlib().interpreter()
275    }
276
277    #[test]
278    fn test_run_script() {
279        interpreter().enter(|vm| {
280            vm.unwrap_pyresult((|| {
281                let scope = setup_main_module(vm)?;
282                // test file run
283                vm.run_script(scope, "extra_tests/snippets/dir_main/__main__.py")?;
284
285                let scope = setup_main_module(vm)?;
286                // test module run
287                vm.run_script(scope, "extra_tests/snippets/dir_main")?;
288
289                Ok(())
290            })());
291        })
292    }
293}
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy