use pyinstaller to package executable on windows 10


Guide

install

pip3 install pywin32 pyinstaller
pip3 install --upgrade setuptools

pyinstall -F demo.py error

pyinstaller AttributeError: 'str' object has no attribute 'items'

solution:

pip3 install --upgrade setuptools

usage

pyinstaller -h 

params:

  • General Options

    -y, –noconfirm

      Replace output directory (default: SPECPATH/dist/SPECNAME) 
      without asking for confirmation
    

    –upx-dir UPX_DIR

      Path to UPX utility (default: search the execution path)
    

    –clean

      Clean PyInstaller cache and remove temporary files before building.
    

    –log-level LEVEL

      Amount of detail in build-time console messages. LEVEL may be one of 
      TRACE, DEBUG, INFO, WARN, ERROR, CRITICAL (default: INFO).
    
  • What to generate

    -D, --onedir    
        Create a one-folder bundle containing an executable (default)
    
    -F, --onefile    
        Create a one-file bundled executable.
    
  • What to bundle, where to search

    --add-data <SRC;DEST or SRC:DEST>  
        This option can be used multiple times.
    
    --add-binary <SRC;DEST or SRC:DEST> 
        This option can be used multiple times.
    
    -p DIR, --paths DIR 
        A path to search for imports (like using PYTHONPATH). 
        Multiple paths are allowed, separated by ‘:’, or use 
        this option multiple times
    
    --hidden-import MODULENAME, --hiddenimport MODULENAME 
        Name an import not visible in the code of the script(s). 
        This option can be used multiple times.
    
  • Windows and Mac OS X specific options

    -c, --console, --nowindowed
        Open a console window for standard i/o (default)
    -w, --windowed, --noconsole
        Windows and Mac OS X: do not provide a console window 
        for standard i/o
    

Example

use pyd

path related source code

sys.path.append('./sdk/superdog/')
import superdog # superdog.pyd

pyinstaller commands

pyinstaller -y -D --path="sdk/superdog" demo.py
# pyinstaller -y -D --add-binary './sdk/superdog/superdog.pyd;.' 
# --add-binary './sdk/superdog/dog_windows_x64.dll;.' demo.py

generate build and dist folder, plus demo.spec

output

78 INFO: Extending PYTHONPATH with paths
['E:\\git\\python\\helloworld',
 'E:\\git\\python\\helloworld\\sdk\\superdog',
 'E:\\git\\python\\helloworld']

demo.spec

# -*- mode: python -*-

block_cipher = None


a = Analysis(['demo.py'],
             pathex=['sdk/superdog', 'E:\\git\\python\\helloworld'],
             binaries=[],
             datas=[],
             hiddenimports=[],
             hookspath=[],
             runtime_hooks=[],
             excludes=[],
             win_no_prefer_redirects=False,
             win_private_assemblies=False,
             cipher=block_cipher,
             noarchive=False)
pyz = PYZ(a.pure, a.zipped_data,
             cipher=block_cipher)
exe = EXE(pyz,
          a.scripts,
          [],
          exclude_binaries=True,
          name='demo',
          debug=False,
          bootloader_ignore_signals=False,
          strip=False,
          upx=True,
          console=True )
coll = COLLECT(exe,
               a.binaries,
               a.zipfiles,
               a.datas,
               strip=False,
               upx=True,
               name='demo')

view build/demo/xref-demo.html

superdog found.

Tips:
if we use pyinstaller -y -D demo.py don’t include --path="sdk/superdog",
package will be missing, and error occur when we run executable.

run executable

cd dist/demo
./demo.exe
# OK

all related dlls have been copied to dist/demo/ folder,
eg. cublas64_80.dll,curand64_80.dll,cudart64_80.dll,cudnn64_6.dll

use ctype DLLs

path related source code

sys.path.append('./sdk/superdog/')
sys.path.append('./sdk/detect/')
import superdog # superdog.pyd
import detect # detect.py cuda80_detect.dll cuda90_detect.dll

CDLL related source code

lib = CDLL("./sdk/detect/cuda80_detect_cpp_dll.dll", RTLD_GLOBAL)

pyinstaller commands

pyinstaller -y -D --path="sdk/superdog;sdk/detect" demo.py

warning

