diff rust/hgcli/src/main.rs @ 44729:26ce8e751503 stable 5.4rc0

merge default into stable for 5.4 release
author Pulkit Goyal <7895pulkit@gmail.com>
date Thu, 16 Apr 2020 22:51:09 +0530
parents ce088b38f92b af739894a4c1
children 426294d06ddc
line wrap: on
line diff
--- a/rust/hgcli/src/main.rs	Mon Apr 13 16:30:13 2020 +0300
+++ b/rust/hgcli/src/main.rs	Thu Apr 16 22:51:09 2020 +0530
@@ -1,219 +1,38 @@
-// main.rs -- Main routines for `hg` program
-//
-// Copyright 2017 Gregory Szorc <gregory.szorc@gmail.com>
-//
-// This software may be used and distributed according to the terms of the
-// GNU General Public License version 2 or any later version.
-
-extern crate cpython;
-extern crate libc;
-extern crate python27_sys;
-
-use cpython::{NoArgs, ObjectProtocol, PyModule, PyResult, Python};
-use libc::{c_char, c_int};
-
-use std::env;
-use std::ffi::{CString, OsStr};
-#[cfg(target_family = "unix")]
-use std::os::unix::ffi::{OsStrExt, OsStringExt};
-use std::path::PathBuf;
-
-#[derive(Debug)]
-struct Environment {
-    _exe: PathBuf,
-    python_exe: PathBuf,
-    python_home: PathBuf,
-    mercurial_modules: PathBuf,
-}
-
-/// Run Mercurial locally from a source distribution or checkout.
-///
-/// hg is <srcdir>/rust/target/<target>/hg
-/// Python interpreter is detected by build script.
-/// Python home is relative to Python interpreter.
-/// Mercurial files are relative to hg binary, which is relative to source root.
-#[cfg(feature = "localdev")]
-fn get_environment() -> Environment {
-    let exe = env::current_exe().unwrap();
-
-    let mut mercurial_modules = exe.clone();
-    mercurial_modules.pop(); // /rust/target/<target>
-    mercurial_modules.pop(); // /rust/target
-    mercurial_modules.pop(); // /rust
-    mercurial_modules.pop(); // /
-
-    let python_exe: &'static str = env!("PYTHON_INTERPRETER");
-    let python_exe = PathBuf::from(python_exe);
-
-    let mut python_home = python_exe.clone();
-    python_home.pop();
-
-    // On Windows, python2.7.exe exists at the root directory of the Python
-    // install. Everywhere else, the Python install root is one level up.
-    if !python_exe.ends_with("python2.7.exe") {
-        python_home.pop();
-    }
-
-    Environment {
-        _exe: exe.clone(),
-        python_exe: python_exe,
-        python_home: python_home,
-        mercurial_modules: mercurial_modules.to_path_buf(),
-    }
-}
-
-// On UNIX, platform string is just bytes and should not contain NUL.
-#[cfg(target_family = "unix")]
-fn cstring_from_os<T: AsRef<OsStr>>(s: T) -> CString {
-    CString::new(s.as_ref().as_bytes()).unwrap()
-}
-
-// TODO convert to ANSI characters?
-#[cfg(target_family = "windows")]
-fn cstring_from_os<T: AsRef<OsStr>>(s: T) -> CString {
-    CString::new(s.as_ref().to_str().unwrap()).unwrap()
-}
-
-// On UNIX, argv starts as an array of char*. So it is easy to convert
-// to C strings.
-#[cfg(target_family = "unix")]
-fn args_to_cstrings() -> Vec<CString> {
-    env::args_os()
-        .map(|a| CString::new(a.into_vec()).unwrap())
-        .collect()
-}
-
-// TODO Windows support is incomplete. We should either use env::args_os()
-// (or call into GetCommandLineW() + CommandLinetoArgvW()), convert these to
-// PyUnicode instances, and pass these into Python/Mercurial outside the
-// standard PySys_SetArgvEx() mechanism. This will allow us to preserve the
-// raw bytes (since PySys_SetArgvEx() is based on char* and can drop wchar
-// data.
-//
-// For now, we use env::args(). This will choke on invalid UTF-8 arguments.
-// But it is better than nothing.
-#[cfg(target_family = "windows")]
-fn args_to_cstrings() -> Vec<CString> {
-    env::args().map(|a| CString::new(a).unwrap()).collect()
-}
+use pyembed::MainPythonInterpreter;
 
