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