Friday, 19 August 2011

How to create Windows executable from Python script

After some online research I decided to give PyInstaller a try. It converts Python scripts into executable Windows programs, able to run without requiring a Python installation on a target machine. PyInstaller works under any version of Python from 2.2 up to 2.7 (it still does not support Python 3) so I installed the latest Python 2 release - 2.7.2.

NOTE: Make sure Python installation path (e.g. in C:\Python27) has been added to PATH environmental variable.

PyInstaller requires PyWin32 to be installed for Python versions 2.6+. It is a Python extension for Microsoft Windows which provides access to the Win32 API, COM and Pythonwin environment. If you try to perform initial PyInstaller configuration without it, a following error is reported:

..\pyinstaller-1.5.1>python Configure.py
ERROR: Python 2.6+ on Windows support needs pywin32
Please install http://sourceforge.net/projects/pywin32/




I picked installer for Python 2.7 - the version I will be using. PyInstaller configuration now runs with no problems:

..\pyinstaller-1.5.1>python Configure.py
I: computing EXE_dependencies
I: Finding TCL/TK...
W: library coredll.dll required via ctypes not found
I: Analyzing c:\Python27\DLLs\_tkinter.pyd
I: Adding tcl85.dll dependency of _tkinter.pyd
I: Adding tk85.dll dependency of _tkinter.pyd
I: Skipping KERNEL32.dll dependency of _tkinter.pyd
I: Adding python27.dll dependency of _tkinter.pyd
I: Skipping MSVCR90.dll dependency of _tkinter.pyd
I: Analyzing c:\Python27\DLLs\_hashlib.pyd
I: Skipping KERNEL32.dll dependency of _hashlib.pyd
I: Skipping USER32.dll dependency of _hashlib.pyd
I: Skipping ADVAPI32.dll dependency of _hashlib.pyd
I: Skipping MSVCR90.dll dependency of _hashlib.pyd
I: Analyzing c:\Python27\DLLs\_ctypes.pyd
I: Skipping KERNEL32.dll dependency of _ctypes.pyd
I: Skipping ole32.dll dependency of _ctypes.pyd
I: Skipping OLEAUT32.dll dependency of _ctypes.pyd
I: Skipping MSVCR90.dll dependency of _ctypes.pyd
I: Analyzing c:\Python27\DLLs\select.pyd
I: Skipping WS2_32.dll dependency of select.pyd
I: Skipping MSVCR90.dll dependency of select.pyd
I: Skipping KERNEL32.dll dependency of select.pyd
I: Analyzing c:\Python27\DLLs\unicodedata.pyd
I: Skipping MSVCR90.dll dependency of unicodedata.pyd
I: Skipping KERNEL32.dll dependency of unicodedata.pyd
I: Analyzing c:\Python27\DLLs\bz2.pyd
I: Skipping MSVCR90.dll dependency of bz2.pyd
I: Skipping KERNEL32.dll dependency of bz2.pyd
I: Analyzing c:\Python27\DLLs\tcl85.dll
I: Dependent assemblies of c:\Python27\DLLs\tcl85.dll:
I: x86_Microsoft.VC90.CRT_1fc8b3b9a1e18e3b_9.0.21022.8_none
I: Searching for assembly x86_Microsoft.VC90.CRT_1fc8b3b9a1e18e3b_9.0.21022.8_no
ne...
I: Found manifest C:\Windows\WinSxS\Manifests\x86_microsoft.vc90.crt_1fc8b3b9a1e
18e3b_9.0.21022.8_none_bcb86ed6ac711f91.manifest
I: Searching for file msvcr90.dll
I: Found file C:\Windows\WinSxS\x86_microsoft.vc90.crt_1fc8b3b9a1e18e3b_9.0.2102
2.8_none_bcb86ed6ac711f91\msvcr90.dll
I: Searching for file msvcp90.dll
I: Found file C:\Windows\WinSxS\x86_microsoft.vc90.crt_1fc8b3b9a1e18e3b_9.0.2102
2.8_none_bcb86ed6ac711f91\msvcp90.dll
I: Searching for file msvcm90.dll
I: Found file C:\Windows\WinSxS\x86_microsoft.vc90.crt_1fc8b3b9a1e18e3b_9.0.2102
2.8_none_bcb86ed6ac711f91\msvcm90.dll
I: Adding Microsoft.VC90.CRT.manifest
I: Adding msvcr90.dll
I: Adding msvcp90.dll
I: Adding msvcm90.dll
I: Skipping KERNEL32.dll dependency of tcl85.dll
I: Skipping USER32.dll dependency of tcl85.dll
I: Skipping WS2_32.dll dependency of tcl85.dll
I: Skipping ADVAPI32.dll dependency of tcl85.dll
I: Analyzing c:\Python27\DLLs\tk85.dll
I: Dependent assemblies of c:\Python27\DLLs\tk85.dll:
I: X86_Microsoft.Windows.Common-Controls_6595b64144ccf1df_6.0.0.0_none
I: Dependent assemblies of c:\Python27\DLLs\tk85.dll:
I: x86_Microsoft.VC90.CRT_1fc8b3b9a1e18e3b_9.0.21022.8_none
I: Skipping assembly X86_Microsoft.Windows.Common-Controls_6595b64144ccf1df_6.0.
0.0_none
I: Skipping KERNEL32.dll dependency of tk85.dll
I: Skipping USER32.dll dependency of tk85.dll
I: Skipping GDI32.dll dependency of tk85.dll
I: Skipping COMDLG32.dll dependency of tk85.dll
I: Skipping SHELL32.dll dependency of tk85.dll
I: Skipping ole32.dll dependency of tk85.dll
I: Skipping OLEAUT32.dll dependency of tk85.dll
I: Skipping COMCTL32.dll dependency of tk85.dll
I: Skipping ADVAPI32.dll dependency of tk85.dll
I: Skipping IMM32.dll dependency of tk85.dll
I: Analyzing C:\Windows\system32\python27.dll
I: Dependent assemblies of C:\Windows\system32\python27.dll:
I: x86_Microsoft.VC90.CRT_1fc8b3b9a1e18e3b_9.0.21022.8_none
I: Skipping KERNEL32.dll dependency of python27.dll
I: Skipping USER32.dll dependency of python27.dll
I: Skipping ADVAPI32.dll dependency of python27.dll
I: Skipping SHELL32.dll dependency of python27.dll
I: Analyzing c:\Python27\python.exe
I: Dependent assemblies of c:\Python27\python.exe:
I: x86_Microsoft.VC90.CRT_1fc8b3b9a1e18e3b_9.0.21022.8_none
I: Skipping KERNEL32.dll dependency of python.exe
I: found TCL/TK version 8.5
I: found TCL/TK version 8.5
I: could not find TCL/TK
I: testing for Zlib...
I: ... Zlib available
I: Testing for ability to set icons, version resources...
I: ... resource update available
I: Testing for Unicode support...
I: ... Unicode available
I: testing for UPX...
I: ...UPX unavailable
I: computing PYZ dependencies...
I: done generating config.dat





