Getting Started¶
Installing¶
PyOxidizer is a Rust application and requires Rust 1.33+ to be installed in order to build binaries. If you don’t have Rust installed, https://www.rust-lang.org/ has very detailed instructions on how to install it.
PyOxidizer can be installed from its latest published crate:
$ cargo install pyoxidizer
From a Git repository using cargo:
# The latest commit in source control.
$ cargo install --git https://github.com/indygreg/PyOxidizer.git --branch master pyoxidizer
$ A specific release
$ cargo install --git https://github.com/indygreg/PyOxidizer.git --tag <TAG> pyoxidizer
Or by cloning the Git repository and building the project locally:
$ git clone https://github.com/indygreg/PyOxidizer.git
$ cd PyOxidizer
$ cargo install --path pyoxidizer
Once the pyoxidizer executable is installed, try to run it:
$ pyoxidizer
PyOxidizer 0.1
Gregory Szorc <gregory.szorc@gmail.com>
Build and distribute Python applications
USAGE:
    pyoxidizer [SUBCOMMAND]
...
Congratulations, PyOxidizer is installed! Now let’s move on to using it.
Creating a PyOxidizer Project¶
The pyoxidizer init command will create a new [Rust] project which supports
embedding Python. Invoke it with the directory you want to create your new
project in:
$ pyoxidizer init pyapp
This should have printed out details on what happened and what to do next. If you actually ran this in a terminal, hopefully you don’t need to continue following the directions here as the printed instructions are sufficient! But if you aren’t, keep reading.
The default project created by pyoxidizer init will produce an
executable that embeds Python and starts a Python REPL by default. Let’s
test that:
$ cd pyapp
$ pyoxidizer run
no existing PyOxidizer artifacts found
processing config file /home/gps/src/pyapp/pyoxidizer.toml
resolving Python distribution...
...
   Compiling pyapp v0.1.0 (/home/gps/src/pyapp)
    Finished dev [unoptimized + debuginfo] target(s) in 53.14s
     Running `target/debug/testapp`
>>>
If all goes according to plan, you just started a Rust executable which started a Python interpreter, which started an interactive Python debugger! Try typing in some Python code:
>>> print("hello, world")
hello, world
It works!
(To exit the REPL, press CTRL+d or CTRL+z.)
Adding PyOxidizer to an Existing Project¶
Do you have an existing Rust project that you want to add an embedded
Python interpreter to? PyOxidizer can help with that too! The
pyoxidizer add command can be used to add an embedded Python
interpreter to an existing Rust project. Simply give the directory
to a project containing a Cargo.toml file:
$ cargo init myrustapp
  Created binary (application) package
$ pyoxidizer add myrustapp
This will add required files and make required modifications to add
an embedded Python interpreter to the target project. Most of the
modifications are in the form of a new pyembed crate.
Important
It is highly recommended to have the destination project under version
control so you can see what changes are made by pyoxidizer add and
so you can undo any unwanted changes.
Danger
This command isn’t very well tested. And results have been known to be
wrong. If it doesn’t just work, you may want to run pyoxidizer init
and incorporate relevant files into your project manually. Sorry for
the inconvenience.
Customizing Python and Packaging Behavior¶
Embedding Python in a Rust executable and starting a REPL is cool and all. But you probably want to do something more exciting.
Inside the project’s root directory is an autogenerated pyoxidizer.toml
file. This file configures how the embedded Python interpreter is built as
well as defines default run-time behavior for that interpreter.
Open that file in your favorite editor and find the [[python_run]]
section. This section configures what to do when the interpreter starts.
By default, it should have a mode = "repl" line. Let’s comment that out
or delete it and replace it with the following:
[[embedded_python_run]]
mode = "eval"
code = "import uuid; print(uuid.uuid4())"
We’re now telling the interpreter to effectively run the Python statement
eval(code) when it starts. Test that out:
$ pyoxidizer run
   Compiling pyembed v0.1.0 (/home/gps/src/pyapp/pyembed)
   Compiling pyapp v0.1.0 (/home/gps/src/pyapp)
    Finished dev [unoptimized + debuginfo] target(s) in 3.92s
     Running `target/debug/pyapp`
96f776c8-c32d-48d8-8c1c-aef8a735f535
It works!
This is still pretty trivial. But it demonstrates how the pyoxidizer.toml
is used to influence the behavior of built binaries.
Let’s do something a little bit more complicated, like package an existing Python application!
Find the existing [[python_packages]] section in the pyoxidizer.toml.
Now let’s add the following lines after the last of those sections:
[[packaging_rule]]
type = "pip-install-simple"
package = "pyflakes==2.1.1"
And change the [[embedded_python_run]] section to:
[[embedded_python_run]]
mode = "eval"
code = "from pyflakes.api import main; main()"
This tells PyOxidizer that you want to install version 2.1.1 of the pyflakes
package. At build time, this will effectively perform a
pip install pyflakes==2.1.1 and take all installed files and add them to the
produced binary. Let’s try that:
$ pyoxidizer run -- --help
   Compiling pyembed v0.1.0 (/home/gps/tmp/pyapp/pyembed)
   Compiling pyapp v0.1.0 (/home/gps/tmp/pyapp)
    Finished dev [unoptimized + debuginfo] target(s) in 5.49s
     Running `target/debug/pyapp --help`
Usage: pyapp [options]
Options:
  --version   show program's version number and exit
  -h, --help  show this help message and exit
You’ve just produced an executable for pyflakes!
There are far more powerful packaging and configuration settings available. Read all about them at Configuration Files.