comparison 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
comparison
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 }