1#![allow(clippy::needless_doctest_main)]
48
49#[macro_use]
50extern crate log;
51
52#[cfg(feature = "flame-it")]
53use vm::Settings;
54
55mod interpreter;
56mod settings;
57mod shell;
58
59#[cfg(feature = "rustpython-pylib")]
60pub use rustpython_pylib as pylib;
61
62use rustpython_vm::{AsObject, PyObjectRef, PyResult, VirtualMachine, scope::Scope};
63use std::env;
64use std::io::IsTerminal;
65use std::process::ExitCode;
66
67pub use interpreter::InterpreterBuilderExt;
68pub use rustpython_vm::{self as vm, Interpreter, InterpreterBuilder};
69pub use settings::{InstallPipMode, RunMode, parse_opts};
70pub use shell::run_shell;
71
72#[cfg(all(
73 feature = "ssl",
74 not(any(feature = "ssl-rustls", feature = "ssl-openssl"))
75))]
76compile_error!(
77 "Feature \"ssl\" is now enabled by either \"ssl-rustls\" or \"ssl-openssl\" to be enabled. Do not manually pass \"ssl\" feature. To enable ssl-openssl, use --no-default-features to disable ssl-rustls"
78);
79
80pub fn run(mut builder: InterpreterBuilder) -> ExitCode {
87 env_logger::init();
88
89 #[cfg(target_os = "wasi")]
91 {
92 if let Ok(pwd) = env::var("PWD") {
93 let _ = env::set_current_dir(pwd);
94 };
95 }
96
97 let (settings, run_mode) = match parse_opts() {
98 Ok(x) => x,
99 Err(e) => {
100 println!("{e}");
101 return ExitCode::FAILURE;
102 }
103 };
104
105 #[cfg(windows)]
107 {
108 unsafe extern "C" {
109 fn _setmode(fd: i32, flags: i32) -> i32;
110 }
111 unsafe {
112 _setmode(0, libc::O_BINARY);
113 _setmode(1, libc::O_BINARY);
114 _setmode(2, libc::O_BINARY);
115 }
116 }
117
118 builder = builder.settings(settings);
119
120 let interp = builder.interpreter();
121 let exitcode = interp.run(move |vm| run_rustpython(vm, run_mode));
122
123 rustpython_vm::common::os::exit_code(exitcode)
124}
125
126fn get_pip(scope: Scope, vm: &VirtualMachine) -> PyResult<()> {
127 let get_getpip = rustpython_vm::py_compile!(
128 source = r#"\
129__import__("io").TextIOWrapper(
130 __import__("urllib.request").request.urlopen("https://bootstrap.pypa.io/get-pip.py")
131).read()
132"#,
133 mode = "eval"
134 );
135 eprintln!("downloading get-pip.py...");
136 let getpip_code = vm.run_code_obj(vm.ctx.new_code(get_getpip), vm.new_scope_with_builtins())?;
137 let getpip_code: rustpython_vm::builtins::PyStrRef = getpip_code
138 .downcast()
139 .expect("TextIOWrapper.read() should return str");
140 eprintln!("running get-pip.py...");
141 vm.run_string(scope, getpip_code.expect_str(), "get-pip.py".to_owned())?;
142 Ok(())
143}
144
145fn install_pip(installer: InstallPipMode, scope: Scope, vm: &VirtualMachine) -> PyResult<()> {
146 if !cfg!(feature = "ssl") {
147 return Err(vm.new_exception_msg(
148 vm.ctx.exceptions.system_error.to_owned(),
149 "install-pip requires rustpython be build with '--features=ssl'".into(),
150 ));
151 }
152
153 match installer {
154 InstallPipMode::Ensurepip => vm.run_module("ensurepip"),
155 InstallPipMode::GetPip => get_pip(scope, vm),
156 }
157}
158
159fn run_file(vm: &VirtualMachine, scope: Scope, path: &str) -> PyResult<()> {
161 if let Some(_importer) = get_importer(path, vm)? {
163 vm.insert_sys_path(vm.new_pyobj(path))?;
164 let runpy = vm.import("runpy", 0)?;
165 let run_module_as_main = runpy.get_attr("_run_module_as_main", vm)?;
166 run_module_as_main.call((vm::identifier!(vm, __main__).to_owned(), false), vm)?;
167 return Ok(());
168 }
169
170 if !vm.state.config.settings.safe_path {
172 let dir = std::path::Path::new(path)
173 .parent()
174 .and_then(|p| p.to_str())
175 .unwrap_or("");
176 vm.insert_sys_path(vm.new_pyobj(dir))?;
177 }
178
179 #[cfg(feature = "host_env")]
180 {
181 vm.run_any_file(scope, path)
182 }
183 #[cfg(not(feature = "host_env"))]
184 {
185 let path = if path.is_empty() { "???" } else { path };
188 match std::fs::read_to_string(path) {
189 Ok(source) => vm.run_string(scope, &source, path.to_owned()).map(drop),
190 Err(err) => Err(vm.new_os_error(err.to_string())),
191 }
192 }
193}
194
195fn get_importer(path: &str, vm: &VirtualMachine) -> PyResult<Option<PyObjectRef>> {
196 use rustpython_vm::builtins::PyDictRef;
197 use rustpython_vm::convert::TryFromObject;
198
199 let path_importer_cache = vm.sys_module.get_attr("path_importer_cache", vm)?;
200 let path_importer_cache = PyDictRef::try_from_object(vm, path_importer_cache)?;
201 if let Some(importer) = path_importer_cache.get_item_opt(path, vm)? {
202 return Ok(Some(importer));
203 }
204 let path_obj = vm.ctx.new_str(path);
205 let path_hooks = vm.sys_module.get_attr("path_hooks", vm)?;
206 let mut importer = None;
207 let path_hooks: Vec<PyObjectRef> = path_hooks.try_into_value(vm)?;
208 for path_hook in path_hooks {
209 match path_hook.call((path_obj.clone(),), vm) {
210 Ok(imp) => {
211 importer = Some(imp);
212 break;
213 }
214 Err(e) if e.fast_isinstance(vm.ctx.exceptions.import_error) => continue,
215 Err(e) => return Err(e),
216 }
217 }
218 Ok(if let Some(imp) = importer {
219 let imp = path_importer_cache.get_or_insert(vm, path_obj.into(), || imp.clone())?;
220 Some(imp)
221 } else {
222 None
223 })
224}
225
226fn run_rustpython(vm: &VirtualMachine, run_mode: RunMode) -> PyResult<()> {
228 #[cfg(feature = "flame-it")]
229 let main_guard = flame::start_guard("RustPython main");
230
231 let scope = vm.new_scope_with_main()?;
232
233 if vm.import("warnings", 0).is_err() {
236 warn!("Failed to import warnings module");
237 }
238
239 let site_result = vm.import("site", 0);
243 if site_result.is_err() {
244 warn!(
245 "Failed to import site, consider adding the Lib directory to your RUSTPYTHONPATH \
246 environment variable",
247 );
248 }
249
250 if !vm.state.config.settings.safe_path {
252 let path0: Option<String> = match &run_mode {
253 RunMode::Command(_) => Some(String::new()),
254 RunMode::Module(_) => env::current_dir()
255 .ok()
256 .and_then(|p| p.to_str().map(|s| s.to_owned())),
257 RunMode::Script(_) | RunMode::InstallPip(_) => None, RunMode::Repl => Some(String::new()),
259 };
260
261 if let Some(path) = path0 {
262 vm.insert_sys_path(vm.new_pyobj(path))?;
263 }
264 }
265
266 if vm.state.config.settings.faulthandler {
269 let _ = vm.run_simple_string("import faulthandler; faulthandler.enable()");
270 }
271
272 let is_repl = matches!(run_mode, RunMode::Repl);
273 if !vm.state.config.settings.quiet
274 && (vm.state.config.settings.verbose > 0 || (is_repl && std::io::stdin().is_terminal()))
275 {
276 eprintln!(
277 "Welcome to the magnificent Rust Python {} interpreter \u{1f631} \u{1f596}",
278 env!("CARGO_PKG_VERSION")
279 );
280 eprintln!(
281 "RustPython {}.{}.{}",
282 vm::version::MAJOR,
283 vm::version::MINOR,
284 vm::version::MICRO,
285 );
286
287 eprintln!("Type \"help\", \"copyright\", \"credits\" or \"license\" for more information.");
288 }
289 let res = match run_mode {
290 RunMode::Command(command) => {
291 debug!("Running command {command}");
292 vm.run_string(scope.clone(), &command, "<string>".to_owned())
293 .map(drop)
294 }
295 RunMode::Module(module) => {
296 debug!("Running module {module}");
297 vm.run_module(&module)
298 }
299 RunMode::InstallPip(installer) => install_pip(installer, scope.clone(), vm),
300 RunMode::Script(script_path) => {
301 debug!("Running script {}", &script_path);
303 run_file(vm, scope.clone(), &script_path)
304 }
305 RunMode::Repl => Ok(()),
306 };
307 let result = if is_repl || vm.state.config.settings.inspect {
308 shell::run_shell(vm, scope)
309 } else {
310 res
311 };
312
313 #[cfg(feature = "flame-it")]
314 {
315 main_guard.end();
316 if let Err(e) = write_profile(&vm.state.as_ref().config.settings) {
317 error!("Error writing profile information: {}", e);
318 }
319 }
320
321 result
322}
323
324#[cfg(feature = "flame-it")]
325fn write_profile(settings: &Settings) -> Result<(), Box<dyn core::error::Error>> {
326 use std::{fs, io};
327
328 enum ProfileFormat {
329 Html,
330 Text,
331 SpeedScope,
332 }
333 let profile_output = settings.profile_output.as_deref();
334 let profile_format = match settings.profile_format.as_deref() {
335 Some("html") => ProfileFormat::Html,
336 Some("text") => ProfileFormat::Text,
337 None if profile_output == Some("-".as_ref()) => ProfileFormat::Text,
338 Some("speedscope") | None => ProfileFormat::SpeedScope,
340 Some(other) => {
341 error!("Unknown profile format {}", other);
342 std::process::exit(1);
344 }
345 };
346
347 let profile_output = profile_output.unwrap_or_else(|| match profile_format {
348 ProfileFormat::Html => "flame-graph.html".as_ref(),
349 ProfileFormat::Text => "flame.txt".as_ref(),
350 ProfileFormat::SpeedScope => "flamescope.json".as_ref(),
351 });
352
353 let profile_output: Box<dyn io::Write> = if profile_output == "-" {
354 Box::new(io::stdout())
355 } else {
356 Box::new(fs::File::create(profile_output)?)
357 };
358
359 let profile_output = io::BufWriter::new(profile_output);
360
361 match profile_format {
362 ProfileFormat::Html => flame::dump_html(profile_output)?,
363 ProfileFormat::Text => flame::dump_text_to_writer(profile_output)?,
364 ProfileFormat::SpeedScope => flamescope::dump(profile_output)?,
365 }
366
367 Ok(())
368}
369
370#[cfg(test)]
371mod tests {
372 use super::*;
373 use rustpython_vm::Interpreter;
374
375 fn interpreter() -> Interpreter {
376 InterpreterBuilder::new().init_stdlib().interpreter()
377 }
378
379 #[test]
380 fn test_run_script() {
381 interpreter().enter(|vm| {
382 vm.unwrap_pyresult((|| {
383 let scope = vm.new_scope_with_main()?;
384 run_file(vm, scope, "extra_tests/snippets/dir_main/__main__.py")?;
386
387 #[cfg(feature = "host_env")]
388 {
389 let scope = vm.new_scope_with_main()?;
390 run_file(vm, scope, "extra_tests/snippets/dir_main")?;
392 }
393
394 Ok(())
395 })());
396 })
397 }
398}