This repository provides an example and a step-by-step guide for cross-compilation of Python project into a standalone ".exe" file for Windows
Below you will find sections for how to compile and run your code on Ubuntu, as well as useful warnings and tips about potential problems that can occur
You can compile the code either via docker (preferable), or via manual installation of wine on your PC
-
Install docker and docker compose (f. e. this official tutorial)
-
Prepare your project for compilation. There are three paths here, depending on what you need:
-
only run the example without thinking:
Just skip this step in this case
-
play with the example / copy your source code into current folder:
-
modify
project/__version__.txtfile according to your preferred python version in full form (3.12.10, not 3.12) -
modify
project/requirements.txtfile with required libraries and their versions -
put all the source files into a
project/folder withproject/main.pyas an entry point
-
-
compile your code located in another directory:
-
create
/path/to/your/project/__version__.txtfile with preferred python version in full form (3.12.10, not 3.12) -
create
/path/to/your/project/requirements.txtfile with required libraries and their versions -
make sure
/path/to/your/project/main.pyis the entry point for your entire application -
modify the
docker/docker-compose.ymlfile, changing the line- ../project:/app/project:ro(inservices.windows-builder.volumes) to- /path/to/your/project:/app/project:ro
-
-
-
If this is your first time using this repository, use
./run.shto build the initial docker image and install basic components. Running this script for the first time may take 5-10 minutes even on high-end hardware and high-speed internet connections. After few minutes have passed, the docker image itself should launch, and you will be prompted to install some of the following:-
Wine Mono Installer
It is a small window with two buttons: Cancel and Install. Press Install and wait for the download to end
-
Visual Studio
I didn't get the prompt last time I checked, in case you are prompted to do something - please open an issue / PR and describe the steps you were prompted to take
-
Python
Check both checkboxes ("Use admin priviliges ..." and "Add python.exe to PATH"), then click "Install Now", after installation is finished click "Close"
-
-
Compile the application. There are several options, depending on what you need:
-
only run the example without thinking:
Run
./run.sh -
compile the project into a single .exe file, with all of the dependencies packaged inside:
Run
./run.sh MODE=compile-onefile -
compile the project into a single directory, containing both
.exefile as well as some dependencies (faster to execute, but more clumsier):Run
./run.sh MODE=compile-onedir -
if you want to compile and pass some custom arguments directly to the pyinstaller:
Run something like
./run.sh MODE=compile PYINSTALLERARGS="--onefile --nowindowed"(you can of course modify the PYINSTALLERARGS argument)
-
-
Run the application:
-
If you compiled the example directly, use
./run.sh MODE=run-onefile MAINARGS="path_to_stl=a.stl target_folder=b" -
If you compiled via "onefile" mode:
./run.sh MODE=run-onefile -
If you compile via "onedir" mode:
./run.sh MODE=run-onedir -
You can also add arguments to the running script. For example, if you compiled via "onefile" and want to run ".\main.exe A=123 B=qwe", then use:
./run.sh MODE=run-onefile MAINARGS="A=123 B=qwe"
-
- Add wine repository:
dpkg --add-architecture i386 && \
mkdir -pm755 /etc/apt/keyrings && \
wget -O - https://dl.winehq.org/wine-builds/winehq.key | gpg --dearmor -o /etc/apt/keyrings/winehq-archive.key - && \
wget -NP /etc/apt/sources.list.d/ https://dl.winehq.org/wine-builds/ubuntu/dists/noble/winehq-noble.sources;Install wine:
sudo apt install --install-recommends winehq-stable
sudo apt install winetricks
winetricks vcrun2022
winetricks -q win10- Download the python installer https://www.python.org/downloads/release/python-31210/
wget https://www.python.org/ftp/python/3.12.10/python-3.12.10-amd64.exe- Install python on wine
wine ./python-3.12.10-amd64.exe
# (check both "use admin priviliges" and "add to Path") when promptedand pyinstaller
wine pip install pyinstaller-
Put all source files into
project/folder withmain.pyas an entry point -
Set an env variable for convenience (it's a path to main.py, but how it's visible from wine):
CROSS_PROJECT=$(echo "Z:"$(pwd)"/project/main.py" | tr / \\)- Run compilation:
install required libraries
wine pip install numpy==2.3.3
wine pip install torch==2.8.0 --index-url https://download.pytorch.org/whl/cpuenter build directory
mkdir build && cd build- Either to one exe file:
wine pyinstaller --onefile "$CROSS_PROJECT"- Or to a single folder:
wine pyinstaller --onefolder "$CROSS_PROJECT"- Test:
cd dist/
wine main.exe path_to_stl=test.stl target_folder=./output- If you are using some additional resources, pass them via PYINSTALLERARGS="--add-data=Z:\app\project\somefolder\somefile.txt:folder\to\store" and use from your code:
import os
def get_resource_path(relative_path):
"""Get absolute path to resource, works for dev and for PyInstaller"""
try:
# PyInstaller creates a temp folder and stores path in _MEIPASS
base_path = sys._MEIPASS
except Exception:
base_path = os.path.abspath(".")
return os.path.join(base_path, relative_path)
open(get_resource_path("a/b/c.txt"))-
If you are using numpy, you should put version <=2.2.1 because 2.2.2+ is broken for current wine (v10.0)
-
pyinstaller doesn't seem to support multithreading. If you or library you imported uses it, you should disable it by putting the following lines in the very beginning of main.py, even before any other import:
from multiprocessing import Process, freeze_support
if __name__ == '__main__':
freeze_support()
# import something
# ... do other stuff ...However, this may not be sufficient especially when trying to launch exe with WINE. So before calling "wine main.exe", export this env variables:
export OMP_NUM_THREADS=1
export OMP_WAIT_POLICY=PASSIVE
export KMP_BLOCKTIME=0
export KMP_AFFINITY=disabled
export MKL_ENABLE_INSTRUCTIONS=AVX2
export MKL_DYNAMIC=FALSEAlso, if needed, look at this SO question
- in general, consult official pyinstaller FAQ section in case of some problems
-
use proper windows docker base image: https://hub.docker.com/r/microsoft/windows-servercore/
-
use nuitka instead of pyinstaller: https://habr.com/ru/articles/838480/