Show More
@@ -0,0 +1,50 | |||||
|
1 | # Oxidized Mercurial | |||
|
2 | ||||
|
3 | This project provides a Rust implementation of the Mercurial (`hg`) | |||
|
4 | version control tool. | |||
|
5 | ||||
|
6 | Under the hood, the project uses | |||
|
7 | [PyOxidizer](https://github.com/indygreg/PyOxidizer) to embed a Python | |||
|
8 | interpreter in a binary built with Rust. At run-time, the Rust `fn main()` | |||
|
9 | is called and Rust code handles initial process startup. An in-process | |||
|
10 | Python interpreter is started (if needed) to provide additional | |||
|
11 | functionality. | |||
|
12 | ||||
|
13 | # Building | |||
|
14 | ||||
|
15 | This project currently requires an unreleased version of PyOxidizer | |||
|
16 | (0.7.0-pre). For best results, build the exact PyOxidizer commit | |||
|
17 | as defined in the `pyoxidizer.bzl` file: | |||
|
18 | ||||
|
19 | $ git clone https://github.com/indygreg/PyOxidizer.git | |||
|
20 | $ cd PyOxidizer | |||
|
21 | $ git checkout <Git commit from pyoxidizer.bzl> | |||
|
22 | $ cargo build --release | |||
|
23 | ||||
|
24 | Then build this Rust project using the built `pyoxidizer` executable:: | |||
|
25 | ||||
|
26 | $ /path/to/pyoxidizer/target/release/pyoxidizer build | |||
|
27 | ||||
|
28 | If all goes according to plan, there should be an assembled application | |||
|
29 | under `build/<arch>/debug/app/` with an `hg` executable: | |||
|
30 | ||||
|
31 | $ build/x86_64-unknown-linux-gnu/debug/app/hg version | |||
|
32 | Mercurial Distributed SCM (version 5.3.1+433-f99cd77d53dc+20200331) | |||
|
33 | (see https://mercurial-scm.org for more information) | |||
|
34 | ||||
|
35 | Copyright (C) 2005-2020 Matt Mackall and others | |||
|
36 | This is free software; see the source for copying conditions. There is NO | |||
|
37 | warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. | |||
|
38 | ||||
|
39 | # Running Tests | |||
|
40 | ||||
|
41 | To run tests with a built `hg` executable, you can use the `--with-hg` | |||
|
42 | argument to `run-tests.py`. But there's a wrinkle: many tests run custom | |||
|
43 | Python scripts that need to `import` modules provided by Mercurial. Since | |||
|
44 | these modules are embedded in the produced `hg` executable, a regular | |||
|
45 | Python interpreter can't access them! To work around this, set `PYTHONPATH` | |||
|
46 | to the Mercurial source directory. e.g.: | |||
|
47 | ||||
|
48 | $ cd /path/to/hg/src/tests | |||
|
49 | $ PYTHONPATH=`pwd`/.. python3.7 run-tests.py \ | |||
|
50 | --with-hg `pwd`/../rust/hgcli/build/x86_64-unknown-linux-gnu/debug/app/hg |
@@ -4,8 +4,12 version = "0.1.0" | |||||
4 | build = "build.rs" |
|
4 | build = "build.rs" | |
5 | authors = ["Gregory Szorc <gregory.szorc@gmail.com>"] |
|
5 | authors = ["Gregory Szorc <gregory.szorc@gmail.com>"] | |
6 | edition = "2018" |
|
6 | edition = "2018" | |
|
7 | license = "GPL-2.0" | |||
|
8 | readme = "README.md" | |||
7 |
|
9 | |||
8 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html |
|
10 | [[bin]] | |
|
11 | name = "hg" | |||
|
12 | path = "src/main.rs" | |||
9 |
|
13 | |||
10 | [dependencies] |
|
14 | [dependencies] | |
11 | jemallocator-global = { version = "0.3", optional = true } |
|
15 | jemallocator-global = { version = "0.3", optional = true } |
@@ -1,147 +1,53 | |||||
1 | # This file defines how PyOxidizer application building and packaging is |
|
1 | ROOT = CWD + "/../.." | |
2 | # performed. See the pyoxidizer crate's documentation for extensive |
|
|||
3 | # documentation on this file format. |
|
|||
4 |
|
2 | |||
5 | # Obtain the default PythonDistribution for our build target. We link |
|
3 | def make_exe(): | |
6 | # this distribution into our produced executable and extract the Python |
|
4 | dist = default_python_distribution() | |
7 | # standard library from it. |
|
5 | ||
8 | def make_dist(): |
|
6 | code = "import hgdemandimport; hgdemandimport.enable(); from mercurial import dispatch; dispatch.run()" | |
9 | return default_python_distribution() |
|
|||
10 |
|
7 | |||
11 | # Configuration files consist of functions which define build "targets." |
|
8 | config = PythonInterpreterConfig( | |
12 | # This function creates a Python executable and installs it in a destination |
|
9 | raw_allocator = "system", | |
13 | # directory. |
|
10 | run_eval = code, | |
14 | def make_exe(dist): |
|
11 | # We want to let the user load extensions from the file system | |
15 | # This variable defines the configuration of the |
|
12 | filesystem_importer = True, | |
16 | # embedded Python interpreter. |
|
13 | # We need this to make resourceutil happy, since it looks for sys.frozen. | |
17 | python_config = PythonInterpreterConfig( |
|
14 | sys_frozen = True, | |
18 | # bytes_warning=0, |
|
15 | legacy_windows_stdio = True, | |
19 | # dont_write_bytecode=True, |
|
|||
20 | # ignore_environment=True, |
|
|||
21 | # inspect=False, |
|
|||
22 | # interactive=False, |
|
|||
23 | # isolated=False, |
|
|||
24 | # legacy_windows_fs_encoding=False, |
|
|||
25 | # legacy_windows_stdio=False, |
|
|||
26 | # no_site=True, |
|
|||
27 | # no_user_site_directory=True, |
|
|||
28 | # optimize_level=0, |
|
|||
29 | # parser_debug=False, |
|
|||
30 | # stdio_encoding=None, |
|
|||
31 | # unbuffered_stdio=False, |
|
|||
32 | # filesystem_importer=False, |
|
|||
33 | # sys_frozen=False, |
|
|||
34 | # sys_meipass=False, |
|
|||
35 | # sys_paths=None, |
|
|||
36 | # raw_allocator=None, |
|
|||
37 | # terminfo_resolution="dynamic", |
|
|||
38 | # terminfo_dirs=None, |
|
|||
39 | # use_hash_seed=False, |
|
|||
40 | # verbose=0, |
|
|||
41 | # write_modules_directory_env=None, |
|
|||
42 | # run_eval=None, |
|
|||
43 | # run_module=None, |
|
|||
44 | # run_noop=False, |
|
|||
45 | # run_repl=True, |
|
|||
46 | ) |
|
16 | ) | |
47 |
|
17 | |||
48 | # The run_eval, run_module, run_noop, and run_repl arguments are mutually |
|
|||
49 | # exclusive controls over what the interpreter should do once it initializes. |
|
|||
50 | # |
|
|||
51 | # run_eval -- Run the specified string value via `eval()`. |
|
|||
52 | # run_module -- Import the specified module as __main__ and run it. |
|
|||
53 | # run_noop -- Do nothing. |
|
|||
54 | # run_repl -- Start a Python REPL. |
|
|||
55 | # |
|
|||
56 | # These arguments can be ignored if you are providing your own Rust code for |
|
|||
57 | # starting the interpreter, as Rust code has full control over interpreter |
|
|||
58 | # behavior. |
|
|||
59 |
|
||||
60 | # Produce a PythonExecutable from a Python distribution, embedded |
|
|||
61 | # resources, and other options. The returned object represents the |
|
|||
62 | # standalone executable that will be built. |
|
|||
63 | exe = dist.to_python_executable( |
|
18 | exe = dist.to_python_executable( | |
64 |
name = "hg |
|
19 | name = "hg", | |
65 | config = python_config, |
|
20 | resources_policy = "prefer-in-memory-fallback-filesystem-relative:lib", | |
66 | # Embed all extension modules, making this a fully-featured Python. |
|
21 | config = config, | |
|
22 | # Extension may depend on any Python functionality. Include all | |||
|
23 | # extensions. | |||
67 | extension_module_filter = "all", |
|
24 | extension_module_filter = "all", | |
68 |
|
||||
69 | # Only package the minimal set of extension modules needed to initialize |
|
|||
70 | # a Python interpreter. Many common packages in Python's standard |
|
|||
71 | # library won't work with this setting. |
|
|||
72 | #extension_module_filter='minimal', |
|
|||
73 |
|
||||
74 | # Only package extension modules that don't require linking against |
|
|||
75 | # non-Python libraries. e.g. will exclude support for OpenSSL, SQLite3, |
|
|||
76 | # other features that require external libraries. |
|
|||
77 | #extension_module_filter='no-libraries', |
|
|||
78 |
|
||||
79 | # Only package extension modules that don't link against GPL licensed |
|
|||
80 | # libraries. |
|
|||
81 | #extension_module_filter='no-gpl', |
|
|||
82 |
|
||||
83 | # Include Python module sources. This isn't strictly required and it does |
|
|||
84 | # make binary sizes larger. But having the sources can be useful for |
|
|||
85 | # activities such as debugging. |
|
|||
86 | include_sources = True, |
|
|||
87 |
|
||||
88 | # Whether to include non-module resource data/files. |
|
|||
89 | include_resources = False, |
|
|||
90 |
|
||||
91 | # Do not include functionality for testing Python itself. |
|
|||
92 | include_test = False, |
|
|||
93 | ) |
|
25 | ) | |
94 |
|
26 | |||
95 | # Invoke `pip install` with our Python distribution to install a single package. |
|
27 | exe.add_python_resources(dist.pip_install([ROOT])) | |
96 | # `pip_install()` returns objects representing installed files. |
|
|||
97 | # `add_in_memory_python_resources()` adds these objects to the binary, |
|
|||
98 | # marking them for in-memory loading. |
|
|||
99 | #exe.add_in_memory_python_resources(dist.pip_install(["appdirs"])) |
|
|||
100 |
|
28 | |||
101 | # Invoke `pip install` using a requirements file and add the collected resources |
|
29 | return exe | |
102 | # to our binary. |
|
30 | ||
103 | #exe.add_in_memory_python_resources(dist.pip_install(["-r", "requirements.txt"])) |
|
31 | def make_install(exe): | |
|
32 | m = FileManifest() | |||
104 |
|
33 | |||
105 | # Read Python files from a local directory and add them to our embedded |
|
34 | # `hg` goes in root directory. | |
106 | # context, taking just the resources belonging to the `foo` and `bar` |
|
35 | m.add_python_resource(".", exe) | |
107 | # Python packages. |
|
|||
108 | #exe.add_in_memory_python_resources(dist.read_package_root( |
|
|||
109 | # path="/src/mypackage", |
|
|||
110 | # packages=["foo", "bar"], |
|
|||
111 | #)) |
|
|||
112 |
|
36 | |||
113 | # Discover Python files from a virtualenv and add them to our embedded |
|
37 | templates = glob( | |
114 | # context. |
|
38 | include = [ROOT + "/mercurial/templates/**/*"], | |
115 | #exe.add_in_memory_python_resources(dist.read_virtualenv(path="/path/to/venv")) |
|
39 | strip_prefix = ROOT + "/mercurial/", | |
|
40 | ) | |||
|
41 | m.add_manifest(templates) | |||
116 |
|
42 | |||
117 | # Filter all resources collected so far through a filter of names |
|
43 | return m | |
118 | # in a file. |
|
|||
119 | #exe.filter_from_files(files=["/path/to/filter-file"])) |
|
|||
120 |
|
||||
121 | # Return our `PythonExecutable` instance so it can be built and |
|
|||
122 | # referenced by other consumers of this target. |
|
|||
123 | return exe |
|
|||
124 |
|
44 | |||
125 | def make_embedded_resources(exe): |
|
45 | def make_embedded_resources(exe): | |
126 | return exe.to_embedded_resources() |
|
46 | return exe.to_embedded_resources() | |
127 |
|
47 | |||
128 | def make_install(exe): |
|
48 | register_target("exe", make_exe) | |
129 | # Create an object that represents our installed application file layout. |
|
49 | register_target("app", make_install, depends = ["exe"], default = True) | |
130 | files = FileManifest() |
|
50 | register_target("embedded", make_embedded_resources, depends = ["exe"], default_build_script = True) | |
131 |
|
||||
132 | # Add the generated executable to our install layout in the root directory. |
|
|||
133 | files.add_python_resource(".", exe) |
|
|||
134 |
|
||||
135 | return files |
|
|||
136 |
|
||||
137 | # Tell PyOxidizer about the build targets defined above. |
|
|||
138 | register_target("dist", make_dist) |
|
|||
139 | register_target("exe", make_exe, depends = ["dist"], default = True) |
|
|||
140 | register_target("resources", make_embedded_resources, depends = ["exe"], default_build_script = True) |
|
|||
141 | register_target("install", make_install, depends = ["exe"]) |
|
|||
142 |
|
||||
143 | # Resolve whatever targets the invoker of this configuration file is requesting |
|
|||
144 | # be resolved. |
|
|||
145 | resolve_targets() |
|
51 | resolve_targets() | |
146 |
|
52 | |||
147 | # END OF COMMON USER-ADJUSTED SETTINGS. |
|
53 | # END OF COMMON USER-ADJUSTED SETTINGS. |
General Comments 0
You need to be logged in to leave comments.
Login now