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