Can the interpreter be chosen at runtime?
Is it possible to specify the interpreter to use, i.e., its path, at runtime?
Something like:
use std::env;
use cpython::...
fn main() {
let path = env::current_exe().unwrap();
let interpreter = path.with_file_name(r"python\pythonw.exe");
Python::set_interpreter(interpreter).unwrap();
// use cpython as normal
}
The reason for this is if I'm distributing a rust & python app I want to include a python subdir and have rust use that python (with all the extra dependencies I need) rather than any that might happen to be on the system. And, of course, this means that my users don't need to have their own python (e.g., on windows).
I'm going to assume you're embedding Python rather than writing a library to be imported by Python.
rust-cpython links against Python as a library in that case. I don't think Python installs its DLLs system-wide, so you probably have to include the requisite Python DLL(s) with your Rust project.
Your assumption is correct. Given a source tree of myapp/ myapp/src/main.rs where does python3.dll go? and where does python's libs dir go? Idealy I'd like: myapp/target/release/python/ # python3.dll + libs/ So that I can ship release/myapp.exe + release/python/**
Given a source tree of myapp/ myapp/src/main.rs where does python3.dll go?
I've never compiled any Rust binaries with dynamic dependencies for Windows, but I think it's safe to assume that Rust+rust-cpython just relies on the OS's dynamic loader. For Windows, that means that any bundled DLLs will need to be in the same folder as the EXE.
(eg. myapp/target/debug/python3.dll or myapp/target/release/python3.dll when using cargo run, assuming that python3.dll actually is the name it's looking for. I'd expect it to include the minor version, for a name like python36.dll, from what I remember of my time on Windows.)
and where does python's libs dir go?
That's determined by the Python import path (sys.path) which, by default with rust-cpython, doesn't include anything specific to your project. It's your job to add an entry.
Here's some Rust code which will show you the default value of sys.path when running Python code via rust-cpython:
use cpython::Python;
fn main() {
let gil = Python::acquire_gil();
let py = gil.python();
let sys = py.import("sys").expect("Cannot import sys");
let path: Vec<String> = sys
.get(py, "path").expect("Cannot get sys.path")
.extract(py).expect("Cannot extract sys.path value");
println!("{:?}", path);
}
They resolved the potential cyclic dependency between the import path and the sys module by having it built into the DLL, as evidenced by this interpreter session:
>>> import sys
>>> sys
<module 'sys' (built-in)>
As with PATH, Python checks things in sys.path in the order you provide them, so you'll want to insert the path to your bundled imports at the beginning.
(It'd make sense if relative paths are resolved relative to the EXE, rather than the current working directory, for consistency with how import works relative to the .py file containing it, but I've never actually confirmed that since I've always preferred to write compiled extensions for Python applications rather than embedding the Python runtime in a Rust application. You'll want to verify that.)
Another option you might find useful is to include some of your Python code inside the EXE. That'd be done by using include_str! to load a .py file into a const at compile time, then using PyModule::new to create an empty module and using that empty module as the globals value when you feed the const full of code to Python::run.
You might be interested in https://pyoxidizer.readthedocs.io/en/stable/ for embedding a Python interpreter in Rust binaries. It aims to solve a lot of these hard packaging problems. You can also look at the source code for the pyembed crate in indygreg/PyOxidizer, which is the component of PyOxidizer interfacing with the Python interpreter. It demonstrates how you can use rust-cpython to manage an embedded Python interpreter.