Let us try now to create a binary example.exe from a script example.py, which just prints "Hello World" to the console window. It is a two-step process. The first step is generating a spec file. Makespec creates it and places it in a directory named after the script:

..\pyinstaller-1.5.1>python Makespec.py "J:\Users\Bojan\wor
kspace\Test1\src\root\nested\example.py"
wrote ..\pyinstaller-1.5.1\example\example.spec
now run Build.py to build the executable

If we follow instruction given above, making sure we are providing correct (relative) path to spec file, Build creates executable:

..\pyinstaller-1.5.1>python Build.py example\example.spec
I: Dependent assemblies of c:\Python27\python.exe:
I: x86_Microsoft.VC90.CRT_1fc8b3b9a1e18e3b_9.0.21022.8_none
checking Analysis
building Analysis because outAnalysis0.toc non existent
running Analysis outAnalysis0.toc
Analyzing: support\_mountzlib.py
Analyzing: support\useUnicode.py
Analyzing: C:\Users\Bojan\workspace\Test1\src\root\nested\example.py
I: Analyzing c:\Python27\python.exe
I: Dependent assemblies of c:\Python27\python.exe:
I: x86_Microsoft.VC90.CRT_1fc8b3b9a1e18e3b_9.0.21022.8_none
Adding Microsoft.VC90.CRT to dependent assemblies of final executable
I: Searching for assembly x86_Microsoft.VC90.CRT_1fc8b3b9a1e18e3b_9.0.21022.8_no
ne...
I: Found manifest C:\Windows\WinSxS\Manifests\x86_microsoft.vc90.crt_1fc8b3b9a1e
18e3b_9.0.21022.8_none_bcb86ed6ac711f91.manifest
I: Searching for file msvcr90.dll
I: Found file C:\Windows\WinSxS\x86_microsoft.vc90.crt_1fc8b3b9a1e18e3b_9.0.2102
2.8_none_bcb86ed6ac711f91\msvcr90.dll
I: Searching for file msvcp90.dll
I: Found file C:\Windows\WinSxS\x86_microsoft.vc90.crt_1fc8b3b9a1e18e3b_9.0.2102
2.8_none_bcb86ed6ac711f91\msvcp90.dll
I: Searching for file msvcm90.dll
I: Found file C:\Windows\WinSxS\x86_microsoft.vc90.crt_1fc8b3b9a1e18e3b_9.0.2102
2.8_none_bcb86ed6ac711f91\msvcm90.dll
I: Adding Microsoft.VC90.CRT.manifest
I: Adding msvcr90.dll
I: Adding msvcp90.dll
I: Adding msvcm90.dll
I: Adding python27.dll dependency of python.exe
I: Skipping KERNEL32.dll dependency of python.exe
I: Analyzing C:\Windows\system32\python27.dll
I: Dependent assemblies of C:\Windows\system32\python27.dll:
I: x86_Microsoft.VC90.CRT_1fc8b3b9a1e18e3b_9.0.21022.8_none
I: Skipping KERNEL32.dll dependency of python27.dll
I: Skipping USER32.dll dependency of python27.dll
I: Skipping ADVAPI32.dll dependency of python27.dll
I: Skipping SHELL32.dll dependency of python27.dll
I: Analyzing c:\Python27\DLLs\_hashlib.pyd
I: Skipping KERNEL32.dll dependency of _hashlib.pyd
I: Skipping USER32.dll dependency of _hashlib.pyd
I: Skipping ADVAPI32.dll dependency of _hashlib.pyd
I: Analyzing c:\Python27\lib\site-packages\win32\win32api.pyd
I: Skipping USER32.dll dependency of win32api.pyd
I: Skipping ADVAPI32.dll dependency of win32api.pyd
I: Skipping SHELL32.dll dependency of win32api.pyd
I: Skipping VERSION.dll dependency of win32api.pyd
I: Skipping POWRPROF.dll dependency of win32api.pyd
I: Adding pywintypes27.dll dependency of win32api.pyd
I: Skipping KERNEL32.dll dependency of win32api.pyd
I: Analyzing c:\Python27\DLLs\select.pyd
I: Skipping WS2_32.dll dependency of select.pyd
I: Skipping KERNEL32.dll dependency of select.pyd
I: Analyzing c:\Python27\DLLs\unicodedata.pyd
I: Skipping KERNEL32.dll dependency of unicodedata.pyd
I: Analyzing c:\Python27\DLLs\bz2.pyd
I: Skipping KERNEL32.dll dependency of bz2.pyd
I: Analyzing C:\Windows\system32\pywintypes27.dll
I: Skipping ADVAPI32.dll dependency of pywintypes27.dll
I: Skipping USER32.dll dependency of pywintypes27.dll
I: Skipping ole32.dll dependency of pywintypes27.dll
I: Skipping OLEAUT32.dll dependency of pywintypes27.dll
I: Skipping KERNEL32.dll dependency of pywintypes27.dll
Warnings written to example\warnexample.txt
checking PYZ
rebuilding outPYZ1.toc because outPYZ1.pyz is missing
building PYZ outPYZ1.toc
checking PKG
rebuilding outPKG3.toc because outPKG3.pkg is missing
building PKG outPKG3.pkg
checking EXE
rebuilding outEXE2.toc because example.exe missing
building EXE from outEXE2.toc
Appending archive to EXE example\dist\example.exe

