rust/hgcli/src/main.rs
branchstable
changeset 44729 26ce8e751503
parent 43818 ce088b38f92b
parent 44638 af739894a4c1
child 45620 426294d06ddc
equal deleted inserted replaced
44692:539490756a72 44729:26ce8e751503
     1 // main.rs -- Main routines for `hg` program
     1 use pyembed::MainPythonInterpreter;
       
     2 
       
     3 // Include an auto-generated file containing the default
       
     4 // `pyembed::PythonConfig` derived by the PyOxidizer configuration file.
     2 //
     5 //
     3 // Copyright 2017 Gregory Szorc <gregory.szorc@gmail.com>
     6 // If you do not want to use PyOxidizer to generate this file, simply
     4 //
     7 // remove this line and instantiate your own instance of
     5 // This software may be used and distributed according to the terms of the
     8 // `pyembed::PythonConfig`.
     6 // GNU General Public License version 2 or any later version.
     9 include!(env!("PYOXIDIZER_DEFAULT_PYTHON_CONFIG_RS"));
     7 
       
     8 extern crate cpython;
       
     9 extern crate libc;
       
    10 extern crate python27_sys;
       
    11 
       
    12 use cpython::{NoArgs, ObjectProtocol, PyModule, PyResult, Python};
       
    13 use libc::{c_char, c_int};
       
    14 
       
    15 use std::env;
       
    16 use std::ffi::{CString, OsStr};
       
    17 #[cfg(target_family = "unix")]
       
    18 use std::os::unix::ffi::{OsStrExt, OsStringExt};
       
    19 use std::path::PathBuf;
       
    20 
       
    21 #[derive(Debug)]
       
    22 struct Environment {
       
    23     _exe: PathBuf,
       
    24     python_exe: PathBuf,
       
    25     python_home: PathBuf,
       
    26     mercurial_modules: PathBuf,
       
    27 }
       
    28 
       
    29 /// Run Mercurial locally from a source distribution or checkout.
       
    30 ///
       
    31 /// hg is <srcdir>/rust/target/<target>/hg
       
    32 /// Python interpreter is detected by build script.
       
    33 /// Python home is relative to Python interpreter.
       
    34 /// Mercurial files are relative to hg binary, which is relative to source root.
       
    35 #[cfg(feature = "localdev")]
       
    36 fn get_environment() -> Environment {
       
    37     let exe = env::current_exe().unwrap();
       
    38 
       
    39     let mut mercurial_modules = exe.clone();
       
    40     mercurial_modules.pop(); // /rust/target/<target>
       
    41     mercurial_modules.pop(); // /rust/target
       
    42     mercurial_modules.pop(); // /rust
       
    43     mercurial_modules.pop(); // /
       
    44 
       
    45     let python_exe: &'static str = env!("PYTHON_INTERPRETER");
       
    46     let python_exe = PathBuf::from(python_exe);
       
    47 
       
    48     let mut python_home = python_exe.clone();
       
    49     python_home.pop();
       
    50 
       
    51     // On Windows, python2.7.exe exists at the root directory of the Python
       
    52     // install. Everywhere else, the Python install root is one level up.
       
    53     if !python_exe.ends_with("python2.7.exe") {
       
    54         python_home.pop();
       
    55     }
       
    56 
       
    57     Environment {
       
    58         _exe: exe.clone(),
       
    59         python_exe: python_exe,
       
    60         python_home: python_home,
       
    61         mercurial_modules: mercurial_modules.to_path_buf(),
       
    62     }
       
    63 }
       
    64 
       
    65 // On UNIX, platform string is just bytes and should not contain NUL.
       
    66 #[cfg(target_family = "unix")]
       
    67 fn cstring_from_os<T: AsRef<OsStr>>(s: T) -> CString {
       
    68     CString::new(s.as_ref().as_bytes()).unwrap()
       
    69 }
       
    70 
       
    71 // TODO convert to ANSI characters?
       
    72 #[cfg(target_family = "windows")]
       
    73 fn cstring_from_os<T: AsRef<OsStr>>(s: T) -> CString {
       
    74     CString::new(s.as_ref().to_str().unwrap()).unwrap()
       
    75 }
       
    76 
       
    77 // On UNIX, argv starts as an array of char*. So it is easy to convert
       
    78 // to C strings.
       
    79 #[cfg(target_family = "unix")]
       
    80 fn args_to_cstrings() -> Vec<CString> {
       
    81     env::args_os()
       
    82         .map(|a| CString::new(a.into_vec()).unwrap())
       
    83         .collect()
       
    84 }
       
    85 
       
    86 // TODO Windows support is incomplete. We should either use env::args_os()
       
    87 // (or call into GetCommandLineW() + CommandLinetoArgvW()), convert these to
       
    88 // PyUnicode instances, and pass these into Python/Mercurial outside the
       
    89 // standard PySys_SetArgvEx() mechanism. This will allow us to preserve the
       
    90 // raw bytes (since PySys_SetArgvEx() is based on char* and can drop wchar
       
    91 // data.
       
    92 //
       
    93 // For now, we use env::args(). This will choke on invalid UTF-8 arguments.
       
    94 // But it is better than nothing.
       
    95 #[cfg(target_family = "windows")]
       
    96 fn args_to_cstrings() -> Vec<CString> {
       
    97     env::args().map(|a| CString::new(a).unwrap()).collect()
       
    98 }
       
    99 
       
   100 fn set_python_home(env: &Environment) {
       
   101     let raw = cstring_from_os(&env.python_home).into_raw();
       
   102     unsafe {
       
   103         python27_sys::Py_SetPythonHome(raw);
       
   104     }
       
   105 }
       
   106 
       
   107 fn update_modules_path(env: &Environment, py: Python, sys_mod: &PyModule) {
       
   108     let sys_path = sys_mod.get(py, "path").unwrap();
       
   109     sys_path
       
   110         .call_method(py, "insert", (0, env.mercurial_modules.to_str()), None)
       
   111         .expect("failed to update sys.path to location of Mercurial modules");
       
   112 }
       
   113 
       
   114 fn run() -> Result<(), i32> {
       
   115     let env = get_environment();
       
   116 
       
   117     //println!("{:?}", env);
       
   118 
       
   119     // Tell Python where it is installed.
       
   120     set_python_home(&env);
       
   121 
       
   122     // Set program name. The backing memory needs to live for the duration of the
       
   123     // interpreter.
       
   124     //
       
   125     // TODO consider storing this in a static or associating with lifetime of
       
   126     // the Python interpreter.
       
   127     //
       
   128     // Yes, we use the path to the Python interpreter not argv[0] here. The
       
   129     // reason is because Python uses the given path to find the location of
       
   130     // Python files. Apparently we could define our own ``Py_GetPath()``
       
   131     // implementation. But this may require statically linking Python, which is
       
   132     // not desirable.
       
   133     let program_name = cstring_from_os(&env.python_exe).as_ptr();
       
   134     unsafe {
       
   135         python27_sys::Py_SetProgramName(program_name as *mut i8);
       
   136     }
       
   137 
       
   138     unsafe {
       
   139         python27_sys::Py_Initialize();
       
   140     }
       
   141 
       
   142     // https://docs.python.org/2/c-api/init.html#c.PySys_SetArgvEx has important
       
   143     // usage information about PySys_SetArgvEx:
       
   144     //
       
   145     // * It says the first argument should be the script that is being executed.
       
   146     //   If not a script, it can be empty. We are definitely not a script.
       
   147     //   However, parts of Mercurial do look at sys.argv[0]. So we need to set
       
   148     //   something here.
       
   149     //
       
   150     // * When embedding Python, we should use ``PySys_SetArgvEx()`` and set
       
   151     //   ``updatepath=0`` for security reasons. Essentially, Python's default
       
   152     //   logic will treat an empty argv[0] in a manner that could result in
       
   153     //   sys.path picking up directories it shouldn't and this could lead to
       
   154     //   loading untrusted modules.
       
   155 
       
   156     // env::args() will panic if it sees a non-UTF-8 byte sequence. And
       
   157     // Mercurial supports arbitrary encodings of input data. So we need to
       
   158     // use OS-specific mechanisms to get the raw bytes without UTF-8
       
   159     // interference.
       
   160     let args = args_to_cstrings();
       
   161     let argv: Vec<*const c_char> = args.iter().map(|a| a.as_ptr()).collect();
       
   162 
       
   163     unsafe {
       
   164         python27_sys::PySys_SetArgvEx(args.len() as c_int, argv.as_ptr() as *mut *mut i8, 0);
       
   165     }
       
   166 
       
   167     let result;
       
   168     {
       
   169         // These need to be dropped before we call Py_Finalize(). Hence the
       
   170         // block.
       
   171         let gil = Python::acquire_gil();
       
   172         let py = gil.python();
       
   173 
       
   174         // Mercurial code could call sys.exit(), which will call exit()
       
   175         // itself. So this may not return.
       
   176         // TODO this may cause issues on Windows due to the CRT mismatch.
       
   177         // Investigate if we can intercept sys.exit() or SystemExit() to
       
   178         // ensure we handle process exit.
       
   179         result = match run_py(&env, py) {
       
   180             // Print unhandled exceptions and exit code 255, as this is what
       
   181             // `python` does.
       
   182             Err(err) => {
       
   183                 err.print(py);
       
   184                 Err(255)
       
   185             }
       
   186             Ok(()) => Ok(()),
       
   187         };
       
   188     }
       
   189 
       
   190     unsafe {
       
   191         python27_sys::Py_Finalize();
       
   192     }
       
   193 
       
   194     result
       
   195 }
       
   196 
       
   197 fn run_py(env: &Environment, py: Python) -> PyResult<()> {
       
   198     let sys_mod = py.import("sys").unwrap();
       
   199 
       
   200     update_modules_path(&env, py, &sys_mod);
       
   201 
       
   202     // TODO consider a better error message on failure to import.
       
   203     let demand_mod = py.import("hgdemandimport")?;
       
   204     demand_mod.call(py, "enable", NoArgs, None)?;
       
   205 
       
   206     let dispatch_mod = py.import("mercurial.dispatch")?;
       
   207     dispatch_mod.call(py, "run", NoArgs, None)?;
       
   208 
       
   209     Ok(())
       
   210 }
       
   211 
    10 
   212 fn main() {
    11 fn main() {
   213     let exit_code = match run() {
    12     // The following code is in a block so the MainPythonInterpreter is destroyed in an
   214         Err(err) => err,
    13     // orderly manner, before process exit.
   215         Ok(()) => 0,
    14     let code = {
       
    15         // Load the default Python configuration as derived by the PyOxidizer config
       
    16         // file used at build time.
       
    17         let config = default_python_config();
       
    18 
       
    19         // Construct a new Python interpreter using that config, handling any errors
       
    20         // from construction.
       
    21         match MainPythonInterpreter::new(config) {
       
    22             Ok(mut interp) => {
       
    23                 // And run it using the default run configuration as specified by the
       
    24                 // configuration. If an uncaught Python exception is raised, handle it.
       
    25                 // This includes the special SystemExit, which is a request to terminate the
       
    26                 // process.
       
    27                 interp.run_as_main()
       
    28             }
       
    29             Err(msg) => {
       
    30                 eprintln!("{}", msg);
       
    31                 1
       
    32             }
       
    33         }
   216     };
    34     };
   217 
    35 
   218     std::process::exit(exit_code);
    36     // And exit the process according to code execution results.
       
    37     std::process::exit(code);
   219 }
    38 }