93 INFO: Extending PYTHONPATH with paths
['E:\\git\\python\\helloworld',
 'E:\\git\\python\\helloworld\\sdk\\superdog',
 'E:\\git\\python\\helloworld\\sdk\\detect',
 'E:\\git\\python\\helloworld']
  ...
WARNING: Ignoring ./sdk/detect/cuda80_detect_cpp_dll.dll imported
    from E:\git\python\helloworld\sdk\detect\detect.py -
    ctypes imports are only supported using bare filenames

fix

#lib = CDLL("./sdk/detect/cuda80_detect_cpp_dll.dll", RTLD_GLOBAL)
lib = CDLL("cuda80_detect_cpp_dll.dll", RTLD_GLOBAL)

Tips: for CDLL with pyinstaller, we must use bare filenames in python source.
see here

now we run pyintaller again

pyinstaller -y -D --path="sdk/superdog;sdk/detect" demo.py

view build/demo/xref-demo.html

superdog and detect.

run executable

cd dist/demo
./demo.exe
# OK

all related dlls have been copied to dist/demo/ folder,
eg. cublas64_80.dll,curand64_80.dll,cudart64_80.dll,cudnn64_6.dll
and cuda80_detect_cpp_dll.dll

Version

get version

python grab_version.py "/path/to/xxx.exe"

C:\Users\zunli\AppData\Local\Programs\Python\Python35\Lib\site-packages\
PyInstaller\utils\cliutils\grab_version.py

qq version info

file_version_info.txt

# UTF-8
#
# For more details about fixed file info 'ffi' see:
# http://msdn.microsoft.com/en-us/library/ms646997.aspx
VSVersionInfo(
  ffi=FixedFileInfo(
    # filevers and prodvers should be always a tuple with four items: (1, 2, 3, 4)
    # Set not needed items to zero 0.
    filevers=(9, 0, 4, 23780),
    prodvers=(9, 0, 4, 23780),
    # Contains a bitmask that specifies the valid bits 'flags'r
    mask=0x3f,
    # Contains a bitmask that specifies the Boolean attributes of the file.
    flags=0x0,
    # The operating system for which this file was designed.
    # 0x4 - NT and there is no need to change it.
    OS=0x40004,
    # The general type of file.
    # 0x1 - the file is an application.
    fileType=0x1,
    # The function of the file.
    # 0x0 - the function is not defined for this fileType
    subtype=0x0,
    # Creation date and time stamp.
    date=(0, 0)
    ),
  kids=[
    StringFileInfo(
      [
      StringTable(
        u'080404b0',
        [StringStruct(u'CompanyName', u'Tencent'),
        StringStruct(u'FileDescription', u'腾讯QQ'),
        StringStruct(u'FileVersion', u'9.0.4.23780'),
        StringStruct(u'LegalCopyright', u'Copyright (C) 1999-2018 Tencent. All Rights Reserved'),
        StringStruct(u'ProductName', u'腾讯QQ'),
        StringStruct(u'ProductVersion', u'9.0.4.23780')])
      ]), 
    VarFileInfo([VarStruct(u'Translation', [2052, 1200])])
  ]
)

set version

python pyinstaller.py \
--version-file=file_version_info.txt \
--icon=ico.ico \
-y -D \
--path="sdk/superdog;sdk/detect" \
demo.py

Advanced

Running PyInstaller with Python optimizations

Tips: be carefull with optimizations.

PyInstaller can be run with Python optimization flags (-O or -OO)
by executing it as a Python module, rather than using the pyinstaller command:

python -O -m PyInstaller -y -D --path="sdk/superdog" demo.py

Or, by explicitly setting the PYTHONOPTIMIZE environment variable to a non-zero value:

# Unix
PYTHONOPTIMIZE=1 pyinstaller myscript.py

# Windows
set PYTHONOPTIMIZE=1 && pyinstaller myscript.py

only import what you need

replace import os with from os import path to reduce final executable size.

Using UPX

see upx

Encrypting Python Bytecode

To encrypt the Python bytecode modules stored in the bundle, pass the --key=key-string argument on the command line.

For this to work, you must have the PyCrypto module installed. The key-string is a string of 16 characters which is used to encrypt each file of Python byte-code before it is stored in the archive inside the executable file.

pip install pycrypto

Other similar tools

Reference

History

  • 20181213: created.

Author: kezunlin
Reprint policy: All articles in this blog are used except for special statements CC BY 4.0 reprint polocy. If reproduced, please indicate source kezunlin !
评论
  TOC