Binary has been created and placed in dist directory. We can run it:

..\pyinstaller-1.5.1\example\dist>example
Hello World

Basically, dist directory contains everything that should be distributed. By default, --onedir option is applied and PyInstaller creates a distribution that contains executable and other files it depends on. For example, distribution can contain following files:

..\example\dist\example\
msvcm90.dll
msvcp90.dll
msvcr90.dll
python27.dll
pywintypes27.dll
example.exe
example.exe.manifest
Microsoft.VC90.CRT.manifest
_hashlib.pyd
_socket.pyd
_ssl.pyd
bz2.pyd
pyexpat.pyd
select.pyd
unicodedata.pyd
win32api.pyd

If we want to have everything within a single binary, we need to apply --onefile option for Makespec.py. Build output is only a single file:

..\example\dist\example\example.exe

This single file is actually a self-extracting executable (bootloader) which contains all shared libraries and other helper files. When run, this executable extracts the following files into temporary _MEIXXXXX directory in user's Temp directory:

C:\Users\Bojan\AppData\Local\Temp\_MEI16522\
msvcm90.dll
msvcp90.dll
msvcr90.dll
python27.dll
pywintypes27.dll
copyupdate.exe.manifest
Microsoft.VC90.CRT.manifest
_hashlib.pyd
_socket.pyd
_ssl.pyd
bz2.pyd
pyexpat.pyd
select.pyd
unicodedata.pyd
win32api.pyd

