From 597ad013b204d706d48780a2bf5117277f89db00 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Sat, 3 Sep 2022 14:22:38 +0900 Subject: [PATCH 1/2] Fix pylib dependency for binary --- pylib/src/lib.rs | 4 ++-- src/lib.rs | 13 +++++++------ wasm/lib/Cargo.toml | 5 ++--- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/pylib/src/lib.rs b/pylib/src/lib.rs index 4ce3c8a7e0..7f52aca999 100644 --- a/pylib/src/lib.rs +++ b/pylib/src/lib.rs @@ -6,10 +6,10 @@ // so build.rs sets this env var pub const LIB_PATH: &str = match option_env!("win_lib_path") { Some(s) => s, - None => concat!(env!("CARGO_MANIFEST_DIR"), "/../Lib"), + None => concat!(env!("CARGO_MANIFEST_DIR"), "/Lib"), }; #[cfg(feature = "freeze-stdlib")] pub fn frozen_stdlib() -> impl Iterator { - rustpython_derive::py_freeze!(dir = "../Lib", crate_name = "rustpython_compiler_core") + rustpython_derive::py_freeze!(dir = "./Lib", crate_name = "rustpython_compiler_core") } diff --git a/src/lib.rs b/src/lib.rs index eb7369991f..44ad16bbf9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -43,6 +43,8 @@ extern crate env_logger; #[macro_use] extern crate log; +use rustpython_pylib; + mod shell; use clap::{App, AppSettings, Arg, ArgMatches}; @@ -257,25 +259,24 @@ fn add_stdlib(vm: &mut VirtualMachine) { { use rustpython_vm::common::rc::PyRc; let state = PyRc::get_mut(&mut vm.state).unwrap(); + let settings = &mut state.settings; #[allow(clippy::needless_collect)] // false positive - let path_list: Vec<_> = state.settings.path_list.drain(..).collect(); + let path_list: Vec<_> = settings.path_list.drain(..).collect(); // BUILDTIME_RUSTPYTHONPATH should be set when distributing if let Some(paths) = option_env!("BUILDTIME_RUSTPYTHONPATH") { - state - .settings + settings .path_list .extend(split_paths(paths).map(|path| path.into_os_string().into_string().unwrap())) } else { #[cfg(feature = "rustpython-pylib")] - state - .settings + settings .path_list .push(rustpython_pylib::LIB_PATH.to_owned()) } - state.settings.path_list.extend(path_list.into_iter()); + settings.path_list.extend(path_list.into_iter()); } } diff --git a/wasm/lib/Cargo.toml b/wasm/lib/Cargo.toml index 1145f63124..b468e3b243 100644 --- a/wasm/lib/Cargo.toml +++ b/wasm/lib/Cargo.toml @@ -11,9 +11,8 @@ edition = "2021" crate-type = ["cdylib", "rlib"] [features] -default = ["stdlib"] -stdlib = ["freeze-stdlib", "rustpython-pylib", "rustpython-stdlib"] -freeze-stdlib = ["rustpython-vm/freeze-stdlib", "rustpython-pylib?/freeze-stdlib"] +default = ["freeze-stdlib"] +freeze-stdlib = ["rustpython-vm/freeze-stdlib", "rustpython-pylib/freeze-stdlib", "rustpython-stdlib"] no-start-func = [] [dependencies] From 8df1f07df9942db56aba46a8dc85a3b0570c415c Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Thu, 25 Aug 2022 06:53:15 +0900 Subject: [PATCH 2/2] InterpreterConfig --- src/interpreter.rs | 71 +++++++ src/lib.rs | 466 +++++---------------------------------------- src/settings.rs | 363 +++++++++++++++++++++++++++++++++++ 3 files changed, 484 insertions(+), 416 deletions(-) create mode 100644 src/interpreter.rs create mode 100644 src/settings.rs diff --git a/src/interpreter.rs b/src/interpreter.rs new file mode 100644 index 0000000000..33eeb80884 --- /dev/null +++ b/src/interpreter.rs @@ -0,0 +1,71 @@ +use rustpython_vm::{Interpreter, Settings, VirtualMachine}; + +pub type InitHook = Box; + +#[derive(Default)] +pub struct InterpreterConfig { + settings: Option, + init_hooks: Vec, +} + +impl InterpreterConfig { + pub fn new() -> Self { + Self::default() + } + pub fn interpreter(self) -> Interpreter { + let settings = self.settings.unwrap_or_default(); + Interpreter::with_init(settings, |vm| { + for hook in self.init_hooks { + hook(vm); + } + }) + } + + pub fn settings(mut self, settings: Settings) -> Self { + self.settings = Some(settings); + self + } + pub fn init_hook(mut self, hook: InitHook) -> Self { + self.init_hooks.push(hook); + self + } + #[cfg(feature = "stdlib")] + pub fn init_stdlib(self) -> Self { + self.init_hook(Box::new(init_stdlib)) + } +} + +#[cfg(feature = "stdlib")] +pub fn init_stdlib(vm: &mut VirtualMachine) { + vm.add_native_modules(rustpython_stdlib::get_module_inits()); + + // if we're on freeze-stdlib, the core stdlib modules will be included anyway + #[cfg(feature = "freeze-stdlib")] + vm.add_frozen(rustpython_pylib::frozen_stdlib()); + + #[cfg(not(feature = "freeze-stdlib"))] + { + use rustpython_vm::common::rc::PyRc; + + let state = PyRc::get_mut(&mut vm.state).unwrap(); + let settings = &mut state.settings; + + #[allow(clippy::needless_collect)] // false positive + let path_list: Vec<_> = settings.path_list.drain(..).collect(); + + // BUILDTIME_RUSTPYTHONPATH should be set when distributing + if let Some(paths) = option_env!("BUILDTIME_RUSTPYTHONPATH") { + settings.path_list.extend( + crate::settings::split_paths(paths) + .map(|path| path.into_os_string().into_string().unwrap()), + ) + } else { + #[cfg(feature = "rustpython-pylib")] + settings + .path_list + .push(rustpython_pylib::LIB_PATH.to_owned()) + } + + settings.path_list.extend(path_list.into_iter()); + } +} diff --git a/src/lib.rs b/src/lib.rs index 44ad16bbf9..436a8f70b2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -43,29 +43,23 @@ extern crate env_logger; #[macro_use] extern crate log; -use rustpython_pylib; - +mod interpreter; +mod settings; mod shell; -use clap::{App, AppSettings, Arg, ArgMatches}; -use rustpython_vm::{scope::Scope, Interpreter, PyResult, Settings, VirtualMachine}; -use std::{env, process::ExitCode, str::FromStr}; +use rustpython_vm::{scope::Scope, PyResult, VirtualMachine}; +use std::{env, process::ExitCode}; +pub use interpreter::InterpreterConfig; pub use rustpython_vm as vm; +pub use settings::{opts_with_clap, RunMode}; /// The main cli of the `rustpython` interpreter. This function will return with `std::process::ExitCode` /// based on the return code of the python code ran through the cli. -pub fn run(init: F) -> ExitCode -where - F: FnOnce(&mut VirtualMachine), -{ - #[cfg(feature = "flame-it")] - let main_guard = flame::start_guard("RustPython main"); +pub fn run(init: impl FnOnce(&mut VirtualMachine) + 'static) -> ExitCode { env_logger::init(); - let app = App::new("RustPython"); - let matches = parse_arguments(app); - let matches = &matches; - let settings = create_settings(matches); + + let (settings, run_mode) = opts_with_clap(); // don't translate newlines (\r\n <=> \n) #[cfg(windows)] @@ -80,12 +74,18 @@ where } } - let interp = Interpreter::with_init(settings, |vm| { - add_stdlib(vm); - init(vm); - }); + let mut config = InterpreterConfig::new().settings(settings); + #[cfg(feature = "stdlib")] + { + config = config.init_stdlib(); + } + config = config.init_hook(Box::new(init)); + let interp = config.interpreter(); + + #[cfg(feature = "flame-it")] + let main_guard = flame::start_guard("RustPython main"); - let exitcode = interp.run(move |vm| run_rustpython(vm, matches)); + let exitcode = interp.run(move |vm| run_rustpython(vm, run_mode)); #[cfg(feature = "flame-it")] { @@ -97,376 +97,6 @@ where ExitCode::from(exitcode) } -fn parse_arguments<'a>(app: App<'a, '_>) -> ArgMatches<'a> { - let app = app - .setting(AppSettings::TrailingVarArg) - .version(crate_version!()) - .author(crate_authors!()) - .about("Rust implementation of the Python language") - .usage("rustpython [OPTIONS] [-c CMD | -m MODULE | FILE] [PYARGS]...") - .arg( - Arg::with_name("script") - .required(false) - .allow_hyphen_values(true) - .multiple(true) - .value_name("script, args") - .min_values(1), - ) - .arg( - Arg::with_name("c") - .short("c") - .takes_value(true) - .allow_hyphen_values(true) - .multiple(true) - .value_name("cmd, args") - .min_values(1) - .help("run the given string as a program"), - ) - .arg( - Arg::with_name("m") - .short("m") - .takes_value(true) - .allow_hyphen_values(true) - .multiple(true) - .value_name("module, args") - .min_values(1) - .help("run library module as script"), - ) - .arg( - Arg::with_name("install_pip") - .long("install-pip") - .takes_value(true) - .allow_hyphen_values(true) - .multiple(true) - .value_name("get-pip args") - .min_values(0) - .help("install the pip package manager for rustpython; \ - requires rustpython be build with the ssl feature enabled." - ), - ) - .arg( - Arg::with_name("optimize") - .short("O") - .multiple(true) - .help("Optimize. Set __debug__ to false. Remove debug statements."), - ) - .arg( - Arg::with_name("verbose") - .short("v") - .multiple(true) - .help("Give the verbosity (can be applied multiple times)"), - ) - .arg(Arg::with_name("debug").short("d").help("Debug the parser.")) - .arg( - Arg::with_name("quiet") - .short("q") - .help("Be quiet at startup."), - ) - .arg( - Arg::with_name("inspect") - .short("i") - .help("Inspect interactively after running the script."), - ) - .arg( - Arg::with_name("no-user-site") - .short("s") - .help("don't add user site directory to sys.path."), - ) - .arg( - Arg::with_name("no-site") - .short("S") - .help("don't imply 'import site' on initialization"), - ) - .arg( - Arg::with_name("dont-write-bytecode") - .short("B") - .help("don't write .pyc files on import"), - ) - .arg( - Arg::with_name("ignore-environment") - .short("E") - .help("Ignore environment variables PYTHON* such as PYTHONPATH"), - ) - .arg( - Arg::with_name("isolate") - .short("I") - .help("isolate Python from the user's environment (implies -E and -s)"), - ) - .arg( - Arg::with_name("implementation-option") - .short("X") - .takes_value(true) - .multiple(true) - .number_of_values(1) - .help("set implementation-specific option"), - ) - .arg( - Arg::with_name("warning-control") - .short("W") - .takes_value(true) - .multiple(true) - .number_of_values(1) - .help("warning control; arg is action:message:category:module:lineno"), - ) - .arg( - Arg::with_name("check-hash-based-pycs") - .long("check-hash-based-pycs") - .takes_value(true) - .number_of_values(1) - .default_value("default") - .help("always|default|never\ncontrol how Python invalidates hash-based .pyc files"), - ) - .arg( - Arg::with_name("bytes-warning") - .short("b") - .multiple(true) - .help("issue warnings about using bytes where strings are usually expected (-bb: issue errors)"), - ).arg( - Arg::with_name("unbuffered") - .short("u") - .help( - "force the stdout and stderr streams to be unbuffered; \ - this option has no effect on stdin; also PYTHONUNBUFFERED=x", - ), - ); - #[cfg(feature = "flame-it")] - let app = app - .arg( - Arg::with_name("profile_output") - .long("profile-output") - .takes_value(true) - .help("the file to output the profiling information to"), - ) - .arg( - Arg::with_name("profile_format") - .long("profile-format") - .takes_value(true) - .help("the profile format to output the profiling information in"), - ); - app.get_matches() -} - -fn add_stdlib(vm: &mut VirtualMachine) { - let _ = vm; - #[cfg(feature = "stdlib")] - vm.add_native_modules(rustpython_stdlib::get_module_inits()); - - // if we're on freeze-stdlib, the core stdlib modules will be included anyway - #[cfg(feature = "freeze-stdlib")] - vm.add_frozen(rustpython_pylib::frozen_stdlib()); - - #[cfg(not(feature = "freeze-stdlib"))] - { - use rustpython_vm::common::rc::PyRc; - let state = PyRc::get_mut(&mut vm.state).unwrap(); - let settings = &mut state.settings; - - #[allow(clippy::needless_collect)] // false positive - let path_list: Vec<_> = settings.path_list.drain(..).collect(); - - // BUILDTIME_RUSTPYTHONPATH should be set when distributing - if let Some(paths) = option_env!("BUILDTIME_RUSTPYTHONPATH") { - settings - .path_list - .extend(split_paths(paths).map(|path| path.into_os_string().into_string().unwrap())) - } else { - #[cfg(feature = "rustpython-pylib")] - settings - .path_list - .push(rustpython_pylib::LIB_PATH.to_owned()) - } - - settings.path_list.extend(path_list.into_iter()); - } -} - -/// Create settings by examining command line arguments and environment -/// variables. -fn create_settings(matches: &ArgMatches) -> Settings { - let mut settings = Settings::default(); - settings.isolated = matches.is_present("isolate"); - settings.ignore_environment = matches.is_present("ignore-environment"); - settings.interactive = !matches.is_present("c") - && !matches.is_present("m") - && (!matches.is_present("script") || matches.is_present("inspect")); - settings.bytes_warning = matches.occurrences_of("bytes-warning"); - settings.no_site = matches.is_present("no-site"); - - let ignore_environment = settings.ignore_environment || settings.isolated; - - if !ignore_environment { - settings.path_list.extend(get_paths("RUSTPYTHONPATH")); - settings.path_list.extend(get_paths("PYTHONPATH")); - } - - // Now process command line flags: - if matches.is_present("debug") || (!ignore_environment && env::var_os("PYTHONDEBUG").is_some()) - { - settings.debug = true; - } - - if matches.is_present("inspect") - || (!ignore_environment && env::var_os("PYTHONINSPECT").is_some()) - { - settings.inspect = true; - } - - if matches.is_present("optimize") { - settings.optimize = matches.occurrences_of("optimize").try_into().unwrap(); - } else if !ignore_environment { - if let Ok(value) = get_env_var_value("PYTHONOPTIMIZE") { - settings.optimize = value; - } - } - - if matches.is_present("verbose") { - settings.verbose = matches.occurrences_of("verbose").try_into().unwrap(); - } else if !ignore_environment { - if let Ok(value) = get_env_var_value("PYTHONVERBOSE") { - settings.verbose = value; - } - } - - if matches.is_present("no-user-site") - || matches.is_present("isolate") - || (!ignore_environment && env::var_os("PYTHONNOUSERSITE").is_some()) - { - settings.no_user_site = true; - } - - if matches.is_present("quiet") { - settings.quiet = true; - } - - if matches.is_present("dont-write-bytecode") - || (!ignore_environment && env::var_os("PYTHONDONTWRITEBYTECODE").is_some()) - { - settings.dont_write_bytecode = true; - } - - settings.check_hash_based_pycs = matches - .value_of("check-hash-based-pycs") - .unwrap_or("default") - .to_owned(); - - let mut dev_mode = false; - let mut warn_default_encoding = false; - if let Some(xopts) = matches.values_of("implementation-option") { - settings.xopts.extend(xopts.map(|s| { - let mut parts = s.splitn(2, '='); - let name = parts.next().unwrap().to_owned(); - if name == "dev" { - dev_mode = true - } - if name == "warn_default_encoding" { - warn_default_encoding = true - } - let value = parts.next().map(ToOwned::to_owned); - (name, value) - })); - } - settings.dev_mode = dev_mode; - if warn_default_encoding - || (!ignore_environment && env::var_os("PYTHONWARNDEFAULTENCODING").is_some()) - { - settings.warn_default_encoding = true; - } - - if dev_mode { - settings.warnopts.push("default".to_owned()) - } - if settings.bytes_warning > 0 { - let warn = if settings.bytes_warning > 1 { - "error::BytesWarning" - } else { - "default::BytesWarning" - }; - settings.warnopts.push(warn.to_owned()); - } - if let Some(warnings) = matches.values_of("warning-control") { - settings.warnopts.extend(warnings.map(ToOwned::to_owned)); - } - - let argv = if let Some(script) = matches.values_of("script") { - script.map(ToOwned::to_owned).collect() - } else if let Some(module) = matches.values_of("m") { - std::iter::once("PLACEHOLDER".to_owned()) - .chain(module.skip(1).map(ToOwned::to_owned)) - .collect() - } else if let Some(get_pip_args) = matches.values_of("install_pip") { - settings.isolated = true; - let mut args: Vec<_> = get_pip_args.map(ToOwned::to_owned).collect(); - if args.is_empty() { - args.push("ensurepip".to_owned()); - args.push("--upgrade".to_owned()); - args.push("--default-pip".to_owned()); - } - match args.first().map(String::as_str) { - Some("ensurepip") | Some("get-pip") => (), - _ => panic!("--install-pip takes ensurepip or get-pip as first argument"), - } - args - } else if let Some(cmd) = matches.values_of("c") { - std::iter::once("-c".to_owned()) - .chain(cmd.skip(1).map(ToOwned::to_owned)) - .collect() - } else { - vec!["".to_owned()] - }; - - let hash_seed = match env::var("PYTHONHASHSEED") { - Ok(s) if s == "random" => Some(None), - Ok(s) => s.parse::().ok().map(Some), - Err(_) => Some(None), - }; - settings.hash_seed = hash_seed.unwrap_or_else(|| { - error!("Fatal Python init error: PYTHONHASHSEED must be \"random\" or an integer in range [0; 4294967295]"); - // TODO: Need to change to ExitCode or Termination - std::process::exit(1) - }); - - settings.argv = argv; - - settings -} - -/// Get environment variable and turn it into integer. -fn get_env_var_value(name: &str) -> Result { - env::var(name).map(|value| { - if let Ok(value) = u8::from_str(&value) { - value - } else { - 1 - } - }) -} - -/// Helper function to retrieve a sequence of paths from an environment variable. -fn get_paths(env_variable_name: &str) -> impl Iterator + '_ { - env::var_os(env_variable_name) - .into_iter() - .flat_map(move |paths| { - split_paths(&paths) - .map(|path| { - path.into_os_string() - .into_string() - .unwrap_or_else(|_| panic!("{} isn't valid unicode", env_variable_name)) - }) - .collect::>() - }) -} -#[cfg(not(target_os = "wasi"))] -use env::split_paths; -#[cfg(target_os = "wasi")] -fn split_paths + ?Sized>( - s: &T, -) -> impl Iterator + '_ { - use std::os::wasi::ffi::OsStrExt; - let s = s.as_ref().as_bytes(); - s.split(|b| *b == b':') - .map(|x| std::ffi::OsStr::from_bytes(x).to_owned().into()) -} - #[cfg(feature = "flame-it")] fn write_profile(matches: &ArgMatches) -> Result<(), Box> { use std::{fs, io}; @@ -558,10 +188,10 @@ fn ensurepip(_: Scope, vm: &VirtualMachine) -> PyResult<()> { vm.run_module("ensurepip") } -fn install_pip(_scope: Scope, vm: &VirtualMachine) -> PyResult<()> { +fn install_pip(_installer: &str, _scope: Scope, vm: &VirtualMachine) -> PyResult<()> { #[cfg(feature = "ssl")] { - match vm.state.settings.argv[0].as_str() { + match _installer { "ensurepip" => ensurepip(_scope, vm), "get-pip" => get_pip(_scope, vm), _ => unreachable!(), @@ -575,7 +205,7 @@ fn install_pip(_scope: Scope, vm: &VirtualMachine) -> PyResult<()> { )) } -fn run_rustpython(vm: &VirtualMachine, matches: &ArgMatches) -> PyResult<()> { +fn run_rustpython(vm: &VirtualMachine, run_mode: RunMode) -> PyResult<()> { let scope = setup_main_module(vm)?; let site_result = vm.import("site", None, 0); @@ -587,27 +217,32 @@ fn run_rustpython(vm: &VirtualMachine, matches: &ArgMatches) -> PyResult<()> { ); } - // Figure out if a -c option was given: - if let Some(command) = matches.value_of("c") { - debug!("Running command {}", command); - vm.run_code_string(scope, command, "".to_owned())?; - } else if let Some(module) = matches.value_of("m") { - debug!("Running module {}", module); - vm.run_module(module)?; - } else if matches.is_present("install_pip") { - install_pip(scope, vm)?; - } else if let Some(filename) = matches.value_of("script") { - debug!("Running file {}", filename); - vm.run_script(scope.clone(), filename)?; - if matches.is_present("inspect") { - shell::run_shell(vm, scope)?; + match run_mode { + RunMode::Command(command) => { + debug!("Running command {}", command); + vm.run_code_string(scope, &command, "".to_owned())?; + } + RunMode::Module(module) => { + debug!("Running module {}", module); + vm.run_module(&module)?; + } + RunMode::InstallPip(installer) => { + install_pip(&installer, scope, vm)?; + } + RunMode::ScriptInteractive(script, interactive) => { + if let Some(script) = script { + debug!("Running script {}", &script); + vm.run_script(scope.clone(), &script)?; + } else { + println!( + "Welcome to the magnificent Rust Python {} interpreter \u{1f631} \u{1f596}", + crate_version!() + ); + } + if interactive { + shell::run_shell(vm, scope)?; + } } - } else { - println!( - "Welcome to the magnificent Rust Python {} interpreter \u{1f631} \u{1f596}", - crate_version!() - ); - shell::run_shell(vm, scope)?; } Ok(()) @@ -616,11 +251,10 @@ fn run_rustpython(vm: &VirtualMachine, matches: &ArgMatches) -> PyResult<()> { #[cfg(test)] mod tests { use super::*; + use rustpython_vm::Interpreter; fn interpreter() -> Interpreter { - Interpreter::with_init(Settings::default(), |vm| { - add_stdlib(vm); - }) + InterpreterConfig::new().init_stdlib().interpreter() } #[test] diff --git a/src/settings.rs b/src/settings.rs new file mode 100644 index 0000000000..06542634b8 --- /dev/null +++ b/src/settings.rs @@ -0,0 +1,363 @@ +use clap::{App, AppSettings, Arg, ArgMatches}; +use rustpython_vm::Settings; +use std::{env, str::FromStr}; + +pub enum RunMode { + ScriptInteractive(Option, bool), + Command(String), + Module(String), + InstallPip(String), +} + +pub fn opts_with_clap() -> (Settings, RunMode) { + let app = App::new("RustPython"); + let matches = parse_arguments(app); + settings_from(&matches) +} + +fn parse_arguments<'a>(app: App<'a, '_>) -> ArgMatches<'a> { + let app = app + .setting(AppSettings::TrailingVarArg) + .version(crate_version!()) + .author(crate_authors!()) + .about("Rust implementation of the Python language") + .usage("rustpython [OPTIONS] [-c CMD | -m MODULE | FILE] [PYARGS]...") + .arg( + Arg::with_name("script") + .required(false) + .allow_hyphen_values(true) + .multiple(true) + .value_name("script, args") + .min_values(1), + ) + .arg( + Arg::with_name("c") + .short("c") + .takes_value(true) + .allow_hyphen_values(true) + .multiple(true) + .value_name("cmd, args") + .min_values(1) + .help("run the given string as a program"), + ) + .arg( + Arg::with_name("m") + .short("m") + .takes_value(true) + .allow_hyphen_values(true) + .multiple(true) + .value_name("module, args") + .min_values(1) + .help("run library module as script"), + ) + .arg( + Arg::with_name("install_pip") + .long("install-pip") + .takes_value(true) + .allow_hyphen_values(true) + .multiple(true) + .value_name("get-pip args") + .min_values(0) + .help("install the pip package manager for rustpython; \ + requires rustpython be build with the ssl feature enabled." + ), + ) + .arg( + Arg::with_name("optimize") + .short("O") + .multiple(true) + .help("Optimize. Set __debug__ to false. Remove debug statements."), + ) + .arg( + Arg::with_name("verbose") + .short("v") + .multiple(true) + .help("Give the verbosity (can be applied multiple times)"), + ) + .arg(Arg::with_name("debug").short("d").help("Debug the parser.")) + .arg( + Arg::with_name("quiet") + .short("q") + .help("Be quiet at startup."), + ) + .arg( + Arg::with_name("inspect") + .short("i") + .help("Inspect interactively after running the script."), + ) + .arg( + Arg::with_name("no-user-site") + .short("s") + .help("don't add user site directory to sys.path."), + ) + .arg( + Arg::with_name("no-site") + .short("S") + .help("don't imply 'import site' on initialization"), + ) + .arg( + Arg::with_name("dont-write-bytecode") + .short("B") + .help("don't write .pyc files on import"), + ) + .arg( + Arg::with_name("ignore-environment") + .short("E") + .help("Ignore environment variables PYTHON* such as PYTHONPATH"), + ) + .arg( + Arg::with_name("isolate") + .short("I") + .help("isolate Python from the user's environment (implies -E and -s)"), + ) + .arg( + Arg::with_name("implementation-option") + .short("X") + .takes_value(true) + .multiple(true) + .number_of_values(1) + .help("set implementation-specific option"), + ) + .arg( + Arg::with_name("warning-control") + .short("W") + .takes_value(true) + .multiple(true) + .number_of_values(1) + .help("warning control; arg is action:message:category:module:lineno"), + ) + .arg( + Arg::with_name("check-hash-based-pycs") + .long("check-hash-based-pycs") + .takes_value(true) + .number_of_values(1) + .default_value("default") + .help("always|default|never\ncontrol how Python invalidates hash-based .pyc files"), + ) + .arg( + Arg::with_name("bytes-warning") + .short("b") + .multiple(true) + .help("issue warnings about using bytes where strings are usually expected (-bb: issue errors)"), + ).arg( + Arg::with_name("unbuffered") + .short("u") + .help( + "force the stdout and stderr streams to be unbuffered; \ + this option has no effect on stdin; also PYTHONUNBUFFERED=x", + ), + ); + #[cfg(feature = "flame-it")] + let app = app + .arg( + Arg::with_name("profile_output") + .long("profile-output") + .takes_value(true) + .help("the file to output the profiling information to"), + ) + .arg( + Arg::with_name("profile_format") + .long("profile-format") + .takes_value(true) + .help("the profile format to output the profiling information in"), + ); + app.get_matches() +} + +/// Create settings by examining command line arguments and environment +/// variables. +fn settings_from(matches: &ArgMatches) -> (Settings, RunMode) { + let mut settings = Settings::default(); + settings.isolated = matches.is_present("isolate"); + settings.ignore_environment = matches.is_present("ignore-environment"); + settings.interactive = !matches.is_present("c") + && !matches.is_present("m") + && (!matches.is_present("script") || matches.is_present("inspect")); + settings.bytes_warning = matches.occurrences_of("bytes-warning"); + settings.no_site = matches.is_present("no-site"); + + let ignore_environment = settings.ignore_environment || settings.isolated; + + if !ignore_environment { + settings.path_list.extend(get_paths("RUSTPYTHONPATH")); + settings.path_list.extend(get_paths("PYTHONPATH")); + } + + // Now process command line flags: + if matches.is_present("debug") || (!ignore_environment && env::var_os("PYTHONDEBUG").is_some()) + { + settings.debug = true; + } + + if matches.is_present("inspect") + || (!ignore_environment && env::var_os("PYTHONINSPECT").is_some()) + { + settings.inspect = true; + } + + if matches.is_present("optimize") { + settings.optimize = matches.occurrences_of("optimize").try_into().unwrap(); + } else if !ignore_environment { + if let Ok(value) = get_env_var_value("PYTHONOPTIMIZE") { + settings.optimize = value; + } + } + + if matches.is_present("verbose") { + settings.verbose = matches.occurrences_of("verbose").try_into().unwrap(); + } else if !ignore_environment { + if let Ok(value) = get_env_var_value("PYTHONVERBOSE") { + settings.verbose = value; + } + } + + if matches.is_present("no-user-site") + || matches.is_present("isolate") + || (!ignore_environment && env::var_os("PYTHONNOUSERSITE").is_some()) + { + settings.no_user_site = true; + } + + if matches.is_present("quiet") { + settings.quiet = true; + } + + if matches.is_present("dont-write-bytecode") + || (!ignore_environment && env::var_os("PYTHONDONTWRITEBYTECODE").is_some()) + { + settings.dont_write_bytecode = true; + } + + settings.check_hash_based_pycs = matches + .value_of("check-hash-based-pycs") + .unwrap_or("default") + .to_owned(); + + let mut dev_mode = false; + let mut warn_default_encoding = false; + if let Some(xopts) = matches.values_of("implementation-option") { + settings.xopts.extend(xopts.map(|s| { + let mut parts = s.splitn(2, '='); + let name = parts.next().unwrap().to_owned(); + if name == "dev" { + dev_mode = true + } + if name == "warn_default_encoding" { + warn_default_encoding = true + } + let value = parts.next().map(ToOwned::to_owned); + (name, value) + })); + } + settings.dev_mode = dev_mode; + if warn_default_encoding + || (!ignore_environment && env::var_os("PYTHONWARNDEFAULTENCODING").is_some()) + { + settings.warn_default_encoding = true; + } + + if dev_mode { + settings.warnopts.push("default".to_owned()) + } + if settings.bytes_warning > 0 { + let warn = if settings.bytes_warning > 1 { + "error::BytesWarning" + } else { + "default::BytesWarning" + }; + settings.warnopts.push(warn.to_owned()); + } + if let Some(warnings) = matches.values_of("warning-control") { + settings.warnopts.extend(warnings.map(ToOwned::to_owned)); + } + + let (mode, argv) = if let Some(mut cmd) = matches.values_of("c") { + let command = cmd.next().expect("clap ensure this exists"); + let argv = std::iter::once("-c".to_owned()) + .chain(cmd.map(ToOwned::to_owned)) + .collect(); + (RunMode::Command(command.to_owned()), argv) + } else if let Some(mut cmd) = matches.values_of("m") { + let module = cmd.next().expect("clap ensure this exists"); + let argv = std::iter::once("PLACEHOLDER".to_owned()) + .chain(cmd.map(ToOwned::to_owned)) + .collect(); + (RunMode::Module(module.to_owned()), argv) + } else if let Some(get_pip_args) = matches.values_of("install_pip") { + settings.isolated = true; + let mut args: Vec<_> = get_pip_args.map(ToOwned::to_owned).collect(); + if args.is_empty() { + args.push("ensurepip".to_owned()); + args.push("--upgrade".to_owned()); + args.push("--default-pip".to_owned()); + } + let installer = args[0].clone(); + let mode = match installer.as_str() { + "ensurepip" | "get-pip" => RunMode::InstallPip(installer), + _ => panic!("--install-pip takes ensurepip or get-pip as first argument"), + }; + (mode, args) + } else if let Some(argv) = matches.values_of("script") { + let argv: Vec<_> = argv.map(ToOwned::to_owned).collect(); + let script = argv[0].clone(); + ( + RunMode::ScriptInteractive(Some(script), matches.is_present("inspect")), + argv, + ) + } else { + (RunMode::ScriptInteractive(None, true), vec!["".to_owned()]) + }; + + let hash_seed = match env::var("PYTHONHASHSEED") { + Ok(s) if s == "random" => Some(None), + Ok(s) => s.parse::().ok().map(Some), + Err(_) => Some(None), + }; + settings.hash_seed = hash_seed.unwrap_or_else(|| { + error!("Fatal Python init error: PYTHONHASHSEED must be \"random\" or an integer in range [0; 4294967295]"); + // TODO: Need to change to ExitCode or Termination + std::process::exit(1) + }); + + settings.argv = argv; + + (settings, mode) +} + +/// Get environment variable and turn it into integer. +fn get_env_var_value(name: &str) -> Result { + env::var(name).map(|value| { + if let Ok(value) = u8::from_str(&value) { + value + } else { + 1 + } + }) +} + +/// Helper function to retrieve a sequence of paths from an environment variable. +fn get_paths(env_variable_name: &str) -> impl Iterator + '_ { + env::var_os(env_variable_name) + .into_iter() + .flat_map(move |paths| { + split_paths(&paths) + .map(|path| { + path.into_os_string() + .into_string() + .unwrap_or_else(|_| panic!("{} isn't valid unicode", env_variable_name)) + }) + .collect::>() + }) +} + +#[cfg(not(target_os = "wasi"))] +pub(crate) use env::split_paths; +#[cfg(target_os = "wasi")] +pub(crate) fn split_paths + ?Sized>( + s: &T, +) -> impl Iterator + '_ { + use std::os::wasi::ffi::OsStrExt; + let s = s.as_ref().as_bytes(); + s.split(|b| *b == b':') + .map(|x| std::ffi::OsStr::from_bytes(x).to_owned().into()) +}