-fn set_python_home(env: &Environment) {
-    let raw = cstring_from_os(&env.python_home).into_raw();
-    unsafe {
-        python27_sys::Py_SetPythonHome(raw);
-    }
-}
-
-fn update_modules_path(env: &Environment, py: Python, sys_mod: &PyModule) {
-    let sys_path = sys_mod.get(py, "path").unwrap();
-    sys_path
-        .call_method(py, "insert", (0, env.mercurial_modules.to_str()), None)
-        .expect("failed to update sys.path to location of Mercurial modules");
-}
-
-fn run() -> Result<(), i32> {
-    let env = get_environment();
-
-    //println!("{:?}", env);
-
-    // Tell Python where it is installed.
-    set_python_home(&env);
-
-    // Set program name. The backing memory needs to live for the duration of the
-    // interpreter.
-    //
-    // TODO consider storing this in a static or associating with lifetime of
-    // the Python interpreter.
-    //
-    // Yes, we use the path to the Python interpreter not argv[0] here. The
-    // reason is because Python uses the given path to find the location of
-    // Python files. Apparently we could define our own ``Py_GetPath()``
-    // implementation. But this may require statically linking Python, which is
-    // not desirable.
-    let program_name = cstring_from_os(&env.python_exe).as_ptr();
-    unsafe {
-        python27_sys::Py_SetProgramName(program_name as *mut i8);
-    }
-
-    unsafe {
-        python27_sys::Py_Initialize();
-    }
-
-    // https://docs.python.org/2/c-api/init.html#c.PySys_SetArgvEx has important
-    // usage information about PySys_SetArgvEx:
-    //
-    // * It says the first argument should be the script that is being executed.
-    //   If not a script, it can be empty. We are definitely not a script.
-    //   However, parts of Mercurial do look at sys.argv[0]. So we need to set
-    //   something here.
-    //
-    // * When embedding Python, we should use ``PySys_SetArgvEx()`` and set
-    //   ``updatepath=0`` for security reasons. Essentially, Python's default
-    //   logic will treat an empty argv[0] in a manner that could result in
-    //   sys.path picking up directories it shouldn't and this could lead to
-    //   loading untrusted modules.
-
-    // env::args() will panic if it sees a non-UTF-8 byte sequence. And
-    // Mercurial supports arbitrary encodings of input data. So we need to
-    // use OS-specific mechanisms to get the raw bytes without UTF-8
-    // interference.
-    let args = args_to_cstrings();
-    let argv: Vec<*const c_char> = args.iter().map(|a| a.as_ptr()).collect();
-
-    unsafe {
-        python27_sys::PySys_SetArgvEx(args.len() as c_int, argv.as_ptr() as *mut *mut i8, 0);
-    }
-
-    let result;
-    {
-        // These need to be dropped before we call Py_Finalize(). Hence the
-        // block.
-        let gil = Python::acquire_gil();
-        let py = gil.python();
-
-        // Mercurial code could call sys.exit(), which will call exit()
-        // itself. So this may not return.
-        // TODO this may cause issues on Windows due to the CRT mismatch.
-        // Investigate if we can intercept sys.exit() or SystemExit() to
-        // ensure we handle process exit.
-        result = match run_py(&env, py) {
-            // Print unhandled exceptions and exit code 255, as this is what
-            // `python` does.
-            Err(err) => {
-                err.print(py);
-                Err(255)
-            }
-            Ok(()) => Ok(()),
-        };
-    }
-
-    unsafe {
-        python27_sys::Py_Finalize();
-    }
-
-    result
-}
-
-fn run_py(env: &Environment, py: Python) -> PyResult<()> {
-    let sys_mod = py.import("sys").unwrap();
-
-    update_modules_path(&env, py, &sys_mod);
-
-    // TODO consider a better error message on failure to import.
-    let demand_mod = py.import("hgdemandimport")?;
-    demand_mod.call(py, "enable", NoArgs, None)?;
-
-    let dispatch_mod = py.import("mercurial.dispatch")?;
-    dispatch_mod.call(py, "run", NoArgs, None)?;
-
-    Ok(())
-}
+// Include an auto-generated file containing the default
+// `pyembed::PythonConfig` derived by the PyOxidizer configuration file.
+//
+// If you do not want to use PyOxidizer to generate this file, simply
+// remove this line and instantiate your own instance of
+// `pyembed::PythonConfig`.
+include!(env!("PYOXIDIZER_DEFAULT_PYTHON_CONFIG_RS"));
 
 fn main() {
-    let exit_code = match run() {
-        Err(err) => err,
-        Ok(()) => 0,
+    // The following code is in a block so the MainPythonInterpreter is destroyed in an
+    // orderly manner, before process exit.
+    let code = {
+        // Load the default Python configuration as derived by the PyOxidizer config
+        // file used at build time.
+        let config = default_python_config();
+
+        // Construct a new Python interpreter using that config, handling any errors
+        // from construction.
+        match MainPythonInterpreter::new(config) {
+            Ok(mut interp) => {
+                // And run it using the default run configuration as specified by the
+                // configuration. If an uncaught Python exception is raised, handle it.
+                // This includes the special SystemExit, which is a request to terminate the
+                // process.
+                interp.run_as_main()
+            }
+            Err(msg) => {
+                eprintln!("{}", msg);
+                1
+            }
+        }
     };
 
-    std::process::exit(exit_code);
+    // And exit the process according to code execution results.
+    std::process::exit(code);
 }