This directory will be automatically deleted upon program termination.

Binary distributable should have its information embedded so File description, File versionProduct descriptionProduct version and Copyrights can be displayed when user does right-click >> Properties >>Details. This can be achieved by applying --version=PATH option where PATH is a path to the version resource file which can look something like this:

version.txt:

VSVersionInfo(
  ffi=FixedFileInfo(
    filevers=(1, 0, 0, 0),
    prodvers=(1, 0, 0, 0),
    mask=0x3f,
    flags=0x0,
    OS=0x40004,
    fileType=0x1,
    subtype=0x0,
    date=(0, 0)
    ),
  kids=[
    StringFileInfo(
      [
      StringTable(
        '040904B0',
        [StringStruct('FileDescription', 'This is a PyInstaller example application'),
        StringStruct('FileVersion', '1.0.0.0'),
        StringStruct('OriginalFilename', 'example.exe'),
        StringStruct('ProductName', 'example'),
        StringStruct('ProductVersion', '1.0.0.0')])
      ]),
    VarFileInfo([VarStruct('Translation', [1033, 1200])])
  ]
)

We can specify name of the executable by applying --name=NAME option where NAME is the name of the application, without extension (.exe) suffix.

This is one typical build configuration which includes some of options stated above:

..\pyinstaller-1.5.1>Makespec.py --onedir --version="d:\dev\workspace\example\src\version.txt" --name="example" "d:\dev\workspace\example\src\main.py"
wrote ..\pyinstaller-1.5.1\example\example.spec
now run Build.py to build the executable

..\pyinstaller-1.5.1>Build.py example\example.spec
I: Dependent assemblies of c:\Python27\python.exe:
I: x86_Microsoft.VC90.CRT_1fc8b3b9a1e18e3b_9.0.21022.8_none
checking Analysis
building Analysis because outAnalysis0.toc non existent
running Analysis outAnalysis0.toc
...
Appending archive to EXE example\build\pyi.win32\example\example.exe
checking COLLECT
building because outCOLLECT4.toc missing or bad
building COLLECT outCOLLECT4.toc

If you're using PyDev for Eclipse and you needed to add modules' paths to PYTHONPATH (External libraries) then you need to add all those paths to Makespec.py within --paths argument.

Links and references:

PyInstaller Manual

1 comment:

Maxim said...

Maybe it's my eyes, but I find the color choice for the terminal output very hard to read. Thanks for the article.