Mercurial > hg
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 } |