Show More
@@ -0,0 +1,7 b'' | |||||
|
1 | # Rust builds with a modern MSVC and uses a newer CRT. | |||
|
2 | # Python 2.7 has a shared library dependency on an older CRT (msvcr90.dll). | |||
|
3 | # We statically link the modern CRT to avoid multiple msvcr*.dll libraries | |||
|
4 | # being loaded and Python possibly picking up symbols from the newer runtime | |||
|
5 | # (which would be loaded first). | |||
|
6 | [target.'cfg(target_os = "windows")'] | |||
|
7 | rustflags = ["-Ctarget-feature=+crt-static"] |
@@ -0,0 +1,78 b'' | |||||
|
1 | =================== | |||
|
2 | Mercurial Rust Code | |||
|
3 | =================== | |||
|
4 | ||||
|
5 | This directory contains various Rust code for the Mercurial project. | |||
|
6 | ||||
|
7 | The top-level ``Cargo.toml`` file defines a workspace containing | |||
|
8 | all primary Mercurial crates. | |||
|
9 | ||||
|
10 | Building | |||
|
11 | ======== | |||
|
12 | ||||
|
13 | To build the Rust components:: | |||
|
14 | ||||
|
15 | $ cargo build | |||
|
16 | ||||
|
17 | If you prefer a non-debug / release configuration:: | |||
|
18 | ||||
|
19 | $ cargo build --release | |||
|
20 | ||||
|
21 | Features | |||
|
22 | -------- | |||
|
23 | ||||
|
24 | The following Cargo features are available: | |||
|
25 | ||||
|
26 | localdev (default) | |||
|
27 | Produce files that work with an in-source-tree build. | |||
|
28 | ||||
|
29 | In this mode, the build finds and uses a ``python2.7`` binary from | |||
|
30 | ``PATH``. The ``hg`` binary assumes it runs from ``rust/target/<target>hg`` | |||
|
31 | and it finds Mercurial files at ``dirname($0)/../../../``. | |||
|
32 | ||||
|
33 | Build Mechanism | |||
|
34 | --------------- | |||
|
35 | ||||
|
36 | The produced ``hg`` binary is *bound* to a CPython installation. The | |||
|
37 | binary links against and loads a CPython library that is discovered | |||
|
38 | at build time (by a ``build.rs`` Cargo build script). The Python | |||
|
39 | standard library defined by this CPython installation is also used. | |||
|
40 | ||||
|
41 | Finding the appropriate CPython installation to use is done by | |||
|
42 | the ``python27-sys`` crate's ``build.rs``. Its search order is:: | |||
|
43 | ||||
|
44 | 1. ``PYTHON_SYS_EXECUTABLE`` environment variable. | |||
|
45 | 2. ``python`` executable on ``PATH`` | |||
|
46 | 3. ``python2`` executable on ``PATH`` | |||
|
47 | 4. ``python2.7`` executable on ``PATH`` | |||
|
48 | ||||
|
49 | Additional verification of the found Python will be performed by our | |||
|
50 | ``build.rs`` to ensure it meets Mercurial's requirements. | |||
|
51 | ||||
|
52 | Details about the build-time configured Python are built into the | |||
|
53 | produced ``hg`` binary. This means that a built ``hg`` binary is only | |||
|
54 | suitable for a specific, well-defined role. These roles are controlled | |||
|
55 | by Cargo features (see above). | |||
|
56 | ||||
|
57 | Running | |||
|
58 | ======= | |||
|
59 | ||||
|
60 | The ``hgcli`` crate produces an ``hg`` binary. You can run this binary | |||
|
61 | via ``cargo run``:: | |||
|
62 | ||||
|
63 | $ cargo run --manifest-path hgcli/Cargo.toml | |||
|
64 | ||||
|
65 | Or directly:: | |||
|
66 | ||||
|
67 | $ target/debug/hg | |||
|
68 | $ target/release/hg | |||
|
69 | ||||
|
70 | You can also run the test harness with this binary:: | |||
|
71 | ||||
|
72 | $ ./run-tests.py --with-hg ../rust/target/debug/hg | |||
|
73 | ||||
|
74 | .. note:: | |||
|
75 | ||||
|
76 | Integration with the test harness is still preliminary. Remember to | |||
|
77 | ``cargo build`` after changes because the test harness doesn't yet | |||
|
78 | automatically build Rust code. |
@@ -0,0 +1,127 b'' | |||||
|
1 | [[package]] | |||
|
2 | name = "aho-corasick" | |||
|
3 | version = "0.5.3" | |||
|
4 | source = "registry+https://github.com/rust-lang/crates.io-index" | |||
|
5 | dependencies = [ | |||
|
6 | "memchr 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", | |||
|
7 | ] | |||
|
8 | ||||
|
9 | [[package]] | |||
|
10 | name = "cpython" | |||
|
11 | version = "0.1.0" | |||
|
12 | source = "git+https://github.com/indygreg/rust-cpython.git?rev=c90d65cf84abfffce7ef54476bbfed56017a2f52#c90d65cf84abfffce7ef54476bbfed56017a2f52" | |||
|
13 | dependencies = [ | |||
|
14 | "libc 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)", | |||
|
15 | "num-traits 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)", | |||
|
16 | "python27-sys 0.1.2 (git+https://github.com/indygreg/rust-cpython.git?rev=c90d65cf84abfffce7ef54476bbfed56017a2f52)", | |||
|
17 | ] | |||
|
18 | ||||
|
19 | [[package]] | |||
|
20 | name = "hgcli" | |||
|
21 | version = "0.1.0" | |||
|
22 | dependencies = [ | |||
|
23 | "cpython 0.1.0 (git+https://github.com/indygreg/rust-cpython.git?rev=c90d65cf84abfffce7ef54476bbfed56017a2f52)", | |||
|
24 | "libc 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)", | |||
|
25 | "python27-sys 0.1.2 (git+https://github.com/indygreg/rust-cpython.git?rev=c90d65cf84abfffce7ef54476bbfed56017a2f52)", | |||
|
26 | ] | |||
|
27 | ||||
|
28 | [[package]] | |||
|
29 | name = "kernel32-sys" | |||
|
30 | version = "0.2.2" | |||
|
31 | source = "registry+https://github.com/rust-lang/crates.io-index" | |||
|
32 | dependencies = [ | |||
|
33 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", | |||
|
34 | "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", | |||
|
35 | ] | |||
|
36 | ||||
|
37 | [[package]] | |||
|
38 | name = "libc" | |||
|
39 | version = "0.2.34" | |||
|
40 | source = "registry+https://github.com/rust-lang/crates.io-index" | |||
|
41 | ||||
|
42 | [[package]] | |||
|
43 | name = "memchr" | |||
|
44 | version = "0.1.11" | |||
|
45 | source = "registry+https://github.com/rust-lang/crates.io-index" | |||
|
46 | dependencies = [ | |||
|
47 | "libc 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)", | |||
|
48 | ] | |||
|
49 | ||||
|
50 | [[package]] | |||
|
51 | name = "num-traits" | |||
|
52 | version = "0.1.41" | |||
|
53 | source = "registry+https://github.com/rust-lang/crates.io-index" | |||
|
54 | ||||
|
55 | [[package]] | |||
|
56 | name = "python27-sys" | |||
|
57 | version = "0.1.2" | |||
|
58 | source = "git+https://github.com/indygreg/rust-cpython.git?rev=c90d65cf84abfffce7ef54476bbfed56017a2f52#c90d65cf84abfffce7ef54476bbfed56017a2f52" | |||
|
59 | dependencies = [ | |||
|
60 | "libc 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)", | |||
|
61 | "regex 0.1.80 (registry+https://github.com/rust-lang/crates.io-index)", | |||
|
62 | ] | |||
|
63 | ||||
|
64 | [[package]] | |||
|
65 | name = "regex" | |||
|
66 | version = "0.1.80" | |||
|
67 | source = "registry+https://github.com/rust-lang/crates.io-index" | |||
|
68 | dependencies = [ | |||
|
69 | "aho-corasick 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", | |||
|
70 | "memchr 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", | |||
|
71 | "regex-syntax 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", | |||
|
72 | "thread_local 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)", | |||
|
73 | "utf8-ranges 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", | |||
|
74 | ] | |||
|
75 | ||||
|
76 | [[package]] | |||
|
77 | name = "regex-syntax" | |||
|
78 | version = "0.3.9" | |||
|
79 | source = "registry+https://github.com/rust-lang/crates.io-index" | |||
|
80 | ||||
|
81 | [[package]] | |||
|
82 | name = "thread-id" | |||
|
83 | version = "2.0.0" | |||
|
84 | source = "registry+https://github.com/rust-lang/crates.io-index" | |||
|
85 | dependencies = [ | |||
|
86 | "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", | |||
|
87 | "libc 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)", | |||
|
88 | ] | |||
|
89 | ||||
|
90 | [[package]] | |||
|
91 | name = "thread_local" | |||
|
92 | version = "0.2.7" | |||
|
93 | source = "registry+https://github.com/rust-lang/crates.io-index" | |||
|
94 | dependencies = [ | |||
|
95 | "thread-id 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)", | |||
|
96 | ] | |||
|
97 | ||||
|
98 | [[package]] | |||
|
99 | name = "utf8-ranges" | |||
|
100 | version = "0.1.3" | |||
|
101 | source = "registry+https://github.com/rust-lang/crates.io-index" | |||
|
102 | ||||
|
103 | [[package]] | |||
|
104 | name = "winapi" | |||
|
105 | version = "0.2.8" | |||
|
106 | source = "registry+https://github.com/rust-lang/crates.io-index" | |||
|
107 | ||||
|
108 | [[package]] | |||
|
109 | name = "winapi-build" | |||
|
110 | version = "0.1.1" | |||
|
111 | source = "registry+https://github.com/rust-lang/crates.io-index" | |||
|
112 | ||||
|
113 | [metadata] | |||
|
114 | "checksum aho-corasick 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ca972c2ea5f742bfce5687b9aef75506a764f61d37f8f649047846a9686ddb66" | |||
|
115 | "checksum cpython 0.1.0 (git+https://github.com/indygreg/rust-cpython.git?rev=c90d65cf84abfffce7ef54476bbfed56017a2f52)" = "<none>" | |||
|
116 | "checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" | |||
|
117 | "checksum libc 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)" = "36fbc8a8929c632868295d0178dd8f63fc423fd7537ad0738372bd010b3ac9b0" | |||
|
118 | "checksum memchr 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "d8b629fb514376c675b98c1421e80b151d3817ac42d7c667717d282761418d20" | |||
|
119 | "checksum num-traits 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)" = "cacfcab5eb48250ee7d0c7896b51a2c5eec99c1feea5f32025635f5ae4b00070" | |||
|
120 | "checksum python27-sys 0.1.2 (git+https://github.com/indygreg/rust-cpython.git?rev=c90d65cf84abfffce7ef54476bbfed56017a2f52)" = "<none>" | |||
|
121 | "checksum regex 0.1.80 (registry+https://github.com/rust-lang/crates.io-index)" = "4fd4ace6a8cf7860714a2c2280d6c1f7e6a413486c13298bbc86fd3da019402f" | |||
|
122 | "checksum regex-syntax 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "f9ec002c35e86791825ed294b50008eea9ddfc8def4420124fbc6b08db834957" | |||
|
123 | "checksum thread-id 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a9539db560102d1cef46b8b78ce737ff0bb64e7e18d35b2a5688f7d097d0ff03" | |||
|
124 | "checksum thread_local 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "8576dbbfcaef9641452d5cf0df9b0e7eeab7694956dd33bb61515fb8f18cfdd5" | |||
|
125 | "checksum utf8-ranges 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a1ca13c08c41c9c3e04224ed9ff80461d97e121589ff27c753a16cb10830ae0f" | |||
|
126 | "checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" | |||
|
127 | "checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" |
@@ -0,0 +1,35 b'' | |||||
|
1 | [package] | |||
|
2 | name = "hgcli" | |||
|
3 | version = "0.1.0" | |||
|
4 | authors = ["Gregory Szorc <gregory.szorc@gmail.com>"] | |||
|
5 | license = "GPL-2.0" | |||
|
6 | ||||
|
7 | build = "build.rs" | |||
|
8 | ||||
|
9 | [[bin]] | |||
|
10 | name = "hg" | |||
|
11 | path = "src/main.rs" | |||
|
12 | ||||
|
13 | [features] | |||
|
14 | # localdev: detect Python in PATH and use files from source checkout. | |||
|
15 | default = ["localdev"] | |||
|
16 | localdev = [] | |||
|
17 | ||||
|
18 | [dependencies] | |||
|
19 | libc = "0.2.34" | |||
|
20 | ||||
|
21 | # We currently use a custom build of cpython and python27-sys with the | |||
|
22 | # following changes: | |||
|
23 | # * GILGuard call of prepare_freethreaded_python() is removed. | |||
|
24 | # TODO switch to official release when our changes are incorporated. | |||
|
25 | [dependencies.cpython] | |||
|
26 | version = "0.1" | |||
|
27 | default-features = false | |||
|
28 | features = ["python27-sys"] | |||
|
29 | git = "https://github.com/indygreg/rust-cpython.git" | |||
|
30 | rev = "c90d65cf84abfffce7ef54476bbfed56017a2f52" | |||
|
31 | ||||
|
32 | [dependencies.python27-sys] | |||
|
33 | version = "0.1.2" | |||
|
34 | git = "https://github.com/indygreg/rust-cpython.git" | |||
|
35 | rev = "c90d65cf84abfffce7ef54476bbfed56017a2f52" |
@@ -0,0 +1,128 b'' | |||||
|
1 | // build.rs -- Configure build environment for `hgcli` Rust package. | |||
|
2 | // | |||
|
3 | // Copyright 2017 Gregory Szorc <gregory.szorc@gmail.com> | |||
|
4 | // | |||
|
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. | |||
|
7 | ||||
|
8 | use std::collections::HashMap; | |||
|
9 | use std::env; | |||
|
10 | use std::path::Path; | |||
|
11 | #[cfg(target_os = "windows")] | |||
|
12 | use std::path::PathBuf; | |||
|
13 | ||||
|
14 | use std::process::Command; | |||
|
15 | ||||
|
16 | struct PythonConfig { | |||
|
17 | python: String, | |||
|
18 | config: HashMap<String, String>, | |||
|
19 | } | |||
|
20 | ||||
|
21 | fn get_python_config() -> PythonConfig { | |||
|
22 | // The python27-sys crate exports a Cargo variable defining the full | |||
|
23 | // path to the interpreter being used. | |||
|
24 | let python = env::var("DEP_PYTHON27_PYTHON_INTERPRETER").expect( | |||
|
25 | "Missing DEP_PYTHON27_PYTHON_INTERPRETER; bad python27-sys crate?", | |||
|
26 | ); | |||
|
27 | ||||
|
28 | if !Path::new(&python).exists() { | |||
|
29 | panic!( | |||
|
30 | "Python interpreter {} does not exist; this should never happen", | |||
|
31 | python | |||
|
32 | ); | |||
|
33 | } | |||
|
34 | ||||
|
35 | // This is a bit hacky but it gets the job done. | |||
|
36 | let separator = "SEPARATOR STRING"; | |||
|
37 | ||||
|
38 | let script = "import sysconfig; \ | |||
|
39 | c = sysconfig.get_config_vars(); \ | |||
|
40 | print('SEPARATOR STRING'.join('%s=%s' % i for i in c.items()))"; | |||
|
41 | ||||
|
42 | let mut command = Command::new(&python); | |||
|
43 | command.arg("-c").arg(script); | |||
|
44 | ||||
|
45 | let out = command.output().unwrap(); | |||
|
46 | ||||
|
47 | if !out.status.success() { | |||
|
48 | panic!( | |||
|
49 | "python script failed: {}", | |||
|
50 | String::from_utf8_lossy(&out.stderr) | |||
|
51 | ); | |||
|
52 | } | |||
|
53 | ||||
|
54 | let stdout = String::from_utf8_lossy(&out.stdout); | |||
|
55 | let mut m = HashMap::new(); | |||
|
56 | ||||
|
57 | for entry in stdout.split(separator) { | |||
|
58 | let mut parts = entry.splitn(2, "="); | |||
|
59 | let key = parts.next().unwrap(); | |||
|
60 | let value = parts.next().unwrap(); | |||
|
61 | m.insert(String::from(key), String::from(value)); | |||
|
62 | } | |||
|
63 | ||||
|
64 | PythonConfig { | |||
|
65 | python: python, | |||
|
66 | config: m, | |||
|
67 | } | |||
|
68 | } | |||
|
69 | ||||
|
70 | #[cfg(not(target_os = "windows"))] | |||
|
71 | fn have_shared(config: &PythonConfig) -> bool { | |||
|
72 | match config.config.get("Py_ENABLE_SHARED") { | |||
|
73 | Some(value) => value == "1", | |||
|
74 | None => false, | |||
|
75 | } | |||
|
76 | } | |||
|
77 | ||||
|
78 | #[cfg(target_os = "windows")] | |||
|
79 | fn have_shared(config: &PythonConfig) -> bool { | |||
|
80 | // python27.dll should exist next to python2.7.exe. | |||
|
81 | let mut dll = PathBuf::from(&config.python); | |||
|
82 | dll.pop(); | |||
|
83 | dll.push("python27.dll"); | |||
|
84 | ||||
|
85 | return dll.exists(); | |||
|
86 | } | |||
|
87 | ||||
|
88 | const REQUIRED_CONFIG_FLAGS: [&'static str; 2] = ["Py_USING_UNICODE", "WITH_THREAD"]; | |||
|
89 | ||||
|
90 | fn main() { | |||
|
91 | let config = get_python_config(); | |||
|
92 | ||||
|
93 | println!("Using Python: {}", config.python); | |||
|
94 | println!("cargo:rustc-env=PYTHON_INTERPRETER={}", config.python); | |||
|
95 | ||||
|
96 | let prefix = config.config.get("prefix").unwrap(); | |||
|
97 | ||||
|
98 | println!("Prefix: {}", prefix); | |||
|
99 | ||||
|
100 | // TODO Windows builds don't expose these config flags. Figure out another | |||
|
101 | // way. | |||
|
102 | #[cfg(not(target_os = "windows"))] | |||
|
103 | for key in REQUIRED_CONFIG_FLAGS.iter() { | |||
|
104 | let result = match config.config.get(*key) { | |||
|
105 | Some(value) => value == "1", | |||
|
106 | None => false, | |||
|
107 | }; | |||
|
108 | ||||
|
109 | if !result { | |||
|
110 | panic!("Detected Python requires feature {}", key); | |||
|
111 | } | |||
|
112 | } | |||
|
113 | ||||
|
114 | // We need a Python shared library. | |||
|
115 | if !have_shared(&config) { | |||
|
116 | panic!("Detected Python lacks a shared library, which is required"); | |||
|
117 | } | |||
|
118 | ||||
|
119 | let ucs4 = match config.config.get("Py_UNICODE_SIZE") { | |||
|
120 | Some(value) => value == "4", | |||
|
121 | None => false, | |||
|
122 | }; | |||
|
123 | ||||
|
124 | if !ucs4 { | |||
|
125 | #[cfg(not(target_os = "windows"))] | |||
|
126 | panic!("Detected Python doesn't support UCS-4 code points"); | |||
|
127 | } | |||
|
128 | } |
@@ -0,0 +1,222 b'' | |||||
|
1 | // main.rs -- Main routines for `hg` program | |||
|
2 | // | |||
|
3 | // Copyright 2017 Gregory Szorc <gregory.szorc@gmail.com> | |||
|
4 | // | |||
|
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. | |||
|
7 | ||||
|
8 | extern crate libc; | |||
|
9 | extern crate cpython; | |||
|
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::path::PathBuf; | |||
|
17 | use std::ffi::CString; | |||
|
18 | #[cfg(target_family = "unix")] | |||
|
19 | use std::os::unix::ffi::OsStringExt; | |||
|
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, argv starts as an array of char*. So it is easy to convert | |||
|
66 | // to C strings. | |||
|
67 | #[cfg(target_family = "unix")] | |||
|
68 | fn args_to_cstrings() -> Vec<CString> { | |||
|
69 | env::args_os() | |||
|
70 | .map(|a| CString::new(a.into_vec()).unwrap()) | |||
|
71 | .collect() | |||
|
72 | } | |||
|
73 | ||||
|
74 | // TODO Windows support is incomplete. We should either use env::args_os() | |||
|
75 | // (or call into GetCommandLineW() + CommandLinetoArgvW()), convert these to | |||
|
76 | // PyUnicode instances, and pass these into Python/Mercurial outside the | |||
|
77 | // standard PySys_SetArgvEx() mechanism. This will allow us to preserve the | |||
|
78 | // raw bytes (since PySys_SetArgvEx() is based on char* and can drop wchar | |||
|
79 | // data. | |||
|
80 | // | |||
|
81 | // For now, we use env::args(). This will choke on invalid UTF-8 arguments. | |||
|
82 | // But it is better than nothing. | |||
|
83 | #[cfg(target_family = "windows")] | |||
|
84 | fn args_to_cstrings() -> Vec<CString> { | |||
|
85 | env::args().map(|a| CString::new(a).unwrap()).collect() | |||
|
86 | } | |||
|
87 | ||||
|
88 | fn set_python_home(env: &Environment) { | |||
|
89 | let raw = CString::new(env.python_home.to_str().unwrap()) | |||
|
90 | .unwrap() | |||
|
91 | .into_raw(); | |||
|
92 | unsafe { | |||
|
93 | python27_sys::Py_SetPythonHome(raw); | |||
|
94 | } | |||
|
95 | } | |||
|
96 | ||||
|
97 | fn update_encoding(_py: Python, _sys_mod: &PyModule) { | |||
|
98 | // Call sys.setdefaultencoding("undefined") if HGUNICODEPEDANTRY is set. | |||
|
99 | let pedantry = env::var("HGUNICODEPEDANTRY").is_ok(); | |||
|
100 | ||||
|
101 | if pedantry { | |||
|
102 | // site.py removes the sys.setdefaultencoding attribute. So we need | |||
|
103 | // to reload the module to get a handle on it. This is a lesser | |||
|
104 | // used feature and we'll support this later. | |||
|
105 | // TODO support this | |||
|
106 | panic!("HGUNICODEPEDANTRY is not yet supported"); | |||
|
107 | } | |||
|
108 | } | |||
|
109 | ||||
|
110 | fn update_modules_path(env: &Environment, py: Python, sys_mod: &PyModule) { | |||
|
111 | let sys_path = sys_mod.get(py, "path").unwrap(); | |||
|
112 | sys_path | |||
|
113 | .call_method(py, "insert", (0, env.mercurial_modules.to_str()), None) | |||
|
114 | .expect("failed to update sys.path to location of Mercurial modules"); | |||
|
115 | } | |||
|
116 | ||||
|
117 | fn run() -> Result<(), i32> { | |||
|
118 | let env = get_environment(); | |||
|
119 | ||||
|
120 | //println!("{:?}", env); | |||
|
121 | ||||
|
122 | // Tell Python where it is installed. | |||
|
123 | set_python_home(&env); | |||
|
124 | ||||
|
125 | // Set program name. The backing memory needs to live for the duration of the | |||
|
126 | // 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::new(env.python_exe.to_str().unwrap()) | |||
|
134 | .unwrap() | |||
|
135 | .as_ptr(); | |||
|
136 | unsafe { | |||
|
137 | python27_sys::Py_SetProgramName(program_name as *mut i8); | |||
|
138 | } | |||
|
139 | ||||
|
140 | unsafe { | |||
|
141 | python27_sys::Py_Initialize(); | |||
|
142 | } | |||
|
143 | ||||
|
144 | // https://docs.python.org/2/c-api/init.html#c.PySys_SetArgvEx has important | |||
|
145 | // usage information about PySys_SetArgvEx: | |||
|
146 | // | |||
|
147 | // * It says the first argument should be the script that is being executed. | |||
|
148 | // If not a script, it can be empty. We are definitely not a script. | |||
|
149 | // However, parts of Mercurial do look at sys.argv[0]. So we need to set | |||
|
150 | // something here. | |||
|
151 | // | |||
|
152 | // * When embedding Python, we should use ``PySys_SetArgvEx()`` and set | |||
|
153 | // ``updatepath=0`` for security reasons. Essentially, Python's default | |||
|
154 | // logic will treat an empty argv[0] in a manner that could result in | |||
|
155 | // sys.path picking up directories it shouldn't and this could lead to | |||
|
156 | // loading untrusted modules. | |||
|
157 | ||||
|
158 | // env::args() will panic if it sees a non-UTF-8 byte sequence. And | |||
|
159 | // Mercurial supports arbitrary encodings of input data. So we need to | |||
|
160 | // use OS-specific mechanisms to get the raw bytes without UTF-8 | |||
|
161 | // interference. | |||
|
162 | let args = args_to_cstrings(); | |||
|
163 | let argv: Vec<*const c_char> = args.iter().map(|a| a.as_ptr()).collect(); | |||
|
164 | ||||
|
165 | unsafe { | |||
|
166 | python27_sys::PySys_SetArgvEx(args.len() as c_int, argv.as_ptr() as *mut *mut i8, 0); | |||
|
167 | } | |||
|
168 | ||||
|
169 | let result; | |||
|
170 | { | |||
|
171 | // These need to be dropped before we call Py_Finalize(). Hence the | |||
|
172 | // block. | |||
|
173 | let gil = Python::acquire_gil(); | |||
|
174 | let py = gil.python(); | |||
|
175 | ||||
|
176 | // Mercurial code could call sys.exit(), which will call exit() | |||
|
177 | // itself. So this may not return. | |||
|
178 | // TODO this may cause issues on Windows due to the CRT mismatch. | |||
|
179 | // Investigate if we can intercept sys.exit() or SystemExit() to | |||
|
180 | // ensure we handle process exit. | |||
|
181 | result = match run_py(&env, py) { | |||
|
182 | // Print unhandled exceptions and exit code 255, as this is what | |||
|
183 | // `python` does. | |||
|
184 | Err(err) => { | |||
|
185 | err.print(py); | |||
|
186 | Err(255) | |||
|
187 | } | |||
|
188 | Ok(()) => Ok(()), | |||
|
189 | }; | |||
|
190 | } | |||
|
191 | ||||
|
192 | unsafe { | |||
|
193 | python27_sys::Py_Finalize(); | |||
|
194 | } | |||
|
195 | ||||
|
196 | result | |||
|
197 | } | |||
|
198 | ||||
|
199 | fn run_py(env: &Environment, py: Python) -> PyResult<()> { | |||
|
200 | let sys_mod = py.import("sys").unwrap(); | |||
|
201 | ||||
|
202 | update_encoding(py, &sys_mod); | |||
|
203 | update_modules_path(&env, py, &sys_mod); | |||
|
204 | ||||
|
205 | // TODO consider a better error message on failure to import. | |||
|
206 | let demand_mod = py.import("hgdemandimport")?; | |||
|
207 | demand_mod.call(py, "enable", NoArgs, None)?; | |||
|
208 | ||||
|
209 | let dispatch_mod = py.import("mercurial.dispatch")?; | |||
|
210 | dispatch_mod.call(py, "run", NoArgs, None)?; | |||
|
211 | ||||
|
212 | Ok(()) | |||
|
213 | } | |||
|
214 | ||||
|
215 | fn main() { | |||
|
216 | let exit_code = match run() { | |||
|
217 | Err(err) => err, | |||
|
218 | Ok(()) => 0, | |||
|
219 | }; | |||
|
220 | ||||
|
221 | std::process::exit(exit_code); | |||
|
222 | } |
@@ -56,6 +56,8 b' i18n/hg.pot' | |||||
56 | locale/*/LC_MESSAGES/hg.mo |
|
56 | locale/*/LC_MESSAGES/hg.mo | |
57 | hgext/__index__.py |
|
57 | hgext/__index__.py | |
58 |
|
58 | |||
|
59 | rust/target/ | |||
|
60 | ||||
59 | # Generated wheels |
|
61 | # Generated wheels | |
60 | wheelhouse/ |
|
62 | wheelhouse/ | |
61 |
|
63 |
@@ -2435,12 +2435,27 b' class TestRunner(object):' | |||||
2435 | self._tmpbindir = os.path.join(self._hgtmp, b'install', b'bin') |
|
2435 | self._tmpbindir = os.path.join(self._hgtmp, b'install', b'bin') | |
2436 | os.makedirs(self._tmpbindir) |
|
2436 | os.makedirs(self._tmpbindir) | |
2437 |
|
2437 | |||
2438 | # This looks redundant with how Python initializes sys.path from |
|
2438 | normbin = os.path.normpath(os.path.abspath(whg)) | |
2439 | # the location of the script being executed. Needed because the |
|
2439 | normbin = normbin.replace(os.sep.encode('ascii'), b'/') | |
2440 | # "hg" specified by --with-hg is not the only Python script |
|
2440 | ||
2441 | # executed in the test suite that needs to import 'mercurial' |
|
2441 | # Other Python scripts in the test harness need to | |
2442 | # ... which means it's not really redundant at all. |
|
2442 | # `import mercurial`. If `hg` is a Python script, we assume | |
2443 | self._pythondir = self._bindir |
|
2443 | # the Mercurial modules are relative to its path and tell the tests | |
|
2444 | # to load Python modules from its directory. | |||
|
2445 | with open(whg, 'rb') as fh: | |||
|
2446 | initial = fh.read(1024) | |||
|
2447 | ||||
|
2448 | if re.match(b'#!.*python', initial): | |||
|
2449 | self._pythondir = self._bindir | |||
|
2450 | # If it looks like our in-repo Rust binary, use the source root. | |||
|
2451 | # This is a bit hacky. But rhg is still not supported outside the | |||
|
2452 | # source directory. So until it is, do the simple thing. | |||
|
2453 | elif re.search(b'|/rust/target/[^/]+/hg', normbin): | |||
|
2454 | self._pythondir = os.path.dirname(self._testdir) | |||
|
2455 | # Fall back to the legacy behavior. | |||
|
2456 | else: | |||
|
2457 | self._pythondir = self._bindir | |||
|
2458 | ||||
2444 | else: |
|
2459 | else: | |
2445 | self._installdir = os.path.join(self._hgtmp, b"install") |
|
2460 | self._installdir = os.path.join(self._hgtmp, b"install") | |
2446 | self._bindir = os.path.join(self._installdir, b"bin") |
|
2461 | self._bindir = os.path.join(self._installdir, b"bin") |
General Comments 0
You need to be logged in to leave comments.
Login now